@azlib/identity 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +119 -54
  2. package/dist/cookie-header-DdTShpuc.d.cts +83 -0
  3. package/dist/cookie-header-DdTShpuc.d.cts.map +1 -0
  4. package/dist/cookie-header-DdTShpuc.d.mts +83 -0
  5. package/dist/cookie-header-DdTShpuc.d.mts.map +1 -0
  6. package/dist/cookie-header-DptIxRmW.mjs +76 -0
  7. package/dist/cookie-header-DptIxRmW.mjs.map +1 -0
  8. package/dist/cookie-header-Dqd5k5BU.cjs +115 -0
  9. package/dist/{errors-CBbiRO2n.d.cts → errors-0Zh7EG06.d.cts} +4 -2
  10. package/dist/errors-0Zh7EG06.d.cts.map +1 -0
  11. package/dist/{errors-_OcodtdV.d.mts → errors-DVDEUk9-.d.mts} +4 -2
  12. package/dist/errors-DVDEUk9-.d.mts.map +1 -0
  13. package/dist/express.d.cts +3 -3
  14. package/dist/express.d.cts.map +1 -1
  15. package/dist/express.d.mts +3 -3
  16. package/dist/express.d.mts.map +1 -1
  17. package/dist/express.mjs.map +1 -1
  18. package/dist/{identity-Bz9RDOvT.mjs → identity-D0qyca_c.mjs} +37 -3
  19. package/dist/identity-D0qyca_c.mjs.map +1 -0
  20. package/dist/{identity-4eP45YIP.cjs → identity-Yz1BVkrM.cjs} +46 -0
  21. package/dist/{identity-router-D1xJ5H7y.d.cts → identity-router-BiratpJR.d.mts} +3 -3
  22. package/dist/identity-router-BiratpJR.d.mts.map +1 -0
  23. package/dist/{identity-router-tj3-teBI.d.mts → identity-router-D4oqYI60.d.cts} +3 -3
  24. package/dist/identity-router-D4oqYI60.d.cts.map +1 -0
  25. package/dist/{identity-service---OTann1.d.cts → identity-service-DE0Ffguz.d.cts} +3 -3
  26. package/dist/{identity-service---OTann1.d.cts.map → identity-service-DE0Ffguz.d.cts.map} +1 -1
  27. package/dist/{identity-service-WpOKb0MK.d.mts → identity-service-JtsoA6lv.d.mts} +3 -3
  28. package/dist/{identity-service-WpOKb0MK.d.mts.map → identity-service-JtsoA6lv.d.mts.map} +1 -1
  29. package/dist/identity-store-BRRahxcS.d.cts.map +1 -1
  30. package/dist/identity-store-BRRahxcS.d.mts.map +1 -1
  31. package/dist/index-CsYqHPvK.d.cts +54 -0
  32. package/dist/index-CsYqHPvK.d.cts.map +1 -0
  33. package/dist/index-LhSdoWEP.d.mts +54 -0
  34. package/dist/index-LhSdoWEP.d.mts.map +1 -0
  35. package/dist/index.cjs +11 -1
  36. package/dist/index.d.cts +4 -3
  37. package/dist/index.d.mts +4 -3
  38. package/dist/index.mjs +3 -2
  39. package/dist/logger-CcCHJVVe.mjs.map +1 -1
  40. package/dist/nestjs.d.cts +3 -3
  41. package/dist/nestjs.d.cts.map +1 -1
  42. package/dist/nestjs.d.mts +3 -3
  43. package/dist/nestjs.d.mts.map +1 -1
  44. package/dist/nestjs.mjs.map +1 -1
  45. package/dist/next.cjs +280 -0
  46. package/dist/next.d.cts +58 -0
  47. package/dist/next.d.cts.map +1 -0
  48. package/dist/next.d.mts +58 -0
  49. package/dist/next.d.mts.map +1 -0
  50. package/dist/next.mjs +269 -0
  51. package/dist/next.mjs.map +1 -0
  52. package/dist/node.cjs +18 -19
  53. package/dist/node.d.cts +5 -4
  54. package/dist/node.d.cts.map +1 -1
  55. package/dist/node.d.mts +5 -4
  56. package/dist/node.d.mts.map +1 -1
  57. package/dist/node.mjs +10 -20
  58. package/dist/node.mjs.map +1 -1
  59. package/dist/test-utils.mjs.map +1 -1
  60. package/package.json +7 -7
  61. package/schema/model.ts +110 -22
  62. package/dist/errors-CBbiRO2n.d.cts.map +0 -1
  63. package/dist/errors-_OcodtdV.d.mts.map +0 -1
  64. package/dist/identity-Bz9RDOvT.mjs.map +0 -1
  65. package/dist/identity-router-D1xJ5H7y.d.cts.map +0 -1
  66. package/dist/identity-router-tj3-teBI.d.mts.map +0 -1
  67. package/dist/index-CpufYgyn.d.cts +0 -30
  68. package/dist/index-CpufYgyn.d.cts.map +0 -1
  69. package/dist/index-CpufYgyn.d.mts +0 -30
  70. package/dist/index-CpufYgyn.d.mts.map +0 -1
package/README.md CHANGED
@@ -9,13 +9,14 @@ you can plug the package into your own database engine and transport layer.
9
9
 
10
10
  ## Entry points
11
11
 
