@valentine-efagene/qshelter-common 2.0.50 → 2.0.51
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.
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Authentication context injected by API Gateway Lambda Authorizer.
|
|
4
|
+
* Services should NEVER trust client-supplied user IDs directly.
|
|
5
|
+
*
|
|
6
|
+
* In production:
|
|
7
|
+
* - API Gateway calls Lambda Authorizer with JWT
|
|
8
|
+
* - Authorizer validates token and returns context
|
|
9
|
+
* - Gateway injects context into event.requestContext.authorizer
|
|
10
|
+
* - serverless-http makes this available as req.requestContext.authorizer
|
|
11
|
+
*
|
|
12
|
+
* In tests:
|
|
13
|
+
* - We simulate the authorizer by setting x-authorizer-* headers
|
|
14
|
+
* - These headers are ONLY set by test harness, never by real clients
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthContext {
|
|
17
|
+
userId: string;
|
|
18
|
+
tenantId: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
roles?: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extracts auth context from API Gateway authorizer.
|
|
24
|
+
*
|
|
25
|
+
* Priority:
|
|
26
|
+
* 1. Production: requestContext.authorizer (set by API Gateway)
|
|
27
|
+
* 2. Test/Dev: x-authorizer-* headers (set by test harness)
|
|
28
|
+
*
|
|
29
|
+
* @param req Express request object
|
|
30
|
+
* @returns AuthContext or null if not authenticated
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractAuthContext(req: Request): AuthContext | null;
|
|
33
|
+
/**
|
|
34
|
+
* Middleware that requires authenticated context.
|
|
35
|
+
* Rejects requests without valid authorizer context.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* app.use('/api', requireAuth, apiRouter);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function requireAuth(req: Request, res: Response, next: NextFunction): Response<any, Record<string, any>> | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Helper to get auth context from request (after requireAuth middleware).
|
|
45
|
+
* Throws if auth context is missing.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* router.get('/profile', (req, res) => {
|
|
50
|
+
* const { userId, tenantId } = getAuthContext(req);
|
|
51
|
+
* // ...
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function getAuthContext(req: Request): AuthContext;
|
|
56
|
+
/**
|
|
57
|
+
* Test helper to generate authorizer headers.
|
|
58
|
+
* Use this in tests to simulate API Gateway authorizer context.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const response = await request(app)
|
|
63
|
+
* .post('/users')
|
|
64
|
+
* .set(authHeaders(userId, tenantId))
|
|
65
|
+
* .send({ name: 'John' });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function authHeaders(userId: string, tenantId: string, extras?: {
|
|
69
|
+
email?: string;
|
|
70
|
+
roles?: string[];
|
|
71
|
+
}): Record<string, string>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts auth context from API Gateway authorizer.
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. Production: requestContext.authorizer (set by API Gateway)
|
|
6
|
+
* 2. Test/Dev: x-authorizer-* headers (set by test harness)
|
|
7
|
+
*
|
|
8
|
+
* @param req Express request object
|
|
9
|
+
* @returns AuthContext or null if not authenticated
|
|
10
|
+
*/
|
|
11
|
+
export function extractAuthContext(req) {
|
|
12
|
+
const lambdaReq = req;
|
|
13
|
+
// Production: API Gateway Lambda integration populates requestContext
|
|
14
|
+
const authorizer = lambdaReq.requestContext?.authorizer;
|
|
15
|
+
if (authorizer?.userId && authorizer?.tenantId) {
|
|
16
|
+
return {
|
|
17
|
+
userId: authorizer.userId,
|
|
18
|
+
tenantId: authorizer.tenantId,
|
|
19
|
+
email: authorizer.email,
|
|
20
|
+
roles: authorizer.roles ? JSON.parse(authorizer.roles) : [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Test/Development: Simulated authorizer headers
|
|
24
|
+
// These headers should only be set by test harness or local dev proxy
|
|
25
|
+
const userId = req.headers['x-authorizer-user-id'];
|
|
26
|
+
const tenantId = req.headers['x-authorizer-tenant-id'];
|
|
27
|
+
if (userId && tenantId) {
|
|
28
|
+
const rolesHeader = req.headers['x-authorizer-roles'];
|
|
29
|
+
return {
|
|
30
|
+
userId,
|
|
31
|
+
tenantId,
|
|
32
|
+
email: req.headers['x-authorizer-email'],
|
|
33
|
+
roles: rolesHeader ? JSON.parse(rolesHeader) : [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Middleware that requires authenticated context.
|
|
40
|
+
* Rejects requests without valid authorizer context.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* app.use('/api', requireAuth, apiRouter);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function requireAuth(req, res, next) {
|
|
48
|
+
const auth = extractAuthContext(req);
|
|
49
|
+
if (!auth) {
|
|
50
|
+
return res.status(401).json({ error: 'Unauthorized - missing auth context' });
|
|
51
|
+
}
|
|
52
|
+
// Attach to request for downstream use
|
|
53
|
+
req.auth = auth;
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Helper to get auth context from request (after requireAuth middleware).
|
|
58
|
+
* Throws if auth context is missing.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* router.get('/profile', (req, res) => {
|
|
63
|
+
* const { userId, tenantId } = getAuthContext(req);
|
|
64
|
+
* // ...
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function getAuthContext(req) {
|
|
69
|
+
const lambdaReq = req;
|
|
70
|
+
// First check if middleware attached it
|
|
71
|
+
if (lambdaReq.auth) {
|
|
72
|
+
return lambdaReq.auth;
|
|
73
|
+
}
|
|
74
|
+
// Otherwise try to extract it
|
|
75
|
+
const auth = extractAuthContext(req);
|
|
76
|
+
if (!auth) {
|
|
77
|
+
throw new Error('Auth context not found. Ensure requireAuth middleware is applied or request has valid auth headers.');
|
|
78
|
+
}
|
|
79
|
+
return auth;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Test helper to generate authorizer headers.
|
|
83
|
+
* Use this in tests to simulate API Gateway authorizer context.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const response = await request(app)
|
|
88
|
+
* .post('/users')
|
|
89
|
+
* .set(authHeaders(userId, tenantId))
|
|
90
|
+
* .send({ name: 'John' });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function authHeaders(userId, tenantId, extras) {
|
|
94
|
+
return {
|
|
95
|
+
'x-authorizer-user-id': userId,
|
|
96
|
+
'x-authorizer-tenant-id': tenantId,
|
|
97
|
+
...(extras?.email && { 'x-authorizer-email': extras.email }),
|
|
98
|
+
...(extras?.roles && { 'x-authorizer-roles': JSON.stringify(extras.roles) }),
|
|
99
|
+
};
|
|
100
|
+
}
|
package/package.json
CHANGED