@digitalpaws/starr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.rst +75 -0
- package/data/.nfs.811bbb0d.43c34.4 +0 -0
- package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
- package/data/.nfs.82d127f7.fffff5704.4 +0 -0
- package/data/.nfs.82d127fe.fffff5704.4 +0 -0
- package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
- package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
- package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
- package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
- package/data/.nfs.82d18536.fffff5e24.4 +0 -0
- package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
- package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
- package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
- package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
- package/data/.nfs.8314122e.fffffb044.4 +0 -0
- package/data/.nfs.83141236.fffffb044.4 +0 -0
- package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
- package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
- package/data/logs/starr.log +66244 -0
- package/data/starr.db +0 -0
- package/data/starr.db-shm +0 -0
- package/data/starr.db-wal +0 -0
- package/data/starr.jwt +1 -0
- package/data/starr.key +1 -0
- package/dist/auth/auth-service.d.ts +42 -0
- package/dist/auth/auth-service.d.ts.map +1 -0
- package/dist/auth/auth-service.js +226 -0
- package/dist/auth/auth-service.js.map +1 -0
- package/dist/auth/http-server.d.ts +16 -0
- package/dist/auth/http-server.d.ts.map +1 -0
- package/dist/auth/http-server.js +207 -0
- package/dist/auth/http-server.js.map +1 -0
- package/dist/auth/jwt-secret.d.ts +6 -0
- package/dist/auth/jwt-secret.d.ts.map +1 -0
- package/dist/auth/jwt-secret.js +51 -0
- package/dist/auth/jwt-secret.js.map +1 -0
- package/dist/auth/oidc/oidc-flow.d.ts +60 -0
- package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-flow.js +201 -0
- package/dist/auth/oidc/oidc-flow.js.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.js +124 -0
- package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
- package/dist/auth/oidc/user-service.d.ts +67 -0
- package/dist/auth/oidc/user-service.d.ts.map +1 -0
- package/dist/auth/oidc/user-service.js +177 -0
- package/dist/auth/oidc/user-service.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +31 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +70 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/config/config-service.d.ts +83 -0
- package/dist/config/config-service.d.ts.map +1 -0
- package/dist/config/config-service.js +309 -0
- package/dist/config/config-service.js.map +1 -0
- package/dist/config/validate.d.ts +12 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +140 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/content-types/registry.d.ts +75 -0
- package/dist/content-types/registry.d.ts.map +1 -0
- package/dist/content-types/registry.js +171 -0
- package/dist/content-types/registry.js.map +1 -0
- package/dist/datastore/db.d.ts +9 -0
- package/dist/datastore/db.d.ts.map +1 -0
- package/dist/datastore/db.js +27 -0
- package/dist/datastore/db.js.map +1 -0
- package/dist/datastore/encrypt.d.ts +9 -0
- package/dist/datastore/encrypt.d.ts.map +1 -0
- package/dist/datastore/encrypt.js +84 -0
- package/dist/datastore/encrypt.js.map +1 -0
- package/dist/datastore/migrations.d.ts +10 -0
- package/dist/datastore/migrations.d.ts.map +1 -0
- package/dist/datastore/migrations.js +340 -0
- package/dist/datastore/migrations.js.map +1 -0
- package/dist/datastore/storage.d.ts +4 -0
- package/dist/datastore/storage.d.ts.map +1 -0
- package/dist/datastore/storage.js +60 -0
- package/dist/datastore/storage.js.map +1 -0
- package/dist/events/event-bus.d.ts +15 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +140 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/spine.d.ts +31 -0
- package/dist/events/spine.d.ts.map +1 -0
- package/dist/events/spine.js +68 -0
- package/dist/events/spine.js.map +1 -0
- package/dist/events/trace.d.ts +24 -0
- package/dist/events/trace.d.ts.map +1 -0
- package/dist/events/trace.js +33 -0
- package/dist/events/trace.js.map +1 -0
- package/dist/host.d.ts +65 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +94 -0
- package/dist/host.js.map +1 -0
- package/dist/library/importer.d.ts +29 -0
- package/dist/library/importer.d.ts.map +1 -0
- package/dist/library/importer.js +145 -0
- package/dist/library/importer.js.map +1 -0
- package/dist/library/library-cache.d.ts +46 -0
- package/dist/library/library-cache.d.ts.map +1 -0
- package/dist/library/library-cache.js +152 -0
- package/dist/library/library-cache.js.map +1 -0
- package/dist/library/library-config.d.ts +12 -0
- package/dist/library/library-config.d.ts.map +1 -0
- package/dist/library/library-config.js +10 -0
- package/dist/library/library-config.js.map +1 -0
- package/dist/library/library-scanner.d.ts +62 -0
- package/dist/library/library-scanner.d.ts.map +1 -0
- package/dist/library/library-scanner.js +336 -0
- package/dist/library/library-scanner.js.map +1 -0
- package/dist/library/library-service.d.ts +65 -0
- package/dist/library/library-service.d.ts.map +1 -0
- package/dist/library/library-service.js +197 -0
- package/dist/library/library-service.js.map +1 -0
- package/dist/library/metadata-matcher.d.ts +56 -0
- package/dist/library/metadata-matcher.d.ts.map +1 -0
- package/dist/library/metadata-matcher.js +207 -0
- package/dist/library/metadata-matcher.js.map +1 -0
- package/dist/library/naming.d.ts +8 -0
- package/dist/library/naming.d.ts.map +1 -0
- package/dist/library/naming.js +47 -0
- package/dist/library/naming.js.map +1 -0
- package/dist/library/scan-controller.d.ts +95 -0
- package/dist/library/scan-controller.d.ts.map +1 -0
- package/dist/library/scan-controller.js +379 -0
- package/dist/library/scan-controller.js.map +1 -0
- package/dist/library/source-resolver.d.ts +17 -0
- package/dist/library/source-resolver.d.ts.map +1 -0
- package/dist/library/source-resolver.js +75 -0
- package/dist/library/source-resolver.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +107 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/main.d.ts +36 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +229 -0
- package/dist/main.js.map +1 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +110 -0
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/discovery.d.ts +2 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +32 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/loader.d.ts +9 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/state/request-intake.d.ts +26 -0
- package/dist/state/request-intake.d.ts.map +1 -0
- package/dist/state/request-intake.js +74 -0
- package/dist/state/request-intake.js.map +1 -0
- package/dist/state/state-store.d.ts +146 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +465 -0
- package/dist/state/state-store.js.map +1 -0
- package/dist/state/trace-store.d.ts +12 -0
- package/dist/state/trace-store.d.ts.map +1 -0
- package/dist/state/trace-store.js +24 -0
- package/dist/state/trace-store.js.map +1 -0
- package/dist/types/plugin.d.ts +93 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/web/index.d.ts +10 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +153 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/routes/activity.d.ts +6 -0
- package/dist/web/routes/activity.d.ts.map +1 -0
- package/dist/web/routes/activity.js +31 -0
- package/dist/web/routes/activity.js.map +1 -0
- package/dist/web/routes/auth.d.ts +6 -0
- package/dist/web/routes/auth.d.ts.map +1 -0
- package/dist/web/routes/auth.js +142 -0
- package/dist/web/routes/auth.js.map +1 -0
- package/dist/web/routes/config.d.ts +7 -0
- package/dist/web/routes/config.d.ts.map +1 -0
- package/dist/web/routes/config.js +204 -0
- package/dist/web/routes/config.js.map +1 -0
- package/dist/web/routes/content-types.d.ts +6 -0
- package/dist/web/routes/content-types.d.ts.map +1 -0
- package/dist/web/routes/content-types.js +29 -0
- package/dist/web/routes/content-types.js.map +1 -0
- package/dist/web/routes/discovery.d.ts +6 -0
- package/dist/web/routes/discovery.d.ts.map +1 -0
- package/dist/web/routes/discovery.js +111 -0
- package/dist/web/routes/discovery.js.map +1 -0
- package/dist/web/routes/library-import.d.ts +6 -0
- package/dist/web/routes/library-import.d.ts.map +1 -0
- package/dist/web/routes/library-import.js +120 -0
- package/dist/web/routes/library-import.js.map +1 -0
- package/dist/web/routes/library-management.d.ts +6 -0
- package/dist/web/routes/library-management.d.ts.map +1 -0
- package/dist/web/routes/library-management.js +120 -0
- package/dist/web/routes/library-management.js.map +1 -0
- package/dist/web/routes/library.d.ts +6 -0
- package/dist/web/routes/library.d.ts.map +1 -0
- package/dist/web/routes/library.js +28 -0
- package/dist/web/routes/library.js.map +1 -0
- package/dist/web/routes/oidc.d.ts +9 -0
- package/dist/web/routes/oidc.d.ts.map +1 -0
- package/dist/web/routes/oidc.js +203 -0
- package/dist/web/routes/oidc.js.map +1 -0
- package/dist/web/routes/requests.d.ts +6 -0
- package/dist/web/routes/requests.d.ts.map +1 -0
- package/dist/web/routes/requests.js +183 -0
- package/dist/web/routes/requests.js.map +1 -0
- package/dist/web/routes/sse.d.ts +6 -0
- package/dist/web/routes/sse.d.ts.map +1 -0
- package/dist/web/routes/sse.js +54 -0
- package/dist/web/routes/sse.js.map +1 -0
- package/package.json +33 -0
- package/package.json.license +2 -0
- package/src/auth/auth-service.ts +333 -0
- package/src/auth/http-server.ts +249 -0
- package/src/auth/jwt-secret.ts +65 -0
- package/src/auth/oidc/oidc-flow.ts +266 -0
- package/src/auth/oidc/oidc-provider-service.ts +174 -0
- package/src/auth/oidc/user-service.ts +227 -0
- package/src/auth/rate-limiter.ts +85 -0
- package/src/config/config-service.ts +434 -0
- package/src/config/validate.ts +153 -0
- package/src/content-types/registry.ts +194 -0
- package/src/datastore/db.ts +38 -0
- package/src/datastore/encrypt.ts +107 -0
- package/src/datastore/migrations.ts +367 -0
- package/src/datastore/storage.ts +83 -0
- package/src/events/event-bus.ts +180 -0
- package/src/events/spine.ts +87 -0
- package/src/events/trace.ts +38 -0
- package/src/host.ts +130 -0
- package/src/library/importer.ts +174 -0
- package/src/library/library-cache.ts +212 -0
- package/src/library/library-config.ts +22 -0
- package/src/library/library-scanner.ts +404 -0
- package/src/library/library-service.ts +282 -0
- package/src/library/metadata-matcher.ts +285 -0
- package/src/library/naming.ts +59 -0
- package/src/library/scan-controller.ts +490 -0
- package/src/library/source-resolver.ts +92 -0
- package/src/logging/logger.ts +115 -0
- package/src/main.ts +364 -0
- package/src/plugins/context.ts +138 -0
- package/src/plugins/discovery.ts +29 -0
- package/src/plugins/loader.ts +309 -0
- package/src/state/request-intake.ts +101 -0
- package/src/state/state-store.ts +621 -0
- package/src/state/trace-store.ts +27 -0
- package/src/types/plugin.ts +110 -0
- package/src/web/index.ts +182 -0
- package/src/web/routes/activity.ts +41 -0
- package/src/web/routes/auth.ts +170 -0
- package/src/web/routes/config.ts +282 -0
- package/src/web/routes/content-types.ts +42 -0
- package/src/web/routes/discovery.ts +156 -0
- package/src/web/routes/library-import.ts +196 -0
- package/src/web/routes/library-management.ts +140 -0
- package/src/web/routes/library.ts +46 -0
- package/src/web/routes/oidc.ts +260 -0
- package/src/web/routes/requests.ts +250 -0
- package/src/web/routes/sse.ts +92 -0
- package/tests/auth.test.ts +510 -0
- package/tests/config-route.test.ts +345 -0
- package/tests/config.test.ts +612 -0
- package/tests/content-types.test.ts +376 -0
- package/tests/datastore.test.ts +345 -0
- package/tests/e2e.test.ts +430 -0
- package/tests/event-bus.test.ts +925 -0
- package/tests/host.test.ts +449 -0
- package/tests/library-cache.test.ts +171 -0
- package/tests/library-scanner.test.ts +177 -0
- package/tests/library.test.ts +210 -0
- package/tests/loader-context.test.ts +644 -0
- package/tests/logger-jsonl.test.ts +154 -0
- package/tests/metadata-matcher.test.ts +198 -0
- package/tests/oidc.test.ts +218 -0
- package/tests/scan-controller.test.ts +328 -0
- package/tests/source-resolver.test.ts +100 -0
- package/tests/starr-video-integration.test.ts +153 -0
- package/tests/state-store.test.ts +700 -0
- package/tests/trace-history.test.ts +356 -0
- package/tsconfig.json +8 -0
- package/tsconfig.json.license +2 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { Logger } from "../../types/plugin.js";
|
|
3
|
+
import type { OidcProviderService } from "./oidc-provider-service.js";
|
|
4
|
+
import type { User, UserService } from "./user-service.js";
|
|
5
|
+
/** Result of a successful OIDC callback. */
|
|
6
|
+
export interface OidcCallbackResult {
|
|
7
|
+
user: User;
|
|
8
|
+
providerId: string;
|
|
9
|
+
created: boolean;
|
|
10
|
+
redirectTo: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class OidcFlow {
|
|
13
|
+
private db;
|
|
14
|
+
private providerService;
|
|
15
|
+
private userService;
|
|
16
|
+
private baseUrl;
|
|
17
|
+
private logger;
|
|
18
|
+
constructor(db: Database.Database, providerService: OidcProviderService, userService: UserService, baseUrl: string, // e.g. "http://localhost:3457"
|
|
19
|
+
logger: Logger);
|
|
20
|
+
/**
|
|
21
|
+
* Stage 1 — Initiate the OIDC login flow.
|
|
22
|
+
*
|
|
23
|
+
* 1. Look up the provider configuration.
|
|
24
|
+
* 2. Discover the OIDC issuer's endpoints (via .well-known).
|
|
25
|
+
* 3. Generate PKCE code verifier and challenge, state, nonce.
|
|
26
|
+
* 4. Store the state in oidc_auth_states.
|
|
27
|
+
* 5. Return the redirect URL to send the user's browser to.
|
|
28
|
+
*
|
|
29
|
+
* @param providerId The OIDC provider's UUID.
|
|
30
|
+
* @param redirectTo Where to send the user after successful login (e.g. "/browse").
|
|
31
|
+
* @returns The authorization URL to redirect the browser to.
|
|
32
|
+
*/
|
|
33
|
+
initiateLogin(providerId: string, redirectTo?: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Stage 2 — Handle the OIDC callback.
|
|
36
|
+
*
|
|
37
|
+
* 1. Validate the state (CSRF protection).
|
|
38
|
+
* 2. Look up stored auth state from DB.
|
|
39
|
+
* 3. Exchange authorization code for tokens.
|
|
40
|
+
* 4. Validate the ID token (handled by openid-client).
|
|
41
|
+
* 5. Find or create the user.
|
|
42
|
+
* 6. Clean up the used auth state.
|
|
43
|
+
*
|
|
44
|
+
* @returns The authenticated user and whether they were just created.
|
|
45
|
+
*/
|
|
46
|
+
handleCallback(providerId: string, code: string, state: string): Promise<OidcCallbackResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a Starr role from an OIDC claim using a JSONPath-like expression.
|
|
49
|
+
*
|
|
50
|
+
* Simple dot-notation path (e.g. "$.starr_roles" or "starr_roles"):
|
|
51
|
+
* - If the resolved value is an array, it's checked for "admin".
|
|
52
|
+
* - If it's a string, it's checked as-is.
|
|
53
|
+
* - If it's a boolean, true=admin, false=defaultRole.
|
|
54
|
+
* - If not found, returns null (caller uses defaultRole).
|
|
55
|
+
*/
|
|
56
|
+
private resolveRoleFromClaim;
|
|
57
|
+
/** Delete auth states older than 10 minutes (capped at 100 rows). */
|
|
58
|
+
private cleanupExpiredStates;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=oidc-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-flow.d.ts","sourceRoot":"","sources":["../../../src/auth/oidc/oidc-flow.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAI3D,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,QAAQ;IAEjB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;gBAJN,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,eAAe,EAAE,mBAAmB,EACpC,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,MAAM,EAAI,+BAA+B;IAClD,MAAM,EAAE,MAAM;IAGxB;;;;;;;;;;;;OAYG;IACG,aAAa,CACjB,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,MAAkB,GAC7B,OAAO,CAAC,MAAM,CAAC;IAqDlB;;;;;;;;;;;OAWG;IACG,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,CAAC;IA2F9B;;;;;;;;OAQG;IACH,OAAO,CAAC,oBAAoB;IAkC5B,qEAAqE;IACrE,OAAO,CAAC,oBAAoB;CAS7B"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
import { discovery, buildAuthorizationUrl, authorizationCodeGrant, randomPKCECodeVerifier, calculatePKCECodeChallenge, randomState, randomNonce, } from "openid-client";
|
|
4
|
+
const STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
5
|
+
export class OidcFlow {
|
|
6
|
+
db;
|
|
7
|
+
providerService;
|
|
8
|
+
userService;
|
|
9
|
+
baseUrl;
|
|
10
|
+
logger;
|
|
11
|
+
constructor(db, providerService, userService, baseUrl, // e.g. "http://localhost:3457"
|
|
12
|
+
logger) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.providerService = providerService;
|
|
15
|
+
this.userService = userService;
|
|
16
|
+
this.baseUrl = baseUrl;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Stage 1 — Initiate the OIDC login flow.
|
|
21
|
+
*
|
|
22
|
+
* 1. Look up the provider configuration.
|
|
23
|
+
* 2. Discover the OIDC issuer's endpoints (via .well-known).
|
|
24
|
+
* 3. Generate PKCE code verifier and challenge, state, nonce.
|
|
25
|
+
* 4. Store the state in oidc_auth_states.
|
|
26
|
+
* 5. Return the redirect URL to send the user's browser to.
|
|
27
|
+
*
|
|
28
|
+
* @param providerId The OIDC provider's UUID.
|
|
29
|
+
* @param redirectTo Where to send the user after successful login (e.g. "/browse").
|
|
30
|
+
* @returns The authorization URL to redirect the browser to.
|
|
31
|
+
*/
|
|
32
|
+
async initiateLogin(providerId, redirectTo = "/browse") {
|
|
33
|
+
const provider = this.providerService.get(providerId);
|
|
34
|
+
if (!provider) {
|
|
35
|
+
throw new Error(`OIDC provider not found: ${providerId}`);
|
|
36
|
+
}
|
|
37
|
+
if (!provider.enabled) {
|
|
38
|
+
throw new Error(`OIDC provider is disabled: ${providerId}`);
|
|
39
|
+
}
|
|
40
|
+
// Discover the OIDC issuer's metadata
|
|
41
|
+
const config = await discovery(new URL(provider.issuerUrl), provider.clientId, { client_secret: provider.clientSecret });
|
|
42
|
+
// Generate PKCE and state values
|
|
43
|
+
const codeVerifier = randomPKCECodeVerifier();
|
|
44
|
+
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
|
45
|
+
const state = randomState();
|
|
46
|
+
const nonce = randomNonce();
|
|
47
|
+
// Store the auth state
|
|
48
|
+
this.db.prepare(`
|
|
49
|
+
INSERT INTO oidc_auth_states (state, nonce, code_verifier, provider_id, redirect_to, created_at)
|
|
50
|
+
VALUES (@state, @nonce, @codeVerifier, @providerId, @redirectTo, datetime('now'))
|
|
51
|
+
`).run({
|
|
52
|
+
state,
|
|
53
|
+
nonce,
|
|
54
|
+
codeVerifier,
|
|
55
|
+
providerId: provider.id,
|
|
56
|
+
redirectTo,
|
|
57
|
+
});
|
|
58
|
+
// Build the authorization URL
|
|
59
|
+
const authUrl = buildAuthorizationUrl(config, {
|
|
60
|
+
redirect_uri: `${this.baseUrl}/api/oidc/callback/${provider.id}`,
|
|
61
|
+
scope: provider.scopes,
|
|
62
|
+
state,
|
|
63
|
+
nonce,
|
|
64
|
+
code_challenge: codeChallenge,
|
|
65
|
+
code_challenge_method: "S256",
|
|
66
|
+
response_type: "code",
|
|
67
|
+
});
|
|
68
|
+
this.logger.info("OIDC login initiated", {
|
|
69
|
+
providerId: provider.id,
|
|
70
|
+
providerName: provider.name,
|
|
71
|
+
});
|
|
72
|
+
return authUrl.href;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Stage 2 — Handle the OIDC callback.
|
|
76
|
+
*
|
|
77
|
+
* 1. Validate the state (CSRF protection).
|
|
78
|
+
* 2. Look up stored auth state from DB.
|
|
79
|
+
* 3. Exchange authorization code for tokens.
|
|
80
|
+
* 4. Validate the ID token (handled by openid-client).
|
|
81
|
+
* 5. Find or create the user.
|
|
82
|
+
* 6. Clean up the used auth state.
|
|
83
|
+
*
|
|
84
|
+
* @returns The authenticated user and whether they were just created.
|
|
85
|
+
*/
|
|
86
|
+
async handleCallback(providerId, code, state) {
|
|
87
|
+
// 1. Validate the state (CSRF)
|
|
88
|
+
if (!state || !code) {
|
|
89
|
+
throw new Error("Missing state or code parameter");
|
|
90
|
+
}
|
|
91
|
+
// 2. Look up the stored auth state
|
|
92
|
+
const authState = this.db.prepare(`
|
|
93
|
+
SELECT * FROM oidc_auth_states WHERE state = @state AND provider_id = @providerId
|
|
94
|
+
`).get({ state, providerId });
|
|
95
|
+
if (!authState) {
|
|
96
|
+
throw new Error("Invalid or expired OIDC state");
|
|
97
|
+
}
|
|
98
|
+
// Delete the used state now (before any further processing)
|
|
99
|
+
this.db.prepare("DELETE FROM oidc_auth_states WHERE state = @state").run({ state });
|
|
100
|
+
// 3. Clean up any expired auth states (older than 10 minutes)
|
|
101
|
+
this.cleanupExpiredStates();
|
|
102
|
+
// 4. Get the provider config and exchange the code
|
|
103
|
+
const provider = this.providerService.get(providerId);
|
|
104
|
+
if (!provider) {
|
|
105
|
+
throw new Error(`OIDC provider not found: ${providerId}`);
|
|
106
|
+
}
|
|
107
|
+
const config = await discovery(new URL(provider.issuerUrl), provider.clientId, { client_secret: provider.clientSecret });
|
|
108
|
+
// Build the callback URL with code + state params (as the provider would redirect to)
|
|
109
|
+
const callbackUrl = new URL(`${this.baseUrl}/api/oidc/callback/${provider.id}`);
|
|
110
|
+
callbackUrl.searchParams.set("code", code);
|
|
111
|
+
callbackUrl.searchParams.set("state", state);
|
|
112
|
+
// Exchange the authorization code for tokens.
|
|
113
|
+
// openid-client automatically:
|
|
114
|
+
// - Validates the state parameter
|
|
115
|
+
// - Validates the ID token (signature, iss, aud, exp, expectedNonce)
|
|
116
|
+
// - Fetches the JWKS from the provider
|
|
117
|
+
const tokenSet = await authorizationCodeGrant(config, callbackUrl, {
|
|
118
|
+
expectedNonce: authState.nonce,
|
|
119
|
+
pkceCodeVerifier: authState.code_verifier,
|
|
120
|
+
expectedState: state,
|
|
121
|
+
});
|
|
122
|
+
// 5. Extract claims from the ID token
|
|
123
|
+
const idTokenClaims = tokenSet.claims();
|
|
124
|
+
if (!idTokenClaims) {
|
|
125
|
+
throw new Error("OIDC provider did not return an ID token");
|
|
126
|
+
}
|
|
127
|
+
// Cast claims to access OIDC standard claims (preferred_username, name, etc.)
|
|
128
|
+
const claims = idTokenClaims;
|
|
129
|
+
this.logger.info("OIDC callback successful", {
|
|
130
|
+
providerId,
|
|
131
|
+
sub: String(claims.sub ?? ""),
|
|
132
|
+
email: claims.email,
|
|
133
|
+
});
|
|
134
|
+
// 6. Resolve the role from the provider's role_attribute, if configured
|
|
135
|
+
let role = provider.defaultRole;
|
|
136
|
+
if (provider.roleAttribute) {
|
|
137
|
+
const resolvedRole = this.resolveRoleFromClaim(claims, provider.roleAttribute, provider.defaultRole);
|
|
138
|
+
if (resolvedRole)
|
|
139
|
+
role = resolvedRole;
|
|
140
|
+
}
|
|
141
|
+
// 7. Find or create the user
|
|
142
|
+
const { user, created } = this.userService.findOrCreateFromOidc({
|
|
143
|
+
sub: String(claims.sub ?? ""),
|
|
144
|
+
preferred_username: claims.preferred_username,
|
|
145
|
+
name: claims.name,
|
|
146
|
+
picture: claims.picture,
|
|
147
|
+
email: claims.email,
|
|
148
|
+
}, providerId, role);
|
|
149
|
+
return { user, providerId, created, redirectTo: authState.redirect_to ?? "/browse" };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Resolve a Starr role from an OIDC claim using a JSONPath-like expression.
|
|
153
|
+
*
|
|
154
|
+
* Simple dot-notation path (e.g. "$.starr_roles" or "starr_roles"):
|
|
155
|
+
* - If the resolved value is an array, it's checked for "admin".
|
|
156
|
+
* - If it's a string, it's checked as-is.
|
|
157
|
+
* - If it's a boolean, true=admin, false=defaultRole.
|
|
158
|
+
* - If not found, returns null (caller uses defaultRole).
|
|
159
|
+
*/
|
|
160
|
+
resolveRoleFromClaim(claims, roleAttribute, defaultRole) {
|
|
161
|
+
try {
|
|
162
|
+
// Simple dot-notation path resolution
|
|
163
|
+
const path = roleAttribute.replace(/^\$\.?/, ""); // strip leading "$." or "$"
|
|
164
|
+
const keys = path.split(".");
|
|
165
|
+
let value = claims;
|
|
166
|
+
for (const key of keys) {
|
|
167
|
+
if (value && typeof value === "object" && key in value) {
|
|
168
|
+
value = value[key];
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return null; // path not found
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Interpret the value
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
return value.includes("admin") ? "admin" : defaultRole;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value === "string") {
|
|
179
|
+
return value === "admin" ? "admin" : (value === "user" ? "user" : null);
|
|
180
|
+
}
|
|
181
|
+
if (typeof value === "boolean") {
|
|
182
|
+
return value ? "admin" : defaultRole;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// If anything goes wrong resolving the role, fall back to default
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/** Delete auth states older than 10 minutes (capped at 100 rows). */
|
|
191
|
+
cleanupExpiredStates() {
|
|
192
|
+
// Use SQLite's datetime arithmetic for consistent comparison with
|
|
193
|
+
// the stored datetime('now') format (no T, no Z).
|
|
194
|
+
this.db.prepare(`
|
|
195
|
+
DELETE FROM oidc_auth_states
|
|
196
|
+
WHERE created_at < datetime('now', @offset)
|
|
197
|
+
LIMIT 100
|
|
198
|
+
`).run({ offset: `-${STATE_TTL_MS / 1000} seconds` });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=oidc-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-flow.js","sourceRoot":"","sources":["../../../src/auth/oidc/oidc-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { Logger } from "../../types/plugin.js";
|
|
3
|
+
/**
|
|
4
|
+
* Shape of an OIDC provider as stored.
|
|
5
|
+
* client_secret is encrypted at rest.
|
|
6
|
+
*/
|
|
7
|
+
export interface OidcProvider {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
issuerUrl: string;
|
|
11
|
+
clientId: string;
|
|
12
|
+
clientSecret: string;
|
|
13
|
+
scopes: string;
|
|
14
|
+
roleAttribute: string | null;
|
|
15
|
+
defaultRole: "admin" | "user";
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Public-facing shape (no secret, for the login page).
|
|
22
|
+
*/
|
|
23
|
+
export interface OidcProviderPublic {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Shape used when creating or updating a provider. */
|
|
29
|
+
export interface OidcProviderInput {
|
|
30
|
+
name: string;
|
|
31
|
+
issuerUrl: string;
|
|
32
|
+
clientId: string;
|
|
33
|
+
clientSecret: string;
|
|
34
|
+
scopes?: string;
|
|
35
|
+
roleAttribute?: string | null;
|
|
36
|
+
defaultRole?: "admin" | "user";
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare class OidcProviderService {
|
|
40
|
+
private db;
|
|
41
|
+
private encrypt;
|
|
42
|
+
private decrypt;
|
|
43
|
+
private logger;
|
|
44
|
+
constructor(db: Database.Database, encrypt: (plaintext: string) => string, decrypt: (ciphertext: string) => string, logger: Logger);
|
|
45
|
+
/** List all providers (with encrypted secrets, for admin API). */
|
|
46
|
+
list(): OidcProvider[];
|
|
47
|
+
/** List only enabled providers (public info, no secrets). */
|
|
48
|
+
listEnabled(): OidcProviderPublic[];
|
|
49
|
+
/** Get a single provider by ID. */
|
|
50
|
+
get(id: string): OidcProvider | null;
|
|
51
|
+
/** Create a new provider. Returns the created provider. */
|
|
52
|
+
create(input: OidcProviderInput): OidcProvider;
|
|
53
|
+
/** Update an existing provider. Fields that are undefined are not changed. */
|
|
54
|
+
update(id: string, input: Partial<OidcProviderInput>): OidcProvider | null;
|
|
55
|
+
/** Delete a provider. Also cleans up auth states referencing it (CASCADE). */
|
|
56
|
+
delete(id: string): boolean;
|
|
57
|
+
private rowToProvider;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=oidc-provider-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-provider-service.d.ts","sourceRoot":"","sources":["../../../src/auth/oidc/oidc-provider-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;gBAHN,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,EACtC,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,EACvC,MAAM,EAAE,MAAM;IAGxB,kEAAkE;IAClE,IAAI,IAAI,YAAY,EAAE;IAOtB,6DAA6D;IAC7D,WAAW,IAAI,kBAAkB,EAAE;IAWnC,mCAAmC;IACnC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAOpC,2DAA2D;IAC3D,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY;IA2B9C,8EAA8E;IAC9E,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,YAAY,GAAG,IAAI;IAmC1E,8EAA8E;IAC9E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAW3B,OAAO,CAAC,aAAa;CAetB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export class OidcProviderService {
|
|
5
|
+
db;
|
|
6
|
+
encrypt;
|
|
7
|
+
decrypt;
|
|
8
|
+
logger;
|
|
9
|
+
constructor(db, encrypt, decrypt, logger) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.encrypt = encrypt;
|
|
12
|
+
this.decrypt = decrypt;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
/** List all providers (with encrypted secrets, for admin API). */
|
|
16
|
+
list() {
|
|
17
|
+
const rows = this.db
|
|
18
|
+
.prepare("SELECT * FROM oidc_providers ORDER BY name ASC")
|
|
19
|
+
.all();
|
|
20
|
+
return rows.map(this.rowToProvider);
|
|
21
|
+
}
|
|
22
|
+
/** List only enabled providers (public info, no secrets). */
|
|
23
|
+
listEnabled() {
|
|
24
|
+
const rows = this.db
|
|
25
|
+
.prepare("SELECT id, name, enabled FROM oidc_providers WHERE enabled = 1 ORDER BY name ASC")
|
|
26
|
+
.all();
|
|
27
|
+
return rows.map((r) => ({
|
|
28
|
+
id: r.id,
|
|
29
|
+
name: r.name,
|
|
30
|
+
enabled: !!r.enabled,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/** Get a single provider by ID. */
|
|
34
|
+
get(id) {
|
|
35
|
+
const row = this.db
|
|
36
|
+
.prepare("SELECT * FROM oidc_providers WHERE id = @id")
|
|
37
|
+
.get({ id });
|
|
38
|
+
return row ? this.rowToProvider(row) : null;
|
|
39
|
+
}
|
|
40
|
+
/** Create a new provider. Returns the created provider. */
|
|
41
|
+
create(input) {
|
|
42
|
+
const id = randomUUID();
|
|
43
|
+
const encryptedSecret = this.encrypt(input.clientSecret);
|
|
44
|
+
const scopes = input.scopes ?? "openid profile email";
|
|
45
|
+
const defaultRole = input.defaultRole ?? "user";
|
|
46
|
+
this.db.prepare(`
|
|
47
|
+
INSERT INTO oidc_providers (id, name, issuer_url, client_id, client_secret,
|
|
48
|
+
scopes, role_attribute, default_role, enabled)
|
|
49
|
+
VALUES (@id, @name, @issuerUrl, @clientId, @clientSecret,
|
|
50
|
+
@scopes, @roleAttribute, @defaultRole, @enabled)
|
|
51
|
+
`).run({
|
|
52
|
+
id,
|
|
53
|
+
name: input.name,
|
|
54
|
+
issuerUrl: input.issuerUrl,
|
|
55
|
+
clientId: input.clientId,
|
|
56
|
+
clientSecret: encryptedSecret,
|
|
57
|
+
scopes,
|
|
58
|
+
roleAttribute: input.roleAttribute ?? null,
|
|
59
|
+
defaultRole,
|
|
60
|
+
enabled: input.enabled !== false ? 1 : 0,
|
|
61
|
+
});
|
|
62
|
+
this.logger.info("OIDC provider created", { id, name: input.name });
|
|
63
|
+
return this.get(id);
|
|
64
|
+
}
|
|
65
|
+
/** Update an existing provider. Fields that are undefined are not changed. */
|
|
66
|
+
update(id, input) {
|
|
67
|
+
const existing = this.get(id);
|
|
68
|
+
if (!existing)
|
|
69
|
+
return null;
|
|
70
|
+
const name = input.name ?? existing.name;
|
|
71
|
+
const issuerUrl = input.issuerUrl ?? existing.issuerUrl;
|
|
72
|
+
const clientId = input.clientId ?? existing.clientId;
|
|
73
|
+
const clientSecret = input.clientSecret
|
|
74
|
+
? this.encrypt(input.clientSecret)
|
|
75
|
+
: existing.clientSecret; // Keep existing encrypted value if no new secret provided
|
|
76
|
+
const scopes = input.scopes ?? existing.scopes;
|
|
77
|
+
const roleAttribute = input.roleAttribute !== undefined
|
|
78
|
+
? (input.roleAttribute ?? null)
|
|
79
|
+
: existing.roleAttribute;
|
|
80
|
+
const defaultRole = input.defaultRole ?? existing.defaultRole;
|
|
81
|
+
const enabled = input.enabled !== undefined ? (input.enabled ? 1 : 0) : (existing.enabled ? 1 : 0);
|
|
82
|
+
this.db.prepare(`
|
|
83
|
+
UPDATE oidc_providers SET
|
|
84
|
+
name = @name,
|
|
85
|
+
issuer_url = @issuerUrl,
|
|
86
|
+
client_id = @clientId,
|
|
87
|
+
client_secret = @clientSecret,
|
|
88
|
+
scopes = @scopes,
|
|
89
|
+
role_attribute = @roleAttribute,
|
|
90
|
+
default_role = @defaultRole,
|
|
91
|
+
enabled = @enabled,
|
|
92
|
+
updated_at = datetime('now')
|
|
93
|
+
WHERE id = @id
|
|
94
|
+
`).run({ id, name, issuerUrl, clientId, clientSecret, scopes, roleAttribute, defaultRole, enabled });
|
|
95
|
+
this.logger.info("OIDC provider updated", { id, name });
|
|
96
|
+
return this.get(id);
|
|
97
|
+
}
|
|
98
|
+
/** Delete a provider. Also cleans up auth states referencing it (CASCADE). */
|
|
99
|
+
delete(id) {
|
|
100
|
+
const existing = this.get(id);
|
|
101
|
+
if (!existing)
|
|
102
|
+
return false;
|
|
103
|
+
this.db.prepare("DELETE FROM oidc_providers WHERE id = @id").run({ id });
|
|
104
|
+
this.logger.info("OIDC provider deleted", { id, name: existing.name });
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// ── Private helpers ──
|
|
108
|
+
rowToProvider(row) {
|
|
109
|
+
return {
|
|
110
|
+
id: row.id,
|
|
111
|
+
name: row.name,
|
|
112
|
+
issuerUrl: row.issuer_url,
|
|
113
|
+
clientId: row.client_id,
|
|
114
|
+
clientSecret: this.decrypt(row.client_secret),
|
|
115
|
+
scopes: row.scopes,
|
|
116
|
+
roleAttribute: row.role_attribute,
|
|
117
|
+
defaultRole: row.default_role,
|
|
118
|
+
enabled: !!row.enabled,
|
|
119
|
+
createdAt: row.created_at,
|
|
120
|
+
updatedAt: row.updated_at,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=oidc-provider-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-provider-service.js","sourceRoot":"","sources":["../../../src/auth/oidc/oidc-provider-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { Logger } from "../../types/plugin.js";
|
|
3
|
+
export interface User {
|
|
4
|
+
id: string;
|
|
5
|
+
username: string;
|
|
6
|
+
displayName: string | null;
|
|
7
|
+
avatarUrl: string | null;
|
|
8
|
+
role: "admin" | "user";
|
|
9
|
+
provider: "local" | "oidc";
|
|
10
|
+
oidcSub: string | null;
|
|
11
|
+
oidcProviderId: string | null;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
/** Shape for updating user role (admin-only). */
|
|
16
|
+
export interface UserRoleUpdate {
|
|
17
|
+
role: "admin" | "user";
|
|
18
|
+
}
|
|
19
|
+
export declare class UserService {
|
|
20
|
+
private db;
|
|
21
|
+
private logger;
|
|
22
|
+
constructor(db: Database.Database, logger: Logger);
|
|
23
|
+
/** Get a user by UUID or the fixed admin id. */
|
|
24
|
+
get(id: string): User | null;
|
|
25
|
+
/** Get a user by username. */
|
|
26
|
+
getByUsername(username: string): User | null;
|
|
27
|
+
/**
|
|
28
|
+
* Find a user by OIDC subject and provider.
|
|
29
|
+
* This is the primary lookup during OIDC callback.
|
|
30
|
+
*/
|
|
31
|
+
getByOidcSub(oidcSub: string, oidcProviderId: string): User | null;
|
|
32
|
+
/**
|
|
33
|
+
* Find or create a user from OIDC claims during callback.
|
|
34
|
+
*
|
|
35
|
+
* Steps:
|
|
36
|
+
* 1. Look up by (oidc_sub, oidc_provider_id).
|
|
37
|
+
* 2. If found, update display_name, avatar_url, username from claims.
|
|
38
|
+
* 3. If not found, create a new user with provider='oidc'.
|
|
39
|
+
*
|
|
40
|
+
* Returns the user and a boolean indicating whether it was newly created.
|
|
41
|
+
*/
|
|
42
|
+
findOrCreateFromOidc(claims: {
|
|
43
|
+
sub: string;
|
|
44
|
+
preferred_username?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
picture?: string;
|
|
47
|
+
email?: string;
|
|
48
|
+
}, oidcProviderId: string, defaultRole: "admin" | "user"): {
|
|
49
|
+
user: User;
|
|
50
|
+
created: boolean;
|
|
51
|
+
};
|
|
52
|
+
/** List all users (admin only). */
|
|
53
|
+
list(): User[];
|
|
54
|
+
/** Count how many admin users exist. */
|
|
55
|
+
countAdmins(): number;
|
|
56
|
+
/** Change a user's role. Returns the updated user or null if not found. */
|
|
57
|
+
setRole(id: string, role: "admin" | "user"): User | null;
|
|
58
|
+
/** Check if there is at least one admin user. */
|
|
59
|
+
hasAdmin(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a user identifier (UUID or "admin") to a user.
|
|
62
|
+
* Used when validating sessions that may have either format in user_id.
|
|
63
|
+
*/
|
|
64
|
+
resolveUser(userId: string): User | null;
|
|
65
|
+
private rowToUser;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=user-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-service.d.ts","sourceRoot":"","sources":["../../../src/auth/oidc/user-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;CACxB;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,MAAM;gBADN,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM;IAGxB,gDAAgD;IAChD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAO5B,8BAA8B;IAC9B,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAO5C;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IASlE;;;;;;;;;OASG;IACH,oBAAoB,CAClB,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,EACD,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,OAAO,GAAG,MAAM,GAC5B;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAiEnC,mCAAmC;IACnC,IAAI,IAAI,IAAI,EAAE;IAOd,wCAAwC;IACxC,WAAW,IAAI,MAAM;IAOrB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI;IA4BxD,iDAAiD;IACjD,QAAQ,IAAI,OAAO;IAOnB;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAUxC,OAAO,CAAC,SAAS;CAclB"}
|