@venturekit/auth 0.0.0-dev.20260701100017 → 0.0.0-dev.20260704225856

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 (62) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +5 -1
  4. package/dist/index.js.map +1 -1
  5. package/{migrations → dist/migrations}/vk_auth_001_verification_codes.sql +7 -4
  6. package/dist/migrations/vk_auth_003_role_scopes.sql +43 -0
  7. package/dist/roles/index.d.ts +5 -1
  8. package/dist/roles/index.d.ts.map +1 -1
  9. package/dist/roles/index.js +4 -1
  10. package/dist/roles/index.js.map +1 -1
  11. package/dist/roles/role-scopes.d.ts +92 -0
  12. package/dist/roles/role-scopes.d.ts.map +1 -0
  13. package/dist/roles/role-scopes.js +122 -0
  14. package/dist/roles/role-scopes.js.map +1 -0
  15. package/dist/server/cookies.d.ts +98 -13
  16. package/dist/server/cookies.d.ts.map +1 -1
  17. package/dist/server/cookies.js +77 -19
  18. package/dist/server/cookies.js.map +1 -1
  19. package/dist/server/federated-routes.d.ts +29 -22
  20. package/dist/server/federated-routes.d.ts.map +1 -1
  21. package/dist/server/federated-routes.js +31 -4
  22. package/dist/server/federated-routes.js.map +1 -1
  23. package/dist/server/federated.d.ts.map +1 -1
  24. package/dist/server/federated.js +7 -11
  25. package/dist/server/federated.js.map +1 -1
  26. package/dist/server/forgot-password.js +0 -1
  27. package/dist/server/forgot-password.js.map +1 -1
  28. package/dist/server/handoff-routes.d.ts +130 -0
  29. package/dist/server/handoff-routes.d.ts.map +1 -0
  30. package/dist/server/handoff-routes.js +178 -0
  31. package/dist/server/handoff-routes.js.map +1 -0
  32. package/dist/server/handoff.d.ts +112 -0
  33. package/dist/server/handoff.d.ts.map +1 -0
  34. package/dist/server/handoff.js +102 -0
  35. package/dist/server/handoff.js.map +1 -0
  36. package/dist/server/index.d.ts +11 -4
  37. package/dist/server/index.d.ts.map +1 -1
  38. package/dist/server/index.js +9 -3
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/server/middleware.d.ts +35 -0
  41. package/dist/server/middleware.d.ts.map +1 -1
  42. package/dist/server/middleware.js +50 -10
  43. package/dist/server/middleware.js.map +1 -1
  44. package/dist/server/passwordless.d.ts +68 -0
  45. package/dist/server/passwordless.d.ts.map +1 -0
  46. package/dist/server/passwordless.js +136 -0
  47. package/dist/server/passwordless.js.map +1 -0
  48. package/dist/server/revoke.d.ts +10 -0
  49. package/dist/server/revoke.d.ts.map +1 -1
  50. package/dist/server/revoke.js +19 -2
  51. package/dist/server/revoke.js.map +1 -1
  52. package/dist/server/store/postgres.d.ts +35 -0
  53. package/dist/server/store/postgres.d.ts.map +1 -0
  54. package/dist/server/store/postgres.js +88 -0
  55. package/dist/server/store/postgres.js.map +1 -0
  56. package/dist/server/token-utils.d.ts +12 -2
  57. package/dist/server/token-utils.d.ts.map +1 -1
  58. package/dist/server/token-utils.js +9 -4
  59. package/dist/server/token-utils.js.map +1 -1
  60. package/package.json +21 -8
  61. package/src/migrations/vk_auth_001_verification_codes.sql +55 -0
  62. package/src/migrations/vk_auth_003_role_scopes.sql +43 -0
@@ -45,6 +45,16 @@ export interface CookieAuthMiddlewareOptions {
45
45
  * tests lets you construct a middleware without touching env.
46
46
  */
47
47
  config?: AuthServerConfig;
48
+ /**
49
+ * Map verified JWT claims to the scope strings the
50
+ * `handler({ scopes })` gate checks. Defaults to
51
+ * {@link defaultScopeMapper}, which unions the access-token `scope`
52
+ * claim with Cognito `cognito:groups` and a `custom:scopes`
53
+ * attribute — so id-token browser sessions (which carry no `scope`
54
+ * claim) can still satisfy scoped routes via group / attribute
55
+ * membership. Provide your own to map app-specific claims.
56
+ */
57
+ scopeMapper?: (claims: Record<string, unknown>) => string[];
48
58
  }
49
59
  /**
50
60
  * Build a `@venturekit/runtime` middleware that verifies a session JWT
@@ -73,4 +83,29 @@ export declare function cookieAuthMiddleware(options?: CookieAuthMiddlewareOptio
73
83
  * {@link cookieAuthMiddleware} instead.
74
84
  */