12
- | Import | Purpose |
13
- | --- | --- |
14
- | `@azlib/identity` | Framework-agnostic types, config, errors, authorization, store contract |
15
- | `@azlib/identity/node` | Node runtime services (password hashing, JWT, register/login/refresh) |
16
- | `@azlib/identity/express` | Express authentication and authorization middleware |
17
- | `@azlib/identity/nestjs` | NestJS module, guards, and decorators |
18
- | `@azlib/identity/schema/*` | Predefined SQL and Prisma schema artifacts |
12
+ | Import | Purpose |
13
+ | -------------------------- | ----------------------------------------------------------------------- |
14
+ | `@azlib/identity` | Framework-agnostic types, config, errors, authorization, store contract, Edge-safe JWT verify and cookie presence helpers |
15
+ | `@azlib/identity/node` | Node runtime services (password hashing, JWT, register/login/refresh) |
16
+ | `@azlib/identity/express` | Express authentication and authorization middleware |
17
+ | `@azlib/identity/nestjs` | NestJS module, guards, and decorators |
18
+ | `@azlib/identity/next` | Next.js / Fetch HttpOnly cookie sessions, `getIdentity`, Route Handlers |
19
+ | `@azlib/identity/schema/*` | Predefined SQL and Prisma schema artifacts |
19
20
 
20
21
  ## Quick start (Express)
21
22
 
@@ -33,7 +34,10 @@ you can plug the package into your own database engine and transport layer.
33
34
  ```ts
34
35
  import express from "express";
35
36
  import { createIdentityService } from "@azlib/identity/node";
36
- import { createIdentityRouter, identityErrorHandler } from "@azlib/identity/express";
37
+ import {
38
+ createIdentityRouter,
39
+ identityErrorHandler,
40
+ } from "@azlib/identity/express";
37
41
 
38
42
  const service = createIdentityService(
39
43
  { accessTokenSecret: process.env.ACCESS_TOKEN_SECRET! },
@@ -48,15 +52,15 @@ you can plug the package into your own database engine and transport layer.
48
52
 
49
53
  That's it. The following endpoints are ready immediately:
50
54
 
51
- | Method | Path | Description |
52
- |--------|------|-------------|
53
- | `POST` | `/auth/register` | Create a new account |
54
- | `POST` | `/auth/login` | Email + password login |
55
- | `POST` | `/auth/refresh` | Rotate the refresh token |
56
- | `POST` | `/auth/logout` | Revoke the session |
57
- | `GET` | `/auth/me` | Return the authenticated principal |
58
- | `GET` | `/auth/:provider` | Start an OAuth flow *(if configured)* |
59
- | `GET` | `/auth/:provider/callback` | Handle an OAuth callback *(if configured)* |
55
+ | Method | Path | Description |
56
+ | ------ | -------------------------- | ------------------------------------------ |
57
+ | `POST` | `/auth/register` | Create a new account |
58
+ | `POST` | `/auth/login` | Email + password login |
59
+ | `POST` | `/auth/refresh` | Rotate the refresh token |
60
+ | `POST` | `/auth/logout` | Revoke the session |
61
+ | `GET` | `/auth/me` | Return the authenticated principal |
62
+ | `GET` | `/auth/:provider` | Start an OAuth flow _(if configured)_ |
63
+ | `GET` | `/auth/:provider/callback` | Handle an OAuth callback _(if configured)_ |
60
64
 
61
65
  ### Cookie-mode refresh tokens
62
66
 
@@ -85,7 +89,7 @@ const service = createIdentityService(
85
89
  oauth: {
86
90
  providers: [
87
91
  createGoogleOAuthProvider({
88
- clientId: process.env.GOOGLE_CLIENT_ID!,
92
+ clientId: process.env.GOOGLE_CLIENT_ID!,
89
93
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
90
94
  }),
91
95
  ],
@@ -127,7 +131,10 @@ Authorization is **deny-by-default**: a principal is only allowed when an explic
127
131
  through an assigned role; both resolve into the principal's `permissions`.
128
132
 
129
133
  ```ts
130
- import { requirePermission, requireAuthorization } from "@azlib/identity/express";
134
+ import {
135
+ requirePermission,
136
+ requireAuthorization,
137
+ } from "@azlib/identity/express";
131
138
 
132
139
  // Require a single permission.
