@relyper/sp-auth 0.1.0 → 0.4.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.
Files changed (44) hide show
  1. package/README.md +216 -69
  2. package/dist/client.d.ts +33 -1
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +57 -4
  5. package/dist/client.js.map +1 -1
  6. package/dist/coins.d.ts +139 -0
  7. package/dist/coins.d.ts.map +1 -0
  8. package/dist/coins.js +210 -0
  9. package/dist/coins.js.map +1 -0
  10. package/dist/oidc/claims.d.ts +4 -0
  11. package/dist/oidc/claims.d.ts.map +1 -0
  12. package/dist/oidc/claims.js +50 -0
  13. package/dist/oidc/claims.js.map +1 -0
  14. package/dist/oidc/client.d.ts +44 -0
  15. package/dist/oidc/client.d.ts.map +1 -0
  16. package/dist/oidc/client.js +340 -0
  17. package/dist/oidc/client.js.map +1 -0
  18. package/dist/oidc/discovery.d.ts +15 -0
  19. package/dist/oidc/discovery.d.ts.map +1 -0
  20. package/dist/oidc/discovery.js +90 -0
  21. package/dist/oidc/discovery.js.map +1 -0
  22. package/dist/oidc/pkce.d.ts +6 -0
  23. package/dist/oidc/pkce.d.ts.map +1 -0
  24. package/dist/oidc/pkce.js +26 -0
  25. package/dist/oidc/pkce.js.map +1 -0
  26. package/dist/oidc/session.d.ts +47 -0
  27. package/dist/oidc/session.d.ts.map +1 -0
  28. package/dist/oidc/session.js +119 -0
  29. package/dist/oidc/session.js.map +1 -0
  30. package/dist/oidc/types.d.ts +130 -0
  31. package/dist/oidc/types.d.ts.map +1 -0
  32. package/dist/oidc/types.js +17 -0
  33. package/dist/oidc/types.js.map +1 -0
  34. package/dist/oidc-fastify.d.ts +123 -0
  35. package/dist/oidc-fastify.d.ts.map +1 -0
  36. package/dist/oidc-fastify.js +264 -0
  37. package/dist/oidc-fastify.js.map +1 -0
  38. package/dist/oidc.d.ts +15 -0
  39. package/dist/oidc.d.ts.map +1 -0
  40. package/dist/oidc.js +14 -0
  41. package/dist/oidc.js.map +1 -0
  42. package/dist/types.d.ts +13 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +25 -5
