@valentine-efagene/qshelter-common 2.0.135 → 2.0.136
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.
|
@@ -7,7 +7,7 @@ import { Request, Response, NextFunction } from 'express';
|
|
|
7
7
|
* - API Gateway calls Lambda Authorizer with JWT
|
|
8
8
|
* - Authorizer validates token and returns context
|
|
9
9
|
* - Gateway injects context into event.requestContext.authorizer
|
|
10
|
-
* - serverless-
|
|
10
|
+
* - @codegenie/serverless-express exposes this via getCurrentInvoke()
|
|
11
11
|
*
|
|
12
12
|
* In tests:
|
|
13
13
|
* - We simulate the authorizer by setting x-authorizer-* headers
|
|
@@ -23,9 +23,10 @@ export interface AuthContext {
|
|
|
23
23
|
* Extracts auth context from API Gateway authorizer or JWT token.
|
|
24
24
|
*
|
|
25
25
|
* Priority:
|
|
26
|
-
* 1.
|
|
27
|
-
* 2.
|
|
28
|
-
* 3.
|
|
26
|
+
* 1. getCurrentInvoke() from @codegenie/serverless-express (preferred for Lambda)
|
|
27
|
+
* 2. HTTP API v2 simple response: requestContext.authorizer.lambda (enableSimpleResponses=true)
|
|
28
|
+
* 3. REST API / HTTP API: requestContext.authorizer (enableSimpleResponses=false)
|
|
29
|
+
* 4. Fallback: Decode JWT from Authorization header (LocalStack/dev/tests)
|
|
29
30
|
*
|
|
30
31
|
* In production, the Lambda Authorizer validates the JWT and injects context.
|
|
31
32
|
* In LocalStack (no authorizer), we decode the JWT directly since it contains
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
// Try to import getCurrentInvoke from @codegenie/serverless-express
|
|
2
|
+
// This is optional - will fallback to other methods if not available
|
|
3
|
+
let getCurrentInvoke = null;
|
|
4
|
+
try {
|
|
5
|
+
// Dynamic import to avoid hard dependency
|
|
6
|
+
const serverlessExpress = require('@codegenie/serverless-express');
|
|
7
|
+
getCurrentInvoke = serverlessExpress.getCurrentInvoke;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Package not available - will use fallback methods
|
|
11
|
+
}
|
|
1
12
|
/**
|
|
2
13
|
* Safely decode JWT payload without verification.
|
|
3
14
|
* Used to extract claims like roles when authorizer context isn't available.
|
|
@@ -19,9 +30,10 @@ function decodeJwtPayload(token) {
|
|
|
19
30
|
* Extracts auth context from API Gateway authorizer or JWT token.
|
|
20
31
|
*
|
|
21
32
|
* Priority:
|
|
22
|
-
* 1.
|
|
23
|
-
* 2.
|
|
24
|
-
* 3.
|
|
33
|
+
* 1. getCurrentInvoke() from @codegenie/serverless-express (preferred for Lambda)
|
|
34
|
+
* 2. HTTP API v2 simple response: requestContext.authorizer.lambda (enableSimpleResponses=true)
|
|
35
|
+
* 3. REST API / HTTP API: requestContext.authorizer (enableSimpleResponses=false)
|
|
36
|
+
* 4. Fallback: Decode JWT from Authorization header (LocalStack/dev/tests)
|
|
25
37
|
*
|
|
26
38
|
* In production, the Lambda Authorizer validates the JWT and injects context.
|
|
27
39
|
* In LocalStack (no authorizer), we decode the JWT directly since it contains
|
|
@@ -31,6 +43,34 @@ function decodeJwtPayload(token) {
|
|
|
31
43
|
* @returns AuthContext or null if not authenticated
|
|
32
44
|
*/
|
|
33
45
|
export function extractAuthContext(req) {
|
|
46
|
+
// Method 1: Use getCurrentInvoke() from @codegenie/serverless-express
|
|
47
|
+
// This is the most reliable method when running in Lambda with this package
|
|
48
|
+
if (getCurrentInvoke) {
|
|
49
|
+
const { event } = getCurrentInvoke();
|
|
50
|
+
if (event?.requestContext?.authorizer) {
|
|
51
|
+
const authorizer = event.requestContext.authorizer;
|
|
52
|
+
// HTTP API v2 with enableSimpleResponses=true: context is under authorizer.lambda
|
|
53
|
+
const lambdaContext = authorizer.lambda;
|
|
54
|
+
if (lambdaContext?.userId && lambdaContext?.tenantId) {
|
|
55
|
+
return {
|
|
56
|
+
userId: lambdaContext.userId,
|
|
57
|
+
tenantId: lambdaContext.tenantId,
|
|
58
|
+
email: lambdaContext.email,
|
|
59
|
+
roles: lambdaContext.roles ? JSON.parse(lambdaContext.roles) : [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// REST API / HTTP API with enableSimpleResponses=false: context is directly on authorizer
|
|
63
|
+
if (authorizer.userId && authorizer.tenantId) {
|
|
64
|
+
return {
|
|
65
|
+
userId: authorizer.userId,
|
|
66
|
+
tenantId: authorizer.tenantId,
|
|
67
|
+
email: authorizer.email,
|
|
68
|
+
roles: authorizer.roles ? JSON.parse(authorizer.roles) : [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Method 2: Try req.requestContext (for packages that populate this)
|
|
34
74
|
const lambdaReq = req;
|
|
35
75
|
const authorizer = lambdaReq.requestContext?.authorizer;
|
|
36
76
|
// HTTP API v2 with enableSimpleResponses=true: context is under authorizer.lambda
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// Try to import getCurrentInvoke from @codegenie/serverless-express
|
|
2
|
+
let getCurrentInvoke = null;
|
|
3
|
+
try {
|
|
4
|
+
const serverlessExpress = require('@codegenie/serverless-express');
|
|
5
|
+
getCurrentInvoke = serverlessExpress.getCurrentInvoke;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// Package not available
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
11
|
* Request logging middleware that logs method, path, status code, and duration.
|
|
3
12
|
* Logs in JSON format for easy parsing by log aggregation tools.
|
|
@@ -7,16 +16,30 @@
|
|
|
7
16
|
export function requestLogger(req, res, next) {
|
|
8
17
|
const start = Date.now();
|
|
9
18
|
// Debug: Log authorizer context structure
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
const lambdaReq = req;
|
|
12
|
-
const authorizer = lambdaReq.apiGateway?.event?.requestContext?.authorizer;
|
|
13
19
|
if (process.env.DEBUG_AUTH === 'true' || process.env.NODE_ENV !== 'production') {
|
|
20
|
+
let authorizer = null;
|
|
21
|
+
let source = 'none';
|
|
22
|
+
// Try getCurrentInvoke first (preferred for @codegenie/serverless-express)
|
|
23
|
+
if (getCurrentInvoke) {
|
|
24
|
+
const { event } = getCurrentInvoke();
|
|
25
|
+
if (event?.requestContext?.authorizer) {
|
|
26
|
+
authorizer = event.requestContext.authorizer;
|
|
27
|
+
source = 'getCurrentInvoke';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Fallback to req.apiGateway (for other packages)
|
|
31
|
+
if (!authorizer) {
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
const lambdaReq = req;
|
|
34
|
+
if (lambdaReq.apiGateway?.event?.requestContext?.authorizer) {
|
|
35
|
+
authorizer = lambdaReq.apiGateway.event.requestContext.authorizer;
|
|
36
|
+
source = 'req.apiGateway';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
14
39
|
console.log(JSON.stringify({
|
|
15
40
|
type: 'auth_debug',
|
|
16
41
|
path: req.path,
|
|
17
|
-
|
|
18
|
-
hasEvent: !!lambdaReq.apiGateway?.event,
|
|
19
|
-
hasRequestContext: !!lambdaReq.apiGateway?.event?.requestContext,
|
|
42
|
+
source,
|
|
20
43
|
hasAuthorizer: !!authorizer,
|
|
21
44
|
authorizerKeys: authorizer ? Object.keys(authorizer) : [],
|
|
22
45
|
authorizer: authorizer,
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { AppError } from '../utils/errors';
|
|
2
2
|
import { createTenantPrisma, } from '../prisma/tenant';
|
|
3
|
+
// Try to import getCurrentInvoke from @codegenie/serverless-express
|
|
4
|
+
let getCurrentInvoke = null;
|
|
5
|
+
try {
|
|
6
|
+
const serverlessExpress = require('@codegenie/serverless-express');
|
|
7
|
+
getCurrentInvoke = serverlessExpress.getCurrentInvoke;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Package not available - will use fallback methods
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* Creates a tenant middleware that extracts tenant context from the request
|
|
5
14
|
* and optionally creates a tenant-scoped Prisma client.
|
|
@@ -20,12 +29,25 @@ export function createTenantMiddleware(options) {
|
|
|
20
29
|
const { prisma, createScopedClient = true } = options;
|
|
21
30
|
return function tenantMiddleware(req, res, next) {
|
|
22
31
|
try {
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
let authorizerContext = null;
|
|
33
|
+
// 1. Try getCurrentInvoke() from @codegenie/serverless-express (preferred)
|
|
34
|
+
if (getCurrentInvoke) {
|
|
35
|
+
const { event } = getCurrentInvoke();
|
|
36
|
+
if (event?.requestContext?.authorizer) {
|
|
37
|
+
const authorizer = event.requestContext.authorizer;
|
|
38
|
+
// HTTP API v2 with enableSimpleResponses=true nests context under authorizer.lambda
|
|
39
|
+
authorizerContext = authorizer.lambda || authorizer;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 2. Fall back to req.apiGateway (for packages that populate this)
|
|
43
|
+
if (!authorizerContext) {
|
|
44
|
+
const authorizer = req.apiGateway?.event?.requestContext?.authorizer;
|
|
45
|
+
if (authorizer) {
|
|
46
|
+
const lambdaContext = authorizer.lambda;
|
|
47
|
+
authorizerContext = lambdaContext || authorizer;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 3. Fall back to x-authorizer-* headers (test/development)
|
|
29
51
|
const headers = req.headers;
|
|
30
52
|
const tenantId = authorizerContext?.tenantId || headers['x-authorizer-tenant-id'];
|
|
31
53
|
const userId = authorizerContext?.userId || headers['x-authorizer-user-id'];
|
package/package.json
CHANGED