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

+1 -1
View File
@@ -1,7 +1,7 @@
const assert=require('node:assert/strict');
(async()=>{const browser=await require('./support/browser.cjs').launchBrowser();try{
const page=await browser.newPage({viewport:{width:1440,height:1000}}),errors=[];page.on('pageerror',e=>errors.push(e.message));
await page.goto('http://127.0.0.1:3000/?view=configuration');await page.getByLabel('Username',{exact:true}).fill('alice');await page.getByLabel('Password',{exact:true}).fill('meguri-demo-only');await page.getByRole('button',{name:'Sign in',exact:true}).click();
await page.goto('http://127.0.0.1:3000/?view=configuration');await page.getByLabel('Username',{exact:true}).fill('alice');await page.getByLabel('Password',{exact:true}).fill(await require('./support/browser.cjs').demoPassword());await page.getByRole('button',{name:'Sign in',exact:true}).click();
await page.getByRole('button',{name:'Edit rule Project owners during working hours',exact:true}).click();
const panel=page.locator('.rule-result');const ready=()=>page.waitForFunction(()=>{const a=Alpine.$data(document.body);return !a.generationLoading&&a.generationResult;});await ready();
assert.equal(await panel.locator('.rule-result-row').count(),18);const text=await panel.innerText();assert.match(text,/Payments/);assert.match(text,/Alice Morgan/);assert.match(text,/Time off/);assert.match(text,/Escalation/);assert.match(text,/Routing/);await panel.screenshot({path:'test-results/rule-result.png'});