@~lyre/auth 0.11.0 → 0.12.0
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.md +93 -6
- package/dist/chunk-F2GDXTY4.js +971 -0
- package/dist/index.d.ts +323 -1
- package/dist/index.js +37 -1
- package/dist/nest.d.ts +22 -4
- package/dist/nest.js +53 -7
- package/dist/sveltekit.d.ts +19 -1
- package/dist/sveltekit.js +141 -36
- package/package.json +6 -4
- package/dist/chunk-TQNQLX3C.js +0 -338
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,270 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
type OidcErrorCode =
|
|
4
|
+
/** Client misconfiguration (e.g. client_secret_basic without a secret). */
|
|
5
|
+
'invalid_config'
|
|
6
|
+
/** Callback `state` missing or not the one this app sent (possible CSRF). */
|
|
7
|
+
| 'state_mismatch'
|
|
8
|
+
/** Accounts redirected back with `?error=…` (e.g. access_denied). */
|
|
9
|
+
| 'authorization_error'
|
|
10
|
+
/** Callback carried no `code`. */
|
|
11
|
+
| 'missing_code'
|
|
12
|
+
/** Callback `iss` parameter (RFC 9207) is not our issuer. */
|
|
13
|
+
| 'issuer_mismatch'
|
|
14
|
+
/** Discovery document unreachable or invalid. */
|
|
15
|
+
| 'discovery_failed'
|
|
16
|
+
/** JWKS unreachable or invalid. */
|
|
17
|
+
| 'jwks_fetch_failed'
|
|
18
|
+
/** fetch() itself failed (DNS, connection refused, …). */
|
|
19
|
+
| 'network_error'
|
|
20
|
+
/** Token endpoint answered non-2xx (see `oauthError`, e.g. invalid_grant, invalid_client). */
|
|
21
|
+
| 'token_request_failed'
|
|
22
|
+
/** Token endpoint answered 2xx with an unusable body. */
|
|
23
|
+
| 'invalid_token_response'
|
|
24
|
+
/** `openid` was requested but no id_token came back. */
|
|
25
|
+
| 'missing_id_token'
|
|
26
|
+
/** Not a well-formed compact JWS. */
|
|
27
|
+
| 'invalid_jwt'
|
|
28
|
+
/** Header alg is not RS256 (blocks `none`, HS256 key confusion, …). */
|
|
29
|
+
| 'unsupported_alg'
|
|
30
|
+
/** No JWKS key matches the token's `kid`, even after a refetch. */
|
|
31
|
+
| 'unknown_kid' | 'invalid_signature' | 'audience_mismatch' | 'token_expired' | 'token_not_yet_valid' | 'nonce_mismatch'
|
|
32
|
+
/** A token that is not an access token (e.g. an id_token) was presented as one. */
|
|
33
|
+
| 'wrong_token_type'
|
|
34
|
+
/** userinfo answered non-2xx, or its `sub` disagrees with the token. */
|
|
35
|
+
| 'userinfo_failed' | 'revocation_failed';
|
|
36
|
+
declare class OidcError extends Error {
|
|
37
|
+
readonly code: OidcErrorCode;
|
|
38
|
+
/** HTTP status from Accounts, when the failure was an HTTP response. */
|
|
39
|
+
readonly status?: number;
|
|
40
|
+
/** OAuth `error` field (`invalid_grant`, `invalid_client`, `access_denied`, …). */
|
|
41
|
+
readonly oauthError?: string;
|
|
42
|
+
readonly oauthErrorDescription?: string;
|
|
43
|
+
constructor(code: OidcErrorCode, message: string, details?: {
|
|
44
|
+
status?: number;
|
|
45
|
+
oauthError?: string;
|
|
46
|
+
oauthErrorDescription?: string;
|
|
47
|
+
cause?: unknown;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
declare function isOidcError(error: unknown, code?: OidcErrorCode): error is OidcError;
|
|
51
|
+
type OidcEndpoints = {
|
|
52
|
+
authorization: string;
|
|
53
|
+
token: string;
|
|
54
|
+
userinfo: string;
|
|
55
|
+
jwks: string;
|
|
56
|
+
revocation: string;
|
|
57
|
+
endSession: string;
|
|
58
|
+
};
|
|
59
|
+
/** Paths fixed by the contract, resolved against the issuer when discovery is off. */
|
|
60
|
+
declare const ACCOUNTS_ENDPOINT_PATHS: Readonly<OidcEndpoints>;
|
|
61
|
+
declare const DEFAULT_OIDC_SCOPES: readonly ["openid", "profile", "email", "offline_access"];
|
|
62
|
+
type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
|
|
63
|
+
type CommonVerifierOptions = {
|
|
64
|
+
/** Issuer, e.g. `https://accounts.nipate.africa`. Tokens must carry exactly this `iss`. */
|
|
65
|
+
issuer: string;
|
|
66
|
+
/** Load endpoints from `/.well-known/openid-configuration` (cached). Default false: contract paths. */
|
|
67
|
+
discovery?: boolean;
|
|
68
|
+
/** Override individual endpoints (absolute URLs or paths relative to the issuer). */
|
|
69
|
+
endpoints?: Partial<OidcEndpoints>;
|
|
70
|
+
fetchImpl?: typeof fetch;
|
|
71
|
+
/** Allowed clock skew for exp/nbf, seconds. Default 30. */
|
|
72
|
+
clockToleranceSec?: number;
|
|
73
|
+
/** Discovery cache TTL. Default 1h. */
|
|
74
|
+
metadataCacheTtlMs?: number;
|
|
75
|
+
/** JWKS cache TTL. Default 1h. */
|
|
76
|
+
jwksCacheTtlMs?: number;
|
|
77
|
+
/** Minimum gap between JWKS refetches triggered by an unknown kid. Default 5s. */
|
|
78
|
+
jwksRefetchCooldownMs?: number;
|
|
79
|
+
/** Clock, epoch ms. For tests. */
|
|
80
|
+
now?: () => number;
|
|
81
|
+
};
|
|
82
|
+
type OidcClientOptions = CommonVerifierOptions & {
|
|
83
|
+
/** The app's client id (its app id in Accounts). Tokens must carry it as `aud`. */
|
|
84
|
+
clientId: string;
|
|
85
|
+
/** Confidential apps only. Public apps (browser-only, CLI) have none. */
|
|
86
|
+
clientSecret?: string;
|
|
87
|
+
/** Must match a registered redirect URI character for character. */
|
|
88
|
+
redirectUri: string;
|
|
89
|
+
/** Where Accounts sends the browser after `/auth/logout`. */
|
|
90
|
+
postLogoutRedirectUri?: string;
|
|
91
|
+
/** Default `openid profile email offline_access`. */
|
|
92
|
+
scopes?: readonly string[] | string;
|
|
93
|
+
/** Default `client_secret_basic` when a secret is set, otherwise `none`. */
|
|
94
|
+
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
95
|
+
};
|
|
96
|
+
type OidcLoginRequest = {
|
|
97
|
+
/** Send the browser here. */
|
|
98
|
+
url: string;
|
|
99
|
+
/** Keep these three server-side (or in a signed httpOnly cookie) until the callback. */
|
|
100
|
+
state: string;
|
|
101
|
+
nonce: string;
|
|
102
|
+
codeVerifier: string;
|
|
103
|
+
};
|
|
104
|
+
type OidcLoginExpectation = Pick<OidcLoginRequest, 'state' | 'nonce' | 'codeVerifier'>;
|
|
105
|
+
type TokenSet = {
|
|
106
|
+
accessToken: string;
|
|
107
|
+
tokenType: 'Bearer';
|
|
108
|
+
expiresIn: number;
|
|
109
|
+
/** Epoch ms at which the access token expires (local clock). */
|
|
110
|
+
expiresAt: number;
|
|
111
|
+
/** Rotates on every refresh: always store the new one; the old one stops working. */
|
|
112
|
+
refreshToken?: string;
|
|
113
|
+
idToken?: string;
|
|
114
|
+
scope?: string;
|
|
115
|
+
raw: Record<string, unknown>;
|
|
116
|
+
};
|
|
117
|
+
type IdTokenClaims = {
|
|
118
|
+
iss: string;
|
|
119
|
+
sub: string;
|
|
120
|
+
aud: string | string[];
|
|
121
|
+
exp: number;
|
|
122
|
+
iat: number;
|
|
123
|
+
auth_time?: number;
|
|
124
|
+
nonce?: string;
|
|
125
|
+
email?: string;
|
|
126
|
+
email_verified?: boolean;
|
|
127
|
+
name?: string;
|
|
128
|
+
given_name?: string;
|
|
129
|
+
family_name?: string;
|
|
130
|
+
picture?: string;
|
|
131
|
+
[claim: string]: unknown;
|
|
132
|
+
};
|
|
133
|
+
type AccessTokenClaims = {
|
|
134
|
+
iss: string;
|
|
135
|
+
sub: string;
|
|
136
|
+
aud: string | string[];
|
|
137
|
+
exp: number;
|
|
138
|
+
iat: number;
|
|
139
|
+
/** Tenant id. */
|
|
140
|
+
tid?: string | null;
|
|
141
|
+
/** Accounts session id. */
|
|
142
|
+
sid?: string;
|
|
143
|
+
/** Space-separated scopes. */
|
|
144
|
+
scope?: string;
|
|
145
|
+
jti?: string;
|
|
146
|
+
[claim: string]: unknown;
|
|
147
|
+
};
|
|
148
|
+
type UserInfo = {
|
|
149
|
+
sub: string;
|
|
150
|
+
email?: string;
|
|
151
|
+
email_verified?: boolean;
|
|
152
|
+
name?: string;
|
|
153
|
+
given_name?: string;
|
|
154
|
+
family_name?: string;
|
|
155
|
+
picture?: string;
|
|
156
|
+
[claim: string]: unknown;
|
|
157
|
+
};
|
|
158
|
+
type VerifiedAccessToken = {
|
|
159
|
+
claims: AccessTokenClaims;
|
|
160
|
+
/** `jwks`: verified locally. `userinfo`: JWKS unreachable, Accounts vouched for it remotely. */
|
|
161
|
+
verifiedBy: 'jwks' | 'userinfo';
|
|
162
|
+
};
|
|
163
|
+
declare function randomToken(bytes?: number): string;
|
|
164
|
+
/** RFC 7636 S256: BASE64URL(SHA256(ascii(verifier))). */
|
|
165
|
+
declare function pkceChallenge(verifier: string): string;
|
|
166
|
+
declare function createPkcePair(): {
|
|
167
|
+
codeVerifier: string;
|
|
168
|
+
codeChallenge: string;
|
|
169
|
+
};
|
|
170
|
+
type MetadataSource = {
|
|
171
|
+
get(): Promise<OidcEndpoints>;
|
|
172
|
+
};
|
|
173
|
+
declare class JwksStore {
|
|
174
|
+
private readonly metadata;
|
|
175
|
+
private readonly fetchImpl;
|
|
176
|
+
private readonly ttlMs;
|
|
177
|
+
private readonly cooldownMs;
|
|
178
|
+
private readonly now;
|
|
179
|
+
private keys;
|
|
180
|
+
private anonymous;
|
|
181
|
+
private fetchedAt;
|
|
182
|
+
private inflight;
|
|
183
|
+
/** Number of JWKS fetches performed (observability / tests). */
|
|
184
|
+
fetchCount: number;
|
|
185
|
+
constructor(metadata: MetadataSource, fetchImpl: typeof fetch, ttlMs: number, cooldownMs: number, now: () => number);
|
|
186
|
+
private refresh;
|
|
187
|
+
private lookup;
|
|
188
|
+
getKey(kid: string | undefined): Promise<KeyObject>;
|
|
189
|
+
}
|
|
190
|
+
/** True when the string has the shape of a compact JWS (three base64url segments). */
|
|
191
|
+
declare function looksLikeJwt(token: string): boolean;
|
|
192
|
+
type AccessTokenVerifierOptions = CommonVerifierOptions & {
|
|
193
|
+
/**
|
|
194
|
+
* Accepted `aud` values (client ids). Omit to accept a token issued to any app; the caller
|
|
195
|
+
* then reads `claims.aud` to see which app it was issued for.
|
|
196
|
+
*/
|
|
197
|
+
audience?: string | readonly string[];
|
|
198
|
+
/**
|
|
199
|
+
* When the JWKS (or discovery) cannot be reached, ask Accounts' userinfo endpoint instead.
|
|
200
|
+
* Default true. Never used for a token that fails verification against a reachable JWKS.
|
|
201
|
+
*/
|
|
202
|
+
remoteFallback?: boolean;
|
|
203
|
+
};
|
|
204
|
+
type AccessTokenVerifier = {
|
|
205
|
+
verify(token: string, options?: {
|
|
206
|
+
audience?: string | readonly string[];
|
|
207
|
+
}): Promise<VerifiedAccessToken>;
|
|
208
|
+
fetchUserInfo(accessToken: string): Promise<UserInfo>;
|
|
209
|
+
metadata(): Promise<OidcEndpoints>;
|
|
210
|
+
/** @internal */
|
|
211
|
+
readonly _jwks: JwksStore;
|
|
212
|
+
};
|
|
213
|
+
declare function createAccessTokenVerifier(opts: AccessTokenVerifierOptions): AccessTokenVerifier;
|
|
214
|
+
type OidcClient = {
|
|
215
|
+
readonly clientId: string;
|
|
216
|
+
readonly issuer: string;
|
|
217
|
+
readonly scope: string;
|
|
218
|
+
readonly authMethod: TokenEndpointAuthMethod;
|
|
219
|
+
metadata(): Promise<OidcEndpoints>;
|
|
220
|
+
/** Step 1: build the /auth/authorize URL. Keep `state`, `nonce`, `codeVerifier` until the callback. */
|
|
221
|
+
beginLogin(options?: {
|
|
222
|
+
scopes?: readonly string[] | string;
|
|
223
|
+
prompt?: string;
|
|
224
|
+
loginHint?: string;
|
|
225
|
+
extraParams?: Record<string, string>;
|
|
226
|
+
}): Promise<OidcLoginRequest>;
|
|
227
|
+
/** Steps 3–5: check state, exchange the code (with PKCE verifier), verify the id_token. */
|
|
228
|
+
handleCallback(input: {
|
|
229
|
+
/** The full callback URL, or just its query parameters. */
|
|
230
|
+
url?: string | URL;
|
|
231
|
+
params?: URLSearchParams | Record<string, string | undefined>;
|
|
232
|
+
expected: Partial<OidcLoginExpectation> | null | undefined;
|
|
233
|
+
}): Promise<{
|
|
234
|
+
claims: IdTokenClaims;
|
|
235
|
+
tokens: TokenSet;
|
|
236
|
+
}>;
|
|
237
|
+
/** Low-level code exchange. Does NOT verify the id_token: call `verifyIdToken(t, { nonce })` yourself. */
|
|
238
|
+
exchangeCode(input: {
|
|
239
|
+
code: string;
|
|
240
|
+
codeVerifier: string;
|
|
241
|
+
}): Promise<TokenSet>;
|
|
242
|
+
/** grant_type=refresh_token. The returned `refreshToken` replaces the old one, which is now dead. */
|
|
243
|
+
refresh(refreshToken: string, options?: {
|
|
244
|
+
scopes?: readonly string[] | string;
|
|
245
|
+
}): Promise<TokenSet>;
|
|
246
|
+
/** POST /api/auth/revoke. Throws OidcError('revocation_failed') on a non-2xx answer. */
|
|
247
|
+
revoke(token: string, tokenTypeHint?: 'refresh_token' | 'access_token'): Promise<void>;
|
|
248
|
+
verifyIdToken(idToken: string, options?: {
|
|
249
|
+
nonce?: string;
|
|
250
|
+
}): Promise<IdTokenClaims>;
|
|
251
|
+
/** Verify locally against the JWKS (aud defaults to this client id); userinfo fallback if the JWKS is down. */
|
|
252
|
+
verifyAccessToken(token: string, options?: {
|
|
253
|
+
audience?: string | readonly string[];
|
|
254
|
+
remoteFallback?: boolean;
|
|
255
|
+
}): Promise<VerifiedAccessToken>;
|
|
256
|
+
fetchUserInfo(accessToken: string): Promise<UserInfo>;
|
|
257
|
+
/** /auth/logout?client_id=…&post_logout_redirect_uri=… */
|
|
258
|
+
logoutUrl(options?: {
|
|
259
|
+
postLogoutRedirectUri?: string;
|
|
260
|
+
state?: string;
|
|
261
|
+
idTokenHint?: string;
|
|
262
|
+
}): Promise<string>;
|
|
263
|
+
/** @internal */
|
|
264
|
+
readonly _jwks: JwksStore;
|
|
265
|
+
};
|
|
266
|
+
declare function createOidcClient(opts: OidcClientOptions): OidcClient;
|
|
267
|
+
|
|
1
268
|
type PermissionSet = string[];
|
|
2
269
|
type AccountsIdentity = {
|
|
3
270
|
id: string;
|
|
@@ -49,18 +316,29 @@ type PlatformSession = {
|
|
|
49
316
|
expiresAt?: string;
|
|
50
317
|
};
|
|
51
318
|
type AccountsClientConfig = {
|
|
319
|
+
/** Accounts origin, e.g. `https://accounts.nipate.africa`. */
|
|
52
320
|
baseUrl?: string;
|
|
321
|
+
/** OIDC issuer. Defaults to `baseUrl`. Added 0.12.0. */
|
|
322
|
+
issuer?: string;
|
|
53
323
|
clientId: string;
|
|
324
|
+
/** Confidential apps only; public apps leave it unset. */
|
|
54
325
|
clientSecret?: string;
|
|
55
326
|
redirectUri: string;
|
|
327
|
+
/** Absolute URL Accounts returns the browser to after `/auth/logout`. */
|
|
56
328
|
logoutRedirectUri?: string;
|
|
329
|
+
/** Requested scopes. Default `openid profile email offline_access`. Added 0.12.0. */
|
|
330
|
+
scopes?: string[];
|
|
331
|
+
/** Load endpoints from `/.well-known/openid-configuration`. Default false. Added 0.12.0. */
|
|
332
|
+
discovery?: boolean;
|
|
57
333
|
useMock?: boolean;
|
|
58
334
|
};
|
|
335
|
+
/** @deprecated Since 0.12.0; legacy login state. See `OidcLoginRequest`. */
|
|
59
336
|
type AuthorizationState = {
|
|
60
337
|
nonce: string;
|
|
61
338
|
nextPath: string;
|
|
62
339
|
tenantSlug?: string;
|
|
63
340
|
};
|
|
341
|
+
/** @deprecated Since 0.12.0; legacy token response with a `user`. See `TokenSet` + `IdTokenClaims`. */
|
|
64
342
|
type AccountsTokenResponse = {
|
|
65
343
|
access_token: string;
|
|
66
344
|
refresh_token?: string;
|
|
@@ -86,10 +364,22 @@ type SyncAccountsUserResult = {
|
|
|
86
364
|
};
|
|
87
365
|
type SyncAccountsUser = (identity: AccountsIdentity) => SyncAccountsUserResult | Promise<SyncAccountsUserResult>;
|
|
88
366
|
declare function createAccountsClientConfig(input?: Partial<AccountsClientConfig>): AccountsClientConfig;
|
|
367
|
+
/**
|
|
368
|
+
* Build an OIDC client from an AccountsClientConfig (issuer falls back to baseUrl). Added 0.12.0.
|
|
369
|
+
*/
|
|
370
|
+
declare function createOidcClientFromConfig(config: AccountsClientConfig, overrides?: Partial<OidcClientOptions>): OidcClient;
|
|
371
|
+
/** Map verified id_token (and optional userinfo) claims to an AccountsIdentity. Added 0.12.0. */
|
|
372
|
+
declare function identityFromClaims(claims: IdTokenClaims | UserInfo, userInfo?: UserInfo): AccountsIdentity;
|
|
373
|
+
/** Only same-origin absolute paths: `/x` yes, `//evil.example` and `/\\evil` no. */
|
|
374
|
+
declare function safeNextPath(value: string | null | undefined): string;
|
|
89
375
|
declare function createPlatformAuth(): {
|
|
90
376
|
readSession(cookieHeader: string | null | undefined): PlatformSession;
|
|
91
377
|
requirePrincipal(session: PlatformSession): AuthenticatedPrincipal;
|
|
92
378
|
};
|
|
379
|
+
/**
|
|
380
|
+
* @deprecated Since 0.12.0. Sends no PKCE, nonce or scope, which Accounts now refuses. Use
|
|
381
|
+
* `createOidcClient().beginLogin()` (+ `createLoginStateCookie`). Still used for mock mode.
|
|
382
|
+
*/
|
|
93
383
|
declare function beginAccountsLoginRedirect(config: AccountsClientConfig, options: {
|
|
94
384
|
nextPath?: string;
|
|
95
385
|
tenantSlug?: string;
|
|
@@ -99,6 +389,10 @@ declare function beginAccountsLoginRedirect(config: AccountsClientConfig, option
|
|
|
99
389
|
stateValue: string;
|
|
100
390
|
stateCookie: string;
|
|
101
391
|
};
|
|
392
|
+
/**
|
|
393
|
+
* @deprecated Since 0.12.0. Trusts the token response's `user` instead of a verified id_token. Use
|
|
394
|
+
* `createOidcClient().handleCallback()` (+ `readLoginStateCookie`). Still used for mock mode.
|
|
395
|
+
*/
|
|
102
396
|
declare function handleAccountsCallback(input: {
|
|
103
397
|
config: AccountsClientConfig;
|
|
104
398
|
code: string;
|
|
@@ -116,6 +410,10 @@ declare function handleAccountsCallback(input: {
|
|
|
116
410
|
sessionCookie: string;
|
|
117
411
|
clearStateCookie: string;
|
|
118
412
|
}>;
|
|
413
|
+
/**
|
|
414
|
+
* @deprecated Since 0.12.0. JSON body, no PKCE verifier, no client authentication. Use
|
|
415
|
+
* `createOidcClient().exchangeCode()` or `.handleCallback()`.
|
|
416
|
+
*/
|
|
119
417
|
declare function exchangeAuthorizationCode(input: {
|
|
120
418
|
config: AccountsClientConfig;
|
|
121
419
|
code: string;
|
|
@@ -142,6 +440,26 @@ type CookieOptions = {
|
|
|
142
440
|
secure?: boolean;
|
|
143
441
|
};
|
|
144
442
|
declare function clearPlatformSessionCookie(opts?: CookieOptions): string;
|
|
443
|
+
/** Set-Cookie string for a PlatformSession (HMAC-signed when `secret` is set). Added 0.12.0. */
|
|
444
|
+
declare function createPlatformSessionCookie(session: PlatformSession, opts?: CookieOptions, secret?: string): string;
|
|
445
|
+
declare const LOGIN_STATE_COOKIE_NAME = "accounts_oidc_login";
|
|
446
|
+
type StoredLogin = OidcLoginExpectation & {
|
|
447
|
+
nextPath: string;
|
|
448
|
+
tenantSlug?: string;
|
|
449
|
+
/** Epoch seconds when the login started. */
|
|
450
|
+
issuedAt: number;
|
|
451
|
+
};
|
|
452
|
+
declare function createLoginStateCookie(login: OidcLoginRequest | OidcLoginExpectation, options?: {
|
|
453
|
+
nextPath?: string;
|
|
454
|
+
tenantSlug?: string;
|
|
455
|
+
secret?: string;
|
|
456
|
+
cookieOptions?: CookieOptions;
|
|
457
|
+
}): string;
|
|
458
|
+
/** Read (and, with a secret, verify) the login state cookie. Null when absent, forged or older than 10 minutes. */
|
|
459
|
+
declare function readLoginStateCookie(cookieHeader: string | null | undefined, options?: {
|
|
460
|
+
secret?: string;
|
|
461
|
+
}): StoredLogin | null;
|
|
462
|
+
declare function clearLoginStateCookie(opts?: CookieOptions): string;
|
|
145
463
|
type ServiceKeyApp = {
|
|
146
464
|
id: string;
|
|
147
465
|
slug: string;
|
|
@@ -162,6 +480,10 @@ declare function introspectServiceKey(input: {
|
|
|
162
480
|
key: string;
|
|
163
481
|
fetchImpl?: typeof fetch;
|
|
164
482
|
}): Promise<ServiceKeyContext | null>;
|
|
483
|
+
/**
|
|
484
|
+
* @deprecated Since 0.12.0. The contract signs out by revoking the refresh token
|
|
485
|
+
* (`OidcClient.revoke`) and optionally sending the browser to `OidcClient.logoutUrl()`.
|
|
486
|
+
*/
|
|
165
487
|
declare function logoutFromAccounts(input: {
|
|
166
488
|
config: Pick<AccountsClientConfig, 'baseUrl'>;
|
|
167
489
|
accessToken: string;
|
|
@@ -188,4 +510,4 @@ type AccountsWebhookEvent = {
|
|
|
188
510
|
declare function normalizeAccountsWebhook(payload: unknown): AccountsWebhookEvent;
|
|
189
511
|
declare function scopeSatisfied(granted: string[] | undefined, required: string): boolean;
|
|
190
512
|
|
|
191
|
-
export { type AccountsClientConfig, type AccountsIdentity, type AccountsTokenResponse, type AccountsWebhookEvent, type AuthenticatedPrincipal, type AuthorizationState, type CookieOptions, type LocalUser, type PermissionSet, type PlatformSession, type ServiceKeyApp, type ServiceKeyContext, type SyncAccountsUser, type SyncAccountsUserResult, type TenantAccessContext, type TenantContext, type TenantMembership, beginAccountsLoginRedirect, clearPlatformSessionCookie, createAccountsClientConfig, createPlatformAuth, exchangeAuthorizationCode, handleAccountsCallback, identityPassthroughSync, introspectServiceKey, logoutFromAccounts, normalizeAccountsWebhook, readPlatformSessionCookie, resolveActiveTenant, scopeSatisfied, serviceKeyFromRequest, syncAccountsUser, verifyWebhookSignature };
|
|
513
|
+
export { ACCOUNTS_ENDPOINT_PATHS, type AccessTokenClaims, type AccessTokenVerifier, type AccessTokenVerifierOptions, type AccountsClientConfig, type AccountsIdentity, type AccountsTokenResponse, type AccountsWebhookEvent, type AuthenticatedPrincipal, type AuthorizationState, type CookieOptions, DEFAULT_OIDC_SCOPES, type IdTokenClaims, LOGIN_STATE_COOKIE_NAME, type LocalUser, type OidcClient, type OidcClientOptions, type OidcEndpoints, OidcError, type OidcErrorCode, type OidcLoginExpectation, type OidcLoginRequest, type PermissionSet, type PlatformSession, type ServiceKeyApp, type ServiceKeyContext, type StoredLogin, type SyncAccountsUser, type SyncAccountsUserResult, type TenantAccessContext, type TenantContext, type TenantMembership, type TokenEndpointAuthMethod, type TokenSet, type UserInfo, type VerifiedAccessToken, beginAccountsLoginRedirect, clearLoginStateCookie, clearPlatformSessionCookie, createAccessTokenVerifier, createAccountsClientConfig, createLoginStateCookie, createOidcClient, createOidcClientFromConfig, createPkcePair, createPlatformAuth, createPlatformSessionCookie, exchangeAuthorizationCode, handleAccountsCallback, identityFromClaims, identityPassthroughSync, introspectServiceKey, isOidcError, logoutFromAccounts, looksLikeJwt, normalizeAccountsWebhook, pkceChallenge, randomToken, readLoginStateCookie, readPlatformSessionCookie, resolveActiveTenant, safeNextPath, scopeSatisfied, serviceKeyFromRequest, syncAccountsUser, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,70 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ACCOUNTS_ENDPOINT_PATHS,
|
|
3
|
+
DEFAULT_OIDC_SCOPES,
|
|
4
|
+
LOGIN_STATE_COOKIE_NAME,
|
|
5
|
+
OidcError,
|
|
2
6
|
beginAccountsLoginRedirect,
|
|
7
|
+
clearLoginStateCookie,
|
|
3
8
|
clearPlatformSessionCookie,
|
|
9
|
+
createAccessTokenVerifier,
|
|
4
10
|
createAccountsClientConfig,
|
|
11
|
+
createLoginStateCookie,
|
|
12
|
+
createOidcClient,
|
|
13
|
+
createOidcClientFromConfig,
|
|
14
|
+
createPkcePair,
|
|
5
15
|
createPlatformAuth,
|
|
16
|
+
createPlatformSessionCookie,
|
|
6
17
|
exchangeAuthorizationCode,
|
|
7
18
|
handleAccountsCallback,
|
|
19
|
+
identityFromClaims,
|
|
8
20
|
identityPassthroughSync,
|
|
9
21
|
introspectServiceKey,
|
|
22
|
+
isOidcError,
|
|
10
23
|
logoutFromAccounts,
|
|
24
|
+
looksLikeJwt,
|
|
11
25
|
normalizeAccountsWebhook,
|
|
26
|
+
pkceChallenge,
|
|
27
|
+
randomToken,
|
|
28
|
+
readLoginStateCookie,
|
|
12
29
|
readPlatformSessionCookie,
|
|
13
30
|
resolveActiveTenant,
|
|
31
|
+
safeNextPath,
|
|
14
32
|
scopeSatisfied,
|
|
15
33
|
serviceKeyFromRequest,
|
|
16
34
|
syncAccountsUser,
|
|
17
35
|
verifyWebhookSignature
|
|
18
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-F2GDXTY4.js";
|
|
19
37
|
export {
|
|
38
|
+
ACCOUNTS_ENDPOINT_PATHS,
|
|
39
|
+
DEFAULT_OIDC_SCOPES,
|
|
40
|
+
LOGIN_STATE_COOKIE_NAME,
|
|
41
|
+
OidcError,
|
|
20
42
|
beginAccountsLoginRedirect,
|
|
43
|
+
clearLoginStateCookie,
|
|
21
44
|
clearPlatformSessionCookie,
|
|
45
|
+
createAccessTokenVerifier,
|
|
22
46
|
createAccountsClientConfig,
|
|
47
|
+
createLoginStateCookie,
|
|
48
|
+
createOidcClient,
|
|
49
|
+
createOidcClientFromConfig,
|
|
50
|
+
createPkcePair,
|
|
23
51
|
createPlatformAuth,
|
|
52
|
+
createPlatformSessionCookie,
|
|
24
53
|
exchangeAuthorizationCode,
|
|
25
54
|
handleAccountsCallback,
|
|
55
|
+
identityFromClaims,
|
|
26
56
|
identityPassthroughSync,
|
|
27
57
|
introspectServiceKey,
|
|
58
|
+
isOidcError,
|
|
28
59
|
logoutFromAccounts,
|
|
60
|
+
looksLikeJwt,
|
|
29
61
|
normalizeAccountsWebhook,
|
|
62
|
+
pkceChallenge,
|
|
63
|
+
randomToken,
|
|
64
|
+
readLoginStateCookie,
|
|
30
65
|
readPlatformSessionCookie,
|
|
31
66
|
resolveActiveTenant,
|
|
67
|
+
safeNextPath,
|
|
32
68
|
scopeSatisfied,
|
|
33
69
|
serviceKeyFromRequest,
|
|
34
70
|
syncAccountsUser,
|
package/dist/nest.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AccountsWebhookEvent, ServiceKeyContext, AccountsClientConfig } from './index.js';
|
|
1
|
+
import { AccountsWebhookEvent, ServiceKeyContext, AccountsClientConfig, AccessTokenVerifierOptions } from './index.js';
|
|
2
2
|
export { normalizeAccountsWebhook } from './index.js';
|
|
3
|
+
import 'node:crypto';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The minimum a request must expose. Fastify's `request` satisfies this as-is
|
|
@@ -37,17 +38,34 @@ type UserSessionContext = {
|
|
|
37
38
|
userId: string;
|
|
38
39
|
email?: string;
|
|
39
40
|
name?: string;
|
|
41
|
+
/** Accounts session id (`sid`). Empty when the token carries none. */
|
|
40
42
|
sessionId: string;
|
|
41
|
-
/** The app the
|
|
43
|
+
/** The app (client id) the token was issued for: the JWT `aud`. */
|
|
42
44
|
appId: string | null;
|
|
43
45
|
tenantId: string | null;
|
|
44
46
|
expiresAt?: string;
|
|
47
|
+
/** Scopes granted to the token (JWT `scope`). Added 0.12.0. */
|
|
48
|
+
scopes?: string[];
|
|
49
|
+
/** How the token was checked. Added 0.12.0. */
|
|
50
|
+
verifiedBy?: 'jwks' | 'userinfo' | 'legacy-me';
|
|
45
51
|
};
|
|
46
52
|
type UserSessionResolverOptions = {
|
|
47
|
-
|
|
53
|
+
/** `baseUrl` is the Accounts origin; `issuer` defaults to it. */
|
|
54
|
+
config: Pick<AccountsClientConfig, 'baseUrl'> & Partial<Pick<AccountsClientConfig, 'issuer' | 'discovery'>>;
|
|
48
55
|
fetchImpl?: typeof fetch;
|
|
49
|
-
/** Cache TTL in ms. Default 60_000; 0 disables caching. */
|
|
56
|
+
/** Cache TTL in ms. Default 60_000; 0 disables caching. Never outlives the token's exp. */
|
|
50
57
|
cacheTtlMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Accept only tokens issued to these client ids (`aud`). Strongly recommended: without it a
|
|
60
|
+
* token issued to ANY Nipate app is accepted and the caller must check `appId` itself.
|
|
61
|
+
*/
|
|
62
|
+
audience?: string | readonly string[];
|
|
63
|
+
/** Ask userinfo when the JWKS is unreachable. Default true. */
|
|
64
|
+
remoteFallback?: boolean;
|
|
65
|
+
/** Check opaque (pre-JWT) bearers via GET /api/auth/me. Default true; turn off once Accounts issues JWTs. */
|
|
66
|
+
legacySessionTokens?: boolean;
|
|
67
|
+
/** Extra verifier options (endpoints, clock tolerance, JWKS cache TTL/cooldown, clock). */
|
|
68
|
+
verifier?: Partial<Omit<AccessTokenVerifierOptions, 'issuer' | 'audience' | 'fetchImpl'>>;
|
|
51
69
|
};
|
|
52
70
|
/** A session bearer is any Authorization: Bearer token that is NOT an `sk_` service key. */
|
|
53
71
|
declare function sessionTokenFromHeaders(carrier: HeaderCarrier): string | null;
|
package/dist/nest.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createAccessTokenVerifier,
|
|
2
3
|
introspectServiceKey,
|
|
4
|
+
looksLikeJwt,
|
|
3
5
|
normalizeAccountsWebhook,
|
|
4
6
|
scopeSatisfied,
|
|
5
7
|
serviceKeyFromRequest,
|
|
6
8
|
verifyWebhookSignature
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-F2GDXTY4.js";
|
|
8
10
|
|
|
9
11
|
// src/nest.ts
|
|
10
12
|
import { createHash } from "crypto";
|
|
@@ -60,9 +62,42 @@ function sessionTokenFromHeaders(carrier) {
|
|
|
60
62
|
function createUserSessionResolver(opts) {
|
|
61
63
|
const ttl = opts.cacheTtlMs ?? 6e4;
|
|
62
64
|
const cache = /* @__PURE__ */ new Map();
|
|
63
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const fetchImpl = opts.fetchImpl ?? ((...args) => fetch(...args));
|
|
66
|
+
const base = opts.config.baseUrl?.trim().replace(/\/$/, "");
|
|
67
|
+
const issuer = (opts.config.issuer ?? base)?.trim();
|
|
68
|
+
const legacy = opts.legacySessionTokens ?? true;
|
|
69
|
+
let verifier = null;
|
|
70
|
+
if (issuer) {
|
|
71
|
+
verifier = createAccessTokenVerifier({
|
|
72
|
+
discovery: opts.config.discovery,
|
|
73
|
+
...opts.verifier,
|
|
74
|
+
issuer,
|
|
75
|
+
audience: opts.audience,
|
|
76
|
+
remoteFallback: opts.remoteFallback ?? true,
|
|
77
|
+
fetchImpl
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function viaJwt(token) {
|
|
81
|
+
if (!verifier) return null;
|
|
82
|
+
try {
|
|
83
|
+
const { claims, verifiedBy } = await verifier.verify(token);
|
|
84
|
+
const aud = Array.isArray(claims.aud) ? claims.aud[0] : claims.aud;
|
|
85
|
+
return {
|
|
86
|
+
userId: claims.sub,
|
|
87
|
+
email: typeof claims.email === "string" ? claims.email : void 0,
|
|
88
|
+
name: typeof claims.name === "string" ? claims.name : void 0,
|
|
89
|
+
sessionId: claims.sid ?? "",
|
|
90
|
+
appId: aud ?? null,
|
|
91
|
+
tenantId: claims.tid ?? null,
|
|
92
|
+
expiresAt: new Date(claims.exp * 1e3).toISOString(),
|
|
93
|
+
scopes: claims.scope ? claims.scope.split(" ").filter(Boolean) : [],
|
|
94
|
+
verifiedBy
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function viaLegacyMe(token) {
|
|
66
101
|
if (!base || !token) return null;
|
|
67
102
|
try {
|
|
68
103
|
const res = await fetchImpl(new URL("/api/auth/me", base), {
|
|
@@ -73,19 +108,27 @@ function createUserSessionResolver(opts) {
|
|
|
73
108
|
const userId = data.user?.id;
|
|
74
109
|
const sessionId = data.session?.id;
|
|
75
110
|
if (!userId || !sessionId) return null;
|
|
111
|
+
const appId = data.session?.appId ?? null;
|
|
112
|
+
const allowed = opts.audience === void 0 ? null : typeof opts.audience === "string" ? [opts.audience] : opts.audience;
|
|
113
|
+
if (allowed && (!appId || !allowed.includes(appId))) return null;
|
|
76
114
|
return {
|
|
77
115
|
userId,
|
|
78
116
|
email: data.user?.emailAddress ?? data.user?.email,
|
|
79
117
|
name: data.user?.name,
|
|
80
118
|
sessionId,
|
|
81
|
-
appId
|
|
119
|
+
appId,
|
|
82
120
|
tenantId: data.session?.tenantId ?? null,
|
|
83
|
-
expiresAt: data.session?.expiresAt
|
|
121
|
+
expiresAt: data.session?.expiresAt,
|
|
122
|
+
verifiedBy: "legacy-me"
|
|
84
123
|
};
|
|
85
124
|
} catch {
|
|
86
125
|
return null;
|
|
87
126
|
}
|
|
88
127
|
}
|
|
128
|
+
async function introspect(token) {
|
|
129
|
+
if (looksLikeJwt(token)) return viaJwt(token);
|
|
130
|
+
return legacy ? viaLegacyMe(token) : null;
|
|
131
|
+
}
|
|
89
132
|
async function resolve(carrier) {
|
|
90
133
|
const token = sessionTokenFromHeaders(carrier);
|
|
91
134
|
if (!token) return null;
|
|
@@ -94,7 +137,10 @@ function createUserSessionResolver(opts) {
|
|
|
94
137
|
const cached = ttl > 0 ? cache.get(hash) : void 0;
|
|
95
138
|
if (cached && cached.expires > now) return cached.value;
|
|
96
139
|
const ctx = await introspect(token);
|
|
97
|
-
if (ttl > 0)
|
|
140
|
+
if (ttl > 0) {
|
|
141
|
+
const exp = ctx?.expiresAt ? Date.parse(ctx.expiresAt) : Infinity;
|
|
142
|
+
cache.set(hash, { value: ctx, expires: Math.min(now + ttl, exp) });
|
|
143
|
+
}
|
|
98
144
|
return ctx;
|
|
99
145
|
}
|
|
100
146
|
function invalidate(token) {
|
package/dist/sveltekit.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RequestEvent, Handle } from '@sveltejs/kit';
|
|
2
|
-
import { AccountsWebhookEvent, PlatformSession, AccountsClientConfig, ServiceKeyContext, SyncAccountsUser, CookieOptions } from './index.js';
|
|
2
|
+
import { AccountsWebhookEvent, PlatformSession, AccountsClientConfig, ServiceKeyContext, SyncAccountsUser, CookieOptions, OidcClientOptions, OidcClient } from './index.js';
|
|
3
3
|
export { clearPlatformSessionCookie } from './index.js';
|
|
4
|
+
import 'node:crypto';
|
|
4
5
|
|
|
5
6
|
type SvelteKitAuthOptions = {
|
|
6
7
|
/** Accounts client config (use createAccountsClientConfig()). */
|
|
@@ -29,6 +30,23 @@ type SvelteKitAuthOptions = {
|
|
|
29
30
|
* recommended in production, since the session payload includes tenant memberships.
|
|
30
31
|
*/
|
|
31
32
|
sessionSecret?: string;
|
|
33
|
+
/** OIDC client overrides (issuer, scopes, discovery, endpoints, tokenEndpointAuthMethod…). Added 0.12.0. */
|
|
34
|
+
oidc?: Partial<OidcClientOptions>;
|
|
35
|
+
/** A prebuilt client; takes precedence over `config` + `oidc`. Added 0.12.0. */
|
|
36
|
+
client?: OidcClient;
|
|
37
|
+
fetchImpl?: typeof fetch;
|
|
38
|
+
/**
|
|
39
|
+
* Refresh the Accounts access token when it is within 60s of expiry and the session holds a
|
|
40
|
+
* refresh token. Default true. A refused refresh (reused/revoked token) logs the session out.
|
|
41
|
+
*/
|
|
42
|
+
refreshTokens?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* After local sign-out, send the browser to Accounts' `/auth/logout` to end the Accounts session
|
|
45
|
+
* too (returning to `config.logoutRedirectUri`). Default false: only this app signs out.
|
|
46
|
+
*/
|
|
47
|
+
endAccountsSession?: boolean;
|
|
48
|
+
/** Render a failed callback. Default: a plain 400 naming the OidcError code. */
|
|
49
|
+
onAuthError?: (error: unknown, event: RequestEvent) => Response | Promise<Response>;
|
|
32
50
|
};
|
|
33
51
|
type PlatformLocals = {
|
|
34
52
|
session: PlatformSession;
|