@discover-cloud/shared 1.2.9 → 1.4.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/dist/authorization/index.d.ts +2 -2
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/permission-cache.service.d.ts +104 -16
- package/dist/authorization/permission-cache.service.js +177 -148
- package/dist/authorization/permission-checker.d.ts +30 -0
- package/dist/authorization/permission-checker.js +40 -0
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/request-context.storage.d.ts +21 -0
- package/dist/context/request-context.storage.js +37 -0
- package/dist/contracts/auth-service/account.dto.d.ts +30 -0
- package/dist/contracts/auth-service/account.dto.js +7 -0
- package/dist/contracts/auth-service/auth.dto.d.ts +21 -0
- package/dist/contracts/auth-service/auth.dto.js +10 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/{security → contracts}/index.js +1 -1
- package/dist/contracts/visibility.d.ts +63 -0
- package/dist/contracts/visibility.js +10 -0
- package/dist/dtos/audit-service.types.d.ts +42 -0
- package/dist/dtos/audit-service.types.js +31 -0
- package/dist/dtos/auth-service.dto.d.ts +87 -26
- package/dist/dtos/auth-service.dto.js +4 -0
- package/dist/dtos/cloud-service.dto.d.ts +57 -27
- package/dist/dtos/cloud-service.dto.js +5 -0
- package/dist/dtos/index.d.ts +6 -5
- package/dist/dtos/index.js +1 -0
- package/dist/dtos/insights-service.dto.d.ts +184 -32
- package/dist/dtos/insights-service.dto.js +8 -0
- package/dist/dtos/response.dto.d.ts +12 -83
- package/dist/dtos/response.dto.js +2 -21
- package/dist/dtos/user-service.dto.d.ts +163 -13
- package/dist/enums/domain.enums.d.ts +272 -75
- package/dist/enums/domain.enums.js +329 -102
- package/dist/enums/index.d.ts +2 -2
- package/dist/enums/permissions.enums.d.ts +142 -80
- package/dist/enums/permissions.enums.js +174 -133
- package/dist/errors/app-error.d.ts +13 -18
- package/dist/errors/app-error.js +15 -21
- package/dist/errors/http-errors.d.ts +106 -18
- package/dist/errors/http-errors.js +136 -51
- package/dist/errors/index.d.ts +2 -2
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +1 -0
- package/dist/http/request-context.d.ts +8 -0
- package/dist/http/request-context.js +37 -0
- package/dist/http/service-client.d.ts +93 -40
- package/dist/http/service-client.js +104 -55
- package/dist/index.d.ts +11 -9
- package/dist/index.js +2 -0
- package/dist/jwt/index.d.ts +3 -2
- package/dist/jwt/index.js +2 -1
- package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
- package/dist/jwt/machine-jwt-verifier.js +83 -0
- package/dist/jwt/machine-token-client.d.ts +36 -5
- package/dist/jwt/machine-token-client.js +61 -27
- package/dist/jwt/user-jwt-verifier.d.ts +31 -0
- package/dist/jwt/user-jwt-verifier.js +102 -0
- package/dist/messaging/audit.publisher.d.ts +14 -0
- package/dist/messaging/audit.publisher.js +49 -0
- package/dist/messaging/index.d.ts +2 -0
- package/dist/{dto → messaging}/index.js +2 -3
- package/dist/messaging/rabbitmq.client.d.ts +15 -0
- package/dist/messaging/rabbitmq.client.js +133 -0
- package/dist/middleware/error-handler.middleware.d.ts +8 -28
- package/dist/middleware/error-handler.middleware.js +63 -54
- package/dist/middleware/index.d.ts +10 -8
- package/dist/middleware/index.js +7 -5
- package/dist/middleware/request-context.middleware.d.ts +6 -0
- package/dist/middleware/request-context.middleware.js +48 -0
- package/dist/middleware/require-auth.middleware.d.ts +7 -76
- package/dist/middleware/require-auth.middleware.js +29 -117
- package/dist/middleware/require-machine.middleware.d.ts +13 -0
- package/dist/middleware/require-machine.middleware.js +128 -0
- package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
- package/dist/middleware/require-org-permission-from-body.middleware.js +23 -0
- package/dist/middleware/require-org-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-org-permission.middleware.js +115 -0
- package/dist/middleware/require-platform-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-platform-permission.middleware.js +91 -0
- package/dist/middleware/require-user.middleware.d.ts +14 -0
- package/dist/middleware/require-user.middleware.js +30 -0
- package/dist/middleware/require-workspace-permission.middleware.d.ts +20 -0
- package/dist/middleware/require-workspace-permission.middleware.js +114 -0
- package/dist/middleware/validate.middleware.d.ts +31 -31
- package/dist/middleware/validate.middleware.js +31 -33
- package/dist/types/audit.types.d.ts +39 -0
- package/dist/types/audit.types.js +30 -0
- package/dist/types/express.types.d.ts +92 -124
- package/dist/types/express.types.js +30 -49
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/date.util.d.ts +6 -0
- package/dist/utils/date.util.js +11 -0
- package/dist/utils/env.util.d.ts +6 -0
- package/dist/utils/env.util.js +19 -0
- package/dist/utils/index.d.ts +5 -4
- package/dist/utils/index.js +5 -4
- package/dist/utils/logger.util.d.ts +31 -0
- package/dist/utils/logger.util.js +91 -0
- package/dist/utils/pagination.util.d.ts +6 -0
- package/dist/utils/pagination.util.js +20 -0
- package/dist/utils/request-context.als.d.ts +10 -0
- package/dist/utils/request-context.als.js +5 -0
- package/dist/utils/response.util.d.ts +27 -0
- package/dist/utils/response.util.js +56 -0
- package/dist/utils/slug.util.d.ts +9 -0
- package/dist/utils/slug.util.js +32 -0
- package/dist/utils/url-safety.util.d.ts +6 -0
- package/dist/utils/url-safety.util.js +75 -0
- package/package.json +3 -1
- package/dist/authorization/permissions.d.ts +0 -78
- package/dist/authorization/permissions.js +0 -174
- package/dist/context/access-context.d.ts +0 -10
- package/dist/context/access-context.js +0 -2
- package/dist/dto/auth-service.dtos.d.ts +0 -44
- package/dist/dto/auth-service.dtos.js +0 -2
- package/dist/dto/index.d.ts +0 -3
- package/dist/dto/response.dtos.d.ts +0 -55
- package/dist/dto/response.dtos.js +0 -6
- package/dist/dto/user-service.dtos.d.ts +0 -50
- package/dist/dto/user-service.dtos.js +0 -2
- package/dist/enums/auth-service.enums.d.ts +0 -12
- package/dist/enums/auth-service.enums.js +0 -17
- package/dist/enums/permissions.types.d.ts +0 -12
- package/dist/enums/permissions.types.js +0 -17
- package/dist/enums/user-service.enums.d.ts +0 -32
- package/dist/enums/user-service.enums.js +0 -41
- package/dist/internal/index.d.ts +0 -4
- package/dist/internal/index.js +0 -20
- package/dist/internal/internal-jwt.service.d.ts +0 -13
- package/dist/internal/internal-jwt.service.js +0 -88
- package/dist/internal/internal-jwt.types.d.ts +0 -7
- package/dist/internal/internal-jwt.types.js +0 -2
- package/dist/internal/internal-key-manager.d.ts +0 -16
- package/dist/internal/internal-key-manager.js +0 -67
- package/dist/internal/registry.d.ts +0 -8
- package/dist/internal/registry.js +0 -34
- package/dist/internal/service-client.d.ts +0 -9
- package/dist/internal/service-client.js +0 -94
- package/dist/jwt/internal-jwt-verifier.d.ts +0 -41
- package/dist/jwt/internal-jwt-verifier.js +0 -185
- package/dist/jwt/jwt-verifier.d.ts +0 -9
- package/dist/jwt/jwt-verifier.js +0 -36
- package/dist/jwt/service-client.d.ts +0 -7
- package/dist/jwt/service-client.js +0 -87
- package/dist/middleware/authorize.d.ts +0 -3
- package/dist/middleware/authorize.js +0 -24
- package/dist/middleware/authorize.middleware.d.ts +0 -54
- package/dist/middleware/authorize.middleware.js +0 -104
- package/dist/middleware/error-handler.d.ts +0 -4
- package/dist/middleware/error-handler.js +0 -23
- package/dist/middleware/request-id.d.ts +0 -2
- package/dist/middleware/request-id.js +0 -9
- package/dist/middleware/request-id.middleware.d.ts +0 -22
- package/dist/middleware/request-id.middleware.js +0 -34
- package/dist/middleware/require-auth.d.ts +0 -10
- package/dist/middleware/require-auth.js +0 -34
- package/dist/middleware/require-human.middleware.d.ts +0 -2
- package/dist/middleware/require-human.middleware.js +0 -18
- package/dist/middleware/require-internal.middleware.d.ts +0 -18
- package/dist/middleware/require-internal.middleware.js +0 -183
- package/dist/middleware/validate.d.ts +0 -5
- package/dist/middleware/validate.js +0 -18
- package/dist/middleware/validated-merge.middleware.d.ts +0 -20
- package/dist/middleware/validated-merge.middleware.js +0 -33
- package/dist/middleware/verify-internal-jwt.d.ts +0 -7
- package/dist/middleware/verify-internal-jwt.js +0 -25
- package/dist/security/guard.d.ts +0 -10
- package/dist/security/guard.js +0 -40
- package/dist/security/index.d.ts +0 -1
- package/dist/types/express.d.ts +0 -22
- package/dist/types/express.js +0 -3
- package/dist/utils/date.utils.d.ts +0 -25
- package/dist/utils/date.utils.js +0 -30
- package/dist/utils/env.d.ts +0 -46
- package/dist/utils/env.js +0 -61
- package/dist/utils/env.utils.d.ts +0 -46
- package/dist/utils/env.utils.js +0 -61
- package/dist/utils/logger.utils.d.ts +0 -66
- package/dist/utils/logger.utils.js +0 -97
- package/dist/utils/response.d.ts +0 -4
- package/dist/utils/response.js +0 -35
- package/dist/utils/response.utils.d.ts +0 -54
- package/dist/utils/response.utils.js +0 -85
|
@@ -1,85 +1,16 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { AccountRole, GlobalPermission } from "../enums";
|
|
3
|
-
import { ILogger } from "../utils";
|
|
4
|
-
import { InternalJwtVerifier } from "../jwt/internal-jwt-verifier";
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and attaches the typed AccessContext to req.accessContext.
|
|
10
|
-
*
|
|
11
|
-
* Moved to shared so every downstream service (auth, user, cloud, insights)
|
|
12
|
-
* uses the same verified implementation rather than maintaining their own copy.
|
|
13
|
-
*
|
|
14
|
-
* Token extraction:
|
|
15
|
-
* Reads a Bearer token from the Authorization header.
|
|
16
|
-
* Does not support cookie-based auth — that is the API gateway's responsibility.
|
|
17
|
-
*
|
|
18
|
-
* On success:
|
|
19
|
-
* req.internalAuth → raw verified JWT payload (use for jti, caller, requestId)
|
|
20
|
-
* req.accessContext → clean typed context (use in controllers and services)
|
|
21
|
-
*
|
|
22
|
-
* On failure:
|
|
23
|
-
* Returns a structured error response and does NOT call next().
|
|
24
|
-
* next(err) is only called for genuinely unexpected errors (not auth failures).
|
|
25
|
-
*
|
|
26
|
-
* Inline guards (requirePermission, requireRole):
|
|
27
|
-
* Convenience methods for single-route guards. For router-level permission
|
|
28
|
-
* enforcement, prefer the standalone factories in permission.middleware.ts —
|
|
29
|
-
* they don't require a reference to this class instance.
|
|
30
|
-
*
|
|
31
|
-
* Logging:
|
|
32
|
-
* Unexpected errors are logged at "error" level with requestId for correlation.
|
|
33
|
-
* Auth failures (expired, invalid token) are intentionally not logged at error
|
|
34
|
-
* level — they are client errors, not server faults.
|
|
35
|
-
*
|
|
36
|
-
* Usage in each service's app.ts:
|
|
37
|
-
* import { RequireAuthMiddleware } from "@discover-cloud/shared";
|
|
38
|
-
*
|
|
39
|
-
* const requireAuth = new RequireAuthMiddleware(jwtVerifier, logger);
|
|
40
|
-
* router.use(requireAuth.handle);
|
|
2
|
+
* @file require-auth.middleware.ts
|
|
3
|
+
* @description Authentication boundary middleware. Verifies the internal gateway JWT,
|
|
4
|
+
* builds the auth context, and gates endpoints.
|
|
41
5
|
*/
|
|
6
|
+
import { Request, Response, NextFunction } from 'express';
|
|
7
|
+
import { ILogger } from '../utils';
|
|
8
|
+
import { UserJwtVerifier } from '../jwt/user-jwt-verifier';
|
|
42
9
|
export declare class RequireAuthMiddleware {
|
|
43
10
|
private readonly verifier;
|
|
44
11
|
private readonly logger;
|
|
45
|
-
constructor(verifier:
|
|
46
|
-
/**
|
|
47
|
-
* Core authentication middleware.
|
|
48
|
-
* Apply via: router.use(requireAuth.handle) or router.get("/", requireAuth.handle, controller.x)
|
|
49
|
-
*/
|
|
12
|
+
constructor(verifier: UserJwtVerifier, logger?: ILogger);
|
|
50
13
|
handle: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Inline permission guard — use when this class instance is available at the call site.
|
|
53
|
-
* For router-level guards, prefer requireGlobalPermission() from permission.middleware.ts.
|
|
54
|
-
*
|
|
55
|
-
* Usage: router.get("/", requireAuth.handle, requireAuth.requirePermission(P.READ_ACCOUNTS), controller.list)
|
|
56
|
-
*/
|
|
57
|
-
requirePermission: (permission: GlobalPermission) => (req: Request, res: Response, next: NextFunction) => void;
|
|
58
|
-
/**
|
|
59
|
-
* Inline role guard — use when this class instance is available at the call site.
|
|
60
|
-
*
|
|
61
|
-
* Prefer permission-based guards over role-based guards where possible.
|
|
62
|
-
* Role checks are appropriate for coarse access gates (e.g. "only admins enter this router").
|
|
63
|
-
*/
|
|
64
|
-
requireRole: (role: AccountRole) => (req: Request, res: Response, next: NextFunction) => void;
|
|
65
|
-
/**
|
|
66
|
-
* Extracts the Bearer token from the Authorization header.
|
|
67
|
-
* Returns null (not an error) if the header is absent or malformed —
|
|
68
|
-
* callers decide how to handle the missing token.
|
|
69
|
-
*/
|
|
70
14
|
private extractBearer;
|
|
71
|
-
/**
|
|
72
|
-
* Maps jose verification errors to appropriate HTTP responses.
|
|
73
|
-
*
|
|
74
|
-
* Error categories:
|
|
75
|
-
* JWTExpired → 401 TOKEN_EXPIRED (client should refresh)
|
|
76
|
-
* JWTClaimValidationFailed,
|
|
77
|
-
* JWSSignatureVerificationFailed,
|
|
78
|
-
* JWSInvalid, JWTInvalid → 401 INVALID_TOKEN (client should re-login)
|
|
79
|
-
* Anything else → 503 SERVICE_UNAVAILABLE (unexpected — log at error)
|
|
80
|
-
*
|
|
81
|
-
* Security note: generic "invalid token" messages are intentional.
|
|
82
|
-
* Never expose which specific claim failed — that aids token forgery attempts.
|
|
83
|
-
*/
|
|
84
15
|
private handleJwtError;
|
|
85
16
|
}
|
|
@@ -1,162 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file require-auth.middleware.ts
|
|
4
|
+
* @description Authentication boundary middleware. Verifies the internal gateway JWT,
|
|
5
|
+
* builds the auth context, and gates endpoints.
|
|
6
|
+
*/
|
|
2
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
8
|
exports.RequireAuthMiddleware = void 0;
|
|
4
9
|
const jose_1 = require("jose");
|
|
5
10
|
const utils_1 = require("../utils");
|
|
6
|
-
const
|
|
7
|
-
/**
|
|
8
|
-
* REQUIRE AUTH MIDDLEWARE (@discover-cloud/shared)
|
|
9
|
-
* ─────────────────────────────────────────────────────────────────────────
|
|
10
|
-
* Verifies the internal JWT, resolves permissions from the Redis cache,
|
|
11
|
-
* and attaches the typed AccessContext to req.accessContext.
|
|
12
|
-
*
|
|
13
|
-
* Moved to shared so every downstream service (auth, user, cloud, insights)
|
|
14
|
-
* uses the same verified implementation rather than maintaining their own copy.
|
|
15
|
-
*
|
|
16
|
-
* Token extraction:
|
|
17
|
-
* Reads a Bearer token from the Authorization header.
|
|
18
|
-
* Does not support cookie-based auth — that is the API gateway's responsibility.
|
|
19
|
-
*
|
|
20
|
-
* On success:
|
|
21
|
-
* req.internalAuth → raw verified JWT payload (use for jti, caller, requestId)
|
|
22
|
-
* req.accessContext → clean typed context (use in controllers and services)
|
|
23
|
-
*
|
|
24
|
-
* On failure:
|
|
25
|
-
* Returns a structured error response and does NOT call next().
|
|
26
|
-
* next(err) is only called for genuinely unexpected errors (not auth failures).
|
|
27
|
-
*
|
|
28
|
-
* Inline guards (requirePermission, requireRole):
|
|
29
|
-
* Convenience methods for single-route guards. For router-level permission
|
|
30
|
-
* enforcement, prefer the standalone factories in permission.middleware.ts —
|
|
31
|
-
* they don't require a reference to this class instance.
|
|
32
|
-
*
|
|
33
|
-
* Logging:
|
|
34
|
-
* Unexpected errors are logged at "error" level with requestId for correlation.
|
|
35
|
-
* Auth failures (expired, invalid token) are intentionally not logged at error
|
|
36
|
-
* level — they are client errors, not server faults.
|
|
37
|
-
*
|
|
38
|
-
* Usage in each service's app.ts:
|
|
39
|
-
* import { RequireAuthMiddleware } from "@discover-cloud/shared";
|
|
40
|
-
*
|
|
41
|
-
* const requireAuth = new RequireAuthMiddleware(jwtVerifier, logger);
|
|
42
|
-
* router.use(requireAuth.handle);
|
|
43
|
-
*/
|
|
11
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
44
12
|
class RequireAuthMiddleware {
|
|
45
13
|
constructor(verifier, logger = utils_1.noopLogger) {
|
|
46
14
|
this.verifier = verifier;
|
|
47
15
|
this.logger = logger;
|
|
48
|
-
/**
|
|
49
|
-
* Core authentication middleware.
|
|
50
|
-
* Apply via: router.use(requireAuth.handle) or router.get("/", requireAuth.handle, controller.x)
|
|
51
|
-
*/
|
|
52
16
|
this.handle = async (req, res, next) => {
|
|
53
17
|
const token = this.extractBearer(req);
|
|
54
18
|
if (!token) {
|
|
55
|
-
(0, utils_1.failure)(res, req,
|
|
19
|
+
(0, utils_1.failure)(res, req, 'Missing Authorization header', 'UNAUTHORIZED', 401);
|
|
56
20
|
return;
|
|
57
21
|
}
|
|
58
22
|
try {
|
|
59
23
|
const { payload, context } = await this.verifier.buildAccessContext(token);
|
|
60
|
-
req.
|
|
61
|
-
req.
|
|
24
|
+
req.authPayload = payload;
|
|
25
|
+
req.authContext = context;
|
|
26
|
+
// Attach audit context. Will throw if requestContextStorage is not initialized.
|
|
27
|
+
request_context_storage_1.RequestContextProvider.setActor(context);
|
|
62
28
|
this.logger.debug({
|
|
63
29
|
requestId: req.id,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
caller: payload.caller,
|
|
68
|
-
}, "Request authenticated");
|
|
30
|
+
identity: context.accountId,
|
|
31
|
+
type: context.type,
|
|
32
|
+
}, 'Request authenticated');
|
|
69
33
|
next();
|
|
70
34
|
}
|
|
71
35
|
catch (err) {
|
|
72
36
|
this.handleJwtError(err, req, res);
|
|
73
37
|
}
|
|
74
38
|
};
|
|
75
|
-
/**
|
|
76
|
-
* Inline permission guard — use when this class instance is available at the call site.
|
|
77
|
-
* For router-level guards, prefer requireGlobalPermission() from permission.middleware.ts.
|
|
78
|
-
*
|
|
79
|
-
* Usage: router.get("/", requireAuth.handle, requireAuth.requirePermission(P.READ_ACCOUNTS), controller.list)
|
|
80
|
-
*/
|
|
81
|
-
this.requirePermission = (permission) => (req, res, next) => {
|
|
82
|
-
const ctx = req.accessContext;
|
|
83
|
-
if (!ctx) {
|
|
84
|
-
// Should not happen after handle() — indicates middleware ordering bug
|
|
85
|
-
(0, utils_1.failure)(res, req, "No access context — ensure requireAuth runs first", "UNAUTHORIZED", 401);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (!internal_jwt_verifier_1.InternalJwtVerifier.hasPermission(ctx, permission)) {
|
|
89
|
-
(0, utils_1.failure)(res, req, `Missing required permission: ${permission}`, "FORBIDDEN", 403);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
next();
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Inline role guard — use when this class instance is available at the call site.
|
|
96
|
-
*
|
|
97
|
-
* Prefer permission-based guards over role-based guards where possible.
|
|
98
|
-
* Role checks are appropriate for coarse access gates (e.g. "only admins enter this router").
|
|
99
|
-
*/
|
|
100
|
-
this.requireRole = (role) => (req, res, next) => {
|
|
101
|
-
const ctx = req.accessContext;
|
|
102
|
-
if (!ctx) {
|
|
103
|
-
(0, utils_1.failure)(res, req, "No access context — ensure requireAuth runs first", "UNAUTHORIZED", 401);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (!internal_jwt_verifier_1.InternalJwtVerifier.hasRole(ctx, role)) {
|
|
107
|
-
(0, utils_1.failure)(res, req, `Missing required role: ${role}`, "FORBIDDEN", 403);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
next();
|
|
111
|
-
};
|
|
112
39
|
}
|
|
113
|
-
/*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Extracts the Bearer token from the Authorization header.
|
|
118
|
-
* Returns null (not an error) if the header is absent or malformed —
|
|
119
|
-
* callers decide how to handle the missing token.
|
|
120
|
-
*/
|
|
40
|
+
/* ---------------------------------------------------------------------------
|
|
41
|
+
Private Helpers
|
|
42
|
+
--------------------------------------------------------------------------- */
|
|
121
43
|
extractBearer(req) {
|
|
122
44
|
const header = req.headers.authorization;
|
|
123
|
-
if (!header)
|
|
45
|
+
if (!header)
|
|
124
46
|
return null;
|
|
125
|
-
}
|
|
126
47
|
const [scheme, token] = header.trim().split(/\s+/);
|
|
127
|
-
if (scheme !==
|
|
48
|
+
if (scheme !== 'Bearer' || !token)
|
|
128
49
|
return null;
|
|
129
|
-
}
|
|
130
50
|
return token;
|
|
131
51
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Maps jose verification errors to appropriate HTTP responses.
|
|
134
|
-
*
|
|
135
|
-
* Error categories:
|
|
136
|
-
* JWTExpired → 401 TOKEN_EXPIRED (client should refresh)
|
|
137
|
-
* JWTClaimValidationFailed,
|
|
138
|
-
* JWSSignatureVerificationFailed,
|
|
139
|
-
* JWSInvalid, JWTInvalid → 401 INVALID_TOKEN (client should re-login)
|
|
140
|
-
* Anything else → 503 SERVICE_UNAVAILABLE (unexpected — log at error)
|
|
141
|
-
*
|
|
142
|
-
* Security note: generic "invalid token" messages are intentional.
|
|
143
|
-
* Never expose which specific claim failed — that aids token forgery attempts.
|
|
144
|
-
*/
|
|
145
52
|
handleJwtError(err, req, res) {
|
|
146
53
|
if (err instanceof jose_1.errors.JWTExpired) {
|
|
147
|
-
(0, utils_1.failure)(res, req,
|
|
54
|
+
(0, utils_1.failure)(res, req, 'Your session has expired. Please log in again.', 'TOKEN_EXPIRED', 401);
|
|
148
55
|
return;
|
|
149
56
|
}
|
|
150
57
|
if (err instanceof jose_1.errors.JWTClaimValidationFailed ||
|
|
151
58
|
err instanceof jose_1.errors.JWSSignatureVerificationFailed ||
|
|
152
59
|
err instanceof jose_1.errors.JWSInvalid ||
|
|
153
60
|
err instanceof jose_1.errors.JWTInvalid) {
|
|
154
|
-
(0, utils_1.failure)(res, req,
|
|
61
|
+
(0, utils_1.failure)(res, req, 'Token is invalid or has been tampered with.', 'INVALID_TOKEN', 401);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Catch the specific Developer Error from RequestContextProvider
|
|
65
|
+
if (err instanceof Error && err.message.includes('AuditRequestContext')) {
|
|
66
|
+
this.logger.error({ err, requestId: req.id }, 'CRITICAL: RequireAuthMiddleware failed because requestContextStorage is not initialized. Check your middleware order.');
|
|
67
|
+
(0, utils_1.failure)(res, req, 'Internal server configuration error.', 'INTERNAL_SERVER_ERROR', 500);
|
|
155
68
|
return;
|
|
156
69
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
(0, utils_1.failure)(res, req, "Authentication service is temporarily unavailable.", "SERVICE_UNAVAILABLE", 503);
|
|
70
|
+
this.logger.error({ err, requestId: req.id }, 'RequireAuthMiddleware: unexpected error during JWT verification');
|
|
71
|
+
(0, utils_1.failure)(res, req, 'Authentication service is temporarily unavailable.', 'SERVICE_UNAVAILABLE', 503);
|
|
160
72
|
}
|
|
161
73
|
}
|
|
162
74
|
exports.RequireAuthMiddleware = RequireAuthMiddleware;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file require-machine.middleware.ts
|
|
3
|
+
* @description Express middleware enforcing auth-service issued machine JWT verification
|
|
4
|
+
* for service-to-service communication endpoints.
|
|
5
|
+
*/
|
|
6
|
+
import { RequestHandler } from 'express';
|
|
7
|
+
import { ILogger } from '../utils/logger.util';
|
|
8
|
+
export interface RequireMachineOptions {
|
|
9
|
+
authServiceJwksUri?: string;
|
|
10
|
+
allowedServices?: string[];
|
|
11
|
+
logger?: ILogger;
|
|
12
|
+
}
|
|
13
|
+
export declare function requireMachine(options?: RequireMachineOptions): RequestHandler;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file require-machine.middleware.ts
|
|
4
|
+
* @description Express middleware enforcing auth-service issued machine JWT verification
|
|
5
|
+
* for service-to-service communication endpoints.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.requireMachine = requireMachine;
|
|
42
|
+
const jose = __importStar(require("jose"));
|
|
43
|
+
const logger_util_1 = require("../utils/logger.util");
|
|
44
|
+
const machine_jwt_verifier_1 = require("../jwt/machine-jwt-verifier");
|
|
45
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
46
|
+
function requireMachine(options = {}) {
|
|
47
|
+
const logger = options.logger ?? logger_util_1.noopLogger;
|
|
48
|
+
const verifier = new machine_jwt_verifier_1.MachineJwtVerifier(options.authServiceJwksUri);
|
|
49
|
+
const { allowedServices } = options;
|
|
50
|
+
return async (req, res, next) => {
|
|
51
|
+
const header = req.headers.authorization;
|
|
52
|
+
if (!header?.startsWith('Bearer ')) {
|
|
53
|
+
res.status(401).json({
|
|
54
|
+
success: false,
|
|
55
|
+
error: {
|
|
56
|
+
code: 'UNAUTHORIZED',
|
|
57
|
+
message: 'Missing Authorization header',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const token = header.slice(7);
|
|
63
|
+
try {
|
|
64
|
+
const payload = await verifier.verify(token);
|
|
65
|
+
if (allowedServices && !allowedServices.includes(payload.serviceId)) {
|
|
66
|
+
logger.warn({ requestId: req.id, serviceId: payload.serviceId }, '[requireMachine] Service rejected');
|
|
67
|
+
res.status(403).json({
|
|
68
|
+
success: false,
|
|
69
|
+
error: {
|
|
70
|
+
code: 'FORBIDDEN',
|
|
71
|
+
message: 'Service is not permitted to access this endpoint',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const context = {
|
|
77
|
+
type: 'machine',
|
|
78
|
+
serviceId: payload.serviceId,
|
|
79
|
+
scopes: payload.scopes,
|
|
80
|
+
};
|
|
81
|
+
req.authPayload = payload;
|
|
82
|
+
req.authContext = context;
|
|
83
|
+
// Attach audit context. Will throw if requestContextStorage is not initialized.
|
|
84
|
+
request_context_storage_1.RequestContextProvider.setActor(context);
|
|
85
|
+
logger.debug({ requestId: req.id, serviceId: payload.serviceId, jti: payload.jti }, '[requireMachine] Machine authenticated');
|
|
86
|
+
next();
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof jose.errors.JWTExpired) {
|
|
90
|
+
res.status(401).json({
|
|
91
|
+
success: false,
|
|
92
|
+
error: { code: 'TOKEN_EXPIRED', message: 'Machine token expired' },
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (err instanceof jose.errors.JWTClaimValidationFailed ||
|
|
97
|
+
err instanceof jose.errors.JWSSignatureVerificationFailed ||
|
|
98
|
+
err instanceof jose.errors.JWSInvalid ||
|
|
99
|
+
err instanceof jose.errors.JWTInvalid) {
|
|
100
|
+
res.status(401).json({
|
|
101
|
+
success: false,
|
|
102
|
+
error: { code: 'INVALID_TOKEN', message: 'Invalid machine token' },
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Catch the specific Developer Error from RequestContextProvider
|
|
107
|
+
if (err instanceof Error && err.message.includes('AuditRequestContext')) {
|
|
108
|
+
logger.error({ err, requestId: req.id }, 'CRITICAL: [requireMachine] failed because requestContextStorage is not initialized. Check middleware order.');
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
success: false,
|
|
111
|
+
error: {
|
|
112
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
113
|
+
message: 'Internal server configuration error',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
logger.error({ err, requestId: req.id }, '[requireMachine] Machine authentication failure');
|
|
119
|
+
res.status(503).json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: {
|
|
122
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
123
|
+
message: 'Machine authentication temporarily unavailable',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file require-org-permission-from-body.middleware.ts
|
|
3
|
+
* @description Checks org permission only when organizationId exists in request body.
|
|
4
|
+
* Used for endpoints that support both personal and organization scopes.
|
|
5
|
+
*/
|
|
6
|
+
import { RequestHandler } from 'express';
|
|
7
|
+
import { Permission } from '../enums';
|
|
8
|
+
export declare const createRequireOrgPermissionFromBody: (requireOrgPermission: (permission: Permission) => RequestHandler) => {
|
|
9
|
+
requireOrgPermissionFromBody: (permission: Permission) => RequestHandler;
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file require-org-permission-from-body.middleware.ts
|
|
4
|
+
* @description Checks org permission only when organizationId exists in request body.
|
|
5
|
+
* Used for endpoints that support both personal and organization scopes.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createRequireOrgPermissionFromBody = void 0;
|
|
9
|
+
const createRequireOrgPermissionFromBody = (requireOrgPermission) => {
|
|
10
|
+
const requireOrgPermissionFromBody = (permission) => {
|
|
11
|
+
return async (req, res, next) => {
|
|
12
|
+
const organizationId = req.body?.organizationId ?? req.validated?.body?.organizationId;
|
|
13
|
+
if (!organizationId) {
|
|
14
|
+
return next();
|
|
15
|
+
}
|
|
16
|
+
return requireOrgPermission(permission)(req, res, next);
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
requireOrgPermissionFromBody,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
exports.createRequireOrgPermissionFromBody = createRequireOrgPermissionFromBody;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file require-org-permission.middleware.ts
|
|
3
|
+
* @description Express middleware generator to enforce organization-scoped permission checks.
|
|
4
|
+
*/
|
|
5
|
+
import { RequestHandler } from 'express';
|
|
6
|
+
import { Permission } from '../enums';
|
|
7
|
+
import { PermissionCacheService } from '../authorization/permission-cache.service';
|
|
8
|
+
/**
|
|
9
|
+
* Creates requireOrgPermission middleware factory.
|
|
10
|
+
*
|
|
11
|
+
* @param {PermissionCacheService} permissionCache - Shared Redis cache service.
|
|
12
|
+
* @param {(accountId: string, organizationId: string) => Promise<string[]>} resolveFn - Cache miss database/API lookup function.
|
|
13
|
+
* @returns {object} Organization permission middlewares.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createRequireOrgPermission: (permissionCache: PermissionCacheService, resolveFn: (accountId: string, organizationId: string) => Promise<string[]>) => {
|
|
16
|
+
requireOrgPermission: (permission: Permission) => RequestHandler;
|
|
17
|
+
requireAnyOrgPermission: (...permissions: Permission[]) => RequestHandler;
|
|
18
|
+
requireAllOrgPermissions: (...permissions: Permission[]) => RequestHandler;
|
|
19
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file require-org-permission.middleware.ts
|
|
4
|
+
* @description Express middleware generator to enforce organization-scoped permission checks.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createRequireOrgPermission = void 0;
|
|
8
|
+
const permission_checker_1 = require("../authorization/permission-checker");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
/**
|
|
11
|
+
* Creates requireOrgPermission middleware factory.
|
|
12
|
+
*
|
|
13
|
+
* @param {PermissionCacheService} permissionCache - Shared Redis cache service.
|
|
14
|
+
* @param {(accountId: string, organizationId: string) => Promise<string[]>} resolveFn - Cache miss database/API lookup function.
|
|
15
|
+
* @returns {object} Organization permission middlewares.
|
|
16
|
+
*/
|
|
17
|
+
const createRequireOrgPermission = (permissionCache, resolveFn) => {
|
|
18
|
+
const requireOrgPermission = (permission) => {
|
|
19
|
+
return async (req, res, next) => {
|
|
20
|
+
const ctx = req.authContext;
|
|
21
|
+
if (!ctx || ctx.type !== 'user') {
|
|
22
|
+
(0, utils_1.failure)(res, req, 'Unauthorized: No auth context', 'UNAUTHORIZED', 401);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const organizationId = req.params['organizationId'] ??
|
|
26
|
+
req.params['orgId'] ??
|
|
27
|
+
req.validated?.params?.['organizationId'] ??
|
|
28
|
+
req.validated?.body?.['organizationId'];
|
|
29
|
+
if (!organizationId || typeof organizationId !== 'string') {
|
|
30
|
+
(0, utils_1.failure)(res, req, 'Bad Request: Missing organization context', 'BAD_REQUEST', 400);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const perms = await permissionCache.resolveOrganizationPermissions(ctx.accountId, organizationId, async () => {
|
|
35
|
+
return resolveFn(ctx.accountId, organizationId);
|
|
36
|
+
});
|
|
37
|
+
if (!permission_checker_1.PermissionChecker.has(perms, permission)) {
|
|
38
|
+
(0, utils_1.failure)(res, req, `Forbidden: Missing required organization permission: ${permission}`, 'FORBIDDEN', 403);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
next();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
next(err);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const requireAnyOrgPermission = (...permissions) => {
|
|
49
|
+
return async (req, res, next) => {
|
|
50
|
+
const ctx = req.authContext;
|
|
51
|
+
if (!ctx || ctx.type !== 'user') {
|
|
52
|
+
(0, utils_1.failure)(res, req, 'Unauthorized: No auth context', 'UNAUTHORIZED', 401);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const organizationId = req.params['organizationId'] ??
|
|
56
|
+
req.params['orgId'] ??
|
|
57
|
+
req.validated?.params?.['organizationId'] ??
|
|
58
|
+
req.validated?.body?.['organizationId'];
|
|
59
|
+
if (!organizationId || typeof organizationId !== 'string') {
|
|
60
|
+
(0, utils_1.failure)(res, req, 'Bad Request: Missing organization context', 'BAD_REQUEST', 400);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const perms = await permissionCache.resolveOrganizationPermissions(ctx.accountId, organizationId, async () => {
|
|
65
|
+
return resolveFn(ctx.accountId, organizationId);
|
|
66
|
+
});
|
|
67
|
+
if (!permission_checker_1.PermissionChecker.hasAny(perms, permissions)) {
|
|
68
|
+
(0, utils_1.failure)(res, req, `Forbidden: Missing one of required organization permissions: [${permissions.join(', ')}]`, 'FORBIDDEN', 403);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
next();
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
next(err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const requireAllOrgPermissions = (...permissions) => {
|
|
79
|
+
return async (req, res, next) => {
|
|
80
|
+
const ctx = req.authContext;
|
|
81
|
+
if (!ctx || ctx.type !== 'user') {
|
|
82
|
+
(0, utils_1.failure)(res, req, 'Unauthorized: No auth context', 'UNAUTHORIZED', 401);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const organizationId = req.params['organizationId'] ??
|
|
86
|
+
req.params['orgId'] ??
|
|
87
|
+
req.validated?.params?.['organizationId'] ??
|
|
88
|
+
req.validated?.body?.['organizationId'];
|
|
89
|
+
if (!organizationId || typeof organizationId !== 'string') {
|
|
90
|
+
(0, utils_1.failure)(res, req, 'Bad Request: Missing organization context', 'BAD_REQUEST', 400);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const perms = await permissionCache.resolveOrganizationPermissions(ctx.accountId, organizationId, async () => {
|
|
95
|
+
return resolveFn(ctx.accountId, organizationId);
|
|
96
|
+
});
|
|
97
|
+
if (!permission_checker_1.PermissionChecker.hasAll(perms, permissions)) {
|
|
98
|
+
const missing = permissions.filter((p) => !permission_checker_1.PermissionChecker.has(perms, p));
|
|
99
|
+
(0, utils_1.failure)(res, req, `Forbidden: Missing required organization permissions: [${missing.join(', ')}]`, 'FORBIDDEN', 403);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
next();
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
next(err);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
requireOrgPermission,
|
|
111
|
+
requireAnyOrgPermission,
|
|
112
|
+
requireAllOrgPermissions,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
exports.createRequireOrgPermission = createRequireOrgPermission;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file require-platform-permission.middleware.ts
|
|
3
|
+
* @description Express middleware generator to enforce platform-scoped permission checks.
|
|
4
|
+
*/
|
|
5
|
+
import { RequestHandler } from 'express';
|
|
6
|
+
import { PlatformPermission } from '../enums';
|
|
7
|
+
import { PermissionCacheService } from '../authorization/permission-cache.service';
|
|
8
|
+
/**
|
|
9
|
+
* Creates requirePlatformPermission middleware.
|
|
10
|
+
*
|
|
11
|
+
* @param {PermissionCacheService} permissionCache - Shared Redis cache service.
|
|
12
|
+
* @param {(accountId: string) => Promise<string[]>} resolveFn - Cache miss database/API lookup function.
|
|
13
|
+
* @returns {object} Platform permission middlewares.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createRequirePlatformPermission: (permissionCache: PermissionCacheService, resolveFn: (accountId: string) => Promise<string[]>) => {
|
|
16
|
+
requirePlatformPermission: (permission: PlatformPermission) => RequestHandler;
|
|
17
|
+
requireAnyPlatformPermission: (...permissions: PlatformPermission[]) => RequestHandler;
|
|
18
|
+
requireAllPlatformPermissions: (...permissions: PlatformPermission[]) => RequestHandler;
|
|
19
|
+
};
|