133
140
  app.get(
@@ -145,7 +152,8 @@ app.delete(
145
152
  identity,
146
153
  {
147
154
  permission: "documents:delete",
148
- policy: ({ principal, resource }) => resource.ownerId === principal.userId,
155
+ policy: ({ principal, resource }) =>
156
+ resource.ownerId === principal.userId,
149
157
  },
150
158
  (req) => loadDocument(req.params.id),
151
159
  ),
@@ -156,7 +164,10 @@ app.delete(
156
164
  Grant roles and permissions through the store contract:
157
165
 
158
166
  ```ts
159
- await store.grantRolePermission({ roleId: "editor", permission: "documents:write" });
167
+ await store.grantRolePermission({
168
+ roleId: "editor",
169
+ permission: "documents:write",
170
+ });
160
171
  await store.assignRole({ userId, roleId: "editor" });
161
172
  await store.grantDirectPermission({ userId, permission: "reports:read" });
162
173
  ```
@@ -194,7 +205,7 @@ IdentityModule.forRoot({
194
205
  prefix: "v1/auth",
195
206
  cookie: { name: "rt", secure: true },
196
207
  },
197
- })
208
+ });
198
209
  ```
199
210
 
200
211
  ### Guards and decorators
@@ -221,6 +232,49 @@ A runnable NestJS app lives in [`examples/nest-basic`](./examples/nest-basic). T
221
232
  `IdentityService` core powers both the Express and NestJS adapters, so behavior is
222
233
  identical across frameworks.
223
234
 
235
+ ## Next.js (App Router)
236
+
237
+ Cookie-based sessions for Server Components, Server Actions, and Route Handlers.
238
+ Both the access JWT and the rotating refresh token are stored as `HttpOnly` cookies
239
+ (`azlib_at` / `azlib_rt` by default). Roles and tenant data stay out of the JWT —
240
+ hydrate those in your app after `getIdentity()`.
241
+
242
+ ```ts
243
+ import { cookies } from "next/headers";
244
+ import { createIdentityService } from "@azlib/identity/node";
245
+ import {
246
+ applyAuthCookies,
247
+ clearAuthCookies,
248
+ getIdentity,
249
+ hasIdentityCookies,
250
+ } from "@azlib/identity/next";
251
+
252
+ const identity = createIdentityService(config, store);
253
+
254
+ // Server Action — login
255
+ const result = await identity.login({ email, password });
256
+ if (!("kind" in result)) {
257
+ applyAuthCookies(await cookies(), result.tokens);
258
+ }
259
+
260
+ // Server Component / Server Action — current principal
261
+ const principal = await getIdentity(identity, await cookies());
262
+
263
+ // proxy.ts (Edge) — presence only, no database
264
+ if (pathname.startsWith("/admin") && !hasIdentityCookies(request.headers)) {
265
+ return NextResponse.redirect(loginUrl);
266
+ }
267
+
268
+ // Optional Route Handlers
269
+ import { createIdentityHandlers } from "@azlib/identity/next";
270
+ export const { login: POST } = createIdentityHandlers(identity);
271
+ ```
272
+
273
+ `getIdentity` authenticates a valid access cookie, or silently refreshes when the
274
+ access token is expired. Refresh rotates the refresh token, so call it where cookies
275
+ can be written (Server Actions and Route Handlers). `verifyAccessToken` from
276
+ `@azlib/identity` is Edge-safe if you need to inspect JWT claims without Node.
277
+
224
278
  ## Account lockout
225
279
 
226
280
  Protect against brute-force credential attacks by automatically locking accounts after too many consecutive failed logins.
@@ -230,8 +284,8 @@ createIdentityService(
230
284
  {
231
285
  accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
232
286
  lockout: {
233
- maxFailedAttempts: 5, // 0 = disabled (default: 10)
234
- durationSeconds: 900, // 0 = permanent lock (default: 900 = 15 min)
287
+ maxFailedAttempts: 5, // 0 = disabled (default: 10)
288
+ durationSeconds: 900, // 0 = permanent lock (default: 900 = 15 min)
235
289
  },
236
290
  },
237
291
  store,
@@ -268,13 +322,13 @@ const identity = createIdentityService(
268
322
  oauth: {
269
323
  providers: [
270
324
  createGoogleOAuthProvider({
271
- clientId: process.env.GOOGLE_CLIENT_ID!,
325
+ clientId: process.env.GOOGLE_CLIENT_ID!,
272
326
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
273
327
  }),
274
328
  createMicrosoftOAuthProvider({
275
- clientId: process.env.MS_CLIENT_ID!,
329
+ clientId: process.env.MS_CLIENT_ID!,
276
330
  clientSecret: process.env.MS_CLIENT_SECRET!,
277
- tenantId: "common", // or a specific tenant UUID
331
+ tenantId: "common", // or a specific tenant UUID
278
332
  }),
279
333
  ],
280
334
  },
@@ -283,15 +337,18 @@ const identity = createIdentityService(
283
337
  );
284
338
 
285
339
  // identity.oauth is an OAuthService when providers are configured.
286
- const { url, state } = identity.oauth!.buildAuthorizationUrl("google", REDIRECT_URI);
340
+ const { url, state } = identity.oauth!.buildAuthorizationUrl(
341
+ "google",
342
+ REDIRECT_URI,
343
+ );
287
344
  // …redirect the user to `url`, persist `state` in a signed cookie/session
288
345
 
289
346
  // In the callback handler:
290
347
  const result = await identity.oauth!.handleCallback("google", {
291
- code: req.query.code as string,
292
- state: req.query.state as string,
348
+ code: req.query.code as string,
349
+ state: req.query.state as string,
293
350
  expectedState: req.session.oauthState,
294
- redirectUri: REDIRECT_URI,
351
+ redirectUri: REDIRECT_URI,
295
352
  });
296
353
  // result.tokens.accessToken is now ready
297
354
  ```
@@ -303,13 +360,18 @@ import { oauthAuthorize, oauthCallback } from "@azlib/identity/express";
303
360
 
304
361
  const options = {
305
362
  redirectUri: `${BASE_URL}/auth/google/callback`,
306
- getState: (req) => req.session?.oauthState ?? null,
307
- setState: (req, _res, state) => { req.session!.oauthState = state; },
308
- onSuccess: (_req, res, result) => res.json(result),
363
+ getState: (req) => req.session?.oauthState ?? null,
364
+ setState: (req, _res, state) => {
365
+ req.session!.oauthState = state;
366
+ },
367
+ onSuccess: (_req, res, result) => res.json(result),
309
368
  };
310
369
 
311
- app.get("/auth/google", oauthAuthorize(identity.oauth!, "google", options));
312
- app.get("/auth/google/callback", oauthCallback(identity.oauth!, "google", options));
370
+ app.get("/auth/google", oauthAuthorize(identity.oauth!, "google", options));
371
+ app.get(
372
+ "/auth/google/callback",
373
+ oauthCallback(identity.oauth!, "google", options),
374
+ );
313
375
  ```
314
376
 
315
377
  ### Callback flow
@@ -325,8 +387,6 @@ Your `IdentityStore` should implement the optional `findUserByOAuthId`, `createO
325
387
  `listOAuthLinks` methods for full OAuth support. The shipped schema artifacts include the
