@venturekit/auth 0.0.1 → 0.0.2
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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/vk_auth_003_role_scopes.sql +43 -0
- package/dist/roles/index.d.ts +5 -1
- package/dist/roles/index.d.ts.map +1 -1
- package/dist/roles/index.js +4 -1
- package/dist/roles/index.js.map +1 -1
- package/dist/roles/role-scopes.d.ts +92 -0
- package/dist/roles/role-scopes.d.ts.map +1 -0
- package/dist/roles/role-scopes.js +122 -0
- package/dist/roles/role-scopes.js.map +1 -0
- package/dist/server/cookies.d.ts +77 -6
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +55 -13
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/federated-routes.d.ts +29 -22
- package/dist/server/federated-routes.d.ts.map +1 -1
- package/dist/server/federated-routes.js +31 -4
- package/dist/server/federated-routes.js.map +1 -1
- package/dist/server/federated.d.ts.map +1 -1
- package/dist/server/federated.js +7 -11
- package/dist/server/federated.js.map +1 -1
- package/dist/server/handoff-routes.d.ts +130 -0
- package/dist/server/handoff-routes.d.ts.map +1 -0
- package/dist/server/handoff-routes.js +178 -0
- package/dist/server/handoff-routes.js.map +1 -0
- package/dist/server/handoff.d.ts +112 -0
- package/dist/server/handoff.d.ts.map +1 -0
- package/dist/server/handoff.js +102 -0
- package/dist/server/handoff.js.map +1 -0
- package/dist/server/index.d.ts +10 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/passwordless.d.ts +68 -0
- package/dist/server/passwordless.d.ts.map +1 -0
- package/dist/server/passwordless.js +136 -0
- package/dist/server/passwordless.js.map +1 -0
- package/dist/server/revoke.d.ts +10 -0
- package/dist/server/revoke.d.ts.map +1 -1
- package/dist/server/revoke.js +19 -1
- package/dist/server/revoke.js.map +1 -1
- package/dist/server/store/postgres.d.ts +35 -0
- package/dist/server/store/postgres.d.ts.map +1 -0
- package/dist/server/store/postgres.js +88 -0
- package/dist/server/store/postgres.js.map +1 -0
- package/dist/server/token-utils.d.ts +12 -2
- package/dist/server/token-utils.d.ts.map +1 -1
- package/dist/server/token-utils.js +9 -4
- package/dist/server/token-utils.js.map +1 -1
- package/package.json +13 -4
- package/src/migrations/vk_auth_003_role_scopes.sql +43 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-domain session handoff — core primitives.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists.** Browser cookies cannot span registrable domains:
|
|
5
|
+
* a session on `app.platform.com` can never be read by `tenant.io`, no
|
|
6
|
+
* matter what `Domain=` attribute it carries. Multi-tenant products
|
|
7
|
+
* with white-label custom domains therefore need an SSO-style handoff
|
|
8
|
+
* to "move" a session between hosts: the host WITH a session mints a
|
|
9
|
+
* short-lived, single-use **handoff code** bound to the target host;
|
|
10
|
+
* the target host redeems it server-side and mints its own first-party
|
|
11
|
+
* cookies. The user's refresh token travels server-to-server through
|
|
12
|
+
* the app's {@link HandoffStore} — only the opaque code ever appears
|
|
13
|
+
* in a URL.
|
|
14
|
+
*
|
|
15
|
+
* **Security properties**
|
|
16
|
+
* - Codes are 256-bit `crypto.randomBytes` values — unguessable.
|
|
17
|
+
* - The store is keyed by the **SHA-256 hash** of the code, so a
|
|
18
|
+
* leaked store (DB dump, logs) cannot be replayed into sessions.
|
|
19
|
+
* - Codes are single-use: {@link HandoffStore.take} must delete
|
|
20
|
+
* atomically (e.g. SQL `DELETE … RETURNING`).
|
|
21
|
+
* - Codes expire after {@link DEFAULT_HANDOFF_TTL_SECONDS}; expiry is
|
|
22
|
+
* enforced BOTH by the store's TTL mechanism and re-checked here on
|
|
23
|
+
* redeem, so a lagging store cleanup can't extend the window.
|
|
24
|
+
* - Codes are bound to the target host at issue time; redeeming on
|
|
25
|
+
* any other host fails — and a wrong-host attempt BURNS the code,
|
|
26
|
+
* so it cannot be probed and then replayed on the right host.
|
|
27
|
+
* Combined with the route factory's `authorize` hook this
|
|
28
|
+
* guarantees a session can only ever materialize on hosts the app
|
|
29
|
+
* explicitly approved for THIS user.
|
|
30
|
+
* - The `complete` redirects carry `Cache-Control: no-store` and
|
|
31
|
+
* `Referrer-Policy: no-referrer` so the code-bearing URL never
|
|
32
|
+
* sits in a shared cache and never leaks via `Referer`.
|
|
33
|
+
*
|
|
34
|
+
* **Residual risks — know what this does NOT protect against**
|
|
35
|
+
* - *Login CSRF*: like every URL-based SSO handoff (OAuth code flow
|
|
36
|
+
* included), an attacker can mint a code for THEIR OWN session and
|
|
37
|
+
* lure a victim into opening the complete URL, logging the victim
|
|
38
|
+
* in as the attacker on that host. The 60s single-use window and
|
|
39
|
+
* the membership-gated `authorize` hook narrow this to hosts the
|
|
40
|
+
* ATTACKER belongs to; apps for which this matters should show the
|
|
41
|
+
* signed-in identity prominently post-handoff.
|
|
42
|
+
* - *Store contents*: payloads hold live refresh tokens. The hashed
|
|
43
|
+
* key prevents lookup-by-code, rows die in ≤60s, but a hostile DBA
|
|
44
|
+
* window exists — encrypt payloads at rest if that is in scope.
|
|
45
|
+
*
|
|
46
|
+
* The route-level flow lives in `handoff-routes.ts`
|
|
47
|
+
* ({@link createSessionHandoffRoutes}); apps supply the storage
|
|
48
|
+
* (Postgres / DynamoDB / Redis — anything with atomic take).
|
|
49
|
+
*/
|
|
50
|
+
/** Default lifetime of a handoff code: one browser redirect, not more. */
|
|
51
|
+
export declare const DEFAULT_HANDOFF_TTL_SECONDS = 60;
|
|
52
|
+
/**
|
|
53
|
+
* What the issuing host stashes for the target host to redeem.
|
|
54
|
+
*
|
|
55
|
+
* Contains the user's **refresh token** — treat stored payloads as
|
|
56
|
+
* secrets (the code-hash key already prevents lookup-by-code, and rows
|
|
57
|
+
* are deleted on redeem / expiry, but encrypt at rest if your store's
|
|
58
|
+
* threat model calls for it).
|
|
59
|
+
*/
|
|
60
|
+
export interface HandoffPayload {
|
|
61
|
+
/** Cognito subject of the user the session belongs to. */
|
|
62
|
+
sub: string;
|
|
63
|
+
/** The session's refresh token — used by the target host to mint fresh tokens. */
|
|
64
|
+
refreshToken: string;
|
|
65
|
+
/** Host (including port, lowercased) the code was issued FOR. */
|
|
66
|
+
targetHost: string;
|
|
67
|
+
/** Epoch millis after which the code is dead even if the store still has it. */
|
|
68
|
+
expiresAt: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Storage the app provides for in-flight handoff codes.
|
|
72
|
+
*
|
|
73
|
+
* Keys are code **hashes** (see module docs). Implementations must make
|
|
74
|
+
* `take` atomic — two concurrent redeems of the same code must never
|
|
75
|
+
* both succeed:
|
|
76
|
+
*
|
|
77
|
+
* ```sql
|
|
78
|
+
* -- Postgres example
|
|
79
|
+
* DELETE FROM auth_handoff_codes WHERE code_hash = $1 RETURNING payload;
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export interface HandoffStore {
|
|
83
|
+
/** Persist a payload under `codeHash`, expiring after `ttlSeconds`. */
|
|
84
|
+
put(codeHash: string, payload: HandoffPayload, ttlSeconds: number): Promise<void>;
|
|
85
|
+
/** Atomically fetch AND delete. Returns null when absent (or already taken). */
|
|
86
|
+
take(codeHash: string): Promise<HandoffPayload | null>;
|
|
87
|
+
}
|
|
88
|
+
/** Generate an unguessable handoff code (256-bit, base64url). */
|
|
89
|
+
export declare function generateHandoffCode(): string;
|
|
90
|
+
/** Hash a handoff code for use as a store key. */
|
|
91
|
+
export declare function hashHandoffCode(code: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Issue a handoff code for `targetHost`.
|
|
94
|
+
*
|
|
95
|
+
* @returns the RAW code — put it in the redirect URL. Only its hash
|
|
96
|
+
* touches the store.
|
|
97
|
+
*/
|
|
98
|
+
export declare function issueHandoffCode(store: HandoffStore, input: {
|
|
99
|
+
sub: string;
|
|
100
|
+
refreshToken: string;
|
|
101
|
+
targetHost: string;
|
|
102
|
+
}, ttlSeconds?: number): Promise<string>;
|
|
103
|
+
/**
|
|
104
|
+
* Redeem a handoff code on `currentHost`.
|
|
105
|
+
*
|
|
106
|
+
* Returns the payload when the code exists, hasn't expired, and was
|
|
107
|
+
* issued for exactly this host — null otherwise. The code is consumed
|
|
108
|
+
* either way (single-use: a failed redeem must burn it too, so an
|
|
109
|
+
* attacker can't probe).
|
|
110
|
+
*/
|
|
111
|
+
export declare function redeemHandoffCode(store: HandoffStore, code: string, currentHost: string): Promise<HandoffPayload | null>;
|
|
112
|
+
//# sourceMappingURL=handoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../../src/server/handoff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAIH,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,gFAAgF;IAChF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACxD;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAOD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAChE,UAAU,GAAE,MAAoC,GAC/C,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-domain session handoff — core primitives.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists.** Browser cookies cannot span registrable domains:
|
|
5
|
+
* a session on `app.platform.com` can never be read by `tenant.io`, no
|
|
6
|
+
* matter what `Domain=` attribute it carries. Multi-tenant products
|
|
7
|
+
* with white-label custom domains therefore need an SSO-style handoff
|
|
8
|
+
* to "move" a session between hosts: the host WITH a session mints a
|
|
9
|
+
* short-lived, single-use **handoff code** bound to the target host;
|
|
10
|
+
* the target host redeems it server-side and mints its own first-party
|
|
11
|
+
* cookies. The user's refresh token travels server-to-server through
|
|
12
|
+
* the app's {@link HandoffStore} — only the opaque code ever appears
|
|
13
|
+
* in a URL.
|
|
14
|
+
*
|
|
15
|
+
* **Security properties**
|
|
16
|
+
* - Codes are 256-bit `crypto.randomBytes` values — unguessable.
|
|
17
|
+
* - The store is keyed by the **SHA-256 hash** of the code, so a
|
|
18
|
+
* leaked store (DB dump, logs) cannot be replayed into sessions.
|
|
19
|
+
* - Codes are single-use: {@link HandoffStore.take} must delete
|
|
20
|
+
* atomically (e.g. SQL `DELETE … RETURNING`).
|
|
21
|
+
* - Codes expire after {@link DEFAULT_HANDOFF_TTL_SECONDS}; expiry is
|
|
22
|
+
* enforced BOTH by the store's TTL mechanism and re-checked here on
|
|
23
|
+
* redeem, so a lagging store cleanup can't extend the window.
|
|
24
|
+
* - Codes are bound to the target host at issue time; redeeming on
|
|
25
|
+
* any other host fails — and a wrong-host attempt BURNS the code,
|
|
26
|
+
* so it cannot be probed and then replayed on the right host.
|
|
27
|
+
* Combined with the route factory's `authorize` hook this
|
|
28
|
+
* guarantees a session can only ever materialize on hosts the app
|
|
29
|
+
* explicitly approved for THIS user.
|
|
30
|
+
* - The `complete` redirects carry `Cache-Control: no-store` and
|
|
31
|
+
* `Referrer-Policy: no-referrer` so the code-bearing URL never
|
|
32
|
+
* sits in a shared cache and never leaks via `Referer`.
|
|
33
|
+
*
|
|
34
|
+
* **Residual risks — know what this does NOT protect against**
|
|
35
|
+
* - *Login CSRF*: like every URL-based SSO handoff (OAuth code flow
|
|
36
|
+
* included), an attacker can mint a code for THEIR OWN session and
|
|
37
|
+
* lure a victim into opening the complete URL, logging the victim
|
|
38
|
+
* in as the attacker on that host. The 60s single-use window and
|
|
39
|
+
* the membership-gated `authorize` hook narrow this to hosts the
|
|
40
|
+
* ATTACKER belongs to; apps for which this matters should show the
|
|
41
|
+
* signed-in identity prominently post-handoff.
|
|
42
|
+
* - *Store contents*: payloads hold live refresh tokens. The hashed
|
|
43
|
+
* key prevents lookup-by-code, rows die in ≤60s, but a hostile DBA
|
|
44
|
+
* window exists — encrypt payloads at rest if that is in scope.
|
|
45
|
+
*
|
|
46
|
+
* The route-level flow lives in `handoff-routes.ts`
|
|
47
|
+
* ({@link createSessionHandoffRoutes}); apps supply the storage
|
|
48
|
+
* (Postgres / DynamoDB / Redis — anything with atomic take).
|
|
49
|
+
*/
|
|
50
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
51
|
+
/** Default lifetime of a handoff code: one browser redirect, not more. */
|
|
52
|
+
export const DEFAULT_HANDOFF_TTL_SECONDS = 60;
|
|
53
|
+
/** Generate an unguessable handoff code (256-bit, base64url). */
|
|
54
|
+
export function generateHandoffCode() {
|
|
55
|
+
return randomBytes(32).toString('base64url');
|
|
56
|
+
}
|
|
57
|
+
/** Hash a handoff code for use as a store key. */
|
|
58
|
+
export function hashHandoffCode(code) {
|
|
59
|
+
return createHash('sha256').update(code).digest('base64url');
|
|
60
|
+
}
|
|
61
|
+
/** Lowercase a host for comparison. Ports are significant (dev hosts). */
|
|
62
|
+
function normalizeHost(host) {
|
|
63
|
+
return host.trim().toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Issue a handoff code for `targetHost`.
|
|
67
|
+
*
|
|
68
|
+
* @returns the RAW code — put it in the redirect URL. Only its hash
|
|
69
|
+
* touches the store.
|
|
70
|
+
*/
|
|
71
|
+
export async function issueHandoffCode(store, input, ttlSeconds = DEFAULT_HANDOFF_TTL_SECONDS) {
|
|
72
|
+
const code = generateHandoffCode();
|
|
73
|
+
const payload = {
|
|
74
|
+
sub: input.sub,
|
|
75
|
+
refreshToken: input.refreshToken,
|
|
76
|
+
targetHost: normalizeHost(input.targetHost),
|
|
77
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
78
|
+
};
|
|
79
|
+
await store.put(hashHandoffCode(code), payload, ttlSeconds);
|
|
80
|
+
return code;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Redeem a handoff code on `currentHost`.
|
|
84
|
+
*
|
|
85
|
+
* Returns the payload when the code exists, hasn't expired, and was
|
|
86
|
+
* issued for exactly this host — null otherwise. The code is consumed
|
|
87
|
+
* either way (single-use: a failed redeem must burn it too, so an
|
|
88
|
+
* attacker can't probe).
|
|
89
|
+
*/
|
|
90
|
+
export async function redeemHandoffCode(store, code, currentHost) {
|
|
91
|
+
if (!code)
|
|
92
|
+
return null;
|
|
93
|
+
const payload = await store.take(hashHandoffCode(code));
|
|
94
|
+
if (!payload)
|
|
95
|
+
return null;
|
|
96
|
+
if (Date.now() > payload.expiresAt)
|
|
97
|
+
return null;
|
|
98
|
+
if (payload.targetHost !== normalizeHost(currentHost))
|
|
99
|
+
return null;
|
|
100
|
+
return payload;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../../src/server/handoff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAwC9C,iEAAiE;AACjE,MAAM,UAAU,mBAAmB;IACjC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC;AAED,0EAA0E;AAC1E,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAmB,EACnB,KAAgE,EAChE,aAAqB,2BAA2B;IAEhD,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAmB;QAC9B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC;QAC3C,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;KAC1C,CAAC;IACF,MAAM,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAmB,EACnB,IAAY,EACZ,WAAmB;IAEnB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,OAAO,CAAC,UAAU,KAAK,aAAa,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -34,19 +34,26 @@ export type { AdminInviteUserInput, AdminInviteUserResult, AdminUpdateAttributes
|
|
|
34
34
|
export { adminInviteUser, adminUpdateUserAttributes, adminDisableUser, adminEnableUser, adminDeleteUser, } from './admin-users.js';
|
|
35
35
|
export type { RefreshResult } from './refresh.js';
|
|
36
36
|
export { refreshSession } from './refresh.js';
|
|
37
|
-
export { revokeRefreshToken } from './revoke.js';
|
|
37
|
+
export { revokeRefreshToken, globalSignOut } from './revoke.js';
|
|
38
|
+
export type { EnsureUserForVerifiedEmailInput, EnsureUserForVerifiedEmailResult, } from './passwordless.js';
|
|
39
|
+
export { ensureUserForVerifiedEmail, mintSessionForVerifiedEmail, } from './passwordless.js';
|
|
38
40
|
export type { ChangePasswordInput } from './change-password.js';
|
|
39
41
|
export { changePassword } from './change-password.js';
|
|
40
42
|
export type { ForgotPasswordInput, ForgotPasswordResult, ConfirmForgotPasswordInput, CodeDeliveryDetails, } from './forgot-password.js';
|
|
41
43
|
export { forgotPassword, confirmForgotPassword } from './forgot-password.js';
|
|
42
44
|
export type { VerifyOptions } from './verify.js';
|
|
43
45
|
export { verifyAndDecode } from './verify.js';
|
|
44
|
-
export
|
|
45
|
-
export {
|
|
46
|
+
export { decodeJwtClaims } from './token-utils.js';
|
|
47
|
+
export type { SessionTokens, CookieOptions, CookieDomainOptions, OAuthStateCookieOptions, } from './cookies.js';
|
|
48
|
+
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, buildOAuthStateCookie, clearOAuthStateCookie, oauthStateCookieName, resolveCookieDomain, } from './cookies.js';
|
|
46
49
|
export type { CookieAuthMiddlewareOptions } from './middleware.js';
|
|
47
50
|
export { cookieAuthMiddleware, extractToken, defaultScopeMapper } from './middleware.js';
|
|
48
51
|
export type { FederatedAuthRoutes, FederatedAuthRoutesOptions, FederatedCallbackBody, FederatedCallbackResult, FederatedOnSignInArgs, FederatedOnSignInResult, FederatedStartBody, } from './federated-routes.js';
|
|
49
52
|
export { createFederatedAuthRoutes } from './federated-routes.js';
|
|
53
|
+
export type { HandoffPayload, HandoffStore } from './handoff.js';
|
|
54
|
+
export { DEFAULT_HANDOFF_TTL_SECONDS, generateHandoffCode, hashHandoffCode, issueHandoffCode, redeemHandoffCode, } from './handoff.js';
|
|
55
|
+
export type { HandoffAuthorizeArgs, HandoffStartBody, SessionHandoffRoutes, SessionHandoffRoutesOptions, } from './handoff-routes.js';
|
|
56
|
+
export { createSessionHandoffRoutes, DEFAULT_HANDOFF_COMPLETE_PATH, } from './handoff-routes.js';
|
|
50
57
|
export type { FederatedProvider, FederatedProfile, FederatedProviderCredentials, SignInAsFederatedUserInput, BuildAuthorizeUrlInput, ExchangeAuthorizationCodeInput, } from './federated.js';
|
|
51
58
|
export { loadFederatedProviderCredentials, generateOAuthState, verifyOAuthState, buildAuthorizeUrl, exchangeAuthorizationCode, signInAsFederatedUser, } from './federated.js';
|
|
52
59
|
export type { VerificationChannel, VerificationCodeStore, VerificationCodeRecord, RequestVerificationCodeInput, RequestVerificationCodeResult, VerifyVerificationCodeInput, } from './verification.js';
|
|
@@ -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,EACf,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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.js
CHANGED
|
@@ -28,13 +28,19 @@ export { signInWithPasswordOrChallenge, respondToNewPasswordChallenge, } from '.
|
|
|
28
28
|
export { signUpUser } from './sign-up.js';
|
|
29
29
|
export { adminInviteUser, adminUpdateUserAttributes, adminDisableUser, adminEnableUser, adminDeleteUser, } from './admin-users.js';
|
|
30
30
|
export { refreshSession } from './refresh.js';
|
|
31
|
-
export { revokeRefreshToken } from './revoke.js';
|
|
31
|
+
export { revokeRefreshToken, globalSignOut } from './revoke.js';
|
|
32
|
+
export { ensureUserForVerifiedEmail, mintSessionForVerifiedEmail, } from './passwordless.js';
|
|
32
33
|
export { changePassword } from './change-password.js';
|
|
33
34
|
export { forgotPassword, confirmForgotPassword } from './forgot-password.js';
|
|
34
35
|
export { verifyAndDecode } from './verify.js';
|
|
35
|
-
|
|
36
|
+
// Unverified claim extraction — ONLY for tokens received directly
|
|
37
|
+
// from the provider (e.g. reading profile claims right after sign-in).
|
|
38
|
+
export { decodeJwtClaims } from './token-utils.js';
|
|
39
|
+
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, buildOAuthStateCookie, clearOAuthStateCookie, oauthStateCookieName, resolveCookieDomain, } from './cookies.js';
|
|
36
40
|
export { cookieAuthMiddleware, extractToken, defaultScopeMapper } from './middleware.js';
|
|
37
41
|
export { createFederatedAuthRoutes } from './federated-routes.js';
|
|
42
|
+
export { DEFAULT_HANDOFF_TTL_SECONDS, generateHandoffCode, hashHandoffCode, issueHandoffCode, redeemHandoffCode, } from './handoff.js';
|
|
43
|
+
export { createSessionHandoffRoutes, DEFAULT_HANDOFF_COMPLETE_PATH, } from './handoff-routes.js';
|
|
38
44
|
export { loadFederatedProviderCredentials, generateOAuthState, verifyOAuthState, buildAuthorizeUrl, exchangeAuthorizationCode, signInAsFederatedUser, } from './federated.js';
|
|
39
45
|
export { generateVerificationCode, hashVerificationCode, requestVerificationCode, verifyVerificationCode, createInMemoryVerificationCodeStore, } from './verification.js';
|
|
40
46
|
//# 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,EACf,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,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,EACf,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQhE,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAG7E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,kEAAkE;AAClE,uEAAuE;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAQnD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAWzF,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAQtB,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC;AAU7B,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,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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"}
|
package/dist/server/revoke.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/server/revoke.js
CHANGED
|
@@ -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
|
/**
|
|
@@ -28,4 +28,22 @@ export async function revokeRefreshToken(refreshToken, config = loadAuthServerCo
|
|
|
28
28
|
console.warn('[@venturekit/auth/server] refresh-token revoke failed (ignored):', err);
|
|
29
29
|
}
|
|
30
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
|
+
}
|
|
31
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,
|
|
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"}
|