@empire-builder-kit/runtime 0.0.1-alpha.8 → 1.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +5 -0
  3. package/README.md +14 -23
  4. package/package.json +53 -97
  5. package/src/auth-backend.d.ts +44 -0
  6. package/src/auth-backend.js +158 -0
  7. package/src/auth-backend.js.map +1 -0
  8. package/src/auth-session.d.ts +175 -0
  9. package/src/auth-session.js +181 -0
  10. package/src/auth-session.js.map +1 -0
  11. package/src/auth-web.d.ts +79 -0
  12. package/src/auth-web.js +153 -0
  13. package/src/auth-web.js.map +1 -0
  14. package/src/auth.d.ts +143 -0
  15. package/src/auth.js +233 -0
  16. package/src/auth.js.map +1 -0
  17. package/{dist → src}/config.d.ts +0 -1
  18. package/{dist → src}/config.js +9 -21
  19. package/src/config.js.map +1 -0
  20. package/src/events.d.ts +74 -0
  21. package/src/events.js +149 -0
  22. package/src/events.js.map +1 -0
  23. package/{dist → src}/frontend.d.ts +1 -2
  24. package/src/frontend.js +62 -0
  25. package/src/frontend.js.map +1 -0
  26. package/src/hosts.d.ts +9 -0
  27. package/src/hosts.js +38 -0
  28. package/src/hosts.js.map +1 -0
  29. package/src/index.d.ts +15 -0
  30. package/src/index.js +30 -0
  31. package/src/index.js.map +1 -0
  32. package/src/lambda.d.ts +45 -0
  33. package/src/lambda.js +117 -0
  34. package/src/lambda.js.map +1 -0
  35. package/{dist → src}/logging.d.ts +0 -1
  36. package/src/logging.js +113 -0
  37. package/src/logging.js.map +1 -0
  38. package/{dist → src}/observability.d.ts +18 -1
  39. package/{dist → src}/observability.js +72 -62
  40. package/src/observability.js.map +1 -0
  41. package/{dist → src}/request-context.d.ts +12 -2
  42. package/src/request-context.js +68 -0
  43. package/src/request-context.js.map +1 -0
  44. package/{dist → src}/response.d.ts +0 -1
  45. package/src/response.js +66 -0
  46. package/src/response.js.map +1 -0
  47. package/dist/auth-backend.d.ts +0 -18
  48. package/dist/auth-backend.d.ts.map +0 -1
  49. package/dist/auth-backend.js +0 -57
  50. package/dist/auth-web.d.ts +0 -18
  51. package/dist/auth-web.d.ts.map +0 -1
  52. package/dist/auth-web.js +0 -32
  53. package/dist/auth.d.ts +0 -68
  54. package/dist/auth.d.ts.map +0 -1
  55. package/dist/auth.js +0 -107
  56. package/dist/config.d.ts.map +0 -1
  57. package/dist/frontend.d.ts.map +0 -1
  58. package/dist/frontend.js +0 -64
  59. package/dist/index.d.ts +0 -12
  60. package/dist/index.d.ts.map +0 -1
  61. package/dist/index.js +0 -23
  62. package/dist/logging.d.ts.map +0 -1
  63. package/dist/logging.js +0 -129
  64. package/dist/observability.d.ts.map +0 -1
  65. package/dist/request-context.d.ts.map +0 -1
  66. package/dist/request-context.js +0 -58
  67. package/dist/response.d.ts.map +0 -1
  68. package/dist/response.js +0 -66