75
85
  export declare function extractToken(event: APIGatewayProxyEventV2, cookieName?: string): string | null;
86
+ /**
87
+ * Default claims→scopes mapper.
88
+ *
89
+ * Unions three sources so BOTH access-token and id-token sessions can
90
+ * satisfy the `handler({ scopes })` gate:
91
+ * - `scope` — space-separated, present on Cognito ACCESS tokens.
92
+ * - `cognito:groups` — `string[]` on both id and access tokens; group
93
+ * names double as coarse scopes (e.g. a `cms.admin` group satisfies
94
+ * `scopes: ['cms.admin']`).
95
+ * - `custom:scopes` — a custom user-pool attribute holding a space- or
96
+ * comma-separated scope list; the recommended way to attach scopes
97
+ * to id-token browser sessions, which otherwise carry no `scope`.
98
+ *
99
+ * De-duplicates and drops empties. Override via
100
+ * {@link CookieAuthMiddlewareOptions.scopeMapper} for app-specific claims.
101
+ *
102
+ * # id-token vs access-token (the A2 decision)
103
+ * The default {@link cookieAuthMiddleware} verifies ID tokens because the
104
+ * browser session cookie is an id token (it carries `email` + profile).
105
+ * ID tokens have no OAuth `scope`, so scope gating on id-token sessions
106
+ * MUST come from Cognito groups or a `custom:scopes` attribute — which
107
+ * this mapper reads out of the box. For service-to-service callers, use
108
+ * `cookieAuthMiddleware({ tokenUse: 'access' })` and the `scope` claim.
109
+ */
110
+ export declare function defaultScopeMapper(claims: Record<string, unknown>): string[];
76
111
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,UAAU,CAAC,cAAc,CAAC,CAiC5B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,sBAAsB,EAC7B,UAAU,GAAE,MAAwB,GACnC,MAAM,GAAG,IAAI,CAoBf"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,UAAU,CAAC,cAAc,CAAC,CAkC5B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,sBAAsB,EAC7B,UAAU,GAAE,MAAwB,GACnC,MAAM,GAAG,IAAI,CAoBf;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,EAAE,CAiBV"}
@@ -46,6 +46,7 @@ import { loadAuthServerConfig } from './config.js';
46
46
  export function cookieAuthMiddleware(options = {}) {
47
47
  const cookieName = options.cookieName ?? ID_TOKEN_COOKIE;
48
48
  const tokenUse = options.tokenUse ?? 'id';
49
+ const scopeMapper = options.scopeMapper ?? defaultScopeMapper;
49
50
  // Resolve config lazily so a route module can import the middleware
50
51
  // factory at file-load time without the env vars being set (tests,
51
52
  // local type-only imports, etc.). The first actual request resolves
@@ -70,7 +71,7 @@ export function cookieAuthMiddleware(options = {}) {
70
71
  ...(cfg.endpoint ? { endpoint: cfg.endpoint } : {}),
71
72
  });
72
73
  if (claims) {
73
- ctx.user = claimsToUserContext(claims);
74
+ ctx.user = claimsToUserContext(claims, scopeMapper);
74
75
  }
75
76
  }
76
77
  return next();
@@ -103,17 +104,56 @@ export function extractToken(event, cookieName = ID_TOKEN_COOKIE) {
103
104
  : (headers['cookie'] ?? headers['Cookie']);
104
105
  return readCookieFromHeader(cookieHeader, cookieName);
105
106
  }
