@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
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './error-handler';
2
2
  export * from './request-logger';
3
3
  export * from './tenant';
4
+ export * from './auth-context';
@@ -1,3 +1,4 @@
1
1
  export * from './error-handler';
2
2
  export * from './request-logger';
3
3
  export * from './tenant';
4
+ export * from './auth-context';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentine-efagene/qshelter-common",
3
- "version": "2.0.50",
3
+ "version": "2.0.51",
4
4
  "description": "Shared database schemas and utilities for QShelter services",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",