@discover-cloud/shared 1.2.9 → 1.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/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/permission-cache.service.d.ts +104 -16
- package/dist/authorization/permission-cache.service.js +156 -143
- package/dist/authorization/permission-checker.d.ts +30 -0
- package/dist/authorization/permission-checker.js +40 -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/dtos/auth-service.dto.d.ts +72 -26
- package/dist/dtos/auth-service.dto.js +4 -0
- package/dist/dtos/cloud-service.dto.d.ts +51 -27
- package/dist/dtos/cloud-service.dto.js +5 -0
- package/dist/dtos/insights-service.dto.d.ts +18 -27
- package/dist/dtos/insights-service.dto.js +8 -0
- package/dist/dtos/response.dto.d.ts +7 -84
- package/dist/dtos/response.dto.js +2 -21
- package/dist/dtos/user-service.dto.d.ts +118 -13
- package/dist/enums/domain.enums.d.ts +246 -75
- package/dist/enums/domain.enums.js +305 -102
- 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 +14 -20
- package/dist/errors/http-errors.d.ts +105 -17
- package/dist/errors/http-errors.js +118 -33
- package/dist/http/index.d.ts +1 -0
- 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 +91 -38
- package/dist/http/service-client.js +102 -53
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jwt/index.d.ts +2 -1
- package/dist/jwt/index.js +2 -1
- package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
- package/dist/jwt/machine-jwt-verifier.js +84 -0
- package/dist/jwt/machine-token-client.d.ts +35 -4
- package/dist/jwt/machine-token-client.js +57 -23
- package/dist/jwt/user-jwt-verifier.d.ts +31 -0
- package/dist/jwt/user-jwt-verifier.js +101 -0
- package/dist/messaging/index.d.ts +1 -0
- package/dist/{security → messaging}/index.js +1 -1
- package/dist/messaging/rabbitmq.client.d.ts +13 -0
- package/dist/messaging/rabbitmq.client.js +127 -0
- package/dist/middleware/error-handler.middleware.d.ts +8 -28
- package/dist/middleware/error-handler.middleware.js +61 -52
- package/dist/middleware/index.d.ts +7 -5
- package/dist/middleware/index.js +7 -5
- package/dist/middleware/request-context.middleware.d.ts +17 -0
- package/dist/middleware/request-context.middleware.js +73 -0
- package/dist/middleware/require-auth.middleware.d.ts +17 -70
- package/dist/middleware/require-auth.middleware.js +24 -103
- package/dist/middleware/require-machine.middleware.d.ts +38 -0
- package/dist/middleware/require-machine.middleware.js +123 -0
- package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
- package/dist/middleware/require-org-permission-from-body.middleware.js +24 -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 +29 -29
- package/dist/middleware/validate.middleware.js +30 -32
- package/dist/types/express.types.d.ts +83 -122
- package/dist/types/express.types.js +28 -47
- 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 +26 -0
- package/dist/utils/logger.util.js +53 -0
- package/dist/utils/pagination.util.d.ts +6 -0
- package/dist/utils/pagination.util.js +20 -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 +72 -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/context/index.d.ts +0 -1
- package/dist/context/index.js +0 -17
- 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/index.js +0 -19
- 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,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GlobalErrorHandler = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const utils_1 = require("../utils");
|
|
6
|
-
class GlobalErrorHandler {
|
|
7
|
-
static handle(err, req, res, _next) {
|
|
8
|
-
// Standardized logging using req.id from your requestId middleware
|
|
9
|
-
console.error(`[req ${req.id}]`, err);
|
|
10
|
-
// 1. Zod Validation Errors
|
|
11
|
-
if (err instanceof zod_1.ZodError) {
|
|
12
|
-
return (0, utils_1.failure)(res, "Validation failed", 400, err.flatten());
|
|
13
|
-
}
|
|
14
|
-
// 2. Custom App Errors (instanceof checks for BadRequestError, etc.)
|
|
15
|
-
const custom = err;
|
|
16
|
-
if (custom.statusCode) {
|
|
17
|
-
return (0, utils_1.failure)(res, custom.message ?? "Error", custom.statusCode, custom.details);
|
|
18
|
-
}
|
|
19
|
-
// 3. Fallback: Internal Server Error
|
|
20
|
-
return (0, utils_1.failure)(res, "Internal Server Error", 500);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
exports.GlobalErrorHandler = GlobalErrorHandler;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requestId = void 0;
|
|
4
|
-
const crypto_1 = require("crypto");
|
|
5
|
-
const requestId = (req, _res, next) => {
|
|
6
|
-
req.id = (0, crypto_1.randomUUID)();
|
|
7
|
-
next();
|
|
8
|
-
};
|
|
9
|
-
exports.requestId = requestId;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
/**
|
|
3
|
-
* REQUEST ID MIDDLEWARE (@discover-cloud/shared)
|
|
4
|
-
* ─────────────────────────────────────────────────
|
|
5
|
-
* Attaches a unique request ID to req.id for distributed tracing.
|
|
6
|
-
* Register early in the middleware stack — before any logger or handler
|
|
7
|
-
* that needs to include the request ID in its output.
|
|
8
|
-
*
|
|
9
|
-
* Priority:
|
|
10
|
-
* 1. x-request-id header forwarded by the API Gateway (or load balancer)
|
|
11
|
-
* 2. Newly generated UUID v4 if no upstream header is present
|
|
12
|
-
*
|
|
13
|
-
* Why forward the upstream header?
|
|
14
|
-
* The gateway mints a requestId and stamps it on every internal request.
|
|
15
|
-
* If downstream services generate their own IDs, the same logical request
|
|
16
|
-
* carries different IDs in each service's logs, breaking cross-service
|
|
17
|
-
* log correlation. Forwarding the upstream ID keeps the trace consistent.
|
|
18
|
-
*
|
|
19
|
-
* The resolved ID is also echoed back on the response via x-request-id
|
|
20
|
-
* so clients and proxies can correlate their own logs with the service's.
|
|
21
|
-
*/
|
|
22
|
-
export declare const requestId: (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requestId = void 0;
|
|
4
|
-
const crypto_1 = require("crypto");
|
|
5
|
-
/**
|
|
6
|
-
* REQUEST ID MIDDLEWARE (@discover-cloud/shared)
|
|
7
|
-
* ─────────────────────────────────────────────────
|
|
8
|
-
* Attaches a unique request ID to req.id for distributed tracing.
|
|
9
|
-
* Register early in the middleware stack — before any logger or handler
|
|
10
|
-
* that needs to include the request ID in its output.
|
|
11
|
-
*
|
|
12
|
-
* Priority:
|
|
13
|
-
* 1. x-request-id header forwarded by the API Gateway (or load balancer)
|
|
14
|
-
* 2. Newly generated UUID v4 if no upstream header is present
|
|
15
|
-
*
|
|
16
|
-
* Why forward the upstream header?
|
|
17
|
-
* The gateway mints a requestId and stamps it on every internal request.
|
|
18
|
-
* If downstream services generate their own IDs, the same logical request
|
|
19
|
-
* carries different IDs in each service's logs, breaking cross-service
|
|
20
|
-
* log correlation. Forwarding the upstream ID keeps the trace consistent.
|
|
21
|
-
*
|
|
22
|
-
* The resolved ID is also echoed back on the response via x-request-id
|
|
23
|
-
* so clients and proxies can correlate their own logs with the service's.
|
|
24
|
-
*/
|
|
25
|
-
const requestId = (req, res, next) => {
|
|
26
|
-
const upstream = req.headers["x-request-id"];
|
|
27
|
-
// Header can be a string array (multi-value header) — take the first value.
|
|
28
|
-
const id = Array.isArray(upstream) ? upstream[0] : (upstream ?? (0, crypto_1.randomUUID)());
|
|
29
|
-
req.id = id;
|
|
30
|
-
// Echo back on the response for client-side and proxy-side correlation.
|
|
31
|
-
res.setHeader("x-request-id", id);
|
|
32
|
-
next();
|
|
33
|
-
};
|
|
34
|
-
exports.requestId = requestId;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { InternalJwtPayload } from "../types";
|
|
3
|
-
export interface IJwtVerifier {
|
|
4
|
-
verifyAccessToken(token: string): Promise<InternalJwtPayload>;
|
|
5
|
-
}
|
|
6
|
-
export declare class RequireAuthMiddleware {
|
|
7
|
-
private readonly verifier;
|
|
8
|
-
constructor(verifier: IJwtVerifier);
|
|
9
|
-
handle: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
|
|
10
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RequireAuthMiddleware = void 0;
|
|
4
|
-
const errors_1 = require("../errors");
|
|
5
|
-
class RequireAuthMiddleware {
|
|
6
|
-
constructor(verifier) {
|
|
7
|
-
this.verifier = verifier;
|
|
8
|
-
this.handle = async (req, _res, next) => {
|
|
9
|
-
const header = req.headers.authorization;
|
|
10
|
-
if (!header?.startsWith("Bearer ")) {
|
|
11
|
-
return next(new errors_1.UnauthorizedError("Missing Authorization header"));
|
|
12
|
-
}
|
|
13
|
-
const token = header.slice(7);
|
|
14
|
-
try {
|
|
15
|
-
// 1. Verify against Gateway Public Keys
|
|
16
|
-
const payload = await this.verifier.verifyAccessToken(token);
|
|
17
|
-
// 2. Store the raw payload (Access to .jti for logouts/suspensions)
|
|
18
|
-
req.internalAuth = payload;
|
|
19
|
-
// 3. Build AccessContext if it's a human user
|
|
20
|
-
if (payload.accountId && payload.accountRole) {
|
|
21
|
-
req.accessContext = {
|
|
22
|
-
accountId: payload.accountId,
|
|
23
|
-
accountRole: payload.accountRole,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
next();
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
next(new errors_1.UnauthorizedError("Unauthorized: Token validation failed"));
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
exports.RequireAuthMiddleware = RequireAuthMiddleware;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requireHuman = void 0;
|
|
4
|
-
const utils_1 = require("../utils");
|
|
5
|
-
const types_1 = require("../types");
|
|
6
|
-
const requireHuman = (req, res, next) => {
|
|
7
|
-
const ctx = req.accessContext;
|
|
8
|
-
if (!ctx) {
|
|
9
|
-
(0, utils_1.failure)(res, req, "Unauthorized: No access context", "UNAUTHORIZED", 401);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (!(0, types_1.isHumanContext)(ctx)) {
|
|
13
|
-
(0, utils_1.failure)(res, req, "Human token required", "FORBIDDEN", 403);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
next();
|
|
17
|
-
};
|
|
18
|
-
exports.requireHuman = requireHuman;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { ILogger } from "../utils/logger.utils";
|
|
3
|
-
export interface RequireInternalOptions {
|
|
4
|
-
/**
|
|
5
|
-
* Gateway JWKS URI.
|
|
6
|
-
* Defaults to GATEWAY_JWKS_URI env var, then the docker-compose default.
|
|
7
|
-
* Must point to the gateway, not any individual service.
|
|
8
|
-
*/
|
|
9
|
-
gatewayJwksUri?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Permitted serviceIds. Reject any machine token whose serviceId is
|
|
12
|
-
* not in this list, even if the signature is valid.
|
|
13
|
-
* Omit to accept any valid machine token (not recommended in production).
|
|
14
|
-
*/
|
|
15
|
-
allowedServices?: string[];
|
|
16
|
-
logger?: ILogger;
|
|
17
|
-
}
|
|
18
|
-
export declare function requireInternal(options?: RequireInternalOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.requireInternal = requireInternal;
|
|
37
|
-
const jose = __importStar(require("jose"));
|
|
38
|
-
const types_1 = require("../types");
|
|
39
|
-
const logger_utils_1 = require("../utils/logger.utils");
|
|
40
|
-
/**
|
|
41
|
-
* REQUIRE INTERNAL MIDDLEWARE (@discover-cloud/shared)
|
|
42
|
-
* ─────────────────────────────────────────────────────────────────────────
|
|
43
|
-
* Protects /internal/* routes by verifying a gateway-signed machine JWT.
|
|
44
|
-
* Replaces X-Internal-Secret on all service-to-service endpoints.
|
|
45
|
-
*
|
|
46
|
-
* Trust model:
|
|
47
|
-
* All machine tokens are signed by the gateway's private key and verified
|
|
48
|
-
* against the gateway's JWKS endpoint — the same trust root used for
|
|
49
|
-
* human tokens. There is exactly one signing authority in the system.
|
|
50
|
-
*
|
|
51
|
-
* Verification:
|
|
52
|
-
* 1. Extract Bearer token from Authorization header
|
|
53
|
-
* 2. Verify RS256 signature against gateway JWKS
|
|
54
|
-
* 3. Assert typ === "internal" (token confusion defence)
|
|
55
|
-
* 4. Assert isMachine === true (rejects human tokens on internal routes)
|
|
56
|
-
* 5. Assert serviceId is in the allowlist (optional but recommended)
|
|
57
|
-
* 6. Attach MachineAccessContext to req.accessContext
|
|
58
|
-
*
|
|
59
|
-
* JWKS caching:
|
|
60
|
-
* One RemoteJWKSet instance is created per middleware instance (i.e. per
|
|
61
|
-
* service process), cached internally by jose for JWKS_CACHE_MAX_AGE_MS.
|
|
62
|
-
* No per-request network call under normal operation.
|
|
63
|
-
*
|
|
64
|
-
* Allowlist:
|
|
65
|
-
* Pass allowedServices to restrict which services can call this endpoint.
|
|
66
|
-
* Example: requireInternal({ allowedServices: ["cloud-service"] })
|
|
67
|
-
* Defaults to accepting any valid machine token if omitted.
|
|
68
|
-
* Always set this in production — it limits blast radius if a service
|
|
69
|
-
* is compromised.
|
|
70
|
-
*
|
|
71
|
-
* Usage:
|
|
72
|
-
* // insights-service internal routes
|
|
73
|
-
* router.use("/internal", requireInternal({
|
|
74
|
-
* gatewayJwksUri: process.env.GATEWAY_JWKS_URI,
|
|
75
|
-
* allowedServices: ["cloud-service"],
|
|
76
|
-
* logger,
|
|
77
|
-
* }));
|
|
78
|
-
*/
|
|
79
|
-
const CLOCK_TOLERANCE_S = 30;
|
|
80
|
-
const JWKS_CACHE_MAX_AGE_MS = 10 * 60 * 1000;
|
|
81
|
-
const JWKS_FETCH_TIMEOUT_MS = 5000;
|
|
82
|
-
function requireInternal(options = {}) {
|
|
83
|
-
const logger = options.logger ?? logger_utils_1.noopLogger;
|
|
84
|
-
const jwksUri = options.gatewayJwksUri ??
|
|
85
|
-
process.env["GATEWAY_JWKS_URI"] ??
|
|
86
|
-
"http://api-gateway:3000/.well-known/jwks.json";
|
|
87
|
-
const issuer = process.env["INTERNAL_JWT_ISSUER"] ?? "discover-cloud:api-gateway";
|
|
88
|
-
const audience = process.env["INTERNAL_JWT_AUDIENCE"] ?? "discover-cloud:internal";
|
|
89
|
-
// One JWKS instance per middleware — jose caches the key set internally
|
|
90
|
-
const jwks = jose.createRemoteJWKSet(new URL(jwksUri), {
|
|
91
|
-
cacheMaxAge: JWKS_CACHE_MAX_AGE_MS,
|
|
92
|
-
timeoutDuration: JWKS_FETCH_TIMEOUT_MS,
|
|
93
|
-
});
|
|
94
|
-
const { allowedServices } = options;
|
|
95
|
-
return async (req, res, next) => {
|
|
96
|
-
const header = req.headers.authorization;
|
|
97
|
-
if (!header?.startsWith("Bearer ")) {
|
|
98
|
-
res.status(401).json({
|
|
99
|
-
success: false,
|
|
100
|
-
error: { code: "UNAUTHORIZED", message: "Missing Authorization header" },
|
|
101
|
-
});
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const token = header.slice(7);
|
|
105
|
-
try {
|
|
106
|
-
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
107
|
-
issuer,
|
|
108
|
-
audience,
|
|
109
|
-
algorithms: ["RS256"],
|
|
110
|
-
clockTolerance: CLOCK_TOLERANCE_S,
|
|
111
|
-
});
|
|
112
|
-
// Token confusion defence — reject human tokens that somehow
|
|
113
|
-
// reach an internal route (wrong header forwarded by caller)
|
|
114
|
-
if (payload["typ"] !== "internal") {
|
|
115
|
-
res.status(401).json({
|
|
116
|
-
success: false,
|
|
117
|
-
error: { code: "INVALID_TOKEN", message: "Token type must be 'internal'" },
|
|
118
|
-
});
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
// Machine guard — reject human internal tokens on machine-only routes
|
|
122
|
-
if (payload["isMachine"] !== true) {
|
|
123
|
-
res.status(403).json({
|
|
124
|
-
success: false,
|
|
125
|
-
error: { code: "FORBIDDEN", message: "Machine token required for internal routes" },
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
(0, types_1.assertMachinePayload)(payload);
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
res.status(401).json({
|
|
134
|
-
success: false,
|
|
135
|
-
error: {
|
|
136
|
-
code: "INVALID_TOKEN",
|
|
137
|
-
message: "Invalid machine token payload",
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const serviceId = payload.serviceId;
|
|
143
|
-
// Allowlist — tightest possible scope per endpoint
|
|
144
|
-
if (allowedServices && !allowedServices.includes(serviceId)) {
|
|
145
|
-
logger.warn({ requestId: req.id, serviceId }, "[requireInternal] serviceId not in allowlist");
|
|
146
|
-
res.status(403).json({
|
|
147
|
-
success: false,
|
|
148
|
-
error: { code: "FORBIDDEN", message: "Service not permitted to call this endpoint" },
|
|
149
|
-
});
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const context = { kind: "machine", serviceId };
|
|
153
|
-
req.internalAuth = payload;
|
|
154
|
-
req.accessContext = context;
|
|
155
|
-
logger.debug({ requestId: req.id, serviceId, jti: payload.jti }, "[requireInternal] Machine token verified");
|
|
156
|
-
next();
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
if (err instanceof jose.errors.JWTExpired) {
|
|
160
|
-
res.status(401).json({
|
|
161
|
-
success: false,
|
|
162
|
-
error: { code: "TOKEN_EXPIRED", message: "Machine token has expired" },
|
|
163
|
-
});
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (err instanceof jose.errors.JWSSignatureVerificationFailed ||
|
|
167
|
-
err instanceof jose.errors.JWTClaimValidationFailed ||
|
|
168
|
-
err instanceof jose.errors.JWSInvalid ||
|
|
169
|
-
err instanceof jose.errors.JWTInvalid) {
|
|
170
|
-
res.status(401).json({
|
|
171
|
-
success: false,
|
|
172
|
-
error: { code: "INVALID_TOKEN", message: "Machine token is invalid" },
|
|
173
|
-
});
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
logger.error({ err, requestId: req.id }, "[requireInternal] Unexpected error verifying machine token");
|
|
177
|
-
res.status(503).json({
|
|
178
|
-
success: false,
|
|
179
|
-
error: { code: "SERVICE_UNAVAILABLE", message: "Authentication temporarily unavailable" },
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Validator = void 0;
|
|
4
|
-
class Validator {
|
|
5
|
-
static validate(schema, source = "body") {
|
|
6
|
-
return (req, _res, next) => {
|
|
7
|
-
const result = schema.safeParse(req[source]);
|
|
8
|
-
if (!result.success) {
|
|
9
|
-
// Pass to GlobalErrorHandler
|
|
10
|
-
return next(result.error);
|
|
11
|
-
}
|
|
12
|
-
// Populate req.validated (defined in your shared .d.ts)
|
|
13
|
-
req.validated = result.data;
|
|
14
|
-
next();
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
exports.Validator = Validator;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
/**
|
|
3
|
-
* VALIDATED MERGE HELPERS
|
|
4
|
-
* ─────────────────────────
|
|
5
|
-
* Used in routes that run two Validator.validate calls (e.g. params + body,
|
|
6
|
-
* or params + query). Each call overwrites req.validated, so values from
|
|
7
|
-
* the first validator must be captured before the second runs.
|
|
8
|
-
*
|
|
9
|
-
* Pattern:
|
|
10
|
-
* Validator.validate(paramsSchema, "params"), // req.validated = { id }
|
|
11
|
-
* captureValidated("id", "_capturedId"), // stashes id
|
|
12
|
-
* Validator.validate(bodySchema, "body"), // req.validated = { role }
|
|
13
|
-
* mergeValidated("id", "_capturedId"), // req.validated = { id, role }
|
|
14
|
-
*
|
|
15
|
-
* The cast through unknown is required — TypeScript won't allow a direct
|
|
16
|
-
* cast from Request to Record<string, unknown> because they don't overlap
|
|
17
|
-
* enough. Casting to unknown first tells TypeScript we know what we're doing.
|
|
18
|
-
*/
|
|
19
|
-
export declare const captureValidated: (key: string, tempKey: string) => (req: Request, _res: Response, next: NextFunction) => void;
|
|
20
|
-
export declare const mergeValidated: (key: string, tempKey: string) => (req: Request, _res: Response, next: NextFunction) => void;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mergeValidated = exports.captureValidated = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* VALIDATED MERGE HELPERS
|
|
6
|
-
* ─────────────────────────
|
|
7
|
-
* Used in routes that run two Validator.validate calls (e.g. params + body,
|
|
8
|
-
* or params + query). Each call overwrites req.validated, so values from
|
|
9
|
-
* the first validator must be captured before the second runs.
|
|
10
|
-
*
|
|
11
|
-
* Pattern:
|
|
12
|
-
* Validator.validate(paramsSchema, "params"), // req.validated = { id }
|
|
13
|
-
* captureValidated("id", "_capturedId"), // stashes id
|
|
14
|
-
* Validator.validate(bodySchema, "body"), // req.validated = { role }
|
|
15
|
-
* mergeValidated("id", "_capturedId"), // req.validated = { id, role }
|
|
16
|
-
*
|
|
17
|
-
* The cast through unknown is required — TypeScript won't allow a direct
|
|
18
|
-
* cast from Request to Record<string, unknown> because they don't overlap
|
|
19
|
-
* enough. Casting to unknown first tells TypeScript we know what we're doing.
|
|
20
|
-
*/
|
|
21
|
-
const captureValidated = (key, tempKey) => (req, _res, next) => {
|
|
22
|
-
req[tempKey] = req.validated[key];
|
|
23
|
-
next();
|
|
24
|
-
};
|
|
25
|
-
exports.captureValidated = captureValidated;
|
|
26
|
-
const mergeValidated = (key, tempKey) => (req, _res, next) => {
|
|
27
|
-
req.validated = {
|
|
28
|
-
...req.validated,
|
|
29
|
-
[key]: req[tempKey],
|
|
30
|
-
};
|
|
31
|
-
next();
|
|
32
|
-
};
|
|
33
|
-
exports.mergeValidated = mergeValidated;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { InternalJwtService } from "../internal";
|
|
3
|
-
export declare class VerifyInternalJwtMiddleware {
|
|
4
|
-
private readonly internalJwt;
|
|
5
|
-
constructor(internalJwt: InternalJwtService);
|
|
6
|
-
handle: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
|
|
7
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VerifyInternalJwtMiddleware = void 0;
|
|
4
|
-
const errors_1 = require("../errors"); // Shared errors
|
|
5
|
-
class VerifyInternalJwtMiddleware {
|
|
6
|
-
constructor(internalJwt) {
|
|
7
|
-
this.internalJwt = internalJwt;
|
|
8
|
-
this.handle = async (req, _res, next) => {
|
|
9
|
-
const raw = req.headers["x-internal-token"]; // Standardize header name
|
|
10
|
-
if (!raw || typeof raw !== "string") {
|
|
11
|
-
return next(new errors_1.UnauthorizedError("Missing internal token"));
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const payload = await this.internalJwt.verify(raw);
|
|
15
|
-
// Populate standardized internalAuth (from your shared .d.ts)
|
|
16
|
-
req.internalAuth = payload;
|
|
17
|
-
next();
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
next(new errors_1.UnauthorizedError("Invalid internal token"));
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
exports.VerifyInternalJwtMiddleware = VerifyInternalJwtMiddleware;
|
package/dist/security/guard.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { AccountRole, GlobalPermission, OrganizationRole, OrgPermission } from "../enums";
|
|
2
|
-
import { AccessContext } from "../types";
|
|
3
|
-
export declare const globalRolePermissions: Record<AccountRole, readonly GlobalPermission[]>;
|
|
4
|
-
export declare const orgRolePermissions: Record<OrganizationRole, readonly OrgPermission[]>;
|
|
5
|
-
/**
|
|
6
|
-
* Updated isAllowed
|
|
7
|
-
* Since memberships are no longer in the JWT, this function
|
|
8
|
-
* primarily validates Global permissions.
|
|
9
|
-
*/
|
|
10
|
-
export declare const isAllowed: (ctx: AccessContext, permission: GlobalPermission | OrgPermission) => boolean;
|
package/dist/security/guard.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isAllowed = exports.orgRolePermissions = exports.globalRolePermissions = void 0;
|
|
4
|
-
const enums_1 = require("../enums");
|
|
5
|
-
exports.globalRolePermissions = {
|
|
6
|
-
[enums_1.AccountRole.SUPERADMIN]: Object.values(enums_1.GlobalPermission), // Superadmin gets everything
|
|
7
|
-
[enums_1.AccountRole.ADMIN]: [
|
|
8
|
-
enums_1.GlobalPermission.MANAGE_USERS,
|
|
9
|
-
enums_1.GlobalPermission.SUPPORT_ACTIONS,
|
|
10
|
-
enums_1.GlobalPermission.VIEW_SYSTEM_LOGS
|
|
11
|
-
],
|
|
12
|
-
[enums_1.AccountRole.SUPPORT]: [enums_1.GlobalPermission.SUPPORT_ACTIONS],
|
|
13
|
-
[enums_1.AccountRole.MODERATOR]: [enums_1.GlobalPermission.MODERATE_CONTENT],
|
|
14
|
-
[enums_1.AccountRole.USER]: []
|
|
15
|
-
};
|
|
16
|
-
// We keep this here as a reference for local service lookups
|
|
17
|
-
exports.orgRolePermissions = {
|
|
18
|
-
[enums_1.OrganizationRole.OWNER]: [enums_1.OrgPermission.MANAGE_ORG, enums_1.OrgPermission.MANAGE_MEMBERS],
|
|
19
|
-
[enums_1.OrganizationRole.ADMIN]: [enums_1.OrgPermission.MANAGE_MEMBERS],
|
|
20
|
-
[enums_1.OrganizationRole.EDITOR]: [],
|
|
21
|
-
[enums_1.OrganizationRole.VIEWER]: [],
|
|
22
|
-
};
|
|
23
|
-
const canPerformGlobalPermission = (role, permission) => {
|
|
24
|
-
return exports.globalRolePermissions[role]?.includes(permission) ?? false;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Updated isAllowed
|
|
28
|
-
* Since memberships are no longer in the JWT, this function
|
|
29
|
-
* primarily validates Global permissions.
|
|
30
|
-
*/
|
|
31
|
-
const isAllowed = (ctx, permission) => {
|
|
32
|
-
// 1. Check if it's a Global Permission (Handled via JWT role)
|
|
33
|
-
if (Object.values(enums_1.GlobalPermission).includes(permission)) {
|
|
34
|
-
return canPerformGlobalPermission(ctx.accountRole, permission);
|
|
35
|
-
}
|
|
36
|
-
// 2. Org Permissions are now handled by specific "Tenant Middleware"
|
|
37
|
-
// within the microservices because they require a DB check.
|
|
38
|
-
return false;
|
|
39
|
-
};
|
|
40
|
-
exports.isAllowed = isAllowed;
|
package/dist/security/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./guard";
|
package/dist/types/express.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import "express-serve-static-core";
|
|
2
|
-
import { JWTPayload } from "jose";
|
|
3
|
-
import { AccountRole } from "../enums";
|
|
4
|
-
export interface AccessContext {
|
|
5
|
-
accountId: string;
|
|
6
|
-
accountRole: AccountRole;
|
|
7
|
-
}
|
|
8
|
-
export interface InternalJwtPayload extends JWTPayload {
|
|
9
|
-
jti: string;
|
|
10
|
-
accountId?: string;
|
|
11
|
-
accountRole?: string;
|
|
12
|
-
isMachine?: boolean;
|
|
13
|
-
}
|
|
14
|
-
declare module "express-serve-static-core" {
|
|
15
|
-
interface Request {
|
|
16
|
-
id: string;
|
|
17
|
-
validated?: any;
|
|
18
|
-
accessContext?: AccessContext;
|
|
19
|
-
internalAuth?: InternalJwtPayload;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export {};
|
package/dist/types/express.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DATE SERIALIZATION UTILS (@discover-cloud/shared)
|
|
3
|
-
* ─────────────────────────────────────────────────────
|
|
4
|
-
* Converts Date objects to ISO 8601 strings for HTTP responses.
|
|
5
|
-
* Use these in toDto() mappers — never call .toISOString() inline.
|
|
6
|
-
*
|
|
7
|
-
* Why centralise this?
|
|
8
|
-
* DTOs cross HTTP boundaries where JSON has no Date type. Domain models
|
|
9
|
-
* keep Date objects internally; these helpers handle the conversion at
|
|
10
|
-
* the boundary in a single consistent place. If the serialisation format
|
|
11
|
-
* ever needs to change (e.g. add milliseconds truncation, force UTC
|
|
12
|
-
* suffix), there is one place to update.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* toIso
|
|
16
|
-
* Converts a Date to an ISO 8601 string.
|
|
17
|
-
* Use for non-nullable timestamp fields in DTOs.
|
|
18
|
-
*/
|
|
19
|
-
export declare const toIso: (date: Date) => string;
|
|
20
|
-
/**
|
|
21
|
-
* toIsoOrNull
|
|
22
|
-
* Converts a Date to an ISO 8601 string, or returns null if the value
|
|
23
|
-
* is absent. Use for nullable timestamp fields (e.g. revokedAt, deletedAt).
|
|
24
|
-
*/
|
|
25
|
-
export declare const toIsoOrNull: (date: Date | null | undefined) => string | null;
|
package/dist/utils/date.utils.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* DATE SERIALIZATION UTILS (@discover-cloud/shared)
|
|
4
|
-
* ─────────────────────────────────────────────────────
|
|
5
|
-
* Converts Date objects to ISO 8601 strings for HTTP responses.
|
|
6
|
-
* Use these in toDto() mappers — never call .toISOString() inline.
|
|
7
|
-
*
|
|
8
|
-
* Why centralise this?
|
|
9
|
-
* DTOs cross HTTP boundaries where JSON has no Date type. Domain models
|
|
10
|
-
* keep Date objects internally; these helpers handle the conversion at
|
|
11
|
-
* the boundary in a single consistent place. If the serialisation format
|
|
12
|
-
* ever needs to change (e.g. add milliseconds truncation, force UTC
|
|
13
|
-
* suffix), there is one place to update.
|
|
14
|
-
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.toIsoOrNull = exports.toIso = void 0;
|
|
17
|
-
/**
|
|
18
|
-
* toIso
|
|
19
|
-
* Converts a Date to an ISO 8601 string.
|
|
20
|
-
* Use for non-nullable timestamp fields in DTOs.
|
|
21
|
-
*/
|
|
22
|
-
const toIso = (date) => date.toISOString();
|
|
23
|
-
exports.toIso = toIso;
|
|
24
|
-
/**
|
|
25
|
-
* toIsoOrNull
|
|
26
|
-
* Converts a Date to an ISO 8601 string, or returns null if the value
|
|
27
|
-
* is absent. Use for nullable timestamp fields (e.g. revokedAt, deletedAt).
|
|
28
|
-
*/
|
|
29
|
-
const toIsoOrNull = (date) => date ? date.toISOString() : null;
|
|
30
|
-
exports.toIsoOrNull = toIsoOrNull;
|