106
- function claimsToUserContext(claims) {
107
+ /**
108
+ * Default claims→scopes mapper.
109
+ *
110
+ * Unions three sources so BOTH access-token and id-token sessions can
111
+ * satisfy the `handler({ scopes })` gate:
112
+ * - `scope` — space-separated, present on Cognito ACCESS tokens.
113
+ * - `cognito:groups` — `string[]` on both id and access tokens; group
114
+ * names double as coarse scopes (e.g. a `cms.admin` group satisfies
115
+ * `scopes: ['cms.admin']`).
116
+ * - `custom:scopes` — a custom user-pool attribute holding a space- or
117
+ * comma-separated scope list; the recommended way to attach scopes
118
+ * to id-token browser sessions, which otherwise carry no `scope`.
119
+ *
120
+ * De-duplicates and drops empties. Override via
121
+ * {@link CookieAuthMiddlewareOptions.scopeMapper} for app-specific claims.
122
+ *
123
+ * # id-token vs access-token (the A2 decision)
124
+ * The default {@link cookieAuthMiddleware} verifies ID tokens because the
125
+ * browser session cookie is an id token (it carries `email` + profile).
126
+ * ID tokens have no OAuth `scope`, so scope gating on id-token sessions
127
+ * MUST come from Cognito groups or a `custom:scopes` attribute — which
128
+ * this mapper reads out of the box. For service-to-service callers, use
129
+ * `cookieAuthMiddleware({ tokenUse: 'access' })` and the `scope` claim.
130
+ */
131
+ export function defaultScopeMapper(claims) {
132
+ const out = new Set();
133
+ const addAll = (raw, splitter) => {
134
+ if (typeof raw === 'string') {
135
+ for (const s of raw.split(splitter))
136
+ if (s)
137
+ out.add(s);
138
+ }
139
+ };
140
+ // Access-token scope claim (space-separated).
141
+ addAll(claims['scope'], / +/);
142
+ // Cognito groups (array on id + access tokens).
143
+ const groups = claims['cognito:groups'];
144
+ if (Array.isArray(groups)) {
145
+ for (const g of groups)
146
+ if (typeof g === 'string' && g)
147
+ out.add(g);
148
+ }
149
+ // custom:scopes attribute (space- or comma-separated).
150
+ addAll(claims['custom:scopes'], /[\s,]+/);
151
+ return [...out];
152
+ }
153
+ function claimsToUserContext(claims, scopeMapper = defaultScopeMapper) {
107
154
  const sub = claims['sub'];
108
155
  const email = claims['email'];
109
- // `scope` appears on access tokens (space-separated). ID tokens don't
110
- // carry scopes; if the app uses ID tokens, scope-based route gating
111
- // should be backed by `custom:*` role/scope attributes on the user
112
- // pool and mapped in a downstream mapper.
113
- const scopeClaim = claims['scope'];
114
- const scopes = typeof scopeClaim === 'string'
115
- ? scopeClaim.split(' ').filter(Boolean)
116
- : [];
156
+ const scopes = scopeMapper(claims);
117
157
  return {
118
158
  id: typeof sub === 'string' ? sub : '',
119
159
  email: typeof email === 'string' ? email : undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Server-verified (passwordless) identity flows.
3
+ *
4
+ * For UXes where the APP proves address ownership out-of-band — magic
5
+ * links, OAuth/OIDC callbacks, emailed one-time codes — and Cognito is
6
+ * only the identity/token backend, never the credential UI:
7
+ *
8
+ * 1. {@link ensureUserForVerifiedEmail} — idempotently create the
9
+ * Cognito user (invite email suppressed, `email_verified=true`)
10
+ * or fetch the existing one; the returned `sub` doubles as the
11
+ * app's `users.id`.
12
+ * 2. {@link mintSessionForVerifiedEmail} — rotate the user's
13
+ * password to a throwaway strong secret (`AdminSetUserPassword`,
14
+ * permanent) and immediately sign in via
15
+ * `InitiateAuth(USER_PASSWORD_AUTH)`. The password is discarded;
16
+ * only the tokens leave the function.
17
+ *
18
+ * Requirements:
19
+ * - the app client must allow `ALLOW_USER_PASSWORD_AUTH`;
20
+ * - the caller's IAM role needs the `cognito-idp:Admin*` actions
21
+ * used below (VentureKit's auth intent grants them to the API
22
+ * Lambdas).
23
+ *
24
+ * Security notes:
25
+ * - the rotated password never leaves the server and satisfies the
26
+ * default "strong" policy (length + upper/lower/digit/symbol);
27
+ * - replays are benign: re-minting just issues another valid token
28
+ * set for the same identity — revoke on sign-out;
29
+ * - concurrent mints race harmlessly (last rotation wins, every
30
+ * token set issued stays valid until expiry/revocation).
31
+ */
32
+ import type { AuthServerConfig } from './config.js';
33
+ import { type SignInResult } from './tokens.js';
34
+ export interface EnsureUserForVerifiedEmailInput {
35
+ /** Address the app has already proven ownership of. */
36
+ email: string;
37
+ /** Optional standard attributes copied onto the Cognito record. */
38
+ firstName?: string;
39
+ lastName?: string;
40
+ /**
41
+ * Additional attributes (e.g. `{ 'custom:tenantRoles': '…' }`).
42
+ * Applied on create AND refreshed on subsequent calls.
43
+ */
44
+ attributes?: Record<string, string>;
45
+ }
46
+ export interface EnsureUserForVerifiedEmailResult {
47
+ /** Cognito `sub` (UUID) — commonly reused as the app's user id. */
48
+ sub: string;
49
+ /** True when this call created the Cognito user. */
50
+ created: boolean;
51
+ }
52
+ /**
53
+ * Idempotently ensure a Cognito user exists for a server-verified
54
+ * email. New users are auto-confirmed with `email_verified=true` and
55
+ * the invite email suppressed — the app owns the UX. On subsequent
56
+ * calls the profile attributes are refreshed best-effort.
57
+ */
58
+ export declare function ensureUserForVerifiedEmail(input: EnsureUserForVerifiedEmailInput, config?: AuthServerConfig): Promise<EnsureUserForVerifiedEmailResult>;
59
+ /**
60
+ * Mint a full token set (id + access + refresh) for a user whose
61
+ * email the app just verified: rotate the password server-side, sign
62
+ * in with it, discard it.
63
+ *
64
+ * Throws {@link AuthError} when the rotation or sign-in fails (e.g.
65
+ * `USER_PASSWORD_AUTH` not enabled on the app client).
66
+ */
67
+ export declare function mintSessionForVerifiedEmail(email: string, config?: AuthServerConfig): Promise<SignInResult>;
68
+ //# sourceMappingURL=passwordless.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwordless.d.ts","sourceRoot":"","sources":["../../src/server/passwordless.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAaH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAWrE,MAAM,WAAW,+BAA+B;IAC9C,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,gCAAgC;IAC/C,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,+BAA+B,EACtC,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,gCAAgC,CAAC,CAgE3C;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Server-verified (passwordless) identity flows.
3
+ *
4
+ * For UXes where the APP proves address ownership out-of-band — magic
5
+ * links, OAuth/OIDC callbacks, emailed one-time codes — and Cognito is
6
+ * only the identity/token backend, never the credential UI:
7
+ *
8
+ * 1. {@link ensureUserForVerifiedEmail} — idempotently create the
9
+ * Cognito user (invite email suppressed, `email_verified=true`)
10
+ * or fetch the existing one; the returned `sub` doubles as the
11
+ * app's `users.id`.
12
+ * 2. {@link mintSessionForVerifiedEmail} — rotate the user's
13
+ * password to a throwaway strong secret (`AdminSetUserPassword`,
14
+ * permanent) and immediately sign in via
15
+ * `InitiateAuth(USER_PASSWORD_AUTH)`. The password is discarded;
16
+ * only the tokens leave the function.
17
+ *
18
+ * Requirements:
19
+ * - the app client must allow `ALLOW_USER_PASSWORD_AUTH`;
20
+ * - the caller's IAM role needs the `cognito-idp:Admin*` actions
21
+ * used below (VentureKit's auth intent grants them to the API
22
+ * Lambdas).
23
+ *
24
+ * Security notes:
25
+ * - the rotated password never leaves the server and satisfies the
26
+ * default "strong" policy (length + upper/lower/digit/symbol);
27
+ * - replays are benign: re-minting just issues another valid token
28
+ * set for the same identity — revoke on sign-out;
29
+ * - concurrent mints race harmlessly (last rotation wins, every
30
+ * token set issued stays valid until expiry/revocation).
31
+ */
32
+ import { AdminCreateUserCommand, AdminGetUserCommand, AdminSetUserPasswordCommand, AdminUpdateUserAttributesCommand, InitiateAuthCommand, MessageActionType, UsernameExistsException, } from '@aws-sdk/client-cognito-identity-provider';
33
+ import { randomBytes } from 'node:crypto';
34
+ import { loadAuthServerConfig } from './config.js';
35
+ import { getCognitoClient } from './cognito-client.js';
36
+ import { AuthError, mapProviderError } from './errors.js';
37
+ import { extractSignInTokens } from './tokens.js';
38
+ /**
39
+ * Cryptographically random password satisfying Cognito's default
40
+ * "strong" policy: 40 base64url chars + `Aa1!` to guarantee the
41
+ * character-class mix. Internal — the value is never surfaced.
42
+ */
43
+ function generateRotatingPassword() {
44
+ return `${randomBytes(30).toString('base64url')}Aa1!`;
45
+ }
46
+ /**
47
+ * Idempotently ensure a Cognito user exists for a server-verified
48
+ * email. New users are auto-confirmed with `email_verified=true` and
49
+ * the invite email suppressed — the app owns the UX. On subsequent
50
+ * calls the profile attributes are refreshed best-effort.
51
+ */
52
+ export async function ensureUserForVerifiedEmail(input, config = loadAuthServerConfig()) {
53
+ const client = getCognitoClient(config.region, config.endpoint);
54
+ const email = input.email.trim().toLowerCase();
55
+ const attributes = [
56
+ { Name: 'email', Value: email },
57
+ { Name: 'email_verified', Value: 'true' },
58
+ ];
59
+ if (input.firstName)
60
+ attributes.push({ Name: 'given_name', Value: input.firstName });
61
+ if (input.lastName)
62
+ attributes.push({ Name: 'family_name', Value: input.lastName });
63
+ for (const [name, value] of Object.entries(input.attributes ?? {})) {
64
+ attributes.push({ Name: name, Value: value });
65
+ }
66
+ try {
67
+ const res = await client.send(new AdminCreateUserCommand({
68
+ UserPoolId: config.userPoolId,
69
+ Username: email,
70
+ UserAttributes: attributes,
71
+ MessageAction: MessageActionType.SUPPRESS,
72
+ }));
73
+ const sub = res.User?.Attributes?.find((a) => a.Name === 'sub')?.Value;
74
+ if (!sub) {
75
+ throw new AuthError('incomplete_auth_result', 'AdminCreateUser did not return a sub', 500);
76
+ }
77
+ return { sub, created: true };
78
+ }
79
+ catch (err) {
80
+ if (!(err instanceof UsernameExistsException)) {
81
+ throw err instanceof AuthError ? err : mapProviderError(err, 'sign_up_failed');
82
+ }
83
+ }
84
+ // Already present — fetch the sub and refresh mutable attributes.
85
+ const existing = await client.send(new AdminGetUserCommand({ UserPoolId: config.userPoolId, Username: email }));
86
+ const sub = existing.UserAttributes?.find((a) => a.Name === 'sub')?.Value;
87
+ if (!sub) {
88
+ throw new AuthError('incomplete_auth_result', 'AdminGetUser did not return a sub', 500);
89
+ }
90
+ const updates = attributes.filter((a) => a.Name !== 'email' && a.Name !== 'email_verified');
91
+ if (updates.length) {
92
+ try {
93
+ await client.send(new AdminUpdateUserAttributesCommand({
94
+ UserPoolId: config.userPoolId,
95
+ Username: email,
96
+ UserAttributes: updates,
97
+ }));
98
+ }
99
+ catch {
100
+ // Best-effort — stale profile attributes are not worth failing
101
+ // an authentication flow over.
102
+ }
103
+ }
104
+ return { sub, created: false };
105
+ }
106
+ /**
107
+ * Mint a full token set (id + access + refresh) for a user whose
108
+ * email the app just verified: rotate the password server-side, sign
109
+ * in with it, discard it.
110
+ *
111
+ * Throws {@link AuthError} when the rotation or sign-in fails (e.g.
112
+ * `USER_PASSWORD_AUTH` not enabled on the app client).
113
+ */
114
+ export async function mintSessionForVerifiedEmail(email, config = loadAuthServerConfig()) {
115
+ const client = getCognitoClient(config.region, config.endpoint);
116
+ const normalized = email.trim().toLowerCase();
117
+ const password = generateRotatingPassword();
118
+ try {
119
+ await client.send(new AdminSetUserPasswordCommand({
120
+ UserPoolId: config.userPoolId,
121
+ Username: normalized,
122
+ Password: password,
123
+ Permanent: true,
124
+ }));
125
+ const res = await client.send(new InitiateAuthCommand({
126
+ AuthFlow: 'USER_PASSWORD_AUTH',
127
+ ClientId: config.appClientId,
128
+ AuthParameters: { USERNAME: normalized, PASSWORD: password },
129
+ }));
130
+ return extractSignInTokens(res.AuthenticationResult);
131
+ }
132
+ catch (err) {
133
+ throw err instanceof AuthError ? err : mapProviderError(err, 'sign_in_failed');
134
+ }
135
+ }
136
+ //# sourceMappingURL=passwordless.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwordless.js","sourceRoot":"","sources":["../../src/server/passwordless.ts"],"names":[],"mappings":"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"}
@@ -15,4 +15,14 @@ import type { AuthServerConfig } from './config.js';
15
15
  * the function never throws.
16
16
  */
17
17
  export declare function revokeRefreshToken(refreshToken: string, config?: AuthServerConfig): Promise<void>;
18
+ /**
19
+ * Invalidate EVERY outstanding refresh token for the caller via
20
+ * Cognito's `GlobalSignOut` (keyed by a valid access token, unlike
21
+ * {@link revokeRefreshToken} which revokes one refresh token). Already
22
+ * issued access tokens keep working until natural expiry.
23
+ *
24
+ * **Best-effort**, same contract as {@link revokeRefreshToken}: sign-out
25
+ * must never fail the user, so errors are logged and swallowed.
26
+ */
27
+ export declare function globalSignOut(accessToken: string, config?: AuthServerConfig): Promise<void>;
18
28
  //# sourceMappingURL=revoke.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,IAAI,CAAC,CAaf"}
1
+ {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -7,7 +7,7 @@
7
7
  * because the server-side revoke had a hiccup — the cookies clear
8
8
  * regardless.
9
9
  */
10
- import { RevokeTokenCommand } from '@aws-sdk/client-cognito-identity-provider';
10
+ import { GlobalSignOutCommand, RevokeTokenCommand, } from '@aws-sdk/client-cognito-identity-provider';
11
11
  import { loadAuthServerConfig } from './config.js';
12
12
  import { getCognitoClient } from './cognito-client.js';
13
13
  /**
@@ -25,8 +25,25 @@ export async function revokeRefreshToken(refreshToken, config = loadAuthServerCo
25
25
  }));
26
26
  }
27
27
  catch (err) {
28
- // eslint-disable-next-line no-console
29
28
  console.warn('[@venturekit/auth/server] refresh-token revoke failed (ignored):', err);
30
29
  }
31
30
  }
31
+ /**
32
+ * Invalidate EVERY outstanding refresh token for the caller via
33
+ * Cognito's `GlobalSignOut` (keyed by a valid access token, unlike
34
+ * {@link revokeRefreshToken} which revokes one refresh token). Already
35
+ * issued access tokens keep working until natural expiry.
36
+ *
37
+ * **Best-effort**, same contract as {@link revokeRefreshToken}: sign-out
38
+ * must never fail the user, so errors are logged and swallowed.
39
+ */
40
+ export async function globalSignOut(accessToken, config = loadAuthServerConfig()) {
41
+ const client = getCognitoClient(config.region, config.endpoint);
42
+ try {
43
+ await client.send(new GlobalSignOutCommand({ AccessToken: accessToken }));
44
+ }
45
+ catch (err) {
46
+ console.warn('[@venturekit/auth/server] global sign-out failed (ignored):', err);
47
+ }
48
+ }
32
49
  //# sourceMappingURL=revoke.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CACf,IAAI,kBAAkB,CAAC;YACrB,QAAQ,EAAE,MAAM,CAAC,WAAW;YAC5B,KAAK,EAAE,YAAY;SACpB,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,kEAAkE,EAAE,GAAG,CAAC,CAAC;IACxF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CACf,IAAI,kBAAkB,CAAC;YACrB,QAAQ,EAAE,MAAM,CAAC,WAAW;YAC5B,KAAK,EAAE,YAAY;SACpB,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QAEb,OAAO,CAAC,IAAI,CAAC,kEAAkE,EAAE,GAAG,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,WAAmB,EACnB,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QAEb,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,GAAG,CAAC,CAAC;IACnF,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @venturekit/auth — Postgres VerificationCodeStore
3
+ *
4
+ * Production {@link VerificationCodeStore} backing the OTP primitives
5
+ * ({@link requestVerificationCode} / {@link verifyVerificationCode}).
6
+ * Talks to the project's primary Postgres database via
7
+ * `@venturekit/data`'s `query()` — or any `Querier`, so the caller can
8
+ * enlist code writes in an already-open transaction (pass a
9
+ * `Transaction`'s `query`; the default is the global connection pool).
10
+ *
11
+ * The backing table `vk_verification_codes` is shipped by this package's
12
+ * `migrations/vk_auth_001_verification_codes.sql` and applied by the
13
+ * in-VPC migration runner `@venturekit/infra` provisions — so consumers
14
+ * get the table and this store from the same package.
15
+ *
16
+ * `@venturekit/data` is an OPTIONAL peer dependency: importing THIS
17
+ * module is what opts a project into the Postgres store, so apps on a
18
+ * different backend (DynamoDB, the in-memory dev store) never load it
19
+ * and `@venturekit/auth/server` stays storage-agnostic.
20
+ *
21
+ * `expires_at` crosses the store boundary as ms-epoch to satisfy
22
+ * {@link VerificationCodeRecord}'s numeric contract: `to_timestamp` on
23
+ * write, `EXTRACT(EPOCH …) * 1000` on read.
24
+ */
25
+ import { type Querier } from '@venturekit/data';
26
+ import type { VerificationCodeStore } from '../verification.js';
27
+ /**
28
+ * Build a Postgres-backed {@link VerificationCodeStore} bound to `q`.
29
+ *
30
+ * Pass a transaction's `query` (see `beginTransaction` /
31
+ * `withTransaction` in `@venturekit/data`) to make code writes
32
+ * participate in an open transaction; omit it to use the global pool.
33
+ */
34
+ export declare function createPostgresVerificationCodeStore(q?: Querier): VerificationCodeStore;
35
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/server/store/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAS,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAwB5B;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CACjD,CAAC,GAAE,OAAe,GACjB,qBAAqB,CAgEvB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @venturekit/auth — Postgres VerificationCodeStore
3
+ *
4
+ * Production {@link VerificationCodeStore} backing the OTP primitives
5
+ * ({@link requestVerificationCode} / {@link verifyVerificationCode}).
6
+ * Talks to the project's primary Postgres database via
7
+ * `@venturekit/data`'s `query()` — or any `Querier`, so the caller can
8
+ * enlist code writes in an already-open transaction (pass a
9
+ * `Transaction`'s `query`; the default is the global connection pool).
10
+ *
11
+ * The backing table `vk_verification_codes` is shipped by this package's
12
+ * `migrations/vk_auth_001_verification_codes.sql` and applied by the
13
+ * in-VPC migration runner `@venturekit/infra` provisions — so consumers
14
+ * get the table and this store from the same package.
15
+ *
16
+ * `@venturekit/data` is an OPTIONAL peer dependency: importing THIS
17
+ * module is what opts a project into the Postgres store, so apps on a
18
+ * different backend (DynamoDB, the in-memory dev store) never load it
19
+ * and `@venturekit/auth/server` stays storage-agnostic.
20
+ *
21
+ * `expires_at` crosses the store boundary as ms-epoch to satisfy
22
+ * {@link VerificationCodeRecord}'s numeric contract: `to_timestamp` on
23
+ * write, `EXTRACT(EPOCH …) * 1000` on read.
24
+ */
25
+ import { query } from '@venturekit/data';
26
+ function rowToRecord(r) {
27
+ return {
28
+ channel: r.channel,
29
+ identifier: r.identifier,
30
+ codeHash: r.code_hash,
31
+ expiresAt: Number(r.expires_at),
32
+ attempts: r.attempts,
33
+ maxAttempts: r.max_attempts,
34
+ };
35
+ }
36
+ /**
37
+ * Build a Postgres-backed {@link VerificationCodeStore} bound to `q`.
38
+ *
39
+ * Pass a transaction's `query` (see `beginTransaction` /
40
+ * `withTransaction` in `@venturekit/data`) to make code writes
41
+ * participate in an open transaction; omit it to use the global pool.
42
+ */
43
+ export function createPostgresVerificationCodeStore(q = query) {
44
+ return {
45
+ async put({ channel, identifier, codeHash, expiresAt, maxAttempts }) {
46
+ // Opportunistic sweep of already-expired rows on every write —
47
+ // keeps the table bounded without a dedicated cron (the migration
48
+ // ships the `expires_at` index that backs this).
49
+ await q(`DELETE FROM vk_verification_codes WHERE expires_at < CURRENT_TIMESTAMP`);
50
+ // Idempotent per the store contract: a re-request overwrites the
51
+ // outstanding code for this (channel, identifier) and resets the
52
+ // attempt counter — a fresh code means "let me try again", not
53
+ // "reset the limit on the old one".
54
+ await q(`INSERT INTO vk_verification_codes
55
+ (channel, identifier, code_hash, expires_at, attempts, max_attempts)
56
+ VALUES ($1, $2, $3, to_timestamp($4 / 1000.0), 0, $5)
57
+ ON CONFLICT (channel, identifier) DO UPDATE
58
+ SET code_hash = EXCLUDED.code_hash,
59
+ expires_at = EXCLUDED.expires_at,
60
+ attempts = 0,
61
+ max_attempts = EXCLUDED.max_attempts,
62
+ created_at = NOW()`, [channel, identifier, codeHash, expiresAt, maxAttempts]);
63
+ },
64
+ async get({ channel, identifier }) {
65
+ // Return the row as-is, INCLUDING when expired — verifyVerificationCode
66
+ // performs the expiry check and deletes the row itself.
67
+ return q(`SELECT channel, identifier, code_hash,
68
+ (EXTRACT(EPOCH FROM expires_at) * 1000)::bigint AS expires_at,
69
+ attempts, max_attempts
70
+ FROM vk_verification_codes
71
+ WHERE channel = $1 AND identifier = $2
72
+ LIMIT 1`, [channel, identifier], (rows) => {
73
+ const r = rows[0];
74
+ return r ? rowToRecord(r) : undefined;
75
+ });
76
+ },
77
+ async incrementAttempts({ channel, identifier }) {
78
+ return q(`UPDATE vk_verification_codes
79
+ SET attempts = attempts + 1
80
+ WHERE channel = $1 AND identifier = $2
81
+ RETURNING attempts`, [channel, identifier], (rows) => rows[0]?.attempts ?? 0);
82
+ },
83
+ async delete({ channel, identifier }) {
84
+ await q(`DELETE FROM vk_verification_codes WHERE channel = $1 AND identifier = $2`, [channel, identifier]);
85
+ },
86
+ };
87
+ }
88
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../../src/server/store/postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,KAAK,EAAgB,MAAM,kBAAkB,CAAC;AAiBvD,SAAS,WAAW,CAAC,CAAsB;IACzC,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,QAAQ,EAAE,CAAC,CAAC,SAAS;QACrB,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;QAC/B,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,WAAW,EAAE,CAAC,CAAC,YAAY;KAC5B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mCAAmC,CACjD,IAAa,KAAK;IAElB,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE;YACjE,+DAA+D;YAC/D,kEAAkE;YAClE,iDAAiD;YACjD,MAAM,CAAC,CACL,wEAAwE,CACzE,CAAC;YACF,iEAAiE;YACjE,iEAAiE;YACjE,+DAA+D;YAC/D,oCAAoC;YACpC,MAAM,CAAC,CACL;;;;;;;;qCAQ6B,EAC7B,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CACxD,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE;YAC/B,wEAAwE;YACxE,wDAAwD;YACxD,OAAO,CAAC,CACN;;;;;kBAKU,EACV,CAAC,OAAO,EAAE,UAAU,CAAC,EACrB,CAAC,IAAI,EAAE,EAAE;gBACP,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAA+C,CAAC;gBAChE,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACxC,CAAC,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE;YAC7C,OAAO,CAAC,CACN;;;6BAGqB,EACrB,CAAC,OAAO,EAAE,UAAU,CAAC,EACrB,CAAC,IAAI,EAAE,EAAE,CACN,IAAI,CAAC,CAAC,CAAsC,EAAE,QAAQ,IAAI,CAAC,CAC/D,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE;YAClC,MAAM,CAAC,CACL,0EAA0E,EAC1E,CAAC,OAAO,EAAE,UAAU,CAAC,CACtB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -8,8 +8,9 @@
8
8
  * treat those responses as fatal, we derive the remaining lifetime
9
9
  * from the access token itself.
10
10
  *
11
- * This file intentionally has no `@venturekit/auth/server` surface
12
- * exposure it's consumed by `sign-in.ts` and `refresh.ts` only.
11
+ * `decodeJwtClaims` IS part of the public surface: consumers that
12
+ * just minted tokens (sign-in, passwordless flows) read profile
13
+ * claims from the id token without a redundant verify round-trip.
13
14
  */
14
15
  /** Cognito's default access-token lifetime (1 hour). */
15
16
  export declare const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
@@ -24,4 +25,13 @@ export declare const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
24
25
  * `InitiateAuthCommand`). Use `verifyAndDecode` for untrusted input.
25
26
  */
26
27
  export declare function deriveExpiresInFromJwt(jwt: string, nowSeconds?: number): number;
28
+ /**
29
+ * Base64url-decode and JSON.parse a JWT payload. Returns `null` on any
30
+ * failure — callers fall back to a default.
31
+ *
32
+ * Does NOT verify the signature — only use on tokens received
33
+ * directly from the identity provider (e.g. right after sign-in).
34
+ * Use `verifyAndDecode` for anything that crossed a trust boundary.
35
+ */
36
+ export declare function decodeJwtClaims<T extends Record<string, unknown> = Record<string, unknown>>(jwt: string): T | null;
27
37
  //# sourceMappingURL=token-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-utils.d.ts","sourceRoot":"","sources":["../../src/server/token-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,wDAAwD;AACxD,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAErD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAsC,GACjD,MAAM,CAgBR"}
1
+ {"version":3,"file":"token-utils.d.ts","sourceRoot":"","sources":["../../src/server/token-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,wDAAwD;AACxD,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAErD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAsC,GACjD,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzF,GAAG,EAAE,MAAM,GACV,CAAC,GAAG,IAAI,CAYV"}
@@ -8,8 +8,9 @@
8
8
  * treat those responses as fatal, we derive the remaining lifetime
9
9
  * from the access token itself.
10
10
  *
11
- * This file intentionally has no `@venturekit/auth/server` surface
12
- * exposure it's consumed by `sign-in.ts` and `refresh.ts` only.
11
+ * `decodeJwtClaims` IS part of the public surface: consumers that
12
+ * just minted tokens (sign-in, passwordless flows) read profile
13
+ * claims from the id token without a redundant verify round-trip.
13
14
  */
14
15
  /** Cognito's default access-token lifetime (1 hour). */
15
16
  export const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
@@ -24,7 +25,7 @@ export const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
24
25
  * `InitiateAuthCommand`). Use `verifyAndDecode` for untrusted input.
25
26
  */
26
27
  export function deriveExpiresInFromJwt(jwt, nowSeconds = Math.floor(Date.now() / 1000)) {
27
- const payload = decodeJwtPayload(jwt);
28
+ const payload = decodeJwtClaims(jwt);
28
29
  if (!payload)
29
30
  return DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
30
31
  const exp = typeof payload.exp === 'number' ? payload.exp : undefined;
@@ -42,8 +43,12 @@ export function deriveExpiresInFromJwt(jwt, nowSeconds = Math.floor(Date.now() /
42
43
  /**
43
44
  * Base64url-decode and JSON.parse a JWT payload. Returns `null` on any
44
45
  * failure — callers fall back to a default.
46
+ *
47
+ * Does NOT verify the signature — only use on tokens received
48
+ * directly from the identity provider (e.g. right after sign-in).
49
+ * Use `verifyAndDecode` for anything that crossed a trust boundary.
45
50
  */
46
- function decodeJwtPayload(jwt) {
51
+ export function decodeJwtClaims(jwt) {
47
52
  if (typeof jwt !== 'string' || jwt.length === 0)
48
53
  return null;
49
54
  const parts = jwt.split('.');