@uniforge/core 0.1.0-alpha.2
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/auth/index.d.cts +165 -0
- package/dist/auth/index.d.ts +165 -0
- package/dist/auth/index.js +443 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +406 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/billing/index.d.cts +34 -0
- package/dist/billing/index.d.ts +34 -0
- package/dist/billing/index.js +254 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/index.mjs +225 -0
- package/dist/billing/index.mjs.map +1 -0
- package/dist/config/index.d.cts +12 -0
- package/dist/config/index.d.ts +12 -0
- package/dist/config/index.js +186 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +156 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/database/index.d.cts +33 -0
- package/dist/database/index.d.ts +33 -0
- package/dist/database/index.js +127 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/index.mjs +95 -0
- package/dist/database/index.mjs.map +1 -0
- package/dist/graphql/index.d.cts +36 -0
- package/dist/graphql/index.d.ts +36 -0
- package/dist/graphql/index.js +209 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/index.mjs +179 -0
- package/dist/graphql/index.mjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -0
- package/dist/multi-store/index.d.cts +11 -0
- package/dist/multi-store/index.d.ts +11 -0
- package/dist/multi-store/index.js +473 -0
- package/dist/multi-store/index.js.map +1 -0
- package/dist/multi-store/index.mjs +447 -0
- package/dist/multi-store/index.mjs.map +1 -0
- package/dist/multi-tenant/index.d.cts +23 -0
- package/dist/multi-tenant/index.d.ts +23 -0
- package/dist/multi-tenant/index.js +69 -0
- package/dist/multi-tenant/index.js.map +1 -0
- package/dist/multi-tenant/index.mjs +41 -0
- package/dist/multi-tenant/index.mjs.map +1 -0
- package/dist/performance/index.d.cts +34 -0
- package/dist/performance/index.d.ts +34 -0
- package/dist/performance/index.js +319 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/index.mjs +290 -0
- package/dist/performance/index.mjs.map +1 -0
- package/dist/platform/index.d.cts +25 -0
- package/dist/platform/index.d.ts +25 -0
- package/dist/platform/index.js +91 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/index.mjs +62 -0
- package/dist/platform/index.mjs.map +1 -0
- package/dist/rbac/index.d.cts +24 -0
- package/dist/rbac/index.d.ts +24 -0
- package/dist/rbac/index.js +267 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/rbac/index.mjs +236 -0
- package/dist/rbac/index.mjs.map +1 -0
- package/dist/schema-CM7mHj_H.d.cts +53 -0
- package/dist/schema-CM7mHj_H.d.ts +53 -0
- package/dist/security/index.d.cts +47 -0
- package/dist/security/index.d.ts +47 -0
- package/dist/security/index.js +505 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/index.mjs +474 -0
- package/dist/security/index.mjs.map +1 -0
- package/dist/session-storage/index.d.cts +70 -0
- package/dist/session-storage/index.d.ts +70 -0
- package/dist/session-storage/index.js +271 -0
- package/dist/session-storage/index.js.map +1 -0
- package/dist/session-storage/index.mjs +242 -0
- package/dist/session-storage/index.mjs.map +1 -0
- package/dist/webhooks/index.d.cts +89 -0
- package/dist/webhooks/index.d.ts +89 -0
- package/dist/webhooks/index.js +380 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/index.mjs +348 -0
- package/dist/webhooks/index.mjs.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/rbac/index.ts
|
|
21
|
+
var rbac_exports = {};
|
|
22
|
+
__export(rbac_exports, {
|
|
23
|
+
DEFAULT_ROLE_PERMISSIONS: () => DEFAULT_ROLE_PERMISSIONS,
|
|
24
|
+
createRBACMiddleware: () => createRBACMiddleware,
|
|
25
|
+
createRBACService: () => createRBACService,
|
|
26
|
+
isRoleAtLeast: () => isRoleAtLeast,
|
|
27
|
+
resolvePermissions: () => resolvePermissions
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(rbac_exports);
|
|
30
|
+
|
|
31
|
+
// src/rbac/permissions.ts
|
|
32
|
+
var import_rbac = require("@uniforge/platform-core/rbac");
|
|
33
|
+
var DEFAULT_ROLE_PERMISSIONS = {
|
|
34
|
+
owner: [],
|
|
35
|
+
admin: [
|
|
36
|
+
"products:read",
|
|
37
|
+
"products:write",
|
|
38
|
+
"orders:read",
|
|
39
|
+
"orders:write",
|
|
40
|
+
"customers:read",
|
|
41
|
+
"customers:write",
|
|
42
|
+
"analytics:read",
|
|
43
|
+
"settings:read",
|
|
44
|
+
"settings:write",
|
|
45
|
+
"settings:manage",
|
|
46
|
+
"staff:read",
|
|
47
|
+
"staff:write"
|
|
48
|
+
],
|
|
49
|
+
staff: [
|
|
50
|
+
"products:read",
|
|
51
|
+
"products:write",
|
|
52
|
+
"orders:read",
|
|
53
|
+
"orders:write",
|
|
54
|
+
"customers:read",
|
|
55
|
+
"analytics:read",
|
|
56
|
+
"settings:read"
|
|
57
|
+
],
|
|
58
|
+
collaborator: [
|
|
59
|
+
"products:read",
|
|
60
|
+
"orders:read",
|
|
61
|
+
"analytics:read"
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
function resolvePermissions(role, customPermissions) {
|
|
65
|
+
const rolePerms = DEFAULT_ROLE_PERMISSIONS[role] ?? [];
|
|
66
|
+
if (!customPermissions || customPermissions.length === 0) {
|
|
67
|
+
return [...rolePerms];
|
|
68
|
+
}
|
|
69
|
+
const combined = /* @__PURE__ */ new Set([...rolePerms, ...customPermissions]);
|
|
70
|
+
return [...combined];
|
|
71
|
+
}
|
|
72
|
+
function isRoleAtLeast(role, minimumRole) {
|
|
73
|
+
const roleIndex = import_rbac.ROLE_HIERARCHY.indexOf(role);
|
|
74
|
+
const minIndex = import_rbac.ROLE_HIERARCHY.indexOf(minimumRole);
|
|
75
|
+
if (roleIndex === -1 || minIndex === -1) return false;
|
|
76
|
+
return roleIndex <= minIndex;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/rbac/service.ts
|
|
80
|
+
function mapShopMember(row) {
|
|
81
|
+
return {
|
|
82
|
+
id: row.id,
|
|
83
|
+
shopDomain: row.shopDomain,
|
|
84
|
+
userId: row.userId,
|
|
85
|
+
email: row.email,
|
|
86
|
+
role: row.role,
|
|
87
|
+
customPermissions: Array.isArray(row.customPermissions) ? row.customPermissions : null,
|
|
88
|
+
createdAt: row.createdAt,
|
|
89
|
+
updatedAt: row.updatedAt
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function createRBACService(prisma) {
|
|
93
|
+
return {
|
|
94
|
+
async upsertMember(input) {
|
|
95
|
+
const createData = {
|
|
96
|
+
shopDomain: input.shopDomain,
|
|
97
|
+
userId: input.userId,
|
|
98
|
+
email: input.email,
|
|
99
|
+
role: input.role
|
|
100
|
+
};
|
|
101
|
+
if (input.customPermissions !== void 0) {
|
|
102
|
+
createData.customPermissions = input.customPermissions;
|
|
103
|
+
}
|
|
104
|
+
const updateData = {
|
|
105
|
+
email: input.email,
|
|
106
|
+
role: input.role
|
|
107
|
+
};
|
|
108
|
+
if (input.customPermissions !== void 0) {
|
|
109
|
+
updateData.customPermissions = input.customPermissions;
|
|
110
|
+
}
|
|
111
|
+
const row = await prisma.shopMember.upsert({
|
|
112
|
+
where: {
|
|
113
|
+
shopDomain_userId: {
|
|
114
|
+
shopDomain: input.shopDomain,
|
|
115
|
+
userId: input.userId
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
create: createData,
|
|
119
|
+
update: updateData
|
|
120
|
+
});
|
|
121
|
+
return mapShopMember(row);
|
|
122
|
+
},
|
|
123
|
+
async getMember(shopDomain, userId) {
|
|
124
|
+
const row = await prisma.shopMember.findUnique({
|
|
125
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
126
|
+
});
|
|
127
|
+
return row ? mapShopMember(row) : null;
|
|
128
|
+
},
|
|
129
|
+
async listMembers(shopDomain) {
|
|
130
|
+
const rows = await prisma.shopMember.findMany({
|
|
131
|
+
where: { shopDomain }
|
|
132
|
+
});
|
|
133
|
+
return rows.map(mapShopMember);
|
|
134
|
+
},
|
|
135
|
+
async updateMember(shopDomain, userId, input) {
|
|
136
|
+
const data = {};
|
|
137
|
+
if (input.role !== void 0) {
|
|
138
|
+
data.role = input.role;
|
|
139
|
+
}
|
|
140
|
+
if (input.email !== void 0) {
|
|
141
|
+
data.email = input.email;
|
|
142
|
+
}
|
|
143
|
+
if (input.customPermissions !== void 0) {
|
|
144
|
+
data.customPermissions = input.customPermissions;
|
|
145
|
+
}
|
|
146
|
+
const row = await prisma.shopMember.update({
|
|
147
|
+
where: { shopDomain_userId: { shopDomain, userId } },
|
|
148
|
+
data
|
|
149
|
+
});
|
|
150
|
+
return mapShopMember(row);
|
|
151
|
+
},
|
|
152
|
+
async removeMember(shopDomain, userId) {
|
|
153
|
+
await prisma.shopMember.delete({
|
|
154
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
async hasPermission(shopDomain, userId, permission) {
|
|
158
|
+
const row = await prisma.shopMember.findUnique({
|
|
159
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
160
|
+
});
|
|
161
|
+
if (!row) return false;
|
|
162
|
+
if (row.role === "owner") return true;
|
|
163
|
+
const perms = resolvePermissions(
|
|
164
|
+
row.role,
|
|
165
|
+
Array.isArray(row.customPermissions) ? row.customPermissions : null
|
|
166
|
+
);
|
|
167
|
+
return perms.includes(permission);
|
|
168
|
+
},
|
|
169
|
+
async getEffectivePermissions(shopDomain, userId) {
|
|
170
|
+
const row = await prisma.shopMember.findUnique({
|
|
171
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
172
|
+
});
|
|
173
|
+
if (!row) return [];
|
|
174
|
+
if (row.role === "owner") return ["*"];
|
|
175
|
+
return resolvePermissions(
|
|
176
|
+
row.role,
|
|
177
|
+
Array.isArray(row.customPermissions) ? row.customPermissions : null
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
isRoleAtLeast
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/auth/route-matcher.ts
|
|
185
|
+
function normalizePath(url) {
|
|
186
|
+
const questionIdx = url.indexOf("?");
|
|
187
|
+
let path = questionIdx >= 0 ? url.slice(0, questionIdx) : url;
|
|
188
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
189
|
+
path = path.slice(0, -1);
|
|
190
|
+
}
|
|
191
|
+
return path;
|
|
192
|
+
}
|
|
193
|
+
function matchGlob(path, pattern) {
|
|
194
|
+
if (pattern === path) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (pattern.endsWith("/**")) {
|
|
198
|
+
const prefix = pattern.slice(0, -3);
|
|
199
|
+
return path.startsWith(prefix + "/");
|
|
200
|
+
}
|
|
201
|
+
if (pattern.includes("*") && !pattern.includes("**")) {
|
|
202
|
+
const regex = patternToRegex(pattern);
|
|
203
|
+
return regex.test(path);
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function patternToRegex(pattern) {
|
|
208
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+");
|
|
209
|
+
return new RegExp(`^${escaped}$`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/rbac/middleware.ts
|
|
213
|
+
function createRBACMiddleware(config) {
|
|
214
|
+
return {
|
|
215
|
+
async authorize(input) {
|
|
216
|
+
const { url, method, shopDomain, user } = input;
|
|
217
|
+
if (!user) {
|
|
218
|
+
return { type: "skipped", reason: "No user identity (offline session)" };
|
|
219
|
+
}
|
|
220
|
+
const normalizedPath = normalizePath(url);
|
|
221
|
+
const matchedRoute = config.routes.find(
|
|
222
|
+
(route) => matchGlob(normalizedPath, route.pattern) && (route.method === "*" || route.method.toUpperCase() === method.toUpperCase())
|
|
223
|
+
);
|
|
224
|
+
if (!matchedRoute) {
|
|
225
|
+
return { type: "skipped", reason: "No matching route permission rule" };
|
|
226
|
+
}
|
|
227
|
+
const permission = matchedRoute.permission;
|
|
228
|
+
const member = await config.getMember(shopDomain, user.userId);
|
|
229
|
+
if (!member && user.isAccountOwner && config.autoProvisionOwner !== false) {
|
|
230
|
+
if (config.onAutoProvision) {
|
|
231
|
+
await config.onAutoProvision(shopDomain, user.userId, user.email);
|
|
232
|
+
}
|
|
233
|
+
return { type: "granted", role: "owner", permission };
|
|
234
|
+
}
|
|
235
|
+
if (!member) {
|
|
236
|
+
return {
|
|
237
|
+
type: "denied",
|
|
238
|
+
role: null,
|
|
239
|
+
permission,
|
|
240
|
+
reason: "User is not a member of this shop"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (member.role === "owner") {
|
|
244
|
+
return { type: "granted", role: "owner", permission };
|
|
245
|
+
}
|
|
246
|
+
const effectivePerms = resolvePermissions(member.role, member.customPermissions);
|
|
247
|
+
if (effectivePerms.includes(permission)) {
|
|
248
|
+
return { type: "granted", role: member.role, permission };
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
type: "denied",
|
|
252
|
+
role: member.role,
|
|
253
|
+
permission,
|
|
254
|
+
reason: `Role "${member.role}" does not have permission "${permission}"`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
260
|
+
0 && (module.exports = {
|
|
261
|
+
DEFAULT_ROLE_PERMISSIONS,
|
|
262
|
+
createRBACMiddleware,
|
|
263
|
+
createRBACService,
|
|
264
|
+
isRoleAtLeast,
|
|
265
|
+
resolvePermissions
|
|
266
|
+
});
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/rbac/index.ts","../../src/rbac/permissions.ts","../../src/rbac/service.ts","../../src/auth/route-matcher.ts","../../src/rbac/middleware.ts"],"sourcesContent":["/**\n * @uniforge/core/rbac\n *\n * Prisma-backed RBAC implementation: role permissions, member service,\n * and route-level permission middleware.\n */\n\nexport { DEFAULT_ROLE_PERMISSIONS, resolvePermissions, isRoleAtLeast } from './permissions.js';\nexport { createRBACService } from './service.js';\nexport { createRBACMiddleware } from './middleware.js';\n","/**\n * Default role permissions and permission resolution.\n */\n\nimport type { Role, RolePermissionMap } from '@uniforge/platform-core/rbac';\nimport { ROLE_HIERARCHY } from '@uniforge/platform-core/rbac';\n\nexport const DEFAULT_ROLE_PERMISSIONS: RolePermissionMap = {\n owner: [],\n admin: [\n 'products:read', 'products:write',\n 'orders:read', 'orders:write',\n 'customers:read', 'customers:write',\n 'analytics:read',\n 'settings:read', 'settings:write', 'settings:manage',\n 'staff:read', 'staff:write',\n ],\n staff: [\n 'products:read', 'products:write',\n 'orders:read', 'orders:write',\n 'customers:read',\n 'analytics:read',\n 'settings:read',\n ],\n collaborator: [\n 'products:read',\n 'orders:read',\n 'analytics:read',\n ],\n};\n\nexport function resolvePermissions(\n role: Role,\n customPermissions?: string[] | null,\n): string[] {\n const rolePerms = DEFAULT_ROLE_PERMISSIONS[role] ?? [];\n if (!customPermissions || customPermissions.length === 0) {\n return [...rolePerms];\n }\n const combined = new Set([...rolePerms, ...customPermissions]);\n return [...combined];\n}\n\nexport function isRoleAtLeast(role: Role, minimumRole: Role): boolean {\n const roleIndex = ROLE_HIERARCHY.indexOf(role);\n const minIndex = ROLE_HIERARCHY.indexOf(minimumRole);\n if (roleIndex === -1 || minIndex === -1) return false;\n return roleIndex <= minIndex;\n}\n","/**\n * Prisma-backed RBAC service for shop member management and permission checks.\n */\n\nimport type { PrismaClient, Prisma } from '@prisma/client';\nimport type {\n RBACService,\n Role,\n ShopMember,\n UpsertShopMemberInput,\n UpdateShopMemberInput,\n} from '@uniforge/platform-core/rbac';\nimport { resolvePermissions, isRoleAtLeast as isRoleAtLeastFn } from './permissions.js';\n\ninterface ShopMemberRow {\n id: string;\n shopDomain: string;\n userId: number;\n email: string;\n role: string;\n customPermissions: unknown;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction mapShopMember(row: ShopMemberRow): ShopMember {\n return {\n id: row.id,\n shopDomain: row.shopDomain,\n userId: row.userId,\n email: row.email,\n role: row.role as Role,\n customPermissions: Array.isArray(row.customPermissions)\n ? (row.customPermissions as string[])\n : null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport function createRBACService(prisma: PrismaClient): RBACService {\n return {\n async upsertMember(input: UpsertShopMemberInput): Promise<ShopMember> {\n const createData: Prisma.ShopMemberUncheckedCreateInput = {\n shopDomain: input.shopDomain,\n userId: input.userId,\n email: input.email,\n role: input.role,\n };\n if (input.customPermissions !== undefined) {\n createData.customPermissions = input.customPermissions;\n }\n\n const updateData: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.customPermissions !== undefined) {\n updateData.customPermissions = input.customPermissions;\n }\n\n const row = await prisma.shopMember.upsert({\n where: {\n shopDomain_userId: {\n shopDomain: input.shopDomain,\n userId: input.userId,\n },\n },\n create: createData,\n update: updateData,\n });\n return mapShopMember(row);\n },\n\n async getMember(shopDomain: string, userId: number): Promise<ShopMember | null> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n return row ? mapShopMember(row) : null;\n },\n\n async listMembers(shopDomain: string): Promise<ShopMember[]> {\n const rows = await prisma.shopMember.findMany({\n where: { shopDomain },\n });\n return rows.map(mapShopMember);\n },\n\n async updateMember(\n shopDomain: string,\n userId: number,\n input: UpdateShopMemberInput,\n ): Promise<ShopMember> {\n const data: Record<string, unknown> = {};\n if (input.role !== undefined) {\n data.role = input.role;\n }\n if (input.email !== undefined) {\n data.email = input.email;\n }\n if (input.customPermissions !== undefined) {\n data.customPermissions = input.customPermissions;\n }\n\n const row = await prisma.shopMember.update({\n where: { shopDomain_userId: { shopDomain, userId } },\n data,\n });\n return mapShopMember(row);\n },\n\n async removeMember(shopDomain: string, userId: number): Promise<void> {\n await prisma.shopMember.delete({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n },\n\n async hasPermission(\n shopDomain: string,\n userId: number,\n permission: string,\n ): Promise<boolean> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n if (!row) return false;\n if (row.role === 'owner') return true;\n const perms = resolvePermissions(\n row.role as Role,\n Array.isArray(row.customPermissions) ? (row.customPermissions as string[]) : null,\n );\n return perms.includes(permission);\n },\n\n async getEffectivePermissions(\n shopDomain: string,\n userId: number,\n ): Promise<string[]> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n if (!row) return [];\n if (row.role === 'owner') return ['*'];\n return resolvePermissions(\n row.role as Role,\n Array.isArray(row.customPermissions) ? (row.customPermissions as string[]) : null,\n );\n },\n\n isRoleAtLeast: isRoleAtLeastFn,\n };\n}\n","/**\n * Route matcher utility for auth middleware.\n *\n * Determines whether a given URL path is public (no auth required)\n * or protected (auth required) based on glob pattern configuration.\n */\n\nimport type { RouteProtectionConfig } from '@uniforge/platform-core/auth';\n\n/** Auth callback paths that are always treated as public. */\nconst ALWAYS_PUBLIC_PATTERNS = ['/auth/callback', '/auth/*/callback'];\n\n/**\n * Check if a URL path is a public route that does not require authentication.\n *\n * Public routes take precedence over protected routes.\n * Auth callback paths (/auth/callback, /auth/STAR/callback) are always public.\n */\nexport function isPublicRoute(\n url: string,\n config: RouteProtectionConfig,\n): boolean {\n const path = normalizePath(url);\n\n // Auth callback routes are always public\n if (matchesAny(path, ALWAYS_PUBLIC_PATTERNS)) {\n return true;\n }\n\n const publicRoutes = config.publicRoutes ?? [];\n\n // Check explicit public routes (takes precedence)\n if (matchesAny(path, publicRoutes)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Normalize a URL path by stripping query parameters and trailing slashes.\n */\nexport function normalizePath(url: string): string {\n // Strip query string\n const questionIdx = url.indexOf('?');\n let path = questionIdx >= 0 ? url.slice(0, questionIdx) : url;\n\n // Strip trailing slash (but keep root /)\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1);\n }\n\n return path;\n}\n\n/**\n * Check if a path matches any of the given patterns.\n */\nfunction matchesAny(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(path, pattern));\n}\n\n/**\n * Simple glob pattern matcher supporting:\n * - Exact matches: `/health` matches `/health`\n * - Single segment wildcard `*`: `/admin/*` matches `/admin/foo` but not `/admin/foo/bar`\n * - Multi-segment wildcard `**`: `/api/**` matches `/api/foo`, `/api/foo/bar`, etc.\n */\nexport function matchGlob(path: string, pattern: string): boolean {\n // Exact match\n if (pattern === path) {\n return true;\n }\n\n // Handle ** (matches any number of path segments)\n if (pattern.endsWith('/**')) {\n const prefix = pattern.slice(0, -3); // Remove /**\n return path.startsWith(prefix + '/');\n }\n\n // Handle * (matches exactly one path segment)\n if (pattern.includes('*') && !pattern.includes('**')) {\n const regex = patternToRegex(pattern);\n return regex.test(path);\n }\n\n return false;\n}\n\n/**\n * Convert a glob pattern with single `*` wildcards to a regex.\n * `*` matches one path segment (no slashes).\n */\nfunction patternToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '[^/]+');\n return new RegExp(`^${escaped}$`);\n}\n","/**\n * RBAC middleware for route-level permission enforcement.\n */\n\nimport type {\n RBACMiddleware,\n RBACMiddlewareConfig,\n RBACResult,\n SessionUser,\n} from '@uniforge/platform-core/rbac';\nimport { matchGlob, normalizePath } from '../auth/route-matcher.js';\nimport { resolvePermissions } from './permissions.js';\n\nexport function createRBACMiddleware(config: RBACMiddlewareConfig): RBACMiddleware {\n return {\n async authorize(input: {\n url: string;\n method: string;\n shopDomain: string;\n user: SessionUser | null;\n }): Promise<RBACResult> {\n const { url, method, shopDomain, user } = input;\n\n if (!user) {\n return { type: 'skipped', reason: 'No user identity (offline session)' };\n }\n\n const normalizedPath = normalizePath(url);\n const matchedRoute = config.routes.find(\n (route) =>\n matchGlob(normalizedPath, route.pattern) &&\n (route.method === '*' || route.method.toUpperCase() === method.toUpperCase()),\n );\n\n if (!matchedRoute) {\n return { type: 'skipped', reason: 'No matching route permission rule' };\n }\n\n const permission = matchedRoute.permission;\n const member = await config.getMember(shopDomain, user.userId);\n\n if (!member && user.isAccountOwner && config.autoProvisionOwner !== false) {\n if (config.onAutoProvision) {\n await config.onAutoProvision(shopDomain, user.userId, user.email);\n }\n return { type: 'granted', role: 'owner', permission };\n }\n\n if (!member) {\n return {\n type: 'denied',\n role: null,\n permission,\n reason: 'User is not a member of this shop',\n };\n }\n\n if (member.role === 'owner') {\n return { type: 'granted', role: 'owner', permission };\n }\n\n const effectivePerms = resolvePermissions(member.role, member.customPermissions);\n if (effectivePerms.includes(permission)) {\n return { type: 'granted', role: member.role, permission };\n }\n\n return {\n type: 'denied',\n role: member.role,\n permission,\n reason: `Role \"${member.role}\" does not have permission \"${permission}\"`,\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,kBAA+B;AAExB,IAAM,2BAA8C;AAAA,EACzD,OAAO,CAAC;AAAA,EACR,OAAO;AAAA,IACL;AAAA,IAAiB;AAAA,IACjB;AAAA,IAAe;AAAA,IACf;AAAA,IAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IAAiB;AAAA,IAAkB;AAAA,IACnC;AAAA,IAAc;AAAA,EAChB;AAAA,EACA,OAAO;AAAA,IACL;AAAA,IAAiB;AAAA,IACjB;AAAA,IAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,mBACd,MACA,mBACU;AACV,QAAM,YAAY,yBAAyB,IAAI,KAAK,CAAC;AACrD,MAAI,CAAC,qBAAqB,kBAAkB,WAAW,GAAG;AACxD,WAAO,CAAC,GAAG,SAAS;AAAA,EACtB;AACA,QAAM,WAAW,oBAAI,IAAI,CAAC,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAC7D,SAAO,CAAC,GAAG,QAAQ;AACrB;AAEO,SAAS,cAAc,MAAY,aAA4B;AACpE,QAAM,YAAY,2BAAe,QAAQ,IAAI;AAC7C,QAAM,WAAW,2BAAe,QAAQ,WAAW;AACnD,MAAI,cAAc,MAAM,aAAa,GAAI,QAAO;AAChD,SAAO,aAAa;AACtB;;;ACvBA,SAAS,cAAc,KAAgC;AACrD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,YAAY,IAAI;AAAA,IAChB,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB,IACjD,IAAI,oBACL;AAAA,IACJ,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEO,SAAS,kBAAkB,QAAmC;AACnE,SAAO;AAAA,IACL,MAAM,aAAa,OAAmD;AACpE,YAAM,aAAoD;AAAA,QACxD,YAAY,MAAM;AAAA,QAClB,QAAQ,MAAM;AAAA,QACd,OAAO,MAAM;AAAA,QACb,MAAM,MAAM;AAAA,MACd;AACA,UAAI,MAAM,sBAAsB,QAAW;AACzC,mBAAW,oBAAoB,MAAM;AAAA,MACvC;AAEA,YAAM,aAAsC;AAAA,QAC1C,OAAO,MAAM;AAAA,QACb,MAAM,MAAM;AAAA,MACd;AACA,UAAI,MAAM,sBAAsB,QAAW;AACzC,mBAAW,oBAAoB,MAAM;AAAA,MACvC;AAEA,YAAM,MAAM,MAAM,OAAO,WAAW,OAAO;AAAA,QACzC,OAAO;AAAA,UACL,mBAAmB;AAAA,YACjB,YAAY,MAAM;AAAA,YAClB,QAAQ,MAAM;AAAA,UAChB;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,cAAc,GAAG;AAAA,IAC1B;AAAA,IAEA,MAAM,UAAU,YAAoB,QAA4C;AAC9E,YAAM,MAAM,MAAM,OAAO,WAAW,WAAW;AAAA,QAC7C,OAAO,EAAE,mBAAmB,EAAE,YAAY,OAAO,EAAE;AAAA,MACrD,CAAC;AACD,aAAO,MAAM,cAAc,GAAG,IAAI;AAAA,IACpC;AAAA,IAEA,MAAM,YAAY,YAA2C;AAC3D,YAAM,OAAO,MAAM,OAAO,WAAW,SAAS;AAAA,QAC5C,OAAO,EAAE,WAAW;AAAA,MACtB,CAAC;AACD,aAAO,KAAK,IAAI,aAAa;AAAA,IAC/B;AAAA,IAEA,MAAM,aACJ,YACA,QACA,OACqB;AACrB,YAAM,OAAgC,CAAC;AACvC,UAAI,MAAM,SAAS,QAAW;AAC5B,aAAK,OAAO,MAAM;AAAA,MACpB;AACA,UAAI,MAAM,UAAU,QAAW;AAC7B,aAAK,QAAQ,MAAM;AAAA,MACrB;AACA,UAAI,MAAM,sBAAsB,QAAW;AACzC,aAAK,oBAAoB,MAAM;AAAA,MACjC;AAEA,YAAM,MAAM,MAAM,OAAO,WAAW,OAAO;AAAA,QACzC,OAAO,EAAE,mBAAmB,EAAE,YAAY,OAAO,EAAE;AAAA,QACnD;AAAA,MACF,CAAC;AACD,aAAO,cAAc,GAAG;AAAA,IAC1B;AAAA,IAEA,MAAM,aAAa,YAAoB,QAA+B;AACpE,YAAM,OAAO,WAAW,OAAO;AAAA,QAC7B,OAAO,EAAE,mBAAmB,EAAE,YAAY,OAAO,EAAE;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,cACJ,YACA,QACA,YACkB;AAClB,YAAM,MAAM,MAAM,OAAO,WAAW,WAAW;AAAA,QAC7C,OAAO,EAAE,mBAAmB,EAAE,YAAY,OAAO,EAAE;AAAA,MACrD,CAAC;AACD,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,IAAI,SAAS,QAAS,QAAO;AACjC,YAAM,QAAQ;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM,QAAQ,IAAI,iBAAiB,IAAK,IAAI,oBAAiC;AAAA,MAC/E;AACA,aAAO,MAAM,SAAS,UAAU;AAAA,IAClC;AAAA,IAEA,MAAM,wBACJ,YACA,QACmB;AACnB,YAAM,MAAM,MAAM,OAAO,WAAW,WAAW;AAAA,QAC7C,OAAO,EAAE,mBAAmB,EAAE,YAAY,OAAO,EAAE;AAAA,MACrD,CAAC;AACD,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAI,IAAI,SAAS,QAAS,QAAO,CAAC,GAAG;AACrC,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,QAAQ,IAAI,iBAAiB,IAAK,IAAI,oBAAiC;AAAA,MAC/E;AAAA,IACF;AAAA,IAEA;AAAA,EACF;AACF;;;AC7GO,SAAS,cAAc,KAAqB;AAEjD,QAAM,cAAc,IAAI,QAAQ,GAAG;AACnC,MAAI,OAAO,eAAe,IAAI,IAAI,MAAM,GAAG,WAAW,IAAI;AAG1D,MAAI,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,GAAG;AACzC,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AAEA,SAAO;AACT;AAeO,SAAS,UAAU,MAAc,SAA0B;AAEhE,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,SAAS,KAAK,GAAG;AAC3B,UAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,WAAO,KAAK,WAAW,SAAS,GAAG;AAAA,EACrC;AAGA,MAAI,QAAQ,SAAS,GAAG,KAAK,CAAC,QAAQ,SAAS,IAAI,GAAG;AACpD,UAAM,QAAQ,eAAe,OAAO;AACpC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,SAAO;AACT;AAMA,SAAS,eAAe,SAAyB;AAC/C,QAAM,UAAU,QACb,QAAQ,qBAAqB,MAAM,EACnC,QAAQ,OAAO,OAAO;AACzB,SAAO,IAAI,OAAO,IAAI,OAAO,GAAG;AAClC;;;ACrFO,SAAS,qBAAqB,QAA8C;AACjF,SAAO;AAAA,IACL,MAAM,UAAU,OAKQ;AACtB,YAAM,EAAE,KAAK,QAAQ,YAAY,KAAK,IAAI;AAE1C,UAAI,CAAC,MAAM;AACT,eAAO,EAAE,MAAM,WAAW,QAAQ,qCAAqC;AAAA,MACzE;AAEA,YAAM,iBAAiB,cAAc,GAAG;AACxC,YAAM,eAAe,OAAO,OAAO;AAAA,QACjC,CAAC,UACC,UAAU,gBAAgB,MAAM,OAAO,MACtC,MAAM,WAAW,OAAO,MAAM,OAAO,YAAY,MAAM,OAAO,YAAY;AAAA,MAC/E;AAEA,UAAI,CAAC,cAAc;AACjB,eAAO,EAAE,MAAM,WAAW,QAAQ,oCAAoC;AAAA,MACxE;AAEA,YAAM,aAAa,aAAa;AAChC,YAAM,SAAS,MAAM,OAAO,UAAU,YAAY,KAAK,MAAM;AAE7D,UAAI,CAAC,UAAU,KAAK,kBAAkB,OAAO,uBAAuB,OAAO;AACzE,YAAI,OAAO,iBAAiB;AAC1B,gBAAM,OAAO,gBAAgB,YAAY,KAAK,QAAQ,KAAK,KAAK;AAAA,QAClE;AACA,eAAO,EAAE,MAAM,WAAW,MAAM,SAAS,WAAW;AAAA,MACtD;AAEA,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA,QAAQ;AAAA,QACV;AAAA,MACF;AAEA,UAAI,OAAO,SAAS,SAAS;AAC3B,eAAO,EAAE,MAAM,WAAW,MAAM,SAAS,WAAW;AAAA,MACtD;AAEA,YAAM,iBAAiB,mBAAmB,OAAO,MAAM,OAAO,iBAAiB;AAC/E,UAAI,eAAe,SAAS,UAAU,GAAG;AACvC,eAAO,EAAE,MAAM,WAAW,MAAM,OAAO,MAAM,WAAW;AAAA,MAC1D;AAEA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,OAAO;AAAA,QACb;AAAA,QACA,QAAQ,SAAS,OAAO,IAAI,+BAA+B,UAAU;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// src/rbac/permissions.ts
|
|
2
|
+
import { ROLE_HIERARCHY } from "@uniforge/platform-core/rbac";
|
|
3
|
+
var DEFAULT_ROLE_PERMISSIONS = {
|
|
4
|
+
owner: [],
|
|
5
|
+
admin: [
|
|
6
|
+
"products:read",
|
|
7
|
+
"products:write",
|
|
8
|
+
"orders:read",
|
|
9
|
+
"orders:write",
|
|
10
|
+
"customers:read",
|
|
11
|
+
"customers:write",
|
|
12
|
+
"analytics:read",
|
|
13
|
+
"settings:read",
|
|
14
|
+
"settings:write",
|
|
15
|
+
"settings:manage",
|
|
16
|
+
"staff:read",
|
|
17
|
+
"staff:write"
|
|
18
|
+
],
|
|
19
|
+
staff: [
|
|
20
|
+
"products:read",
|
|
21
|
+
"products:write",
|
|
22
|
+
"orders:read",
|
|
23
|
+
"orders:write",
|
|
24
|
+
"customers:read",
|
|
25
|
+
"analytics:read",
|
|
26
|
+
"settings:read"
|
|
27
|
+
],
|
|
28
|
+
collaborator: [
|
|
29
|
+
"products:read",
|
|
30
|
+
"orders:read",
|
|
31
|
+
"analytics:read"
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
function resolvePermissions(role, customPermissions) {
|
|
35
|
+
const rolePerms = DEFAULT_ROLE_PERMISSIONS[role] ?? [];
|
|
36
|
+
if (!customPermissions || customPermissions.length === 0) {
|
|
37
|
+
return [...rolePerms];
|
|
38
|
+
}
|
|
39
|
+
const combined = /* @__PURE__ */ new Set([...rolePerms, ...customPermissions]);
|
|
40
|
+
return [...combined];
|
|
41
|
+
}
|
|
42
|
+
function isRoleAtLeast(role, minimumRole) {
|
|
43
|
+
const roleIndex = ROLE_HIERARCHY.indexOf(role);
|
|
44
|
+
const minIndex = ROLE_HIERARCHY.indexOf(minimumRole);
|
|
45
|
+
if (roleIndex === -1 || minIndex === -1) return false;
|
|
46
|
+
return roleIndex <= minIndex;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/rbac/service.ts
|
|
50
|
+
function mapShopMember(row) {
|
|
51
|
+
return {
|
|
52
|
+
id: row.id,
|
|
53
|
+
shopDomain: row.shopDomain,
|
|
54
|
+
userId: row.userId,
|
|
55
|
+
email: row.email,
|
|
56
|
+
role: row.role,
|
|
57
|
+
customPermissions: Array.isArray(row.customPermissions) ? row.customPermissions : null,
|
|
58
|
+
createdAt: row.createdAt,
|
|
59
|
+
updatedAt: row.updatedAt
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createRBACService(prisma) {
|
|
63
|
+
return {
|
|
64
|
+
async upsertMember(input) {
|
|
65
|
+
const createData = {
|
|
66
|
+
shopDomain: input.shopDomain,
|
|
67
|
+
userId: input.userId,
|
|
68
|
+
email: input.email,
|
|
69
|
+
role: input.role
|
|
70
|
+
};
|
|
71
|
+
if (input.customPermissions !== void 0) {
|
|
72
|
+
createData.customPermissions = input.customPermissions;
|
|
73
|
+
}
|
|
74
|
+
const updateData = {
|
|
75
|
+
email: input.email,
|
|
76
|
+
role: input.role
|
|
77
|
+
};
|
|
78
|
+
if (input.customPermissions !== void 0) {
|
|
79
|
+
updateData.customPermissions = input.customPermissions;
|
|
80
|
+
}
|
|
81
|
+
const row = await prisma.shopMember.upsert({
|
|
82
|
+
where: {
|
|
83
|
+
shopDomain_userId: {
|
|
84
|
+
shopDomain: input.shopDomain,
|
|
85
|
+
userId: input.userId
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
create: createData,
|
|
89
|
+
update: updateData
|
|
90
|
+
});
|
|
91
|
+
return mapShopMember(row);
|
|
92
|
+
},
|
|
93
|
+
async getMember(shopDomain, userId) {
|
|
94
|
+
const row = await prisma.shopMember.findUnique({
|
|
95
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
96
|
+
});
|
|
97
|
+
return row ? mapShopMember(row) : null;
|
|
98
|
+
},
|
|
99
|
+
async listMembers(shopDomain) {
|
|
100
|
+
const rows = await prisma.shopMember.findMany({
|
|
101
|
+
where: { shopDomain }
|
|
102
|
+
});
|
|
103
|
+
return rows.map(mapShopMember);
|
|
104
|
+
},
|
|
105
|
+
async updateMember(shopDomain, userId, input) {
|
|
106
|
+
const data = {};
|
|
107
|
+
if (input.role !== void 0) {
|
|
108
|
+
data.role = input.role;
|
|
109
|
+
}
|
|
110
|
+
if (input.email !== void 0) {
|
|
111
|
+
data.email = input.email;
|
|
112
|
+
}
|
|
113
|
+
if (input.customPermissions !== void 0) {
|
|
114
|
+
data.customPermissions = input.customPermissions;
|
|
115
|
+
}
|
|
116
|
+
const row = await prisma.shopMember.update({
|
|
117
|
+
where: { shopDomain_userId: { shopDomain, userId } },
|
|
118
|
+
data
|
|
119
|
+
});
|
|
120
|
+
return mapShopMember(row);
|
|
121
|
+
},
|
|
122
|
+
async removeMember(shopDomain, userId) {
|
|
123
|
+
await prisma.shopMember.delete({
|
|
124
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
async hasPermission(shopDomain, userId, permission) {
|
|
128
|
+
const row = await prisma.shopMember.findUnique({
|
|
129
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
130
|
+
});
|
|
131
|
+
if (!row) return false;
|
|
132
|
+
if (row.role === "owner") return true;
|
|
133
|
+
const perms = resolvePermissions(
|
|
134
|
+
row.role,
|
|
135
|
+
Array.isArray(row.customPermissions) ? row.customPermissions : null
|
|
136
|
+
);
|
|
137
|
+
return perms.includes(permission);
|
|
138
|
+
},
|
|
139
|
+
async getEffectivePermissions(shopDomain, userId) {
|
|
140
|
+
const row = await prisma.shopMember.findUnique({
|
|
141
|
+
where: { shopDomain_userId: { shopDomain, userId } }
|
|
142
|
+
});
|
|
143
|
+
if (!row) return [];
|
|
144
|
+
if (row.role === "owner") return ["*"];
|
|
145
|
+
return resolvePermissions(
|
|
146
|
+
row.role,
|
|
147
|
+
Array.isArray(row.customPermissions) ? row.customPermissions : null
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
isRoleAtLeast
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/auth/route-matcher.ts
|
|
155
|
+
function normalizePath(url) {
|
|
156
|
+
const questionIdx = url.indexOf("?");
|
|
157
|
+
let path = questionIdx >= 0 ? url.slice(0, questionIdx) : url;
|
|
158
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
159
|
+
path = path.slice(0, -1);
|
|
160
|
+
}
|
|
161
|
+
return path;
|
|
162
|
+
}
|
|
163
|
+
function matchGlob(path, pattern) {
|
|
164
|
+
if (pattern === path) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (pattern.endsWith("/**")) {
|
|
168
|
+
const prefix = pattern.slice(0, -3);
|
|
169
|
+
return path.startsWith(prefix + "/");
|
|
170
|
+
}
|
|
171
|
+
if (pattern.includes("*") && !pattern.includes("**")) {
|
|
172
|
+
const regex = patternToRegex(pattern);
|
|
173
|
+
return regex.test(path);
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
function patternToRegex(pattern) {
|
|
178
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+");
|
|
179
|
+
return new RegExp(`^${escaped}$`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/rbac/middleware.ts
|
|
183
|
+
function createRBACMiddleware(config) {
|
|
184
|
+
return {
|
|
185
|
+
async authorize(input) {
|
|
186
|
+
const { url, method, shopDomain, user } = input;
|
|
187
|
+
if (!user) {
|
|
188
|
+
return { type: "skipped", reason: "No user identity (offline session)" };
|
|
189
|
+
}
|
|
190
|
+
const normalizedPath = normalizePath(url);
|
|
191
|
+
const matchedRoute = config.routes.find(
|
|
192
|
+
(route) => matchGlob(normalizedPath, route.pattern) && (route.method === "*" || route.method.toUpperCase() === method.toUpperCase())
|
|
193
|
+
);
|
|
194
|
+
if (!matchedRoute) {
|
|
195
|
+
return { type: "skipped", reason: "No matching route permission rule" };
|
|
196
|
+
}
|
|
197
|
+
const permission = matchedRoute.permission;
|
|
198
|
+
const member = await config.getMember(shopDomain, user.userId);
|
|
199
|
+
if (!member && user.isAccountOwner && config.autoProvisionOwner !== false) {
|
|
200
|
+
if (config.onAutoProvision) {
|
|
201
|
+
await config.onAutoProvision(shopDomain, user.userId, user.email);
|
|
202
|
+
}
|
|
203
|
+
return { type: "granted", role: "owner", permission };
|
|
204
|
+
}
|
|
205
|
+
if (!member) {
|
|
206
|
+
return {
|
|
207
|
+
type: "denied",
|
|
208
|
+
role: null,
|
|
209
|
+
permission,
|
|
210
|
+
reason: "User is not a member of this shop"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (member.role === "owner") {
|
|
214
|
+
return { type: "granted", role: "owner", permission };
|
|
215
|
+
}
|
|
216
|
+
const effectivePerms = resolvePermissions(member.role, member.customPermissions);
|
|
217
|
+
if (effectivePerms.includes(permission)) {
|
|
218
|
+
return { type: "granted", role: member.role, permission };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
type: "denied",
|
|
222
|
+
role: member.role,
|
|
223
|
+
permission,
|
|
224
|
+
reason: `Role "${member.role}" does not have permission "${permission}"`
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
DEFAULT_ROLE_PERMISSIONS,
|
|
231
|
+
createRBACMiddleware,
|
|
232
|
+
createRBACService,
|
|
233
|
+
isRoleAtLeast,
|
|
234
|
+
resolvePermissions
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/rbac/permissions.ts","../../src/rbac/service.ts","../../src/auth/route-matcher.ts","../../src/rbac/middleware.ts"],"sourcesContent":["/**\n * Default role permissions and permission resolution.\n */\n\nimport type { Role, RolePermissionMap } from '@uniforge/platform-core/rbac';\nimport { ROLE_HIERARCHY } from '@uniforge/platform-core/rbac';\n\nexport const DEFAULT_ROLE_PERMISSIONS: RolePermissionMap = {\n owner: [],\n admin: [\n 'products:read', 'products:write',\n 'orders:read', 'orders:write',\n 'customers:read', 'customers:write',\n 'analytics:read',\n 'settings:read', 'settings:write', 'settings:manage',\n 'staff:read', 'staff:write',\n ],\n staff: [\n 'products:read', 'products:write',\n 'orders:read', 'orders:write',\n 'customers:read',\n 'analytics:read',\n 'settings:read',\n ],\n collaborator: [\n 'products:read',\n 'orders:read',\n 'analytics:read',\n ],\n};\n\nexport function resolvePermissions(\n role: Role,\n customPermissions?: string[] | null,\n): string[] {\n const rolePerms = DEFAULT_ROLE_PERMISSIONS[role] ?? [];\n if (!customPermissions || customPermissions.length === 0) {\n return [...rolePerms];\n }\n const combined = new Set([...rolePerms, ...customPermissions]);\n return [...combined];\n}\n\nexport function isRoleAtLeast(role: Role, minimumRole: Role): boolean {\n const roleIndex = ROLE_HIERARCHY.indexOf(role);\n const minIndex = ROLE_HIERARCHY.indexOf(minimumRole);\n if (roleIndex === -1 || minIndex === -1) return false;\n return roleIndex <= minIndex;\n}\n","/**\n * Prisma-backed RBAC service for shop member management and permission checks.\n */\n\nimport type { PrismaClient, Prisma } from '@prisma/client';\nimport type {\n RBACService,\n Role,\n ShopMember,\n UpsertShopMemberInput,\n UpdateShopMemberInput,\n} from '@uniforge/platform-core/rbac';\nimport { resolvePermissions, isRoleAtLeast as isRoleAtLeastFn } from './permissions.js';\n\ninterface ShopMemberRow {\n id: string;\n shopDomain: string;\n userId: number;\n email: string;\n role: string;\n customPermissions: unknown;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction mapShopMember(row: ShopMemberRow): ShopMember {\n return {\n id: row.id,\n shopDomain: row.shopDomain,\n userId: row.userId,\n email: row.email,\n role: row.role as Role,\n customPermissions: Array.isArray(row.customPermissions)\n ? (row.customPermissions as string[])\n : null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport function createRBACService(prisma: PrismaClient): RBACService {\n return {\n async upsertMember(input: UpsertShopMemberInput): Promise<ShopMember> {\n const createData: Prisma.ShopMemberUncheckedCreateInput = {\n shopDomain: input.shopDomain,\n userId: input.userId,\n email: input.email,\n role: input.role,\n };\n if (input.customPermissions !== undefined) {\n createData.customPermissions = input.customPermissions;\n }\n\n const updateData: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.customPermissions !== undefined) {\n updateData.customPermissions = input.customPermissions;\n }\n\n const row = await prisma.shopMember.upsert({\n where: {\n shopDomain_userId: {\n shopDomain: input.shopDomain,\n userId: input.userId,\n },\n },\n create: createData,\n update: updateData,\n });\n return mapShopMember(row);\n },\n\n async getMember(shopDomain: string, userId: number): Promise<ShopMember | null> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n return row ? mapShopMember(row) : null;\n },\n\n async listMembers(shopDomain: string): Promise<ShopMember[]> {\n const rows = await prisma.shopMember.findMany({\n where: { shopDomain },\n });\n return rows.map(mapShopMember);\n },\n\n async updateMember(\n shopDomain: string,\n userId: number,\n input: UpdateShopMemberInput,\n ): Promise<ShopMember> {\n const data: Record<string, unknown> = {};\n if (input.role !== undefined) {\n data.role = input.role;\n }\n if (input.email !== undefined) {\n data.email = input.email;\n }\n if (input.customPermissions !== undefined) {\n data.customPermissions = input.customPermissions;\n }\n\n const row = await prisma.shopMember.update({\n where: { shopDomain_userId: { shopDomain, userId } },\n data,\n });\n return mapShopMember(row);\n },\n\n async removeMember(shopDomain: string, userId: number): Promise<void> {\n await prisma.shopMember.delete({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n },\n\n async hasPermission(\n shopDomain: string,\n userId: number,\n permission: string,\n ): Promise<boolean> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n if (!row) return false;\n if (row.role === 'owner') return true;\n const perms = resolvePermissions(\n row.role as Role,\n Array.isArray(row.customPermissions) ? (row.customPermissions as string[]) : null,\n );\n return perms.includes(permission);\n },\n\n async getEffectivePermissions(\n shopDomain: string,\n userId: number,\n ): Promise<string[]> {\n const row = await prisma.shopMember.findUnique({\n where: { shopDomain_userId: { shopDomain, userId } },\n });\n if (!row) return [];\n if (row.role === 'owner') return ['*'];\n return resolvePermissions(\n row.role as Role,\n Array.isArray(row.customPermissions) ? (row.customPermissions as string[]) : null,\n );\n },\n\n isRoleAtLeast: isRoleAtLeastFn,\n };\n}\n","/**\n * Route matcher utility for auth middleware.\n *\n * Determines whether a given URL path is public (no auth required)\n * or protected (auth required) based on glob pattern configuration.\n */\n\nimport type { RouteProtectionConfig } from '@uniforge/platform-core/auth';\n\n/** Auth callback paths that are always treated as public. */\nconst ALWAYS_PUBLIC_PATTERNS = ['/auth/callback', '/auth/*/callback'];\n\n/**\n * Check if a URL path is a public route that does not require authentication.\n *\n * Public routes take precedence over protected routes.\n * Auth callback paths (/auth/callback, /auth/STAR/callback) are always public.\n */\nexport function isPublicRoute(\n url: string,\n config: RouteProtectionConfig,\n): boolean {\n const path = normalizePath(url);\n\n // Auth callback routes are always public\n if (matchesAny(path, ALWAYS_PUBLIC_PATTERNS)) {\n return true;\n }\n\n const publicRoutes = config.publicRoutes ?? [];\n\n // Check explicit public routes (takes precedence)\n if (matchesAny(path, publicRoutes)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Normalize a URL path by stripping query parameters and trailing slashes.\n */\nexport function normalizePath(url: string): string {\n // Strip query string\n const questionIdx = url.indexOf('?');\n let path = questionIdx >= 0 ? url.slice(0, questionIdx) : url;\n\n // Strip trailing slash (but keep root /)\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1);\n }\n\n return path;\n}\n\n/**\n * Check if a path matches any of the given patterns.\n */\nfunction matchesAny(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(path, pattern));\n}\n\n/**\n * Simple glob pattern matcher supporting:\n * - Exact matches: `/health` matches `/health`\n * - Single segment wildcard `*`: `/admin/*` matches `/admin/foo` but not `/admin/foo/bar`\n * - Multi-segment wildcard `**`: `/api/**` matches `/api/foo`, `/api/foo/bar`, etc.\n */\nexport function matchGlob(path: string, pattern: string): boolean {\n // Exact match\n if (pattern === path) {\n return true;\n }\n\n // Handle ** (matches any number of path segments)\n if (pattern.endsWith('/**')) {\n const prefix = pattern.slice(0, -3); // Remove /**\n return path.startsWith(prefix + '/');\n }\n\n // Handle * (matches exactly one path segment)\n if (pattern.includes('*') && !pattern.includes('**')) {\n const regex = patternToRegex(pattern);\n return regex.test(path);\n }\n\n return false;\n}\n\n/**\n * Convert a glob pattern with single `*` wildcards to a regex.\n * `*` matches one path segment (no slashes).\n */\nfunction patternToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '[^/]+');\n return new RegExp(`^${escaped}$`);\n}\n","/**\n * RBAC middleware for route-level permission enforcement.\n */\n\nimport type {\n RBACMiddleware,\n RBACMiddlewareConfig,\n RBACResult,\n SessionUser,\n} from '@uniforge/platform-core/rbac';\nimport { matchGlob, normalizePath } from '../auth/route-matcher.js';\nimport { resolvePermissions } from './permissions.js';\n\nexport function createRBACMiddleware(config: RBACMiddlewareConfig): RBACMiddleware {\n return {\n async authorize(input: {\n url: string;\n method: string;\n shopDomain: string;\n user: SessionUser | null;\n }): Promise<RBACResult> {\n const { url, method, shopDomain, user } = input;\n\n if (!user) {\n return { type: 'skipped', reason: 'No user identity (offline session)' };\n }\n\n const normalizedPath = normalizePath(url);\n const matchedRoute = config.routes.find(\n (route) =>\n matchGlob(normalizedPath, route.pattern) &&\n (route.method === '*' || route.method.toUpperCase() === method.toUpperCase()),\n );\n\n if (!matchedRoute) {\n return { type: 'skipped', reason: 'No matching route permission rule' };\n }\n\n const permission = matchedRoute.permission;\n const member = await config.getMember(shopDomain, user.userId);\n\n if (!member && user.isAccountOwner && config.autoProvisionOwner !== false) {\n if (config.onAutoProvision) {\n await config.onAutoProvision(shopDomain, user.userId, user.email);\n }\n return { type: 'granted', role: 'owner', permission };\n }\n\n if (!member) {\n return {\n type: 'denied',\n role: null,\n permission,\n reason: 'User is not a member of this shop',\n };\n }\n\n if (member.role === 'owner') {\n return { type: 'granted', role: 'owner', permission };\n }\n\n const effectivePerms = resolvePermissions(member.role, member.customPermissions);\n if (effectivePerms.includes(permission)) {\n return { type: 'granted', role: member.role, permission };\n }\n\n return {\n type: 'denied',\n role: member.role,\n permission,\n reason: `Role \"${member.role}\" does not have permission \"${permission}\"`,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface UniforgeConfig {
|
|
2
|
+
appName: string;
|
|
3
|
+
shopify: {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiSecretKey: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
hostName: string;
|
|
8
|
+
apiVersion: string;
|
|
9
|
+
isEmbeddedApp: boolean;
|
|
10
|
+
};
|
|
11
|
+
database: {
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
redis: {
|
|
15
|
+
url: string;
|
|
16
|
+
};
|
|
17
|
+
features: {
|
|
18
|
+
encryption: boolean;
|
|
19
|
+
dualSessionStorage: boolean;
|
|
20
|
+
};
|
|
21
|
+
multiStore?: {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
defaultAccountType: 'individual' | 'enterprise';
|
|
24
|
+
autoProvisionAccount: boolean;
|
|
25
|
+
basePrice: number;
|
|
26
|
+
includedStores: number;
|
|
27
|
+
};
|
|
28
|
+
billing?: {
|
|
29
|
+
returnUrl: string;
|
|
30
|
+
trialDays?: number;
|
|
31
|
+
};
|
|
32
|
+
platform?: {
|
|
33
|
+
id: string;
|
|
34
|
+
autoRegister: boolean;
|
|
35
|
+
};
|
|
36
|
+
security?: {
|
|
37
|
+
rateLimiting: boolean;
|
|
38
|
+
inputValidation: boolean;
|
|
39
|
+
securityHeaders: boolean;
|
|
40
|
+
csp: boolean;
|
|
41
|
+
auditEnabled: boolean;
|
|
42
|
+
};
|
|
43
|
+
performance?: {
|
|
44
|
+
cacheEnabled: boolean;
|
|
45
|
+
cacheTTL: number;
|
|
46
|
+
cacheMaxEntries: number;
|
|
47
|
+
queryCostTracking: boolean;
|
|
48
|
+
queryBatching: boolean;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
declare const defaultConfig: Partial<UniforgeConfig>;
|
|
52
|
+
|
|
53
|
+
export { type UniforgeConfig as U, defaultConfig as d };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface UniforgeConfig {
|
|
2
|
+
appName: string;
|
|
3
|
+
shopify: {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiSecretKey: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
hostName: string;
|
|
8
|
+
apiVersion: string;
|
|
9
|
+
isEmbeddedApp: boolean;
|
|
10
|
+
};
|
|
11
|
+
database: {
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
redis: {
|
|
15
|
+
url: string;
|
|
16
|
+
};
|
|
17
|
+
features: {
|
|
18
|
+
encryption: boolean;
|
|
19
|
+
dualSessionStorage: boolean;
|
|
20
|
+
};
|
|
21
|
+
multiStore?: {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
defaultAccountType: 'individual' | 'enterprise';
|
|
24
|
+
autoProvisionAccount: boolean;
|
|
25
|
+
basePrice: number;
|
|
26
|
+
includedStores: number;
|
|
27
|
+
};
|
|
28
|
+
billing?: {
|
|
29
|
+
returnUrl: string;
|
|
30
|
+
trialDays?: number;
|
|
31
|
+
};
|
|
32
|
+
platform?: {
|
|
33
|
+
id: string;
|
|
34
|
+
autoRegister: boolean;
|
|
35
|
+
};
|
|
36
|
+
security?: {
|
|
37
|
+
rateLimiting: boolean;
|
|
38
|
+
inputValidation: boolean;
|
|
39
|
+
securityHeaders: boolean;
|
|
40
|
+
csp: boolean;
|
|
41
|
+
auditEnabled: boolean;
|
|
42
|
+
};
|
|
43
|
+
performance?: {
|
|
44
|
+
cacheEnabled: boolean;
|
|
45
|
+
cacheTTL: number;
|
|
46
|
+
cacheMaxEntries: number;
|
|
47
|
+
queryCostTracking: boolean;
|
|
48
|
+
queryBatching: boolean;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
declare const defaultConfig: Partial<UniforgeConfig>;
|
|
52
|
+
|
|
53
|
+
export { type UniforgeConfig as U, defaultConfig as d };
|