@axova/shared 1.0.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/CONFIGURATION_GUIDE.md +1 -0
- package/README.md +384 -0
- package/SCHEMA_ORGANIZATION.md +209 -0
- package/dist/configs/index.d.ts +85 -0
- package/dist/configs/index.js +555 -0
- package/dist/events/kafka.d.ts +40 -0
- package/dist/events/kafka.js +311 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +41 -0
- package/dist/interfaces/customer-events.d.ts +85 -0
- package/dist/interfaces/customer-events.js +2 -0
- package/dist/interfaces/inventory-events.d.ts +453 -0
- package/dist/interfaces/inventory-events.js +3 -0
- package/dist/interfaces/inventory-types.d.ts +894 -0
- package/dist/interfaces/inventory-types.js +3 -0
- package/dist/interfaces/order-events.d.ts +320 -0
- package/dist/interfaces/order-events.js +3 -0
- package/dist/lib/auditLogger.d.ts +162 -0
- package/dist/lib/auditLogger.js +626 -0
- package/dist/lib/authOrganization.d.ts +24 -0
- package/dist/lib/authOrganization.js +110 -0
- package/dist/lib/db.d.ts +6 -0
- package/dist/lib/db.js +88 -0
- package/dist/middleware/serviceAuth.d.ts +60 -0
- package/dist/middleware/serviceAuth.js +272 -0
- package/dist/middleware/storeOwnership.d.ts +15 -0
- package/dist/middleware/storeOwnership.js +156 -0
- package/dist/middleware/storeValidationMiddleware.d.ts +44 -0
- package/dist/middleware/storeValidationMiddleware.js +180 -0
- package/dist/middleware/userAuth.d.ts +27 -0
- package/dist/middleware/userAuth.js +218 -0
- package/dist/schemas/admin/admin-schema.d.ts +741 -0
- package/dist/schemas/admin/admin-schema.js +111 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +648 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.js +88 -0
- package/dist/schemas/common/common-schemas.d.ts +436 -0
- package/dist/schemas/common/common-schemas.js +94 -0
- package/dist/schemas/compliance/compliance-schema.d.ts +3388 -0
- package/dist/schemas/compliance/compliance-schema.js +472 -0
- package/dist/schemas/compliance/kyc-schema.d.ts +2642 -0
- package/dist/schemas/compliance/kyc-schema.js +361 -0
- package/dist/schemas/customer/customer-schema.d.ts +2727 -0
- package/dist/schemas/customer/customer-schema.js +399 -0
- package/dist/schemas/index.d.ts +27 -0
- package/dist/schemas/index.js +138 -0
- package/dist/schemas/inventory/inventory-tables.d.ts +9476 -0
- package/dist/schemas/inventory/inventory-tables.js +1470 -0
- package/dist/schemas/inventory/lot-tables.d.ts +3281 -0
- package/dist/schemas/inventory/lot-tables.js +608 -0
- package/dist/schemas/order/order-schema.d.ts +5825 -0
- package/dist/schemas/order/order-schema.js +954 -0
- package/dist/schemas/product/discount-relations.d.ts +15 -0
- package/dist/schemas/product/discount-relations.js +34 -0
- package/dist/schemas/product/discount-schema.d.ts +1975 -0
- package/dist/schemas/product/discount-schema.js +297 -0
- package/dist/schemas/product/product-relations.d.ts +41 -0
- package/dist/schemas/product/product-relations.js +133 -0
- package/dist/schemas/product/product-schema.d.ts +4544 -0
- package/dist/schemas/product/product-schema.js +671 -0
- package/dist/schemas/store/store-audit-schema.d.ts +4135 -0
- package/dist/schemas/store/store-audit-schema.js +556 -0
- package/dist/schemas/store/store-schema.d.ts +3100 -0
- package/dist/schemas/store/store-schema.js +381 -0
- package/dist/schemas/store/store-settings-schema.d.ts +665 -0
- package/dist/schemas/store/store-settings-schema.js +141 -0
- package/dist/schemas/types.d.ts +50 -0
- package/dist/schemas/types.js +3 -0
- package/dist/types/events.d.ts +2396 -0
- package/dist/types/events.js +505 -0
- package/dist/utils/errorHandler.d.ts +12 -0
- package/dist/utils/errorHandler.js +36 -0
- package/dist/utils/subdomain.d.ts +6 -0
- package/dist/utils/subdomain.js +20 -0
- package/nul +8 -0
- package/package.json +43 -0
- package/src/configs/index.ts +654 -0
- package/src/events/kafka.ts +429 -0
- package/src/index.ts +26 -0
- package/src/interfaces/customer-events.ts +106 -0
- package/src/interfaces/inventory-events.ts +545 -0
- package/src/interfaces/inventory-types.ts +1004 -0
- package/src/interfaces/order-events.ts +381 -0
- package/src/lib/auditLogger.ts +1117 -0
- package/src/lib/authOrganization.ts +153 -0
- package/src/lib/db.ts +64 -0
- package/src/middleware/serviceAuth.ts +328 -0
- package/src/middleware/storeOwnership.ts +199 -0
- package/src/middleware/storeValidationMiddleware.ts +247 -0
- package/src/middleware/userAuth.ts +248 -0
- package/src/schemas/admin/admin-schema.ts +208 -0
- package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -0
- package/src/schemas/common/common-schemas.ts +108 -0
- 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 +189 -0
- package/src/schemas/inventory/inventory-tables.ts +1927 -0
- package/src/schemas/inventory/lot-tables.ts +799 -0
- package/src/schemas/order/order-schema.ts +1400 -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 +661 -0
- package/src/schemas/store/store-settings-schema.ts +231 -0
- package/src/schemas/types.ts +67 -0
- package/src/types/events.ts +646 -0
- package/src/utils/errorHandler.ts +44 -0
- package/src/utils/subdomain.ts +19 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireStoreOwner = requireStoreOwner;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const db_1 = require("../lib/db");
|
|
6
|
+
const store_schema_1 = require("../schemas/store/store-schema");
|
|
7
|
+
const serviceAuth_1 = require("./serviceAuth");
|
|
8
|
+
/**
|
|
9
|
+
* Factory that returns a middleware ensuring the current user owns the target store.
|
|
10
|
+
* - Uses shared store validation under the hood for existence/active checks
|
|
11
|
+
* - Supports optional admin/permission overrides (e.g. SUPER_ADMIN or * permissions)
|
|
12
|
+
*/
|
|
13
|
+
function requireStoreOwner(options = {}) {
|
|
14
|
+
const { requireActive = true, allowParameterStoreId = true, storeIdParamName = "storeId", allowAdminOverride = true, overrideRoles = ["SUPER_ADMIN"], overridePermissions = ["*", "store:read:all", "store:write:all"], } = options;
|
|
15
|
+
return async (request, reply) => {
|
|
16
|
+
const user = request.user;
|
|
17
|
+
// Extract storeId from params/query/body/user.context
|
|
18
|
+
let storeId;
|
|
19
|
+
const params = (request.params || {});
|
|
20
|
+
const query = (request.query || {});
|
|
21
|
+
const body = (request.body || {});
|
|
22
|
+
if (allowParameterStoreId && typeof params[storeIdParamName] === "string") {
|
|
23
|
+
storeId = params[storeIdParamName];
|
|
24
|
+
}
|
|
25
|
+
if (!storeId && typeof query.storeId === "string")
|
|
26
|
+
storeId = query.storeId;
|
|
27
|
+
if (!storeId && typeof body.storeId === "string")
|
|
28
|
+
storeId = body.storeId;
|
|
29
|
+
if (!storeId && typeof request.user?.storeId === "string") {
|
|
30
|
+
storeId = request.user?.storeId;
|
|
31
|
+
}
|
|
32
|
+
if (!storeId) {
|
|
33
|
+
return reply.status(400).send({
|
|
34
|
+
error: "Store ID Required",
|
|
35
|
+
message: `Store ID must be provided in URL (:${storeIdParamName}), query, or body`,
|
|
36
|
+
code: "STORE_ID_MISSING",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Admin/permission overrides
|
|
40
|
+
const hasRoleOverride = !!user?.role && overrideRoles.includes(user.role);
|
|
41
|
+
const hasPermissionOverride = !!user?.permissions?.some((p) => overridePermissions.includes(p));
|
|
42
|
+
if (allowAdminOverride && (hasRoleOverride || hasPermissionOverride)) {
|
|
43
|
+
// Optionally attach minimal store info by fetching; if it fails, still allow
|
|
44
|
+
await attachStoreInfoIfPossible(request, storeId);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Fetch store details from Store Service internal route
|
|
48
|
+
try {
|
|
49
|
+
const store = await fetchStoreById(storeId);
|
|
50
|
+
if (!store) {
|
|
51
|
+
return reply.status(404).send({
|
|
52
|
+
error: "Store Not Found",
|
|
53
|
+
message: `Store with ID ${storeId} does not exist`,
|
|
54
|
+
code: "STORE_NOT_FOUND",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// requireActive check
|
|
58
|
+
if (requireActive && store.isActive === false) {
|
|
59
|
+
return reply.status(403).send({
|
|
60
|
+
error: "Store Inactive",
|
|
61
|
+
message: `Store ${storeId} is currently inactive`,
|
|
62
|
+
code: "STORE_INACTIVE",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Ownership check
|
|
66
|
+
if (!user?.userId || store.userId !== user.userId) {
|
|
67
|
+
return reply.status(403).send({
|
|
68
|
+
error: "Store Access Denied",
|
|
69
|
+
message: "You do not have permission to access this store",
|
|
70
|
+
code: "STORE_ACCESS_DENIED",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Attach store info to request for downstream handlers
|
|
74
|
+
request.storeInfo = {
|
|
75
|
+
storeId: store.id,
|
|
76
|
+
storeName: store.storeName,
|
|
77
|
+
isActive: !!store.isActive,
|
|
78
|
+
userId: store.userId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error("Store ownership validation error:", error);
|
|
83
|
+
return reply.status(500).send({
|
|
84
|
+
error: "Internal Server Error",
|
|
85
|
+
message: "Failed to validate store ownership",
|
|
86
|
+
code: "STORE_OWNERSHIP_VALIDATION_ERROR",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Fetches store by ID from the Store Service internal endpoint
|
|
92
|
+
async function fetchStoreById(storeId) {
|
|
93
|
+
const baseUrl = process.env.STORE_SERVICE_URL || "http://localhost:3001";
|
|
94
|
+
const url = `${baseUrl}/internal/stores/${encodeURIComponent(storeId)}`;
|
|
95
|
+
let token;
|
|
96
|
+
try {
|
|
97
|
+
const cfg = (0, serviceAuth_1.getServiceAuthConfigFromEnv)();
|
|
98
|
+
token = (0, serviceAuth_1.createServiceAuthenticator)(cfg).generateServiceToken(process.env.SERVICE_NAME || "inventory-core-service", ["store:read"]);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Development fallback tokens accepted by store service
|
|
102
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
103
|
+
if (isDev)
|
|
104
|
+
token = "inventory-dev-access";
|
|
105
|
+
}
|
|
106
|
+
const headers = {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
};
|
|
109
|
+
if (token)
|
|
110
|
+
headers["x-service-token"] = token;
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch(url, { method: "GET", headers });
|
|
113
|
+
if (res.ok) {
|
|
114
|
+
const data = (await res.json());
|
|
115
|
+
const store = data?.store || data?.data?.store || null;
|
|
116
|
+
if (store)
|
|
117
|
+
return store;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// ignore network error and try DB fallback when possible
|
|
122
|
+
}
|
|
123
|
+
// DB fallback (primarily for in-process store-service)
|
|
124
|
+
try {
|
|
125
|
+
const result = await db_1.db
|
|
126
|
+
.select({
|
|
127
|
+
id: store_schema_1.stores.id,
|
|
128
|
+
storeName: store_schema_1.stores.storeName,
|
|
129
|
+
isActive: store_schema_1.stores.isActive,
|
|
130
|
+
userId: store_schema_1.stores.userId,
|
|
131
|
+
})
|
|
132
|
+
.from(store_schema_1.stores)
|
|
133
|
+
.where((0, drizzle_orm_1.eq)(store_schema_1.stores.id, storeId))
|
|
134
|
+
.limit(1);
|
|
135
|
+
return result[0] || null;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function attachStoreInfoIfPossible(request, storeId) {
|
|
142
|
+
try {
|
|
143
|
+
const store = await fetchStoreById(storeId);
|
|
144
|
+
if (store) {
|
|
145
|
+
request.storeInfo = {
|
|
146
|
+
storeId: store.id,
|
|
147
|
+
storeName: store.storeName,
|
|
148
|
+
isActive: !!store.isActive,
|
|
149
|
+
userId: store.userId,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// ignore
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
export interface StoreValidationOptions {
|
|
3
|
+
requireActive?: boolean;
|
|
4
|
+
allowParameterStoreId?: boolean;
|
|
5
|
+
storeIdParamName?: string;
|
|
6
|
+
requireOwnership?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare module "fastify" {
|
|
9
|
+
interface FastifyRequest {
|
|
10
|
+
user?: {
|
|
11
|
+
userId: string;
|
|
12
|
+
storeId: string;
|
|
13
|
+
role: string;
|
|
14
|
+
permissions: string[];
|
|
15
|
+
iat?: number;
|
|
16
|
+
exp?: number;
|
|
17
|
+
};
|
|
18
|
+
session?: unknown;
|
|
19
|
+
storeInfo?: {
|
|
20
|
+
storeId: string;
|
|
21
|
+
storeName: string;
|
|
22
|
+
isActive: boolean;
|
|
23
|
+
userId: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Middleware to validate store existence and status
|
|
29
|
+
*/
|
|
30
|
+
export declare const validateStoreMiddleware: (options?: StoreValidationOptions) => (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Helper to clear store cache entry
|
|
33
|
+
*/
|
|
34
|
+
export declare const clearStoreCache: (storeId: string) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Helper to clear all store cache
|
|
37
|
+
*/
|
|
38
|
+
export declare const clearAllStoreCache: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Simplified middleware for common use cases
|
|
41
|
+
*/
|
|
42
|
+
export declare const requireValidStore: (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
43
|
+
export declare const requireOwnedStore: (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
44
|
+
export declare const requireAnyStore: (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireAnyStore = exports.requireOwnedStore = exports.requireValidStore = exports.clearAllStoreCache = exports.clearStoreCache = exports.validateStoreMiddleware = void 0;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const db_1 = require("../lib/db");
|
|
6
|
+
const store_schema_1 = require("../schemas/store/store-schema");
|
|
7
|
+
// Store cache to reduce database calls
|
|
8
|
+
const storeCache = new Map();
|
|
9
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
/**
|
|
11
|
+
* Middleware to validate store existence and status
|
|
12
|
+
*/
|
|
13
|
+
const validateStoreMiddleware = (options = {}) => {
|
|
14
|
+
return async (request, reply) => {
|
|
15
|
+
try {
|
|
16
|
+
const { requireActive = true, allowParameterStoreId = true, storeIdParamName = "storeId", requireOwnership = false, } = options;
|
|
17
|
+
// Extract store ID from various sources
|
|
18
|
+
let storeId;
|
|
19
|
+
// 1. Try from authenticated user context
|
|
20
|
+
if (request.user?.storeId) {
|
|
21
|
+
storeId = request.user.storeId;
|
|
22
|
+
}
|
|
23
|
+
// 2. Try from URL parameters if allowed
|
|
24
|
+
if (!storeId && allowParameterStoreId) {
|
|
25
|
+
const params = request.params;
|
|
26
|
+
storeId = params[storeIdParamName];
|
|
27
|
+
}
|
|
28
|
+
// 3. Try from query parameters
|
|
29
|
+
if (!storeId) {
|
|
30
|
+
const query = request.query;
|
|
31
|
+
storeId = query.storeId;
|
|
32
|
+
}
|
|
33
|
+
// 4. Try from request body
|
|
34
|
+
if (!storeId && request.body && typeof request.body === "object") {
|
|
35
|
+
const body = request.body;
|
|
36
|
+
if (typeof body.storeId === "string") {
|
|
37
|
+
storeId = body.storeId;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!storeId) {
|
|
41
|
+
return reply.status(400).send({
|
|
42
|
+
error: "Store ID Required",
|
|
43
|
+
message: "Store ID must be provided in URL, query parameters, or request body",
|
|
44
|
+
code: "STORE_ID_MISSING",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Check cache first
|
|
48
|
+
const cached = storeCache.get(storeId);
|
|
49
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
50
|
+
if (!cached.isValid) {
|
|
51
|
+
return reply.status(404).send({
|
|
52
|
+
error: "Store Not Found",
|
|
53
|
+
message: `Store with ID ${storeId} does not exist`,
|
|
54
|
+
code: "STORE_NOT_FOUND",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Use cached store data if available
|
|
58
|
+
if (cached.storeData) {
|
|
59
|
+
const storeData = cached.storeData;
|
|
60
|
+
// Check if store is active when required
|
|
61
|
+
if (requireActive && !storeData.isActive) {
|
|
62
|
+
return reply.status(403).send({
|
|
63
|
+
error: "Store Inactive",
|
|
64
|
+
message: `Store ${storeId} is currently inactive`,
|
|
65
|
+
code: "STORE_INACTIVE",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Check ownership when required
|
|
69
|
+
if (requireOwnership && request.user?.userId !== storeData.userId) {
|
|
70
|
+
return reply.status(403).send({
|
|
71
|
+
error: "Store Access Denied",
|
|
72
|
+
message: "You do not have permission to access this store",
|
|
73
|
+
code: "STORE_ACCESS_DENIED",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Attach store information to request
|
|
77
|
+
request.storeInfo = {
|
|
78
|
+
storeId: storeData.id,
|
|
79
|
+
storeName: storeData.storeName,
|
|
80
|
+
isActive: storeData.isActive,
|
|
81
|
+
userId: storeData.userId,
|
|
82
|
+
};
|
|
83
|
+
return; // Successfully validated using cache
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Validate store exists and get details from database
|
|
87
|
+
const store = await db_1.db
|
|
88
|
+
.select({
|
|
89
|
+
id: store_schema_1.stores.id,
|
|
90
|
+
storeName: store_schema_1.stores.storeName,
|
|
91
|
+
isActive: store_schema_1.stores.isActive,
|
|
92
|
+
userId: store_schema_1.stores.userId,
|
|
93
|
+
})
|
|
94
|
+
.from(store_schema_1.stores)
|
|
95
|
+
.where((0, drizzle_orm_1.eq)(store_schema_1.stores.id, storeId))
|
|
96
|
+
.limit(1);
|
|
97
|
+
if (store.length === 0) {
|
|
98
|
+
// Cache negative result
|
|
99
|
+
storeCache.set(storeId, { isValid: false, timestamp: Date.now() });
|
|
100
|
+
return reply.status(404).send({
|
|
101
|
+
error: "Store Not Found",
|
|
102
|
+
message: `Store with ID ${storeId} does not exist`,
|
|
103
|
+
code: "STORE_NOT_FOUND",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const storeData = store[0];
|
|
107
|
+
// Check if store is active when required
|
|
108
|
+
if (requireActive && !storeData.isActive) {
|
|
109
|
+
return reply.status(403).send({
|
|
110
|
+
error: "Store Inactive",
|
|
111
|
+
message: `Store ${storeId} is currently inactive`,
|
|
112
|
+
code: "STORE_INACTIVE",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Check ownership when required
|
|
116
|
+
if (requireOwnership && request.user?.userId !== storeData.userId) {
|
|
117
|
+
return reply.status(403).send({
|
|
118
|
+
error: "Store Access Denied",
|
|
119
|
+
message: "You do not have permission to access this store",
|
|
120
|
+
code: "STORE_ACCESS_DENIED",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Cache positive result with store data
|
|
124
|
+
storeCache.set(storeId, {
|
|
125
|
+
isValid: true,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
storeData: {
|
|
128
|
+
id: storeData.id,
|
|
129
|
+
storeName: storeData.storeName,
|
|
130
|
+
isActive: storeData.isActive,
|
|
131
|
+
userId: storeData.userId,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// Attach store information to request
|
|
135
|
+
request.storeInfo = {
|
|
136
|
+
storeId: storeData.id,
|
|
137
|
+
storeName: storeData.storeName,
|
|
138
|
+
isActive: storeData.isActive,
|
|
139
|
+
userId: storeData.userId,
|
|
140
|
+
};
|
|
141
|
+
// Continue to next handler - explicitly return to proceed
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error("Store validation error:", error);
|
|
145
|
+
return reply.status(500).send({
|
|
146
|
+
error: "Internal Server Error",
|
|
147
|
+
message: "Failed to validate store",
|
|
148
|
+
code: "STORE_VALIDATION_ERROR",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
exports.validateStoreMiddleware = validateStoreMiddleware;
|
|
154
|
+
/**
|
|
155
|
+
* Helper to clear store cache entry
|
|
156
|
+
*/
|
|
157
|
+
const clearStoreCache = (storeId) => {
|
|
158
|
+
storeCache.delete(storeId);
|
|
159
|
+
};
|
|
160
|
+
exports.clearStoreCache = clearStoreCache;
|
|
161
|
+
/**
|
|
162
|
+
* Helper to clear all store cache
|
|
163
|
+
*/
|
|
164
|
+
const clearAllStoreCache = () => {
|
|
165
|
+
storeCache.clear();
|
|
166
|
+
};
|
|
167
|
+
exports.clearAllStoreCache = clearAllStoreCache;
|
|
168
|
+
/**
|
|
169
|
+
* Simplified middleware for common use cases
|
|
170
|
+
*/
|
|
171
|
+
exports.requireValidStore = (0, exports.validateStoreMiddleware)({
|
|
172
|
+
requireActive: true,
|
|
173
|
+
});
|
|
174
|
+
exports.requireOwnedStore = (0, exports.validateStoreMiddleware)({
|
|
175
|
+
requireActive: true,
|
|
176
|
+
requireOwnership: true,
|
|
177
|
+
});
|
|
178
|
+
exports.requireAnyStore = (0, exports.validateStoreMiddleware)({
|
|
179
|
+
requireActive: false,
|
|
180
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
export interface SharedUserContext {
|
|
3
|
+
userId: string;
|
|
4
|
+
storeId: string;
|
|
5
|
+
role: string;
|
|
6
|
+
permissions: string[];
|
|
7
|
+
organizationId?: string;
|
|
8
|
+
activeOrganizationId?: string;
|
|
9
|
+
iat?: number;
|
|
10
|
+
exp?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]>;
|
|
13
|
+
export declare const AuthPreHandler: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
14
|
+
export declare const fastifyUserAuthPlugin: FastifyPluginAsync;
|
|
15
|
+
export declare function getAuthTokenFromRequest(request: FastifyRequest): string | undefined;
|
|
16
|
+
export declare function getAuthHeadersFromRequest(request: FastifyRequest): Record<string, string>;
|
|
17
|
+
export declare function getUserDataFromRequest(request: FastifyRequest): {
|
|
18
|
+
userId: string;
|
|
19
|
+
organizationId?: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
permissions?: string[];
|
|
22
|
+
} | null;
|
|
23
|
+
export declare function getUserIdFromRequest(request: FastifyRequest): string | null;
|
|
24
|
+
export declare function hasPermission(request: FastifyRequest, permission: string): boolean;
|
|
25
|
+
export declare function hasRole(request: FastifyRequest, role: string): boolean;
|
|
26
|
+
export declare function requirePermission(permission: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
27
|
+
export declare function requireRole(role: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fastifyUserAuthPlugin = exports.AuthPreHandler = exports.DEFAULT_ROLE_PERMISSIONS = void 0;
|
|
7
|
+
exports.getAuthTokenFromRequest = getAuthTokenFromRequest;
|
|
8
|
+
exports.getAuthHeadersFromRequest = getAuthHeadersFromRequest;
|
|
9
|
+
exports.getUserDataFromRequest = getUserDataFromRequest;
|
|
10
|
+
exports.getUserIdFromRequest = getUserIdFromRequest;
|
|
11
|
+
exports.hasPermission = hasPermission;
|
|
12
|
+
exports.hasRole = hasRole;
|
|
13
|
+
exports.requirePermission = requirePermission;
|
|
14
|
+
exports.requireRole = requireRole;
|
|
15
|
+
const better_middleware_1 = require("better-middleware");
|
|
16
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
17
|
+
// Optional role → permission mapping (used for helpers)
|
|
18
|
+
exports.DEFAULT_ROLE_PERMISSIONS = {
|
|
19
|
+
SUPER_ADMIN: ["*"],
|
|
20
|
+
STORE_ADMIN: [
|
|
21
|
+
"store:read",
|
|
22
|
+
"store:write",
|
|
23
|
+
"store:delete",
|
|
24
|
+
"orders:read",
|
|
25
|
+
"orders:write",
|
|
26
|
+
"products:read",
|
|
27
|
+
"products:write",
|
|
28
|
+
],
|
|
29
|
+
STORE_MANAGER: [
|
|
30
|
+
"store:read",
|
|
31
|
+
"store:write",
|
|
32
|
+
"orders:read",
|
|
33
|
+
"orders:write",
|
|
34
|
+
"products:read",
|
|
35
|
+
"products:write",
|
|
36
|
+
],
|
|
37
|
+
STORE_EMPLOYEE: ["store:read", "orders:read", "products:read"],
|
|
38
|
+
USER: ["store:read"],
|
|
39
|
+
};
|
|
40
|
+
// Core adapter of better-middleware to Fastify
|
|
41
|
+
function createFastifyAuthHandler() {
|
|
42
|
+
const auth = (0, better_middleware_1.createAuthMiddleware)({
|
|
43
|
+
baseURL: process.env.NEXT_PUBLIC_AUTH_URL || "http://localhost:4000",
|
|
44
|
+
cache: { enabled: true, ttl: 300, max: 1000 },
|
|
45
|
+
onError: async () => ({
|
|
46
|
+
status: 401,
|
|
47
|
+
body: {
|
|
48
|
+
success: false,
|
|
49
|
+
error: "Authentication required",
|
|
50
|
+
code: "UNAUTHORIZED",
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
logger: {
|
|
54
|
+
info: (m, d) => process.env.NODE_ENV === "development"
|
|
55
|
+
? console.log(`[Auth] ${m}`, d)
|
|
56
|
+
: undefined,
|
|
57
|
+
error: (m, d) => console.error(`[Auth] ${m}`, d),
|
|
58
|
+
debug: (m, d) => process.env.NODE_ENV === "development"
|
|
59
|
+
? console.debug(`[Auth] ${m}`, d)
|
|
60
|
+
: undefined,
|
|
61
|
+
},
|
|
62
|
+
framework: {
|
|
63
|
+
getHeaders: (request) => {
|
|
64
|
+
const headers = {};
|
|
65
|
+
Object.entries(request.headers || {}).forEach(([k, v]) => {
|
|
66
|
+
if (typeof v === "string")
|
|
67
|
+
headers[k] = v;
|
|
68
|
+
else if (Array.isArray(v))
|
|
69
|
+
headers[k] = v.join(", ");
|
|
70
|
+
});
|
|
71
|
+
const hasCookie = (headers["cookie"] || "").includes("better-auth.session_token=");
|
|
72
|
+
const bearer = headers["authorization"]?.startsWith("Bearer ")
|
|
73
|
+
? headers["authorization"].substring(7)
|
|
74
|
+
: undefined;
|
|
75
|
+
if (!hasCookie && bearer) {
|
|
76
|
+
headers["cookie"] =
|
|
77
|
+
`${headers["cookie"] ? headers["cookie"] + "; " : ""}better-auth.session_token=${bearer}`;
|
|
78
|
+
}
|
|
79
|
+
return headers;
|
|
80
|
+
},
|
|
81
|
+
getCookies: (request) => {
|
|
82
|
+
const cookies = request.cookies || {};
|
|
83
|
+
if (!cookies["better-auth.session_token"] &&
|
|
84
|
+
request.headers.authorization?.startsWith("Bearer ")) {
|
|
85
|
+
cookies["better-auth.session_token"] =
|
|
86
|
+
request.headers.authorization.substring(7);
|
|
87
|
+
}
|
|
88
|
+
return cookies;
|
|
89
|
+
},
|
|
90
|
+
setContext: (request, key, value) => {
|
|
91
|
+
if (key === "user" && value) {
|
|
92
|
+
const u = value;
|
|
93
|
+
request.user = {
|
|
94
|
+
userId: u.id,
|
|
95
|
+
storeId: u.activeOrganizationId || u.organizationId || "",
|
|
96
|
+
role: u.role || "USER",
|
|
97
|
+
permissions: exports.DEFAULT_ROLE_PERMISSIONS[u.role || "USER"] || [],
|
|
98
|
+
organizationId: u.organizationId,
|
|
99
|
+
activeOrganizationId: u.activeOrganizationId,
|
|
100
|
+
iat: u.iat,
|
|
101
|
+
exp: u.exp,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
else if (key === "session" && value) {
|
|
105
|
+
const s = value;
|
|
106
|
+
if (request.user && !request.user.storeId)
|
|
107
|
+
request.user.storeId =
|
|
108
|
+
s.activeOrganizationId || request.user.storeId;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
createResponse: (_request, body, status) => ({
|
|
112
|
+
status,
|
|
113
|
+
body,
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return auth;
|
|
118
|
+
}
|
|
119
|
+
// Pre-handler that runs authentication; emits response when not authorized
|
|
120
|
+
const AuthPreHandler = async (request, reply) => {
|
|
121
|
+
const auth = createFastifyAuthHandler();
|
|
122
|
+
const result = await auth(request, request, async () => { });
|
|
123
|
+
if (result) {
|
|
124
|
+
reply.code(result.status).send(result.body);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
exports.AuthPreHandler = AuthPreHandler;
|
|
128
|
+
// Fastify plugin for easy registration
|
|
129
|
+
exports.fastifyUserAuthPlugin = (0, fastify_plugin_1.default)(async (fastify) => {
|
|
130
|
+
fastify.addHook("preHandler", exports.AuthPreHandler);
|
|
131
|
+
});
|
|
132
|
+
// ---------- Helper utilities ----------
|
|
133
|
+
function getAuthTokenFromRequest(request) {
|
|
134
|
+
const cookies = request.cookies || {};
|
|
135
|
+
const tokenFromCookie = cookies["better-auth.session_token"];
|
|
136
|
+
if (tokenFromCookie)
|
|
137
|
+
return tokenFromCookie;
|
|
138
|
+
const cookieHeader = request.headers.cookie;
|
|
139
|
+
if (cookieHeader) {
|
|
140
|
+
const match = cookieHeader.match(/better-auth\.session_token=([^;]+)/);
|
|
141
|
+
if (match)
|
|
142
|
+
return match[1];
|
|
143
|
+
}
|
|
144
|
+
const authHeader = request.headers.authorization;
|
|
145
|
+
if (authHeader?.startsWith("Bearer "))
|
|
146
|
+
return authHeader.substring(7);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
function getAuthHeadersFromRequest(request) {
|
|
150
|
+
const headers = {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
};
|
|
153
|
+
const token = getAuthTokenFromRequest(request);
|
|
154
|
+
if (token) {
|
|
155
|
+
headers["Cookie"] = `better-auth.session_token=${token}`;
|
|
156
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
157
|
+
}
|
|
158
|
+
return headers;
|
|
159
|
+
}
|
|
160
|
+
function getUserDataFromRequest(request) {
|
|
161
|
+
const user = request.user;
|
|
162
|
+
if (user) {
|
|
163
|
+
return {
|
|
164
|
+
userId: user.userId,
|
|
165
|
+
organizationId: user.storeId || user.organizationId,
|
|
166
|
+
role: user.role,
|
|
167
|
+
permissions: user.permissions,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const userIdFromHeader = request.headers["x-user-id"];
|
|
171
|
+
const storeIdFromHeader = request.headers["x-store-id"];
|
|
172
|
+
const roleFromHeader = request.headers["x-user-role"];
|
|
173
|
+
if (userIdFromHeader) {
|
|
174
|
+
return {
|
|
175
|
+
userId: userIdFromHeader,
|
|
176
|
+
organizationId: storeIdFromHeader,
|
|
177
|
+
role: roleFromHeader || "USER",
|
|
178
|
+
permissions: exports.DEFAULT_ROLE_PERMISSIONS[roleFromHeader || "USER"] || [],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
function getUserIdFromRequest(request) {
|
|
184
|
+
return getUserDataFromRequest(request)?.userId || null;
|
|
185
|
+
}
|
|
186
|
+
function hasPermission(request, permission) {
|
|
187
|
+
const user = request.user;
|
|
188
|
+
const perms = user?.permissions || [];
|
|
189
|
+
return perms.includes("*") || perms.includes(permission);
|
|
190
|
+
}
|
|
191
|
+
function hasRole(request, role) {
|
|
192
|
+
const user = request.user;
|
|
193
|
+
return (user?.role || "").toUpperCase() === role.toUpperCase();
|
|
194
|
+
}
|
|
195
|
+
function requirePermission(permission) {
|
|
196
|
+
return async (request, reply) => {
|
|
197
|
+
if (!hasPermission(request, permission)) {
|
|
198
|
+
reply.code(403).send({
|
|
199
|
+
success: false,
|
|
200
|
+
error: "Insufficient permissions",
|
|
201
|
+
message: `Required permission: ${permission}`,
|
|
202
|
+
code: "FORBIDDEN",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function requireRole(role) {
|
|
208
|
+
return async (request, reply) => {
|
|
209
|
+
if (!hasRole(request, role)) {
|
|
210
|
+
reply.code(403).send({
|
|
211
|
+
success: false,
|
|
212
|
+
error: "Insufficient role",
|
|
213
|
+
message: `Required role: ${role}`,
|
|
214
|
+
code: "FORBIDDEN",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|