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:
Aleksandr BogomiakovandClaude committed 2026-10-07 13:28:43 +01:00
1 parent e37d583179
commit 4764f3545f
68 files changed
+7220 -964

No files matched your search

+5 -12
View File
@@ -1,4 +1,4 @@
const {openSetup}=require('./support/browser.cjs');
const {openSetup,isolatedUser,signIn}=require('./support/browser.cjs');
// Regression for dependency-aware removal and routing precedence through the real UI/API.
const assert=require('node:assert/strict');
@@ -24,17 +24,10 @@ fs.mkdirSync('test-results',{recursive:true});
assert.deepEqual((await state()).document.routes.map(r=>r.order),names.map((_,i)=>i));
};
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('configuration-'+unique);
await page.getByLabel('Display name',{exact:true}).fill('Configuration 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('Configuration '+unique);await save();
await page.getByRole('button',{name:'New workspace',exact:true}).click();
await dialog().getByLabel('Name',{exact:true}).fill('Configuration '+unique);await save();
// Seed only this test's new team; all mutations under test use visible controls.
const tester=await isolatedUser({sandbox:3,prefix:'configuration',display:'Configuration tester'});
await signIn(page,tester.username,tester.password);
await page.getByLabel('Workspace',{exact:true}).waitFor();
// Seed only this test's emptied workspace; all mutations under test use visible controls.
await page.evaluate(async()=>{
const app=Alpine.$data(document.body),doc=JSON.parse(JSON.stringify(app.doc));
doc.schedules=['Payments · primary','Payments · secondary','Alternate'].map(name=>({id:crypto.randomUUID(),name,handover:'11:00:00',type:'rotation',participants:[app.user.id],anchor:'2026-09-21',shift_days:7}));