feat: add OIDC-only mode to disable password authentication

When OIDC is configured, admins can now enable 'Disable password
authentication' in Admin → Settings → SSO. This blocks all password-
based login and registration, forcing users through the SSO identity
provider instead.

Backend:
- routes/admin.ts: expose oidc_only flag on GET /admin/oidc and accept
  it on PUT /admin/oidc (persisted to app_settings)
- routes/auth.ts: add isOidcOnlyMode() helper; block POST /auth/login,
  POST /auth/register (for non-first-user), and PUT /auth/me/password
  with HTTP 403 when OIDC-only mode is active
- routes/auth.ts: expose oidc_only_mode boolean in GET /auth/app-config

Frontend:
- AdminPage: toggle in OIDC/SSO settings section (oidc_only saved with
  rest of OIDC config on same Save button)
- LoginPage: when oidc_only_mode is active, replace form with a
  single-button OIDC redirect; hide register toggle
- SettingsPage: hide password change section when oidc_only_mode is on
- i18n (en/de): admin.oidcOnlyMode, admin.oidcOnlyModeHint,
  login.oidcOnly
This commit is contained in:
Stephen Wheet
2026-03-28 19:33:18 +00:00
parent 3f26a68f64
commit 9f8d3f8d99
7 changed files with 101 additions and 6 deletions

View File

@@ -122,16 +122,18 @@ router.get('/oidc', (_req: Request, res: Response) => {
client_id: get('oidc_client_id'),
client_secret_set: !!secret,
display_name: get('oidc_display_name'),
oidc_only: get('oidc_only') === 'true',
});
});
router.put('/oidc', (req: Request, res: Response) => {
const { issuer, client_id, client_secret, display_name } = req.body;
const { issuer, client_id, client_secret, display_name, oidc_only } = req.body;
const set = (key: string, val: string) => db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)").run(key, val || '');
set('oidc_issuer', issuer);
set('oidc_client_id', client_id);
if (client_secret !== undefined) set('oidc_client_secret', client_secret);
set('oidc_display_name', display_name);
set('oidc_only', oidc_only ? 'true' : 'false');
res.json({ success: true });
});