@venturekit/auth 0.0.0-dev.20260507015944 → 0.0.0-dev.20260511023410
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/dist/server/federated.d.ts +153 -0
- package/dist/server/federated.d.ts.map +1 -0
- package/dist/server/federated.js +436 -0
- package/dist/server/federated.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/verification.d.ts +148 -0
- package/dist/server/verification.d.ts.map +1 -0
- package/dist/server/verification.js +177 -0
- package/dist/server/verification.js.map +1 -0
- package/migrations/vk_auth_001_verification_codes.sql +52 -0
- package/package.json +10 -5
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated sign-in — server-side OAuth Authorization Code flow.
|
|
3
|
+
*
|
|
4
|
+
* VentureKit deliberately does NOT enable the Cognito Hosted UI: your
|
|
5
|
+
* application owns the login screen. The flow is:
|
|
6
|
+
*
|
|
7
|
+
* 1. **SPA** — user clicks "Sign in with Google".
|
|
8
|
+
* 2. **SPA → API** — `POST /auth/federated/google/start { redirectUri }`.
|
|
9
|
+
* The `redirectUri` is a page on the SPA itself (e.g.
|
|
10
|
+
* `https://app.example.com/auth/google/callback`) and must be
|
|
11
|
+
* registered with the IdP as an allowed redirect.
|
|
12
|
+
* 3. **API → SPA** — `{ authorizeUrl }` plus an HttpOnly cookie
|
|
13
|
+
* holding a CSRF `state`. The API computes the URL with
|
|
14
|
+
* {@link buildAuthorizeUrl}.
|
|
15
|
+
* 4. **SPA** navigates to `authorizeUrl`.
|
|
16
|
+
* 5. **IdP → SPA** — after authentication, the IdP 302s to
|
|
17
|
+
* `redirectUri?code=…&state=…`. The SPA reads the query, then
|
|
18
|
+
* calls the API again.
|
|
19
|
+
* 6. **SPA → API** — `POST /auth/federated/google/complete
|
|
20
|
+
* { code, state, redirectUri }`. The API:
|
|
21
|
+
* - re-checks `state` against the cookie ({@link verifyOAuthState});
|
|
22
|
+
* - exchanges the code for tokens at the IdP via
|
|
23
|
+
* {@link exchangeAuthorizationCode} (the
|
|
24
|
+
* `client_secret` lives in Secrets Manager, never in the SPA);
|
|
25
|
+
* - resolves the user's verified profile;
|
|
26
|
+
* - calls {@link signInAsFederatedUser} which mints a Cognito
|
|
27
|
+
* session for the same email (creating the user on first
|
|
28
|
+
* contact).
|
|
29
|
+
* 7. **API → SPA** — Set-Cookie session + JSON envelope.
|
|
30
|
+
*
|
|
31
|
+
* The SPA never sees the `client_secret`, the IdP `access_token`, or
|
|
32
|
+
* the OAuth `code` after step 6. Cookies stay HttpOnly + SameSite=Lax.
|
|
33
|
+
*
|
|
34
|
+
* The OAuth `client_id` / `client_secret` per provider live in the
|
|
35
|
+
* Secrets Manager placeholder VentureKit provisions when
|
|
36
|
+
* `AuthIntent.federated` is set; the ARN is exposed as
|
|
37
|
+
* `COGNITO_FEDERATED_<PROVIDER>_SECRET_ARN` on every Lambda.
|
|
38
|
+
*/
|
|
39
|
+
import type { AuthServerConfig } from './config.js';
|
|
40
|
+
import { type SignInResult } from './tokens.js';
|
|
41
|
+
export type FederatedProvider = 'google' | 'facebook' | 'apple';
|
|
42
|
+
/**
|
|
43
|
+
* Verified federated profile — the shape every IdP normalizes to
|
|
44
|
+
* after `exchangeAuthorizationCode` runs.
|
|
45
|
+
*/
|
|
46
|
+
export interface FederatedProfile {
|
|
47
|
+
/** Stable identifier from the IdP. Google `sub`, Facebook `id`. */
|
|
48
|
+
externalId: string;
|
|
49
|
+
/** Verified email. Required — Cognito uses email as the username. */
|
|
50
|
+
email: string;
|
|
51
|
+
/** Optional display name, passed through to Cognito's `name` attr. */
|
|
52
|
+
name?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface FederatedProviderCredentials {
|
|
55
|
+
clientId: string;
|
|
56
|
+
clientSecret: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read the OAuth client credentials for `provider` from the Secrets
|
|
60
|
+
* Manager placeholder VentureKit provisions. The ARN is read from
|
|
61
|
+
* `COGNITO_FEDERATED_<PROVIDER>_SECRET_ARN` on every Lambda; the
|
|
62
|
+
* secret value is JSON `{"clientId":"…","clientSecret":"…"}`.
|
|
63
|
+
*
|
|
64
|
+
* Cached for the lifetime of the Lambda container — IdP credentials
|
|
65
|
+
* rotate via Secrets Manager rotation + a redeploy, not per-request.
|
|
66
|
+
*/
|
|
67
|
+
export declare function loadFederatedProviderCredentials(provider: FederatedProvider, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): Promise<FederatedProviderCredentials>;
|
|
68
|
+
/** Test-only — drop the cached credentials between vitest runs. */
|
|
69
|
+
export declare function _resetFederatedCredentialsForTesting(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Generate a 32-byte URL-safe random string used as the OAuth `state`
|
|
72
|
+
* parameter. The same value is returned to the SPA (so it can be
|
|
73
|
+
* appended to `authorizeUrl`) and pinned to the user's browser via an
|
|
74
|
+
* HttpOnly cookie. {@link verifyOAuthState} performs a constant-time
|
|
75
|
+
* comparison on the callback.
|
|
76
|
+
*/
|
|
77
|
+
export declare function generateOAuthState(): string;
|
|
78
|
+
/**
|
|
79
|
+
* Constant-time equality check between the `state` echoed back by the
|
|
80
|
+
* IdP and the value that was set on the state cookie at `start` time.
|
|
81
|
+
* Returns `false` for any mismatch including length differences.
|
|
82
|
+
*/
|
|
83
|
+
export declare function verifyOAuthState(fromQuery: string | undefined, fromCookie: string | undefined): boolean;
|
|
84
|
+
export interface BuildAuthorizeUrlInput {
|
|
85
|
+
provider: FederatedProvider;
|
|
86
|
+
/**
|
|
87
|
+
* Where the IdP should redirect after auth. Must be a page on your
|
|
88
|
+
* SPA and must be registered as an allowed redirect URI in the IdP
|
|
89
|
+
* console — Google / Facebook reject mismatches.
|
|
90
|
+
*/
|
|
91
|
+
redirectUri: string;
|
|
92
|
+
/** Output of {@link generateOAuthState}. */
|
|
93
|
+
state: string;
|
|
94
|
+
/** Override default scopes if you need extra IdP permissions. */
|
|
95
|
+
scopes?: string[];
|
|
96
|
+
/**
|
|
97
|
+
* Extra query parameters appended verbatim. Useful for
|
|
98
|
+
* `prompt=select_account` (Google) or `auth_type=rerequest`
|
|
99
|
+
* (Facebook re-consent).
|
|
100
|
+
*/
|
|
101
|
+
extraParams?: Record<string, string>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Build the IdP authorize URL the SPA navigates to after `start`.
|
|
105
|
+
*
|
|
106
|
+
* Loads the OAuth `client_id` from Secrets Manager (cached). The
|
|
107
|
+
* `client_secret` is NOT used here — it's only needed at token
|
|
108
|
+
* exchange.
|
|
109
|
+
*/
|
|
110
|
+
export declare function buildAuthorizeUrl(input: BuildAuthorizeUrlInput, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): Promise<string>;
|
|
111
|
+
export interface ExchangeAuthorizationCodeInput {
|
|
112
|
+
provider: FederatedProvider;
|
|
113
|
+
/** The `code` query param the IdP appended to `redirectUri`. */
|
|
114
|
+
code: string;
|
|
115
|
+
/**
|
|
116
|
+
* Must match the `redirectUri` used in `buildAuthorizeUrl`. Both
|
|
117
|
+
* Google and Facebook compare these byte-for-byte and 400 on a
|
|
118
|
+
* mismatch.
|
|
119
|
+
*/
|
|
120
|
+
redirectUri: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Exchange an OAuth `code` for tokens at the IdP, then resolve the
|
|
124
|
+
* verified `FederatedProfile`. Throws {@link AuthError}
|
|
125
|
+
* (`federated_token_invalid`, HTTP 401) on any IdP error.
|
|
126
|
+
*/
|
|
127
|
+
export declare function exchangeAuthorizationCode(input: ExchangeAuthorizationCodeInput, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): Promise<FederatedProfile>;
|
|
128
|
+
export interface SignInAsFederatedUserInput {
|
|
129
|
+
/** Verified profile from {@link exchangeAuthorizationCode}. */
|
|
130
|
+
profile: FederatedProfile;
|
|
131
|
+
/** Provider name — recorded as `custom:federated_provider`. */
|
|
132
|
+
provider: FederatedProvider;
|
|
133
|
+
/**
|
|
134
|
+
* Standard Cognito attributes to set on first creation only — e.g.
|
|
135
|
+
* `{ phone_number: '+212…' }`. Ignored for existing users.
|
|
136
|
+
*/
|
|
137
|
+
initialAttributes?: Record<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Custom attributes to set on first creation only — keyed without
|
|
140
|
+
* the `custom:` prefix (the helper prepends it).
|
|
141
|
+
*/
|
|
142
|
+
initialCustomAttributes?: Record<string, string>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Sign a verified federated user in. Creates the Cognito user on
|
|
146
|
+
* first contact (idempotent), rotates a server-side password, and
|
|
147
|
+
* mints session tokens via `ADMIN_USER_PASSWORD_AUTH`.
|
|
148
|
+
*
|
|
149
|
+
* Returns the same {@link SignInResult} shape `signInWithPassword`
|
|
150
|
+
* returns, so cookie / session helpers stay identical.
|
|
151
|
+
*/
|
|
152
|
+
export declare function signInAsFederatedUser(input: SignInAsFederatedUserInput, config?: AuthServerConfig): Promise<SignInResult>;
|
|
153
|
+
//# sourceMappingURL=federated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federated.d.ts","sourceRoot":"","sources":["../../src/server/federated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAYH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAQD;;;;;;;;GAQG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,iBAAiB,EAC3B,GAAG,GAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxE,OAAO,CAAC,4BAA4B,CAAC,CAgEvC;AAED,mEAAmE;AACnE,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAMT;AAyCD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,sBAAsB,EAC7B,GAAG,GAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxE,OAAO,CAAC,MAAM,CAAC,CAejB;AAMD,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,EACrC,GAAG,GAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxE,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AA2JD,MAAM,WAAW,0BAA0B;IACzC,+DAA+D;IAC/D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,0BAA0B,EACjC,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,YAAY,CAAC,CA6HvB"}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated sign-in — server-side OAuth Authorization Code flow.
|
|
3
|
+
*
|
|
4
|
+
* VentureKit deliberately does NOT enable the Cognito Hosted UI: your
|
|
5
|
+
* application owns the login screen. The flow is:
|
|
6
|
+
*
|
|
7
|
+
* 1. **SPA** — user clicks "Sign in with Google".
|
|
8
|
+
* 2. **SPA → API** — `POST /auth/federated/google/start { redirectUri }`.
|
|
9
|
+
* The `redirectUri` is a page on the SPA itself (e.g.
|
|
10
|
+
* `https://app.example.com/auth/google/callback`) and must be
|
|
11
|
+
* registered with the IdP as an allowed redirect.
|
|
12
|
+
* 3. **API → SPA** — `{ authorizeUrl }` plus an HttpOnly cookie
|
|
13
|
+
* holding a CSRF `state`. The API computes the URL with
|
|
14
|
+
* {@link buildAuthorizeUrl}.
|
|
15
|
+
* 4. **SPA** navigates to `authorizeUrl`.
|
|
16
|
+
* 5. **IdP → SPA** — after authentication, the IdP 302s to
|
|
17
|
+
* `redirectUri?code=…&state=…`. The SPA reads the query, then
|
|
18
|
+
* calls the API again.
|
|
19
|
+
* 6. **SPA → API** — `POST /auth/federated/google/complete
|
|
20
|
+
* { code, state, redirectUri }`. The API:
|
|
21
|
+
* - re-checks `state` against the cookie ({@link verifyOAuthState});
|
|
22
|
+
* - exchanges the code for tokens at the IdP via
|
|
23
|
+
* {@link exchangeAuthorizationCode} (the
|
|
24
|
+
* `client_secret` lives in Secrets Manager, never in the SPA);
|
|
25
|
+
* - resolves the user's verified profile;
|
|
26
|
+
* - calls {@link signInAsFederatedUser} which mints a Cognito
|
|
27
|
+
* session for the same email (creating the user on first
|
|
28
|
+
* contact).
|
|
29
|
+
* 7. **API → SPA** — Set-Cookie session + JSON envelope.
|
|
30
|
+
*
|
|
31
|
+
* The SPA never sees the `client_secret`, the IdP `access_token`, or
|
|
32
|
+
* the OAuth `code` after step 6. Cookies stay HttpOnly + SameSite=Lax.
|
|
33
|
+
*
|
|
34
|
+
* The OAuth `client_id` / `client_secret` per provider live in the
|
|
35
|
+
* Secrets Manager placeholder VentureKit provisions when
|
|
36
|
+
* `AuthIntent.federated` is set; the ARN is exposed as
|
|
37
|
+
* `COGNITO_FEDERATED_<PROVIDER>_SECRET_ARN` on every Lambda.
|
|
38
|
+
*/
|
|
39
|
+
import { AdminCreateUserCommand, AdminGetUserCommand, AdminInitiateAuthCommand, AdminSetUserPasswordCommand, AdminUpdateUserAttributesCommand, } from '@aws-sdk/client-cognito-identity-provider';
|
|
40
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
41
|
+
import { loadAuthServerConfig } from './config.js';
|
|
42
|
+
import { getCognitoClient } from './cognito-client.js';
|
|
43
|
+
import { AuthError, mapProviderError } from './errors.js';
|
|
44
|
+
import { extractSignInTokens } from './tokens.js';
|
|
45
|
+
// ────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Provider-credential resolution (Secrets Manager → cached)
|
|
47
|
+
// ────────────────────────────────────────────────────────────────────
|
|
48
|
+
const credentialsCache = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* Read the OAuth client credentials for `provider` from the Secrets
|
|
51
|
+
* Manager placeholder VentureKit provisions. The ARN is read from
|
|
52
|
+
* `COGNITO_FEDERATED_<PROVIDER>_SECRET_ARN` on every Lambda; the
|
|
53
|
+
* secret value is JSON `{"clientId":"…","clientSecret":"…"}`.
|
|
54
|
+
*
|
|
55
|
+
* Cached for the lifetime of the Lambda container — IdP credentials
|
|
56
|
+
* rotate via Secrets Manager rotation + a redeploy, not per-request.
|
|
57
|
+
*/
|
|
58
|
+
export async function loadFederatedProviderCredentials(provider, env = process.env) {
|
|
59
|
+
const cached = credentialsCache.get(provider);
|
|
60
|
+
if (cached)
|
|
61
|
+
return cached;
|
|
62
|
+
const envVar = `COGNITO_FEDERATED_${provider.toUpperCase()}_SECRET_ARN`;
|
|
63
|
+
const arn = env[envVar];
|
|
64
|
+
if (!arn) {
|
|
65
|
+
throw new AuthError('federated_provider_not_configured', `[${provider}] missing env var ${envVar} — declare ` +
|
|
66
|
+
`\`federated: ['${provider}']\` on the auth intent in vk.config.ts ` +
|
|
67
|
+
`so VentureKit provisions the Secrets Manager placeholder, then ` +
|
|
68
|
+
`populate it with the OAuth client credentials from the IdP console.`, 500);
|
|
69
|
+
}
|
|
70
|
+
const { SecretsManagerClient, GetSecretValueCommand } = await import('@aws-sdk/client-secrets-manager');
|
|
71
|
+
const client = new SecretsManagerClient({
|
|
72
|
+
region: env['AWS_REGION'] ?? env['COGNITO_REGION'],
|
|
73
|
+
});
|
|
74
|
+
const res = await client.send(new GetSecretValueCommand({ SecretId: arn }));
|
|
75
|
+
if (!res.SecretString) {
|
|
76
|
+
throw new AuthError('federated_provider_not_configured', `[${provider}] Secrets Manager entry ${arn} is empty`, 500);
|
|
77
|
+
}
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(res.SecretString);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new AuthError('federated_provider_not_configured', `[${provider}] Secrets Manager entry ${arn} is not valid JSON`, 500);
|
|
84
|
+
}
|
|
85
|
+
if (!parsed.clientId ||
|
|
86
|
+
!parsed.clientSecret ||
|
|
87
|
+
parsed.clientId === 'PLACEHOLDER' ||
|
|
88
|
+
parsed.clientSecret === 'PLACEHOLDER') {
|
|
89
|
+
throw new AuthError('federated_provider_not_configured', `[${provider}] Secrets Manager entry ${arn} still holds the ` +
|
|
90
|
+
`placeholder. Populate it with the OAuth client id + secret ` +
|
|
91
|
+
`from the IdP console: aws secretsmanager put-secret-value ` +
|
|
92
|
+
`--secret-id ${arn} --secret-string '{"clientId":"…","clientSecret":"…"}'`, 500);
|
|
93
|
+
}
|
|
94
|
+
const creds = {
|
|
95
|
+
clientId: parsed.clientId,
|
|
96
|
+
clientSecret: parsed.clientSecret,
|
|
97
|
+
};
|
|
98
|
+
credentialsCache.set(provider, creds);
|
|
99
|
+
return creds;
|
|
100
|
+
}
|
|
101
|
+
/** Test-only — drop the cached credentials between vitest runs. */
|
|
102
|
+
export function _resetFederatedCredentialsForTesting() {
|
|
103
|
+
credentialsCache.clear();
|
|
104
|
+
}
|
|
105
|
+
// ────────────────────────────────────────────────────────────────────
|
|
106
|
+
// CSRF state — random token, signed cookie
|
|
107
|
+
// ────────────────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Generate a 32-byte URL-safe random string used as the OAuth `state`
|
|
110
|
+
* parameter. The same value is returned to the SPA (so it can be
|
|
111
|
+
* appended to `authorizeUrl`) and pinned to the user's browser via an
|
|
112
|
+
* HttpOnly cookie. {@link verifyOAuthState} performs a constant-time
|
|
113
|
+
* comparison on the callback.
|
|
114
|
+
*/
|
|
115
|
+
export function generateOAuthState() {
|
|
116
|
+
return randomBytes(32).toString('base64url');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Constant-time equality check between the `state` echoed back by the
|
|
120
|
+
* IdP and the value that was set on the state cookie at `start` time.
|
|
121
|
+
* Returns `false` for any mismatch including length differences.
|
|
122
|
+
*/
|
|
123
|
+
export function verifyOAuthState(fromQuery, fromCookie) {
|
|
124
|
+
if (!fromQuery || !fromCookie)
|
|
125
|
+
return false;
|
|
126
|
+
const a = Buffer.from(fromQuery);
|
|
127
|
+
const b = Buffer.from(fromCookie);
|
|
128
|
+
if (a.length !== b.length)
|
|
129
|
+
return false;
|
|
130
|
+
return timingSafeEqual(a, b);
|
|
131
|
+
}
|
|
132
|
+
const PROVIDER_ENDPOINTS = {
|
|
133
|
+
google: {
|
|
134
|
+
authorize: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
135
|
+
token: 'https://oauth2.googleapis.com/token',
|
|
136
|
+
// `openid email profile` returns an `id_token` (JWT) we can decode
|
|
137
|
+
// for the verified profile without a second `/userinfo` call.
|
|
138
|
+
defaultScopes: ['openid', 'email', 'profile'],
|
|
139
|
+
},
|
|
140
|
+
facebook: {
|
|
141
|
+
authorize: 'https://www.facebook.com/v18.0/dialog/oauth',
|
|
142
|
+
token: 'https://graph.facebook.com/v18.0/oauth/access_token',
|
|
143
|
+
// `email` is required to read the address; `public_profile` is
|
|
144
|
+
// implicit but listing it makes the consent screen explicit.
|
|
145
|
+
defaultScopes: ['email', 'public_profile'],
|
|
146
|
+
},
|
|
147
|
+
apple: {
|
|
148
|
+
authorize: 'https://appleid.apple.com/auth/authorize',
|
|
149
|
+
token: 'https://appleid.apple.com/auth/token',
|
|
150
|
+
// Apple wants `name email`; `response_mode=form_post` is required
|
|
151
|
+
// when scopes include `name`/`email` — callers should set it.
|
|
152
|
+
defaultScopes: ['name', 'email'],
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Build the IdP authorize URL the SPA navigates to after `start`.
|
|
157
|
+
*
|
|
158
|
+
* Loads the OAuth `client_id` from Secrets Manager (cached). The
|
|
159
|
+
* `client_secret` is NOT used here — it's only needed at token
|
|
160
|
+
* exchange.
|
|
161
|
+
*/
|
|
162
|
+
export async function buildAuthorizeUrl(input, env = process.env) {
|
|
163
|
+
const { provider, redirectUri, state, scopes, extraParams } = input;
|
|
164
|
+
const { clientId } = await loadFederatedProviderCredentials(provider, env);
|
|
165
|
+
const endpoints = PROVIDER_ENDPOINTS[provider];
|
|
166
|
+
const url = new URL(endpoints.authorize);
|
|
167
|
+
url.searchParams.set('response_type', 'code');
|
|
168
|
+
url.searchParams.set('client_id', clientId);
|
|
169
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
170
|
+
url.searchParams.set('scope', (scopes ?? endpoints.defaultScopes).join(' '));
|
|
171
|
+
url.searchParams.set('state', state);
|
|
172
|
+
for (const [k, v] of Object.entries(extraParams ?? {})) {
|
|
173
|
+
url.searchParams.set(k, v);
|
|
174
|
+
}
|
|
175
|
+
return url.toString();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Exchange an OAuth `code` for tokens at the IdP, then resolve the
|
|
179
|
+
* verified `FederatedProfile`. Throws {@link AuthError}
|
|
180
|
+
* (`federated_token_invalid`, HTTP 401) on any IdP error.
|
|
181
|
+
*/
|
|
182
|
+
export async function exchangeAuthorizationCode(input, env = process.env) {
|
|
183
|
+
const { provider, code, redirectUri } = input;
|
|
184
|
+
const { clientId, clientSecret } = await loadFederatedProviderCredentials(provider, env);
|
|
185
|
+
switch (provider) {
|
|
186
|
+
case 'google':
|
|
187
|
+
return exchangeGoogle(code, redirectUri, clientId, clientSecret);
|
|
188
|
+
case 'facebook':
|
|
189
|
+
return exchangeFacebook(code, redirectUri, clientId, clientSecret);
|
|
190
|
+
case 'apple':
|
|
191
|
+
throw new AuthError('federated_provider_not_configured', 'Sign in with Apple requires a JWT-signed client_secret rotated ' +
|
|
192
|
+
'every 6 months — implement the Apple-specific exchange in your ' +
|
|
193
|
+
'application until VentureKit ships a built-in helper.', 500);
|
|
194
|
+
default: {
|
|
195
|
+
const _exhaustive = provider;
|
|
196
|
+
void _exhaustive;
|
|
197
|
+
throw new AuthError('federated_provider_not_configured', `Unknown federated provider: ${String(provider)}`, 500);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function exchangeGoogle(code, redirectUri, clientId, clientSecret) {
|
|
202
|
+
// Google's token endpoint accepts application/x-www-form-urlencoded
|
|
203
|
+
// and returns `{ access_token, id_token, expires_in, ... }`. We only
|
|
204
|
+
// need the id_token: it's a signed JWT carrying `sub`, `email`,
|
|
205
|
+
// `email_verified`, `name`. Signature verification is unnecessary
|
|
206
|
+
// because we just received it from Google's TLS endpoint over a
|
|
207
|
+
// back-channel call we initiated — there's no way for an attacker
|
|
208
|
+
// to substitute it. (Per Google's docs:
|
|
209
|
+
// https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken)
|
|
210
|
+
const body = new URLSearchParams({
|
|
211
|
+
code,
|
|
212
|
+
client_id: clientId,
|
|
213
|
+
client_secret: clientSecret,
|
|
214
|
+
redirect_uri: redirectUri,
|
|
215
|
+
grant_type: 'authorization_code',
|
|
216
|
+
});
|
|
217
|
+
const tokenRes = await fetch(PROVIDER_ENDPOINTS.google.token, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
220
|
+
body,
|
|
221
|
+
});
|
|
222
|
+
if (!tokenRes.ok) {
|
|
223
|
+
throw new AuthError('federated_token_invalid', `Google token exchange failed (HTTP ${tokenRes.status}): ` +
|
|
224
|
+
(await tokenRes.text().catch(() => '')), 401);
|
|
225
|
+
}
|
|
226
|
+
const tokenJson = (await tokenRes.json());
|
|
227
|
+
if (!tokenJson.id_token) {
|
|
228
|
+
throw new AuthError('federated_token_invalid', 'Google token exchange returned no id_token', 401);
|
|
229
|
+
}
|
|
230
|
+
const claims = decodeJwtPayload(tokenJson.id_token);
|
|
231
|
+
const sub = typeof claims['sub'] === 'string' ? claims['sub'] : null;
|
|
232
|
+
const email = typeof claims['email'] === 'string'
|
|
233
|
+
? claims['email'].toLowerCase()
|
|
234
|
+
: null;
|
|
235
|
+
const emailVerified = claims['email_verified'] === true;
|
|
236
|
+
if (!sub || !email || !emailVerified) {
|
|
237
|
+
throw new AuthError('federated_token_invalid', 'Google id_token missing required verified email claim', 401);
|
|
238
|
+
}
|
|
239
|
+
const name = typeof claims['name'] === 'string' ? claims['name'] : undefined;
|
|
240
|
+
return { externalId: sub, email, name };
|
|
241
|
+
}
|
|
242
|
+
async function exchangeFacebook(code, redirectUri, clientId, clientSecret) {
|
|
243
|
+
// Facebook's token endpoint accepts the same params as a GET. The
|
|
244
|
+
// response is `{ access_token, token_type, expires_in }`. There's
|
|
245
|
+
// no id_token, so we follow up with a Graph `/me` call.
|
|
246
|
+
const tokenUrl = new URL(PROVIDER_ENDPOINTS.facebook.token);
|
|
247
|
+
tokenUrl.searchParams.set('code', code);
|
|
248
|
+
tokenUrl.searchParams.set('client_id', clientId);
|
|
249
|
+
tokenUrl.searchParams.set('client_secret', clientSecret);
|
|
250
|
+
tokenUrl.searchParams.set('redirect_uri', redirectUri);
|
|
251
|
+
const tokenRes = await fetch(tokenUrl);
|
|
252
|
+
if (!tokenRes.ok) {
|
|
253
|
+
throw new AuthError('federated_token_invalid', `Facebook token exchange failed (HTTP ${tokenRes.status}): ` +
|
|
254
|
+
(await tokenRes.text().catch(() => '')), 401);
|
|
255
|
+
}
|
|
256
|
+
const tokenJson = (await tokenRes.json());
|
|
257
|
+
if (!tokenJson.access_token) {
|
|
258
|
+
throw new AuthError('federated_token_invalid', 'Facebook token exchange returned no access_token', 401);
|
|
259
|
+
}
|
|
260
|
+
// appsecret_proof = HMAC_SHA256(access_token, app_secret) — Meta
|
|
261
|
+
// requires it for sensitive scopes and recommends it for everything.
|
|
262
|
+
const proof = createHmac('sha256', clientSecret)
|
|
263
|
+
.update(tokenJson.access_token)
|
|
264
|
+
.digest('hex');
|
|
265
|
+
const meUrl = new URL('https://graph.facebook.com/v18.0/me');
|
|
266
|
+
meUrl.searchParams.set('fields', 'id,email,name');
|
|
267
|
+
meUrl.searchParams.set('access_token', tokenJson.access_token);
|
|
268
|
+
meUrl.searchParams.set('appsecret_proof', proof);
|
|
269
|
+
const meRes = await fetch(meUrl);
|
|
270
|
+
if (!meRes.ok) {
|
|
271
|
+
throw new AuthError('federated_token_invalid', `Facebook /me failed (HTTP ${meRes.status})`, 401);
|
|
272
|
+
}
|
|
273
|
+
const me = (await meRes.json());
|
|
274
|
+
if (!me.id || !me.email) {
|
|
275
|
+
throw new AuthError('federated_token_invalid', 'Facebook profile missing id or email — request the `email` scope.', 401);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
externalId: me.id,
|
|
279
|
+
email: me.email.toLowerCase(),
|
|
280
|
+
name: me.name,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Decode a JWT payload **without** verifying the signature. Safe here
|
|
285
|
+
* because the caller just fetched the token over TLS from the IdP's
|
|
286
|
+
* own token endpoint; the decoder is internal-only.
|
|
287
|
+
*/
|
|
288
|
+
function decodeJwtPayload(jwt) {
|
|
289
|
+
const parts = jwt.split('.');
|
|
290
|
+
if (parts.length < 2) {
|
|
291
|
+
throw new AuthError('federated_token_invalid', 'Malformed JWT', 401);
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
295
|
+
return JSON.parse(payload);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
throw new AuthError('federated_token_invalid', 'Malformed JWT', 401);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Sign a verified federated user in. Creates the Cognito user on
|
|
303
|
+
* first contact (idempotent), rotates a server-side password, and
|
|
304
|
+
* mints session tokens via `ADMIN_USER_PASSWORD_AUTH`.
|
|
305
|
+
*
|
|
306
|
+
* Returns the same {@link SignInResult} shape `signInWithPassword`
|
|
307
|
+
* returns, so cookie / session helpers stay identical.
|
|
308
|
+
*/
|
|
309
|
+
export async function signInAsFederatedUser(input, config = loadAuthServerConfig()) {
|
|
310
|
+
const client = getCognitoClient(config.region, config.endpoint);
|
|
311
|
+
const email = input.profile.email.toLowerCase();
|
|
312
|
+
// Generate a strong server-side password every sign-in so even if
|
|
313
|
+
// the value were ever exfiltrated it'd already be replaced. The
|
|
314
|
+
// user never sees this password — federated sign-in never exposes
|
|
315
|
+
// it; password sign-in for the same account is still possible
|
|
316
|
+
// after a `forgot-password` flow.
|
|
317
|
+
const password = generateStrongPassword();
|
|
318
|
+
let userExists;
|
|
319
|
+
try {
|
|
320
|
+
await client.send(new AdminGetUserCommand({
|
|
321
|
+
UserPoolId: config.userPoolId,
|
|
322
|
+
Username: email,
|
|
323
|
+
}));
|
|
324
|
+
userExists = true;
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
if (err.name === 'UserNotFoundException') {
|
|
328
|
+
userExists = false;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
throw mapProviderError(err, 'federated_signin_failed');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!userExists) {
|
|
335
|
+
const attrs = [
|
|
336
|
+
{ Name: 'email', Value: email },
|
|
337
|
+
{ Name: 'email_verified', Value: 'true' },
|
|
338
|
+
];
|
|
339
|
+
if (input.profile.name) {
|
|
340
|
+
attrs.push({ Name: 'name', Value: input.profile.name });
|
|
341
|
+
}
|
|
342
|
+
for (const [k, v] of Object.entries(input.initialAttributes ?? {})) {
|
|
343
|
+
if (k === 'email' || k === 'email_verified' || k === 'name')
|
|
344
|
+
continue;
|
|
345
|
+
attrs.push({ Name: k, Value: v });
|
|
346
|
+
}
|
|
347
|
+
attrs.push({
|
|
348
|
+
Name: 'custom:federated_provider',
|
|
349
|
+
Value: input.provider,
|
|
350
|
+
});
|
|
351
|
+
for (const [k, v] of Object.entries(input.initialCustomAttributes ?? {})) {
|
|
352
|
+
attrs.push({ Name: `custom:${k}`, Value: v });
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
await client.send(new AdminCreateUserCommand({
|
|
356
|
+
UserPoolId: config.userPoolId,
|
|
357
|
+
Username: email,
|
|
358
|
+
UserAttributes: attrs,
|
|
359
|
+
// Suppress the welcome email — the user just authenticated
|
|
360
|
+
// through the IdP, they don't need a Cognito-generated one.
|
|
361
|
+
MessageAction: 'SUPPRESS',
|
|
362
|
+
TemporaryPassword: password,
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
// `UsernameExistsException` here is a race between two parallel
|
|
367
|
+
// federated sign-ins for the same email — fall through to the
|
|
368
|
+
// password reset + auth path; both will succeed.
|
|
369
|
+
if (err.name !== 'UsernameExistsException') {
|
|
370
|
+
throw mapProviderError(err, 'federated_signin_failed');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// Existing user — make sure email is marked verified (older
|
|
376
|
+
// password-only signups may have `email_verified=false` if the
|
|
377
|
+
// confirmation email was never clicked) so `AdminInitiateAuth`
|
|
378
|
+
// doesn't raise a verification challenge.
|
|
379
|
+
try {
|
|
380
|
+
await client.send(new AdminUpdateUserAttributesCommand({
|
|
381
|
+
UserPoolId: config.userPoolId,
|
|
382
|
+
Username: email,
|
|
383
|
+
UserAttributes: [
|
|
384
|
+
{ Name: 'email', Value: email },
|
|
385
|
+
{ Name: 'email_verified', Value: 'true' },
|
|
386
|
+
],
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
throw mapProviderError(err, 'federated_signin_failed');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Set a fresh permanent password whether the user is new (the
|
|
394
|
+
// temporary one above lands them in `FORCE_CHANGE_PASSWORD`) or
|
|
395
|
+
// returning. `Permanent: true` skips the new-password challenge.
|
|
396
|
+
try {
|
|
397
|
+
await client.send(new AdminSetUserPasswordCommand({
|
|
398
|
+
UserPoolId: config.userPoolId,
|
|
399
|
+
Username: email,
|
|
400
|
+
Password: password,
|
|
401
|
+
Permanent: true,
|
|
402
|
+
}));
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
throw mapProviderError(err, 'federated_signin_failed');
|
|
406
|
+
}
|
|
407
|
+
let res;
|
|
408
|
+
try {
|
|
409
|
+
res = await client.send(new AdminInitiateAuthCommand({
|
|
410
|
+
UserPoolId: config.userPoolId,
|
|
411
|
+
ClientId: config.appClientId,
|
|
412
|
+
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
|
|
413
|
+
AuthParameters: { USERNAME: email, PASSWORD: password },
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
throw mapProviderError(err, 'federated_signin_failed');
|
|
418
|
+
}
|
|
419
|
+
if (res.ChallengeName) {
|
|
420
|
+
throw new AuthError('federated_signin_failed', `Cognito returned an unexpected challenge after federated sign-in: ${res.ChallengeName}`, 500);
|
|
421
|
+
}
|
|
422
|
+
return extractSignInTokens(res.AuthenticationResult);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Generate a 32-char password that satisfies the strongest password
|
|
426
|
+
* policy VentureKit ships (`passwordStrength: 'strong'` — 12+ chars,
|
|
427
|
+
* upper, lower, digit, symbol).
|
|
428
|
+
*/
|
|
429
|
+
function generateStrongPassword() {
|
|
430
|
+
const base = randomBytes(24)
|
|
431
|
+
.toString('base64')
|
|
432
|
+
.replace(/[^A-Za-z0-9]/g, '')
|
|
433
|
+
.slice(0, 28);
|
|
434
|
+
return `Aa1!${base}`;
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=federated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federated.js","sourceRoot":"","sources":["../../src/server/federated.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -43,4 +43,8 @@ export type { SessionTokens, CookieOptions } from './cookies.js';
|
|
|
43
43
|
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, } from './cookies.js';
|
|
44
44
|
export type { CookieAuthMiddlewareOptions } from './middleware.js';
|
|
45
45
|
export { cookieAuthMiddleware, extractToken } from './middleware.js';
|
|
46
|
+
export type { FederatedProvider, FederatedProfile, FederatedProviderCredentials, SignInAsFederatedUserInput, BuildAuthorizeUrlInput, ExchangeAuthorizationCodeInput, } from './federated.js';
|
|
47
|
+
export { loadFederatedProviderCredentials, generateOAuthState, verifyOAuthState, buildAuthorizeUrl, exchangeAuthorizationCode, signInAsFederatedUser, } from './federated.js';
|
|
48
|
+
export type { VerificationChannel, VerificationCodeStore, VerificationCodeRecord, RequestVerificationCodeInput, RequestVerificationCodeResult, VerifyVerificationCodeInput, } from './verification.js';
|
|
49
|
+
export { generateVerificationCode, hashVerificationCode, requestVerificationCode, verifyVerificationCode, createInMemoryVerificationCodeStore, } from './verification.js';
|
|
46
50
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAErE,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,4BAA4B,EAC5B,0BAA0B,EAC1B,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,gCAAgC,EAChC,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,mCAAmC,GACpC,MAAM,mBAAmB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -33,4 +33,6 @@ export { changePassword } from './change-password.js';
|
|
|
33
33
|
export { verifyAndDecode } from './verify.js';
|
|
34
34
|
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, } from './cookies.js';
|
|
35
35
|
export { cookieAuthMiddleware, extractToken } from './middleware.js';
|
|
36
|
+
export { loadFederatedProviderCredentials, generateOAuthState, verifyOAuthState, buildAuthorizeUrl, exchangeAuthorizationCode, signInAsFederatedUser, } from './federated.js';
|
|
37
|
+
export { generateVerificationCode, hashVerificationCode, requestVerificationCode, verifyVerificationCode, createInMemoryVerificationCodeStore, } from './verification.js';
|
|
36
38
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAOlD,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAO1C,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAOlD,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAO1C,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAUrE,OAAO,EACL,gCAAgC,EAChC,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,mCAAmC,GACpC,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time verification codes (OTP) for sign-up and sign-in.
|
|
3
|
+
*
|
|
4
|
+
* Generic primitives for the "we'll text/email you a 6-digit code"
|
|
5
|
+
* pattern that gates registration when an app wants to confirm an
|
|
6
|
+
* email or phone number before it ever lives in Cognito. The code
|
|
7
|
+
* channel is opaque to this module — the caller plugs in `email` /
|
|
8
|
+
* `whatsapp` / `sms` and decides how to deliver the code (typically
|
|
9
|
+
* via `@venturekit/notify`).
|
|
10
|
+
*
|
|
11
|
+
* Storage is also pluggable via {@link VerificationCodeStore} so
|
|
12
|
+
* applications can pick Postgres, DynamoDB, or in-memory (tests). The
|
|
13
|
+
* stored value is a SHA-256 hash of the code, never the code itself —
|
|
14
|
+
* if the table leaks the codes are still useless.
|
|
15
|
+
*
|
|
16
|
+
* Usage sketch (route handler):
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Step 1 — user submits email, we send a code
|
|
20
|
+
* const { code } = await requestVerificationCode({
|
|
21
|
+
* channel: 'email',
|
|
22
|
+
* identifier: email,
|
|
23
|
+
* store,
|
|
24
|
+
* });
|
|
25
|
+
* await notify.sendEmail({ to: email, subject: 'Code', text: code });
|
|
26
|
+
*
|
|
27
|
+
* // Step 2 — user submits the code; we verify it
|
|
28
|
+
* await verifyVerificationCode({
|
|
29
|
+
* channel: 'email', identifier: email, code: req.body.code, store,
|
|
30
|
+
* });
|
|
31
|
+
* await signUpUser({ email, password });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
/** A delivery channel — `email`, `sms`, `whatsapp`, or anything else. */
|
|
35
|
+
export type VerificationChannel = string;
|
|
36
|
+
/**
|
|
37
|
+
* Pluggable storage for outstanding codes. Implementations must be
|
|
38
|
+
* idempotent on `put` (overwrite previous code for the same key) and
|
|
39
|
+
* return `undefined` for unknown / expired keys on `get`.
|
|
40
|
+
*
|
|
41
|
+
* Keys are tuples of `(channel, identifier)`; e.g.
|
|
42
|
+
* `('email', 'foo@example.com')` and `('whatsapp', '+212600000000')`
|
|
43
|
+
* occupy distinct slots so a user with both can verify each one.
|
|
44
|
+
*/
|
|
45
|
+
export interface VerificationCodeStore {
|
|
46
|
+
put(input: {
|
|
47
|
+
channel: VerificationChannel;
|
|
48
|
+
identifier: string;
|
|
49
|
+
/** SHA-256 hex digest of the plaintext code. */
|
|
50
|
+
codeHash: string;
|
|
51
|
+
/** Wall-clock expiry, ms epoch. */
|
|
52
|
+
expiresAt: number;
|
|
53
|
+
/** Number of failed `verify` attempts allowed before rotation. */
|
|
54
|
+
maxAttempts: number;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
get(input: {
|
|
57
|
+
channel: VerificationChannel;
|
|
58
|
+
identifier: string;
|
|
59
|
+
}): Promise<VerificationCodeRecord | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Increment the attempt counter for an outstanding code (after a
|
|
62
|
+
* mismatch). Returning the new counter lets the helper decide
|
|
63
|
+
* whether to delete the record on the spot once the limit is hit.
|
|
64
|
+
*/
|
|
65
|
+
incrementAttempts(input: {
|
|
66
|
+
channel: VerificationChannel;
|
|
67
|
+
identifier: string;
|
|
68
|
+
}): Promise<number>;
|
|
69
|
+
delete(input: {
|
|
70
|
+
channel: VerificationChannel;
|
|
71
|
+
identifier: string;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export interface VerificationCodeRecord {
|
|
75
|
+
channel: VerificationChannel;
|
|
76
|
+
identifier: string;
|
|
77
|
+
codeHash: string;
|
|
78
|
+
expiresAt: number;
|
|
79
|
+
attempts: number;
|
|
80
|
+
maxAttempts: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate a random numeric code of `length` digits (default 6).
|
|
84
|
+
* Uses `crypto.randomInt` so each digit is uniformly distributed —
|
|
85
|
+
* `Math.random()` would NOT meet the bar for an authentication code.
|
|
86
|
+
*/
|
|
87
|
+
export declare function generateVerificationCode(length?: number): string;
|
|
88
|
+
/**
|
|
89
|
+
* Hash a code with SHA-256. Stored hashes use this exact function so
|
|
90
|
+
* `verifyVerificationCode` can recompute the digest and
|
|
91
|
+
* constant-time-compare. We don't bother with bcrypt/scrypt: codes
|
|
92
|
+
* live for minutes, not years, and brute-forcing 6 digits in <5
|
|
93
|
+
* minutes is what `maxAttempts` is for.
|
|
94
|
+
*/
|
|
95
|
+
export declare function hashVerificationCode(code: string): string;
|
|
96
|
+
export interface RequestVerificationCodeInput {
|
|
97
|
+
channel: VerificationChannel;
|
|
98
|
+
/** Email, phone number, etc. — opaque to the helper. */
|
|
99
|
+
identifier: string;
|
|
100
|
+
store: VerificationCodeStore;
|
|
101
|
+
/** Code length in digits. Default 6. */
|
|
102
|
+
length?: number;
|
|
103
|
+
/** TTL in seconds. Default 600 (10 minutes). */
|
|
104
|
+
ttlSeconds?: number;
|
|
105
|
+
/** Max wrong-attempts before the code self-destructs. Default 5. */
|
|
106
|
+
maxAttempts?: number;
|
|
107
|
+
}
|
|
108
|
+
export interface RequestVerificationCodeResult {
|
|
109
|
+
/** The plaintext code — caller is responsible for delivery. */
|
|
110
|
+
code: string;
|
|
111
|
+
/** Wall-clock expiry, ms epoch. */
|
|
112
|
+
expiresAt: number;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Mint a fresh code and store its hash. Overwrites any previous code
|
|
116
|
+
* for the same `(channel, identifier)` so a user who didn't receive
|
|
117
|
+
* the first email can hit "resend" without touching state manually.
|
|
118
|
+
*
|
|
119
|
+
* Returns the plaintext code so the caller can deliver it via
|
|
120
|
+
* email / WhatsApp / SMS. The plaintext never re-enters this module.
|
|
121
|
+
*/
|
|
122
|
+
export declare function requestVerificationCode(input: RequestVerificationCodeInput): Promise<RequestVerificationCodeResult>;
|
|
123
|
+
export interface VerifyVerificationCodeInput {
|
|
124
|
+
channel: VerificationChannel;
|
|
125
|
+
identifier: string;
|
|
126
|
+
/** Plaintext code submitted by the user. */
|
|
127
|
+
code: string;
|
|
128
|
+
store: VerificationCodeStore;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Verify a submitted code. On success the record is deleted (codes
|
|
132
|
+
* are single-use). On mismatch the attempt counter is bumped; once
|
|
133
|
+
* `maxAttempts` is reached the record is wiped and the next call
|
|
134
|
+
* looks like "no code requested" — the user has to start over.
|
|
135
|
+
*
|
|
136
|
+
* Throws {@link AuthError} (`verification_failed`, HTTP 401) on any
|
|
137
|
+
* failure path so route handlers can surface a single error to the
|
|
138
|
+
* SPA without leaking which path was taken.
|
|
139
|
+
*/
|
|
140
|
+
export declare function verifyVerificationCode(input: VerifyVerificationCodeInput): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Process-local store. Useful in unit tests and local `vk dev` runs
|
|
143
|
+
* (single Lambda warm container); never use in production — codes
|
|
144
|
+
* vanish on cold start, and parallel containers don't see each
|
|
145
|
+
* other's records.
|
|
146
|
+
*/
|
|
147
|
+
export declare function createInMemoryVerificationCodeStore(): VerificationCodeStore;
|
|
148
|
+
//# sourceMappingURL=verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../src/server/verification.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,KAAK,EAAE;QACT,OAAO,EAAE,mBAAmB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;QACnB,gDAAgD;QAChD,QAAQ,EAAE,MAAM,CAAC;QACjB,mCAAmC;QACnC,SAAS,EAAE,MAAM,CAAC;QAClB,kEAAkE;QAClE,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,GAAG,CAAC,KAAK,EAAE;QACT,OAAO,EAAE,mBAAmB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAC;IAChD;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,EAAE;QACvB,OAAO,EAAE,mBAAmB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpB,MAAM,CAAC,KAAK,EAAE;QACZ,OAAO,EAAE,mBAAmB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,SAAI,GAAG,MAAM,CAW3D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAMD,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,mBAAmB,CAAC;IAC7B,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,qBAAqB,CAAC;IAC7B,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CAexC;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,qBAAqB,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC,IAAI,CAAC,CAsDf;AAMD;;;;;GAKG;AACH,wBAAgB,mCAAmC,IAAI,qBAAqB,CA2B3E"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time verification codes (OTP) for sign-up and sign-in.
|
|
3
|
+
*
|
|
4
|
+
* Generic primitives for the "we'll text/email you a 6-digit code"
|
|
5
|
+
* pattern that gates registration when an app wants to confirm an
|
|
6
|
+
* email or phone number before it ever lives in Cognito. The code
|
|
7
|
+
* channel is opaque to this module — the caller plugs in `email` /
|
|
8
|
+
* `whatsapp` / `sms` and decides how to deliver the code (typically
|
|
9
|
+
* via `@venturekit/notify`).
|
|
10
|
+
*
|
|
11
|
+
* Storage is also pluggable via {@link VerificationCodeStore} so
|
|
12
|
+
* applications can pick Postgres, DynamoDB, or in-memory (tests). The
|
|
13
|
+
* stored value is a SHA-256 hash of the code, never the code itself —
|
|
14
|
+
* if the table leaks the codes are still useless.
|
|
15
|
+
*
|
|
16
|
+
* Usage sketch (route handler):
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Step 1 — user submits email, we send a code
|
|
20
|
+
* const { code } = await requestVerificationCode({
|
|
21
|
+
* channel: 'email',
|
|
22
|
+
* identifier: email,
|
|
23
|
+
* store,
|
|
24
|
+
* });
|
|
25
|
+
* await notify.sendEmail({ to: email, subject: 'Code', text: code });
|
|
26
|
+
*
|
|
27
|
+
* // Step 2 — user submits the code; we verify it
|
|
28
|
+
* await verifyVerificationCode({
|
|
29
|
+
* channel: 'email', identifier: email, code: req.body.code, store,
|
|
30
|
+
* });
|
|
31
|
+
* await signUpUser({ email, password });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import { createHash, randomInt, timingSafeEqual } from 'node:crypto';
|
|
35
|
+
import { AuthError } from './errors.js';
|
|
36
|
+
// ────────────────────────────────────────────────────────────────────
|
|
37
|
+
// Primitives
|
|
38
|
+
// ────────────────────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Generate a random numeric code of `length` digits (default 6).
|
|
41
|
+
* Uses `crypto.randomInt` so each digit is uniformly distributed —
|
|
42
|
+
* `Math.random()` would NOT meet the bar for an authentication code.
|
|
43
|
+
*/
|
|
44
|
+
export function generateVerificationCode(length = 6) {
|
|
45
|
+
if (length < 4 || length > 10) {
|
|
46
|
+
throw new Error(`[verification] code length ${length} outside the supported 4–10 range`);
|
|
47
|
+
}
|
|
48
|
+
let out = '';
|
|
49
|
+
for (let i = 0; i < length; i++) {
|
|
50
|
+
out += String(randomInt(0, 10));
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Hash a code with SHA-256. Stored hashes use this exact function so
|
|
56
|
+
* `verifyVerificationCode` can recompute the digest and
|
|
57
|
+
* constant-time-compare. We don't bother with bcrypt/scrypt: codes
|
|
58
|
+
* live for minutes, not years, and brute-forcing 6 digits in <5
|
|
59
|
+
* minutes is what `maxAttempts` is for.
|
|
60
|
+
*/
|
|
61
|
+
export function hashVerificationCode(code) {
|
|
62
|
+
return createHash('sha256').update(code).digest('hex');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Mint a fresh code and store its hash. Overwrites any previous code
|
|
66
|
+
* for the same `(channel, identifier)` so a user who didn't receive
|
|
67
|
+
* the first email can hit "resend" without touching state manually.
|
|
68
|
+
*
|
|
69
|
+
* Returns the plaintext code so the caller can deliver it via
|
|
70
|
+
* email / WhatsApp / SMS. The plaintext never re-enters this module.
|
|
71
|
+
*/
|
|
72
|
+
export async function requestVerificationCode(input) {
|
|
73
|
+
const length = input.length ?? 6;
|
|
74
|
+
const ttlSeconds = input.ttlSeconds ?? 600;
|
|
75
|
+
const maxAttempts = input.maxAttempts ?? 5;
|
|
76
|
+
const code = generateVerificationCode(length);
|
|
77
|
+
const codeHash = hashVerificationCode(code);
|
|
78
|
+
const expiresAt = Date.now() + ttlSeconds * 1000;
|
|
79
|
+
await input.store.put({
|
|
80
|
+
channel: input.channel,
|
|
81
|
+
identifier: input.identifier,
|
|
82
|
+
codeHash,
|
|
83
|
+
expiresAt,
|
|
84
|
+
maxAttempts,
|
|
85
|
+
});
|
|
86
|
+
return { code, expiresAt };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Verify a submitted code. On success the record is deleted (codes
|
|
90
|
+
* are single-use). On mismatch the attempt counter is bumped; once
|
|
91
|
+
* `maxAttempts` is reached the record is wiped and the next call
|
|
92
|
+
* looks like "no code requested" — the user has to start over.
|
|
93
|
+
*
|
|
94
|
+
* Throws {@link AuthError} (`verification_failed`, HTTP 401) on any
|
|
95
|
+
* failure path so route handlers can surface a single error to the
|
|
96
|
+
* SPA without leaking which path was taken.
|
|
97
|
+
*/
|
|
98
|
+
export async function verifyVerificationCode(input) {
|
|
99
|
+
const record = await input.store.get({
|
|
100
|
+
channel: input.channel,
|
|
101
|
+
identifier: input.identifier,
|
|
102
|
+
});
|
|
103
|
+
if (!record) {
|
|
104
|
+
throw new AuthError('verification_failed', 'No verification code outstanding — request a new one', 401);
|
|
105
|
+
}
|
|
106
|
+
if (record.expiresAt < Date.now()) {
|
|
107
|
+
await input.store.delete({
|
|
108
|
+
channel: input.channel,
|
|
109
|
+
identifier: input.identifier,
|
|
110
|
+
});
|
|
111
|
+
throw new AuthError('verification_failed', 'Verification code expired — request a new one', 401);
|
|
112
|
+
}
|
|
113
|
+
const submittedHash = hashVerificationCode(input.code);
|
|
114
|
+
const a = Buffer.from(submittedHash, 'hex');
|
|
115
|
+
const b = Buffer.from(record.codeHash, 'hex');
|
|
116
|
+
const matches = a.length === b.length && timingSafeEqual(a, b);
|
|
117
|
+
if (matches) {
|
|
118
|
+
await input.store.delete({
|
|
119
|
+
channel: input.channel,
|
|
120
|
+
identifier: input.identifier,
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Wrong code — bump the counter; if we've hit the limit, wipe the
|
|
125
|
+
// record so the user has to request a new one (this is the
|
|
126
|
+
// brute-force defense, since 6-digit codes only have 1M states).
|
|
127
|
+
const attempts = await input.store.incrementAttempts({
|
|
128
|
+
channel: input.channel,
|
|
129
|
+
identifier: input.identifier,
|
|
130
|
+
});
|
|
131
|
+
if (attempts >= record.maxAttempts) {
|
|
132
|
+
await input.store.delete({
|
|
133
|
+
channel: input.channel,
|
|
134
|
+
identifier: input.identifier,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
throw new AuthError('verification_failed', 'Invalid verification code', 401);
|
|
138
|
+
}
|
|
139
|
+
// ────────────────────────────────────────────────────────────────────
|
|
140
|
+
// In-memory store — for tests / local dev only
|
|
141
|
+
// ────────────────────────────────────────────────────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* Process-local store. Useful in unit tests and local `vk dev` runs
|
|
144
|
+
* (single Lambda warm container); never use in production — codes
|
|
145
|
+
* vanish on cold start, and parallel containers don't see each
|
|
146
|
+
* other's records.
|
|
147
|
+
*/
|
|
148
|
+
export function createInMemoryVerificationCodeStore() {
|
|
149
|
+
const map = new Map();
|
|
150
|
+
const key = (c, i) => `${c}\u0000${i}`;
|
|
151
|
+
return {
|
|
152
|
+
async put({ channel, identifier, codeHash, expiresAt, maxAttempts }) {
|
|
153
|
+
map.set(key(channel, identifier), {
|
|
154
|
+
channel,
|
|
155
|
+
identifier,
|
|
156
|
+
codeHash,
|
|
157
|
+
expiresAt,
|
|
158
|
+
attempts: 0,
|
|
159
|
+
maxAttempts,
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
async get({ channel, identifier }) {
|
|
163
|
+
return map.get(key(channel, identifier));
|
|
164
|
+
},
|
|
165
|
+
async incrementAttempts({ channel, identifier }) {
|
|
166
|
+
const rec = map.get(key(channel, identifier));
|
|
167
|
+
if (!rec)
|
|
168
|
+
return 0;
|
|
169
|
+
rec.attempts += 1;
|
|
170
|
+
return rec.attempts;
|
|
171
|
+
},
|
|
172
|
+
async delete({ channel, identifier }) {
|
|
173
|
+
map.delete(key(channel, identifier));
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../src/server/verification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
-- vk_auth_001_verification_codes.sql
|
|
2
|
+
--
|
|
3
|
+
-- Owned by `@venturekit/auth`. Backs `VerificationCodeStore` (used by
|
|
4
|
+
-- the OTP gating on self-service sign-up + sign-in / passwordless).
|
|
5
|
+
--
|
|
6
|
+
-- Rows are pre-Cognito state — created BEFORE a user exists in either
|
|
7
|
+
-- Cognito or any consumer-side `users` mirror. The `identifier` is the
|
|
8
|
+
-- email or E.164 phone number the code was sent to; `channel` is the
|
|
9
|
+
-- delivery surface.
|
|
10
|
+
--
|
|
11
|
+
-- Storage shape:
|
|
12
|
+
-- - `code_hash` is SHA-256 of the plaintext code (hex). Plaintext
|
|
13
|
+
-- never lands in the DB so a future leak still requires brute
|
|
14
|
+
-- force against the (short) TTL.
|
|
15
|
+
-- - `expires_at` is the wall-clock cutoff after which the
|
|
16
|
+
-- `verifyVerificationCode` helper deletes the row and surfaces
|
|
17
|
+
-- `verification_failed`.
|
|
18
|
+
-- - `attempts` is incremented on every wrong-code submission;
|
|
19
|
+
-- once it reaches `max_attempts` the row is deleted (forces the
|
|
20
|
+
-- user to request a fresh code).
|
|
21
|
+
--
|
|
22
|
+
-- Channel typing: stored as TEXT + CHECK rather than a Postgres enum
|
|
23
|
+
-- so this migration does not depend on `@venturekit/notify`'s
|
|
24
|
+
-- `notify_channel` enum (auth must be usable without notify — e.g. a
|
|
25
|
+
-- project that ships its own delivery layer). Add new values here when
|
|
26
|
+
-- the auth package learns to dispatch over them.
|
|
27
|
+
--
|
|
28
|
+
-- Idempotent: uses `CREATE TABLE IF NOT EXISTS` so projects that
|
|
29
|
+
-- previously created the table from their own migration (with a
|
|
30
|
+
-- compatible shape) can adopt this one without dropping data. The
|
|
31
|
+
-- expectation is that those projects will then leave their old
|
|
32
|
+
-- migration file in place but stop hand-maintaining the schema here.
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS verification_codes (
|
|
35
|
+
channel TEXT NOT NULL,
|
|
36
|
+
identifier TEXT NOT NULL,
|
|
37
|
+
code_hash TEXT NOT NULL,
|
|
38
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
39
|
+
attempts INT NOT NULL DEFAULT 0,
|
|
40
|
+
max_attempts INT NOT NULL DEFAULT 5,
|
|
41
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
42
|
+
PRIMARY KEY (channel, identifier),
|
|
43
|
+
CONSTRAINT verification_codes_channel_chk
|
|
44
|
+
CHECK (channel IN ('email', 'whatsapp', 'sms')),
|
|
45
|
+
CONSTRAINT verification_codes_attempts_chk
|
|
46
|
+
CHECK (attempts >= 0),
|
|
47
|
+
CONSTRAINT verification_codes_max_attempts_chk
|
|
48
|
+
CHECK (max_attempts >= 1)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_verification_codes_expires_at
|
|
52
|
+
ON verification_codes (expires_at);
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venturekit/auth",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.20260511023410",
|
|
4
4
|
"description": "Authentication and authorization for VentureKit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"migrations"
|
|
10
11
|
],
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
@@ -18,6 +19,9 @@
|
|
|
18
19
|
"access": "public"
|
|
19
20
|
},
|
|
20
21
|
"license": "Apache-2.0",
|
|
22
|
+
"vk": {
|
|
23
|
+
"migrations": "migrations"
|
|
24
|
+
},
|
|
21
25
|
"exports": {
|
|
22
26
|
".": {
|
|
23
27
|
"import": "./dist/index.js",
|
|
@@ -29,12 +33,13 @@
|
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"dependencies": {
|
|
32
|
-
"@venturekit/core": "0.0.0-dev.
|
|
36
|
+
"@venturekit/core": "0.0.0-dev.20260511023410",
|
|
33
37
|
"@aws-sdk/client-cognito-identity-provider": "^3.668.0",
|
|
38
|
+
"@aws-sdk/client-secrets-manager": "^3.668.0",
|
|
34
39
|
"aws-jwt-verify": "^4.0.1"
|
|
35
40
|
},
|
|
36
41
|
"peerDependencies": {
|
|
37
|
-
"@venturekit/runtime": "0.0.0-dev.
|
|
42
|
+
"@venturekit/runtime": "0.0.0-dev.20260511023410"
|
|
38
43
|
},
|
|
39
44
|
"peerDependenciesMeta": {
|
|
40
45
|
"@venturekit/runtime": {
|
|
@@ -42,7 +47,7 @@
|
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
49
|
"devDependencies": {
|
|
45
|
-
"@venturekit/runtime": "0.0.0-dev.
|
|
50
|
+
"@venturekit/runtime": "0.0.0-dev.20260511023410",
|
|
46
51
|
"@types/aws-lambda": "^8.10.131",
|
|
47
52
|
"@types/node": "^25.6.0",
|
|
48
53
|
"typescript": "^5.3.0"
|