@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.
- package/LICENSE +201 -21
- package/NOTICE +5 -0
- package/README.md +14 -23
- package/package.json +53 -97
- package/src/auth-backend.d.ts +44 -0
- package/src/auth-backend.js +158 -0
- package/src/auth-backend.js.map +1 -0
- package/src/auth-session.d.ts +175 -0
- package/src/auth-session.js +181 -0
- package/src/auth-session.js.map +1 -0
- package/src/auth-web.d.ts +79 -0
- package/src/auth-web.js +153 -0
- package/src/auth-web.js.map +1 -0
- package/src/auth.d.ts +143 -0
- package/src/auth.js +233 -0
- package/src/auth.js.map +1 -0
- package/{dist → src}/config.d.ts +0 -1
- package/{dist → src}/config.js +9 -21
- package/src/config.js.map +1 -0
- package/src/events.d.ts +74 -0
- package/src/events.js +149 -0
- package/src/events.js.map +1 -0
- package/{dist → src}/frontend.d.ts +1 -2
- package/src/frontend.js +62 -0
- package/src/frontend.js.map +1 -0
- package/src/hosts.d.ts +9 -0
- package/src/hosts.js +38 -0
- package/src/hosts.js.map +1 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +30 -0
- package/src/index.js.map +1 -0
- package/src/lambda.d.ts +45 -0
- package/src/lambda.js +117 -0
- package/src/lambda.js.map +1 -0
- package/{dist → src}/logging.d.ts +0 -1
- package/src/logging.js +113 -0
- package/src/logging.js.map +1 -0
- package/{dist → src}/observability.d.ts +18 -1
- package/{dist → src}/observability.js +72 -62
- package/src/observability.js.map +1 -0
- package/{dist → src}/request-context.d.ts +12 -2
- package/src/request-context.js +68 -0
- package/src/request-context.js.map +1 -0
- package/{dist → src}/response.d.ts +0 -1
- package/src/response.js +66 -0
- package/src/response.js.map +1 -0
- package/dist/auth-backend.d.ts +0 -18
- package/dist/auth-backend.d.ts.map +0 -1
- package/dist/auth-backend.js +0 -57
- package/dist/auth-web.d.ts +0 -18
- package/dist/auth-web.d.ts.map +0 -1
- package/dist/auth-web.js +0 -32
- package/dist/auth.d.ts +0 -68
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -107
- package/dist/config.d.ts.map +0 -1
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js +0 -64
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -23
- package/dist/logging.d.ts.map +0 -1
- package/dist/logging.js +0 -129
- package/dist/observability.d.ts.map +0 -1
- package/dist/request-context.d.ts.map +0 -1
- package/dist/request-context.js +0 -58
- package/dist/response.d.ts.map +0 -1
- 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;
|