bezzie 0.2.1 → 1.0.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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # Bezzie
2
2
 
3
- **Bezzie** — your BFF's BFF. Handles the Backend for Frontend OAuth pattern so you don't have to.
4
-
5
3
  [![npm downloads](https://img.shields.io/npm/dw/bezzie)](https://www.npmjs.com/package/bezzie)
6
4
 
7
- A BFF (Backend for Frontend) OAuth 2.0 auth library for Cloudflare Workers.
5
+ > Bezzie is a BFF (Backend for Frontend) OAuth 2.0 library for Cloudflare Workers. It implements [BCP212](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps) — your frontend never sees a JWT.
6
+
7
+ **Bezzie** — your BFF's BFF. Handles the Backend for Frontend OAuth pattern so you don't have to.
8
8
 
9
- Implements the [OAuth 2.0 for Browser-Based Apps (BCP212)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps) pattern — JWTs never touch the browser. The BFF owns the OAuth flow and issues a session cookie to the frontend instead.
9
+ The BFF owns the OAuth flow and issues a session cookie to the frontend instead of handing tokens to the browser.
10
10
 
11
11
  ```
12
12
  npm install bezzie
@@ -33,13 +33,13 @@ wrangler secret put AUTH0_CLIENT_SECRET
33
33
 
34
34
  **4. Wire it up:**
35
35
  ```typescript
36
- import { createBezzie, providers, cloudflareKV } from 'bezzie'
36
+ import { createBezzie, providers, cloudflareKVAdapter } from 'bezzie'
37
37
 
38
38
  const auth = createBezzie({
39
39
  ...providers.auth0('your-tenant.auth0.com'),
40
40
  clientId: 'xxx',
41
41
  clientSecret: env.AUTH0_CLIENT_SECRET,
42
- adapter: cloudflareKV(env.SESSION_KV),
42
+ adapter: cloudflareKVAdapter(env.SESSION_KV),
43
43
  baseUrl: 'https://app.yourproject.com',
44
44
  })
45
45
 
@@ -75,14 +75,14 @@ There's no open source library for this specific combination (BFF OAuth on Cloud
75
75
  ## Usage
76
76
 
77
77
  ```typescript
78
- import { createBezzie, providers, cloudflareKV } from 'bezzie'
78
+ import { createBezzie, providers, cloudflareKVAdapter } from 'bezzie'
79
79
 
80
80
  const auth = createBezzie({
81
81
  ...providers.auth0('your-tenant.auth0.com'),
82
82
  clientId: 'xxx',
83
83
  clientSecret: env.AUTH0_CLIENT_SECRET,
84
84
  audience: 'https://api.yourproject.com',
85
- adapter: cloudflareKV(env.SESSION_KV),
85
+ adapter: cloudflareKVAdapter(env.SESSION_KV),
86
86
  baseUrl: 'https://app.yourproject.com',
87
87
  })
88
88
 
@@ -139,18 +139,44 @@ app.all('/api/proxy/*', async (c) => {
139
139
 
140
140
  ## How It Works
141
141
 
142
- ### Login Flow
142
+ ### System context
143
143
 
144
+ ```mermaid
145
+ C4Context
146
+ title System Context — Bezzie
147
+
148
+ Person(user, "User", "Browser application user")
149
+ System(bezzie, "Cloudflare Worker (bezzie)", "BFF: owns the OAuth flow, issues session cookies to the browser")
150
+ System_Ext(idp, "Identity Provider", "Auth0 / Okta / Keycloak / Google — issues tokens")
151
+ System_Ext(upstream, "Upstream API", "Your backend — trusts Bearer tokens forwarded by the Worker")
152
+
153
+ Rel(user, bezzie, "HTTPS requests + session cookie")
154
+ Rel(bezzie, idp, "OIDC discovery, token exchange, token refresh")
155
+ Rel(bezzie, upstream, "Proxied requests with Authorization: Bearer")
156
+ Rel(idp, user, "Redirect back after login")
144
157
  ```
145
- Browser → BFF /auth/login → Auth0 (Authorization Code + PKCE)
146
-
147
- code returned
148
-
149
- BFF exchanges code → tokens stored in KV
150
- BFF issues HttpOnly session cookie → Browser
158
+
159
+ ### Containers
160
+
161
+ ```mermaid
162
+ C4Container
163
+ title Container bezzie deployment
164
+
165
+ Person(user, "User")
166
+ Container(spa, "React SPA", "Cloudflare Pages", "Public landing page + protected dashboard")
167
+ Container(worker, "Cloudflare Worker", "Hono + bezzie", "BFF: auth routes + request middleware + token management")
168
+ ContainerDb(kv, "Cloudflare KV", "KVNamespace", "Stores sessions and PKCE state")
169
+ System_Ext(idp, "Identity Provider", "Auth0 / Okta / Keycloak")
170
+ System_Ext(upstream, "Upstream API", "Backend services")
171
+
172
+ Rel(user, spa, "HTTPS")
173
+ Rel(spa, worker, "API calls + __Host-session cookie")
174
+ Rel(worker, kv, "Session read / write / delete")
175
+ Rel(worker, idp, "OIDC discovery + token exchange + token refresh + JWKS")
176
+ Rel(worker, upstream, "Authorization: Bearer {accessToken}")
151
177
  ```
152
178
 
153
- ### Per-Request Flow
179
+ ### Per-request flow
154
180
 
155
181
  1. Browser sends request to BFF with session cookie
156
182
  2. BFF looks up session in KV, retrieves access token
@@ -158,16 +184,6 @@ Browser → BFF /auth/login → Auth0 (Authorization Code + PKCE)
158
184
  4. If expired, BFF uses refresh token to get a new one and updates KV
159
185
  5. BFF forwards request upstream with `Authorization: Bearer <token>`
160
186
 
161
- ### Session Storage
162
-
163
- Sessions are stored in Cloudflare KV:
164
-
165
- ```
166
- sessionId → { accessToken, refreshToken, expiresAt, user }
167
- ```
168
-
169
- KV TTL is aligned with the refresh token lifetime. When the refresh token expires, the user must log in again.
170
-
171
187
  ---
172
188
 
173
189
  ## Adapters
@@ -177,17 +193,22 @@ Bezzie supports multiple session storage backends:
177
193
  ### Cloudflare KV
178
194
  Recommended for production on Cloudflare Workers.
179
195
  ```typescript
180
- import { cloudflareKV } from 'bezzie'
196
+ import { cloudflareKVAdapter } from 'bezzie'
181
197
  // ...
182
- adapter: cloudflareKV(env.SESSION_KV)
198
+ adapter: cloudflareKVAdapter(env.SESSION_KV)
183
199
  ```
184
200
 
185
201
  ### Redis (Upstash)
186
- Good for cross-region session consistency.
202
+ Good for cross-region session consistency. Works with [Upstash Redis](https://upstash.com) (recommended for Cloudflare Workers) and any Redis client with `get`/`set`/`del` methods.
203
+
187
204
  ```typescript
188
- import { RedisAdapter } from 'bezzie'
189
- // ...
190
- adapter: new RedisAdapter({ url: env.REDIS_URL, token: env.REDIS_TOKEN })
205
+ import { redisAdapter } from 'bezzie'
206
+ import { Redis } from '@upstash/redis/cloudflare'
207
+
208
+ adapter: redisAdapter(new Redis({
209
+ url: env.UPSTASH_REDIS_REST_URL,
210
+ token: env.UPSTASH_REDIS_REST_TOKEN,
211
+ }))
191
212
  ```
192
213
 
193
214
  ### Memory
@@ -208,9 +229,9 @@ adapter: new MemoryAdapter()
208
229
  | `clientId` | `string` | OAuth client ID |
209
230
  | `clientSecret` | `string` | OAuth client secret — keep in Workers secrets |
210
231
  | `audience` | `string` | API audience identifier |
211
- | `adapter` | `SessionAdapter` | Session adapter (e.g. `cloudflareKV(env.SESSION_KV)`) |
232
+ | `adapter` | `SessionAdapter` | Session adapter (e.g. `cloudflareKVAdapter(env.SESSION_KV)`) |
212
233
  | `baseUrl` | `string` | Base URL of your application (used for callback and redirects) |
213
- | `providerHints` | `object` | Optional tweaks for specific providers (`logoutUrl`, `tokenEndpoint`) |
234
+ | `providerOverrides` | `object` | Hard overrides for specific provider values (`logoutUrl`, `tokenEndpoint`) |
214
235
 
215
236
  ---
216
237
 
@@ -232,6 +253,20 @@ wrangler secret put AUTH0_CLIENT_SECRET
232
253
 
233
254
  ---
234
255
 
256
+ ## Alternatives
257
+
258
+ | | Bezzie | Auth.js (NextAuth) | Lucia | Roll your own (oauth4webapi) |
259
+ |---|---|---|---|---|
260
+ | BFF pattern (tokens never in browser) | ✅ | ✅ (some adapters) | ❌ | You decide |
261
+ | Cloudflare Workers native | ✅ | ⚠️ Edge adapter | ⚠️ | ✅ |
262
+ | Hono integration | ✅ | ❌ | ❌ | ✅ |
263
+ | OIDC discovery | ✅ | ✅ | ❌ | ✅ |
264
+ | Token refresh | ✅ | ✅ | Manual | Manual |
265
+ | Pluggable session storage | ✅ (KV, Redis, Memory) | ✅ | ✅ | Manual |
266
+ | Zero Node.js deps | ✅ | ❌ | ✅ | ✅ |
267
+
268
+ ---
269
+
235
270
  ## Stack
236
271
 
237
272
  | Component | Choice |
@@ -249,6 +284,12 @@ v0.1.0 — pre-release
249
284
 
250
285
  ---
251
286
 
287
+ ## Changelog
288
+
289
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
290
+
291
+ ---
292
+
252
293
  ## License
253
294
 
254
295
  MIT
@@ -1,5 +1,5 @@
1
1
  import { Session } from '../session';
2
- import { PKCEState, SessionAdapter } from './types';
2
+ import { PKCEState, SessionAdapter, SessionAdapterFactory } from './types';
3
3
  export declare class CloudflareKVAdapter<TUser extends Record<string, unknown> = Record<string, unknown>> implements SessionAdapter<TUser> {
4
4
  private kv;
5
5
  constructor(kv: KVNamespace);
@@ -7,4 +7,8 @@ export declare class CloudflareKVAdapter<TUser extends Record<string, unknown> =
7
7
  set(sessionId: string, session: Session<TUser> | PKCEState, ttlSeconds: number): Promise<void>;
8
8
  delete(sessionId: string): Promise<void>;
9
9
  }
10
+ /**
11
+ * Creates a Cloudflare KV session adapter factory.
12
+ */
13
+ export declare function cloudflareKVAdapter(kv: KVNamespace): SessionAdapterFactory;
10
14
  //# sourceMappingURL=cloudflare-kv.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare-kv.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare-kv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEnD,qBAAa,mBAAmB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC9F,YAAW,cAAc,CAAC,KAAK,CAAC;IAEpB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,WAAW;IAE7B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAIlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IASV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
1
+ {"version":3,"file":"cloudflare-kv.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare-kv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAG1E,qBAAa,mBAAmB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC9F,YAAW,cAAc,CAAC,KAAK,CAAC;IAEpB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,WAAW;IAE7B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAIlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAYV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,qBAAqB,CAG1E"}
@@ -1,3 +1,4 @@
1
+ import { SessionStoreError } from '../errors';
1
2
  export class CloudflareKVAdapter {
2
3
  kv;
3
4
  constructor(kv) {
@@ -8,7 +9,7 @@ export class CloudflareKVAdapter {
8
9
  }
9
10
  async set(sessionId, session, ttlSeconds) {
10
11
  if (ttlSeconds < 60) {
11
- throw new Error('Bezzie: KV TTL must be at least 60 seconds');
12
+ throw new SessionStoreError('session_storage_failed', 'Bezzie: KV TTL must be at least 60 seconds');
12
13
  }
13
14
  await this.kv.put(sessionId, JSON.stringify(session), {
14
15
  expirationTtl: ttlSeconds,
@@ -18,4 +19,10 @@ export class CloudflareKVAdapter {
18
19
  await this.kv.delete(sessionId);
19
20
  }
20
21
  }
22
+ /**
23
+ * Creates a Cloudflare KV session adapter factory.
24
+ */
25
+ export function cloudflareKVAdapter(kv) {
26
+ return () => new CloudflareKVAdapter(kv);
27
+ }
21
28
  //# sourceMappingURL=cloudflare-kv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare-kv.js","sourceRoot":"","sources":["../../src/adapters/cloudflare-kv.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,mBAAmB;IAGV;IAApB,YAAoB,EAAe;QAAf,OAAE,GAAF,EAAE,CAAa;IAAG,CAAC;IAEvC,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,OAAO,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAA6B,SAAS,EAAE,MAAM,CAAC,CAAA;IACzE,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,IAAI,UAAU,GAAG,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;QAC/D,CAAC;QACD,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;YACpD,aAAa,EAAE,UAAU;SAC1B,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;CACF"}
1
+ {"version":3,"file":"cloudflare-kv.js","sourceRoot":"","sources":["../../src/adapters/cloudflare-kv.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAE7C,MAAM,OAAO,mBAAmB;IAGV;IAApB,YAAoB,EAAe;QAAf,OAAE,GAAF,EAAE,CAAa;IAAG,CAAC;IAEvC,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,OAAO,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAA6B,SAAS,EAAE,MAAM,CAAC,CAAA;IACzE,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,IAAI,UAAU,GAAG,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,iBAAiB,CACzB,wBAAwB,EACxB,4CAA4C,CAC7C,CAAA;QACH,CAAC;QACD,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;YACpD,aAAa,EAAE,UAAU;SAC1B,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAe;IACjD,OAAO,GAA2F,EAAE,CAClG,IAAI,mBAAmB,CAAQ,EAAE,CAAC,CAAA;AACtC,CAAC"}
@@ -1,9 +1,25 @@
1
1
  import { Session } from '../session';
2
- import { SessionAdapter, PKCEState } from './types';
2
+ import { SessionAdapter, SessionAdapterFactory, PKCEState } from './types';
3
3
  export declare class MemoryAdapter<TUser extends Record<string, unknown> = Record<string, unknown>> implements SessionAdapter<TUser> {
4
4
  private store;
5
+ private lastCleanup;
6
+ private static readonly CLEANUP_INTERVAL_MS;
7
+ /**
8
+ * Evicts all entries whose TTL has expired.
9
+ *
10
+ * Workers runtimes don't support long-lived timers (`setInterval`), so rather
11
+ * than scheduling cleanup we trigger it opportunistically from `get()` at
12
+ * most once every `CLEANUP_INTERVAL_MS`. This bounds memory growth for
13
+ * long-running processes (e.g. local dev, tests) without adding overhead to
14
+ * every read.
15
+ */
16
+ cleanup(): void;
5
17
  get(sessionId: string): Promise<Session<TUser> | PKCEState | null>;
6
18
  set(sessionId: string, session: Session<TUser> | PKCEState, ttlSeconds: number): Promise<void>;
7
19
  delete(sessionId: string): Promise<void>;
8
20
  }
21
+ /**
22
+ * Creates an in-memory session adapter factory. Useful for tests and local dev.
23
+ */
24
+ export declare function memoryAdapter(): SessionAdapterFactory;
9
25
  //# sourceMappingURL=memory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAOnD,qBAAa,aAAa,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxF,YAAW,cAAc,CAAC,KAAK,CAAC;IAEhC,OAAO,CAAC,KAAK,CAA0C;IAEjD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAUlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAOV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAO1E,qBAAa,aAAa,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxF,YAAW,cAAc,CAAC,KAAK,CAAC;IAEhC,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAEpD;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IAUT,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAgBlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAOV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,qBAAqB,CAGrD"}
@@ -1,10 +1,35 @@
1
1
  export class MemoryAdapter {
2
2
  store = new Map();
3
+ lastCleanup = 0;
4
+ static CLEANUP_INTERVAL_MS = 60_000;
5
+ /**
6
+ * Evicts all entries whose TTL has expired.
7
+ *
8
+ * Workers runtimes don't support long-lived timers (`setInterval`), so rather
9
+ * than scheduling cleanup we trigger it opportunistically from `get()` at
10
+ * most once every `CLEANUP_INTERVAL_MS`. This bounds memory growth for
11
+ * long-running processes (e.g. local dev, tests) without adding overhead to
12
+ * every read.
13
+ */
14
+ cleanup() {
15
+ const now = Date.now();
16
+ for (const [id, entry] of this.store) {
17
+ if (now > entry.expiresAt) {
18
+ this.store.delete(id);
19
+ }
20
+ }
21
+ this.lastCleanup = now;
22
+ }
3
23
  async get(sessionId) {
24
+ // Proactive TTL eviction (C6): run at most once per CLEANUP_INTERVAL_MS.
25
+ const now = Date.now();
26
+ if (now - this.lastCleanup > MemoryAdapter.CLEANUP_INTERVAL_MS) {
27
+ this.cleanup();
28
+ }
4
29
  const entry = this.store.get(sessionId);
5
30
  if (!entry)
6
31
  return null;
7
- if (Date.now() > entry.expiresAt) {
32
+ if (now > entry.expiresAt) {
8
33
  this.store.delete(sessionId);
9
34
  return null;
10
35
  }
@@ -20,4 +45,10 @@ export class MemoryAdapter {
20
45
  this.store.delete(sessionId);
21
46
  }
22
47
  }
48
+ /**
49
+ * Creates an in-memory session adapter factory. Useful for tests and local dev.
50
+ */
51
+ export function memoryAdapter() {
52
+ return () => new MemoryAdapter();
53
+ }
23
54
  //# sourceMappingURL=memory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,aAAa;IAGhB,KAAK,GAAG,IAAI,GAAG,EAAgC,CAAA;IAEvD,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAC5B,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE;YACxB,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;SAC1C,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC9B,CAAC;CACF"}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,aAAa;IAGhB,KAAK,GAAG,IAAI,GAAG,EAAgC,CAAA;IAC/C,WAAW,GAAG,CAAC,CAAA;IACf,MAAM,CAAU,mBAAmB,GAAG,MAAM,CAAA;IAEpD;;;;;;;;OAQG;IACH,OAAO;QACL,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACrC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,GAAG,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,yEAAyE;QACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC,mBAAmB,EAAE,CAAC;YAC/D,IAAI,CAAC,OAAO,EAAE,CAAA;QAChB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAC5B,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE;YACxB,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;SAC1C,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC9B,CAAC;;AAGH;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,GAA2F,EAAE,CAClG,IAAI,aAAa,EAAS,CAAA;AAC9B,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Session } from '../session';
2
- import { SessionAdapter, PKCEState } from './types';
2
+ import { SessionAdapter, SessionAdapterFactory, PKCEState } from './types';
3
3
  export interface RedisClient {
4
4
  get(key: string): Promise<string | null>;
5
5
  set(key: string, value: string, options?: {
@@ -7,6 +7,24 @@ export interface RedisClient {
7
7
  }): Promise<unknown>;
8
8
  del(key: string): Promise<unknown>;
9
9
  }
10
+ /**
11
+ * Redis-backed session adapter.
12
+ *
13
+ * Compatible with any Redis client that implements the {@link RedisClient} interface,
14
+ * including [Upstash Redis](https://upstash.com) — the recommended choice for Cloudflare Workers
15
+ * because it communicates over HTTP rather than TCP.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { redisAdapter } from 'bezzie'
20
+ * import { Redis } from '@upstash/redis/cloudflare'
21
+ *
22
+ * adapter: redisAdapter(new Redis({
23
+ * url: env.UPSTASH_REDIS_REST_URL,
24
+ * token: env.UPSTASH_REDIS_REST_TOKEN,
25
+ * }))
26
+ * ```
27
+ */
10
28
  export declare class RedisAdapter<TUser extends Record<string, unknown> = Record<string, unknown>> implements SessionAdapter<TUser> {
11
29
  private redis;
12
30
  constructor(redis: RedisClient);
@@ -14,4 +32,23 @@ export declare class RedisAdapter<TUser extends Record<string, unknown> = Record
14
32
  set(sessionId: string, session: Session<TUser> | PKCEState, ttlSeconds: number): Promise<void>;
15
33
  delete(sessionId: string): Promise<void>;
16
34
  }
35
+ /**
36
+ * Creates a Redis-backed session adapter factory.
37
+ *
38
+ * Accepts any client that satisfies the {@link RedisClient} interface, including
39
+ * [Upstash Redis](https://upstash.com) (`@upstash/redis/cloudflare`) — recommended for
40
+ * Cloudflare Workers because it uses the Upstash REST API over HTTP rather than a TCP socket.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * import { redisAdapter } from 'bezzie'
45
+ * import { Redis } from '@upstash/redis/cloudflare'
46
+ *
47
+ * adapter: redisAdapter(new Redis({
48
+ * url: env.UPSTASH_REDIS_REST_URL,
49
+ * token: env.UPSTASH_REDIS_REST_TOKEN,
50
+ * }))
51
+ * ```
52
+ */
53
+ export declare function redisAdapter(client: RedisClient): SessionAdapterFactory;
17
54
  //# sourceMappingURL=redis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/adapters/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACnC;AAED,qBAAa,YAAY,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACvF,YAAW,cAAc,CAAC,KAAK,CAAC;IAEpB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAEhC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAMlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/adapters/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAE1E,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,YAAY,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACvF,YAAW,cAAc,CAAC,KAAK,CAAC;IAEpB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAEhC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAMlE,GAAG,CACP,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIV,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,qBAAqB,CAGvE"}
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Redis-backed session adapter.
3
+ *
4
+ * Compatible with any Redis client that implements the {@link RedisClient} interface,
5
+ * including [Upstash Redis](https://upstash.com) — the recommended choice for Cloudflare Workers
6
+ * because it communicates over HTTP rather than TCP.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { redisAdapter } from 'bezzie'
11
+ * import { Redis } from '@upstash/redis/cloudflare'
12
+ *
13
+ * adapter: redisAdapter(new Redis({
14
+ * url: env.UPSTASH_REDIS_REST_URL,
15
+ * token: env.UPSTASH_REDIS_REST_TOKEN,
16
+ * }))
17
+ * ```
18
+ */
1
19
  export class RedisAdapter {
2
20
  redis;
3
21
  constructor(redis) {
@@ -16,4 +34,25 @@ export class RedisAdapter {
16
34
  await this.redis.del(sessionId);
17
35
  }
18
36
  }
37
+ /**
38
+ * Creates a Redis-backed session adapter factory.
39
+ *
40
+ * Accepts any client that satisfies the {@link RedisClient} interface, including
41
+ * [Upstash Redis](https://upstash.com) (`@upstash/redis/cloudflare`) — recommended for
42
+ * Cloudflare Workers because it uses the Upstash REST API over HTTP rather than a TCP socket.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { redisAdapter } from 'bezzie'
47
+ * import { Redis } from '@upstash/redis/cloudflare'
48
+ *
49
+ * adapter: redisAdapter(new Redis({
50
+ * url: env.UPSTASH_REDIS_REST_URL,
51
+ * token: env.UPSTASH_REDIS_REST_TOKEN,
52
+ * }))
53
+ * ```
54
+ */
55
+ export function redisAdapter(client) {
56
+ return () => new RedisAdapter(client);
57
+ }
19
58
  //# sourceMappingURL=redis.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/adapters/redis.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,YAAY;IAGH;IAApB,YAAoB,KAAkB;QAAlB,UAAK,GAAL,KAAK,CAAa;IAAG,CAAC;IAE1C,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA+B,CAAA;IAC1D,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;IAC9E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;CACF"}
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/adapters/redis.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,YAAY;IAGH;IAApB,YAAoB,KAAkB;QAAlB,UAAK,GAAL,KAAK,CAAa;IAAG,CAAC;IAE1C,KAAK,CAAC,GAAG,CAAC,SAAiB;QACzB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA+B,CAAA;IAC1D,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAmC,EACnC,UAAkB;QAElB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;IAC9E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,OAAO,GAA2F,EAAE,CAClG,IAAI,YAAY,CAAQ,MAAM,CAAC,CAAA;AACnC,CAAC"}
@@ -15,6 +15,17 @@ export interface PKCEState {
15
15
  * URL to redirect to after successful authentication.
16
16
  */
17
17
  returnTo?: string;
18
+ /**
19
+ * CSRF token bound to the user's browser session via the `__Host-pkce-csrf` cookie.
20
+ * Used to prevent login-CSRF attacks (S4).
21
+ */
22
+ csrfToken: string;
23
+ /**
24
+ * OIDC `nonce` value. Generated at `/login`, passed in the authorization
25
+ * request, and verified against the `nonce` claim of the returned ID token
26
+ * at `/callback` to prevent ID token replay attacks (S8).
27
+ */
28
+ nonce: string;
18
29
  }
19
30
  /**
20
31
  * Interface for session storage adapters.
@@ -42,4 +53,12 @@ export interface SessionAdapter<TUser extends Record<string, unknown> = Record<s
42
53
  */
43
54
  delete(sessionId: string): Promise<void>;
44
55
  }
56
+ /**
57
+ * Factory function that produces a {@link SessionAdapter} for a given `TUser`.
58
+ *
59
+ * Consumers construct adapters via the factory form (e.g. `memoryAdapter()`,
60
+ * `cloudflareKVAdapter(env.SESSION_KV)`) so `TUser` is inferred from
61
+ * `createBezzie<TUser>(...)` rather than needing to be specified twice.
62
+ */
63
+ export type SessionAdapterFactory = <TUser extends Record<string, unknown> = Record<string, unknown>>() => SessionAdapter<TUser>;
45
64
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7F;;;;;OAKG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,CAAA;IAElE;;;;;;OAMG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9F;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7F;;;;;OAKG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,CAAA;IAElE;;;;;;OAMG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9F;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAA"}
@@ -1,10 +1,21 @@
1
1
  import * as oauth from 'oauth4webapi';
2
- import type { BezzieConfig } from './index';
2
+ /**
3
+ * Minimal config shape that discovery needs — it only reads `issuer` and
4
+ * `providerOverrides.tokenEndpoint`. Accepting this interface (rather than
5
+ * `BezzieConfig`) avoids a generic parameter that has no bearing on discovery.
6
+ */
7
+ interface DiscoveryConfig {
8
+ issuer: string;
9
+ providerOverrides?: {
10
+ tokenEndpoint?: string;
11
+ };
12
+ }
3
13
  export interface DiscoveryCache {
4
14
  cachedAS: oauth.AuthorizationServer | null;
5
15
  cacheExpiresAt: number;
6
16
  jwksCache: oauth.JWKSCacheInput;
7
17
  }
8
18
  export declare function createDiscoveryCache(): DiscoveryCache;
9
- export declare function getAuthorizationServer(config: BezzieConfig, cache: DiscoveryCache): Promise<oauth.AuthorizationServer>;
19
+ export declare function getAuthorizationServer(config: DiscoveryConfig, cache: DiscoveryCache): Promise<oauth.AuthorizationServer>;
20
+ export {};
10
21
  //# sourceMappingURL=discovery.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC,mBAAmB,GAAG,IAAI,CAAA;IAC1C,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,KAAK,CAAC,cAAc,CAAA;CAChC;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAoBpC"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAGrC;;;;GAIG;AACH,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,CAAC,EAAE;QAClB,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC,mBAAmB,GAAG,IAAI,CAAA;IAC1C,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,KAAK,CAAC,cAAc,CAAA;CAChC;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAoBpC"}
package/dist/discovery.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as oauth from 'oauth4webapi';
2
+ import { DiscoveryError } from './errors';
2
3
  export function createDiscoveryCache() {
3
4
  return { cachedAS: null, cacheExpiresAt: 0, jwksCache: {} };
4
5
  }
@@ -8,17 +9,17 @@ export async function getAuthorizationServer(config, cache) {
8
9
  }
9
10
  const issuer = new URL(config.issuer);
10
11
  try {
11
- const response = await oauth.discoveryRequest(issuer);
12
+ const response = await oauth.discoveryRequest(issuer, { signal: AbortSignal.timeout(5000) });
12
13
  const as = await oauth.processDiscoveryResponse(issuer, response);
13
- const cachedAS = config.providerHints?.tokenEndpoint
14
- ? { ...as, token_endpoint: config.providerHints.tokenEndpoint }
14
+ const cachedAS = config.providerOverrides?.tokenEndpoint
15
+ ? { ...as, token_endpoint: config.providerOverrides.tokenEndpoint }
15
16
  : as;
16
17
  cache.cachedAS = cachedAS;
17
18
  cache.cacheExpiresAt = Date.now() + 60 * 60 * 1000;
18
19
  return cachedAS;
19
20
  }
20
21
  catch (err) {
21
- throw new Error(`Bezzie: OIDC discovery failed for ${config.issuer}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
22
+ throw new DiscoveryError(`Bezzie: OIDC discovery failed for ${config.issuer}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
22
23
  }
23
24
  }
24
25
  //# sourceMappingURL=discovery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AASrC,MAAM,UAAU,oBAAoB;IAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAoB,EACpB,KAAqB;IAErB,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAA;QACrD,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,EAAE,aAAa;YAClD,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE;YAC/D,CAAC,CAAC,EAAE,CAAA;QACN,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACzB,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QAClD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,qCAAqC,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EACzG,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAoBzC,MAAM,UAAU,oBAAoB;IAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAuB,EACvB,KAAqB;IAErB,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC5F,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,iBAAiB,EAAE,aAAa;YACtD,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC,iBAAiB,CAAC,aAAa,EAAE;YACnE,CAAC,CAAC,EAAE,CAAA;QACN,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACzB,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QAClD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,cAAc,CACtB,qCAAqC,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EACzG,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ export type BezzieErrorCode = 'discovery_failed' | 'callback_invalid_state' | 'callback_provider_error' | 'token_exchange_failed' | 'refresh_failed' | 'refresh_token_revoked' | 'session_storage_failed' | 'session_not_found' | 'config_invalid';
2
+ export interface BezzieErrorOptions {
3
+ cause?: unknown;
4
+ httpStatus?: number;
5
+ oauthError?: string;
6
+ oauthErrorDescription?: string;
7
+ }
8
+ export declare class BezzieError extends Error {
9
+ readonly code: BezzieErrorCode;
10
+ readonly httpStatus?: number;
11
+ readonly oauthError?: string;
12
+ readonly oauthErrorDescription?: string;
13
+ constructor(code: BezzieErrorCode, message: string, opts?: BezzieErrorOptions);
14
+ }
15
+ export declare class DiscoveryError extends BezzieError {
16
+ constructor(message: string, opts?: BezzieErrorOptions);
17
+ }
18
+ export declare class CallbackError extends BezzieError {
19
+ }
20
+ export declare class TokenExchangeError extends BezzieError {
21
+ }
22
+ export declare class RefreshError extends BezzieError {
23
+ }
24
+ export declare class SessionStoreError extends BezzieError {
25
+ }
26
+ export declare class ConfigError extends BezzieError {
27
+ }
28
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,kBAAkB,GAClB,wBAAwB,GACxB,yBAAyB,GACzB,uBAAuB,GACvB,gBAAgB,GAChB,uBAAuB,GACvB,wBAAwB,GACxB,mBAAmB,GACnB,gBAAgB,CAAA;AAEpB,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;gBAE3B,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB;CASlF;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB;CAGvD;AAED,qBAAa,aAAc,SAAQ,WAAW;CAAG;AACjD,qBAAa,kBAAmB,SAAQ,WAAW;CAAG;AACtD,qBAAa,YAAa,SAAQ,WAAW;CAAG;AAChD,qBAAa,iBAAkB,SAAQ,WAAW;CAAG;AACrD,qBAAa,WAAY,SAAQ,WAAW;CAAG"}
package/dist/errors.js ADDED
@@ -0,0 +1,31 @@
1
+ export class BezzieError extends Error {
2
+ code;
3
+ httpStatus;
4
+ oauthError;
5
+ oauthErrorDescription;
6
+ constructor(code, message, opts = {}) {
7
+ super(message, { cause: opts.cause });
8
+ this.name = this.constructor.name;
9
+ this.code = code;
10
+ this.httpStatus = opts.httpStatus;
11
+ this.oauthError = opts.oauthError;
12
+ this.oauthErrorDescription = opts.oauthErrorDescription;
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ }
16
+ export class DiscoveryError extends BezzieError {
17
+ constructor(message, opts) {
18
+ super('discovery_failed', message, opts);
19
+ }
20
+ }
21
+ export class CallbackError extends BezzieError {
22
+ }
23
+ export class TokenExchangeError extends BezzieError {
24
+ }
25
+ export class RefreshError extends BezzieError {
26
+ }
27
+ export class SessionStoreError extends BezzieError {
28
+ }
29
+ export class ConfigError extends BezzieError {
30
+ }
31
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAkBA,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAiB;IACrB,UAAU,CAAS;IACnB,UAAU,CAAS;IACnB,qBAAqB,CAAS;IAEvC,YAAY,IAAqB,EAAE,OAAe,EAAE,OAA2B,EAAE;QAC/E,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAA;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QACjC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QACjC,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,CAAA;QACvD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACnD,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,WAAW;IAC7C,YAAY,OAAe,EAAE,IAAyB;QACpD,KAAK,CAAC,kBAAkB,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,WAAW;CAAG;AACjD,MAAM,OAAO,kBAAmB,SAAQ,WAAW;CAAG;AACtD,MAAM,OAAO,YAAa,SAAQ,WAAW;CAAG;AAChD,MAAM,OAAO,iBAAkB,SAAQ,WAAW;CAAG;AACrD,MAAM,OAAO,WAAY,SAAQ,WAAW;CAAG"}