feat(center): OIDC login (auto-provision by email) + CLI config-oidc

- OidcConfig entity (single row) + DB registration.
- OidcService: discovery, Authorization Code + PKCE, state/ticket as
  short-lived signed JWTs (no server session), userinfo/id_token claim
  resolution. Auto-provisions a passwordless user by email.
- Public endpoints /auth/oidc/{status,start,callback,exchange}; callback
  bounces a one-time ticket to the SPA in the URL fragment.
- AuthService.provisionOidcUser + issueSessionForUserId (login shape).
- CLI: 'config oidc' upserts issuer/clientId/secret/callback/redirect/
  scopes/enabled (secret masked on print).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-18 09:44:49 +01:00
parent 6afb935302
commit 2a394969d2
7 changed files with 383 additions and 4 deletions

View File

@@ -0,0 +1,37 @@
import { Column, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
// Single-row OIDC provider configuration, managed via the CLI
// `config-oidc`. id is always 'default'.
@Entity('oidc_config')
export class OidcConfig {
@PrimaryColumn({ type: 'varchar', length: 16 })
id!: string; // always 'default'
@Column({ type: 'varchar', length: 512, default: '' })
issuer!: string;
@Column({ type: 'varchar', length: 256, default: '' })
clientId!: string;
@Column({ type: 'varchar', length: 512, default: '' })
clientSecret!: string;
// Center's own callback URL registered at the IdP, e.g.
// https://fabric-api.example/api/auth/oidc/callback
@Column({ type: 'varchar', length: 512, default: '' })
redirectUri!: string;
// Where to send the browser back after a successful login (the SPA),
// e.g. https://fabric.example/oidc
@Column({ type: 'varchar', length: 512, default: '' })
postLoginRedirect!: string;
@Column({ type: 'varchar', length: 256, default: 'openid email profile' })
scopes!: string;
@Column({ type: 'boolean', default: false })
enabled!: boolean;
@UpdateDateColumn()
updatedAt!: Date;
}