@@ -0,0 +1,340 @@
1
+ import { createRemoteJWKSet, customFetch, jwtVerify } from 'jose';
2
+ import { createDiscoveryLoader } from './discovery.js';
3
+ import { defaultClaimsToIdentity } from './claims.js';
4
+ import { codeChallengeFor, createCodeVerifier, createNonce, createState } from './pkce.js';
5
+ import { RelyperOidcError } from './types.js';
6
+ /**
7
+ * OIDC client for a Relyper service provider.
8
+ *
9
+ * Authorization Code Flow with PKCE and a confidential client: the client secret
10
+ * only ever travels from this server to the IdP's token endpoint, never through
11
+ * the browser. Every ID token is verified against the IdP's JWKS before a single
12
+ * claim in it is believed.
13
+ */
14
+ const DEFAULT_SCOPE = 'openid email profile roles tenant teams';
15
+ function resolveOptions(options) {
16
+ const required = ['issuer', 'clientId', 'clientSecret', 'redirectUri'];
17
+ for (const key of required) {
18
+ if (!options[key] || typeof options[key] !== 'string') {
19
+ throw new TypeError('@relyper/sp-auth/oidc: ' + key + ' is required.');
20
+ }
21
+ }
22
+ const requiredRoles = options.requiredRole === undefined
23
+ ? []
24
+ : (Array.isArray(options.requiredRole) ? options.requiredRole : [options.requiredRole]).filter(Boolean);
25
+ return {
26
+ issuer: trimTrailingSlashes(options.issuer),
27
+ clientId: options.clientId,
28
+ clientSecret: options.clientSecret,
29
+ redirectUri: options.redirectUri,
30
+ scope: options.scope ?? DEFAULT_SCOPE,
31
+ tokenEndpointAuthMethod: options.tokenEndpointAuthMethod ?? null,
32
+ requiredRoles,
33
+ roleMatch: options.roleMatch ?? 'any',
34
+ requireEmail: options.requireEmail ?? true,
35
+ useUserInfo: options.useUserInfo ?? false,
36
+ clockToleranceSeconds: options.clockToleranceSeconds ?? 60,
37
+ discoveryTtlMs: options.discoveryTtlMs ?? 3_600_000,
38
+ requestTimeoutMs: options.requestTimeoutMs ?? 10_000
39
+ };
40
+ }
41
+ export function createRelyperOidcClient(options) {
42
+ const resolved = resolveOptions(options);
43
+ const doFetch = options.fetch ?? globalThis.fetch;
44
+ if (typeof doFetch !== 'function') {
45
+ throw new TypeError('@relyper/sp-auth/oidc: no fetch implementation available.');
46
+ }
47
+ const mapClaims = options.mapClaims ?? defaultClaimsToIdentity;
48
+ const discovery = createDiscoveryLoader({
49
+ issuer: resolved.issuer,
50
+ ttlMs: resolved.discoveryTtlMs,
51
+ requestTimeoutMs: resolved.requestTimeoutMs,
52
+ fetch: doFetch
53
+ });
54
+ // One JWKS per jwks_uri. jose caches the keys, refetches on an unknown `kid`
55
+ // and rate-limits those refetches, which is exactly the key-rotation behaviour
56
+ // we would otherwise have to build by hand.
57
+ const jwksCache = new Map();
58
+ function jwksFor(uri) {
59
+ let jwks = jwksCache.get(uri);
60
+ if (!jwks) {
61
+ jwks = createRemoteJWKSet(new URL(uri), {
62
+ timeoutDuration: resolved.requestTimeoutMs,
63
+ cooldownDuration: 30_000,
64
+ // The JWKS has to travel the same path as every other call to the IdP,
65
+ // so a proxy-aware or test fetch covers key retrieval as well.
66
+ [customFetch]: doFetch
67
+ });
68
+ jwksCache.set(uri, jwks);
69
+ }
70
+ return jwks;
71
+ }
72
+ function authMethod(document) {
73
+ if (resolved.tokenEndpointAuthMethod)
74
+ return resolved.tokenEndpointAuthMethod;
75
+ const supported = document.token_endpoint_auth_methods_supported ?? [];
76
+ if (supported.includes('client_secret_basic'))
77
+ return 'client_secret_basic';
78
+ if (supported.includes('client_secret_post'))
79
+ return 'client_secret_post';
80
+ // The OIDC default for a confidential client when the document says nothing.
81
+ return 'client_secret_basic';
82
+ }
83
+ async function exchangeCode(document, code, codeVerifier) {
84
+ const body = new URLSearchParams({
85
+ grant_type: 'authorization_code',
86
+ code,
87
+ redirect_uri: resolved.redirectUri,
88
+ code_verifier: codeVerifier
89
+ });
90
+ const headers = {
91
+ 'content-type': 'application/x-www-form-urlencoded',
92
+ accept: 'application/json'
93
+ };
94
+ if (authMethod(document) === 'client_secret_basic') {
95
+ // RFC 6749 section 2.3.1: both halves are form-urlencoded before the
96
+ // base64. Skipping that breaks any secret holding a reserved character.
97
+ const credentials = encodeURIComponent(resolved.clientId) + ':' + encodeURIComponent(resolved.clientSecret);
98
+ headers.authorization = 'Basic ' + Buffer.from(credentials, 'utf8').toString('base64');
99
+ }
100
+ else {
101
+ body.set('client_id', resolved.clientId);
102
+ body.set('client_secret', resolved.clientSecret);
103
+ }
104
+ let response;
105
+ try {
106
+ response = await doFetch(document.token_endpoint, {
107
+ method: 'POST',
108
+ headers,
109
+ body: body.toString(),
110
+ signal: AbortSignal.timeout(resolved.requestTimeoutMs)
111
+ });
112
+ }
113
+ catch (cause) {
114
+ throw new RelyperOidcError('token_exchange_failed', 'The identity provider could not be reached.', { cause });
115
+ }
116
+ const payload = await readJson(response);
117
+ if (!response.ok) {
118
+ // The IdP's own error code is safe to log; the body may hold more, so only
119
+ // the two documented fields are carried over.
120
+ const error = typeof payload?.error === 'string' ? payload.error : 'HTTP ' + response.status;
121
+ const description = typeof payload?.error_description === 'string' ? payload.error_description : '';
122
+ throw new RelyperOidcError('token_exchange_failed', 'The login could not be completed.', {
123
+ detail: description ? error + ': ' + description : error
124
+ });
125
+ }
126
+ const idToken = typeof payload?.id_token === 'string' ? payload.id_token : '';
127
+ if (!idToken) {
128
+ throw new RelyperOidcError('token_exchange_failed', 'The identity provider returned no ID token.', {
129
+ detail: 'token response without id_token'
130
+ });
131
+ }
132
+ return {
133
+ idToken,
134
+ accessToken: typeof payload?.access_token === 'string' ? payload.access_token : '',
135
+ tokenType: typeof payload?.token_type === 'string' ? payload.token_type : 'Bearer',
136
+ expiresIn: typeof payload?.expires_in === 'number' ? payload.expires_in : null,
137
+ refreshToken: typeof payload?.refresh_token === 'string' ? payload.refresh_token : null,
138
+ scope: typeof payload?.scope === 'string' ? payload.scope : null
139
+ };
140
+ }
141
+ async function verifyIdToken(document, idToken, nonce) {
142
+ let payload;
143
+ try {
144
+ const result = await jwtVerify(idToken, jwksFor(document.jwks_uri), {
145
+ issuer: document.issuer,
146
+ audience: resolved.clientId,
147
+ clockTolerance: resolved.clockToleranceSeconds,
148
+ // Pinning the algorithms is what stops a token downgraded to `none`, or
149
+ // to an HMAC signed with the provider's public key, from being accepted.
150
+ algorithms: ['RS256', 'RS384', 'RS512', 'PS256', 'ES256', 'ES384']
151
+ });
152
+ payload = result.payload;
153
+ }
154
+ catch (cause) {
155
+ throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
156
+ cause,
157
+ detail: cause instanceof Error ? cause.message : undefined
158
+ });
159
+ }
160
+ // OpenID Connect Core 3.1.3.7 step 11: the nonce ties this token to the login
161
+ // this browser started. Without the check a token captured elsewhere replays.
162
+ if (payload.nonce !== nonce) {
163
+ throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
164
+ detail: 'nonce mismatch'
165
+ });
166
+ }
167
+ // Step 12: with several audiences, `azp` has to name this client.
168
+ if (Array.isArray(payload.aud) && payload.aud.length > 1 && payload.azp !== resolved.clientId) {
169
+ throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
170
+ detail: 'azp does not match the client id'
171
+ });
172
+ }
173
+ if (typeof payload.sub !== 'string' || !payload.sub) {
174
+ throw new RelyperOidcError('invalid_id_token', 'The identity provider returned an invalid token.', {
175
+ detail: 'missing sub claim'
176
+ });
177
+ }
178
+ return payload;
179
+ }
180
+ async function fetchUserInfo(document, accessToken) {
181
+ if (!document.userinfo_endpoint || !accessToken)
182
+ return {};
183
+ let response;
184
+ try {
185
+ response = await doFetch(document.userinfo_endpoint, {
186
+ method: 'GET',
187
+ headers: { authorization: 'Bearer ' + accessToken, accept: 'application/json' },
188
+ signal: AbortSignal.timeout(resolved.requestTimeoutMs)
189
+ });
190
+ }
191
+ catch (cause) {
192
+ throw new RelyperOidcError('userinfo_failed', 'The identity provider could not be reached.', { cause });
193
+ }
194
+ if (!response.ok) {
195
+ throw new RelyperOidcError('userinfo_failed', 'The user profile could not be read.', {
196
+ detail: 'HTTP ' + response.status
197
+ });
198
+ }
199
+ return (await readJson(response)) ?? {};
200
+ }
201
+ return {
202
+ options: resolved,
203
+ discover: () => discovery.load(),
204
+ async createAuthorizationRequest(requestOptions = {}) {
205
+ const document = await discovery.load();
206
+ const transaction = {
207
+ state: createState(),
208
+ nonce: createNonce(),
209
+ codeVerifier: createCodeVerifier(),
210
+ returnTo: safeReturnTo(requestOptions.returnTo)
211
+ };
212
+ const url = new URL(document.authorization_endpoint);
213
+ url.searchParams.set('response_type', 'code');
214
+ url.searchParams.set('client_id', resolved.clientId);
215
+ url.searchParams.set('redirect_uri', resolved.redirectUri);
216
+ url.searchParams.set('scope', resolved.scope);
217
+ url.searchParams.set('state', transaction.state);
218
+ url.searchParams.set('nonce', transaction.nonce);
219
+ url.searchParams.set('code_challenge', codeChallengeFor(transaction.codeVerifier));
220
+ url.searchParams.set('code_challenge_method', 'S256');
221
+ if (requestOptions.prompt)
222
+ url.searchParams.set('prompt', requestOptions.prompt);
223
+ if (requestOptions.loginHint)
224
+ url.searchParams.set('login_hint', requestOptions.loginHint);
225
+ return { url: url.toString(), transaction };
226
+ },
227
+ async completeLogin({ query, transaction }) {
228
+ const idpError = firstValue(query.error);
229
+ if (idpError) {
230
+ throw new RelyperOidcError('idp_error', 'The identity provider refused the login.', {
231
+ status: 401,
232
+ detail: [idpError, firstValue(query.error_description)].filter(Boolean).join(': ')
233
+ });
234
+ }
235
+ const state = firstValue(query.state);
236
+ // A constant-time comparison would buy nothing here: whoever can measure
237
+ // the response already holds the cookie this is compared against.
238
+ if (!state || state !== transaction.state) {
239
+ throw new RelyperOidcError('invalid_state', 'The login could not be verified. Please try again.', {
240
+ status: 400,
241
+ detail: state ? 'state does not match the login transaction' : 'state parameter missing'
242
+ });
243
+ }
244
+ const code = firstValue(query.code);
245
+ if (!code) {
246
+ throw new RelyperOidcError('idp_error', 'The identity provider returned no authorization code.', {
247
+ status: 400
248
+ });
249
+ }
250
+ const document = await discovery.load();
251
+ const tokens = await exchangeCode(document, code, transaction.codeVerifier);
252
+ const idClaims = await verifyIdToken(document, tokens.idToken, transaction.nonce);
253
+ let claims = { ...idClaims };
254
+ if (resolved.useUserInfo) {
255
+ const userInfo = await fetchUserInfo(document, tokens.accessToken);
256
+ // OpenID Connect Core 5.3.2: a UserInfo response whose `sub` differs from
257
+ // the ID token's must be discarded outright.
258
+ if (userInfo.sub && userInfo.sub !== idClaims.sub) {
259
+ throw new RelyperOidcError('userinfo_failed', 'The user profile could not be read.', {
260
+ detail: 'userinfo sub does not match the id token'
261
+ });
262
+ }
263
+ claims = { ...claims, ...userInfo };
264
+ }
265
+ const identity = mapClaims(claims);
266
+ if (resolved.requireEmail && !identity.email) {
267
+ throw new RelyperOidcError('missing_email', 'The identity provider supplied no e-mail address.', {
268
+ status: 403
269
+ });
270
+ }
271
+ if (resolved.requiredRoles.length && !matchesRoles(identity.roles, resolved.requiredRoles, resolved.roleMatch)) {
272
+ throw new RelyperOidcError('missing_role', 'Your account does not have access to this application.', {
273
+ status: 403,
274
+ detail: 'presented roles: ' + (identity.roles.join(', ') || '(none)')
275
+ });
276
+ }
277
+ return { identity, claims, tokens };
278
+ },
279
+ hasRole(identity, role, match = 'any') {
280
+ const wanted = Array.isArray(role) ? role : [role];
281
+ return matchesRoles(identity.roles, wanted, match);
282
+ },
283
+ async endSessionUrl(endSessionOptions = {}) {
284
+ const document = await discovery.load();
285
+ if (!document.end_session_endpoint)
286
+ return null;
287
+ const url = new URL(document.end_session_endpoint);
288
+ if (endSessionOptions.idTokenHint)
289
+ url.searchParams.set('id_token_hint', endSessionOptions.idTokenHint);
290
+ if (endSessionOptions.postLogoutRedirectUri) {
291
+ url.searchParams.set('post_logout_redirect_uri', endSessionOptions.postLogoutRedirectUri);
292
+ }
293
+ url.searchParams.set('client_id', resolved.clientId);
294
+ return url.toString();
295
+ }
296
+ };
297
+ }
298
+ function matchesRoles(actual, required, match) {
299
+ return match === 'all'
300
+ ? required.every((role) => actual.includes(role))
301
+ : required.some((role) => actual.includes(role));
302
+ }
303
+ function firstValue(value) {
304
+ if (Array.isArray(value))
305
+ return typeof value[0] === 'string' ? value[0] : '';
306
+ return typeof value === 'string' ? value : '';
307
+ }
308
+ async function readJson(response) {
309
+ try {
310
+ return (await response.json());
311
+ }
312
+ catch {
313
+ return null;
314
+ }
315
+ }
316
+ function trimTrailingSlashes(value) {
317
+ let end = value.length;
318
+ while (end > 0 && value[end - 1] === '/')
319
+ end -= 1;
320
+ return value.slice(0, end);
321
+ }
322
+ /**
323
+ * Only a local path is ever used as a post-login target. An absolute URL, or a
324
+ * protocol-relative one starting with two slashes, would turn the login route
325
+ * into an open redirect. Backslashes are rejected too, because some browsers
326
+ * normalise them to forward slashes and would follow "/\\evil.example".
327
+ */
328
+ export function safeReturnTo(value, fallback = '/') {
329
+ const candidate = typeof value === 'string' ? value.trim() : '';
330
+ if (!candidate)
331
+ return fallback;
332
+ if (!candidate.startsWith('/'))
333
+ return fallback;
334
+ if (candidate.startsWith('//'))
335
+ return fallback;
336
+ if (candidate.includes('\\'))
337
+ return fallback;
338
+ return candidate;
339
+ }
340
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/oidc/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type OidcDiscoveryDocument } from './types.js';
2
+ export type DiscoveryLoaderOptions = {
3
+ issuer: string;
4
+ ttlMs: number;
5
+ requestTimeoutMs: number;
6
+ fetch: typeof globalThis.fetch;
7
+ };
8
+ export type DiscoveryLoader = {
9
+ load(): Promise<OidcDiscoveryDocument>;
10
+ /** Drops the cache, e.g. after a JWKS lookup failed against a rotated IdP. */
11
+ invalidate(): void;
12
+ };
13
+ export declare function discoveryUrl(issuer: string): string;
14
+ export declare function createDiscoveryLoader(options: DiscoveryLoaderOptions): DiscoveryLoader;
15
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oidc/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAY1E,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACvC,8EAA8E;IAC9E,UAAU,IAAI,IAAI,CAAC;CACpB,CAAC;AAEF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CA8EtF"}
@@ -0,0 +1,90 @@
1
+ import { RelyperOidcError } from './types.js';
2
+ /**
3
+ * Reads and caches the IdP's discovery document.
4
+ *
5
+ * The endpoints are never hardcoded: an IdP is free to move them, and the
6
+ * document is the contract. Concurrent callers share one in-flight request so a
7
+ * burst of logins does not turn into a burst of requests against the IdP.
8
+ */
9
+ const REQUIRED_FIELDS = ['issuer', 'authorization_endpoint', 'token_endpoint', 'jwks_uri'];
10
+ export function discoveryUrl(issuer) {
11
+ const base = issuer.replace(/\/+$/, '');
12
+ return base + '/.well-known/openid-configuration';
13
+ }
14
+ export function createDiscoveryLoader(options) {
15
+ let cached = null;
16
+ let inFlight = null;
17
+ async function fetchDocument() {
18
+ const url = discoveryUrl(options.issuer);
19
+ let response;
20
+ try {
21
+ response = await options.fetch(url, {
22
+ method: 'GET',
23
+ headers: { accept: 'application/json' },
24
+ signal: AbortSignal.timeout(options.requestTimeoutMs)
25
+ });
26
+ }
27
+ catch (cause) {
28
+ throw new RelyperOidcError('discovery_failed', 'The identity provider could not be reached.', {
29
+ cause,
30
+ detail: 'GET ' + url
31
+ });
32
+ }
33
+ if (!response.ok) {
34
+ throw new RelyperOidcError('discovery_failed', 'The identity provider could not be reached.', {
35
+ detail: 'GET ' + url + ' returned HTTP ' + response.status
36
+ });
37
+ }
38
+ let document;
39
+ try {
40
+ document = (await response.json());
41
+ }
42
+ catch (cause) {
43
+ throw new RelyperOidcError('discovery_failed', 'The identity provider returned a malformed discovery document.', {
44
+ cause,
45
+ detail: 'GET ' + url
46
+ });
47
+ }
48
+ for (const field of REQUIRED_FIELDS) {
49
+ if (typeof document?.[field] !== 'string' || !document[field]) {
50
+ throw new RelyperOidcError('discovery_failed', 'The discovery document is incomplete.', {
51
+ detail: 'missing field: ' + field
52
+ });
53
+ }
54
+ }
55
+ // The issuer is the identity of the IdP and is compared against the `iss`
56
+ // claim later. A document that names a different issuer than the one we
57
+ // configured means we are talking to the wrong party.
58
+ if (trimSlash(document.issuer) !== trimSlash(options.issuer)) {
59
+ throw new RelyperOidcError('discovery_failed', 'The identity provider reports a different issuer.', {
60
+ detail: 'configured ' + options.issuer + ', document ' + document.issuer
61
+ });
62
+ }
63
+ return document;
64
+ }
65
+ return {
66
+ async load() {
67
+ const now = Date.now();
68
+ if (cached && cached.expiresAt > now)
69
+ return cached.document;
70
+ if (inFlight)
71
+ return inFlight;
72
+ inFlight = fetchDocument()
73
+ .then((document) => {
74
+ cached = { document, expiresAt: Date.now() + options.ttlMs };
75
+ return document;
76
+ })
77
+ .finally(() => {
78
+ inFlight = null;
79
+ });
80
+ return inFlight;
81
+ },
82
+ invalidate() {
83
+ cached = null;
84
+ }
85
+ };
86
+ }
87
+ function trimSlash(value) {
88
+ return value.replace(/\/+$/, '');
89
+ }
90
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/oidc/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function createCodeVerifier(): string;
2
+ export declare function createState(): string;
3
+ export declare function createNonce(): string;
4
+ /** S256 challenge for a verifier. `plain` is deliberately not offered. */
5
+ export declare function codeChallengeFor(verifier: string): string;
6
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/oidc/pkce.ts"],"names":[],"mappings":"AAeA,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD"}
@@ -0,0 +1,26 @@
1
+ import { randomBytes, createHash } from 'node:crypto';
2
+ /**
3
+ * Client side of Proof Key for Code Exchange (RFC 7636) plus the random values
4
+ * that protect the authorization request: `state` against CSRF on the callback,
5
+ * `nonce` against a replayed ID token.
6
+ *
7
+ * All three are 256 bits of CSPRNG output rendered as base64url, which lands in
8
+ * the 43 characters RFC 7636 asks for as a minimum.
9
+ */
10
+ function randomUrlSafe(byteLength = 32) {
11
+ return randomBytes(byteLength).toString('base64url');
12
+ }
13
+ export function createCodeVerifier() {
14
+ return randomUrlSafe(32);
15
+ }
16
+ export function createState() {
17
+ return randomUrlSafe(32);
18
+ }
19
+ export function createNonce() {
20
+ return randomUrlSafe(32);
21
+ }
22
+ /** S256 challenge for a verifier. `plain` is deliberately not offered. */
23
+ export function codeChallengeFor(verifier) {
24
+ return createHash('sha256').update(verifier).digest('base64url');
25
+ }
26
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oidc/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;GAOG;AAEH,SAAS,aAAa,CAAC,UAAU,GAAG,EAAE;IACpC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,aAAa,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { RelyperIdentity } from '../types.js';
2
+ import type { RelyperLoginTransaction } from './types.js';
3
+ export type SessionPayload = {
4
+ identity: RelyperIdentity;
5
+ /** Seconds since the epoch at which the IdP login happened. */
6
+ authenticatedAt: number;
7
+ /**
8
+ * Identifier of this login. The session itself is stateless, so this exists
9
+ * for the application: recording it at logout and rejecting it afterwards is
10
+ * what turns "the browser dropped its cookie" into a revocation that also
11
+ * stops a cookie someone copied beforehand.
12
+ */
13
+ sessionId: string;
14
+ /**
15
+ * ID token, kept only when `keepIdToken` is on -- it is needed as the
16
+ * `id_token_hint` of an RP-initiated logout and for nothing else.
17
+ */
18
+ idToken?: string;
19
+ };
20
+ /** Opaque, unguessable identifier for one login. */
21
+ export declare function createSessionId(): string;
22
+ export type SealedCodec<T> = {
23
+ seal(value: T, ttlSeconds: number): Promise<string>;
24
+ /** Returns null for anything that is not a valid, unexpired token of this codec. */
25
+ open(token: string | undefined | null): Promise<T | null>;
26
+ };
27
+ export declare function createSessionCodec(secret: string): SealedCodec<SessionPayload>;
28
+ export declare function createLoginCodec(secret: string): SealedCodec<RelyperLoginTransaction>;
29
+ /** Constant-time string comparison for CSRF-style tokens of equal expected length. */
30
+ export declare function safeEqual(a: string, b: string): boolean;
31
+ export type CookieOptions = {
32
+ path?: string;
33
+ domain?: string;
34
+ maxAgeSeconds?: number;
35
+ httpOnly?: boolean;
36
+ secure?: boolean;
37
+ sameSite?: 'Lax' | 'Strict' | 'None';
38
+ };
39
+ /**
40
+ * Minimal cookie serialisation, so consumers are not forced to install a cookie
41
+ * plugin just to use this package.
42
+ */
43
+ export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
44
+ /** Cookie header that deletes `name`. */
45
+ export declare function clearCookie(name: string, options?: CookieOptions): string;
46
+ export declare function parseCookies(header: string | undefined | null): Record<string, string>;
47
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/oidc/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAwB1D,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,eAAe,CAAC;IAC1B,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,oDAAoD;AACpD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,oFAAoF;IACpF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CAC3D,CAAC;AAqCF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,cAAc,CAAC,CAE9E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,uBAAuB,CAAC,CAErF;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKvD;AAID,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACtC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAchG;AAED,yCAAyC;AACzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE7E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBtF"}