Add OIDC sign-in, group-derived roles and declarative instance configuration
Workspaces, group mappings and sign-in methods now come from a configuration file synchronized at startup; workspaces dropped from it are disabled, never deleted. Instance administrators (a new role) see and help every workspace and may delete disabled ones; workspace administrators and employees are derived from the user's groups at every SSO sign-in or administrator edit, and the employee permission set belongs to the workspace. Self-registration is gone: users come from SSO (only when their groups map) or from the administrator. Break-glass bootstrap accounts get an HMAC-derived hourly password that all replicas agree on. First SSO sign-in links a matching email contact to the user. The demo runs on the same model with a generated per-start password. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
e37d583179
commit
4764f3545f
68 files changed
+7220
-964
No files matched your search
@@ -1,4 +1,4 @@
|
||||
const {openSetup}=require('./support/browser.cjs');
|
||||
const {openSetup,isolatedUser,signIn}=require('./support/browser.cjs');
|
||||
|
||||
const assert=require('node:assert/strict');
|
||||
const base=process.env.MEGURI_BROWSER_URL || 'http://127.0.0.1:3000';
|
||||
@@ -11,14 +11,13 @@ assert(['localhost','127.0.0.1'].includes(new URL(base).hostname));
|
||||
const save=async()=>{await dialog().getByRole('button',{name:'Save changes',exact:true}).click();await dialog().waitFor({state:'hidden'});};
|
||||
const fmt=iso=>iso.split('-').reverse().join('-');
|
||||
try {
|
||||
const unique=Date.now();await page.goto(base);await page.getByRole('button',{name:'New here? Create an account'}).click();
|
||||
await page.getByLabel('Username',{exact:true}).fill('absence-'+unique);await page.getByLabel('Display name',{exact:true}).fill('Absence tester');await page.getByLabel('Password',{exact:true}).fill('local-browser-test-password');await page.getByRole('button',{name:'Create account',exact:true}).click();
|
||||
await page.getByRole('button',{name:'New workspace',exact:true}).click();await dialog().getByLabel('Name',{exact:true}).fill('Absence '+unique);await save();
|
||||
await page.getByRole('button',{name:'New workspace',exact:true}).click();await dialog().getByLabel('Name',{exact:true}).fill('Absence '+unique);await save();
|
||||
// Sandbox 4 belongs to this suite: an administrator and two substitutes (Bob Chen, Carol Substitute).
|
||||
const tester=await isolatedUser({sandbox:4,prefix:'absence',display:'Absence tester',employees:['Bob Chen','Carol Substitute']});
|
||||
await signIn(page,tester.username,tester.password);
|
||||
await page.getByLabel('Workspace',{exact:true}).waitFor();
|
||||
const dates=await page.evaluate(async()=>{
|
||||
const app=Alpine.$data(document.body);
|
||||
for(const username of ['bob','carol']){await app.api(app.endpoint('/members'),'PUT',{revision:app.workspace.revision,username,permissions:['view','request_overrides']});await app.loadWorkspace();}
|
||||
const bob=app.directory.find(p=>p.username==='bob').id,doc=JSON.parse(JSON.stringify(app.doc)),start=app.addDays(app.today(),2),end=app.addDays(start,13);
|
||||
const bob=app.directory.find(p=>p.display_name==='Bob Chen').id,doc=JSON.parse(JSON.stringify(app.doc)),start=app.addDays(app.today(),2),end=app.addDays(start,13);
|
||||
const rotation=crypto.randomUUID();
|
||||
doc.schedules=[{id:rotation,name:'General',handover:'11:00:00',type:'rotation',participants:[app.user.id],anchor:start,shift_days:7},{id:crypto.randomUUID(),name:'Backup',handover:'09:00:00',type:'rotation',participants:[app.user.id],anchor:'2026-09-21',shift_days:7}];
|
||||
for(let i=1;i<=9;i++)doc.schedules.push({id:crypto.randomUUID(),name:'Project '+i,handover:'11:00:00',type:'coverage',primary:app.user.id,secondary:bob,fallback:rotation});
|
||||
|
||||
Reference in new issue
Block a user