326
388
  `identity_oauth_linked_accounts` table.
327
389
 
328
-
329
-
330
390
  The package never talks to a database directly — you provide an `IdentityStore`. Two
331
391
  adoption paths:
332
392
 
@@ -381,16 +441,18 @@ createIdentityService(
381
441
 
382
442
  ### Core Exports
383
443
 
384
- | Entry Point / Import | Export | Type | Description |
385
- | --- | --- | --- | --- |
386
- | `@azlib/identity` | `evaluateAuthorization` | Function | Evaluates custom permission requirements against a user principal. |
387
- | `@azlib/identity` | `identitySchemaModel` | Object | Relation structures and constraints definitions. |
388
- | `@azlib/identity/node` | `createIdentityService` | Function | Instantiates the core user authentication / registration service. |
389
- | `@azlib/identity/node` | `createGoogleOAuthProvider` | Function | Factory for Google OAuth provider link configurations. |
390
- | `@azlib/identity/express` | `createIdentityRouter` | Function | Mountable Express router containing all login/register endpoints. |
391
- | `@azlib/identity/express` | `requireAuth` | Middleware | Express middleware checking request Bearer token authorization. |
392
- | `@azlib/identity/express` | `requirePermission` | Middleware | Express middleware restricting actions to specific permissions. |
393
- | `@azlib/identity/nestjs` | `IdentityModule` | NestJS Module | Core NestJS DI registration boundary. |
444
+ | Entry Point / Import | Export | Type | Description |
445
+ | ------------------------- | --------------------------- | ------------- | ------------------------------------------------------------------ |
446
+ | `@azlib/identity` | `evaluateAuthorization` | Function | Evaluates custom permission requirements against a user principal. |
447
+ | `@azlib/identity` | `identitySchemaModel` | Object | Relation structures and constraints definitions. |
448
+ | `@azlib/identity/node` | `createIdentityService` | Function | Instantiates the core user authentication / registration service. |
449
+ | `@azlib/identity/node` | `createGoogleOAuthProvider` | Function | Factory for Google OAuth provider link configurations. |
450
+ | `@azlib/identity/express` | `createIdentityRouter` | Function | Mountable Express router containing all login/register endpoints. |
451
+ | `@azlib/identity/express` | `requireAuth` | Middleware | Express middleware checking request Bearer token authorization. |
452
+ | `@azlib/identity/express` | `requirePermission` | Middleware | Express middleware restricting actions to specific permissions. |
453
+ | `@azlib/identity/next` | `getIdentity` | Function | Resolve the principal from access/refresh cookies, with silent refresh. |
454
+ | `@azlib/identity/next` | `hasIdentityCookies` | Function | Edge-safe check that identity cookies are present. |
455
+ | `@azlib/identity/nestjs` | `IdentityModule` | NestJS Module | Core NestJS DI registration boundary. |
394
456
 
395
457
  ### Core Types
396
458
 
@@ -408,23 +470,26 @@ createIdentityService(
408
470
  import { createIdentityService } from "@azlib/identity/node";
409
471
  import { createIdentityRouter } from "@azlib/identity/express";
410
472
 
411
- const service = createIdentityService({
412
- accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
413
- lockout: { maxFailedAttempts: 5, durationSeconds: 900 }
414
- }, myDatabaseStore);
473
+ const service = createIdentityService(
474
+ {
475
+ accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
476
+ lockout: { maxFailedAttempts: 5, durationSeconds: 900 },
477
+ },
478
+ myDatabaseStore,
479
+ );
415
480
 
416
481
  app.use("/auth", createIdentityRouter(service));
417
482
  ```
418
483
 
419
484
  ### Behavioral Gotchas
485
+
420
486
  - **Deny-by-Default**: Principals have no permissions by default. Access must be explicitly granted via roles or direct grants.
421
487
  - **Scrypt Password Hashing**: Hashing is computational and synchronous. Avoid calling login in high-throughput loops without caching/throttling.
422
488
  - **Refresh Token Rotation**: Refresh tokens are single-use. Re-using an old token immediately revokes all sessions associated with that user.
423
489
 
424
490
  ## Status
425
491
 
426
- This package is under active implementation. See
427
- [`specs/009-identity-package`](../../specs/009-identity-package) for the plan and tasks.
492
+ This package is under active implementation.
428
493
 
429
494
  ## License
430
495
 
@@ -0,0 +1,83 @@
1
+ //#region core/cookie-header.d.ts
2
+ /**
3
+ * Cookie names and Cookie-header parsing for identity sessions.
4
+ *
5
+ * Edge-safe: no Node APIs. Use {@link hasIdentityCookies} in Next.js `proxy.ts` /
6
+ * middleware to gate routes without hitting the database.
7
+ */
8
+ /** Default HttpOnly cookie that holds the short-lived access JWT. */
9
+ declare const DEFAULT_ACCESS_COOKIE = "azlib_at";
10
+ /** Default HttpOnly cookie that holds the rotating refresh token. */
11
+ declare const DEFAULT_REFRESH_COOKIE = "azlib_rt";
12
+ /** Cookie attribute set shared by access and refresh cookies. */
13
+ interface IdentityCookieAttributes {
14
+ /** Mark cookies as `HttpOnly`. Default: `true`. */
15
+ httpOnly?: boolean;
16
+ /**
17
+ * Mark cookies as `Secure`. Defaults to `true` when `NODE_ENV` is `"production"`,
18
+ * `false` otherwise.
19
+ */
20
+ secure?: boolean;
21
+ /** `SameSite` policy. Default: `"lax"`. */
22
+ sameSite?: "strict" | "lax" | "none";
23
+ /** Cookie path. Default: `"/"`. */
24
+ path?: string;
25
+ /** Cookie domain. Omit to use the current host. */
26
+ domain?: string;
27
+ }
28
+ /** Names and attributes for the access + refresh cookie pair. */
29
+ interface IdentityCookieOptions extends IdentityCookieAttributes {
30
+ /** Access-token cookie name. Default: {@link DEFAULT_ACCESS_COOKIE}. */
31
+ accessCookie?: string;
32
+ /** Refresh-token cookie name. Default: {@link DEFAULT_REFRESH_COOKIE}. */
33
+ refreshCookie?: string;
34
+ }
35
+ /** Resolved cookie names. */
36
+ interface IdentityCookieNames {
37
+ access: string;
38
+ refresh: string;
39
+ }
40
+ /**
41
+ * A Next.js `cookies().get` / Request-cookies compatible reader.
42
+ */
43
+ interface CookieReader {
44
+ get(name: string): {
45
+ value: string;
46
+ } | undefined;
47
+ }
48
+ /**
49
+ * A Next.js `cookies().set` / `cookies().delete` compatible writer.
50
+ * `maxAge` is in **seconds** (Web / Next.js convention, not Express milliseconds).
51
+ */
52
+ interface CookieWriter {
53
+ set(name: string, value: string, options?: {
54
+ httpOnly?: boolean;
55
+ secure?: boolean;
56
+ sameSite?: "strict" | "lax" | "none";
57
+ path?: string;
58
+ domain?: string;
59
+ maxAge?: number;
60
+ expires?: Date;
61
+ }): unknown;
62
+ delete(name: string): unknown;
63
+ }
64
+ type CookieStore = CookieReader & CookieWriter;
65
+ /** Anything {@link hasIdentityCookies} / {@link readCookieValue} can inspect. */
66
+ type CookieSource = CookieReader | Headers | string | null | undefined;
67
+ declare function resolveCookieNames(options?: IdentityCookieOptions): IdentityCookieNames;
68
+ declare function resolveCookieAttributes(options?: IdentityCookieOptions): Required<Pick<IdentityCookieAttributes, "httpOnly" | "sameSite" | "path">> & IdentityCookieAttributes;
69
+ /**
70
+ * Parses a raw `Cookie` header into a name → value map.
71
+ * Values are `decodeURIComponent`'d when possible.
72
+ */
73
+ declare function parseCookieHeader(header: string | null | undefined): Map<string, string>;
74
+ /** Reads a single cookie value from a header string, `Headers`, or cookie reader. */
75
+ declare function readCookieValue(source: CookieSource, name: string): string | undefined;
76
+ /**
77
+ * Returns true when the request carries an identity access and/or refresh cookie.
78
+ * Intended for Edge/proxy gates: presence only — do not treat this as authentication.
79
+ */
80
+ declare function hasIdentityCookies(source: CookieSource, options?: IdentityCookieOptions): boolean;
81
+ //#endregion
82
+ export { DEFAULT_ACCESS_COOKIE as a, IdentityCookieNames as c, parseCookieHeader as d, readCookieValue as f, CookieWriter as i, IdentityCookieOptions as l, resolveCookieNames as m, CookieSource as n, DEFAULT_REFRESH_COOKIE as o, resolveCookieAttributes as p, CookieStore as r, IdentityCookieAttributes as s, CookieReader as t, hasIdentityCookies as u };
83
+ //# sourceMappingURL=cookie-header-DdTShpuc.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-header-DdTShpuc.d.cts","names":[],"sources":["../core/cookie-header.ts"],"mappings":";;;;;;;;cAQa;;cAGA;;UAGI;;EAEf;;;;;EAKA;;EAEA;;EAEA;;EAEA;;;UAIe,8BAA8B;;EAE7C;;EAEA;;;UAIe;EACf;EACA;;;;;UAMe;EACf,IAAI;IAAiB;;;;;;;UAON;EACf,IACE,cACA,eACA;IACE;IACA;IACA;IACA;IACA;IACA;IACA,UAAU;;EAGd,OAAO;;KAGG,cAAc,eAAe;;KAG7B,eACR,eACA;iBAKY,mBACd,UAAS,wBACR;iBAOa,wBACd,UAAS,wBACR,SAAS,KAAK,+DACf;;;;;iBAcc,kBACd,oCACC;;iBA8Ba,gBACd,QAAQ,cACR;;;;;iBAqBc,mBACd,QAAQ,cACR,UAAS"}
@@ -0,0 +1,83 @@
1
+ //#region core/cookie-header.d.ts
2
+ /**
3
+ * Cookie names and Cookie-header parsing for identity sessions.
4
+ *
5
+ * Edge-safe: no Node APIs. Use {@link hasIdentityCookies} in Next.js `proxy.ts` /
6
+ * middleware to gate routes without hitting the database.
7
+ */
8
+ /** Default HttpOnly cookie that holds the short-lived access JWT. */
9
+ declare const DEFAULT_ACCESS_COOKIE = "azlib_at";
10
+ /** Default HttpOnly cookie that holds the rotating refresh token. */
11
+ declare const DEFAULT_REFRESH_COOKIE = "azlib_rt";
12
+ /** Cookie attribute set shared by access and refresh cookies. */
13
+ interface IdentityCookieAttributes {
14
+ /** Mark cookies as `HttpOnly`. Default: `true`. */
15
+ httpOnly?: boolean;
16
+ /**
17
+ * Mark cookies as `Secure`. Defaults to `true` when `NODE_ENV` is `"production"`,
18
+ * `false` otherwise.
19
+ */
20
+ secure?: boolean;
21
+ /** `SameSite` policy. Default: `"lax"`. */
22
+ sameSite?: "strict" | "lax" | "none";
23
+ /** Cookie path. Default: `"/"`. */
24
+ path?: string;
25
+ /** Cookie domain. Omit to use the current host. */
26
+ domain?: string;
27
+ }
28
+ /** Names and attributes for the access + refresh cookie pair. */
29
+ interface IdentityCookieOptions extends IdentityCookieAttributes {
30
+ /** Access-token cookie name. Default: {@link DEFAULT_ACCESS_COOKIE}. */
31
+ accessCookie?: string;
32
+ /** Refresh-token cookie name. Default: {@link DEFAULT_REFRESH_COOKIE}. */
33
+ refreshCookie?: string;
34
+ }
35
+ /** Resolved cookie names. */
36
+ interface IdentityCookieNames {
37
+ access: string;
38
+ refresh: string;
39
+ }
40
+ /**
41
+ * A Next.js `cookies().get` / Request-cookies compatible reader.
42
+ */
43
+ interface CookieReader {
44
+ get(name: string): {
45
+ value: string;
46
+ } | undefined;
47
+ }
48
+ /**
49
+ * A Next.js `cookies().set` / `cookies().delete` compatible writer.
50
+ * `maxAge` is in **seconds** (Web / Next.js convention, not Express milliseconds).
51
+ */
52
+ interface CookieWriter {
53
+ set(name: string, value: string, options?: {
54
+ httpOnly?: boolean;
55
+ secure?: boolean;
56
+ sameSite?: "strict" | "lax" | "none";
57
+ path?: string;
58
+ domain?: string;
59
+ maxAge?: number;
60
+ expires?: Date;
61
+ }): unknown;
62
+ delete(name: string): unknown;
63
+ }
64
+ type CookieStore = CookieReader & CookieWriter;
65
+ /** Anything {@link hasIdentityCookies} / {@link readCookieValue} can inspect. */
66
+ type CookieSource = CookieReader | Headers | string | null | undefined;
67
+ declare function resolveCookieNames(options?: IdentityCookieOptions): IdentityCookieNames;
68
+ declare function resolveCookieAttributes(options?: IdentityCookieOptions): Required<Pick<IdentityCookieAttributes, "httpOnly" | "sameSite" | "path">> & IdentityCookieAttributes;
69
+ /**
70
+ * Parses a raw `Cookie` header into a name → value map.
71
+ * Values are `decodeURIComponent`'d when possible.
72
+ */
73
+ declare function parseCookieHeader(header: string | null | undefined): Map<string, string>;
74
+ /** Reads a single cookie value from a header string, `Headers`, or cookie reader. */
75
+ declare function readCookieValue(source: CookieSource, name: string): string | undefined;
76
+ /**
77
+ * Returns true when the request carries an identity access and/or refresh cookie.
78
+ * Intended for Edge/proxy gates: presence only — do not treat this as authentication.
79
+ */
80
+ declare function hasIdentityCookies(source: CookieSource, options?: IdentityCookieOptions): boolean;
81
+ //#endregion
82
+ export { DEFAULT_ACCESS_COOKIE as a, IdentityCookieNames as c, parseCookieHeader as d, readCookieValue as f, CookieWriter as i, IdentityCookieOptions as l, resolveCookieNames as m, CookieSource as n, DEFAULT_REFRESH_COOKIE as o, resolveCookieAttributes as p, CookieStore as r, IdentityCookieAttributes as s, CookieReader as t, hasIdentityCookies as u };
83
+ //# sourceMappingURL=cookie-header-DdTShpuc.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-header-DdTShpuc.d.mts","names":[],"sources":["../core/cookie-header.ts"],"mappings":";;;;;;;;cAQa;;cAGA;;UAGI;;EAEf;;;;;EAKA;;EAEA;;EAEA;;EAEA;;;UAIe,8BAA8B;;EAE7C;;EAEA;;;UAIe;EACf;EACA;;;;;UAMe;EACf,IAAI;IAAiB;;;;;;;UAON;EACf,IACE,cACA,eACA;IACE;IACA;IACA;IACA;IACA;IACA;IACA,UAAU;;EAGd,OAAO;;KAGG,cAAc,eAAe;;KAG7B,eACR,eACA;iBAKY,mBACd,UAAS,wBACR;iBAOa,wBACd,UAAS,wBACR,SAAS,KAAK,+DACf;;;;;iBAcc,kBACd,oCACC;;iBA8Ba,gBACd,QAAQ,cACR;;;;;iBAqBc,mBACd,QAAQ,cACR,UAAS"}
@@ -0,0 +1,76 @@
1
+ //#region core/cookie-header.ts
2
+ /**
3
+ * Cookie names and Cookie-header parsing for identity sessions.
4
+ *
5
+ * Edge-safe: no Node APIs. Use {@link hasIdentityCookies} in Next.js `proxy.ts` /
6
+ * middleware to gate routes without hitting the database.
7
+ */
8
+ /** Default HttpOnly cookie that holds the short-lived access JWT. */
9
+ const DEFAULT_ACCESS_COOKIE = "azlib_at";
10
+ /** Default HttpOnly cookie that holds the rotating refresh token. */
11
+ const DEFAULT_REFRESH_COOKIE = "azlib_rt";
12
+ function resolveCookieNames(options = {}) {
13
+ return {
14
+ access: options.accessCookie ?? "azlib_at",
15
+ refresh: options.refreshCookie ?? "azlib_rt"
16
+ };
17
+ }
18
+ function resolveCookieAttributes(options = {}) {
19
+ return {
20
+ httpOnly: options.httpOnly ?? true,
21
+ secure: options.secure ?? process.env.NODE_ENV === "production",
22
+ sameSite: options.sameSite ?? "lax",
23
+ path: options.path ?? "/",
24
+ domain: options.domain
25
+ };
26
+ }
27
+ /**
28
+ * Parses a raw `Cookie` header into a name → value map.
29
+ * Values are `decodeURIComponent`'d when possible.
30
+ */
31
+ function parseCookieHeader(header) {
32
+ const result = /* @__PURE__ */ new Map();
33
+ if (!header) return result;
34
+ for (const part of header.split(";")) {
35
+ const eq = part.indexOf("=");
36
+ if (eq === -1) continue;
37
+ const key = part.slice(0, eq).trim();
38
+ if (!key) continue;
39
+ const raw = part.slice(eq + 1).trim();
40
+ if (result.has(key)) continue;
41
+ try {
42
+ result.set(key, decodeURIComponent(raw));
43
+ } catch {
44
+ result.set(key, raw);
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+ function isCookieReader(source) {
50
+ return typeof source === "object" && source !== null && "get" in source && typeof source.get === "function" && !(source instanceof Headers);
51
+ }
52
+ /** Reads a single cookie value from a header string, `Headers`, or cookie reader. */
53
+ function readCookieValue(source, name) {
54
+ if (source == null) return void 0;
55
+ if (typeof source === "string") {
56
+ const value = parseCookieHeader(source).get(name);
57
+ return value && value.length > 0 ? value : void 0;
58
+ }
59
+ if (source instanceof Headers) return readCookieValue(source.get("cookie"), name);
60
+ if (isCookieReader(source)) {
61
+ const value = source.get(name)?.value?.trim();
62
+ return value && value.length > 0 ? value : void 0;
63
+ }
64
+ }
65
+ /**
66
+ * Returns true when the request carries an identity access and/or refresh cookie.
67
+ * Intended for Edge/proxy gates: presence only — do not treat this as authentication.
68
+ */
69
+ function hasIdentityCookies(source, options = {}) {
70
+ const names = resolveCookieNames(options);
71
+ return readCookieValue(source, names.access) !== void 0 || readCookieValue(source, names.refresh) !== void 0;
72
+ }
73
+ //#endregion
74
+ export { readCookieValue as a, parseCookieHeader as i, DEFAULT_REFRESH_COOKIE as n, resolveCookieAttributes as o, hasIdentityCookies as r, resolveCookieNames as s, DEFAULT_ACCESS_COOKIE as t };
75
+
76
+ //# sourceMappingURL=cookie-header-DptIxRmW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-header-DptIxRmW.mjs","names":[],"sources":["../core/cookie-header.ts"],"sourcesContent":["/**\n * Cookie names and Cookie-header parsing for identity sessions.\n *\n * Edge-safe: no Node APIs. Use {@link hasIdentityCookies} in Next.js `proxy.ts` /\n * middleware to gate routes without hitting the database.\n */\n\n/** Default HttpOnly cookie that holds the short-lived access JWT. */\nexport const DEFAULT_ACCESS_COOKIE = \"azlib_at\";\n\n/** Default HttpOnly cookie that holds the rotating refresh token. */\nexport const DEFAULT_REFRESH_COOKIE = \"azlib_rt\";\n\n/** Cookie attribute set shared by access and refresh cookies. */\nexport interface IdentityCookieAttributes {\n /** Mark cookies as `HttpOnly`. Default: `true`. */\n httpOnly?: boolean;\n /**\n * Mark cookies as `Secure`. Defaults to `true` when `NODE_ENV` is `\"production\"`,\n * `false` otherwise.\n */\n secure?: boolean;\n /** `SameSite` policy. Default: `\"lax\"`. */\n sameSite?: \"strict\" | \"lax\" | \"none\";\n /** Cookie path. Default: `\"/\"`. */\n path?: string;\n /** Cookie domain. Omit to use the current host. */\n domain?: string;\n}\n\n/** Names and attributes for the access + refresh cookie pair. */\nexport interface IdentityCookieOptions extends IdentityCookieAttributes {\n /** Access-token cookie name. Default: {@link DEFAULT_ACCESS_COOKIE}. */\n accessCookie?: string;\n /** Refresh-token cookie name. Default: {@link DEFAULT_REFRESH_COOKIE}. */\n refreshCookie?: string;\n}\n\n/** Resolved cookie names. */\nexport interface IdentityCookieNames {\n access: string;\n refresh: string;\n}\n\n/**\n * A Next.js `cookies().get` / Request-cookies compatible reader.\n */\nexport interface CookieReader {\n get(name: string): { value: string } | undefined;\n}\n\n/**\n * A Next.js `cookies().set` / `cookies().delete` compatible writer.\n * `maxAge` is in **seconds** (Web / Next.js convention, not Express milliseconds).\n */\nexport interface CookieWriter {\n set(\n name: string,\n value: string,\n options?: {\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"strict\" | \"lax\" | \"none\";\n path?: string;\n domain?: string;\n maxAge?: number;\n expires?: Date;\n },\n ): unknown;\n delete(name: string): unknown;\n}\n\nexport type CookieStore = CookieReader & CookieWriter;\n\n/** Anything {@link hasIdentityCookies} / {@link readCookieValue} can inspect. */\nexport type CookieSource =\n | CookieReader\n | Headers\n | string\n | null\n | undefined;\n\nexport function resolveCookieNames(\n options: IdentityCookieOptions = {},\n): IdentityCookieNames {\n return {\n access: options.accessCookie ?? DEFAULT_ACCESS_COOKIE,\n refresh: options.refreshCookie ?? DEFAULT_REFRESH_COOKIE,\n };\n}\n\nexport function resolveCookieAttributes(\n options: IdentityCookieOptions = {},\n): Required<Pick<IdentityCookieAttributes, \"httpOnly\" | \"sameSite\" | \"path\">> &\n IdentityCookieAttributes {\n return {\n httpOnly: options.httpOnly ?? true,\n secure: options.secure ?? process.env.NODE_ENV === \"production\",\n sameSite: options.sameSite ?? \"lax\",\n path: options.path ?? \"/\",\n domain: options.domain,\n };\n}\n\n/**\n * Parses a raw `Cookie` header into a name → value map.\n * Values are `decodeURIComponent`'d when possible.\n */\nexport function parseCookieHeader(\n header: string | null | undefined,\n): Map<string, string> {\n const result = new Map<string, string>();\n if (!header) return result;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n if (!key) continue;\n const raw = part.slice(eq + 1).trim();\n if (result.has(key)) continue;\n try {\n result.set(key, decodeURIComponent(raw));\n } catch {\n result.set(key, raw);\n }\n }\n return result;\n}\n\nfunction isCookieReader(source: CookieSource): source is CookieReader {\n return (\n typeof source === \"object\" &&\n source !== null &&\n \"get\" in source &&\n typeof source.get === \"function\" &&\n !(source instanceof Headers)\n );\n}\n\n/** Reads a single cookie value from a header string, `Headers`, or cookie reader. */\nexport function readCookieValue(\n source: CookieSource,\n name: string,\n): string | undefined {\n if (source == null) return undefined;\n if (typeof source === \"string\") {\n const value = parseCookieHeader(source).get(name);\n return value && value.length > 0 ? value : undefined;\n }\n if (source instanceof Headers) {\n return readCookieValue(source.get(\"cookie\"), name);\n }\n if (isCookieReader(source)) {\n const value = source.get(name)?.value?.trim();\n return value && value.length > 0 ? value : undefined;\n }\n return undefined;\n}\n\n/**\n * Returns true when the request carries an identity access and/or refresh cookie.\n * Intended for Edge/proxy gates: presence only — do not treat this as authentication.\n */\nexport function hasIdentityCookies(\n source: CookieSource,\n options: IdentityCookieOptions = {},\n): boolean {\n const names = resolveCookieNames(options);\n return (\n readCookieValue(source, names.access) !== undefined ||\n readCookieValue(source, names.refresh) !== undefined\n );\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,wBAAwB;;AAGrC,MAAa,yBAAyB;AAuEtC,SAAgB,mBACd,UAAiC,CAAC,GACb;CACrB,OAAO;EACL,QAAQ,QAAQ,gBAAA;EAChB,SAAS,QAAQ,iBAAA;CACnB;AACF;AAEA,SAAgB,wBACd,UAAiC,CAAC,GAET;CACzB,OAAO;EACL,UAAU,QAAQ,YAAY;EAC9B,QAAQ,QAAQ,UAAU,QAAQ,IAAI,aAAa;EACnD,UAAU,QAAQ,YAAY;EAC9B,MAAM,QAAQ,QAAQ;EACtB,QAAQ,QAAQ;CAClB;AACF;;;;;AAMA,SAAgB,kBACd,QACqB;CACrB,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,CAAC,QAAQ,OAAO;CACpB,KAAK,MAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;EACpC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;EACf,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK;EACnC,IAAI,CAAC,KAAK;EACV,MAAM,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;EACpC,IAAI,OAAO,IAAI,GAAG,GAAG;EACrB,IAAI;GACF,OAAO,IAAI,KAAK,mBAAmB,GAAG,CAAC;EACzC,QAAQ;GACN,OAAO,IAAI,KAAK,GAAG;EACrB;CACF;CACA,OAAO;AACT;AAEA,SAAS,eAAe,QAA8C;CACpE,OACE,OAAO,WAAW,YAClB,WAAW,QACX,SAAS,UACT,OAAO,OAAO,QAAQ,cACtB,EAAE,kBAAkB;AAExB;;AAGA,SAAgB,gBACd,QACA,MACoB;CACpB,IAAI,UAAU,MAAM,OAAO,KAAA;CAC3B,IAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,QAAQ,kBAAkB,MAAM,CAAC,CAAC,IAAI,IAAI;EAChD,OAAO,SAAS,MAAM,SAAS,IAAI,QAAQ,KAAA;CAC7C;CACA,IAAI,kBAAkB,SACpB,OAAO,gBAAgB,OAAO,IAAI,QAAQ,GAAG,IAAI;CAEnD,IAAI,eAAe,MAAM,GAAG;EAC1B,MAAM,QAAQ,OAAO,IAAI,IAAI,CAAC,EAAE,OAAO,KAAK;EAC5C,OAAO,SAAS,MAAM,SAAS,IAAI,QAAQ,KAAA;CAC7C;AAEF;;;;;AAMA,SAAgB,mBACd,QACA,UAAiC,CAAC,GACzB;CACT,MAAM,QAAQ,mBAAmB,OAAO;CACxC,OACE,gBAAgB,QAAQ,MAAM,MAAM,MAAM,KAAA,KAC1C,gBAAgB,QAAQ,MAAM,OAAO,MAAM,KAAA;AAE/C"}