@@ -0,0 +1,158 @@
1
+ import { __awaiter } from "tslib";
2
+ import { assertPrincipalKind, AuthError, authErrorCodes, isAuthError, sessionFromCognitoToken, toPrincipal, } from './auth.js';
3
+ import { createRemoteJWKSet, jwtVerify, } from 'jose';
4
+ function hasExpectedAudience(payload, clientId) {
5
+ return (payload.aud === clientId ||
6
+ (Array.isArray(payload.aud) && payload.aud.includes(clientId)) ||
7
+ payload.client_id === clientId);
8
+ }
9
+ function normalizeGroups(groups) {
10
+ if (!Array.isArray(groups)) {
11
+ return undefined;
12
+ }
13
+ const normalized = groups.filter((group) => typeof group === 'string');
14
+ return normalized.length > 0 ? normalized : undefined;
15
+ }
16
+ function joseErrorCode(error) {
17
+ return typeof error === 'object' &&
18
+ error !== null &&
19
+ 'code' in error &&
20
+ typeof error.code === 'string'
21
+ ? error.code
22
+ : undefined;
23
+ }
24
+ function isJwksAvailabilityError(error) {
25
+ var _a;
26
+ return (error instanceof TypeError ||
27
+ ['ERR_JWKS_INVALID', 'ERR_JWKS_TIMEOUT'].includes((_a = joseErrorCode(error)) !== null && _a !== void 0 ? _a : ''));
28
+ }
29
+ export function createCognitoJwtVerifier(config, options = {}) {
30
+ var _a, _b;
31
+ const clockToleranceSeconds = (_a = options.clockToleranceSeconds) !== null && _a !== void 0 ? _a : 30;
32
+ if (!Number.isFinite(clockToleranceSeconds) || clockToleranceSeconds < 0) {
33
+ throw new Error('JWT clock tolerance must be a non-negative number.');
34
+ }
35
+ const jwks = (_b = options.jwks) !== null && _b !== void 0 ? _b : createRemoteJWKSet(new URL(`${config.issuerUrl}/.well-known/jwks.json`));
36
+ return {
37
+ verify(token) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ var _a, _b;
40
+ const parts = token.split('.');
41
+ if (parts.length !== 3) {
42
+ throw new Error('Invalid JWT format');
43
+ }
44
+ let payload;
45
+ try {
46
+ ({ payload } = yield jwtVerify(token, jwks, {
47
+ algorithms: ['RS256'],
48
+ clockTolerance: clockToleranceSeconds,
49
+ issuer: config.issuerUrl,
50
+ }));
51
+ }
52
+ catch (error) {
53
+ if (isJwksAvailabilityError(error)) {
54
+ const report = (_a = options.onJwksError) !== null && _a !== void 0 ? _a : ((cause) => {
55
+ var _a;
56
+ return console.error('[runtime/auth-backend] Cognito JWKS verification is unavailable.', {
57
+ code: (_a = joseErrorCode(cause)) !== null && _a !== void 0 ? _a : 'ERR_JWKS_FETCH',
58
+ issuer: config.issuerUrl,
59
+ });
60
+ });
61
+ report(error);
62
+ }
63
+ throw error;
64
+ }
65
+ if (!hasExpectedAudience(payload, config.clientId)) {
66
+ throw new Error('Invalid audience');
67
+ }
68
+ if (!payload.sub) {
69
+ throw new Error('Invalid subject');
70
+ }
71
+ if (typeof payload.exp !== 'number') {
72
+ throw new Error('Missing token expiry');
73
+ }
74
+ const tokenUse = (_b = options.tokenUse) !== null && _b !== void 0 ? _b : 'any';
75
+ if (tokenUse !== 'any' && payload.token_use !== tokenUse) {
76
+ throw new Error('Invalid token_use');
77
+ }
78
+ return sessionFromCognitoToken({
79
+ sub: payload.sub,
80
+ aud: payload.aud,
81
+ client_id: payload.client_id,
82
+ email: payload.email,
83
+ 'cognito:username': payload['cognito:username'],
84
+ 'cognito:groups': normalizeGroups(payload['cognito:groups']),
85
+ exp: payload.exp,
86
+ iat: payload.iat,
87
+ scope: payload.scope,
88
+ token_use: payload.token_use,
89
+ });
90
+ });
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * Extracts a Bearer token from an Authorization header. The `Bearer` scheme
96
+ * is matched case-insensitively per RFC 6750, while the token itself is
97
+ * preserved verbatim.
98
+ */
99
+ export function extractBearerToken(authHeader) {
100
+ const match = /^bearer\s+/i.exec(authHeader !== null && authHeader !== void 0 ? authHeader : '');
101
+ if (!match) {
102
+ return null;
103
+ }
104
+ return (authHeader !== null && authHeader !== void 0 ? authHeader : '').slice(match[0].length).trim() || null;
105
+ }
106
+ function getHeader(headers, name) {
107
+ if (!headers) {
108
+ return undefined;
109
+ }
110
+ const lowerName = name.toLowerCase();
111
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === lowerName);
112
+ return entry === null || entry === void 0 ? void 0 : entry[1];
113
+ }
114
+ export function createAuthGuard(options) {
115
+ var _a, _b, _c;
116
+ const verifier = (_a = options.verifier) !== null && _a !== void 0 ? _a : createCognitoJwtVerifier(options.config, Object.assign(Object.assign({}, options.verifierOptions), { tokenUse: (_c = (_b = options.verifierOptions) === null || _b === void 0 ? void 0 : _b.tokenUse) !== null && _c !== void 0 ? _c : 'access' }));
117
+ function authorizeToken(token) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ if (!token) {
120
+ throw new AuthError({
121
+ code: authErrorCodes.unauthenticated,
122
+ message: 'Authentication required',
123
+ status: 401,
124
+ });
125
+ }
126
+ let session;
127
+ try {
128
+ session = yield verifier.verify(token);
129
+ }
130
+ catch (error) {
131
+ if (isAuthError(error)) {
132
+ throw error;
133
+ }
134
+ throw new AuthError({
135
+ code: authErrorCodes.invalidToken,
136
+ message: 'Invalid or expired token',
137
+ status: 401,
138
+ });
139
+ }
140
+ const principal = toPrincipal(session, options.policy);
141
+ if (options.allowedKinds) {
142
+ assertPrincipalKind(principal, options.allowedKinds);
143
+ }
144
+ return principal;
145
+ });
146
+ }
147
+ return {
148
+ authorize(event) {
149
+ return __awaiter(this, void 0, void 0, function* () {
150
+ const token = extractBearerToken(getHeader(event.headers, 'authorization'));
151
+ return authorizeToken(token);
152
+ });
153
+ },
154
+ authorizeToken,
155
+ verifier,
156
+ };
157
+ }
158
+ //# sourceMappingURL=auth-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-backend.js","sourceRoot":"","sources":["../../../../packages/runtime/src/auth-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ import type { AuthenticatedSession } from './auth.js';
2
+ /**
3
+ * Cookie-based token persistence and clock-skew-aware token refresh
4
+ * primitives.
5
+ *
6
+ * Provides a dependency-light way for downstream consumers to persist
7
+ * Cognito tokens in bounded httpOnly cookies, detect when the access
8
+ * token is inside its refresh window, and rotate tokens without pulling a
9
+ * BetterAuth runtime dependency into `@empire-builder-kit/runtime`.
10
+ *
11
+ * The transport is deliberately injected: this module never calls the Cognito
12
+ * token endpoint itself. The consumer supplies a `refresh` callback (a POST to
13
+ * the hosted-UI `/oauth2/token` endpoint, an AWS SDK `InitiateAuth` call, or a
14
+ * fake in tests). Keeping the network out of the runtime keeps the primitive
15
+ * verifiable locally with mocked issuers, matching the rest of the auth
16
+ * surface.
17
+ *
18
+ * A server-side session adapter can replace the browser token plan when a
19
+ * product needs larger claims or centrally revocable sessions.
20
+ */
21
+ export declare const authSessionContract: {
22
+ readonly exportPath: "@empire-builder-kit/runtime/auth-session";
23
+ readonly packageName: "@empire-builder-kit/runtime";
24
+ };
25
+ /**
26
+ * The token material persisted alongside the resolved session. Access and id
27
+ * tokens are short-lived Cognito JWTs; the refresh token is the long-lived
28
+ * secret used to mint new ones. `expiresAt` is the access-token expiry as an
29
+ * epoch-seconds value so refresh-window math never has to re-parse the JWT.
30
+ */
31
+ export interface SessionTokens {
32
+ accessToken: string;
33
+ expiresAt: number;
34
+ idToken?: string;
35
+ refreshToken?: string;
36
+ }
37
+ /**
38
+ * Verified server-side session state plus the token material used by the
39
+ * refresh primitive. This object is deliberately not serialized to a browser
40
+ * cookie; consumers re-derive it from the issuer-signed access token.
41
+ */
42
+ export interface SessionState {
43
+ session: AuthenticatedSession;
44
+ tokens: SessionTokens;
45
+ }
46
+ export declare const MAX_SESSION_TOKEN_COOKIE_BYTES = 3800;
47
+ export declare const DEFAULT_REFRESH_TOKEN_MAX_AGE_SECONDS: number;
48
+ /**
49
+ * Browser cookie options emitted by the token plan.
50
+ */
51
+ export interface SessionCookieOptions {
52
+ httpOnly: boolean;
53
+ maxAge?: number;
54
+ name: string;
55
+ path: string;
56
+ sameSite: 'lax' | 'strict' | 'none';
57
+ secure: boolean;
58
+ }
59
+ export interface SessionTokenCookie extends SessionCookieOptions {
60
+ value: string;
61
+ }
62
+ export interface SessionTokenCookieNames {
63
+ access: string;
64
+ refresh: string;
65
+ }
66
+ export interface CreateSessionTokenCookiePlanOptions {
67
+ namespace: string;
68
+ now?: number;
69
+ refreshMaxAge?: number;
70
+ secure?: boolean;
71
+ }
72
+ export declare function getSessionTokenCookieNames(options: Pick<CreateSessionTokenCookiePlanOptions, 'namespace' | 'secure'>): SessionTokenCookieNames;
73
+ /**
74
+ * Plans independent access and refresh cookies instead of serializing access,
75
+ * ID, refresh, and derived-session data into one value. Each production name
76
+ * uses the browser-enforced `__Host-` prefix (Secure, Path=/, no Domain), and
77
+ * every value is checked below the common 4 KiB per-cookie limit before a
78
+ * response is emitted. Access-token integrity remains issuer/JWT verified;
79
+ * refresh-token validity remains Cognito verified.
80
+ */
81
+ export declare function createSessionTokenCookiePlan(tokens: SessionTokens, options: CreateSessionTokenCookiePlanOptions): SessionTokenCookie[];
82
+ /**
83
+ * Emits expiry cookies with the same host, path, SameSite, and Secure
84
+ * attributes as the session cookies. Browsers otherwise reject deletion of a
85
+ * production `__Host-` cookie when `Secure` is omitted.
86
+ */
87
+ export declare function createSessionTokenCookieDeletionPlan(options: Pick<CreateSessionTokenCookiePlanOptions, 'namespace' | 'secure'>): SessionTokenCookie[];
88
+ export interface RefreshWindowOptions {
89
+ /** Current time as epoch seconds. Defaults to `Date.now() / 1000`. */
90
+ now?: number;
91
+ /**
92
+ * Clock skew allowance in seconds. Treated as if the local clock could be
93
+ * this far behind the issuer, so expiry is evaluated against `now + skew`.
94
+ * Defaults to 30s.
95
+ */
96
+ skewSeconds?: number;
97
+ /**
98
+ * How long before hard expiry the token is considered "expiring" and eligible
99
+ * for a proactive refresh. Defaults to 300s (5 minutes).
100
+ */
101
+ windowSeconds?: number;
102
+ }
103
+ /**
104
+ * True when the access token has already expired (accounting for clock skew).
105
+ */
106
+ export declare function isTokenExpired(expiresAt: number, options?: RefreshWindowOptions): boolean;
107
+ /**
108
+ * True when the access token is inside its refresh window: not necessarily
109
+ * expired yet, but close enough that a consumer should proactively refresh
110
+ * before the next protected call. Clock skew widens the window conservatively.
111
+ */
112
+ export declare function isTokenExpiring(expiresAt: number, options?: RefreshWindowOptions): boolean;
113
+ /**
114
+ * The subset of a Cognito token-endpoint response this refresh path consumes.
115
+ * A refresh-token grant returns a new access token (and usually a new id
116
+ * token) but reuses the caller's refresh token, so `refreshToken` is optional.
117
+ */
118
+ export interface RefreshedTokens {
119
+ accessToken: string;
120
+ expiresIn: number;
121
+ idToken?: string;
122
+ refreshToken?: string;
123
+ }
124
+ /**
125
+ * Injected transport that exchanges a refresh token for new tokens. In the
126
+ * example this POSTs to the Cognito hosted-UI `/oauth2/token` endpoint; in
127
+ * tests it is a fake returning canned tokens. Keeping it injected means this
128
+ * module has no network dependency and stays verifiable with mocked issuers.
129
+ */
130
+ export type RefreshTransport = (refreshToken: string) => Promise<RefreshedTokens>;
131
+ export interface RefreshSessionOptions extends RefreshWindowOptions {
132
+ /**
133
+ * Force a refresh even when the token is not yet inside its window. Used by
134
+ * an explicit "refresh now" path; the default relies on {@link isTokenExpiring}.
135
+ */
136
+ force?: boolean;
137
+ refresh: RefreshTransport;
138
+ /**
139
+ * Rebuilds the resolved {@link AuthenticatedSession} from the freshly minted
140
+ * access token (typically your Cognito JWT verifier's output). The refresh
141
+ * grant only returns tokens, so the session must be re-derived to keep
142
+ * `expiresAt`/claims in sync.
143
+ */
144
+ resolveSession: (tokens: SessionTokens) => Promise<AuthenticatedSession> | AuthenticatedSession;
145
+ }
146
+ export type RefreshSessionResult = {
147
+ payload: SessionState;
148
+ refreshed: true;
149
+ } | {
150
+ payload: SessionState;
151
+ refreshed: false;
152
+ } | {
153
+ reason: 'missing_refresh_token';
154
+ refreshed: false;
155
+ };
156
+ /**
157
+ * The core refresh path for cookie-persisted sessions.
158
+ *
159
+ * When the token is still comfortably valid (and `force` is not set) it takes
160
+ * the steady-state path: it re-derives the session from the persisted access
161
+ * token via {@link RefreshSessionOptions.resolveSession} and returns it with
162
+ * `refreshed: false`. This re-derivation is the security boundary: callers
163
+ * never trust browser-provided session claims and instead run the JWT verifier
164
+ * over the issuer-signed access token.
165
+ *
166
+ * When inside the refresh window it calls the injected transport, re-resolves
167
+ * the session from the new access token, preserves the refresh token if the
168
+ * grant did not rotate it, and returns a fresh payload the caller can
169
+ * persist through the bounded cookie plan. It never throws for the "no refresh token"
170
+ * case — that is a normal terminal state a caller handles by forcing
171
+ * re-authentication — but transport/verify errors propagate (on both the
172
+ * refresh and the steady-state path) so the caller can decide whether to clear
173
+ * the session.
174
+ */
175
+ export declare function refreshSession(payload: SessionState, options: RefreshSessionOptions): Promise<RefreshSessionResult>;
@@ -0,0 +1,181 @@
1
+ import { __awaiter } from "tslib";
2
+ /**
3
+ * Cookie-based token persistence and clock-skew-aware token refresh
4
+ * primitives.
5
+ *
6
+ * Provides a dependency-light way for downstream consumers to persist
7
+ * Cognito tokens in bounded httpOnly cookies, detect when the access
8
+ * token is inside its refresh window, and rotate tokens without pulling a
9
+ * BetterAuth runtime dependency into `@empire-builder-kit/runtime`.
10
+ *
11
+ * The transport is deliberately injected: this module never calls the Cognito
12
+ * token endpoint itself. The consumer supplies a `refresh` callback (a POST to
13
+ * the hosted-UI `/oauth2/token` endpoint, an AWS SDK `InitiateAuth` call, or a
14
+ * fake in tests). Keeping the network out of the runtime keeps the primitive
15
+ * verifiable locally with mocked issuers, matching the rest of the auth
16
+ * surface.
17
+ *
18
+ * A server-side session adapter can replace the browser token plan when a
19
+ * product needs larger claims or centrally revocable sessions.
20
+ */
21
+ export const authSessionContract = {
22
+ exportPath: '@empire-builder-kit/runtime/auth-session',
23
+ packageName: '@empire-builder-kit/runtime',
24
+ };
25
+ export const MAX_SESSION_TOKEN_COOKIE_BYTES = 3800;
26
+ export const DEFAULT_REFRESH_TOKEN_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
27
+ const COOKIE_NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
28
+ export function getSessionTokenCookieNames(options) {
29
+ var _a;
30
+ const namespace = options.namespace.trim();
31
+ if (!COOKIE_NAMESPACE_PATTERN.test(namespace)) {
32
+ throw new Error('[runtime/auth-session] Cookie namespace must start with a lowercase letter or number and contain at most 63 lowercase letters, numbers, or hyphens.');
33
+ }
34
+ const secure = (_a = options.secure) !== null && _a !== void 0 ? _a : true;
35
+ const prefix = secure ? '__Host-ebk-' : 'ebk-';
36
+ return {
37
+ access: `${prefix}${namespace}-access`,
38
+ refresh: `${prefix}${namespace}-refresh`,
39
+ };
40
+ }
41
+ function assertCookieSized(value, label) {
42
+ const bytes = Buffer.byteLength(value, 'utf8');
43
+ if (bytes > MAX_SESSION_TOKEN_COOKIE_BYTES) {
44
+ throw new Error(`[runtime/auth-session] ${label} is ${bytes} bytes and cannot be persisted safely in a browser cookie. Reduce token claims or use a server-side session adapter.`);
45
+ }
46
+ }
47
+ /**
48
+ * Plans independent access and refresh cookies instead of serializing access,
49
+ * ID, refresh, and derived-session data into one value. Each production name
50
+ * uses the browser-enforced `__Host-` prefix (Secure, Path=/, no Domain), and
51
+ * every value is checked below the common 4 KiB per-cookie limit before a
52
+ * response is emitted. Access-token integrity remains issuer/JWT verified;
53
+ * refresh-token validity remains Cognito verified.
54
+ */
55
+ export function createSessionTokenCookiePlan(tokens, options) {
56
+ var _a, _b, _c;
57
+ if (!tokens.accessToken) {
58
+ throw new Error('[runtime/auth-session] An access token is required.');
59
+ }
60
+ if (!Number.isFinite(tokens.expiresAt)) {
61
+ throw new Error('[runtime/auth-session] A finite access-token expiry is required.');
62
+ }
63
+ assertCookieSized(tokens.accessToken, 'Access token');
64
+ if (tokens.refreshToken) {
65
+ assertCookieSized(tokens.refreshToken, 'Refresh token');
66
+ }
67
+ const secure = (_a = options.secure) !== null && _a !== void 0 ? _a : true;
68
+ const names = getSessionTokenCookieNames({
69
+ namespace: options.namespace,
70
+ secure,
71
+ });
72
+ const common = {
73
+ httpOnly: true,
74
+ path: '/',
75
+ sameSite: 'strict',
76
+ secure,
77
+ };
78
+ const now = (_b = options.now) !== null && _b !== void 0 ? _b : Math.floor(Date.now() / 1000);
79
+ const cookies = [
80
+ Object.assign(Object.assign({}, common), { maxAge: Math.max(0, Math.floor(tokens.expiresAt - now)), name: names.access, value: tokens.accessToken }),
81
+ ];
82
+ if (tokens.refreshToken) {
83
+ cookies.push(Object.assign(Object.assign({}, common), { maxAge: (_c = options.refreshMaxAge) !== null && _c !== void 0 ? _c : DEFAULT_REFRESH_TOKEN_MAX_AGE_SECONDS, name: names.refresh, value: tokens.refreshToken }));
84
+ }
85
+ return cookies;
86
+ }
87
+ /**
88
+ * Emits expiry cookies with the same host, path, SameSite, and Secure
89
+ * attributes as the session cookies. Browsers otherwise reject deletion of a
90
+ * production `__Host-` cookie when `Secure` is omitted.
91
+ */
92
+ export function createSessionTokenCookieDeletionPlan(options) {
93
+ var _a;
94
+ const secure = (_a = options.secure) !== null && _a !== void 0 ? _a : true;
95
+ const names = getSessionTokenCookieNames({
96
+ namespace: options.namespace,
97
+ secure,
98
+ });
99
+ return Object.values(names).map((name) => ({
100
+ httpOnly: true,
101
+ maxAge: 0,
102
+ name,
103
+ path: '/',
104
+ sameSite: 'strict',
105
+ secure,
106
+ value: '',
107
+ }));
108
+ }
109
+ function nowSeconds(options) {
110
+ var _a;
111
+ return (_a = options.now) !== null && _a !== void 0 ? _a : Math.floor(Date.now() / 1000);
112
+ }
113
+ /**
114
+ * True when the access token has already expired (accounting for clock skew).
115
+ */
116
+ export function isTokenExpired(expiresAt, options = {}) {
117
+ var _a;
118
+ const skew = (_a = options.skewSeconds) !== null && _a !== void 0 ? _a : 30;
119
+ return nowSeconds(options) + skew >= expiresAt;
120
+ }
121
+ /**
122
+ * True when the access token is inside its refresh window: not necessarily
123
+ * expired yet, but close enough that a consumer should proactively refresh
124
+ * before the next protected call. Clock skew widens the window conservatively.
125
+ */
126
+ export function isTokenExpiring(expiresAt, options = {}) {
127
+ var _a, _b;
128
+ const skew = (_a = options.skewSeconds) !== null && _a !== void 0 ? _a : 30;
129
+ const window = (_b = options.windowSeconds) !== null && _b !== void 0 ? _b : 300;
130
+ return nowSeconds(options) + skew + window >= expiresAt;
131
+ }
132
+ /**
133
+ * The core refresh path for cookie-persisted sessions.
134
+ *
135
+ * When the token is still comfortably valid (and `force` is not set) it takes
136
+ * the steady-state path: it re-derives the session from the persisted access
137
+ * token via {@link RefreshSessionOptions.resolveSession} and returns it with
138
+ * `refreshed: false`. This re-derivation is the security boundary: callers
139
+ * never trust browser-provided session claims and instead run the JWT verifier
140
+ * over the issuer-signed access token.
141
+ *
142
+ * When inside the refresh window it calls the injected transport, re-resolves
143
+ * the session from the new access token, preserves the refresh token if the
144
+ * grant did not rotate it, and returns a fresh payload the caller can
145
+ * persist through the bounded cookie plan. It never throws for the "no refresh token"
146
+ * case — that is a normal terminal state a caller handles by forcing
147
+ * re-authentication — but transport/verify errors propagate (on both the
148
+ * refresh and the steady-state path) so the caller can decide whether to clear
149
+ * the session.
150
+ */
151
+ export function refreshSession(payload, options) {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ var _a, _b, _c;
154
+ const shouldRefresh = options.force === true ||
155
+ isTokenExpiring(payload.tokens.expiresAt, options);
156
+ if (!shouldRefresh) {
157
+ // Steady-state path: never trust caller-held `session` data directly.
158
+ // Re-derive it from the access token through the caller's verifier.
159
+ // Verify errors propagate exactly as on the refresh path.
160
+ const session = yield options.resolveSession(payload.tokens);
161
+ return { payload: { session, tokens: payload.tokens }, refreshed: false };
162
+ }
163
+ const refreshToken = payload.tokens.refreshToken;
164
+ if (!refreshToken) {
165
+ return { reason: 'missing_refresh_token', refreshed: false };
166
+ }
167
+ const refreshed = yield options.refresh(refreshToken);
168
+ const expiresAt = ((_a = options.now) !== null && _a !== void 0 ? _a : Math.floor(Date.now() / 1000)) + refreshed.expiresIn;
169
+ const tokens = {
170
+ accessToken: refreshed.accessToken,
171
+ expiresAt,
172
+ idToken: (_b = refreshed.idToken) !== null && _b !== void 0 ? _b : payload.tokens.idToken,
173
+ // Cognito refresh-token grants reuse the caller's refresh token unless
174
+ // refresh-token rotation is enabled; preserve the old one when absent.
175
+ refreshToken: (_c = refreshed.refreshToken) !== null && _c !== void 0 ? _c : refreshToken,
176
+ };
177
+ const session = yield options.resolveSession(tokens);
178
+ return { payload: { session, tokens }, refreshed: true };
179
+ });
180
+ }
181
+ //# sourceMappingURL=auth-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-session.js","sourceRoot":"","sources":["../../../../packages/runtime/src/auth-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { type AuthPolicy, type AuthSession, type BetterAuthConfig, type Principal, type PrincipalKind } from './auth.js';
2
+ import { type CognitoJwtVerifierOptions } from './auth-backend.js';
3
+ export interface AuthMiddlewareRequest {
4
+ headers: {
5
+ get(name: string): string | null;
6
+ };
7
+ }
8
+ export interface CreateAuthMiddlewareConfigOptions {
9
+ policy?: AuthPolicy;
10
+ verifierOptions?: CognitoJwtVerifierOptions;
11
+ }
12
+ /**
13
+ * Creates Next.js middleware configuration for auth.
14
+ * Validates JWTs from the standard Authorization header.
15
+ *
16
+ * The verifier defaults to `tokenUse: 'access'` (matching `createAuthGuard`),
17
+ * so a Cognito id token — which is audience-scoped to the client and not
18
+ * intended as an API access credential (OAuth 2.0 Security BCP / RFC 9700) —
19
+ * is rejected as a bearer credential. Pass `verifierOptions.tokenUse` to
20
+ * override (e.g. `'any'` or `'id'`).
21
+ */
22
+ export declare function createAuthMiddlewareConfig(config: BetterAuthConfig, options?: CreateAuthMiddlewareConfigOptions): {
23
+ verifier: import("./auth-backend.js").CognitoJwtVerifier;
24
+ getSession: (request: AuthMiddlewareRequest) => Promise<import("./auth.js").AuthenticatedSession | null>;
25
+ /**
26
+ * Resolve a typed principal from the request session. Returns null
27
+ * (never throws) when the token is missing/invalid, no policy is
28
+ * configured, or the session maps to no principal kind.
29
+ */
30
+ getPrincipal(request: AuthMiddlewareRequest): Promise<Principal | null>;
31
+ };
32
+ export interface RouteRule {
33
+ /** Principal kinds allowed on this route; omit to allow any principal. */
34
+ allow?: PrincipalKind[];
35
+ /** Path prefix; '/admin' matches '/admin' and '/admin/*'. */
36
+ path: string;
37
+ /** Public routes skip authentication entirely. */
38
+ public?: boolean;
39
+ }
40
+ export type UnmatchedRouteBehavior = 'allow' | 'deny';
41
+ export type RouteGuardDecision = {
42
+ allowed: true;
43
+ principal?: Principal;
44
+ } | {
45
+ allowed: false;
46
+ reason: 'forbidden' | 'unauthenticated';
47
+ status: 401 | 403;
48
+ };
49
+ export interface RouteGuard {
50
+ check(pathname: string, session: AuthSession | null | undefined): RouteGuardDecision;
51
+ }
52
+ export interface CreateRouteGuardOptions {
53
+ /**
54
+ * How to handle paths with no matching route rule. Defaults to deny so
55
+ * generated middleware fails closed when a protected path is forgotten.
56
+ *
57
+ * Keep the default `deny`. Route matching is case-sensitive (see
58
+ * {@link createRouteGuard}), so setting `allow` on a routing layer that
59
+ * treats paths case-insensitively lets requests like `/Admin` slip past a
60
+ * `/admin` rule and reach a protected tree unguarded.
61
+ */
62
+ defaultBehavior?: UnmatchedRouteBehavior;
63
+ policy: AuthPolicy;
64
+ rules: RouteRule[];
65
+ }
66
+ /**
67
+ * Framework-agnostic route guard for web middleware. Returns a decision
68
+ * object instead of throwing so middleware templates can translate it into
69
+ * redirects or responses. The most specific (longest) matching rule wins;
70
+ * equal-length rules keep their input order; unmatched paths are denied
71
+ * unless `defaultBehavior` is set to `allow`.
72
+ *
73
+ * Path matching is case-sensitive: `/Admin` does not match a `/admin` rule.
74
+ * Under the default `deny` this fails closed (an unmatched `/Admin` is
75
+ * denied). Do not combine `defaultBehavior: 'allow'` with a case-insensitive
76
+ * routing layer, or a case-variant of a protected path can bypass its rule
77
+ * and fall through to `allow`.
78
+ */
79
+ export declare function createRouteGuard(options: CreateRouteGuardOptions): RouteGuard;