@eventuras/fides-auth 0.14.0 → 0.15.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 +225 -33
- package/dist/activity-tracker.d.ts +1 -1
- package/dist/activity-tracker.d.ts.map +1 -1
- package/dist/activity-tracker.js +1 -2
- package/dist/cookies.d.ts +6 -0
- package/dist/cookies.d.ts.map +1 -1
- package/dist/cookies.js +8 -3
- package/dist/cookies.js.map +1 -1
- package/dist/heartbeat.d.ts +1 -1
- package/dist/heartbeat.js +1 -2
- package/dist/id-token.d.ts +31 -0
- package/dist/id-token.d.ts.map +1 -0
- package/dist/id-token.js +60 -0
- package/dist/id-token.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/logger.js +1 -2
- package/dist/oauth-browser.d.ts +13 -10
- package/dist/oauth-browser.d.ts.map +1 -1
- package/dist/oauth-browser.js +4 -2
- package/dist/oauth-browser.js.map +1 -1
- package/dist/oauth-config.d.ts +70 -0
- package/dist/oauth-config.d.ts.map +1 -0
- package/dist/oauth-logging.d.ts +6 -5
- package/dist/oauth-logging.d.ts.map +1 -1
- package/dist/oauth-logging.js +5 -5
- package/dist/oauth-logging.js.map +1 -1
- package/dist/oauth.d.ts +48 -46
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +58 -29
- package/dist/oauth.js.map +1 -1
- package/dist/oidc-discovery.d.ts +13 -4
- package/dist/oidc-discovery.d.ts.map +1 -1
- package/dist/oidc-discovery.js +82 -15
- package/dist/oidc-discovery.js.map +1 -1
- package/dist/pkce.d.ts +10 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +23 -0
- package/dist/pkce.js.map +1 -0
- package/dist/providers/vipps/client.d.ts +4 -3
- package/dist/providers/vipps/client.d.ts.map +1 -1
- package/dist/providers/vipps/index.js +10 -10
- package/dist/providers/vipps/index.js.map +1 -1
- package/dist/providers/vipps/types.d.ts +10 -10
- package/dist/providers/vipps/types.d.ts.map +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +1 -2
- package/dist/server/authorized-fetch.d.ts +78 -0
- package/dist/server/authorized-fetch.d.ts.map +1 -0
- package/dist/server/cookie-store.d.ts +1 -1
- package/dist/server/heartbeat-handler.d.ts +7 -5
- package/dist/server/heartbeat-handler.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +744 -94
- package/dist/server/index.js.map +1 -1
- package/dist/server/login-transaction.d.ts +55 -0
- package/dist/server/login-transaction.d.ts.map +1 -0
- package/dist/server/oidc-callback.d.ts +14 -5
- package/dist/server/oidc-callback.d.ts.map +1 -1
- package/dist/server/oidc-login.d.ts +15 -6
- package/dist/server/oidc-login.d.ts.map +1 -1
- package/dist/server/oidc-logout.d.ts +7 -5
- package/dist/server/oidc-logout.d.ts.map +1 -1
- package/dist/server/session-persistence.d.ts +102 -0
- package/dist/server/session-persistence.d.ts.map +1 -0
- package/dist/server/session-store.d.ts +39 -0
- package/dist/server/session-store.d.ts.map +1 -0
- package/dist/server/session.d.ts +38 -31
- package/dist/server/session.d.ts.map +1 -1
- package/dist/session-cookies.d.ts +2 -1
- package/dist/session-cookies.d.ts.map +1 -1
- package/dist/session-cookies.js +4 -3
- package/dist/session-cookies.js.map +1 -1
- package/dist/session-events.d.ts +16 -9
- package/dist/session-events.d.ts.map +1 -1
- package/dist/session-events.js +4 -3
- package/dist/session-events.js.map +1 -1
- package/dist/session-refresh.d.ts +2 -2
- package/dist/session-refresh.d.ts.map +1 -1
- package/dist/session-refresh.js +1 -2
- package/dist/session-refresh.js.map +1 -1
- package/dist/session-validation.d.ts.map +1 -1
- package/dist/session-validation.js +9 -6
- package/dist/session-validation.js.map +1 -1
- package/dist/silent-login.d.ts +14 -5
- package/dist/silent-login.d.ts.map +1 -1
- package/dist/silent-login.js +3 -3
- package/dist/silent-login.js.map +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -15
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -24
- package/dist/utils.js.map +1 -1
- package/docs/discovery-cache.md +7 -3
- package/docs/rp-initiated-logout.md +6 -3
- package/docs/server-sessions.md +154 -0
- package/docs/session-events.md +17 -16
- package/package.json +18 -10
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Framework-agnostic OAuth 2.0 / OpenID Connect library with PKCE, encrypted sessi
|
|
|
8
8
|
- **Pushed Authorization Requests** — Opt-in RFC 9126 support (`usePar`) when the provider advertises it
|
|
9
9
|
- **Dual environment** — Node.js (`openid-client`) and browser (Web Crypto) entry points
|
|
10
10
|
- **Encrypted sessions** — AES-256-GCM encrypted JWTs for secure session storage
|
|
11
|
+
- **Server-side sessions** — Opt-in store behind an opaque handle cookie, for providers whose tokens don't fit a cookie
|
|
11
12
|
- **Rate limiting** — Generic token-bucket algorithm, ready for login protection
|
|
12
13
|
- **RP-initiated logout** — End the provider's session too, with `id_token_hint` (OIDC RP-Initiated Logout 1.0)
|
|
13
14
|
- **Silent login** — Detect `prompt=none` results without user interaction
|
|
@@ -68,37 +69,6 @@ const jwt = await createEncryptedJWT({ user, tokens }, secret);
|
|
|
68
69
|
const { status, session } = await validateSessionJwt(jwt, secret);
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
### Browser — OAuth SPA Flow
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import {
|
|
75
|
-
generatePKCE,
|
|
76
|
-
buildAuthorizationUrl,
|
|
77
|
-
exchangeCodeForTokens,
|
|
78
|
-
} from "@eventuras/fides-auth/oauth-browser";
|
|
79
|
-
|
|
80
|
-
const config = {
|
|
81
|
-
issuer: "https://auth.example.com",
|
|
82
|
-
clientId: "my-client-id",
|
|
83
|
-
redirect_uri: "https://myapp.com/callback",
|
|
84
|
-
scope: "openid profile email",
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Start flow
|
|
88
|
-
const pkce = await generatePKCE();
|
|
89
|
-
sessionStorage.setItem("code_verifier", pkce.code_verifier);
|
|
90
|
-
sessionStorage.setItem("state", pkce.state);
|
|
91
|
-
window.location.href = buildAuthorizationUrl(config, pkce);
|
|
92
|
-
|
|
93
|
-
// Handle callback
|
|
94
|
-
const code = new URLSearchParams(location.search).get("code");
|
|
95
|
-
const tokens = await exchangeCodeForTokens(
|
|
96
|
-
config,
|
|
97
|
-
code,
|
|
98
|
-
sessionStorage.getItem("code_verifier")!,
|
|
99
|
-
);
|
|
100
|
-
```
|
|
101
|
-
|
|
102
72
|
## Modules
|
|
103
73
|
|
|
104
74
|
Each module is available as a separate entry point for tree-shaking:
|
|
@@ -107,7 +77,9 @@ Each module is available as a separate entry point for tree-shaking:
|
|
|
107
77
|
| ------------------------------------------ | ----------- | ----------------------------------------------------- |
|
|
108
78
|
| `@eventuras/fides-auth` | Node.js | Main entry — re-exports utils, session, types, logger |
|
|
109
79
|
| `@eventuras/fides-auth/oauth` | Node.js | OAuth/OIDC flows with `openid-client` |
|
|
110
|
-
| `@eventuras/fides-auth/
|
|
80
|
+
| `@eventuras/fides-auth/pkce` | Universal | PKCE verifier/challenge and `state` (Web Crypto) |
|
|
81
|
+
| `@eventuras/fides-auth/id-token` | Universal | ID token verification against the provider JWKS |
|
|
82
|
+
| `@eventuras/fides-auth/oauth-browser` | Browser | **Deprecated** — OAuth/OIDC flows with Web Crypto API |
|
|
111
83
|
| `@eventuras/fides-auth/session-validation` | Node.js | Encrypted JWT session validation |
|
|
112
84
|
| `@eventuras/fides-auth/session-refresh` | Node.js | Automatic token refresh for sessions |
|
|
113
85
|
| `@eventuras/fides-auth/silent-login` | Node.js | Silent login (`prompt=none`) utilities |
|
|
@@ -138,6 +110,133 @@ import {
|
|
|
138
110
|
} from "@eventuras/fides-auth/oauth";
|
|
139
111
|
```
|
|
140
112
|
|
|
113
|
+
`extractUserFromTokens` and `buildSessionFromTokens` only decode the ID token —
|
|
114
|
+
they don't check its signature, `iss`, `aud` or `exp`. That is safe on tokens
|
|
115
|
+
straight from `exchangeAuthorizationCode`. Verify tokens from anywhere else
|
|
116
|
+
first with `verifyIdToken` from `@eventuras/fides-auth/id-token`.
|
|
117
|
+
|
|
118
|
+
#### Session mapping
|
|
119
|
+
|
|
120
|
+
`buildSessionFromTokens` keeps the standard fields. To keep more of the token
|
|
121
|
+
response or the ID token claims, pass `mapSession`, here or to
|
|
122
|
+
`handleOidcCallback`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const session = buildSessionFromTokens(tokens, {
|
|
126
|
+
rolesClaim: "groups",
|
|
127
|
+
mapSession: (tokens, claims, session) => ({
|
|
128
|
+
...session,
|
|
129
|
+
data: { tenant: tokens.tenant },
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`claims` are decoded, not verified. Without an `id_token` (no `openid` scope),
|
|
135
|
+
`claims` is undefined and the session has no `user`. `user.email` is optional.
|
|
136
|
+
Extra fields make the session bigger; large ones need
|
|
137
|
+
[server-side sessions](./docs/server-sessions.md).
|
|
138
|
+
|
|
139
|
+
#### Client authentication
|
|
140
|
+
|
|
141
|
+
`clientAuth` sets how the client authenticates at the token endpoint. Omit it
|
|
142
|
+
for `client_secret_post`:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
{ clientSecret: "…" } // client_secret_post
|
|
146
|
+
{ clientAuth: "client_secret_basic", clientSecret: "…" }
|
|
147
|
+
{ clientAuth: "none" } // public client, PKCE only
|
|
148
|
+
{ clientAuth: "private_key_jwt", privateKey } // CryptoKey or { key, kid }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`clientCredentialsGrant` takes the same options except `none`.
|
|
152
|
+
|
|
153
|
+
#### Server metadata instead of discovery
|
|
154
|
+
|
|
155
|
+
If the server publishes its metadata somewhere other than
|
|
156
|
+
`/.well-known/openid-configuration` (an RFC 8414 document, say), fetch it
|
|
157
|
+
yourself and pass it as `serverMetadata`. fides-auth then skips discovery:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const oauthConfig: OAuthConfig = {
|
|
161
|
+
issuer: "https://auth.example.com",
|
|
162
|
+
clientId: "your-client-id",
|
|
163
|
+
clientAuth: "none",
|
|
164
|
+
redirect_uri: "https://app.example.com/callback",
|
|
165
|
+
scope: "openid profile",
|
|
166
|
+
serverMetadata: await fetch(metadataUrl).then((r) => r.json()),
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
With `openid` in the scope the metadata must include `issuer`, so the ID token's
|
|
171
|
+
`iss` can be checked.
|
|
172
|
+
|
|
173
|
+
#### PKCE and state (`/pkce`)
|
|
174
|
+
|
|
175
|
+
For flows that manage their own login transaction. Web Crypto only, so it also
|
|
176
|
+
runs in the browser and on edge runtimes:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { createPkcePair, createState } from "@eventuras/fides-auth/pkce";
|
|
180
|
+
|
|
181
|
+
const { verifier, challenge } = await createPkcePair(); // S256
|
|
182
|
+
const state = createState();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`buildPKCEOptions` is built on the same two functions.
|
|
186
|
+
|
|
187
|
+
#### ID token verification (`/id-token`)
|
|
188
|
+
|
|
189
|
+
Checks an ID token's signature, `iss`, `aud` and `exp` against the provider's
|
|
190
|
+
JWKS and returns the claims. Also runs in the browser:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { verifyIdToken } from "@eventuras/fides-auth/id-token";
|
|
194
|
+
|
|
195
|
+
const claims = await verifyIdToken(idToken, {
|
|
196
|
+
issuer: "https://auth.example.com",
|
|
197
|
+
audience: "your-client-id",
|
|
198
|
+
oauthConfig, // or jwksUri: "https://auth.example.com/jwks"
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`algorithms` defaults to `RS*`, `PS*`, `ES*` and `EdDSA`. The JWKS is cached per URI.
|
|
203
|
+
|
|
204
|
+
#### Scope and extra authorization parameters
|
|
205
|
+
|
|
206
|
+
`scope` takes a string or an array. Other authorization parameters (`prompt`,
|
|
207
|
+
`login_hint`, `resource`, …) can be set on the config, per call, or per request
|
|
208
|
+
in `handleOidcLogin`. They go through PAR too.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const oauthConfig = {
|
|
212
|
+
// …
|
|
213
|
+
scope: ["openid", "profile", "offline_access"],
|
|
214
|
+
authorizationParameters: { resource: "https://api.example.com" },
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const pkce = await buildPKCEOptions(oauthConfig, {
|
|
218
|
+
parameters: { login_hint: "user@example.com" },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Or per request in the server handler
|
|
222
|
+
return handleOidcLogin(request, {
|
|
223
|
+
oauthConfig,
|
|
224
|
+
cookies,
|
|
225
|
+
secret: process.env.SESSION_SECRET!,
|
|
226
|
+
authorizationParameters: (request) =>
|
|
227
|
+
new URL(request.url).searchParams.has("reauth") ? { prompt: "login" } : {},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Per-call parameters override the config's, and a `scope` among them replaces
|
|
232
|
+
`OAuthConfig.scope`. These keys are reserved, and passing one throws:
|
|
233
|
+
|
|
234
|
+
- `state`, `code_challenge`, `code_challenge_method` and `redirect_uri`, which
|
|
235
|
+
fides-auth sets.
|
|
236
|
+
- `client_id` and `response_type`, which openid-client sets.
|
|
237
|
+
- `request` and `request_uri`, which would bypass PKCE and `state`.
|
|
238
|
+
- `nonce`, which the callback doesn't check, so the login would fail.
|
|
239
|
+
|
|
141
240
|
#### Pushed Authorization Requests (PAR, RFC 9126)
|
|
142
241
|
|
|
143
242
|
Opt in by setting `usePar: true` on your `OAuthConfig`. The convenience
|
|
@@ -190,7 +289,7 @@ const logoutUrl = await buildOidcLogoutUrl(oauthConfig, {
|
|
|
190
289
|
```
|
|
191
290
|
|
|
192
291
|
The `/server` entry point wraps the whole dance — read the hint, clear the cookies,
|
|
193
|
-
redirect:
|
|
292
|
+
redirect. Its handlers never read the environment; pass the session `secret` in:
|
|
194
293
|
|
|
195
294
|
```typescript
|
|
196
295
|
import { handleOidcLogout } from "@eventuras/fides-auth/server";
|
|
@@ -198,6 +297,7 @@ import { handleOidcLogout } from "@eventuras/fides-auth/server";
|
|
|
198
297
|
return handleOidcLogout(request, {
|
|
199
298
|
oauthConfig,
|
|
200
299
|
cookies,
|
|
300
|
+
secret: process.env.SESSION_SECRET!,
|
|
201
301
|
postLogoutRedirectUri: "https://app.example.com/",
|
|
202
302
|
});
|
|
203
303
|
```
|
|
@@ -205,8 +305,100 @@ return handleOidcLogout(request, {
|
|
|
205
305
|
See [docs/rp-initiated-logout.md](./docs/rp-initiated-logout.md) for the full
|
|
206
306
|
parameter set, where the ID token is stored, and why `client_id` is still sent.
|
|
207
307
|
|
|
308
|
+
#### Server-side sessions
|
|
309
|
+
|
|
310
|
+
By default the session is encrypted into cookies. When a provider's tokens are
|
|
311
|
+
too large for that (OpenIddict's encrypted JWE tokens, for instance), keep the
|
|
312
|
+
session in a store and give the browser only a handle:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { serverPersistence, createMemorySessionStore } from "@eventuras/fides-auth/server";
|
|
316
|
+
|
|
317
|
+
const persistence = serverPersistence({
|
|
318
|
+
store: createMemorySessionStore(), // or your own SessionStore (Mongo, Redis, …)
|
|
319
|
+
secret: process.env.SESSION_SECRET!,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return handleHeartbeat(request, { oauthConfig, cookies, persistence });
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Logout, heartbeat and the session helpers take `persistence` in place of
|
|
326
|
+
`secret`. The callback takes both: `secret` also opens the login transaction.
|
|
327
|
+
The store gets encrypted records keyed by a hash of the handle; `clearSession`
|
|
328
|
+
deletes the record, so logout revokes a copied cookie too. See
|
|
329
|
+
[docs/server-sessions.md](./docs/server-sessions.md).
|
|
330
|
+
|
|
331
|
+
#### Login transaction
|
|
332
|
+
|
|
333
|
+
`handleOidcLogin` seals each login (state, PKCE verifier, returnTo, start time)
|
|
334
|
+
into one of three `__Host-oauth_tx_<n>` cookies, so parallel logins don't
|
|
335
|
+
collide; a fourth evicts the oldest, and a `returnTo` over 1 KB as JSON is dropped. With
|
|
336
|
+
an `http:` `redirect_uri` (plain-http development) the cookie is a plain,
|
|
337
|
+
non-Secure `oauth_tx_<n>`. `handleOidcCallback` rejects it after 10 minutes and
|
|
338
|
+
deletes it whatever the outcome. Pass both handlers the same `secret`. A denied
|
|
339
|
+
or cancelled login redirects to `defaultRedirectPath` with
|
|
340
|
+
`?login=error&error=<code>`, or `error=login_expired` once the login has ended.
|
|
341
|
+
|
|
342
|
+
Custom flows use the same helpers. Pass them the handlers' cookie options, so a
|
|
343
|
+
custom login and the built-in callback (or the reverse) agree on the cookie:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import {
|
|
347
|
+
createTransaction,
|
|
348
|
+
persistTransaction,
|
|
349
|
+
readTransaction,
|
|
350
|
+
clearTransaction,
|
|
351
|
+
validateCallback,
|
|
352
|
+
transactionCookieOptions,
|
|
353
|
+
} from "@eventuras/fides-auth/server";
|
|
354
|
+
|
|
355
|
+
const txCookie = transactionCookieOptions(oauthConfig.redirect_uri);
|
|
356
|
+
|
|
357
|
+
// Login
|
|
358
|
+
const pkce = await buildPKCEOptions(oauthConfig);
|
|
359
|
+
await persistTransaction(cookies, createTransaction(pkce, { returnTo }), secret, txCookie);
|
|
360
|
+
|
|
361
|
+
// Callback
|
|
362
|
+
try {
|
|
363
|
+
const transaction = await readTransaction(cookies, state, secret, txCookie);
|
|
364
|
+
if (!transaction) throw new Error("No login in progress");
|
|
365
|
+
validateCallback(query, transaction); // throws CallbackValidationError
|
|
366
|
+
// … exchangeAuthorizationCode(oauthConfig, url, transaction.codeVerifier, transaction.state)
|
|
367
|
+
} finally {
|
|
368
|
+
await clearTransaction(cookies, state, secret, txCookie);
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### API calls with the access token
|
|
373
|
+
|
|
374
|
+
`authorizedFetch` sends the session's access token only to URLs under
|
|
375
|
+
`allowedBase` (same origin, and a path under its path), never follows a
|
|
376
|
+
redirect, and times out after 10 seconds by default. A refused URL, non-2xx
|
|
377
|
+
response, timeout or failed `parse` throws an `AuthorizedFetchError` with a
|
|
378
|
+
`reason`, the status, and the URL without its query. On a 401 it calls `refresh`
|
|
379
|
+
once and retries once:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { authorizedFetch, tryRefreshSessionInStore } from "@eventuras/fides-auth/server";
|
|
383
|
+
|
|
384
|
+
const { data, session: current } = await authorizedFetch(session, `users/${id}`, {
|
|
385
|
+
allowedBase: "https://api.example.com/v1/",
|
|
386
|
+
parse: async (response) => parseUser(await response.json()),
|
|
387
|
+
refresh: () => tryRefreshSessionInStore(cookies, oauthConfig, secret),
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`refresh` may also return `refreshSessionInStore`'s result. Both persist the new
|
|
392
|
+
session; use the returned `session` for later calls. When the refresh fails, the
|
|
393
|
+
error is `refresh_failed`, and `error.refreshFailure.cause === "invalid_grant"`
|
|
394
|
+
means the user must log in again.
|
|
395
|
+
|
|
208
396
|
### OAuth — Browser (`/oauth-browser`)
|
|
209
397
|
|
|
398
|
+
> **Deprecated.** It hardcodes the provider endpoints and doesn't validate the
|
|
399
|
+
> token response. Use the server flow instead (`handleOidcLogin` /
|
|
400
|
+
> `handleOidcCallback` from `/server`). It will be removed in a later minor.
|
|
401
|
+
|
|
210
402
|
Lightweight browser-only flows using Web Crypto:
|
|
211
403
|
|
|
212
404
|
```typescript
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* Uses `focusin` rather than `focus` because `focus` does not bubble — focusing
|
|
30
30
|
* a nested input would otherwise be missed when listening on `window`.
|
|
31
31
|
*/
|
|
32
|
-
export declare const DEFAULT_ACTIVITY_EVENTS: readonly [
|
|
32
|
+
export declare const DEFAULT_ACTIVITY_EVENTS: readonly ['pointerdown', 'keydown', 'touchstart', 'focusin'];
|
|
33
33
|
export interface ActivityTrackerOptions {
|
|
34
34
|
/**
|
|
35
35
|
* Target to attach listeners to. Defaults to `globalThis.window` in the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-tracker.d.ts","sourceRoot":"","sources":["../src/activity-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"activity-tracker.d.ts","sourceRoot":"","sources":["../src/activity-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,YAClC,aAAa,EACb,SAAS,EACT,YAAY,EACZ,SAAS,CACD,CAAC;AAEX,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,uFAAuF;IACvF,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,eAAe,IAAI,MAAM,CAAC;IAC1B,uEAAuE;IACvE,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,6EAA6E;IAC7E,cAAc,IAAI,IAAI,CAAC;IACvB,oCAAoC;IACpC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAyC3F"}
|
package/dist/activity-tracker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/activity-tracker.ts
|
|
2
2
|
/**
|
|
3
3
|
* Records the most recent user activity timestamp by listening to DOM events.
|
|
4
4
|
*
|
|
@@ -69,7 +69,6 @@ function createActivityTracker(options = {}) {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
//#endregion
|
|
73
72
|
export { DEFAULT_ACTIVITY_EVENTS, createActivityTracker };
|
|
74
73
|
|
|
75
74
|
//# sourceMappingURL=activity-tracker.js.map
|
package/dist/cookies.d.ts
CHANGED
|
@@ -89,6 +89,12 @@ export declare const ID_TOKEN_COOKIE_NAME = "session_it";
|
|
|
89
89
|
* apart.
|
|
90
90
|
*/
|
|
91
91
|
export declare const SESSION_COOKIE_NAME = "session";
|
|
92
|
+
/**
|
|
93
|
+
* Name of the cookie holding the opaque session handle under server
|
|
94
|
+
* persistence, where the session itself lives in a `SessionStore` and no token
|
|
95
|
+
* reaches the browser. See `server/session-persistence.ts`.
|
|
96
|
+
*/
|
|
97
|
+
export declare const SESSION_REF_COOKIE_NAME = "session_ref";
|
|
92
98
|
/**
|
|
93
99
|
* Serialized byte size of a `name=value` cookie pair. The browser per-cookie
|
|
94
100
|
* limit applies to the serialized pair, so the `=` separator is counted too —
|
package/dist/cookies.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,UAAU,EAAE,MAAM;aAClB,IAAI,EAAE,MAAM;aACZ,KAAK,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,UAAU,EAAE,MAAM;aAClB,IAAI,EAAE,MAAM;aACZ,KAAK,EAAE,MAAM;IAH/B,YACkB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAyB,EAMjD;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,2BAA2B,EAAE,aAMzC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,aAMvC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM3E"}
|
package/dist/cookies.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/cookies.ts
|
|
2
2
|
/**
|
|
3
3
|
* Browser per-cookie size limit (RFC 6265: browsers must support at least
|
|
4
4
|
* 4096 bytes for the sum of the cookie's name and value). Cookies at or above
|
|
@@ -93,6 +93,12 @@ var ID_TOKEN_COOKIE_NAME = "session_it";
|
|
|
93
93
|
*/
|
|
94
94
|
var SESSION_COOKIE_NAME = "session";
|
|
95
95
|
/**
|
|
96
|
+
* Name of the cookie holding the opaque session handle under server
|
|
97
|
+
* persistence, where the session itself lives in a `SessionStore` and no token
|
|
98
|
+
* reaches the browser. See `server/session-persistence.ts`.
|
|
99
|
+
*/
|
|
100
|
+
var SESSION_REF_COOKIE_NAME = "session_ref";
|
|
101
|
+
/**
|
|
96
102
|
* Serialized byte size of a `name=value` cookie pair. The browser per-cookie
|
|
97
103
|
* limit applies to the serialized pair, so the `=` separator is counted too —
|
|
98
104
|
* otherwise a borderline pair (name+value === 4095) would slip through only to be
|
|
@@ -112,7 +118,6 @@ function assertCookieWithinLimit(name, value) {
|
|
|
112
118
|
if (size >= 4096) throw new CookieTooLargeError(name, size);
|
|
113
119
|
return size;
|
|
114
120
|
}
|
|
115
|
-
|
|
116
|
-
export { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, assertCookieWithinLimit, cookieByteSize, defaultOAuthCookieOptions, defaultSessionCookieOptions };
|
|
121
|
+
export { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, SESSION_REF_COOKIE_NAME, assertCookieWithinLimit, cookieByteSize, defaultOAuthCookieOptions, defaultSessionCookieOptions };
|
|
117
122
|
|
|
118
123
|
//# sourceMappingURL=cookies.js.map
|
package/dist/cookies.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.js","names":[],"sources":["../src/cookies.ts"],"sourcesContent":["// cookies.ts\n//\n// Framework-agnostic cookie attributes, size limits, and the access-token cookie\n// name. This module performs no I/O — it never reads or writes cookies. Framework\n// adapters (Next.js, React Router, …) own the actual cookie store and use these\n// values and helpers to configure and size-check the cookies they set.\n//\n// The companion module `session-cookies.ts` encodes/decodes a session into cookie\n// *values*; this module is about the cookie *attributes* and *limits*.\n\n/**\n * Browser per-cookie size limit (RFC 6265: browsers must support at least\n * 4096 bytes for the sum of the cookie's name and value). Cookies at or above\n * this size are silently dropped by the browser, which manifests as a broken\n * login (the cookie is never stored, so the user appears unauthenticated).\n */\nexport const COOKIE_MAX_BYTES = 4096;\n\n/**\n * Threshold at which an adapter should emit an informational log that a cookie is\n * getting large. This is not an error — a session with many scopes/roles can\n * legitimately approach this size — it just gives visibility before the hard limit.\n */\nexport const COOKIE_INFO_BYTES = 3500;\n\n/**\n * Thrown when a cookie's name + value would meet or exceed the browser's\n * per-cookie size limit. Setting such a cookie would be silently dropped by the\n * browser, so adapters should fail loudly instead. Callers (e.g. an OIDC callback)\n * can catch this to surface a clear error rather than producing a broken login.\n */\nexport class CookieTooLargeError extends Error {\n constructor(\n public readonly cookieName: string,\n public readonly size: number,\n public readonly limit: number = COOKIE_MAX_BYTES,\n ) {\n super(\n `Cookie \"${cookieName}\" is ${size} bytes, which meets or exceeds the browser limit of ${limit} bytes and would be silently dropped`,\n );\n this.name = 'CookieTooLargeError';\n }\n}\n\nexport interface CookieOptions {\n /** Cookie path (default: '/') */\n path?: string;\n /** Max age in seconds */\n maxAge?: number;\n /** SameSite policy (default: 'lax') */\n sameSite?: 'strict' | 'lax' | 'none';\n /** HTTP only flag (default: true) */\n httpOnly?: boolean;\n /**\n * Secure flag. `true` in {@link defaultSessionCookieOptions} and\n * {@link defaultOAuthCookieOptions}; when omitted from a hand-built options\n * object, the adapter's own default applies.\n */\n secure?: boolean;\n}\n\n/**\n * Default cookie options for session cookies.\n * - Secure\n * - HTTP only\n * - Lax same-site policy\n * - 30 days max age\n *\n * `secure` is unconditional rather than derived from NODE_ENV: a deployment that\n * doesn't set it — staging, a container, any non-Next server — would otherwise put\n * the session cookie on the wire in the clear, silently.\n *\n * The cookie spec exempts localhost from the https requirement, so a `Secure`\n * cookie is still set and sent over `http://localhost` and local development is\n * unaffected. Plain http on any other host — a LAN address, a custom dev hostname —\n * needs an explicit `secure: false`.\n */\nexport const defaultSessionCookieOptions: CookieOptions = {\n path: '/',\n maxAge: 60 * 60 * 24 * 30, // 30 days\n sameSite: 'lax',\n httpOnly: true,\n secure: true,\n};\n\n/**\n * Default cookie options for OAuth state/PKCE cookies.\n * - Secure — see {@link defaultSessionCookieOptions}\n * - HTTP only\n * - Lax same-site policy\n * - 10 minutes max age (short-lived for security)\n */\nexport const defaultOAuthCookieOptions: CookieOptions = {\n path: '/',\n maxAge: 60 * 10, // 10 minutes\n sameSite: 'lax',\n httpOnly: true,\n secure: true,\n};\n\n/**\n * Name of the cookie holding the (split-out) access token.\n *\n * The access token — typically a large JWT carrying scopes/roles — is stored in\n * its own cookie so it gets a full per-cookie byte budget, instead of competing\n * for space with the rest of the session inside a single \"session\" cookie. See\n * {@link CookieTooLargeError} for the limit this works around.\n */\nexport const ACCESS_TOKEN_COOKIE_NAME = 'session_at';\n\n/**\n * Name of the cookie holding the (split-out) ID token, kept for `id_token_hint`\n * on RP-initiated logout.\n *\n * It gets its own cookie for two reasons: it is a second large JWT, which would\n * eat into the access token's byte budget, and it must outlive access-token\n * expiry — a logout after the session has gone stale is exactly when the hint is\n * needed.\n */\nexport const ID_TOKEN_COOKIE_NAME = 'session_it';\n\n/**\n * Name of the main session cookie (everything except the split-out access and ID\n * tokens). See {@link ACCESS_TOKEN_COOKIE_NAME} for why the raw tokens are stored\n * apart.\n */\nexport const SESSION_COOKIE_NAME = 'session';\n\n/**\n * Serialized byte size of a `name=value` cookie pair. The browser per-cookie\n * limit applies to the serialized pair, so the `=` separator is counted too —\n * otherwise a borderline pair (name+value === 4095) would slip through only to be\n * dropped at 4096 once serialized.\n */\nexport function cookieByteSize(name: string, value: string): number {\n const encoder = new TextEncoder();\n return encoder.encode(name).length + 1 + encoder.encode(value).length;\n}\n\n/**\n * Asserts that a `name=value` cookie pair is within the browser's per-cookie\n * limit, throwing {@link CookieTooLargeError} if not. Returns the measured byte\n * size so callers can additionally log when it crosses {@link COOKIE_INFO_BYTES}.\n */\nexport function assertCookieWithinLimit(name: string, value: string): number {\n const size = cookieByteSize(name, value);\n if (size >= COOKIE_MAX_BYTES) {\n throw new CookieTooLargeError(name, size);\n }\n return size;\n}\n"],"mappings":";;;;;;;AAgBA,IAAa,mBAAmB;;;;;;AAOhC,IAAa,oBAAoB;;;;;;;AAQjC,IAAa,sBAAb,cAAyC,MAAM;CAE3B;CACA;CACA;CAHlB,YACE,YACA,MACA,QAAgC,kBAChC;EACA,MACE,WAAW,WAAW,OAAO,KAAK,sDAAsD,MAAM,qCAChG;EANgB,KAAA,aAAA;EACA,KAAA,OAAA;EACA,KAAA,QAAA;EAKhB,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;;AAmCA,IAAa,8BAA6C;CACxD,MAAM;CACN,QAAQ;CACR,UAAU;CACV,UAAU;CACV,QAAQ;AACV;;;;;;;;AASA,IAAa,4BAA2C;CACtD,MAAM;CACN,QAAQ;CACR,UAAU;CACV,UAAU;CACV,QAAQ;AACV;;;;;;;;;AAUA,IAAa,2BAA2B;;;;;;;;;;AAWxC,IAAa,uBAAuB;;;;;;AAOpC,IAAa,sBAAsB;;;;;;;
|
|
1
|
+
{"version":3,"file":"cookies.js","names":[],"sources":["../src/cookies.ts"],"sourcesContent":["// cookies.ts\n//\n// Framework-agnostic cookie attributes, size limits, and the access-token cookie\n// name. This module performs no I/O — it never reads or writes cookies. Framework\n// adapters (Next.js, React Router, …) own the actual cookie store and use these\n// values and helpers to configure and size-check the cookies they set.\n//\n// The companion module `session-cookies.ts` encodes/decodes a session into cookie\n// *values*; this module is about the cookie *attributes* and *limits*.\n\n/**\n * Browser per-cookie size limit (RFC 6265: browsers must support at least\n * 4096 bytes for the sum of the cookie's name and value). Cookies at or above\n * this size are silently dropped by the browser, which manifests as a broken\n * login (the cookie is never stored, so the user appears unauthenticated).\n */\nexport const COOKIE_MAX_BYTES = 4096;\n\n/**\n * Threshold at which an adapter should emit an informational log that a cookie is\n * getting large. This is not an error — a session with many scopes/roles can\n * legitimately approach this size — it just gives visibility before the hard limit.\n */\nexport const COOKIE_INFO_BYTES = 3500;\n\n/**\n * Thrown when a cookie's name + value would meet or exceed the browser's\n * per-cookie size limit. Setting such a cookie would be silently dropped by the\n * browser, so adapters should fail loudly instead. Callers (e.g. an OIDC callback)\n * can catch this to surface a clear error rather than producing a broken login.\n */\nexport class CookieTooLargeError extends Error {\n constructor(\n public readonly cookieName: string,\n public readonly size: number,\n public readonly limit: number = COOKIE_MAX_BYTES,\n ) {\n super(\n `Cookie \"${cookieName}\" is ${size} bytes, which meets or exceeds the browser limit of ${limit} bytes and would be silently dropped`,\n );\n this.name = 'CookieTooLargeError';\n }\n}\n\nexport interface CookieOptions {\n /** Cookie path (default: '/') */\n path?: string;\n /** Max age in seconds */\n maxAge?: number;\n /** SameSite policy (default: 'lax') */\n sameSite?: 'strict' | 'lax' | 'none';\n /** HTTP only flag (default: true) */\n httpOnly?: boolean;\n /**\n * Secure flag. `true` in {@link defaultSessionCookieOptions} and\n * {@link defaultOAuthCookieOptions}; when omitted from a hand-built options\n * object, the adapter's own default applies.\n */\n secure?: boolean;\n}\n\n/**\n * Default cookie options for session cookies.\n * - Secure\n * - HTTP only\n * - Lax same-site policy\n * - 30 days max age\n *\n * `secure` is unconditional rather than derived from NODE_ENV: a deployment that\n * doesn't set it — staging, a container, any non-Next server — would otherwise put\n * the session cookie on the wire in the clear, silently.\n *\n * The cookie spec exempts localhost from the https requirement, so a `Secure`\n * cookie is still set and sent over `http://localhost` and local development is\n * unaffected. Plain http on any other host — a LAN address, a custom dev hostname —\n * needs an explicit `secure: false`.\n */\nexport const defaultSessionCookieOptions: CookieOptions = {\n path: '/',\n maxAge: 60 * 60 * 24 * 30, // 30 days\n sameSite: 'lax',\n httpOnly: true,\n secure: true,\n};\n\n/**\n * Default cookie options for OAuth state/PKCE cookies.\n * - Secure — see {@link defaultSessionCookieOptions}\n * - HTTP only\n * - Lax same-site policy\n * - 10 minutes max age (short-lived for security)\n */\nexport const defaultOAuthCookieOptions: CookieOptions = {\n path: '/',\n maxAge: 60 * 10, // 10 minutes\n sameSite: 'lax',\n httpOnly: true,\n secure: true,\n};\n\n/**\n * Name of the cookie holding the (split-out) access token.\n *\n * The access token — typically a large JWT carrying scopes/roles — is stored in\n * its own cookie so it gets a full per-cookie byte budget, instead of competing\n * for space with the rest of the session inside a single \"session\" cookie. See\n * {@link CookieTooLargeError} for the limit this works around.\n */\nexport const ACCESS_TOKEN_COOKIE_NAME = 'session_at';\n\n/**\n * Name of the cookie holding the (split-out) ID token, kept for `id_token_hint`\n * on RP-initiated logout.\n *\n * It gets its own cookie for two reasons: it is a second large JWT, which would\n * eat into the access token's byte budget, and it must outlive access-token\n * expiry — a logout after the session has gone stale is exactly when the hint is\n * needed.\n */\nexport const ID_TOKEN_COOKIE_NAME = 'session_it';\n\n/**\n * Name of the main session cookie (everything except the split-out access and ID\n * tokens). See {@link ACCESS_TOKEN_COOKIE_NAME} for why the raw tokens are stored\n * apart.\n */\nexport const SESSION_COOKIE_NAME = 'session';\n\n/**\n * Name of the cookie holding the opaque session handle under server\n * persistence, where the session itself lives in a `SessionStore` and no token\n * reaches the browser. See `server/session-persistence.ts`.\n */\nexport const SESSION_REF_COOKIE_NAME = 'session_ref';\n\n/**\n * Serialized byte size of a `name=value` cookie pair. The browser per-cookie\n * limit applies to the serialized pair, so the `=` separator is counted too —\n * otherwise a borderline pair (name+value === 4095) would slip through only to be\n * dropped at 4096 once serialized.\n */\nexport function cookieByteSize(name: string, value: string): number {\n const encoder = new TextEncoder();\n return encoder.encode(name).length + 1 + encoder.encode(value).length;\n}\n\n/**\n * Asserts that a `name=value` cookie pair is within the browser's per-cookie\n * limit, throwing {@link CookieTooLargeError} if not. Returns the measured byte\n * size so callers can additionally log when it crosses {@link COOKIE_INFO_BYTES}.\n */\nexport function assertCookieWithinLimit(name: string, value: string): number {\n const size = cookieByteSize(name, value);\n if (size >= COOKIE_MAX_BYTES) {\n throw new CookieTooLargeError(name, size);\n }\n return size;\n}\n"],"mappings":";;;;;;;AAgBA,IAAa,mBAAmB;;;;;;AAOhC,IAAa,oBAAoB;;;;;;;AAQjC,IAAa,sBAAb,cAAyC,MAAM;CAE3B;CACA;CACA;CAHlB,YACE,YACA,MACA,QAAgC,kBAChC;EACA,MACE,WAAW,WAAW,OAAO,KAAK,sDAAsD,MAAM,qCAChG;EANgB,KAAA,aAAA;EACA,KAAA,OAAA;EACA,KAAA,QAAA;EAKhB,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;;AAmCA,IAAa,8BAA6C;CACxD,MAAM;CACN,QAAQ;CACR,UAAU;CACV,UAAU;CACV,QAAQ;AACV;;;;;;;;AASA,IAAa,4BAA2C;CACtD,MAAM;CACN,QAAQ;CACR,UAAU;CACV,UAAU;CACV,QAAQ;AACV;;;;;;;;;AAUA,IAAa,2BAA2B;;;;;;;;;;AAWxC,IAAa,uBAAuB;;;;;;AAOpC,IAAa,sBAAsB;;;;;;AAOnC,IAAa,0BAA0B;;;;;;;AAQvC,SAAgB,eAAe,MAAc,OAAuB;CAClE,MAAM,UAAU,IAAI,YAAY;CAChC,OAAO,QAAQ,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI,QAAQ,OAAO,KAAK,CAAC,CAAC;AACjE;;;;;;AAOA,SAAgB,wBAAwB,MAAc,OAAuB;CAC3E,MAAM,OAAO,eAAe,MAAM,KAAK;CACvC,IAAI,QAAA,MACF,MAAM,IAAI,oBAAoB,MAAM,IAAI;CAE1C,OAAO;AACT"}
|
package/dist/heartbeat.d.ts
CHANGED
package/dist/heartbeat.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_ACTIVITY_EVENTS, createActivityTracker } from "./activity-tracker.js";
|
|
2
2
|
import { createLogger } from "./logger.js";
|
|
3
3
|
import { SESSION_EVENT, isSessionRejectedReason } from "./session-events.js";
|
|
4
|
-
|
|
4
|
+
// src/heartbeat.ts
|
|
5
5
|
var DEFAULTS = {
|
|
6
6
|
endpoint: "/api/auth/heartbeat",
|
|
7
7
|
fraction: .3,
|
|
@@ -197,7 +197,6 @@ function createHeartbeat(config = {}) {
|
|
|
197
197
|
}, "Heartbeat started");
|
|
198
198
|
return { stop: teardown };
|
|
199
199
|
}
|
|
200
|
-
//#endregion
|
|
201
200
|
export { createHeartbeat };
|
|
202
201
|
|
|
203
202
|
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type JWTPayload } from 'jose';
|
|
2
|
+
import type { OAuthConfig } from './oauth-config';
|
|
3
|
+
interface VerifyIdTokenBaseOptions {
|
|
4
|
+
/** Expected `iss`. */
|
|
5
|
+
issuer: string;
|
|
6
|
+
/** Expected `aud`, usually the client ID. */
|
|
7
|
+
audience: string | string[];
|
|
8
|
+
/** Accepted `alg` values. Defaults to `RS*`, `PS*`, `ES*` and `EdDSA`. */
|
|
9
|
+
algorithms?: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Options for {@link verifyIdToken}. Pass `jwksUri` or `oauthConfig`. */
|
|
12
|
+
export type VerifyIdTokenOptions = VerifyIdTokenBaseOptions & ({
|
|
13
|
+
/** The provider's JWKS URL. */
|
|
14
|
+
jwksUri: string | URL;
|
|
15
|
+
oauthConfig?: never;
|
|
16
|
+
} | {
|
|
17
|
+
/** Reads `jwks_uri` from discovery, or from `serverMetadata`. */
|
|
18
|
+
oauthConfig: OAuthConfig;
|
|
19
|
+
jwksUri?: never;
|
|
20
|
+
});
|
|
21
|
+
/** Drops every cached JWKS. Tests should call it between cases. */
|
|
22
|
+
export declare function clearJwksCache(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Verifies an ID token's signature, `iss`, `aud` and `exp` against the provider's JWKS.
|
|
25
|
+
*
|
|
26
|
+
* @returns The verified claims.
|
|
27
|
+
* @throws A jose error if the token is invalid, or a `TypeError` if no JWKS URI is found.
|
|
28
|
+
*/
|
|
29
|
+
export declare function verifyIdToken(idToken: string, options: VerifyIdTokenOptions): Promise<JWTPayload>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=id-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-token.d.ts","sourceRoot":"","sources":["../src/id-token.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,UAAU,EAAa,MAAM,MAAM,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAsBlD,UAAU,wBAAwB;IAChC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,GACzD,CACI;IACE,+BAA+B;IAC/B,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB,GACD;IACE,iEAAiE;IACjE,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB,CACJ,CAAC;AAEJ,mEAAmE;AACnE,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAqBD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAUrB"}
|
package/dist/id-token.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { discoverConfiguration } from "./oidc-discovery.js";
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
|
+
// src/id-token.ts
|
|
4
|
+
/** Asymmetric only, so `none` and `HS*` are rejected. */
|
|
5
|
+
var DEFAULT_ALGORITHMS = [
|
|
6
|
+
"RS256",
|
|
7
|
+
"RS384",
|
|
8
|
+
"RS512",
|
|
9
|
+
"PS256",
|
|
10
|
+
"PS384",
|
|
11
|
+
"PS512",
|
|
12
|
+
"ES256",
|
|
13
|
+
"ES384",
|
|
14
|
+
"ES512",
|
|
15
|
+
"EdDSA"
|
|
16
|
+
];
|
|
17
|
+
/** One getter per JWKS URI; jose refetches it on an unknown `kid`. */
|
|
18
|
+
var jwksCache = /* @__PURE__ */ new Map();
|
|
19
|
+
/** Drops every cached JWKS. Tests should call it between cases. */
|
|
20
|
+
function clearJwksCache() {
|
|
21
|
+
jwksCache.clear();
|
|
22
|
+
}
|
|
23
|
+
function getJwks(uri) {
|
|
24
|
+
const url = new URL(uri);
|
|
25
|
+
let jwks = jwksCache.get(url.href);
|
|
26
|
+
if (!jwks) {
|
|
27
|
+
jwks = createRemoteJWKSet(url);
|
|
28
|
+
jwksCache.set(url.href, jwks);
|
|
29
|
+
}
|
|
30
|
+
return jwks;
|
|
31
|
+
}
|
|
32
|
+
async function resolveJwksUri(options) {
|
|
33
|
+
if (options.jwksUri !== void 0) return options.jwksUri;
|
|
34
|
+
const { jwks_uri } = (await discoverConfiguration(options.oauthConfig)).serverMetadata();
|
|
35
|
+
if (!jwks_uri) throw new TypeError("The provider metadata has no jwks_uri");
|
|
36
|
+
return jwks_uri;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verifies an ID token's signature, `iss`, `aud` and `exp` against the provider's JWKS.
|
|
40
|
+
*
|
|
41
|
+
* @returns The verified claims.
|
|
42
|
+
* @throws A jose error if the token is invalid, or a `TypeError` if no JWKS URI is found.
|
|
43
|
+
*/
|
|
44
|
+
async function verifyIdToken(idToken, options) {
|
|
45
|
+
const jwks = getJwks(await resolveJwksUri(options));
|
|
46
|
+
const { payload } = await jwtVerify(idToken, jwks, {
|
|
47
|
+
issuer: options.issuer,
|
|
48
|
+
audience: options.audience,
|
|
49
|
+
algorithms: options.algorithms ?? DEFAULT_ALGORITHMS,
|
|
50
|
+
requiredClaims: [
|
|
51
|
+
"sub",
|
|
52
|
+
"exp",
|
|
53
|
+
"iat"
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
return payload;
|
|
57
|
+
}
|
|
58
|
+
export { clearJwksCache, verifyIdToken };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=id-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-token.js","names":[],"sources":["../src/id-token.ts"],"sourcesContent":["// id-token.ts\n//\n// Verifies ID tokens against the provider's JWKS. Universal: jose uses Web Crypto and fetch.\n\nimport { createRemoteJWKSet, type JWTPayload, jwtVerify } from 'jose';\n\nimport type { OAuthConfig } from './oauth-config';\nimport { discoverConfiguration } from './oidc-discovery';\n\n/** Asymmetric only, so `none` and `HS*` are rejected. */\nconst DEFAULT_ALGORITHMS = [\n 'RS256',\n 'RS384',\n 'RS512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'EdDSA',\n];\n\ntype RemoteJwks = ReturnType<typeof createRemoteJWKSet>;\n\n/** One getter per JWKS URI; jose refetches it on an unknown `kid`. */\nconst jwksCache = new Map<string, RemoteJwks>();\n\ninterface VerifyIdTokenBaseOptions {\n /** Expected `iss`. */\n issuer: string;\n /** Expected `aud`, usually the client ID. */\n audience: string | string[];\n /** Accepted `alg` values. Defaults to `RS*`, `PS*`, `ES*` and `EdDSA`. */\n algorithms?: string[];\n}\n\n/** Options for {@link verifyIdToken}. Pass `jwksUri` or `oauthConfig`. */\nexport type VerifyIdTokenOptions = VerifyIdTokenBaseOptions &\n (\n | {\n /** The provider's JWKS URL. */\n jwksUri: string | URL;\n oauthConfig?: never;\n }\n | {\n /** Reads `jwks_uri` from discovery, or from `serverMetadata`. */\n oauthConfig: OAuthConfig;\n jwksUri?: never;\n }\n );\n\n/** Drops every cached JWKS. Tests should call it between cases. */\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n\nfunction getJwks(uri: string | URL): RemoteJwks {\n const url = new URL(uri);\n let jwks = jwksCache.get(url.href);\n if (!jwks) {\n jwks = createRemoteJWKSet(url);\n jwksCache.set(url.href, jwks);\n }\n return jwks;\n}\n\nasync function resolveJwksUri(options: VerifyIdTokenOptions): Promise<string | URL> {\n if (options.jwksUri !== undefined) return options.jwksUri;\n\n const configuration = await discoverConfiguration(options.oauthConfig);\n const { jwks_uri } = configuration.serverMetadata();\n if (!jwks_uri) throw new TypeError('The provider metadata has no jwks_uri');\n return jwks_uri;\n}\n\n/**\n * Verifies an ID token's signature, `iss`, `aud` and `exp` against the provider's JWKS.\n *\n * @returns The verified claims.\n * @throws A jose error if the token is invalid, or a `TypeError` if no JWKS URI is found.\n */\nexport async function verifyIdToken(\n idToken: string,\n options: VerifyIdTokenOptions,\n): Promise<JWTPayload> {\n const jwks = getJwks(await resolveJwksUri(options));\n const { payload } = await jwtVerify(idToken, jwks, {\n issuer: options.issuer,\n audience: options.audience,\n algorithms: options.algorithms ?? DEFAULT_ALGORITHMS,\n // jose checks `exp` only when present; OIDC requires it.\n requiredClaims: ['sub', 'exp', 'iat'],\n });\n return payload;\n}\n"],"mappings":";;;;AAUA,IAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAKA,IAAM,4BAAY,IAAI,IAAwB;;AA2B9C,SAAgB,iBAAuB;CACrC,UAAU,MAAM;AAClB;AAEA,SAAS,QAAQ,KAA+B;CAC9C,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,IAAI,OAAO,UAAU,IAAI,IAAI,IAAI;CACjC,IAAI,CAAC,MAAM;EACT,OAAO,mBAAmB,GAAG;EAC7B,UAAU,IAAI,IAAI,MAAM,IAAI;CAC9B;CACA,OAAO;AACT;AAEA,eAAe,eAAe,SAAsD;CAClF,IAAI,QAAQ,YAAY,KAAA,GAAW,OAAO,QAAQ;CAGlD,MAAM,EAAE,cAAa,MADO,sBAAsB,QAAQ,WAAW,EAAA,CAClC,eAAe;CAClD,IAAI,CAAC,UAAU,MAAM,IAAI,UAAU,uCAAuC;CAC1E,OAAO;AACT;;;;;;;AAQA,eAAsB,cACpB,SACA,SACqB;CACrB,MAAM,OAAO,QAAQ,MAAM,eAAe,OAAO,CAAC;CAClD,MAAM,EAAE,YAAY,MAAM,UAAU,SAAS,MAAM;EACjD,QAAQ,QAAQ;EAChB,UAAU,QAAQ;EAClB,YAAY,QAAQ,cAAc;EAElC,gBAAgB;GAAC;GAAO;GAAO;EAAK;CACtC,CAAC;CACD,OAAO;AACT"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_ACTIVITY_EVENTS, createActivityTracker } from "./activity-tracker.js";
|
|
2
|
-
import { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, assertCookieWithinLimit, cookieByteSize, defaultOAuthCookieOptions, defaultSessionCookieOptions } from "./cookies.js";
|
|
2
|
+
import { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, SESSION_REF_COOKIE_NAME, assertCookieWithinLimit, cookieByteSize, defaultOAuthCookieOptions, defaultSessionCookieOptions } from "./cookies.js";
|
|
3
3
|
import { configureLogger, createLogger } from "./logger.js";
|
|
4
4
|
import { classifyRefreshFailure, getOAuthConfigLogContext, getOAuthErrorLogContext } from "./oauth-logging.js";
|
|
5
5
|
import { SESSION_EVENT, SESSION_REJECTED_REASONS, isSessionRejectedReason, logSessionEvent, newSessionId, sessionEventLevel } from "./session-events.js";
|
|
6
6
|
import { refreshSession } from "./session-refresh.js";
|
|
7
|
-
import { accessTokenExpires, createEncryptedJWT, decrypt, decryptJWT, encrypt, generateToken,
|
|
7
|
+
import { accessTokenExpires, createEncryptedJWT, decrypt, decryptJWT, encrypt, generateToken, hasScope, hexToUint8Array, sha256, sha512, toHex } from "./utils.js";
|
|
8
8
|
import { isSession, validateSessionJwt } from "./session-validation.js";
|
|
9
9
|
import "./types.js";
|
|
10
|
-
export { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, DEFAULT_ACTIVITY_EVENTS, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, SESSION_EVENT, SESSION_REJECTED_REASONS, accessTokenExpires, assertCookieWithinLimit, classifyRefreshFailure, configureLogger, cookieByteSize, createActivityTracker, createEncryptedJWT, createLogger, decrypt, decryptJWT, defaultOAuthCookieOptions, defaultSessionCookieOptions, encrypt, generateToken, getOAuthConfigLogContext, getOAuthErrorLogContext,
|
|
10
|
+
export { ACCESS_TOKEN_COOKIE_NAME, COOKIE_INFO_BYTES, COOKIE_MAX_BYTES, CookieTooLargeError, DEFAULT_ACTIVITY_EVENTS, ID_TOKEN_COOKIE_NAME, SESSION_COOKIE_NAME, SESSION_EVENT, SESSION_REF_COOKIE_NAME, SESSION_REJECTED_REASONS, accessTokenExpires, assertCookieWithinLimit, classifyRefreshFailure, configureLogger, cookieByteSize, createActivityTracker, createEncryptedJWT, createLogger, decrypt, decryptJWT, defaultOAuthCookieOptions, defaultSessionCookieOptions, encrypt, generateToken, getOAuthConfigLogContext, getOAuthErrorLogContext, hasScope, hexToUint8Array, isSession, isSessionRejectedReason, logSessionEvent, newSessionId, refreshSession, sessionEventLevel, sha256, sha512, toHex, validateSessionJwt };
|
package/dist/logger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/logger.ts
|
|
2
2
|
function serializeError(value) {
|
|
3
3
|
if (value instanceof Error) return {
|
|
4
4
|
name: value.name,
|
|
@@ -129,7 +129,6 @@ function createLogger(options) {
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
|
-
//#endregion
|
|
133
132
|
export { configureLogger, createLogger };
|
|
134
133
|
|
|
135
134
|
//# sourceMappingURL=logger.js.map
|
package/dist/oauth-browser.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-compatible OAuth 2.0 + PKCE helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* browser-native Web Crypto API for PKCE generation.
|
|
7
|
-
*
|
|
8
|
-
* **Use this in:**
|
|
9
|
-
* - Vite/React SPAs
|
|
10
|
-
* - Browser-only applications
|
|
11
|
-
* - Any frontend that can't use Node.js APIs
|
|
4
|
+
* Deprecated and will be removed in a later minor. Use the server flow instead:
|
|
5
|
+
* `handleOidcLogin` / `handleOidcCallback` from `@eventuras/fides-auth/server`.
|
|
12
6
|
*
|
|
13
7
|
* **Usage:**
|
|
14
8
|
* ```typescript
|
|
@@ -29,14 +23,20 @@
|
|
|
29
23
|
* const tokens = await exchangeCodeForTokens(config, code, verifier);
|
|
30
24
|
* ```
|
|
31
25
|
*/
|
|
32
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for browser-side OAuth / OIDC flows (no client secret).
|
|
28
|
+
* @deprecated Only used by the deprecated `oauth-browser` helpers. Use `handleOidcLogin` / `handleOidcCallback`.
|
|
29
|
+
*/
|
|
33
30
|
export interface OAuthConfig {
|
|
34
31
|
issuer: string;
|
|
35
32
|
clientId: string;
|
|
36
33
|
redirect_uri: string;
|
|
37
34
|
scope: string;
|
|
38
35
|
}
|
|
39
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* PKCE parameters for a browser-initiated authorization request.
|
|
38
|
+
* @deprecated Only used by the deprecated `oauth-browser` helpers. Use `handleOidcLogin`.
|
|
39
|
+
*/
|
|
40
40
|
export interface PKCEParams {
|
|
41
41
|
code_verifier: string;
|
|
42
42
|
code_challenge: string;
|
|
@@ -44,14 +44,17 @@ export interface PKCEParams {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Generate PKCE parameters for OAuth flow
|
|
47
|
+
* @deprecated The random strings have modulo bias. Use `handleOidcLogin`, which builds PKCE server-side.
|
|
47
48
|
*/
|
|
48
49
|
export declare function generatePKCE(): Promise<PKCEParams>;
|
|
49
50
|
/**
|
|
50
51
|
* Build authorization URL for OAuth flow
|
|
52
|
+
* @deprecated Hardcodes `${issuer}/auth` instead of using discovery. Use `handleOidcLogin`.
|
|
51
53
|
*/
|
|
52
54
|
export declare function buildAuthorizationUrl(config: OAuthConfig, pkce: PKCEParams): string;
|
|
53
55
|
/**
|
|
54
56
|
* Exchange authorization code for tokens
|
|
57
|
+
* @deprecated Hardcodes `${issuer}/token` and doesn't validate the response. Use `handleOidcCallback`.
|
|
55
58
|
*/
|
|
56
59
|
export declare function exchangeCodeForTokens(config: OAuthConfig, code: string, codeVerifier: string): Promise<{
|
|
57
60
|
access_token: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-browser.d.ts","sourceRoot":"","sources":["../src/oauth-browser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"oauth-browser.d.ts","sourceRoot":"","sources":["../src/oauth-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AA+BD;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC,CAUxD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,UAAU,GACf,MAAM,CAYR;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAqBD"}
|