@discover-cloud/shared 1.3.0 → 1.5.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/permission-cache.service.js +27 -11
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +17 -0
- 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 +1 -1
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +17 -0
- 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 +16 -1
- package/dist/dtos/cloud-service.dto.d.ts +9 -3
- package/dist/dtos/index.d.ts +6 -5
- package/dist/dtos/index.js +1 -0
- package/dist/dtos/insights-service.dto.d.ts +174 -13
- package/dist/dtos/response.dto.d.ts +6 -0
- package/dist/dtos/user-service.dto.d.ts +52 -7
- package/dist/enums/domain.enums.d.ts +52 -26
- package/dist/enums/domain.enums.js +64 -40
- package/dist/enums/index.d.ts +2 -2
- package/dist/errors/app-error.js +1 -1
- package/dist/errors/http-errors.d.ts +1 -1
- package/dist/errors/http-errors.js +26 -26
- package/dist/errors/index.d.ts +2 -2
- package/dist/http/index.d.ts +2 -2
- package/dist/http/request-context.d.ts +2 -2
- package/dist/http/request-context.js +5 -5
- package/dist/http/service-client.d.ts +2 -2
- package/dist/http/service-client.js +2 -2
- package/dist/index.d.ts +11 -10
- package/dist/index.js +1 -0
- package/dist/jwt/index.d.ts +3 -3
- package/dist/jwt/machine-jwt-verifier.d.ts +1 -1
- package/dist/jwt/machine-jwt-verifier.js +13 -14
- package/dist/jwt/machine-token-client.d.ts +1 -1
- package/dist/jwt/machine-token-client.js +8 -8
- package/dist/jwt/user-jwt-verifier.d.ts +1 -1
- package/dist/jwt/user-jwt-verifier.js +17 -16
- package/dist/messaging/audit.publisher.d.ts +14 -0
- package/dist/messaging/audit.publisher.js +49 -0
- package/dist/messaging/index.d.ts +1 -0
- package/dist/messaging/index.js +1 -0
- package/dist/messaging/rabbitmq.client.d.ts +17 -5
- package/dist/messaging/rabbitmq.client.js +83 -36
- package/dist/middleware/error-handler.middleware.d.ts +2 -2
- package/dist/middleware/error-handler.middleware.js +10 -10
- package/dist/middleware/index.d.ts +10 -10
- package/dist/middleware/request-context.middleware.d.ts +1 -12
- package/dist/middleware/request-context.middleware.js +17 -42
- package/dist/middleware/require-auth.middleware.d.ts +3 -19
- package/dist/middleware/require-auth.middleware.js +17 -26
- package/dist/middleware/require-machine.middleware.d.ts +2 -27
- package/dist/middleware/require-machine.middleware.js +30 -25
- package/dist/middleware/require-org-permission-from-body.middleware.js +1 -2
- package/dist/middleware/require-org-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-org-permission.middleware.js +28 -28
- package/dist/middleware/require-platform-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-platform-permission.middleware.js +10 -10
- package/dist/middleware/require-user.middleware.d.ts +1 -1
- package/dist/middleware/require-user.middleware.js +2 -2
- package/dist/middleware/require-workspace-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-workspace-permission.middleware.js +25 -25
- package/dist/middleware/validate.middleware.d.ts +3 -3
- package/dist/middleware/validate.middleware.js +1 -1
- package/dist/types/audit.types.d.ts +39 -0
- package/dist/types/audit.types.js +30 -0
- package/dist/types/express.types.d.ts +14 -7
- package/dist/types/express.types.js +10 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/env.util.js +1 -1
- package/dist/utils/index.d.ts +5 -5
- package/dist/utils/logger.util.d.ts +5 -0
- package/dist/utils/logger.util.js +46 -8
- 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 +1 -1
- package/dist/utils/url-safety.util.js +4 -1
- package/package.json +1 -1
package/dist/enums/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './permissions.enums';
|
|
2
|
+
export * from './domain.enums';
|
package/dist/errors/app-error.js
CHANGED
|
@@ -23,7 +23,7 @@ class AppError extends Error {
|
|
|
23
23
|
this.statusCode = statusCode;
|
|
24
24
|
this.details = details;
|
|
25
25
|
this.name = this.constructor.name;
|
|
26
|
-
if (typeof Error.captureStackTrace ===
|
|
26
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
27
27
|
Error.captureStackTrace(this, this.constructor);
|
|
28
28
|
}
|
|
29
29
|
Object.freeze(this);
|
|
@@ -14,8 +14,8 @@ class BadRequestError extends app_error_1.AppError {
|
|
|
14
14
|
* @param {string} [message] - Error explanation message.
|
|
15
15
|
* @param {unknown} [details] - Detailed error context.
|
|
16
16
|
*/
|
|
17
|
-
constructor(message =
|
|
18
|
-
super(
|
|
17
|
+
constructor(message = 'Bad Request', details) {
|
|
18
|
+
super('BAD_REQUEST', 400, message, details);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
exports.BadRequestError = BadRequestError;
|
|
@@ -27,8 +27,8 @@ class UnauthorizedError extends app_error_1.AppError {
|
|
|
27
27
|
* @param {string} [message] - Error explanation message.
|
|
28
28
|
* @param {unknown} [details] - Detailed error context.
|
|
29
29
|
*/
|
|
30
|
-
constructor(message =
|
|
31
|
-
super(
|
|
30
|
+
constructor(message = 'Unauthorized', details) {
|
|
31
|
+
super('UNAUTHORIZED', 401, message, details);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
exports.UnauthorizedError = UnauthorizedError;
|
|
@@ -40,8 +40,8 @@ class ForbiddenError extends app_error_1.AppError {
|
|
|
40
40
|
* @param {string} [message] - Error explanation message.
|
|
41
41
|
* @param {unknown} [details] - Detailed error context.
|
|
42
42
|
*/
|
|
43
|
-
constructor(message =
|
|
44
|
-
super(
|
|
43
|
+
constructor(message = 'Forbidden', details) {
|
|
44
|
+
super('FORBIDDEN', 403, message, details);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
exports.ForbiddenError = ForbiddenError;
|
|
@@ -53,8 +53,8 @@ class NotFoundError extends app_error_1.AppError {
|
|
|
53
53
|
* @param {string} [message] - Error explanation message.
|
|
54
54
|
* @param {unknown} [details] - Detailed error context.
|
|
55
55
|
*/
|
|
56
|
-
constructor(message =
|
|
57
|
-
super(
|
|
56
|
+
constructor(message = 'Not Found', details) {
|
|
57
|
+
super('NOT_FOUND', 404, message, details);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
exports.NotFoundError = NotFoundError;
|
|
@@ -66,8 +66,8 @@ class GoneError extends app_error_1.AppError {
|
|
|
66
66
|
* @param {string} [message] - Error explanation message.
|
|
67
67
|
* @param {unknown} [details] - Detailed error context.
|
|
68
68
|
*/
|
|
69
|
-
constructor(message =
|
|
70
|
-
super(
|
|
69
|
+
constructor(message = 'Gone', details) {
|
|
70
|
+
super('GONE', 410, message, details);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
exports.GoneError = GoneError;
|
|
@@ -79,8 +79,8 @@ class ConflictError extends app_error_1.AppError {
|
|
|
79
79
|
* @param {string} [message] - Error explanation message.
|
|
80
80
|
* @param {unknown} [details] - Detailed error context.
|
|
81
81
|
*/
|
|
82
|
-
constructor(message =
|
|
83
|
-
super(
|
|
82
|
+
constructor(message = 'Conflict', details) {
|
|
83
|
+
super('CONFLICT', 409, message, details);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
exports.ConflictError = ConflictError;
|
|
@@ -92,8 +92,8 @@ class UnprocessableEntityError extends app_error_1.AppError {
|
|
|
92
92
|
* @param {string} [message] - Error explanation message.
|
|
93
93
|
* @param {unknown} [details] - Detailed error context.
|
|
94
94
|
*/
|
|
95
|
-
constructor(message =
|
|
96
|
-
super(
|
|
95
|
+
constructor(message = 'Unprocessable Entity', details) {
|
|
96
|
+
super('UNPROCESSABLE_ENTITY', 422, message, details);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
exports.UnprocessableEntityError = UnprocessableEntityError;
|
|
@@ -105,8 +105,8 @@ class TooManyRequestsError extends app_error_1.AppError {
|
|
|
105
105
|
* @param {string} [message] - Error explanation message.
|
|
106
106
|
* @param {unknown} [details] - Detailed error context.
|
|
107
107
|
*/
|
|
108
|
-
constructor(message =
|
|
109
|
-
super(
|
|
108
|
+
constructor(message = 'Too Many Requests', details) {
|
|
109
|
+
super('TOO_MANY_REQUESTS', 429, message, details);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
exports.TooManyRequestsError = TooManyRequestsError;
|
|
@@ -118,8 +118,8 @@ class InternalServerError extends app_error_1.AppError {
|
|
|
118
118
|
* @param {string} [message] - Error explanation message.
|
|
119
119
|
* @param {unknown} [details] - Detailed error context.
|
|
120
120
|
*/
|
|
121
|
-
constructor(message =
|
|
122
|
-
super(
|
|
121
|
+
constructor(message = 'Internal Server Error', details) {
|
|
122
|
+
super('INTERNAL_SERVER_ERROR', 500, message, details);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
exports.InternalServerError = InternalServerError;
|
|
@@ -131,8 +131,8 @@ class ServiceUnavailableError extends app_error_1.AppError {
|
|
|
131
131
|
* @param {string} [message] - Error explanation message.
|
|
132
132
|
* @param {unknown} [details] - Detailed error context.
|
|
133
133
|
*/
|
|
134
|
-
constructor(message =
|
|
135
|
-
super(
|
|
134
|
+
constructor(message = 'Service Unavailable', details) {
|
|
135
|
+
super('SERVICE_UNAVAILABLE', 503, message, details);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
@@ -144,8 +144,8 @@ exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
|
144
144
|
* prompt rather than treating this as a generic auth failure.
|
|
145
145
|
*/
|
|
146
146
|
class AccountPendingDeletionError extends app_error_1.AppError {
|
|
147
|
-
constructor(message =
|
|
148
|
-
super(
|
|
147
|
+
constructor(message = 'This account is scheduled for deletion. Recover it within the 30-day window.', details) {
|
|
148
|
+
super('ACCOUNT_PENDING_DELETION', 403, message, details);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
exports.AccountPendingDeletionError = AccountPendingDeletionError;
|
|
@@ -157,8 +157,8 @@ exports.AccountPendingDeletionError = AccountPendingDeletionError;
|
|
|
157
157
|
* prompt the user to restore the organization instead.
|
|
158
158
|
*/
|
|
159
159
|
class OrganizationPendingDeletionError extends app_error_1.AppError {
|
|
160
|
-
constructor(message =
|
|
161
|
-
super(
|
|
160
|
+
constructor(message = 'This organization is scheduled for deletion. Restore it within the 30-day window.', details) {
|
|
161
|
+
super('ORGANIZATION_PENDING_DELETION', 403, message, details);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
exports.OrganizationPendingDeletionError = OrganizationPendingDeletionError;
|
|
@@ -170,8 +170,8 @@ exports.OrganizationPendingDeletionError = OrganizationPendingDeletionError;
|
|
|
170
170
|
* prompt the user to restore the workspace instead.
|
|
171
171
|
*/
|
|
172
172
|
class WorkspacePendingDeletionError extends app_error_1.AppError {
|
|
173
|
-
constructor(message =
|
|
174
|
-
super(
|
|
173
|
+
constructor(message = 'This workspace is scheduled for deletion. Restore it within the 30-day window.', details) {
|
|
174
|
+
super('WORKSPACE_PENDING_DELETION', 403, message, details);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
exports.WorkspacePendingDeletionError = WorkspacePendingDeletionError;
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './app-error';
|
|
2
|
+
export * from './http-errors';
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './service-client';
|
|
2
|
+
export * from './request-context';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Request } from
|
|
2
|
-
import { UserContext, MachineContext, RequestMetadataContext } from
|
|
1
|
+
import { Request } from 'express';
|
|
2
|
+
import { UserContext, MachineContext, RequestMetadataContext } from '../types';
|
|
3
3
|
export declare function getUserContext(req: Request): UserContext;
|
|
4
4
|
export declare function getMachineContext(req: Request): MachineContext;
|
|
5
5
|
export declare function getRequestContext(req: Request): RequestMetadataContext;
|
|
@@ -8,21 +8,21 @@ exports.getValidatedParams = getValidatedParams;
|
|
|
8
8
|
exports.getValidatedQuery = getValidatedQuery;
|
|
9
9
|
function getUserContext(req) {
|
|
10
10
|
const ctx = req.authContext;
|
|
11
|
-
if (!ctx || ctx.type !==
|
|
12
|
-
throw new Error(
|
|
11
|
+
if (!ctx || ctx.type !== 'user') {
|
|
12
|
+
throw new Error('User context missing. Did you forget requireUser?');
|
|
13
13
|
}
|
|
14
14
|
return ctx;
|
|
15
15
|
}
|
|
16
16
|
function getMachineContext(req) {
|
|
17
17
|
const ctx = req.authContext;
|
|
18
|
-
if (!ctx || ctx.type !==
|
|
19
|
-
throw new Error(
|
|
18
|
+
if (!ctx || ctx.type !== 'machine') {
|
|
19
|
+
throw new Error('Machine context missing. Did you forget requireMachine?');
|
|
20
20
|
}
|
|
21
21
|
return ctx;
|
|
22
22
|
}
|
|
23
23
|
function getRequestContext(req) {
|
|
24
24
|
if (!req.requestContext) {
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error('Request context missing. Did you forget requestContext middleware?');
|
|
26
26
|
}
|
|
27
27
|
return req.requestContext;
|
|
28
28
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Handles automatic JWT forwarding, request correlation tracking via header propagation,
|
|
5
5
|
* and automatic retry behaviors on transient failures.
|
|
6
6
|
*/
|
|
7
|
-
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from
|
|
8
|
-
import { Request } from
|
|
7
|
+
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
8
|
+
import { Request } from 'express';
|
|
9
9
|
/**
|
|
10
10
|
* ServiceClient encapsulates an AxiosInstance configured for service-to-service HTTP requests.
|
|
11
11
|
* Supports token forwarding (Pattern 1) and secure internal calls (Pattern 2).
|
|
@@ -151,14 +151,14 @@ class ServiceClient {
|
|
|
151
151
|
*/
|
|
152
152
|
mergeAuth(req, config) {
|
|
153
153
|
const authHeader = req.headers.authorization;
|
|
154
|
-
const upstream = req.headers[
|
|
154
|
+
const upstream = req.headers['x-request-id'];
|
|
155
155
|
const requestId = req.id ?? (Array.isArray(upstream) ? upstream[0] : upstream) ?? `fallback-${Date.now()}`;
|
|
156
156
|
return {
|
|
157
157
|
...config,
|
|
158
158
|
headers: {
|
|
159
159
|
...config?.headers,
|
|
160
160
|
...(authHeader ? { Authorization: authHeader } : {}),
|
|
161
|
-
|
|
161
|
+
'x-request-id': requestId,
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
1
|
+
export * from './enums';
|
|
2
|
+
export * from './errors';
|
|
3
|
+
export * from './dtos';
|
|
4
|
+
export * from './contracts';
|
|
5
|
+
export * from './authorization';
|
|
6
|
+
export * from './utils';
|
|
7
|
+
export * from './middleware';
|
|
8
|
+
export * from './types';
|
|
9
|
+
export * from './jwt';
|
|
10
|
+
export * from './http';
|
|
11
|
+
export * from './messaging';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./enums"), exports);
|
|
18
18
|
__exportStar(require("./errors"), exports);
|
|
19
19
|
__exportStar(require("./dtos"), exports);
|
|
20
|
+
__exportStar(require("./contracts"), exports);
|
|
20
21
|
__exportStar(require("./authorization"), exports);
|
|
21
22
|
__exportStar(require("./utils"), exports);
|
|
22
23
|
__exportStar(require("./middleware"), exports);
|
package/dist/jwt/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './user-jwt-verifier';
|
|
2
|
+
export * from './machine-jwt-verifier';
|
|
3
|
+
export * from './machine-token-client';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file machine-jwt-verifier.ts
|
|
3
3
|
* @description Verifies service-to-service RS256 machine JWTs issued by auth-service.
|
|
4
4
|
*/
|
|
5
|
-
import { MachineTokenPayload } from
|
|
5
|
+
import { MachineTokenPayload } from '../types';
|
|
6
6
|
/**
|
|
7
7
|
* Verifies auth-service issued machine tokens used for service-to-service calls.
|
|
8
8
|
*/
|
|
@@ -39,19 +39,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.MachineJwtVerifier = void 0;
|
|
41
41
|
const jose = __importStar(require("jose"));
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
42
|
+
const env_util_1 = require("../utils/env.util");
|
|
43
|
+
const CLOCK_TOLERANCE_SECONDS = Number((0, env_util_1.getEnv)('JWT_CLOCK_TOLERANCE_SECONDS'));
|
|
44
|
+
const JWKS_CACHE_MAX_AGE_MS = Number((0, env_util_1.getEnv)('JWKS_CACHE_MAX_AGE_MS'));
|
|
45
|
+
const JWKS_FETCH_TIMEOUT_MS = Number((0, env_util_1.getEnv)('JWKS_FETCH_TIMEOUT_MS'));
|
|
45
46
|
/**
|
|
46
47
|
* Verifies auth-service issued machine tokens used for service-to-service calls.
|
|
47
48
|
*/
|
|
48
49
|
class MachineJwtVerifier {
|
|
49
50
|
constructor(authServiceJwksUri) {
|
|
50
|
-
const jwksUri = authServiceJwksUri ??
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.issuer = process.env.MACHINE_JWT_ISSUER ?? "discover-cloud:auth-service";
|
|
54
|
-
this.audience = process.env.MACHINE_JWT_AUDIENCE ?? "discover-cloud:services";
|
|
51
|
+
const jwksUri = authServiceJwksUri ?? (0, env_util_1.getEnv)('AUTH_JWKS_URI');
|
|
52
|
+
this.issuer = (0, env_util_1.getEnv)('MACHINE_JWT_ISSUER');
|
|
53
|
+
this.audience = (0, env_util_1.getEnv)('MACHINE_JWT_AUDIENCE');
|
|
55
54
|
this.jwks = jose.createRemoteJWKSet(new URL(jwksUri), {
|
|
56
55
|
cacheMaxAge: JWKS_CACHE_MAX_AGE_MS,
|
|
57
56
|
timeoutDuration: JWKS_FETCH_TIMEOUT_MS,
|
|
@@ -67,16 +66,16 @@ class MachineJwtVerifier {
|
|
|
67
66
|
const { payload } = await jose.jwtVerify(token, this.jwks, {
|
|
68
67
|
issuer: this.issuer,
|
|
69
68
|
audience: this.audience,
|
|
70
|
-
algorithms: [
|
|
69
|
+
algorithms: ['RS256'],
|
|
71
70
|
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
72
71
|
});
|
|
73
|
-
if (payload.typ !==
|
|
74
|
-
throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "machine", got "${String(payload.typ)}"`, payload,
|
|
72
|
+
if (payload.typ !== 'machine') {
|
|
73
|
+
throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "machine", got "${String(payload.typ)}"`, payload, 'typ', 'check_failed');
|
|
75
74
|
}
|
|
76
|
-
if (typeof payload.serviceId !==
|
|
77
|
-
typeof payload.jti !==
|
|
75
|
+
if (typeof payload.serviceId !== 'string' ||
|
|
76
|
+
typeof payload.jti !== 'string' ||
|
|
78
77
|
!Array.isArray(payload.scopes)) {
|
|
79
|
-
throw new jose.errors.JWTClaimValidationFailed(
|
|
78
|
+
throw new jose.errors.JWTClaimValidationFailed('Missing required machine token claims', payload, 'payload', 'missing');
|
|
80
79
|
}
|
|
81
80
|
return payload;
|
|
82
81
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description HTTP client for fetching and caching short-lived machine JWTs
|
|
4
4
|
* from the Auth Service for service-to-service communication.
|
|
5
5
|
*/
|
|
6
|
-
import { ILogger } from
|
|
6
|
+
import { ILogger } from '../utils';
|
|
7
7
|
/**
|
|
8
8
|
* Handles retrieving and caching auth-service issued machine JWTs.
|
|
9
9
|
*/
|
|
@@ -66,12 +66,12 @@ class MachineTokenClient {
|
|
|
66
66
|
* @returns {Promise<string>} The freshly issued machine token.
|
|
67
67
|
*/
|
|
68
68
|
async fetchToken(requestId) {
|
|
69
|
-
const url = `${this.authServiceUrl.replace(/\/$/,
|
|
70
|
-
this.logger.debug({ serviceId: this.serviceId, requestId },
|
|
69
|
+
const url = `${this.authServiceUrl.replace(/\/$/, '')}/internal/machine-token`;
|
|
70
|
+
this.logger.debug({ serviceId: this.serviceId, requestId }, 'Fetching machine token');
|
|
71
71
|
const response = await fetch(url, {
|
|
72
|
-
method:
|
|
72
|
+
method: 'POST',
|
|
73
73
|
headers: {
|
|
74
|
-
|
|
74
|
+
'content-type': 'application/json',
|
|
75
75
|
},
|
|
76
76
|
body: JSON.stringify({
|
|
77
77
|
serviceId: this.serviceId,
|
|
@@ -80,15 +80,15 @@ class MachineTokenClient {
|
|
|
80
80
|
}),
|
|
81
81
|
});
|
|
82
82
|
if (!response.ok) {
|
|
83
|
-
const text = await response.text().catch(() =>
|
|
83
|
+
const text = await response.text().catch(() => '(unreadable)');
|
|
84
84
|
throw new Error(`Machine token request failed (${response.status}): ${text}`);
|
|
85
85
|
}
|
|
86
86
|
const body = (await response.json());
|
|
87
87
|
if (!body.success ||
|
|
88
88
|
!body.data?.accessToken ||
|
|
89
|
-
typeof body.data.expiresIn !==
|
|
89
|
+
typeof body.data.expiresIn !== 'number' ||
|
|
90
90
|
body.data.expiresIn <= 0) {
|
|
91
|
-
throw new Error(
|
|
91
|
+
throw new Error('Invalid machine token response');
|
|
92
92
|
}
|
|
93
93
|
const now = Math.floor(Date.now() / 1000);
|
|
94
94
|
this.cache = {
|
|
@@ -98,7 +98,7 @@ class MachineTokenClient {
|
|
|
98
98
|
this.logger.debug({
|
|
99
99
|
serviceId: this.serviceId,
|
|
100
100
|
expiresAt: this.cache.expiresAt,
|
|
101
|
-
},
|
|
101
|
+
}, 'Machine token cached');
|
|
102
102
|
return this.cache.token;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file user-jwt-verifier.ts
|
|
3
3
|
* @description Verifies user RS256 access tokens issued by auth-service.
|
|
4
4
|
*/
|
|
5
|
-
import { AccessTokenPayload, UserContext } from
|
|
5
|
+
import { AccessTokenPayload, UserContext } from '../types';
|
|
6
6
|
/**
|
|
7
7
|
* Verifies auth-service issued user access tokens.
|
|
8
8
|
*/
|
|
@@ -39,17 +39,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.UserJwtVerifier = void 0;
|
|
41
41
|
const jose = __importStar(require("jose"));
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
42
|
+
const env_util_1 = require("../utils/env.util");
|
|
43
|
+
const CLOCK_TOLERANCE_SECONDS = Number((0, env_util_1.getEnv)('JWT_CLOCK_TOLERANCE_SECONDS'));
|
|
44
|
+
const JWKS_CACHE_MAX_AGE_MS = Number((0, env_util_1.getEnv)('JWKS_CACHE_MAX_AGE_MS'));
|
|
45
|
+
const JWKS_FETCH_TIMEOUT_MS = Number((0, env_util_1.getEnv)('JWKS_FETCH_TIMEOUT_MS'));
|
|
45
46
|
/**
|
|
46
47
|
* Verifies auth-service issued user access tokens.
|
|
47
48
|
*/
|
|
48
49
|
class UserJwtVerifier {
|
|
49
50
|
constructor() {
|
|
50
|
-
const authJwksUri =
|
|
51
|
-
this.issuer =
|
|
52
|
-
this.audience =
|
|
51
|
+
const authJwksUri = (0, env_util_1.getEnv)('AUTH_JWKS_URI');
|
|
52
|
+
this.issuer = (0, env_util_1.getEnv)('EXTERNAL_JWT_ISSUER');
|
|
53
|
+
this.audience = (0, env_util_1.getEnv)('EXTERNAL_JWT_AUDIENCE');
|
|
53
54
|
this.jwks = jose.createRemoteJWKSet(new URL(authJwksUri), {
|
|
54
55
|
cacheMaxAge: JWKS_CACHE_MAX_AGE_MS,
|
|
55
56
|
timeoutDuration: JWKS_FETCH_TIMEOUT_MS,
|
|
@@ -65,18 +66,18 @@ class UserJwtVerifier {
|
|
|
65
66
|
const { payload } = await jose.jwtVerify(token, this.jwks, {
|
|
66
67
|
issuer: this.issuer,
|
|
67
68
|
audience: this.audience,
|
|
68
|
-
algorithms: [
|
|
69
|
+
algorithms: ['RS256'],
|
|
69
70
|
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
70
71
|
});
|
|
71
|
-
if (payload.typ !==
|
|
72
|
-
throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "access", got "${String(payload.typ)}"`, payload,
|
|
72
|
+
if (payload.typ !== 'access') {
|
|
73
|
+
throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "access", got "${String(payload.typ)}"`, payload, 'typ', 'check_failed');
|
|
73
74
|
}
|
|
74
|
-
if (typeof payload.accountId !==
|
|
75
|
-
typeof payload.sessionId !==
|
|
76
|
-
typeof payload.jti !==
|
|
77
|
-
typeof payload.iat !==
|
|
78
|
-
typeof payload.exp !==
|
|
79
|
-
throw new jose.errors.JWTClaimValidationFailed(
|
|
75
|
+
if (typeof payload.accountId !== 'string' ||
|
|
76
|
+
typeof payload.sessionId !== 'string' ||
|
|
77
|
+
typeof payload.jti !== 'string' ||
|
|
78
|
+
typeof payload.iat !== 'number' ||
|
|
79
|
+
typeof payload.exp !== 'number') {
|
|
80
|
+
throw new jose.errors.JWTClaimValidationFailed('Missing required access token claims', payload, 'payload', 'missing');
|
|
80
81
|
}
|
|
81
82
|
return payload;
|
|
82
83
|
}
|
|
@@ -91,7 +92,7 @@ class UserJwtVerifier {
|
|
|
91
92
|
return {
|
|
92
93
|
payload,
|
|
93
94
|
context: {
|
|
94
|
-
type:
|
|
95
|
+
type: 'user',
|
|
95
96
|
accountId: payload.accountId,
|
|
96
97
|
sessionId: payload.sessionId,
|
|
97
98
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file audit.publisher.ts
|
|
3
|
+
* @description Publishes audit events with automatic request context enrichment.
|
|
4
|
+
*/
|
|
5
|
+
import { RabbitMQClient } from '../messaging';
|
|
6
|
+
import { RecordAuditActionInput } from '../dtos';
|
|
7
|
+
import { ILogger } from '../utils/logger.util';
|
|
8
|
+
export type PublishAuditInput = Omit<RecordAuditActionInput, 'actorId' | 'actorType' | 'requestId'> & Partial<Pick<RecordAuditActionInput, 'actorId' | 'actorType' | 'requestId'>>;
|
|
9
|
+
export declare class AuditPublisher {
|
|
10
|
+
private readonly mq;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
constructor(mq: RabbitMQClient, logger: ILogger);
|
|
13
|
+
publish(input: PublishAuditInput): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file audit.publisher.ts
|
|
4
|
+
* @description Publishes audit events with automatic request context enrichment.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AuditPublisher = void 0;
|
|
8
|
+
const dtos_1 = require("../dtos");
|
|
9
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
10
|
+
class AuditPublisher {
|
|
11
|
+
constructor(mq, logger) {
|
|
12
|
+
this.mq = mq;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
async publish(input) {
|
|
16
|
+
const ctx = request_context_storage_1.RequestContextProvider.get();
|
|
17
|
+
let actorType = dtos_1.AuditActorType.SYSTEM;
|
|
18
|
+
let actorId = null;
|
|
19
|
+
if (ctx?.actor) {
|
|
20
|
+
switch (ctx.actor.type) {
|
|
21
|
+
case 'user':
|
|
22
|
+
actorType = dtos_1.AuditActorType.USER;
|
|
23
|
+
actorId = ctx.actor.accountId;
|
|
24
|
+
break;
|
|
25
|
+
case 'machine':
|
|
26
|
+
actorType = dtos_1.AuditActorType.SERVICE;
|
|
27
|
+
actorId = ctx.actor.serviceId;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const payload = {
|
|
32
|
+
severity: dtos_1.AuditSeverity.INFO,
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
actorType,
|
|
35
|
+
actorId,
|
|
36
|
+
requestId: ctx?.requestId ?? null,
|
|
37
|
+
ipAddress: ctx?.ipAddress ?? null,
|
|
38
|
+
userAgent: ctx?.userAgent ?? null,
|
|
39
|
+
...input,
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
await this.mq.publishEvent(`audit.${input.action}`, payload);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
this.logger.error({ err, action: input.action }, 'Failed to publish audit event');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.AuditPublisher = AuditPublisher;
|
package/dist/messaging/index.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import { ILogger } from '../utils/logger.util';
|
|
1
2
|
export declare class RabbitMQClient {
|
|
2
3
|
private connection;
|
|
3
4
|
private channel;
|
|
4
|
-
private url;
|
|
5
|
-
private exchangeName;
|
|
6
|
-
|
|
5
|
+
private readonly url;
|
|
6
|
+
private readonly exchangeName;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private reconnecting;
|
|
9
|
+
constructor(url: string, logger?: ILogger);
|
|
10
|
+
/**
|
|
11
|
+
* Establishes a connection to RabbitMQ.
|
|
12
|
+
* Blocks until a connection is successfully established.
|
|
13
|
+
*/
|
|
7
14
|
connect(): Promise<void>;
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Background reconnect loop.
|
|
17
|
+
*/
|
|
18
|
+
private reconnect;
|
|
19
|
+
publishEvent(routingKey: string, payload: unknown): Promise<void>;
|
|
20
|
+
subscribe(queueName: string, routingKey: string, handler: (payload: unknown) => Promise<void>, options?: {
|
|
10
21
|
prefetch?: number;
|
|
11
22
|
}): Promise<void>;
|
|
12
23
|
close(): Promise<void>;
|
|
24
|
+
private delay;
|
|
13
25
|
}
|