serverless-event-orchestrator 2.0.1 → 2.3.0
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 +21 -21
- package/README.md +489 -434
- package/dist/dispatcher.d.ts +6 -1
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +66 -7
- package/dist/dispatcher.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/event-type.enum.d.ts +1 -0
- package/dist/types/event-type.enum.d.ts.map +1 -1
- package/dist/types/event-type.enum.js +1 -0
- package/dist/types/event-type.enum.js.map +1 -1
- package/dist/types/routes.d.ts +6 -0
- package/dist/types/routes.d.ts.map +1 -1
- package/jest.config.js +32 -32
- package/package.json +82 -81
- package/src/dispatcher.ts +586 -519
- package/src/http/body-parser.ts +60 -60
- package/src/http/cors.ts +76 -76
- package/src/http/index.ts +3 -3
- package/src/http/response.ts +209 -209
- package/src/identity/extractor.ts +207 -207
- package/src/identity/index.ts +2 -2
- package/src/identity/jwt-verifier.ts +41 -41
- package/src/index.ts +128 -127
- package/src/middleware/crm-guard.ts +51 -51
- package/src/middleware/index.ts +3 -3
- package/src/middleware/init-tenant-context.ts +59 -59
- package/src/middleware/tenant-guard.ts +54 -54
- package/src/tenant/TenantContext.ts +115 -115
- package/src/tenant/helpers.ts +112 -112
- package/src/tenant/index.ts +21 -21
- package/src/tenant/types.ts +101 -101
- package/src/types/event-type.enum.ts +21 -20
- package/src/types/index.ts +2 -2
- package/src/types/routes.ts +218 -211
- package/src/utils/headers.ts +72 -72
- package/src/utils/index.ts +2 -2
- package/src/utils/path-matcher.ts +84 -84
- package/tests/cors.test.ts +133 -133
- package/tests/dispatcher.test.ts +795 -715
- package/tests/headers.test.ts +99 -99
- package/tests/identity.test.ts +301 -301
- package/tests/middleware/crm-guard.test.ts +69 -69
- package/tests/middleware/init-tenant-context.test.ts +147 -147
- package/tests/middleware/tenant-guard.test.ts +100 -100
- package/tests/path-matcher.test.ts +102 -102
- package/tests/response.test.ts +155 -155
- package/tests/tenant/TenantContext.test.ts +134 -134
- package/tests/tenant/helpers.test.ts +187 -187
- package/tsconfig.json +24 -24
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
import { IdentityContext, JwtVerificationPoolConfig } from '../types/routes.js';
|
|
2
|
-
import { verifyJwt } from './jwt-verifier.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Options for extractIdentity when using JWT verification
|
|
6
|
-
*/
|
|
7
|
-
export interface ExtractIdentityOptions {
|
|
8
|
-
autoExtract?: boolean;
|
|
9
|
-
jwtVerificationConfig?: JwtVerificationPoolConfig;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Extracts identity context from API Gateway authorizer claims
|
|
14
|
-
* Supports multiple API Gateway formats:
|
|
15
|
-
* - REST API with Cognito User Pool Authorizer
|
|
16
|
-
* - HTTP API with JWT Authorizer
|
|
17
|
-
* - REST API with Custom Lambda Authorizer
|
|
18
|
-
* - HTTP API with Lambda Authorizer
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Extracts claims from the authorizer context, handling multiple API Gateway formats
|
|
23
|
-
* @param authorizer - The authorizer object from requestContext
|
|
24
|
-
* @returns Claims object or undefined
|
|
25
|
-
*/
|
|
26
|
-
function extractClaims(authorizer: any): Record<string, any> | undefined {
|
|
27
|
-
if (!authorizer) return undefined;
|
|
28
|
-
|
|
29
|
-
// 1. REST API with Cognito User Pool Authorizer: authorizer.claims
|
|
30
|
-
if (authorizer.claims && typeof authorizer.claims === 'object') {
|
|
31
|
-
return authorizer.claims;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 2. HTTP API with JWT Authorizer: authorizer.jwt.claims
|
|
35
|
-
if (authorizer.jwt?.claims && typeof authorizer.jwt.claims === 'object') {
|
|
36
|
-
return authorizer.jwt.claims;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 3. HTTP API with Lambda Authorizer: authorizer.lambda
|
|
40
|
-
if (authorizer.lambda && typeof authorizer.lambda === 'object') {
|
|
41
|
-
return authorizer.lambda;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 4. REST API with Custom Lambda Authorizer: claims directly in authorizer
|
|
45
|
-
// Check if authorizer has identity-like properties (sub, userId, email, etc.)
|
|
46
|
-
if (hasIdentityProperties(authorizer)) {
|
|
47
|
-
return authorizer;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Checks if an object has identity-like properties
|
|
55
|
-
*/
|
|
56
|
-
function hasIdentityProperties(obj: any): boolean {
|
|
57
|
-
if (!obj || typeof obj !== 'object') return false;
|
|
58
|
-
const identityKeys = ['sub', 'userId', 'user_id', 'email', 'cognito:username', 'iss', 'aud'];
|
|
59
|
-
return identityKeys.some(key => key in obj);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Extracts identity information from the event's authorizer context or Authorization header.
|
|
64
|
-
* When jwtVerificationConfig is provided, JWT signatures are cryptographically verified
|
|
65
|
-
* against the Cognito JWKS endpoint. Without it, identity is only extracted from
|
|
66
|
-
* API Gateway authorizer claims (already verified by API Gateway).
|
|
67
|
-
*
|
|
68
|
-
* @param event - Raw API Gateway event
|
|
69
|
-
* @param optionsOrAutoExtract - Boolean for backwards compatibility, or ExtractIdentityOptions
|
|
70
|
-
* @returns Identity context or undefined if not authenticated
|
|
71
|
-
*/
|
|
72
|
-
export async function extractIdentity(event: any, optionsOrAutoExtract?: boolean | ExtractIdentityOptions): Promise<IdentityContext | undefined> {
|
|
73
|
-
const options: ExtractIdentityOptions = typeof optionsOrAutoExtract === 'boolean'
|
|
74
|
-
? { autoExtract: optionsOrAutoExtract }
|
|
75
|
-
: optionsOrAutoExtract ?? {};
|
|
76
|
-
|
|
77
|
-
const { autoExtract = false, jwtVerificationConfig } = options;
|
|
78
|
-
|
|
79
|
-
const authorizer = event?.requestContext?.authorizer;
|
|
80
|
-
const claims = extractClaims(authorizer);
|
|
81
|
-
|
|
82
|
-
// If authorizer claims exist, API Gateway already verified the token — use them directly
|
|
83
|
-
if (claims) {
|
|
84
|
-
return buildIdentity(claims);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// If autoExtract is enabled, extract identity from Authorization header
|
|
88
|
-
if (autoExtract) {
|
|
89
|
-
const authHeader = event?.headers?.authorization || event?.headers?.Authorization;
|
|
90
|
-
if (authHeader) {
|
|
91
|
-
// If jwtVerificationConfig is provided, verify signature cryptographically
|
|
92
|
-
if (jwtVerificationConfig) {
|
|
93
|
-
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
|
|
94
|
-
const verifiedClaims = await verifyJwt(token, jwtVerificationConfig);
|
|
95
|
-
if (verifiedClaims) {
|
|
96
|
-
return buildIdentity(verifiedClaims);
|
|
97
|
-
}
|
|
98
|
-
// Verification failed → return undefined (caller will return 401)
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// No jwtVerificationConfig → decode-only fallback (no signature verification)
|
|
103
|
-
const decoded = decodeJwtFromHeader(authHeader);
|
|
104
|
-
if (decoded) {
|
|
105
|
-
return buildIdentity(decoded);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Decodes a JWT from the Authorization header without validating signature
|
|
115
|
-
* @param authHeader - The Authorization header value (e.g., "Bearer eyJ...")
|
|
116
|
-
*/
|
|
117
|
-
function decodeJwtFromHeader(authHeader: string): Record<string, any> | undefined {
|
|
118
|
-
try {
|
|
119
|
-
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
|
|
120
|
-
const parts = token.split('.');
|
|
121
|
-
if (parts.length !== 3) return undefined;
|
|
122
|
-
|
|
123
|
-
const payload = parts[1];
|
|
124
|
-
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
125
|
-
const jsonPayload = Buffer.from(base64, 'base64').toString('utf8');
|
|
126
|
-
|
|
127
|
-
return JSON.parse(jsonPayload);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('[SEO] Error decoding JWT from header:', error);
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Builds an IdentityContext from raw claims
|
|
136
|
-
*/
|
|
137
|
-
function buildIdentity(claims: Record<string, any>): IdentityContext {
|
|
138
|
-
return {
|
|
139
|
-
userId: claims.userId || claims.user_id || claims['cognito:username'] || claims.sub,
|
|
140
|
-
email: claims.email,
|
|
141
|
-
groups: parseGroups(claims['cognito:groups'] || claims.groups),
|
|
142
|
-
issuer: claims.iss,
|
|
143
|
-
claims,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Parses Cognito groups from claims
|
|
149
|
-
* Groups can come as a string or array depending on configuration
|
|
150
|
-
*/
|
|
151
|
-
function parseGroups(groups: string | string[] | undefined): string[] {
|
|
152
|
-
if (!groups) return [];
|
|
153
|
-
if (Array.isArray(groups)) return groups;
|
|
154
|
-
|
|
155
|
-
// Cognito sometimes sends groups as a comma-separated string
|
|
156
|
-
return groups.split(',').map(g => g.trim()).filter(Boolean);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Extracts the User Pool ID from the issuer URL
|
|
161
|
-
* @param issuer - Cognito issuer URL (e.g., https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx)
|
|
162
|
-
* @returns User Pool ID or undefined
|
|
163
|
-
*/
|
|
164
|
-
export function extractUserPoolId(issuer: string | undefined): string | undefined {
|
|
165
|
-
if (!issuer) return undefined;
|
|
166
|
-
|
|
167
|
-
// Extract the last segment of the issuer URL
|
|
168
|
-
const parts = issuer.split('/');
|
|
169
|
-
return parts[parts.length - 1];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Validates that the token issuer matches the expected User Pool
|
|
174
|
-
* @param identity - Extracted identity context
|
|
175
|
-
* @param expectedUserPoolId - Expected User Pool ID
|
|
176
|
-
* @returns True if issuer matches
|
|
177
|
-
*/
|
|
178
|
-
export function validateIssuer(identity: IdentityContext | undefined, expectedUserPoolId: string): boolean {
|
|
179
|
-
if (!identity?.issuer) return false;
|
|
180
|
-
|
|
181
|
-
const actualUserPoolId = extractUserPoolId(identity.issuer);
|
|
182
|
-
return actualUserPoolId === expectedUserPoolId;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Checks if the user belongs to any of the specified groups
|
|
187
|
-
* @param identity - Extracted identity context
|
|
188
|
-
* @param allowedGroups - Groups that grant access
|
|
189
|
-
* @returns True if user is in at least one allowed group
|
|
190
|
-
*/
|
|
191
|
-
export function hasAnyGroup(identity: IdentityContext | undefined, allowedGroups: string[]): boolean {
|
|
192
|
-
if (!identity?.groups || identity.groups.length === 0) return false;
|
|
193
|
-
|
|
194
|
-
return allowedGroups.some(group => identity.groups?.includes(group));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Checks if the user belongs to all specified groups
|
|
199
|
-
* @param identity - Extracted identity context
|
|
200
|
-
* @param requiredGroups - Groups required for access
|
|
201
|
-
* @returns True if user is in all required groups
|
|
202
|
-
*/
|
|
203
|
-
export function hasAllGroups(identity: IdentityContext | undefined, requiredGroups: string[]): boolean {
|
|
204
|
-
if (!identity?.groups || identity.groups.length === 0) return false;
|
|
205
|
-
|
|
206
|
-
return requiredGroups.every(group => identity.groups?.includes(group));
|
|
207
|
-
}
|
|
1
|
+
import { IdentityContext, JwtVerificationPoolConfig } from '../types/routes.js';
|
|
2
|
+
import { verifyJwt } from './jwt-verifier.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for extractIdentity when using JWT verification
|
|
6
|
+
*/
|
|
7
|
+
export interface ExtractIdentityOptions {
|
|
8
|
+
autoExtract?: boolean;
|
|
9
|
+
jwtVerificationConfig?: JwtVerificationPoolConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts identity context from API Gateway authorizer claims
|
|
14
|
+
* Supports multiple API Gateway formats:
|
|
15
|
+
* - REST API with Cognito User Pool Authorizer
|
|
16
|
+
* - HTTP API with JWT Authorizer
|
|
17
|
+
* - REST API with Custom Lambda Authorizer
|
|
18
|
+
* - HTTP API with Lambda Authorizer
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extracts claims from the authorizer context, handling multiple API Gateway formats
|
|
23
|
+
* @param authorizer - The authorizer object from requestContext
|
|
24
|
+
* @returns Claims object or undefined
|
|
25
|
+
*/
|
|
26
|
+
function extractClaims(authorizer: any): Record<string, any> | undefined {
|
|
27
|
+
if (!authorizer) return undefined;
|
|
28
|
+
|
|
29
|
+
// 1. REST API with Cognito User Pool Authorizer: authorizer.claims
|
|
30
|
+
if (authorizer.claims && typeof authorizer.claims === 'object') {
|
|
31
|
+
return authorizer.claims;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. HTTP API with JWT Authorizer: authorizer.jwt.claims
|
|
35
|
+
if (authorizer.jwt?.claims && typeof authorizer.jwt.claims === 'object') {
|
|
36
|
+
return authorizer.jwt.claims;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. HTTP API with Lambda Authorizer: authorizer.lambda
|
|
40
|
+
if (authorizer.lambda && typeof authorizer.lambda === 'object') {
|
|
41
|
+
return authorizer.lambda;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 4. REST API with Custom Lambda Authorizer: claims directly in authorizer
|
|
45
|
+
// Check if authorizer has identity-like properties (sub, userId, email, etc.)
|
|
46
|
+
if (hasIdentityProperties(authorizer)) {
|
|
47
|
+
return authorizer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Checks if an object has identity-like properties
|
|
55
|
+
*/
|
|
56
|
+
function hasIdentityProperties(obj: any): boolean {
|
|
57
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
58
|
+
const identityKeys = ['sub', 'userId', 'user_id', 'email', 'cognito:username', 'iss', 'aud'];
|
|
59
|
+
return identityKeys.some(key => key in obj);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extracts identity information from the event's authorizer context or Authorization header.
|
|
64
|
+
* When jwtVerificationConfig is provided, JWT signatures are cryptographically verified
|
|
65
|
+
* against the Cognito JWKS endpoint. Without it, identity is only extracted from
|
|
66
|
+
* API Gateway authorizer claims (already verified by API Gateway).
|
|
67
|
+
*
|
|
68
|
+
* @param event - Raw API Gateway event
|
|
69
|
+
* @param optionsOrAutoExtract - Boolean for backwards compatibility, or ExtractIdentityOptions
|
|
70
|
+
* @returns Identity context or undefined if not authenticated
|
|
71
|
+
*/
|
|
72
|
+
export async function extractIdentity(event: any, optionsOrAutoExtract?: boolean | ExtractIdentityOptions): Promise<IdentityContext | undefined> {
|
|
73
|
+
const options: ExtractIdentityOptions = typeof optionsOrAutoExtract === 'boolean'
|
|
74
|
+
? { autoExtract: optionsOrAutoExtract }
|
|
75
|
+
: optionsOrAutoExtract ?? {};
|
|
76
|
+
|
|
77
|
+
const { autoExtract = false, jwtVerificationConfig } = options;
|
|
78
|
+
|
|
79
|
+
const authorizer = event?.requestContext?.authorizer;
|
|
80
|
+
const claims = extractClaims(authorizer);
|
|
81
|
+
|
|
82
|
+
// If authorizer claims exist, API Gateway already verified the token — use them directly
|
|
83
|
+
if (claims) {
|
|
84
|
+
return buildIdentity(claims);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// If autoExtract is enabled, extract identity from Authorization header
|
|
88
|
+
if (autoExtract) {
|
|
89
|
+
const authHeader = event?.headers?.authorization || event?.headers?.Authorization;
|
|
90
|
+
if (authHeader) {
|
|
91
|
+
// If jwtVerificationConfig is provided, verify signature cryptographically
|
|
92
|
+
if (jwtVerificationConfig) {
|
|
93
|
+
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
|
|
94
|
+
const verifiedClaims = await verifyJwt(token, jwtVerificationConfig);
|
|
95
|
+
if (verifiedClaims) {
|
|
96
|
+
return buildIdentity(verifiedClaims);
|
|
97
|
+
}
|
|
98
|
+
// Verification failed → return undefined (caller will return 401)
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// No jwtVerificationConfig → decode-only fallback (no signature verification)
|
|
103
|
+
const decoded = decodeJwtFromHeader(authHeader);
|
|
104
|
+
if (decoded) {
|
|
105
|
+
return buildIdentity(decoded);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Decodes a JWT from the Authorization header without validating signature
|
|
115
|
+
* @param authHeader - The Authorization header value (e.g., "Bearer eyJ...")
|
|
116
|
+
*/
|
|
117
|
+
function decodeJwtFromHeader(authHeader: string): Record<string, any> | undefined {
|
|
118
|
+
try {
|
|
119
|
+
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
|
|
120
|
+
const parts = token.split('.');
|
|
121
|
+
if (parts.length !== 3) return undefined;
|
|
122
|
+
|
|
123
|
+
const payload = parts[1];
|
|
124
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
125
|
+
const jsonPayload = Buffer.from(base64, 'base64').toString('utf8');
|
|
126
|
+
|
|
127
|
+
return JSON.parse(jsonPayload);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[SEO] Error decoding JWT from header:', error);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Builds an IdentityContext from raw claims
|
|
136
|
+
*/
|
|
137
|
+
function buildIdentity(claims: Record<string, any>): IdentityContext {
|
|
138
|
+
return {
|
|
139
|
+
userId: claims.userId || claims.user_id || claims['cognito:username'] || claims.sub,
|
|
140
|
+
email: claims.email,
|
|
141
|
+
groups: parseGroups(claims['cognito:groups'] || claims.groups),
|
|
142
|
+
issuer: claims.iss,
|
|
143
|
+
claims,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parses Cognito groups from claims
|
|
149
|
+
* Groups can come as a string or array depending on configuration
|
|
150
|
+
*/
|
|
151
|
+
function parseGroups(groups: string | string[] | undefined): string[] {
|
|
152
|
+
if (!groups) return [];
|
|
153
|
+
if (Array.isArray(groups)) return groups;
|
|
154
|
+
|
|
155
|
+
// Cognito sometimes sends groups as a comma-separated string
|
|
156
|
+
return groups.split(',').map(g => g.trim()).filter(Boolean);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Extracts the User Pool ID from the issuer URL
|
|
161
|
+
* @param issuer - Cognito issuer URL (e.g., https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx)
|
|
162
|
+
* @returns User Pool ID or undefined
|
|
163
|
+
*/
|
|
164
|
+
export function extractUserPoolId(issuer: string | undefined): string | undefined {
|
|
165
|
+
if (!issuer) return undefined;
|
|
166
|
+
|
|
167
|
+
// Extract the last segment of the issuer URL
|
|
168
|
+
const parts = issuer.split('/');
|
|
169
|
+
return parts[parts.length - 1];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validates that the token issuer matches the expected User Pool
|
|
174
|
+
* @param identity - Extracted identity context
|
|
175
|
+
* @param expectedUserPoolId - Expected User Pool ID
|
|
176
|
+
* @returns True if issuer matches
|
|
177
|
+
*/
|
|
178
|
+
export function validateIssuer(identity: IdentityContext | undefined, expectedUserPoolId: string): boolean {
|
|
179
|
+
if (!identity?.issuer) return false;
|
|
180
|
+
|
|
181
|
+
const actualUserPoolId = extractUserPoolId(identity.issuer);
|
|
182
|
+
return actualUserPoolId === expectedUserPoolId;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Checks if the user belongs to any of the specified groups
|
|
187
|
+
* @param identity - Extracted identity context
|
|
188
|
+
* @param allowedGroups - Groups that grant access
|
|
189
|
+
* @returns True if user is in at least one allowed group
|
|
190
|
+
*/
|
|
191
|
+
export function hasAnyGroup(identity: IdentityContext | undefined, allowedGroups: string[]): boolean {
|
|
192
|
+
if (!identity?.groups || identity.groups.length === 0) return false;
|
|
193
|
+
|
|
194
|
+
return allowedGroups.some(group => identity.groups?.includes(group));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Checks if the user belongs to all specified groups
|
|
199
|
+
* @param identity - Extracted identity context
|
|
200
|
+
* @param requiredGroups - Groups required for access
|
|
201
|
+
* @returns True if user is in all required groups
|
|
202
|
+
*/
|
|
203
|
+
export function hasAllGroups(identity: IdentityContext | undefined, requiredGroups: string[]): boolean {
|
|
204
|
+
if (!identity?.groups || identity.groups.length === 0) return false;
|
|
205
|
+
|
|
206
|
+
return requiredGroups.every(group => identity.groups?.includes(group));
|
|
207
|
+
}
|
package/src/identity/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './extractor.js';
|
|
2
|
-
export { verifyJwt } from './jwt-verifier.js';
|
|
1
|
+
export * from './extractor.js';
|
|
2
|
+
export { verifyJwt } from './jwt-verifier.js';
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { CognitoJwtVerifier } from 'aws-jwt-verify';
|
|
2
|
-
import type { JwtVerificationPoolConfig } from '../types/routes.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Module-level cache of CognitoJwtVerifier instances keyed by userPoolId.
|
|
6
|
-
* Persists across Lambda warm invocations. Each verifier internally caches JWKS.
|
|
7
|
-
*/
|
|
8
|
-
const verifierCache = new Map<string, ReturnType<typeof CognitoJwtVerifier.create>>();
|
|
9
|
-
|
|
10
|
-
function getVerifier(poolConfig: JwtVerificationPoolConfig) {
|
|
11
|
-
const cacheKey = poolConfig.userPoolId;
|
|
12
|
-
|
|
13
|
-
if (!verifierCache.has(cacheKey)) {
|
|
14
|
-
const verifier = CognitoJwtVerifier.create({
|
|
15
|
-
userPoolId: poolConfig.userPoolId,
|
|
16
|
-
clientId: poolConfig.clientId ?? null,
|
|
17
|
-
tokenUse: poolConfig.tokenUse ?? null,
|
|
18
|
-
});
|
|
19
|
-
verifierCache.set(cacheKey, verifier);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return verifierCache.get(cacheKey)!;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Verifies a JWT token against a Cognito User Pool's JWKS.
|
|
27
|
-
* Returns the verified payload (claims) or undefined if verification fails.
|
|
28
|
-
*/
|
|
29
|
-
export async function verifyJwt(
|
|
30
|
-
token: string,
|
|
31
|
-
poolConfig: JwtVerificationPoolConfig
|
|
32
|
-
): Promise<Record<string, any> | undefined> {
|
|
33
|
-
try {
|
|
34
|
-
const verifier = getVerifier(poolConfig);
|
|
35
|
-
const payload = await verifier.verify(token);
|
|
36
|
-
return payload as unknown as Record<string, any>;
|
|
37
|
-
} catch (error: any) {
|
|
38
|
-
console.error('[SEO] JWT verification failed:', error?.message || error);
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import { CognitoJwtVerifier } from 'aws-jwt-verify';
|
|
2
|
+
import type { JwtVerificationPoolConfig } from '../types/routes.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Module-level cache of CognitoJwtVerifier instances keyed by userPoolId.
|
|
6
|
+
* Persists across Lambda warm invocations. Each verifier internally caches JWKS.
|
|
7
|
+
*/
|
|
8
|
+
const verifierCache = new Map<string, ReturnType<typeof CognitoJwtVerifier.create>>();
|
|
9
|
+
|
|
10
|
+
function getVerifier(poolConfig: JwtVerificationPoolConfig) {
|
|
11
|
+
const cacheKey = poolConfig.userPoolId;
|
|
12
|
+
|
|
13
|
+
if (!verifierCache.has(cacheKey)) {
|
|
14
|
+
const verifier = CognitoJwtVerifier.create({
|
|
15
|
+
userPoolId: poolConfig.userPoolId,
|
|
16
|
+
clientId: poolConfig.clientId ?? null,
|
|
17
|
+
tokenUse: poolConfig.tokenUse ?? null,
|
|
18
|
+
});
|
|
19
|
+
verifierCache.set(cacheKey, verifier);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return verifierCache.get(cacheKey)!;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Verifies a JWT token against a Cognito User Pool's JWKS.
|
|
27
|
+
* Returns the verified payload (claims) or undefined if verification fails.
|
|
28
|
+
*/
|
|
29
|
+
export async function verifyJwt(
|
|
30
|
+
token: string,
|
|
31
|
+
poolConfig: JwtVerificationPoolConfig
|
|
32
|
+
): Promise<Record<string, any> | undefined> {
|
|
33
|
+
try {
|
|
34
|
+
const verifier = getVerifier(poolConfig);
|
|
35
|
+
const payload = await verifier.verify(token);
|
|
36
|
+
return payload as unknown as Record<string, any>;
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
console.error('[SEO] JWT verification failed:', error?.message || error);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|