@venturekit/auth 0.0.0-dev.20260427225816 → 0.0.0-dev.20260429113801
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/cognito-client.d.ts +18 -0
- package/dist/server/cognito-client.d.ts.map +1 -0
- package/dist/server/cognito-client.js +28 -0
- package/dist/server/cognito-client.js.map +1 -0
- package/dist/server/config.d.ts +35 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +52 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/cookies.d.ts +75 -0
- package/dist/server/cookies.d.ts.map +1 -0
- package/dist/server/cookies.js +109 -0
- package/dist/server/cookies.js.map +1 -0
- package/dist/server/errors.d.ts +20 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +45 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/index.d.ts +38 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +32 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +76 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +115 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/refresh.d.ts +31 -0
- package/dist/server/refresh.d.ts.map +1 -0
- package/dist/server/refresh.js +45 -0
- package/dist/server/refresh.js.map +1 -0
- package/dist/server/revoke.d.ts +18 -0
- package/dist/server/revoke.d.ts.map +1 -0
- package/dist/server/revoke.js +32 -0
- package/dist/server/revoke.js.map +1 -0
- package/dist/server/sign-in.d.ts +40 -0
- package/dist/server/sign-in.d.ts.map +1 -0
- package/dist/server/sign-in.js +63 -0
- package/dist/server/sign-in.js.map +1 -0
- package/dist/server/verify.d.ts +31 -0
- package/dist/server/verify.d.ts.map +1 -0
- package/dist/server/verify.js +53 -0
- package/dist/server/verify.js.map +1 -0
- package/dist/session/index.d.ts +2 -22
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +4 -41
- package/dist/session/index.js.map +1 -1
- package/package.json +18 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Cognito client cache.
|
|
3
|
+
*
|
|
4
|
+
* The server flows in this directory all hit the same Cognito User Pool
|
|
5
|
+
* within a Lambda invocation, so we cache one
|
|
6
|
+
* `CognitoIdentityProviderClient` per region. The cache is keyed by region
|
|
7
|
+
* because a single deployment could (in theory) host pools in multiple
|
|
8
|
+
* regions — in practice every flow uses the region from
|
|
9
|
+
* {@link AuthServerConfig.region}, so the cache stays at size 1.
|
|
10
|
+
*
|
|
11
|
+
* This module is **not** part of the public API surface; consumers go
|
|
12
|
+
* through `sign-in.ts`, `refresh.ts`, `revoke.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider';
|
|
15
|
+
export declare function getCognitoClient(region: string): CognitoIdentityProviderClient;
|
|
16
|
+
/** Test-only — clear the cached clients between vitest runs. */
|
|
17
|
+
export declare function _resetCognitoClientCacheForTesting(): void;
|
|
18
|
+
//# sourceMappingURL=cognito-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cognito-client.d.ts","sourceRoot":"","sources":["../../src/server/cognito-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,2CAA2C,CAAC;AAI1F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,6BAA6B,CAM9E;AAED,gEAAgE;AAChE,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Cognito client cache.
|
|
3
|
+
*
|
|
4
|
+
* The server flows in this directory all hit the same Cognito User Pool
|
|
5
|
+
* within a Lambda invocation, so we cache one
|
|
6
|
+
* `CognitoIdentityProviderClient` per region. The cache is keyed by region
|
|
7
|
+
* because a single deployment could (in theory) host pools in multiple
|
|
8
|
+
* regions — in practice every flow uses the region from
|
|
9
|
+
* {@link AuthServerConfig.region}, so the cache stays at size 1.
|
|
10
|
+
*
|
|
11
|
+
* This module is **not** part of the public API surface; consumers go
|
|
12
|
+
* through `sign-in.ts`, `refresh.ts`, `revoke.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider';
|
|
15
|
+
const cache = new Map();
|
|
16
|
+
export function getCognitoClient(region) {
|
|
17
|
+
const existing = cache.get(region);
|
|
18
|
+
if (existing)
|
|
19
|
+
return existing;
|
|
20
|
+
const client = new CognitoIdentityProviderClient({ region });
|
|
21
|
+
cache.set(region, client);
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
/** Test-only — clear the cached clients between vitest runs. */
|
|
25
|
+
export function _resetCognitoClientCacheForTesting() {
|
|
26
|
+
cache.clear();
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=cognito-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cognito-client.js","sourceRoot":"","sources":["../../src/server/cognito-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,2CAA2C,CAAC;AAE1F,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyC,CAAC;AAE/D,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,6BAA6B,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,kCAAkC;IAChD,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side auth configuration.
|
|
3
|
+
*
|
|
4
|
+
* `AuthServerConfig` is the minimal set of values the server flows in
|
|
5
|
+
* `@venturekit/auth/server` need to talk to Cognito:
|
|
6
|
+
*
|
|
7
|
+
* - `region` — AWS region the user pool lives in
|
|
8
|
+
* - `userPoolId` — Cognito User Pool id
|
|
9
|
+
* - `appClientId` — Cognito User Pool app client id
|
|
10
|
+
*
|
|
11
|
+
* Most consumers will call {@link loadAuthServerConfig} which reads these
|
|
12
|
+
* from environment variables (`COGNITO_REGION`/`AWS_REGION`,
|
|
13
|
+
* `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`). The VentureKit
|
|
14
|
+
* deploy pipeline injects these into the Lambda environment when an
|
|
15
|
+
* `auth` intent is declared in `vk.config.ts`.
|
|
16
|
+
*
|
|
17
|
+
* Tests / scripts can pass a `config` argument explicitly to every flow
|
|
18
|
+
* and skip env-var loading entirely.
|
|
19
|
+
*/
|
|
20
|
+
export interface AuthServerConfig {
|
|
21
|
+
region: string;
|
|
22
|
+
userPoolId: string;
|
|
23
|
+
appClientId: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read server-side auth configuration from environment variables.
|
|
27
|
+
*
|
|
28
|
+
* Throws when any required value is missing — server flows fail fast at
|
|
29
|
+
* first auth call rather than silently producing 401s on a misconfigured
|
|
30
|
+
* deploy.
|
|
31
|
+
*
|
|
32
|
+
* @param env Source of environment variables. Defaults to `process.env`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadAuthServerConfig(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): AuthServerConfig;
|
|
35
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxE,gBAAgB,CAqBlB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side auth configuration.
|
|
3
|
+
*
|
|
4
|
+
* `AuthServerConfig` is the minimal set of values the server flows in
|
|
5
|
+
* `@venturekit/auth/server` need to talk to Cognito:
|
|
6
|
+
*
|
|
7
|
+
* - `region` — AWS region the user pool lives in
|
|
8
|
+
* - `userPoolId` — Cognito User Pool id
|
|
9
|
+
* - `appClientId` — Cognito User Pool app client id
|
|
10
|
+
*
|
|
11
|
+
* Most consumers will call {@link loadAuthServerConfig} which reads these
|
|
12
|
+
* from environment variables (`COGNITO_REGION`/`AWS_REGION`,
|
|
13
|
+
* `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`). The VentureKit
|
|
14
|
+
* deploy pipeline injects these into the Lambda environment when an
|
|
15
|
+
* `auth` intent is declared in `vk.config.ts`.
|
|
16
|
+
*
|
|
17
|
+
* Tests / scripts can pass a `config` argument explicitly to every flow
|
|
18
|
+
* and skip env-var loading entirely.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Read server-side auth configuration from environment variables.
|
|
22
|
+
*
|
|
23
|
+
* Throws when any required value is missing — server flows fail fast at
|
|
24
|
+
* first auth call rather than silently producing 401s on a misconfigured
|
|
25
|
+
* deploy.
|
|
26
|
+
*
|
|
27
|
+
* @param env Source of environment variables. Defaults to `process.env`.
|
|
28
|
+
*/
|
|
29
|
+
export function loadAuthServerConfig(env = process.env) {
|
|
30
|
+
const region = env['COGNITO_REGION'] ?? env['AWS_REGION'];
|
|
31
|
+
const userPoolId = env['COGNITO_USER_POOL_ID'];
|
|
32
|
+
const appClientId = env['COGNITO_APP_CLIENT_ID'];
|
|
33
|
+
const missing = [];
|
|
34
|
+
if (!region)
|
|
35
|
+
missing.push('COGNITO_REGION (or AWS_REGION)');
|
|
36
|
+
if (!userPoolId)
|
|
37
|
+
missing.push('COGNITO_USER_POOL_ID');
|
|
38
|
+
if (!appClientId)
|
|
39
|
+
missing.push('COGNITO_APP_CLIENT_ID');
|
|
40
|
+
if (missing.length > 0) {
|
|
41
|
+
throw new Error(`[@venturekit/auth/server] missing env var(s): ${missing.join(', ')} — ` +
|
|
42
|
+
`VentureKit's auth intent in vk.config.ts injects these into the Lambda ` +
|
|
43
|
+
`environment from the deployed user pool. Set them manually in .env.local ` +
|
|
44
|
+
`for local dev.`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
region: region,
|
|
48
|
+
userPoolId: userPoolId,
|
|
49
|
+
appClientId: appClientId,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAA8D,OAAO,CAAC,GAAG;IAEzE,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACjD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtD,IAAI,CAAC,WAAW;QAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,iDAAiD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACtE,yEAAyE;YACzE,2EAA2E;YAC3E,gBAAgB,CACnB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAO;QACf,UAAU,EAAE,UAAW;QACvB,WAAW,EAAE,WAAY;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session cookie helpers — read from inbound `Cookie:` headers, build
|
|
3
|
+
* `Set-Cookie` header values for outgoing responses.
|
|
4
|
+
*
|
|
5
|
+
* **Cookie attributes** (every auth cookie):
|
|
6
|
+
* - `HttpOnly` — JS in the browser cannot read these. The browser
|
|
7
|
+
* attaches them automatically when fetching with
|
|
8
|
+
* `credentials: 'include'`.
|
|
9
|
+
* - `Secure` — set when {@link CookieOptions.secure} is true (default:
|
|
10
|
+
* `process.env.NODE_ENV === 'production'`). Omitted in dev so cookies
|
|
11
|
+
* work over plain HTTP localhost.
|
|
12
|
+
* - `SameSite=Lax` for the id/access cookies, `SameSite=Strict` for
|
|
13
|
+
* the refresh cookie. The refresh cookie is only ever sent to the
|
|
14
|
+
* paths under {@link CookieOptions.refreshPath} (default `/auth`)
|
|
15
|
+
* so it never leaks to non-auth handlers.
|
|
16
|
+
* - `Path=/` for the id/access tokens, `Path=/auth` for refresh.
|
|
17
|
+
*
|
|
18
|
+
* The cookies are **host-only** (no `Domain` attribute) so they're
|
|
19
|
+
* scoped to the host the response came from. The browser sends them on
|
|
20
|
+
* every request to that host, including cross-origin XHR from a sibling
|
|
21
|
+
* subdomain when `credentials: 'include'` is used and both hosts share
|
|
22
|
+
* the same registrable domain.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ID_TOKEN_COOKIE = "vk_id_token";
|
|
25
|
+
export declare const ACCESS_TOKEN_COOKIE = "vk_access_token";
|
|
26
|
+
export declare const REFRESH_TOKEN_COOKIE = "vk_refresh_token";
|
|
27
|
+
export interface SessionTokens {
|
|
28
|
+
idToken: string;
|
|
29
|
+
accessToken: string;
|
|
30
|
+
/** May be omitted on refresh — Cognito doesn't always rotate it. */
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
/** Seconds until id/access tokens expire (typically 3600). */
|
|
33
|
+
expiresIn: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CookieOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Emit the `Secure` flag. Defaults to
|
|
38
|
+
* `process.env.NODE_ENV === 'production'`.
|
|
39
|
+
*/
|
|
40
|
+
secure?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Path scope for the refresh cookie. Defaults to `/auth`. The refresh
|
|
43
|
+
* token is never sent on requests outside this prefix.
|
|
44
|
+
*/
|
|
45
|
+
refreshPath?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Lifetime of the refresh cookie in seconds. Defaults to 30 days.
|
|
48
|
+
*/
|
|
49
|
+
refreshMaxAgeSeconds?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build the `Set-Cookie` header values to attach to a sign-in / refresh
|
|
53
|
+
* response. Returns an array — API Gateway v2 supports multi-valued
|
|
54
|
+
* `Set-Cookie` via the `cookies: string[]` field on
|
|
55
|
+
* `APIGatewayProxyStructuredResultV2`.
|
|
56
|
+
*
|
|
57
|
+
* The refresh slot is skipped when {@link SessionTokens.refreshToken} is
|
|
58
|
+
* empty, which is the normal case after a refresh exchange (Cognito's
|
|
59
|
+
* `REFRESH_TOKEN_AUTH` doesn't rotate the refresh token).
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildSessionCookies(tokens: SessionTokens, options?: CookieOptions): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Build the `Set-Cookie` header values that clear all auth cookies.
|
|
64
|
+
* Used on sign-out and as a defensive cleanup when refresh fails
|
|
65
|
+
* terminally so the next attempt goes straight to sign-in.
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildClearSessionCookies(options?: CookieOptions): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Read a single cookie out of a `Cookie:` header value. Returns `null`
|
|
70
|
+
* when the cookie is absent. Permissive: duplicates take the last
|
|
71
|
+
* occurrence, whitespace around `=` is allowed, URL-encoded values are
|
|
72
|
+
* decoded.
|
|
73
|
+
*/
|
|
74
|
+
export declare function readCookieFromHeader(rawCookieHeader: string | undefined | null, name: string): string | null;
|
|
75
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/server/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AACrD,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AAIvD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,EAAE,CAqBV;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE,CAY1E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC1C,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,IAAI,CAef"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session cookie helpers — read from inbound `Cookie:` headers, build
|
|
3
|
+
* `Set-Cookie` header values for outgoing responses.
|
|
4
|
+
*
|
|
5
|
+
* **Cookie attributes** (every auth cookie):
|
|
6
|
+
* - `HttpOnly` — JS in the browser cannot read these. The browser
|
|
7
|
+
* attaches them automatically when fetching with
|
|
8
|
+
* `credentials: 'include'`.
|
|
9
|
+
* - `Secure` — set when {@link CookieOptions.secure} is true (default:
|
|
10
|
+
* `process.env.NODE_ENV === 'production'`). Omitted in dev so cookies
|
|
11
|
+
* work over plain HTTP localhost.
|
|
12
|
+
* - `SameSite=Lax` for the id/access cookies, `SameSite=Strict` for
|
|
13
|
+
* the refresh cookie. The refresh cookie is only ever sent to the
|
|
14
|
+
* paths under {@link CookieOptions.refreshPath} (default `/auth`)
|
|
15
|
+
* so it never leaks to non-auth handlers.
|
|
16
|
+
* - `Path=/` for the id/access tokens, `Path=/auth` for refresh.
|
|
17
|
+
*
|
|
18
|
+
* The cookies are **host-only** (no `Domain` attribute) so they're
|
|
19
|
+
* scoped to the host the response came from. The browser sends them on
|
|
20
|
+
* every request to that host, including cross-origin XHR from a sibling
|
|
21
|
+
* subdomain when `credentials: 'include'` is used and both hosts share
|
|
22
|
+
* the same registrable domain.
|
|
23
|
+
*/
|
|
24
|
+
export const ID_TOKEN_COOKIE = 'vk_id_token';
|
|
25
|
+
export const ACCESS_TOKEN_COOKIE = 'vk_access_token';
|
|
26
|
+
export const REFRESH_TOKEN_COOKIE = 'vk_refresh_token';
|
|
27
|
+
const REFRESH_MAX_AGE_SECONDS = 30 * 24 * 60 * 60; // 30 days
|
|
28
|
+
function resolve(opts) {
|
|
29
|
+
return {
|
|
30
|
+
secure: opts?.secure ?? process.env.NODE_ENV === 'production',
|
|
31
|
+
refreshPath: opts?.refreshPath ?? '/auth',
|
|
32
|
+
refreshMaxAgeSeconds: opts?.refreshMaxAgeSeconds ?? REFRESH_MAX_AGE_SECONDS,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildSetCookie(name, value, attrs, secure) {
|
|
36
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
37
|
+
parts.push(`Path=${attrs.path ?? '/'}`);
|
|
38
|
+
parts.push(`SameSite=${attrs.sameSite ?? 'Lax'}`);
|
|
39
|
+
parts.push('HttpOnly');
|
|
40
|
+
if (secure)
|
|
41
|
+
parts.push('Secure');
|
|
42
|
+
if (typeof attrs.maxAge === 'number') {
|
|
43
|
+
parts.push(`Max-Age=${Math.floor(attrs.maxAge)}`);
|
|
44
|
+
}
|
|
45
|
+
return parts.join('; ');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the `Set-Cookie` header values to attach to a sign-in / refresh
|
|
49
|
+
* response. Returns an array — API Gateway v2 supports multi-valued
|
|
50
|
+
* `Set-Cookie` via the `cookies: string[]` field on
|
|
51
|
+
* `APIGatewayProxyStructuredResultV2`.
|
|
52
|
+
*
|
|
53
|
+
* The refresh slot is skipped when {@link SessionTokens.refreshToken} is
|
|
54
|
+
* empty, which is the normal case after a refresh exchange (Cognito's
|
|
55
|
+
* `REFRESH_TOKEN_AUTH` doesn't rotate the refresh token).
|
|
56
|
+
*/
|
|
57
|
+
export function buildSessionCookies(tokens, options) {
|
|
58
|
+
const { secure, refreshPath, refreshMaxAgeSeconds } = resolve(options);
|
|
59
|
+
const accessMaxAge = Math.max(60, Math.min(tokens.expiresIn, 3600));
|
|
60
|
+
const headers = [];
|
|
61
|
+
headers.push(buildSetCookie(ID_TOKEN_COOKIE, tokens.idToken, { maxAge: accessMaxAge }, secure));
|
|
62
|
+
headers.push(buildSetCookie(ACCESS_TOKEN_COOKIE, tokens.accessToken, { maxAge: accessMaxAge }, secure));
|
|
63
|
+
if (tokens.refreshToken) {
|
|
64
|
+
headers.push(buildSetCookie(REFRESH_TOKEN_COOKIE, tokens.refreshToken, {
|
|
65
|
+
path: refreshPath,
|
|
66
|
+
maxAge: refreshMaxAgeSeconds,
|
|
67
|
+
sameSite: 'Strict',
|
|
68
|
+
}, secure));
|
|
69
|
+
}
|
|
70
|
+
return headers;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the `Set-Cookie` header values that clear all auth cookies.
|
|
74
|
+
* Used on sign-out and as a defensive cleanup when refresh fails
|
|
75
|
+
* terminally so the next attempt goes straight to sign-in.
|
|
76
|
+
*/
|
|
77
|
+
export function buildClearSessionCookies(options) {
|
|
78
|
+
const { secure, refreshPath } = resolve(options);
|
|
79
|
+
return [
|
|
80
|
+
buildSetCookie(ID_TOKEN_COOKIE, '', { maxAge: 0 }, secure),
|
|
81
|
+
buildSetCookie(ACCESS_TOKEN_COOKIE, '', { maxAge: 0 }, secure),
|
|
82
|
+
buildSetCookie(REFRESH_TOKEN_COOKIE, '', { path: refreshPath, maxAge: 0, sameSite: 'Strict' }, secure),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read a single cookie out of a `Cookie:` header value. Returns `null`
|
|
87
|
+
* when the cookie is absent. Permissive: duplicates take the last
|
|
88
|
+
* occurrence, whitespace around `=` is allowed, URL-encoded values are
|
|
89
|
+
* decoded.
|
|
90
|
+
*/
|
|
91
|
+
export function readCookieFromHeader(rawCookieHeader, name) {
|
|
92
|
+
if (!rawCookieHeader)
|
|
93
|
+
return null;
|
|
94
|
+
const target = `${name}=`;
|
|
95
|
+
let found = null;
|
|
96
|
+
for (const part of rawCookieHeader.split(';')) {
|
|
97
|
+
const trimmed = part.trim();
|
|
98
|
+
if (trimmed.startsWith(target)) {
|
|
99
|
+
try {
|
|
100
|
+
found = decodeURIComponent(trimmed.slice(target.length));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
found = trimmed.slice(target.length);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/server/cookies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth errors thrown by the server-side flows in `@venturekit/auth/server`.
|
|
3
|
+
*
|
|
4
|
+
* `AuthError` carries a stable machine-readable `code` (e.g.
|
|
5
|
+
* `invalid_credentials`, `password_reset_required`) and an HTTP status
|
|
6
|
+
* hint so route handlers can map it straight to a typed response without
|
|
7
|
+
* inspecting Cognito-specific error names.
|
|
8
|
+
*/
|
|
9
|
+
export declare class AuthError extends Error {
|
|
10
|
+
readonly code: string;
|
|
11
|
+
readonly status: number;
|
|
12
|
+
constructor(code: string, message: string, status?: number);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Map an underlying provider error (e.g. an AWS SDK error name) to a
|
|
16
|
+
* stable {@link AuthError}. Unknown error names fall through with their
|
|
17
|
+
* original `name` as `code` and the supplied default status.
|
|
18
|
+
*/
|
|
19
|
+
export declare function mapProviderError(err: unknown, fallbackCode: string, fallbackStatus?: number): AuthError;
|
|
20
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAM;CAMxD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,YAAY,EAAE,MAAM,EACpB,cAAc,SAAM,GACnB,SAAS,CAoBX"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth errors thrown by the server-side flows in `@venturekit/auth/server`.
|
|
3
|
+
*
|
|
4
|
+
* `AuthError` carries a stable machine-readable `code` (e.g.
|
|
5
|
+
* `invalid_credentials`, `password_reset_required`) and an HTTP status
|
|
6
|
+
* hint so route handlers can map it straight to a typed response without
|
|
7
|
+
* inspecting Cognito-specific error names.
|
|
8
|
+
*/
|
|
9
|
+
export class AuthError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
status;
|
|
12
|
+
constructor(code, message, status = 401) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'AuthError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.status = status;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Map an underlying provider error (e.g. an AWS SDK error name) to a
|
|
21
|
+
* stable {@link AuthError}. Unknown error names fall through with their
|
|
22
|
+
* original `name` as `code` and the supplied default status.
|
|
23
|
+
*/
|
|
24
|
+
export function mapProviderError(err, fallbackCode, fallbackStatus = 401) {
|
|
25
|
+
const e = err;
|
|
26
|
+
const code = e.name ?? fallbackCode;
|
|
27
|
+
const message = e.message ?? 'Authentication failed';
|
|
28
|
+
switch (code) {
|
|
29
|
+
case 'NotAuthorizedException':
|
|
30
|
+
case 'UserNotFoundException':
|
|
31
|
+
return new AuthError('invalid_credentials', 'Incorrect email or password', 401);
|
|
32
|
+
case 'UserNotConfirmedException':
|
|
33
|
+
return new AuthError('user_not_confirmed', message, 403);
|
|
34
|
+
case 'PasswordResetRequiredException':
|
|
35
|
+
return new AuthError('password_reset_required', message, 403);
|
|
36
|
+
case 'TooManyRequestsException':
|
|
37
|
+
case 'TooManyFailedAttemptsException':
|
|
38
|
+
return new AuthError('too_many_requests', message, 429);
|
|
39
|
+
case 'InvalidParameterException':
|
|
40
|
+
return new AuthError('invalid_parameter', message, 422);
|
|
41
|
+
default:
|
|
42
|
+
return new AuthError(code, message, fallbackStatus);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAS;IACb,MAAM,CAAS;IACxB,YAAY,IAAY,EAAE,OAAe,EAAE,MAAM,GAAG,GAAG;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAY,EACZ,YAAoB,EACpB,cAAc,GAAG,GAAG;IAEpB,MAAM,CAAC,GAAG,GAA0C,CAAC;IACrD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,YAAY,CAAC;IACpC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,uBAAuB,CAAC;IACrD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,wBAAwB,CAAC;QAC9B,KAAK,uBAAuB;YAC1B,OAAO,IAAI,SAAS,CAAC,qBAAqB,EAAE,6BAA6B,EAAE,GAAG,CAAC,CAAC;QAClF,KAAK,2BAA2B;YAC9B,OAAO,IAAI,SAAS,CAAC,oBAAoB,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC3D,KAAK,gCAAgC;YACnC,OAAO,IAAI,SAAS,CAAC,yBAAyB,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAChE,KAAK,0BAA0B,CAAC;QAChC,KAAK,gCAAgC;YACnC,OAAO,IAAI,SAAS,CAAC,mBAAmB,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1D,KAAK,2BAA2B;YAC9B,OAAO,IAAI,SAAS,CAAC,mBAAmB,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1D;YACE,OAAO,IAAI,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @venturekit/auth/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side authentication primitives for Lambda / Node handlers
|
|
5
|
+
* fronting a Cognito User Pool.
|
|
6
|
+
*
|
|
7
|
+
* **Responsibilities**
|
|
8
|
+
* - Password / refresh / revoke flows against Cognito's user-facing
|
|
9
|
+
* `InitiateAuth` and `RevokeToken` endpoints
|
|
10
|
+
* ({@link signInWithPassword}, {@link refreshSession},
|
|
11
|
+
* {@link revokeRefreshToken}).
|
|
12
|
+
* - JWT verification against the User Pool's JWKS
|
|
13
|
+
* ({@link verifyAndDecode}).
|
|
14
|
+
* - HTTP-only session cookie helpers
|
|
15
|
+
* ({@link buildSessionCookies}, {@link buildClearSessionCookies},
|
|
16
|
+
* {@link readCookieFromHeader}).
|
|
17
|
+
* - A stable {@link AuthError} surface that hides Cognito-specific
|
|
18
|
+
* error names from route handlers.
|
|
19
|
+
*
|
|
20
|
+
* Consumers should import from `@venturekit/auth/server` rather than
|
|
21
|
+
* the package root so the lightweight client-safe entry stays free of
|
|
22
|
+
* AWS SDK dependencies.
|
|
23
|
+
*/
|
|
24
|
+
export { AuthError, mapProviderError } from './errors.js';
|
|
25
|
+
export type { AuthServerConfig } from './config.js';
|
|
26
|
+
export { loadAuthServerConfig } from './config.js';
|
|
27
|
+
export type { SignInCredentials, SignInResult } from './sign-in.js';
|
|
28
|
+
export { signInWithPassword } from './sign-in.js';
|
|
29
|
+
export type { RefreshResult } from './refresh.js';
|
|
30
|
+
export { refreshSession } from './refresh.js';
|
|
31
|
+
export { revokeRefreshToken } from './revoke.js';
|
|
32
|
+
export type { VerifyOptions } from './verify.js';
|
|
33
|
+
export { verifyAndDecode } from './verify.js';
|
|
34
|
+
export type { SessionTokens, CookieOptions } from './cookies.js';
|
|
35
|
+
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, } from './cookies.js';
|
|
36
|
+
export type { CookieAuthMiddlewareOptions } from './middleware.js';
|
|
37
|
+
export { cookieAuthMiddleware, extractToken } from './middleware.js';
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,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,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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @venturekit/auth/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side authentication primitives for Lambda / Node handlers
|
|
5
|
+
* fronting a Cognito User Pool.
|
|
6
|
+
*
|
|
7
|
+
* **Responsibilities**
|
|
8
|
+
* - Password / refresh / revoke flows against Cognito's user-facing
|
|
9
|
+
* `InitiateAuth` and `RevokeToken` endpoints
|
|
10
|
+
* ({@link signInWithPassword}, {@link refreshSession},
|
|
11
|
+
* {@link revokeRefreshToken}).
|
|
12
|
+
* - JWT verification against the User Pool's JWKS
|
|
13
|
+
* ({@link verifyAndDecode}).
|
|
14
|
+
* - HTTP-only session cookie helpers
|
|
15
|
+
* ({@link buildSessionCookies}, {@link buildClearSessionCookies},
|
|
16
|
+
* {@link readCookieFromHeader}).
|
|
17
|
+
* - A stable {@link AuthError} surface that hides Cognito-specific
|
|
18
|
+
* error names from route handlers.
|
|
19
|
+
*
|
|
20
|
+
* Consumers should import from `@venturekit/auth/server` rather than
|
|
21
|
+
* the package root so the lightweight client-safe entry stays free of
|
|
22
|
+
* AWS SDK dependencies.
|
|
23
|
+
*/
|
|
24
|
+
export { AuthError, mapProviderError } from './errors.js';
|
|
25
|
+
export { loadAuthServerConfig } from './config.js';
|
|
26
|
+
export { signInWithPassword } from './sign-in.js';
|
|
27
|
+
export { refreshSession } from './refresh.js';
|
|
28
|
+
export { revokeRefreshToken } from './revoke.js';
|
|
29
|
+
export { verifyAndDecode } from './verify.js';
|
|
30
|
+
export { ID_TOKEN_COOKIE, ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE, buildSessionCookies, buildClearSessionCookies, readCookieFromHeader, } from './cookies.js';
|
|
31
|
+
export { cookieAuthMiddleware, extractToken } from './middleware.js';
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@venturekit/runtime`-compatible auth middleware.
|
|
3
|
+
*
|
|
4
|
+
* Transparently reads the session JWT from either:
|
|
5
|
+
* - the `vk_id_token` cookie (default; configurable via
|
|
6
|
+
* {@link CookieAuthMiddlewareOptions.cookieName}), OR
|
|
7
|
+
* - the `Authorization: Bearer <token>` header (bearer wins when both
|
|
8
|
+
* are present — simplifies mixed SPA + CLI clients).
|
|
9
|
+
*
|
|
10
|
+
* Verifies the token against the User Pool's JWKS via
|
|
11
|
+
* {@link verifyAndDecode} and populates `ctx.user` with the verified
|
|
12
|
+
* claims. When the token is missing / invalid / expired, `ctx.user`
|
|
13
|
+
* stays `null`; the downstream `handler({ scopes: [...] })` gate in
|
|
14
|
+
* `@venturekit/runtime` raises the 401. Public routes (no `scopes`)
|
|
15
|
+
* pass through unaffected.
|
|
16
|
+
*
|
|
17
|
+
* **Why a middleware and not a gateway authorizer?** API Gateway's
|
|
18
|
+
* `HttpJwtAuthorizer` only reads `Authorization: Bearer`; it cannot
|
|
19
|
+
* read cookies. This middleware runs inside the Lambda so HttpOnly
|
|
20
|
+
* cookie-based sessions (XSS-safer than localStorage bearers) keep
|
|
21
|
+
* working. The JWKS verifier caches keys across warm invocations
|
|
22
|
+
* ({@link verifyAndDecode}), so the per-request cost is the same
|
|
23
|
+
* signature-verify a gateway authorizer would do.
|
|
24
|
+
*/
|
|
25
|
+
import type { APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
26
|
+
import type { Middleware, RequestContext } from '@venturekit/runtime';
|
|
27
|
+
import type { AuthServerConfig } from './config.js';
|
|
28
|
+
export interface CookieAuthMiddlewareOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Cookie name carrying the session JWT. Defaults to `vk_id_token`
|
|
31
|
+
* (matches the cookie `buildSessionCookies` sets).
|
|
32
|
+
*/
|
|
33
|
+
cookieName?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Which token type to verify:
|
|
36
|
+
* - `'id'` (default) — matches id-token cookies which carry `email`
|
|
37
|
+
* and Cognito `custom:*` attributes. Use this for browser sessions.
|
|
38
|
+
* - `'access'` — matches access tokens which carry `scope` and
|
|
39
|
+
* `client_id`. Use this for service-to-service bearer calls.
|
|
40
|
+
*/
|
|
41
|
+
tokenUse?: 'id' | 'access';
|
|
42
|
+
/**
|
|
43
|
+
* Explicit Cognito config. Defaults to
|
|
44
|
+
* {@link loadAuthServerConfig} (reads env vars). Providing this in
|
|
45
|
+
* tests lets you construct a middleware without touching env.
|
|
46
|
+
*/
|
|
47
|
+
config?: AuthServerConfig;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a `@venturekit/runtime` middleware that verifies a session JWT
|
|
51
|
+
* and populates `ctx.user` with the verified claims.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { handler } from '@venturekit/runtime';
|
|
56
|
+
* import { cookieAuthMiddleware } from '@venturekit/auth/server';
|
|
57
|
+
*
|
|
58
|
+
* export const main = handler(
|
|
59
|
+
* async (body, ctx) => ({ userId: ctx.user!.id }),
|
|
60
|
+
* {
|
|
61
|
+
* scopes: ['cms.admin'],
|
|
62
|
+
* middleware: [cookieAuthMiddleware()],
|
|
63
|
+
* },
|
|
64
|
+
* );
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function cookieAuthMiddleware(options?: CookieAuthMiddlewareOptions): Middleware<RequestContext>;
|
|
68
|
+
/**
|
|
69
|
+
* Pull the session token out of the request. Bearer header wins over
|
|
70
|
+
* cookie so a mixed client (browser + CLI) stays predictable.
|
|
71
|
+
*
|
|
72
|
+
* Exported for tests only — route handlers should use
|
|
73
|
+
* {@link cookieAuthMiddleware} instead.
|
|
74
|
+
*/
|
|
75
|
+
export declare function extractToken(event: APIGatewayProxyEventV2, cookieName?: string): string | null;
|
|
76
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,UAAU,CAAC,cAAc,CAAC,CAgC5B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,sBAAsB,EAC7B,UAAU,GAAE,MAAwB,GACnC,MAAM,GAAG,IAAI,CAaf"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@venturekit/runtime`-compatible auth middleware.
|
|
3
|
+
*
|
|
4
|
+
* Transparently reads the session JWT from either:
|
|
5
|
+
* - the `vk_id_token` cookie (default; configurable via
|
|
6
|
+
* {@link CookieAuthMiddlewareOptions.cookieName}), OR
|
|
7
|
+
* - the `Authorization: Bearer <token>` header (bearer wins when both
|
|
8
|
+
* are present — simplifies mixed SPA + CLI clients).
|
|
9
|
+
*
|
|
10
|
+
* Verifies the token against the User Pool's JWKS via
|
|
11
|
+
* {@link verifyAndDecode} and populates `ctx.user` with the verified
|
|
12
|
+
* claims. When the token is missing / invalid / expired, `ctx.user`
|
|
13
|
+
* stays `null`; the downstream `handler({ scopes: [...] })` gate in
|
|
14
|
+
* `@venturekit/runtime` raises the 401. Public routes (no `scopes`)
|
|
15
|
+
* pass through unaffected.
|
|
16
|
+
*
|
|
17
|
+
* **Why a middleware and not a gateway authorizer?** API Gateway's
|
|
18
|
+
* `HttpJwtAuthorizer` only reads `Authorization: Bearer`; it cannot
|
|
19
|
+
* read cookies. This middleware runs inside the Lambda so HttpOnly
|
|
20
|
+
* cookie-based sessions (XSS-safer than localStorage bearers) keep
|
|
21
|
+
* working. The JWKS verifier caches keys across warm invocations
|
|
22
|
+
* ({@link verifyAndDecode}), so the per-request cost is the same
|
|
23
|
+
* signature-verify a gateway authorizer would do.
|
|
24
|
+
*/
|
|
25
|
+
import { ID_TOKEN_COOKIE, readCookieFromHeader } from './cookies.js';
|
|
26
|
+
import { verifyAndDecode } from './verify.js';
|
|
27
|
+
import { loadAuthServerConfig } from './config.js';
|
|
28
|
+
/**
|
|
29
|
+
* Build a `@venturekit/runtime` middleware that verifies a session JWT
|
|
30
|
+
* and populates `ctx.user` with the verified claims.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { handler } from '@venturekit/runtime';
|
|
35
|
+
* import { cookieAuthMiddleware } from '@venturekit/auth/server';
|
|
36
|
+
*
|
|
37
|
+
* export const main = handler(
|
|
38
|
+
* async (body, ctx) => ({ userId: ctx.user!.id }),
|
|
39
|
+
* {
|
|
40
|
+
* scopes: ['cms.admin'],
|
|
41
|
+
* middleware: [cookieAuthMiddleware()],
|
|
42
|
+
* },
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function cookieAuthMiddleware(options = {}) {
|
|
47
|
+
const cookieName = options.cookieName ?? ID_TOKEN_COOKIE;
|
|
48
|
+
const tokenUse = options.tokenUse ?? 'id';
|
|
49
|
+
// Resolve config lazily so a route module can import the middleware
|
|
50
|
+
// factory at file-load time without the env vars being set (tests,
|
|
51
|
+
// local type-only imports, etc.). The first actual request resolves
|
|
52
|
+
// and caches it — if the env is misconfigured, the error surfaces on
|
|
53
|
+
// first auth attempt rather than at cold start.
|
|
54
|
+
let resolvedConfig = options.config ?? null;
|
|
55
|
+
const getConfig = () => {
|
|
56
|
+
if (!resolvedConfig)
|
|
57
|
+
resolvedConfig = loadAuthServerConfig();
|
|
58
|
+
return resolvedConfig;
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
name: 'cookieAuth',
|
|
62
|
+
fn: async (ctx, next) => {
|
|
63
|
+
const token = extractToken(ctx.rawEvent, cookieName);
|
|
64
|
+
if (token) {
|
|
65
|
+
const cfg = getConfig();
|
|
66
|
+
const claims = await verifyAndDecode(token, {
|
|
67
|
+
userPoolId: cfg.userPoolId,
|
|
68
|
+
clientId: cfg.appClientId,
|
|
69
|
+
tokenUse,
|
|
70
|
+
});
|
|
71
|
+
if (claims) {
|
|
72
|
+
ctx.user = claimsToUserContext(claims);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return next();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pull the session token out of the request. Bearer header wins over
|
|
81
|
+
* cookie so a mixed client (browser + CLI) stays predictable.
|
|
82
|
+
*
|
|
83
|
+
* Exported for tests only — route handlers should use
|
|
84
|
+
* {@link cookieAuthMiddleware} instead.
|
|
85
|
+
*/
|
|
86
|
+
export function extractToken(event, cookieName = ID_TOKEN_COOKIE) {
|
|
87
|
+
const headers = event.headers ?? {};
|
|
88
|
+
const authHeader = (headers['authorization'] ?? headers['Authorization']);
|
|
89
|
+
if (authHeader) {
|
|
90
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
91
|
+
if (match && match[1])
|
|
92
|
+
return match[1].trim();
|
|
93
|
+
}
|
|
94
|
+
const cookieHeader = (headers['cookie'] ?? headers['Cookie']);
|
|
95
|
+
return readCookieFromHeader(cookieHeader, cookieName);
|
|
96
|
+
}
|
|
97
|
+
function claimsToUserContext(claims) {
|
|
98
|
+
const sub = claims['sub'];
|
|
99
|
+
const email = claims['email'];
|
|
100
|
+
// `scope` appears on access tokens (space-separated). ID tokens don't
|
|
101
|
+
// carry scopes; if the app uses ID tokens, scope-based route gating
|
|
102
|
+
// should be backed by `custom:*` role/scope attributes on the user
|
|
103
|
+
// pool and mapped in a downstream mapper.
|
|
104
|
+
const scopeClaim = claims['scope'];
|
|
105
|
+
const scopes = typeof scopeClaim === 'string'
|
|
106
|
+
? scopeClaim.split(' ').filter(Boolean)
|
|
107
|
+
: [];
|
|
108
|
+
return {
|
|
109
|
+
id: typeof sub === 'string' ? sub : '',
|
|
110
|
+
email: typeof email === 'string' ? email : undefined,
|
|
111
|
+
scopes,
|
|
112
|
+
claims,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side refresh-token flow against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `REFRESH_TOKEN_AUTH` flow to swap a refresh token for a fresh
|
|
5
|
+
* id+access token pair. Cognito does NOT rotate the refresh token by
|
|
6
|
+
* default; the same one stays valid until its User-Pool-configured TTL
|
|
7
|
+
* or until {@link revokeRefreshToken} is called — so the returned
|
|
8
|
+
* `refreshToken` is always the empty string.
|
|
9
|
+
*/
|
|
10
|
+
import type { AuthServerConfig } from './config.js';
|
|
11
|
+
export interface RefreshResult {
|
|
12
|
+
idToken: string;
|
|
13
|
+
accessToken: string;
|
|
14
|
+
/**
|
|
15
|
+
* Always `''` — Cognito's `REFRESH_TOKEN_AUTH` flow does not rotate
|
|
16
|
+
* the refresh token. Callers should NOT overwrite their stored refresh
|
|
17
|
+
* cookie with this value.
|
|
18
|
+
*/
|
|
19
|
+
refreshToken: string;
|
|
20
|
+
/** Seconds until the id/access tokens expire. */
|
|
21
|
+
expiresIn: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Exchange a refresh token for fresh id + access tokens.
|
|
25
|
+
*
|
|
26
|
+
* Throws {@link AuthError} when the refresh token is expired / revoked
|
|
27
|
+
* / malformed; route handlers should clear session cookies on terminal
|
|
28
|
+
* failures so the user is redirected straight to sign-in.
|
|
29
|
+
*/
|
|
30
|
+
export declare function refreshSession(refreshToken: string, config?: AuthServerConfig): Promise<RefreshResult>;
|
|
31
|
+
//# sourceMappingURL=refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/server/refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKpD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EACpB,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,aAAa,CAAC,CA4BxB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side refresh-token flow against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `REFRESH_TOKEN_AUTH` flow to swap a refresh token for a fresh
|
|
5
|
+
* id+access token pair. Cognito does NOT rotate the refresh token by
|
|
6
|
+
* default; the same one stays valid until its User-Pool-configured TTL
|
|
7
|
+
* or until {@link revokeRefreshToken} is called — so the returned
|
|
8
|
+
* `refreshToken` is always the empty string.
|
|
9
|
+
*/
|
|
10
|
+
import { InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider';
|
|
11
|
+
import { loadAuthServerConfig } from './config.js';
|
|
12
|
+
import { getCognitoClient } from './cognito-client.js';
|
|
13
|
+
import { AuthError, mapProviderError } from './errors.js';
|
|
14
|
+
/**
|
|
15
|
+
* Exchange a refresh token for fresh id + access tokens.
|
|
16
|
+
*
|
|
17
|
+
* Throws {@link AuthError} when the refresh token is expired / revoked
|
|
18
|
+
* / malformed; route handlers should clear session cookies on terminal
|
|
19
|
+
* failures so the user is redirected straight to sign-in.
|
|
20
|
+
*/
|
|
21
|
+
export async function refreshSession(refreshToken, config = loadAuthServerConfig()) {
|
|
22
|
+
const client = getCognitoClient(config.region);
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await client.send(new InitiateAuthCommand({
|
|
26
|
+
ClientId: config.appClientId,
|
|
27
|
+
AuthFlow: 'REFRESH_TOKEN_AUTH',
|
|
28
|
+
AuthParameters: { REFRESH_TOKEN: refreshToken },
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
throw mapProviderError(err, 'refresh_failed');
|
|
33
|
+
}
|
|
34
|
+
const result = res.AuthenticationResult;
|
|
35
|
+
if (!result?.IdToken || !result.AccessToken || !result.ExpiresIn) {
|
|
36
|
+
throw new AuthError('incomplete_auth_result', 'Identity provider returned an incomplete refresh result', 500);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
idToken: result.IdToken,
|
|
40
|
+
accessToken: result.AccessToken,
|
|
41
|
+
refreshToken: '',
|
|
42
|
+
expiresIn: result.ExpiresIn,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/server/refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAEhF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAe1D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,YAAoB,EACpB,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CACrB,IAAI,mBAAmB,CAAC;YACtB,QAAQ,EAAE,MAAM,CAAC,WAAW;YAC5B,QAAQ,EAAE,oBAAoB;YAC9B,cAAc,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE;SAChD,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,oBAAoB,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACjE,MAAM,IAAI,SAAS,CACjB,wBAAwB,EACxB,yDAAyD,EACzD,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EAAE,EAAE;QAChB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort refresh-token revocation against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Sign-out routes call this to invalidate a refresh token server-side
|
|
5
|
+
* even though the cookies are about to be cleared on the response. We
|
|
6
|
+
* swallow + log all errors because logout shouldn't fail the user just
|
|
7
|
+
* because the server-side revoke had a hiccup — the cookies clear
|
|
8
|
+
* regardless.
|
|
9
|
+
*/
|
|
10
|
+
import type { AuthServerConfig } from './config.js';
|
|
11
|
+
/**
|
|
12
|
+
* Revoke a refresh token at the identity provider.
|
|
13
|
+
*
|
|
14
|
+
* **Best-effort.** Failures are logged via `console.warn` and swallowed —
|
|
15
|
+
* the function never throws.
|
|
16
|
+
*/
|
|
17
|
+
export declare function revokeRefreshToken(refreshToken: string, config?: AuthServerConfig): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=revoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort refresh-token revocation against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Sign-out routes call this to invalidate a refresh token server-side
|
|
5
|
+
* even though the cookies are about to be cleared on the response. We
|
|
6
|
+
* swallow + log all errors because logout shouldn't fail the user just
|
|
7
|
+
* because the server-side revoke had a hiccup — the cookies clear
|
|
8
|
+
* regardless.
|
|
9
|
+
*/
|
|
10
|
+
import { RevokeTokenCommand } from '@aws-sdk/client-cognito-identity-provider';
|
|
11
|
+
import { loadAuthServerConfig } from './config.js';
|
|
12
|
+
import { getCognitoClient } from './cognito-client.js';
|
|
13
|
+
/**
|
|
14
|
+
* Revoke a refresh token at the identity provider.
|
|
15
|
+
*
|
|
16
|
+
* **Best-effort.** Failures are logged via `console.warn` and swallowed —
|
|
17
|
+
* the function never throws.
|
|
18
|
+
*/
|
|
19
|
+
export async function revokeRefreshToken(refreshToken, config = loadAuthServerConfig()) {
|
|
20
|
+
const client = getCognitoClient(config.region);
|
|
21
|
+
try {
|
|
22
|
+
await client.send(new RevokeTokenCommand({
|
|
23
|
+
ClientId: config.appClientId,
|
|
24
|
+
Token: refreshToken,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.warn('[@venturekit/auth/server] refresh-token revoke failed (ignored):', err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=revoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/server/revoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CACf,IAAI,kBAAkB,CAAC;YACrB,QAAQ,EAAE,MAAM,CAAC,WAAW;YAC5B,KAAK,EAAE,YAAY;SACpB,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,kEAAkE,EAAE,GAAG,CAAC,CAAC;IACxF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side password sign-in against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `USER_PASSWORD_AUTH` flow — a public Cognito flow that
|
|
5
|
+
* accepts username + password directly over TLS to Cognito's regular
|
|
6
|
+
* endpoint. The User Pool's app client must have
|
|
7
|
+
* `ALLOW_USER_PASSWORD_AUTH` enabled (VentureKit's CDK stack enables it
|
|
8
|
+
* by default).
|
|
9
|
+
*
|
|
10
|
+
* Picked over `ADMIN_USER_PASSWORD_AUTH` because the latter would
|
|
11
|
+
* require both `adminUserPassword: true` on the app client AND
|
|
12
|
+
* `cognito-idp:AdminInitiateAuth` on the Lambda role — neither of which
|
|
13
|
+
* is enabled out of the box.
|
|
14
|
+
*
|
|
15
|
+
* Errors are wrapped in {@link AuthError} via {@link mapProviderError}
|
|
16
|
+
* so route handlers can map them to typed responses without inspecting
|
|
17
|
+
* Cognito-specific error names.
|
|
18
|
+
*/
|
|
19
|
+
import type { AuthServerConfig } from './config.js';
|
|
20
|
+
export interface SignInResult {
|
|
21
|
+
idToken: string;
|
|
22
|
+
accessToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
/** Seconds until the id/access tokens expire. */
|
|
25
|
+
expiresIn: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SignInCredentials {
|
|
28
|
+
email: string;
|
|
29
|
+
password: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Authenticate a user with email + password and return freshly-minted
|
|
33
|
+
* id, access, and refresh tokens.
|
|
34
|
+
*
|
|
35
|
+
* @param credentials Email + password (email is lower-cased before send).
|
|
36
|
+
* @param config Optional explicit config; defaults to env vars via
|
|
37
|
+
* {@link loadAuthServerConfig}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function signInWithPassword(credentials: SignInCredentials, config?: AuthServerConfig): Promise<SignInResult>;
|
|
40
|
+
//# sourceMappingURL=sign-in.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-in.d.ts","sourceRoot":"","sources":["../../src/server/sign-in.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKpD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,iBAAiB,EAC9B,MAAM,GAAE,gBAAyC,GAChD,OAAO,CAAC,YAAY,CAAC,CAkBvB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side password sign-in against a Cognito User Pool.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `USER_PASSWORD_AUTH` flow — a public Cognito flow that
|
|
5
|
+
* accepts username + password directly over TLS to Cognito's regular
|
|
6
|
+
* endpoint. The User Pool's app client must have
|
|
7
|
+
* `ALLOW_USER_PASSWORD_AUTH` enabled (VentureKit's CDK stack enables it
|
|
8
|
+
* by default).
|
|
9
|
+
*
|
|
10
|
+
* Picked over `ADMIN_USER_PASSWORD_AUTH` because the latter would
|
|
11
|
+
* require both `adminUserPassword: true` on the app client AND
|
|
12
|
+
* `cognito-idp:AdminInitiateAuth` on the Lambda role — neither of which
|
|
13
|
+
* is enabled out of the box.
|
|
14
|
+
*
|
|
15
|
+
* Errors are wrapped in {@link AuthError} via {@link mapProviderError}
|
|
16
|
+
* so route handlers can map them to typed responses without inspecting
|
|
17
|
+
* Cognito-specific error names.
|
|
18
|
+
*/
|
|
19
|
+
import { InitiateAuthCommand, } from '@aws-sdk/client-cognito-identity-provider';
|
|
20
|
+
import { loadAuthServerConfig } from './config.js';
|
|
21
|
+
import { getCognitoClient } from './cognito-client.js';
|
|
22
|
+
import { AuthError, mapProviderError } from './errors.js';
|
|
23
|
+
/**
|
|
24
|
+
* Authenticate a user with email + password and return freshly-minted
|
|
25
|
+
* id, access, and refresh tokens.
|
|
26
|
+
*
|
|
27
|
+
* @param credentials Email + password (email is lower-cased before send).
|
|
28
|
+
* @param config Optional explicit config; defaults to env vars via
|
|
29
|
+
* {@link loadAuthServerConfig}.
|
|
30
|
+
*/
|
|
31
|
+
export async function signInWithPassword(credentials, config = loadAuthServerConfig()) {
|
|
32
|
+
const client = getCognitoClient(config.region);
|
|
33
|
+
let res;
|
|
34
|
+
try {
|
|
35
|
+
res = await client.send(new InitiateAuthCommand({
|
|
36
|
+
ClientId: config.appClientId,
|
|
37
|
+
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
38
|
+
AuthParameters: {
|
|
39
|
+
USERNAME: credentials.email.toLowerCase(),
|
|
40
|
+
PASSWORD: credentials.password,
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw mapProviderError(err, 'auth_failed');
|
|
46
|
+
}
|
|
47
|
+
return extractSignInTokens(res.AuthenticationResult);
|
|
48
|
+
}
|
|
49
|
+
function extractSignInTokens(result) {
|
|
50
|
+
if (!result?.IdToken ||
|
|
51
|
+
!result.AccessToken ||
|
|
52
|
+
!result.RefreshToken ||
|
|
53
|
+
!result.ExpiresIn) {
|
|
54
|
+
throw new AuthError('incomplete_auth_result', 'Identity provider returned an incomplete authentication result', 500);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
idToken: result.IdToken,
|
|
58
|
+
accessToken: result.AccessToken,
|
|
59
|
+
refreshToken: result.RefreshToken,
|
|
60
|
+
expiresIn: result.ExpiresIn,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=sign-in.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-in.js","sourceRoot":"","sources":["../../src/server/sign-in.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,mBAAmB,GAEpB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAe1D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAA8B,EAC9B,SAA2B,oBAAoB,EAAE;IAEjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CACrB,IAAI,mBAAmB,CAAC;YACtB,QAAQ,EAAE,MAAM,CAAC,WAAW;YAC5B,QAAQ,EAAE,oBAAoB;YAC9B,cAAc,EAAE;gBACd,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE;gBACzC,QAAQ,EAAE,WAAW,CAAC,QAAQ;aAC/B;SACF,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,gBAAgB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,mBAAmB,CAC1B,MAA4C;IAE5C,IACE,CAAC,MAAM,EAAE,OAAO;QAChB,CAAC,MAAM,CAAC,WAAW;QACnB,CAAC,MAAM,CAAC,YAAY;QACpB,CAAC,MAAM,CAAC,SAAS,EACjB,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,wBAAwB,EACxB,gEAAgE,EAChE,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT verification against a Cognito User Pool's JWKS.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `aws-jwt-verify`'s `CognitoJwtVerifier` so consumers don't take
|
|
5
|
+
* a direct dependency on it. The verifier is cached per
|
|
6
|
+
* `(userPoolId, tokenUse, clientId)` tuple so the JWKS is fetched once
|
|
7
|
+
* per cold start.
|
|
8
|
+
*
|
|
9
|
+
* Use this when API Gateway is NOT fronting your Lambda with a Cognito
|
|
10
|
+
* Authorizer — for example, cookie-based sessions where the Lambda
|
|
11
|
+
* itself reads the cookie and verifies the token.
|
|
12
|
+
*/
|
|
13
|
+
export interface VerifyOptions {
|
|
14
|
+
userPoolId: string;
|
|
15
|
+
/** App client id(s) the token must have been issued to. */
|
|
16
|
+
clientId?: string | string[];
|
|
17
|
+
/** Defaults to `'access'`. Use `'id'` for id tokens. */
|
|
18
|
+
tokenUse?: 'access' | 'id';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify a Cognito-signed JWT and return its claims, or `null` when the
|
|
22
|
+
* token is invalid / expired / issued to a different client.
|
|
23
|
+
*
|
|
24
|
+
* Verification covers signature (against the User Pool's JWKS),
|
|
25
|
+
* `exp`/`iat`, `iss`, `token_use`, and `aud`/`client_id` (when
|
|
26
|
+
* `clientId` is provided).
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyAndDecode(token: string, options: VerifyOptions): Promise<Record<string, unknown> | null>;
|
|
29
|
+
/** Test-only — drop the cached verifiers between vitest runs. */
|
|
30
|
+
export declare function _resetVerifierCacheForTesting(): void;
|
|
31
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/server/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC5B;AAcD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAiBzC;AAED,iEAAiE;AACjE,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT verification against a Cognito User Pool's JWKS.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `aws-jwt-verify`'s `CognitoJwtVerifier` so consumers don't take
|
|
5
|
+
* a direct dependency on it. The verifier is cached per
|
|
6
|
+
* `(userPoolId, tokenUse, clientId)` tuple so the JWKS is fetched once
|
|
7
|
+
* per cold start.
|
|
8
|
+
*
|
|
9
|
+
* Use this when API Gateway is NOT fronting your Lambda with a Cognito
|
|
10
|
+
* Authorizer — for example, cookie-based sessions where the Lambda
|
|
11
|
+
* itself reads the cookie and verifies the token.
|
|
12
|
+
*/
|
|
13
|
+
import { CognitoJwtVerifier } from 'aws-jwt-verify';
|
|
14
|
+
const verifierCache = new Map();
|
|
15
|
+
function cacheKey(opts) {
|
|
16
|
+
const tokenUse = opts.tokenUse ?? 'access';
|
|
17
|
+
const clientId = Array.isArray(opts.clientId)
|
|
18
|
+
? opts.clientId.join(',')
|
|
19
|
+
: (opts.clientId ?? 'null');
|
|
20
|
+
return `${opts.userPoolId}|${tokenUse}|${clientId}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify a Cognito-signed JWT and return its claims, or `null` when the
|
|
24
|
+
* token is invalid / expired / issued to a different client.
|
|
25
|
+
*
|
|
26
|
+
* Verification covers signature (against the User Pool's JWKS),
|
|
27
|
+
* `exp`/`iat`, `iss`, `token_use`, and `aud`/`client_id` (when
|
|
28
|
+
* `clientId` is provided).
|
|
29
|
+
*/
|
|
30
|
+
export async function verifyAndDecode(token, options) {
|
|
31
|
+
const key = cacheKey(options);
|
|
32
|
+
let verifier = verifierCache.get(key);
|
|
33
|
+
if (!verifier) {
|
|
34
|
+
verifier = CognitoJwtVerifier.create({
|
|
35
|
+
userPoolId: options.userPoolId,
|
|
36
|
+
tokenUse: options.tokenUse ?? 'access',
|
|
37
|
+
clientId: options.clientId ?? null,
|
|
38
|
+
});
|
|
39
|
+
verifierCache.set(key, verifier);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const payload = await verifier.verify(token);
|
|
43
|
+
return payload;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Test-only — drop the cached verifiers between vitest runs. */
|
|
50
|
+
export function _resetVerifierCacheForTesting() {
|
|
51
|
+
verifierCache.clear();
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/server/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAYpD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAoB,CAAC;AAElD,SAAS,QAAQ,CAAC,IAAmB;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC;IAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3C,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QACzB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;IAC9B,OAAO,GAAG,IAAI,CAAC,UAAU,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAa,EACb,OAAsB;IAEtB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC;YACnC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,QAAQ;YACtC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;SACnC,CAAC,CAAC;QACH,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,OAAkC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,6BAA6B;IAC3C,aAAa,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC"}
|
package/dist/session/index.d.ts
CHANGED
|
@@ -27,28 +27,8 @@ export declare function decodeTokenUnsafe(token: string): Record<string, unknown
|
|
|
27
27
|
* @deprecated Use `decodeTokenUnsafe` or `verifyAndDecode`.
|
|
28
28
|
*/
|
|
29
29
|
export declare const decodeToken: typeof decodeTokenUnsafe;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* token is invalid/expired/untrusted. Uses `aws-jwt-verify` under the hood,
|
|
33
|
-
* which is lazily imported so the dependency is optional.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```ts
|
|
37
|
-
* const claims = await verifyAndDecode(token, {
|
|
38
|
-
* userPoolId: process.env.COGNITO_USER_POOL_ID!,
|
|
39
|
-
* clientId: process.env.COGNITO_CLIENT_ID!,
|
|
40
|
-
* tokenUse: 'access',
|
|
41
|
-
* });
|
|
42
|
-
* if (!claims) throw new UnauthorizedError();
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* Requires `aws-jwt-verify` to be installed at the consumer site.
|
|
46
|
-
*/
|
|
47
|
-
export declare function verifyAndDecode(token: string, options: {
|
|
48
|
-
userPoolId: string;
|
|
49
|
-
clientId?: string | string[];
|
|
50
|
-
tokenUse?: 'access' | 'id';
|
|
51
|
-
}): Promise<Record<string, unknown> | null>;
|
|
30
|
+
export { verifyAndDecode } from '../server/verify.js';
|
|
31
|
+
export type { VerifyOptions } from '../server/verify.js';
|
|
52
32
|
/**
|
|
53
33
|
* Extract user from ID token claims.
|
|
54
34
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,mBAAmB,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAW/E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,0BAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,mBAAmB,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAW/E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,0BAAoB,CAAC;AAK7C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAwBjE;AAuDD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAKzD"}
|
package/dist/session/index.js
CHANGED
|
@@ -38,47 +38,10 @@ export function decodeTokenUnsafe(token) {
|
|
|
38
38
|
* @deprecated Use `decodeTokenUnsafe` or `verifyAndDecode`.
|
|
39
39
|
*/
|
|
40
40
|
export const decodeToken = decodeTokenUnsafe;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* const claims = await verifyAndDecode(token, {
|
|
49
|
-
* userPoolId: process.env.COGNITO_USER_POOL_ID!,
|
|
50
|
-
* clientId: process.env.COGNITO_CLIENT_ID!,
|
|
51
|
-
* tokenUse: 'access',
|
|
52
|
-
* });
|
|
53
|
-
* if (!claims) throw new UnauthorizedError();
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* Requires `aws-jwt-verify` to be installed at the consumer site.
|
|
57
|
-
*/
|
|
58
|
-
export async function verifyAndDecode(token, options) {
|
|
59
|
-
let CognitoJwtVerifier;
|
|
60
|
-
try {
|
|
61
|
-
// Lazy, peer-optional import — consumers only pay the cost if they use it.
|
|
62
|
-
({ CognitoJwtVerifier } = await import(
|
|
63
|
-
/* @vite-ignore */ 'aws-jwt-verify'));
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
throw new Error("verifyAndDecode requires 'aws-jwt-verify'. " +
|
|
67
|
-
"Install it with: pnpm add aws-jwt-verify");
|
|
68
|
-
}
|
|
69
|
-
const verifier = CognitoJwtVerifier.create({
|
|
70
|
-
userPoolId: options.userPoolId,
|
|
71
|
-
tokenUse: options.tokenUse ?? 'access',
|
|
72
|
-
clientId: options.clientId ?? null,
|
|
73
|
-
});
|
|
74
|
-
try {
|
|
75
|
-
const payload = await verifier.verify(token);
|
|
76
|
-
return payload;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
41
|
+
// `verifyAndDecode` now lives in `../server/verify.ts` and is re-exported
|
|
42
|
+
// from the package root for backward compatibility. Server-side consumers
|
|
43
|
+
// should import from `@venturekit/auth/server` instead.
|
|
44
|
+
export { verifyAndDecode } from '../server/verify.js';
|
|
82
45
|
/**
|
|
83
46
|
* Extract user from ID token claims.
|
|
84
47
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venturekit/auth",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.20260429113801",
|
|
4
4
|
"description": "Authentication and authorization for VentureKit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,12 +22,28 @@
|
|
|
22
22
|
".": {
|
|
23
23
|
"import": "./dist/index.js",
|
|
24
24
|
"types": "./dist/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./server": {
|
|
27
|
+
"import": "./dist/server/index.js",
|
|
28
|
+
"types": "./dist/server/index.d.ts"
|
|
25
29
|
}
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@venturekit/core": "0.0.0-dev.
|
|
32
|
+
"@venturekit/core": "0.0.0-dev.20260429113801",
|
|
33
|
+
"@aws-sdk/client-cognito-identity-provider": "^3.668.0",
|
|
34
|
+
"aws-jwt-verify": "^4.0.1"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@venturekit/runtime": "0.0.0-dev.20260429113801"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@venturekit/runtime": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
29
43
|
},
|
|
30
44
|
"devDependencies": {
|
|
45
|
+
"@venturekit/runtime": "0.0.0-dev.20260429113801",
|
|
46
|
+
"@types/aws-lambda": "^8.10.131",
|
|
31
47
|
"@types/node": "^25.6.0",
|
|
32
48
|
"typescript": "^5.3.0"
|
|
33
49
|
},
|