@axova/shared 1.0.2 → 1.1.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/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/db.d.ts +34406 -1
- package/dist/lib/db.js +21 -1
- package/dist/middleware/storeOwnership.js +22 -3
- package/dist/middleware/storeValidationMiddleware.js +16 -39
- package/dist/schemas/admin/admin-schema.d.ts +2 -2
- package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +6 -6
- package/dist/schemas/common/common-schemas.d.ts +71 -71
- package/dist/schemas/compliance/compliance-schema.d.ts +20 -20
- package/dist/schemas/compliance/kyc-schema.d.ts +8 -8
- package/dist/schemas/customer/customer-schema.d.ts +18 -18
- package/dist/schemas/index.d.ts +28 -0
- package/dist/schemas/index.js +134 -3
- package/dist/schemas/inventory/inventory-tables.d.ts +188 -188
- package/dist/schemas/inventory/lot-tables.d.ts +102 -102
- package/dist/schemas/order/cart-schema.d.ts +2865 -0
- package/dist/schemas/order/cart-schema.js +396 -0
- package/dist/schemas/order/order-schema.d.ts +19 -19
- package/dist/schemas/order/order-schema.js +8 -2
- package/dist/schemas/product/discount-schema.d.ts +3 -3
- package/dist/schemas/product/product-schema.d.ts +3 -3
- package/dist/schemas/store/store-audit-schema.d.ts +20 -20
- package/dist/schemas/store/store-schema.d.ts +182 -2
- package/dist/schemas/store/store-schema.js +19 -0
- package/dist/schemas/store/storefront-config-schema.d.ts +434 -823
- package/dist/schemas/store/storefront-config-schema.js +35 -62
- package/dist/utils/subdomain.d.ts +1 -1
- package/dist/utils/subdomain.js +10 -15
- package/package.json +1 -1
- package/src/configs/index.ts +654 -654
- package/src/index.ts +26 -23
- package/src/interfaces/customer-events.ts +106 -106
- package/src/interfaces/inventory-events.ts +545 -545
- package/src/interfaces/inventory-types.ts +1004 -1004
- package/src/interfaces/order-events.ts +381 -381
- package/src/lib/auditLogger.ts +1117 -1117
- package/src/lib/authOrganization.ts +153 -153
- package/src/lib/db.ts +84 -64
- package/src/middleware/serviceAuth.ts +328 -328
- package/src/middleware/storeOwnership.ts +199 -181
- package/src/middleware/storeValidationMiddleware.ts +17 -50
- package/src/middleware/userAuth.ts +248 -248
- package/src/schemas/admin/admin-schema.ts +208 -208
- package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -180
- package/src/schemas/common/common-schemas.ts +108 -108
- package/src/schemas/compliance/compliance-schema.ts +927 -0
- package/src/schemas/compliance/kyc-schema.ts +649 -0
- package/src/schemas/customer/customer-schema.ts +576 -0
- package/src/schemas/index.ts +202 -3
- package/src/schemas/inventory/inventory-tables.ts +1927 -0
- package/src/schemas/inventory/lot-tables.ts +799 -0
- package/src/schemas/order/cart-schema.ts +652 -0
- package/src/schemas/order/order-schema.ts +1406 -0
- package/src/schemas/product/discount-relations.ts +44 -0
- package/src/schemas/product/discount-schema.ts +464 -0
- package/src/schemas/product/product-relations.ts +187 -0
- package/src/schemas/product/product-schema.ts +955 -0
- package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
- package/src/schemas/store/store-audit-schema.ts +1257 -0
- package/src/schemas/store/store-schema.ts +682 -0
- package/src/schemas/store/store-settings-schema.ts +231 -0
- package/src/schemas/store/storefront-config-schema.ts +382 -0
- package/src/schemas/types.ts +67 -67
- package/src/types/events.ts +646 -646
- package/src/utils/errorHandler.ts +44 -44
- package/src/utils/subdomain.ts +19 -23
- package/tsconfig.json +21 -21
|
@@ -1,328 +1,328 @@
|
|
|
1
|
-
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
-
import jwt from "jsonwebtoken";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
// Service claims schema
|
|
6
|
-
export const ServiceClaimsSchema = z.object({
|
|
7
|
-
service: z.string(),
|
|
8
|
-
permissions: z.array(z.string()),
|
|
9
|
-
iat: z.number(),
|
|
10
|
-
exp: z.number(),
|
|
11
|
-
iss: z.literal("axova-platform"),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export type ServiceClaims = z.infer<typeof ServiceClaimsSchema>;
|
|
15
|
-
|
|
16
|
-
// Service authentication configuration
|
|
17
|
-
export interface ServiceAuthConfig {
|
|
18
|
-
internalSecret: string;
|
|
19
|
-
tokenExpiry?: string; // Default: '1h'
|
|
20
|
-
requiredPermissions?: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Extended request interface
|
|
24
|
-
export interface AuthenticatedRequest extends FastifyRequest {
|
|
25
|
-
serviceClaims?: ServiceClaims;
|
|
26
|
-
isServiceAuthenticated?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Service authentication class
|
|
30
|
-
export class ServiceAuthenticator {
|
|
31
|
-
private secret: string;
|
|
32
|
-
private tokenExpiry: string;
|
|
33
|
-
|
|
34
|
-
constructor(config: ServiceAuthConfig) {
|
|
35
|
-
this.secret = config.internalSecret;
|
|
36
|
-
this.tokenExpiry = config.tokenExpiry || "1h";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Generate service token
|
|
40
|
-
generateServiceToken(service: string, permissions: string[] = []): string {
|
|
41
|
-
const payload: Omit<ServiceClaims, "iat" | "exp"> = {
|
|
42
|
-
service,
|
|
43
|
-
permissions,
|
|
44
|
-
iss: "axova-platform",
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return jwt.sign(payload, this.secret, {
|
|
48
|
-
expiresIn: this.tokenExpiry,
|
|
49
|
-
algorithm: "HS256",
|
|
50
|
-
} as jwt.SignOptions);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Verify service token
|
|
54
|
-
verifyServiceToken(token: string): ServiceClaims {
|
|
55
|
-
try {
|
|
56
|
-
const decoded = jwt.verify(token, this.secret, {
|
|
57
|
-
algorithms: ["HS256"],
|
|
58
|
-
issuer: "axova-platform",
|
|
59
|
-
}) as ServiceClaims;
|
|
60
|
-
|
|
61
|
-
// Validate with Zod schema
|
|
62
|
-
return ServiceClaimsSchema.parse(decoded);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Invalid service token: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Fastify middleware for service authentication
|
|
71
|
-
serviceAuthMiddleware(requiredPermissions: string[] = []) {
|
|
72
|
-
return async (request: AuthenticatedRequest, reply: FastifyReply) => {
|
|
73
|
-
try {
|
|
74
|
-
const authHeader = request.headers.authorization;
|
|
75
|
-
const serviceToken = request.headers["x-service-token"] as string;
|
|
76
|
-
|
|
77
|
-
let token: string | null = null;
|
|
78
|
-
|
|
79
|
-
// Check for Bearer token in Authorization header
|
|
80
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
81
|
-
token = authHeader.substring(7);
|
|
82
|
-
}
|
|
83
|
-
// Check for service token in custom header
|
|
84
|
-
else if (serviceToken) {
|
|
85
|
-
token = serviceToken;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!token) {
|
|
89
|
-
return reply.code(401).send({
|
|
90
|
-
error: "Service authentication required",
|
|
91
|
-
code: "MISSING_SERVICE_TOKEN",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// DEVELOPMENT MODE: Accept simple constant tokens
|
|
96
|
-
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
97
|
-
const developmentTokens = [
|
|
98
|
-
"dev-compliance-token",
|
|
99
|
-
"development-mode",
|
|
100
|
-
"compliance-dev-access",
|
|
101
|
-
"simple-dev-token",
|
|
102
|
-
"inventory-dev-access",
|
|
103
|
-
"product-dev-access",
|
|
104
|
-
"oxa-dev-access",
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
if (isDevelopment && developmentTokens.includes(token)) {
|
|
108
|
-
// In development, validate against a proper development service registry
|
|
109
|
-
// instead of using mock claims
|
|
110
|
-
try {
|
|
111
|
-
// TODO: Implement proper development service authentication
|
|
112
|
-
// For now, only allow specific development services with limited permissions
|
|
113
|
-
let servicePermissions: string[] = [];
|
|
114
|
-
let serviceName = "unknown-service";
|
|
115
|
-
|
|
116
|
-
// Grant permissions based on the specific development token
|
|
117
|
-
switch (token) {
|
|
118
|
-
case "inventory-dev-access":
|
|
119
|
-
serviceName = "inventory-service-dev";
|
|
120
|
-
servicePermissions = ["store:read", "store:write"];
|
|
121
|
-
break;
|
|
122
|
-
case "product-dev-access":
|
|
123
|
-
serviceName = "product-service-dev";
|
|
124
|
-
servicePermissions = ["store:read", "store:write"];
|
|
125
|
-
break;
|
|
126
|
-
case "compliance-dev-access":
|
|
127
|
-
serviceName = "compliance-service-dev";
|
|
128
|
-
servicePermissions = ["compliance:read", "compliance:write"];
|
|
129
|
-
break;
|
|
130
|
-
case "oxa-dev-access":
|
|
131
|
-
serviceName = "oxa-service-dev";
|
|
132
|
-
servicePermissions = ["store:read", "ai:moderate"];
|
|
133
|
-
break;
|
|
134
|
-
default:
|
|
135
|
-
throw new Error("Unknown development token");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const devClaims: ServiceClaims = {
|
|
139
|
-
service: serviceName,
|
|
140
|
-
permissions: servicePermissions,
|
|
141
|
-
iss: "axova-platform",
|
|
142
|
-
iat: Math.floor(Date.now() / 1000),
|
|
143
|
-
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24 hours
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
request.serviceClaims = devClaims;
|
|
147
|
-
request.isServiceAuthenticated = true;
|
|
148
|
-
console.log(
|
|
149
|
-
`🔧 Development mode: Service authenticated with token "${token}"`,
|
|
150
|
-
);
|
|
151
|
-
return; // Skip JWT verification in development
|
|
152
|
-
} catch (error) {
|
|
153
|
-
return reply.code(401).send({
|
|
154
|
-
error: "Invalid development token",
|
|
155
|
-
code: "INVALID_DEVELOPMENT_TOKEN",
|
|
156
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Verify the token
|
|
162
|
-
const claims = this.verifyServiceToken(token);
|
|
163
|
-
|
|
164
|
-
// Check required permissions
|
|
165
|
-
if (requiredPermissions.length > 0) {
|
|
166
|
-
const hasPermissions = requiredPermissions.every((perm) =>
|
|
167
|
-
claims.permissions.includes(perm),
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
if (!hasPermissions) {
|
|
171
|
-
return reply.code(403).send({
|
|
172
|
-
error: "Insufficient permissions",
|
|
173
|
-
code: "INSUFFICIENT_PERMISSIONS",
|
|
174
|
-
required: requiredPermissions,
|
|
175
|
-
granted: claims.permissions,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Attach claims to request
|
|
181
|
-
request.serviceClaims = claims;
|
|
182
|
-
request.isServiceAuthenticated = true;
|
|
183
|
-
|
|
184
|
-
console.log(`Service authenticated: ${claims.service}`);
|
|
185
|
-
} catch (error) {
|
|
186
|
-
return reply.code(401).send({
|
|
187
|
-
error: "Invalid service token",
|
|
188
|
-
code: "INVALID_SERVICE_TOKEN",
|
|
189
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Alternative HMAC-based authentication
|
|
196
|
-
generateHMACSignature(payload: string, timestamp: string): string {
|
|
197
|
-
const crypto = require("node:crypto");
|
|
198
|
-
const message = `${timestamp}.${payload}`;
|
|
199
|
-
return crypto
|
|
200
|
-
.createHmac("sha256", this.secret)
|
|
201
|
-
.update(message)
|
|
202
|
-
.digest("hex");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// HMAC middleware
|
|
206
|
-
hmacAuthMiddleware() {
|
|
207
|
-
return async (request: AuthenticatedRequest, reply: FastifyReply) => {
|
|
208
|
-
try {
|
|
209
|
-
const signature = request.headers["x-signature"] as string;
|
|
210
|
-
const timestamp = request.headers["x-timestamp"] as string;
|
|
211
|
-
const service = request.headers["x-service"] as string;
|
|
212
|
-
|
|
213
|
-
if (!signature || !timestamp || !service) {
|
|
214
|
-
return reply.code(401).send({
|
|
215
|
-
error: "HMAC authentication headers missing",
|
|
216
|
-
code: "MISSING_HMAC_HEADERS",
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Check timestamp (prevent replay attacks)
|
|
221
|
-
const now = Date.now();
|
|
222
|
-
const requestTime = Number.parseInt(timestamp);
|
|
223
|
-
const timeDiff = Math.abs(now - requestTime);
|
|
224
|
-
|
|
225
|
-
// Allow 5 minutes tolerance
|
|
226
|
-
if (timeDiff > 300000) {
|
|
227
|
-
return reply.code(401).send({
|
|
228
|
-
error: "Request timestamp too old",
|
|
229
|
-
code: "TIMESTAMP_EXPIRED",
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Get request body as string
|
|
234
|
-
const body = JSON.stringify(request.body || {});
|
|
235
|
-
const expectedSignature = this.generateHMACSignature(body, timestamp);
|
|
236
|
-
|
|
237
|
-
if (signature !== expectedSignature) {
|
|
238
|
-
return reply.code(401).send({
|
|
239
|
-
error: "Invalid HMAC signature",
|
|
240
|
-
code: "INVALID_HMAC_SIGNATURE",
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Set service info on request
|
|
245
|
-
request.serviceClaims = {
|
|
246
|
-
service,
|
|
247
|
-
permissions: [], // HMAC doesn't include permissions by default
|
|
248
|
-
iat: requestTime,
|
|
249
|
-
exp: requestTime + 300000, // 5 minutes
|
|
250
|
-
iss: "axova-platform",
|
|
251
|
-
};
|
|
252
|
-
request.isServiceAuthenticated = true;
|
|
253
|
-
|
|
254
|
-
console.log(`Service authenticated via HMAC: ${service}`);
|
|
255
|
-
} catch (error) {
|
|
256
|
-
return reply.code(401).send({
|
|
257
|
-
error: "HMAC authentication failed",
|
|
258
|
-
code: "HMAC_AUTH_FAILED",
|
|
259
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Singleton factory
|
|
267
|
-
let serviceAuthInstance: ServiceAuthenticator | null = null;
|
|
268
|
-
|
|
269
|
-
export function createServiceAuthenticator(
|
|
270
|
-
config: ServiceAuthConfig,
|
|
271
|
-
): ServiceAuthenticator {
|
|
272
|
-
if (!serviceAuthInstance) {
|
|
273
|
-
serviceAuthInstance = new ServiceAuthenticator(config);
|
|
274
|
-
}
|
|
275
|
-
return serviceAuthInstance;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function getServiceAuthenticator(): ServiceAuthenticator {
|
|
279
|
-
if (!serviceAuthInstance) {
|
|
280
|
-
throw new Error(
|
|
281
|
-
"Service authenticator not created. Call createServiceAuthenticator() first.",
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
return serviceAuthInstance;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Utility to get auth config from environment
|
|
288
|
-
export function getServiceAuthConfigFromEnv(): ServiceAuthConfig {
|
|
289
|
-
const internalSecret = process.env.INTERNAL_SECRET;
|
|
290
|
-
|
|
291
|
-
if (!internalSecret) {
|
|
292
|
-
throw new Error("INTERNAL_SECRET environment variable is required");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
internalSecret,
|
|
297
|
-
tokenExpiry: process.env.SERVICE_TOKEN_EXPIRY || "1h",
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Common service permissions
|
|
302
|
-
export const SERVICE_PERMISSIONS = {
|
|
303
|
-
// Store service permissions
|
|
304
|
-
STORE_READ: "store:read",
|
|
305
|
-
STORE_WRITE: "store:write",
|
|
306
|
-
STORE_DELETE: "store:delete",
|
|
307
|
-
|
|
308
|
-
// Compliance service permissions
|
|
309
|
-
COMPLIANCE_READ: "compliance:read",
|
|
310
|
-
COMPLIANCE_WRITE: "compliance:write",
|
|
311
|
-
COMPLIANCE_BAN: "compliance:ban",
|
|
312
|
-
COMPLIANCE_APPEAL: "compliance:appeal",
|
|
313
|
-
|
|
314
|
-
// Notification service permissions
|
|
315
|
-
NOTIFICATION_SEND: "notification:send",
|
|
316
|
-
NOTIFICATION_READ: "notification:read",
|
|
317
|
-
|
|
318
|
-
// Audit service permissions
|
|
319
|
-
AUDIT_READ: "audit:read",
|
|
320
|
-
AUDIT_WRITE: "audit:write",
|
|
321
|
-
|
|
322
|
-
// Admin service permissions
|
|
323
|
-
ADMIN_OVERRIDE: "admin:override",
|
|
324
|
-
ADMIN_REVIEW: "admin:review",
|
|
325
|
-
|
|
326
|
-
// AI moderation permissions
|
|
327
|
-
AI_MODERATE: "ai:moderate",
|
|
328
|
-
} as const;
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// Service claims schema
|
|
6
|
+
export const ServiceClaimsSchema = z.object({
|
|
7
|
+
service: z.string(),
|
|
8
|
+
permissions: z.array(z.string()),
|
|
9
|
+
iat: z.number(),
|
|
10
|
+
exp: z.number(),
|
|
11
|
+
iss: z.literal("axova-platform"),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type ServiceClaims = z.infer<typeof ServiceClaimsSchema>;
|
|
15
|
+
|
|
16
|
+
// Service authentication configuration
|
|
17
|
+
export interface ServiceAuthConfig {
|
|
18
|
+
internalSecret: string;
|
|
19
|
+
tokenExpiry?: string; // Default: '1h'
|
|
20
|
+
requiredPermissions?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Extended request interface
|
|
24
|
+
export interface AuthenticatedRequest extends FastifyRequest {
|
|
25
|
+
serviceClaims?: ServiceClaims;
|
|
26
|
+
isServiceAuthenticated?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Service authentication class
|
|
30
|
+
export class ServiceAuthenticator {
|
|
31
|
+
private secret: string;
|
|
32
|
+
private tokenExpiry: string;
|
|
33
|
+
|
|
34
|
+
constructor(config: ServiceAuthConfig) {
|
|
35
|
+
this.secret = config.internalSecret;
|
|
36
|
+
this.tokenExpiry = config.tokenExpiry || "1h";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Generate service token
|
|
40
|
+
generateServiceToken(service: string, permissions: string[] = []): string {
|
|
41
|
+
const payload: Omit<ServiceClaims, "iat" | "exp"> = {
|
|
42
|
+
service,
|
|
43
|
+
permissions,
|
|
44
|
+
iss: "axova-platform",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return jwt.sign(payload, this.secret, {
|
|
48
|
+
expiresIn: this.tokenExpiry,
|
|
49
|
+
algorithm: "HS256",
|
|
50
|
+
} as jwt.SignOptions);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Verify service token
|
|
54
|
+
verifyServiceToken(token: string): ServiceClaims {
|
|
55
|
+
try {
|
|
56
|
+
const decoded = jwt.verify(token, this.secret, {
|
|
57
|
+
algorithms: ["HS256"],
|
|
58
|
+
issuer: "axova-platform",
|
|
59
|
+
}) as ServiceClaims;
|
|
60
|
+
|
|
61
|
+
// Validate with Zod schema
|
|
62
|
+
return ServiceClaimsSchema.parse(decoded);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Invalid service token: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fastify middleware for service authentication
|
|
71
|
+
serviceAuthMiddleware(requiredPermissions: string[] = []) {
|
|
72
|
+
return async (request: AuthenticatedRequest, reply: FastifyReply) => {
|
|
73
|
+
try {
|
|
74
|
+
const authHeader = request.headers.authorization;
|
|
75
|
+
const serviceToken = request.headers["x-service-token"] as string;
|
|
76
|
+
|
|
77
|
+
let token: string | null = null;
|
|
78
|
+
|
|
79
|
+
// Check for Bearer token in Authorization header
|
|
80
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
81
|
+
token = authHeader.substring(7);
|
|
82
|
+
}
|
|
83
|
+
// Check for service token in custom header
|
|
84
|
+
else if (serviceToken) {
|
|
85
|
+
token = serviceToken;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!token) {
|
|
89
|
+
return reply.code(401).send({
|
|
90
|
+
error: "Service authentication required",
|
|
91
|
+
code: "MISSING_SERVICE_TOKEN",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// DEVELOPMENT MODE: Accept simple constant tokens
|
|
96
|
+
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
97
|
+
const developmentTokens = [
|
|
98
|
+
"dev-compliance-token",
|
|
99
|
+
"development-mode",
|
|
100
|
+
"compliance-dev-access",
|
|
101
|
+
"simple-dev-token",
|
|
102
|
+
"inventory-dev-access",
|
|
103
|
+
"product-dev-access",
|
|
104
|
+
"oxa-dev-access",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (isDevelopment && developmentTokens.includes(token)) {
|
|
108
|
+
// In development, validate against a proper development service registry
|
|
109
|
+
// instead of using mock claims
|
|
110
|
+
try {
|
|
111
|
+
// TODO: Implement proper development service authentication
|
|
112
|
+
// For now, only allow specific development services with limited permissions
|
|
113
|
+
let servicePermissions: string[] = [];
|
|
114
|
+
let serviceName = "unknown-service";
|
|
115
|
+
|
|
116
|
+
// Grant permissions based on the specific development token
|
|
117
|
+
switch (token) {
|
|
118
|
+
case "inventory-dev-access":
|
|
119
|
+
serviceName = "inventory-service-dev";
|
|
120
|
+
servicePermissions = ["store:read", "store:write"];
|
|
121
|
+
break;
|
|
122
|
+
case "product-dev-access":
|
|
123
|
+
serviceName = "product-service-dev";
|
|
124
|
+
servicePermissions = ["store:read", "store:write"];
|
|
125
|
+
break;
|
|
126
|
+
case "compliance-dev-access":
|
|
127
|
+
serviceName = "compliance-service-dev";
|
|
128
|
+
servicePermissions = ["compliance:read", "compliance:write"];
|
|
129
|
+
break;
|
|
130
|
+
case "oxa-dev-access":
|
|
131
|
+
serviceName = "oxa-service-dev";
|
|
132
|
+
servicePermissions = ["store:read", "ai:moderate"];
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
throw new Error("Unknown development token");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const devClaims: ServiceClaims = {
|
|
139
|
+
service: serviceName,
|
|
140
|
+
permissions: servicePermissions,
|
|
141
|
+
iss: "axova-platform",
|
|
142
|
+
iat: Math.floor(Date.now() / 1000),
|
|
143
|
+
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24 hours
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
request.serviceClaims = devClaims;
|
|
147
|
+
request.isServiceAuthenticated = true;
|
|
148
|
+
console.log(
|
|
149
|
+
`🔧 Development mode: Service authenticated with token "${token}"`,
|
|
150
|
+
);
|
|
151
|
+
return; // Skip JWT verification in development
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return reply.code(401).send({
|
|
154
|
+
error: "Invalid development token",
|
|
155
|
+
code: "INVALID_DEVELOPMENT_TOKEN",
|
|
156
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Verify the token
|
|
162
|
+
const claims = this.verifyServiceToken(token);
|
|
163
|
+
|
|
164
|
+
// Check required permissions
|
|
165
|
+
if (requiredPermissions.length > 0) {
|
|
166
|
+
const hasPermissions = requiredPermissions.every((perm) =>
|
|
167
|
+
claims.permissions.includes(perm),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!hasPermissions) {
|
|
171
|
+
return reply.code(403).send({
|
|
172
|
+
error: "Insufficient permissions",
|
|
173
|
+
code: "INSUFFICIENT_PERMISSIONS",
|
|
174
|
+
required: requiredPermissions,
|
|
175
|
+
granted: claims.permissions,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Attach claims to request
|
|
181
|
+
request.serviceClaims = claims;
|
|
182
|
+
request.isServiceAuthenticated = true;
|
|
183
|
+
|
|
184
|
+
console.log(`Service authenticated: ${claims.service}`);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return reply.code(401).send({
|
|
187
|
+
error: "Invalid service token",
|
|
188
|
+
code: "INVALID_SERVICE_TOKEN",
|
|
189
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Alternative HMAC-based authentication
|
|
196
|
+
generateHMACSignature(payload: string, timestamp: string): string {
|
|
197
|
+
const crypto = require("node:crypto");
|
|
198
|
+
const message = `${timestamp}.${payload}`;
|
|
199
|
+
return crypto
|
|
200
|
+
.createHmac("sha256", this.secret)
|
|
201
|
+
.update(message)
|
|
202
|
+
.digest("hex");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// HMAC middleware
|
|
206
|
+
hmacAuthMiddleware() {
|
|
207
|
+
return async (request: AuthenticatedRequest, reply: FastifyReply) => {
|
|
208
|
+
try {
|
|
209
|
+
const signature = request.headers["x-signature"] as string;
|
|
210
|
+
const timestamp = request.headers["x-timestamp"] as string;
|
|
211
|
+
const service = request.headers["x-service"] as string;
|
|
212
|
+
|
|
213
|
+
if (!signature || !timestamp || !service) {
|
|
214
|
+
return reply.code(401).send({
|
|
215
|
+
error: "HMAC authentication headers missing",
|
|
216
|
+
code: "MISSING_HMAC_HEADERS",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check timestamp (prevent replay attacks)
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
const requestTime = Number.parseInt(timestamp);
|
|
223
|
+
const timeDiff = Math.abs(now - requestTime);
|
|
224
|
+
|
|
225
|
+
// Allow 5 minutes tolerance
|
|
226
|
+
if (timeDiff > 300000) {
|
|
227
|
+
return reply.code(401).send({
|
|
228
|
+
error: "Request timestamp too old",
|
|
229
|
+
code: "TIMESTAMP_EXPIRED",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Get request body as string
|
|
234
|
+
const body = JSON.stringify(request.body || {});
|
|
235
|
+
const expectedSignature = this.generateHMACSignature(body, timestamp);
|
|
236
|
+
|
|
237
|
+
if (signature !== expectedSignature) {
|
|
238
|
+
return reply.code(401).send({
|
|
239
|
+
error: "Invalid HMAC signature",
|
|
240
|
+
code: "INVALID_HMAC_SIGNATURE",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Set service info on request
|
|
245
|
+
request.serviceClaims = {
|
|
246
|
+
service,
|
|
247
|
+
permissions: [], // HMAC doesn't include permissions by default
|
|
248
|
+
iat: requestTime,
|
|
249
|
+
exp: requestTime + 300000, // 5 minutes
|
|
250
|
+
iss: "axova-platform",
|
|
251
|
+
};
|
|
252
|
+
request.isServiceAuthenticated = true;
|
|
253
|
+
|
|
254
|
+
console.log(`Service authenticated via HMAC: ${service}`);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return reply.code(401).send({
|
|
257
|
+
error: "HMAC authentication failed",
|
|
258
|
+
code: "HMAC_AUTH_FAILED",
|
|
259
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Singleton factory
|
|
267
|
+
let serviceAuthInstance: ServiceAuthenticator | null = null;
|
|
268
|
+
|
|
269
|
+
export function createServiceAuthenticator(
|
|
270
|
+
config: ServiceAuthConfig,
|
|
271
|
+
): ServiceAuthenticator {
|
|
272
|
+
if (!serviceAuthInstance) {
|
|
273
|
+
serviceAuthInstance = new ServiceAuthenticator(config);
|
|
274
|
+
}
|
|
275
|
+
return serviceAuthInstance;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function getServiceAuthenticator(): ServiceAuthenticator {
|
|
279
|
+
if (!serviceAuthInstance) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"Service authenticator not created. Call createServiceAuthenticator() first.",
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return serviceAuthInstance;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Utility to get auth config from environment
|
|
288
|
+
export function getServiceAuthConfigFromEnv(): ServiceAuthConfig {
|
|
289
|
+
const internalSecret = process.env.INTERNAL_SECRET;
|
|
290
|
+
|
|
291
|
+
if (!internalSecret) {
|
|
292
|
+
throw new Error("INTERNAL_SECRET environment variable is required");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
internalSecret,
|
|
297
|
+
tokenExpiry: process.env.SERVICE_TOKEN_EXPIRY || "1h",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Common service permissions
|
|
302
|
+
export const SERVICE_PERMISSIONS = {
|
|
303
|
+
// Store service permissions
|
|
304
|
+
STORE_READ: "store:read",
|
|
305
|
+
STORE_WRITE: "store:write",
|
|
306
|
+
STORE_DELETE: "store:delete",
|
|
307
|
+
|
|
308
|
+
// Compliance service permissions
|
|
309
|
+
COMPLIANCE_READ: "compliance:read",
|
|
310
|
+
COMPLIANCE_WRITE: "compliance:write",
|
|
311
|
+
COMPLIANCE_BAN: "compliance:ban",
|
|
312
|
+
COMPLIANCE_APPEAL: "compliance:appeal",
|
|
313
|
+
|
|
314
|
+
// Notification service permissions
|
|
315
|
+
NOTIFICATION_SEND: "notification:send",
|
|
316
|
+
NOTIFICATION_READ: "notification:read",
|
|
317
|
+
|
|
318
|
+
// Audit service permissions
|
|
319
|
+
AUDIT_READ: "audit:read",
|
|
320
|
+
AUDIT_WRITE: "audit:write",
|
|
321
|
+
|
|
322
|
+
// Admin service permissions
|
|
323
|
+
ADMIN_OVERRIDE: "admin:override",
|
|
324
|
+
ADMIN_REVIEW: "admin:review",
|
|
325
|
+
|
|
326
|
+
// AI moderation permissions
|
|
327
|
+
AI_MODERATE: "ai:moderate",
|
|
328
|
+
} as const;
|