@infuro/cms-core 1.0.8 → 1.0.10

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.
Files changed (43) hide show
  1. package/dist/admin.cjs +2562 -1176
  2. package/dist/admin.cjs.map +1 -1
  3. package/dist/admin.d.cts +41 -2
  4. package/dist/admin.d.ts +41 -2
  5. package/dist/admin.js +2596 -1214
  6. package/dist/admin.js.map +1 -1
  7. package/dist/api.cjs +1695 -151
  8. package/dist/api.cjs.map +1 -1
  9. package/dist/api.d.cts +2 -1
  10. package/dist/api.d.ts +2 -1
  11. package/dist/api.js +1689 -146
  12. package/dist/api.js.map +1 -1
  13. package/dist/auth.cjs +153 -9
  14. package/dist/auth.cjs.map +1 -1
  15. package/dist/auth.d.cts +17 -27
  16. package/dist/auth.d.ts +17 -27
  17. package/dist/auth.js +143 -8
  18. package/dist/auth.js.map +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +1 -1
  22. package/dist/cli.js.map +1 -1
  23. package/dist/helpers-dlrF_49e.d.cts +60 -0
  24. package/dist/helpers-dlrF_49e.d.ts +60 -0
  25. package/dist/{index-P5ajDo8-.d.ts → index-C_CZLmHD.d.cts} +88 -1
  26. package/dist/{index-P5ajDo8-.d.cts → index-DeO4AnAj.d.ts} +88 -1
  27. package/dist/index.cjs +3340 -715
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +154 -5
  30. package/dist/index.d.ts +154 -5
  31. package/dist/index.js +2821 -223
  32. package/dist/index.js.map +1 -1
  33. package/dist/migrations/1772178563555-ChatAndKnowledgeBase.ts +55 -0
  34. package/dist/migrations/{1731900000000-KnowledgeBaseVector.ts → 1772178563556-KnowledgeBaseVector.ts} +3 -4
  35. package/dist/migrations/1774300000000-RbacSeedGroupsAndPermissionUnique.ts +24 -0
  36. package/dist/migrations/1774300000001-SeedAdministratorUsersPermission.ts +35 -0
  37. package/dist/migrations/1774400000000-CustomerAdminAccessContactUser.ts +37 -0
  38. package/dist/migrations/1774400000001-StorefrontCartWishlist.ts +100 -0
  39. package/dist/migrations/1774400000002-WishlistGuestId.ts +29 -0
  40. package/dist/migrations/1774500000000-ProductCollectionHsn.ts +15 -0
  41. package/package.json +13 -7
  42. package/dist/migrations/1731800000000-ChatAndKnowledgeBase.ts +0 -39
  43. /package/{dist → src/admin}/admin.css +0 -0
package/dist/auth.cjs CHANGED
@@ -30,19 +30,93 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/auth/index.ts
31
31
  var auth_exports = {};
32
32
  __export(auth_exports, {
33
+ ADMIN_GROUP_NAME: () => ADMIN_GROUP_NAME,
33
34
  OPEN_ENDPOINTS: () => OPEN_ENDPOINTS,
34
35
  PERMISSION_REQUIRED_ENDPOINTS: () => PERMISSION_REQUIRED_ENDPOINTS,
36
+ RBAC_ADMIN_ONLY_ENTITIES: () => RBAC_ADMIN_ONLY_ENTITIES,
37
+ canManageRoles: () => canManageRoles,
35
38
  createAuthHelpers: () => createAuthHelpers,
36
39
  createCmsMiddleware: () => createCmsMiddleware,
37
40
  defaultPublicApiMethods: () => defaultPublicApiMethods,
38
41
  getNextAuthOptions: () => getNextAuthOptions,
42
+ getPermissionableEntityKeys: () => getPermissionableEntityKeys,
39
43
  getRequiredPermission: () => getRequiredPermission,
44
+ hasEntityPermission: () => hasEntityPermission,
40
45
  isOpenEndpoint: () => isOpenEndpoint,
41
- isPublicMethod: () => isPublicMethod
46
+ isPublicMethod: () => isPublicMethod,
47
+ isSuperAdminGroupName: () => isSuperAdminGroupName,
48
+ permissionRowsToRecord: () => permissionRowsToRecord,
49
+ seedAdministratorPermissions: () => seedAdministratorPermissions,
50
+ sessionHasEntityAccess: () => sessionHasEntityAccess
42
51
  });
43
52
  module.exports = __toCommonJS(auth_exports);
44
53
 
54
+ // src/auth/permission-entities.ts
55
+ var PERMISSION_ENTITY_INTERNAL_EXCLUDE = /* @__PURE__ */ new Set([
56
+ "users",
57
+ "password_reset_tokens",
58
+ "user_groups",
59
+ "permissions",
60
+ "comments",
61
+ "form_fields",
62
+ "configs",
63
+ "knowledge_base_chunks",
64
+ "carts",
65
+ "cart_items",
66
+ "wishlists",
67
+ "wishlist_items"
68
+ ]);
69
+ var PERMISSION_LOGICAL_ENTITIES = [
70
+ "users",
71
+ "forms",
72
+ "form_submissions",
73
+ "dashboard",
74
+ "upload",
75
+ "settings",
76
+ "analytics",
77
+ "chat"
78
+ ];
79
+ var ADMIN_GROUP_NAME = "Administrator";
80
+ function isSuperAdminGroupName(name) {
81
+ return name === ADMIN_GROUP_NAME;
82
+ }
83
+ function getPermissionableEntityKeys(entityMap) {
84
+ const fromMap = Object.keys(entityMap).filter((k) => !PERMISSION_ENTITY_INTERNAL_EXCLUDE.has(k));
85
+ const logical = PERMISSION_LOGICAL_ENTITIES.filter((k) => !fromMap.includes(k));
86
+ return [...fromMap.sort(), ...logical].filter((k, i, a) => a.indexOf(k) === i);
87
+ }
88
+ function permissionRowsToRecord(rows) {
89
+ const out = {};
90
+ if (!rows?.length) return out;
91
+ for (const p of rows) {
92
+ out[p.entity] = {
93
+ c: !!p.canCreate,
94
+ r: !!p.canRead,
95
+ u: !!p.canUpdate,
96
+ d: !!p.canDelete
97
+ };
98
+ }
99
+ return out;
100
+ }
101
+ function hasEntityPermission(record, entity, action) {
102
+ const p = record?.[entity];
103
+ if (!p) return false;
104
+ if (action === "create") return p.c;
105
+ if (action === "read") return p.r;
106
+ if (action === "update") return p.u;
107
+ return p.d;
108
+ }
109
+
45
110
  // src/auth/helpers.ts
111
+ var RBAC_ADMIN_ONLY_ENTITIES = /* @__PURE__ */ new Set(["users", "user_groups", "permissions"]);
112
+ function sessionHasEntityAccess(user, entity, action) {
113
+ if (!user?.email) return false;
114
+ if (user.isRBACAdmin && RBAC_ADMIN_ONLY_ENTITIES.has(entity)) return true;
115
+ return hasEntityPermission(user.entityPerms, entity, action);
116
+ }
117
+ function canManageRoles(user) {
118
+ return !!(user?.email && user.isRBACAdmin);
119
+ }
46
120
  var OPEN_ENDPOINTS = [
47
121
  { "/api/contacts": ["POST"] },
48
122
  { "/api/form-submissions": ["POST"] },
@@ -78,6 +152,25 @@ function createAuthHelpers(getSession, NextResponse) {
78
152
  }
79
153
  return null;
80
154
  },
155
+ async requireEntityPermission(_req, entity, action) {
156
+ const session = await getSession();
157
+ if (!session?.user?.email) {
158
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
159
+ }
160
+ const u = session.user;
161
+ if (sessionHasEntityAccess(u, entity, action)) return null;
162
+ return NextResponse.json({ error: "Forbidden", entity, action }, { status: 403 });
163
+ },
164
+ async requireAdminAccess() {
165
+ const session = await getSession();
166
+ if (!session?.user?.email) {
167
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
168
+ }
169
+ const u = session.user;
170
+ if (u.isRBACAdmin) return null;
171
+ if (u.adminAccess === false) return NextResponse.json({ error: "Forbidden", reason: "admin_access" }, { status: 403 });
172
+ return null;
173
+ },
81
174
  async getAuthenticatedUser() {
82
175
  const session = await getSession();
83
176
  return session?.user ?? null;
@@ -85,6 +178,36 @@ function createAuthHelpers(getSession, NextResponse) {
85
178
  };
86
179
  }
87
180
 
181
+ // src/auth/seed-permissions.ts
182
+ async function seedAdministratorPermissions(dataSource, entityMap) {
183
+ const entities = getPermissionableEntityKeys(entityMap);
184
+ const groupRepo = dataSource.getRepository(entityMap.user_groups);
185
+ const permRepo = dataSource.getRepository(entityMap.permissions);
186
+ const adminGroup = await groupRepo.findOne({ where: { name: ADMIN_GROUP_NAME, deleted: false } });
187
+ if (!adminGroup) return;
188
+ const fullCrud = { canCreate: true, canRead: true, canUpdate: true, canDelete: true };
189
+ for (const entity of entities) {
190
+ const existing = await permRepo.findOne({
191
+ where: { groupId: adminGroup.id, entity }
192
+ });
193
+ if (existing) {
194
+ existing.canCreate = true;
195
+ existing.canRead = true;
196
+ existing.canUpdate = true;
197
+ existing.canDelete = true;
198
+ await permRepo.save(existing);
199
+ } else {
200
+ await permRepo.save(
201
+ permRepo.create({
202
+ groupId: adminGroup.id,
203
+ entity,
204
+ ...fullCrud
205
+ })
206
+ );
207
+ }
208
+ }
209
+ }
210
+
88
211
  // src/auth/middleware.ts
89
212
  var defaultPublicApiMethods = {
90
213
  "/api/contacts": ["POST"],
@@ -159,12 +282,18 @@ function getNextAuthOptions(config) {
159
282
  if (!user || user.blocked || user.deleted || !user.password) return null;
160
283
  const valid = await comparePassword(credentials.password, user.password);
161
284
  if (!valid) return null;
285
+ const g = user.group;
286
+ const isRBACAdmin = isSuperAdminGroupName(g?.name);
287
+ const entityPerms = permissionRowsToRecord(g?.permissions);
288
+ const adminAccess = user.adminAccess === true;
162
289
  return {
163
290
  id: user.id.toString(),
164
291
  email: user.email,
165
292
  name: user.name,
166
293
  groupId: user.groupId ?? void 0,
167
- permissions: ["admin"]
294
+ isRBACAdmin,
295
+ entityPerms,
296
+ adminAccess
168
297
  };
169
298
  } catch {
170
299
  return null;
@@ -188,17 +317,23 @@ function getNextAuthOptions(config) {
188
317
  callbacks: {
189
318
  async jwt({ token, user }) {
190
319
  if (user) {
191
- token.id = user.id;
192
- token.groupId = user.groupId;
193
- token.permissions = user.permissions;
320
+ const u = user;
321
+ token.id = u.id;
322
+ token.groupId = u.groupId;
323
+ token.isRBACAdmin = u.isRBACAdmin;
324
+ token.entityPerms = u.entityPerms;
325
+ token.adminAccess = u.adminAccess;
194
326
  }
195
327
  return token;
196
328
  },
197
329
  async session({ session, token }) {
198
330
  if (session.user) {
199
- session.user.id = token.id;
200
- session.user.groupId = token.groupId;
201
- session.user.permissions = token.permissions;
331
+ const t = token;
332
+ session.user.id = t.id;
333
+ session.user.groupId = t.groupId;
334
+ session.user.isRBACAdmin = t.isRBACAdmin;
335
+ session.user.entityPerms = t.entityPerms;
336
+ session.user.adminAccess = t.adminAccess;
202
337
  }
203
338
  return session;
204
339
  }
@@ -208,14 +343,23 @@ function getNextAuthOptions(config) {
208
343
  }
209
344
  // Annotate the CommonJS export names for ESM import in node:
210
345
  0 && (module.exports = {
346
+ ADMIN_GROUP_NAME,
211
347
  OPEN_ENDPOINTS,
212
348
  PERMISSION_REQUIRED_ENDPOINTS,
349
+ RBAC_ADMIN_ONLY_ENTITIES,
350
+ canManageRoles,
213
351
  createAuthHelpers,
214
352
  createCmsMiddleware,
215
353
  defaultPublicApiMethods,
216
354
  getNextAuthOptions,
355
+ getPermissionableEntityKeys,
217
356
  getRequiredPermission,
357
+ hasEntityPermission,
218
358
  isOpenEndpoint,
219
- isPublicMethod
359
+ isPublicMethod,
360
+ isSuperAdminGroupName,
361
+ permissionRowsToRecord,
362
+ seedAdministratorPermissions,
363
+ sessionHasEntityAccess
220
364
  });
221
365
  //# sourceMappingURL=auth.cjs.map
package/dist/auth.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/index.ts","../src/auth/helpers.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["export {\n createAuthHelpers,\n OPEN_ENDPOINTS,\n PERMISSION_REQUIRED_ENDPOINTS,\n isOpenEndpoint,\n isPublicMethod,\n getRequiredPermission,\n} from './helpers';\nexport type { SessionUser, GetSession, AuthHelpers } from './helpers';\nexport { createCmsMiddleware, defaultPublicApiMethods } from './middleware';\nexport type { CmsMiddlewareConfig } from './middleware';\nexport { getNextAuthOptions } from './nextauth-options';\nexport type { NextAuthOptionsConfig, NextAuthUser } from './nextauth-options';\n","export interface SessionUser {\n id?: string;\n email?: string | null;\n name?: string | null;\n groupId?: number;\n permissions?: string[];\n}\n\nexport type GetSession = () => Promise<{ user?: SessionUser } | null>;\n\nexport const OPEN_ENDPOINTS: Array<Record<string, string[]>> = [\n { '/api/contacts': ['POST'] },\n { '/api/form-submissions': ['POST'] },\n { '/api/blogs': ['GET'] },\n];\n\nexport const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]> = {};\n\nexport function isOpenEndpoint(pathname: string): boolean {\n return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));\n}\n\nexport function getRequiredPermission(pathname: string): string[] | null {\n return null;\n}\n\nexport function isPublicMethod(pathname: string, method: string): boolean {\n for (const endpoint of OPEN_ENDPOINTS) {\n const key = Object.keys(endpoint)[0];\n if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;\n }\n return false;\n}\n\nexport interface AuthHelpers {\n requireAuth(req: Request): Promise<Response | null>;\n requirePermission(req: Request, permission: string): Promise<Response | null>;\n getAuthenticatedUser(): Promise<SessionUser | null>;\n}\n\nexport function createAuthHelpers(getSession: GetSession, NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }): AuthHelpers {\n return {\n async requireAuth() {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n return null;\n },\n async requirePermission() {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n return null;\n },\n async getAuthenticatedUser() {\n const session = await getSession();\n return session?.user ?? null;\n },\n };\n}\n","export interface CmsMiddlewareConfig {\n publicAdminPaths?: string[];\n publicApiPaths?: string[];\n /** path -> allowed methods */\n publicApiMethods?: Record<string, string[]>;\n signInPath?: string;\n getSessionToken?: (request: { cookies: { get: (name: string) => { value?: string } | undefined } }) => string | undefined;\n}\n\n/** Default public API paths (no auth). Sites should extend this with their own routes. */\nexport const defaultPublicApiMethods: Record<string, string[]> = {\n '/api/contacts': ['POST'],\n '/api/form-submissions': ['POST'],\n '/api/blogs': ['GET'],\n '/api/forms': ['GET'],\n '/api/auth': ['GET', 'POST'],\n '/api/health': ['GET'],\n '/api/users/forgot-password': ['POST'],\n '/api/users/set-password': ['POST'],\n '/api/users/invite': ['POST'],\n};\n\nfunction defaultGetSessionToken(request: { cookies: { get: (name: string) => { value?: string } | undefined } }): string | undefined {\n return (\n request.cookies.get('__Secure-next-auth.session-token')?.value ??\n request.cookies.get('next-auth.session-token')?.value\n );\n}\n\nfunction isPublicMethod(pathname: string, method: string, publicApiMethods: Record<string, string[]>): boolean {\n for (const [endpoint, methods] of Object.entries(publicApiMethods)) {\n if (pathname.startsWith(endpoint) && methods.includes(method)) return true;\n }\n return false;\n}\n\n/**\n * Returns middleware logic. Use from Next.js middleware:\n * import { createCmsMiddleware } from '@infuro/cms-core';\n * export const middleware = createCmsMiddleware({ ... });\n * export const config = { matcher: ['/admin/:path*', '/api/:path*'] };\n */\nexport function createCmsMiddleware(config: CmsMiddlewareConfig = {}) {\n const {\n publicAdminPaths = ['/admin/signin', '/admin/forgot-password', '/admin/reset-password', '/admin/invite'],\n publicApiMethods = defaultPublicApiMethods,\n signInPath = '/admin/signin',\n getSessionToken = defaultGetSessionToken,\n } = config;\n\n return function cmsMiddleware(request: {\n nextUrl: { pathname: string };\n url: string;\n method: string;\n cookies: { get: (name: string) => { value?: string } | undefined };\n }): { type: 'next' } | { type: 'redirect'; url: string } | { type: 'json'; status: number; body: unknown } {\n const pathname = request.nextUrl.pathname;\n const method = request.method;\n\n if (publicAdminPaths.some((p) => pathname === p || pathname.startsWith(p + '/'))) {\n return { type: 'next' };\n }\n\n if (pathname.startsWith('/admin')) {\n const token = getSessionToken(request);\n if (!token) {\n return { type: 'redirect', url: new URL(signInPath, request.url).toString() };\n }\n }\n\n if (pathname.startsWith('/api')) {\n if (isPublicMethod(pathname, method, publicApiMethods)) {\n return { type: 'next' };\n }\n const token = getSessionToken(request);\n if (!token) {\n return { type: 'json', status: 401, body: { error: 'Unauthorized' } };\n }\n }\n\n return { type: 'next' };\n };\n}\n","/**\n * Build NextAuth options for credentials auth. App can extend/override via extend().\n */\nimport type { NextAuthOptions } from 'next-auth';\nimport _CredentialsProvider from 'next-auth/providers/credentials';\nconst CredentialsProvider = (_CredentialsProvider as unknown as { default: typeof _CredentialsProvider }).default ?? _CredentialsProvider;\n\nexport interface NextAuthUser {\n id: number;\n email: string;\n name: string | null;\n password: string | null;\n blocked?: boolean;\n deleted?: boolean;\n groupId?: number | null;\n group?: { permissions?: unknown[] };\n}\n\nexport interface NextAuthOptionsConfig {\n /** Resolve user by email (e.g. from TypeORM). Return null if not found. */\n getUserByEmail: (email: string) => Promise<NextAuthUser | null>;\n comparePassword: (plain: string, hash: string) => Promise<boolean>;\n signInPage?: string;\n secret?: string;\n extend?: (options: NextAuthOptions) => NextAuthOptions;\n}\n\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\n const { getUserByEmail, comparePassword, signInPage = '/admin/signin', secret, extend } = config;\n\n const options: NextAuthOptions = {\n secret: secret ?? process.env.NEXTAUTH_SECRET,\n providers: [\n CredentialsProvider({\n name: 'credentials',\n credentials: {\n email: { label: 'Email', type: 'email' },\n password: { label: 'Password', type: 'password' },\n },\n async authorize(credentials) {\n if (!credentials?.email || !credentials?.password) return null;\n try {\n const user = await getUserByEmail(credentials.email);\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted || !user.password) return null;\n const valid = await comparePassword(credentials.password, user.password);\n if (!valid) return null;\n return {\n id: user.id.toString(),\n email: user.email,\n name: user.name,\n groupId: user.groupId ?? undefined,\n permissions: ['admin'],\n };\n } catch {\n return null;\n }\n },\n }),\n ],\n session: { strategy: 'jwt' },\n pages: { signIn: signInPage },\n cookies: {\n sessionToken: {\n name: process.env.NEXTAUTH_URL?.startsWith('https')\n ? '__Secure-next-auth.session-token'\n : 'next-auth.session-token',\n options: {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NEXTAUTH_URL?.startsWith('https') ?? false,\n },\n },\n },\n callbacks: {\n async jwt({ token, user }) {\n if (user) {\n (token as Record<string, unknown>).id = user.id;\n (token as Record<string, unknown>).groupId = (user as { groupId?: number }).groupId;\n (token as Record<string, unknown>).permissions = (user as { permissions?: string[] }).permissions;\n }\n return token;\n },\n async session({ session, token }) {\n if (session.user) {\n (session.user as Record<string, unknown>).id = (token as Record<string, unknown>).id;\n (session.user as Record<string, unknown>).groupId = (token as Record<string, unknown>).groupId;\n (session.user as Record<string, unknown>).permissions = (token as Record<string, unknown>).permissions;\n }\n return session;\n },\n },\n };\n\n return extend ? extend(options) : options;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUO,IAAM,iBAAkD;AAAA,EAC7D,EAAE,iBAAiB,CAAC,MAAM,EAAE;AAAA,EAC5B,EAAE,yBAAyB,CAAC,MAAM,EAAE;AAAA,EACpC,EAAE,cAAc,CAAC,KAAK,EAAE;AAC1B;AAEO,IAAM,gCAA0D,CAAC;AAEjE,SAAS,eAAe,UAA2B;AACxD,SAAO,eAAe,KAAK,CAAC,aAAa,SAAS,WAAW,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC;AACxF;AAEO,SAAS,sBAAsB,UAAmC;AACvE,SAAO;AACT;AAEO,SAAS,eAAe,UAAkB,QAAyB;AACxE,aAAW,YAAY,gBAAgB;AACrC,UAAM,MAAM,OAAO,KAAK,QAAQ,EAAE,CAAC;AACnC,QAAI,SAAS,WAAW,GAAG,KAAK,SAAS,GAAG,EAAE,SAAS,MAAM,EAAG,QAAO;AAAA,EACzE;AACA,SAAO;AACT;AAQO,SAAS,kBAAkB,YAAwB,cAA8F;AACtJ,SAAO;AAAA,IACL,MAAM,cAAc;AAClB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,oBAAoB;AACxB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,uBAAuB;AAC3B,YAAM,UAAU,MAAM,WAAW;AACjC,aAAO,SAAS,QAAQ;AAAA,IAC1B;AAAA,EACF;AACF;;;ACnDO,IAAM,0BAAoD;AAAA,EAC/D,iBAAiB,CAAC,MAAM;AAAA,EACxB,yBAAyB,CAAC,MAAM;AAAA,EAChC,cAAc,CAAC,KAAK;AAAA,EACpB,cAAc,CAAC,KAAK;AAAA,EACpB,aAAa,CAAC,OAAO,MAAM;AAAA,EAC3B,eAAe,CAAC,KAAK;AAAA,EACrB,8BAA8B,CAAC,MAAM;AAAA,EACrC,2BAA2B,CAAC,MAAM;AAAA,EAClC,qBAAqB,CAAC,MAAM;AAC9B;AAEA,SAAS,uBAAuB,SAAqG;AACnI,SACE,QAAQ,QAAQ,IAAI,kCAAkC,GAAG,SACzD,QAAQ,QAAQ,IAAI,yBAAyB,GAAG;AAEpD;AAEA,SAASA,gBAAe,UAAkB,QAAgB,kBAAqD;AAC7G,aAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAClE,QAAI,SAAS,WAAW,QAAQ,KAAK,QAAQ,SAAS,MAAM,EAAG,QAAO;AAAA,EACxE;AACA,SAAO;AACT;AAQO,SAAS,oBAAoB,SAA8B,CAAC,GAAG;AACpE,QAAM;AAAA,IACJ,mBAAmB,CAAC,iBAAiB,0BAA0B,yBAAyB,eAAe;AAAA,IACvG,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,kBAAkB;AAAA,EACpB,IAAI;AAEJ,SAAO,SAAS,cAAc,SAK6E;AACzG,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,SAAS,QAAQ;AAEvB,QAAI,iBAAiB,KAAK,CAAC,MAAM,aAAa,KAAK,SAAS,WAAW,IAAI,GAAG,CAAC,GAAG;AAChF,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AAEA,QAAI,SAAS,WAAW,QAAQ,GAAG;AACjC,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,YAAY,KAAK,IAAI,IAAI,YAAY,QAAQ,GAAG,EAAE,SAAS,EAAE;AAAA,MAC9E;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,MAAM,GAAG;AAC/B,UAAIA,gBAAe,UAAU,QAAQ,gBAAgB,GAAG;AACtD,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB;AACA,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,QAAQ,QAAQ,KAAK,MAAM,EAAE,OAAO,eAAe,EAAE;AAAA,MACtE;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AACF;;;AC9EA,yBAAiC;AACjC,IAAM,sBAAuB,mBAAAC,QAA6E,WAAW,mBAAAA;AAsB9G,SAAS,mBAAmB,QAAgD;AACjF,QAAM,EAAE,gBAAgB,iBAAiB,aAAa,iBAAiB,QAAQ,OAAO,IAAI;AAE1F,QAAM,UAA2B;AAAA,IAC/B,QAAQ,UAAU,QAAQ,IAAI;AAAA,IAC9B,WAAW;AAAA,MACT,oBAAoB;AAAA,QAClB,MAAM;AAAA,QACN,aAAa;AAAA,UACX,OAAO,EAAE,OAAO,SAAS,MAAM,QAAQ;AAAA,UACvC,UAAU,EAAE,OAAO,YAAY,MAAM,WAAW;AAAA,QAClD;AAAA,QACA,MAAM,UAAU,aAAa;AAC3B,cAAI,CAAC,aAAa,SAAS,CAAC,aAAa,SAAU,QAAO;AAC1D,cAAI;AACF,kBAAM,OAAO,MAAM,eAAe,YAAY,KAAK;AACnD,gBAAI,CAAC,QAAQ,KAAK,WAAY,KAA+B,WAAW,CAAC,KAAK,SAAU,QAAO;AAC/F,kBAAM,QAAQ,MAAM,gBAAgB,YAAY,UAAU,KAAK,QAAQ;AACvE,gBAAI,CAAC,MAAO,QAAO;AACnB,mBAAO;AAAA,cACL,IAAI,KAAK,GAAG,SAAS;AAAA,cACrB,OAAO,KAAK;AAAA,cACZ,MAAM,KAAK;AAAA,cACX,SAAS,KAAK,WAAW;AAAA,cACzB,aAAa,CAAC,OAAO;AAAA,YACvB;AAAA,UACF,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,SAAS,EAAE,UAAU,MAAM;AAAA,IAC3B,OAAO,EAAE,QAAQ,WAAW;AAAA,IAC5B,SAAS;AAAA,MACP,cAAc;AAAA,QACZ,MAAM,QAAQ,IAAI,cAAc,WAAW,OAAO,IAC9C,qCACA;AAAA,QACJ,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,QAAQ,IAAI,cAAc,WAAW,OAAO,KAAK;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG;AACzB,YAAI,MAAM;AACR,UAAC,MAAkC,KAAK,KAAK;AAC7C,UAAC,MAAkC,UAAW,KAA8B;AAC5E,UAAC,MAAkC,cAAe,KAAoC;AAAA,QACxF;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAG;AAChC,YAAI,QAAQ,MAAM;AAChB,UAAC,QAAQ,KAAiC,KAAM,MAAkC;AAClF,UAAC,QAAQ,KAAiC,UAAW,MAAkC;AACvF,UAAC,QAAQ,KAAiC,cAAe,MAAkC;AAAA,QAC7F;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS,OAAO,OAAO,IAAI;AACpC;","names":["isPublicMethod","_CredentialsProvider"]}
1
+ {"version":3,"sources":["../src/auth/index.ts","../src/auth/permission-entities.ts","../src/auth/helpers.ts","../src/auth/seed-permissions.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["export {\n createAuthHelpers,\n OPEN_ENDPOINTS,\n PERMISSION_REQUIRED_ENDPOINTS,\n isOpenEndpoint,\n isPublicMethod,\n getRequiredPermission,\n} from './helpers';\nexport type { SessionUser, GetSession, AuthHelpers } from './helpers';\nexport { sessionHasEntityAccess, canManageRoles, RBAC_ADMIN_ONLY_ENTITIES } from './helpers';\nexport {\n getPermissionableEntityKeys,\n ADMIN_GROUP_NAME,\n isSuperAdminGroupName,\n permissionRowsToRecord,\n hasEntityPermission,\n} from './permission-entities';\nexport { seedAdministratorPermissions } from './seed-permissions';\nexport type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\nexport { createCmsMiddleware, defaultPublicApiMethods } from './middleware';\nexport type { CmsMiddlewareConfig } from './middleware';\nexport { getNextAuthOptions } from './nextauth-options';\nexport type { NextAuthOptionsConfig, NextAuthUser } from './nextauth-options';\n","/** API resource keys excluded from the permission matrix (internal / not admin-CRUD). */\nexport const PERMISSION_ENTITY_INTERNAL_EXCLUDE = new Set([\n 'users',\n 'password_reset_tokens',\n 'user_groups',\n 'permissions',\n 'comments',\n 'form_fields',\n 'configs',\n 'knowledge_base_chunks',\n 'carts',\n 'cart_items',\n 'wishlists',\n 'wishlist_items',\n]);\n\n/** Non-CRUD admin surfaces mapped to entity keys for RBAC. */\nexport const PERMISSION_LOGICAL_ENTITIES = [\n 'users',\n 'forms',\n 'form_submissions',\n 'dashboard',\n 'upload',\n 'settings',\n 'analytics',\n 'chat',\n] as const;\n\n/** Canonical name for new installs / migrations */\nexport const ADMIN_GROUP_NAME = 'Administrator';\n\n/** System administrator group (roles UI + users / user_groups / permissions bypass). */\nexport function isSuperAdminGroupName(name: string | null | undefined): boolean {\n return name === ADMIN_GROUP_NAME;\n}\n\nexport type EntityCrudAction = 'create' | 'read' | 'update' | 'delete';\n\nexport type EntityPermissionFlags = { c: boolean; r: boolean; u: boolean; d: boolean };\n\nexport function getPermissionableEntityKeys(entityMap: Record<string, unknown>): string[] {\n const fromMap = Object.keys(entityMap).filter((k) => !PERMISSION_ENTITY_INTERNAL_EXCLUDE.has(k));\n const logical = PERMISSION_LOGICAL_ENTITIES.filter((k) => !fromMap.includes(k));\n return [...fromMap.sort(), ...logical].filter((k, i, a) => a.indexOf(k) === i);\n}\n\nexport function permissionRowsToRecord(\n rows: Array<{ entity: string; canCreate: boolean; canRead: boolean; canUpdate: boolean; canDelete: boolean }> | undefined\n): Record<string, EntityPermissionFlags> {\n const out: Record<string, EntityPermissionFlags> = {};\n if (!rows?.length) return out;\n for (const p of rows) {\n out[p.entity] = {\n c: !!p.canCreate,\n r: !!p.canRead,\n u: !!p.canUpdate,\n d: !!p.canDelete,\n };\n }\n return out;\n}\n\nexport function hasEntityPermission(\n record: Record<string, EntityPermissionFlags> | undefined,\n entity: string,\n action: EntityCrudAction\n): boolean {\n const p = record?.[entity];\n if (!p) return false;\n if (action === 'create') return p.c;\n if (action === 'read') return p.r;\n if (action === 'update') return p.u;\n return p.d;\n}\n","import type { EntityCrudAction, EntityPermissionFlags } from './permission-entities';\nimport { hasEntityPermission } from './permission-entities';\n\n/** isRBACAdmin bypasses entity checks only for these (users / roles plumbing). */\nexport const RBAC_ADMIN_ONLY_ENTITIES = new Set(['users', 'user_groups', 'permissions']);\n\nexport interface SessionUser {\n id?: string;\n email?: string | null;\n name?: string | null;\n groupId?: number;\n /** @deprecated use entityPerms / isRBACAdmin */\n permissions?: string[];\n /** Administrator group: full access only for users, user_groups, permissions */\n isRBACAdmin?: boolean;\n entityPerms?: Record<string, EntityPermissionFlags>;\n /** When false and not isRBACAdmin, admin API/UI is denied. */\n adminAccess?: boolean;\n}\n\nexport function sessionHasEntityAccess(\n user: SessionUser | null | undefined,\n entity: string,\n action: EntityCrudAction\n): boolean {\n if (!user?.email) return false;\n if (user.isRBACAdmin && RBAC_ADMIN_ONLY_ENTITIES.has(entity)) return true;\n return hasEntityPermission(user.entityPerms, entity, action);\n}\n\nexport function canManageRoles(user: SessionUser | null | undefined): boolean {\n return !!(user?.email && user.isRBACAdmin);\n}\n\nexport type GetSession = () => Promise<{ user?: SessionUser } | null>;\n\nexport const OPEN_ENDPOINTS: Array<Record<string, string[]>> = [\n { '/api/contacts': ['POST'] },\n { '/api/form-submissions': ['POST'] },\n { '/api/blogs': ['GET'] },\n];\n\nexport const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]> = {};\n\nexport function isOpenEndpoint(pathname: string): boolean {\n return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));\n}\n\nexport function getRequiredPermission(pathname: string): string[] | null {\n return null;\n}\n\nexport function isPublicMethod(pathname: string, method: string): boolean {\n for (const endpoint of OPEN_ENDPOINTS) {\n const key = Object.keys(endpoint)[0];\n if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;\n }\n return false;\n}\n\nexport interface AuthHelpers {\n requireAuth(req: Request): Promise<Response | null>;\n requirePermission(req: Request, permission: string): Promise<Response | null>;\n requireEntityPermission(req: Request, entity: string, action: EntityCrudAction): Promise<Response | null>;\n requireAdminAccess(req: Request): Promise<Response | null>;\n getAuthenticatedUser(): Promise<SessionUser | null>;\n}\n\nexport function createAuthHelpers(getSession: GetSession, NextResponse: { json: (body: unknown, init?: { status?: number }) => Response }): AuthHelpers {\n return {\n async requireAuth() {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n return null;\n },\n async requirePermission() {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n return null;\n },\n async requireEntityPermission(_req: Request, entity: string, action: EntityCrudAction) {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n const u = session.user as SessionUser;\n if (sessionHasEntityAccess(u, entity, action)) return null;\n return NextResponse.json({ error: 'Forbidden', entity, action }, { status: 403 });\n },\n async requireAdminAccess() {\n const session = await getSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n const u = session.user as SessionUser;\n if (u.isRBACAdmin) return null;\n if (u.adminAccess === false) return NextResponse.json({ error: 'Forbidden', reason: 'admin_access' }, { status: 403 });\n return null;\n },\n async getAuthenticatedUser() {\n const session = await getSession();\n return (session?.user as SessionUser) ?? null;\n },\n };\n}\n","import type { DataSource } from 'typeorm';\nimport type { EntityTarget } from 'typeorm';\nimport type { ObjectLiteral } from 'typeorm';\nimport { getPermissionableEntityKeys, ADMIN_GROUP_NAME } from './permission-entities';\n\n/**\n * Ensures the Administrator group has full CRUD on every permissionable entity\n * for the given entity map. Idempotent: upserts per entity.\n */\nexport async function seedAdministratorPermissions(\n dataSource: DataSource,\n entityMap: Record<string, unknown>\n): Promise<void> {\n const entities = getPermissionableEntityKeys(entityMap);\n const groupRepo = dataSource.getRepository(entityMap.user_groups as EntityTarget<ObjectLiteral>);\n const permRepo = dataSource.getRepository(entityMap.permissions as EntityTarget<ObjectLiteral>);\n\n const adminGroup = await groupRepo.findOne({ where: { name: ADMIN_GROUP_NAME, deleted: false } });\n if (!adminGroup) return;\n\n const fullCrud = { canCreate: true, canRead: true, canUpdate: true, canDelete: true };\n\n for (const entity of entities) {\n const existing = await permRepo.findOne({\n where: { groupId: adminGroup.id, entity },\n });\n if (existing) {\n existing.canCreate = true;\n existing.canRead = true;\n existing.canUpdate = true;\n existing.canDelete = true;\n await permRepo.save(existing);\n } else {\n await permRepo.save(\n permRepo.create({\n groupId: adminGroup.id,\n entity,\n ...fullCrud,\n })\n );\n }\n }\n}\n","export interface CmsMiddlewareConfig {\n publicAdminPaths?: string[];\n publicApiPaths?: string[];\n /** path -> allowed methods */\n publicApiMethods?: Record<string, string[]>;\n signInPath?: string;\n getSessionToken?: (request: { cookies: { get: (name: string) => { value?: string } | undefined } }) => string | undefined;\n}\n\n/** Default public API paths (no auth). Sites should extend this with their own routes. */\nexport const defaultPublicApiMethods: Record<string, string[]> = {\n '/api/contacts': ['POST'],\n '/api/form-submissions': ['POST'],\n '/api/blogs': ['GET'],\n '/api/forms': ['GET'],\n '/api/auth': ['GET', 'POST'],\n '/api/health': ['GET'],\n '/api/users/forgot-password': ['POST'],\n '/api/users/set-password': ['POST'],\n '/api/users/invite': ['POST'],\n};\n\nfunction defaultGetSessionToken(request: { cookies: { get: (name: string) => { value?: string } | undefined } }): string | undefined {\n return (\n request.cookies.get('__Secure-next-auth.session-token')?.value ??\n request.cookies.get('next-auth.session-token')?.value\n );\n}\n\nfunction isPublicMethod(pathname: string, method: string, publicApiMethods: Record<string, string[]>): boolean {\n for (const [endpoint, methods] of Object.entries(publicApiMethods)) {\n if (pathname.startsWith(endpoint) && methods.includes(method)) return true;\n }\n return false;\n}\n\n/**\n * Returns middleware logic. Use from Next.js middleware:\n * import { createCmsMiddleware } from '@infuro/cms-core';\n * export const middleware = createCmsMiddleware({ ... });\n * export const config = { matcher: ['/admin/:path*', '/api/:path*'] };\n */\nexport function createCmsMiddleware(config: CmsMiddlewareConfig = {}) {\n const {\n publicAdminPaths = ['/admin/signin', '/admin/forgot-password', '/admin/reset-password', '/admin/invite'],\n publicApiMethods = defaultPublicApiMethods,\n signInPath = '/admin/signin',\n getSessionToken = defaultGetSessionToken,\n } = config;\n\n return function cmsMiddleware(request: {\n nextUrl: { pathname: string };\n url: string;\n method: string;\n cookies: { get: (name: string) => { value?: string } | undefined };\n }): { type: 'next' } | { type: 'redirect'; url: string } | { type: 'json'; status: number; body: unknown } {\n const pathname = request.nextUrl.pathname;\n const method = request.method;\n\n if (publicAdminPaths.some((p) => pathname === p || pathname.startsWith(p + '/'))) {\n return { type: 'next' };\n }\n\n if (pathname.startsWith('/admin')) {\n const token = getSessionToken(request);\n if (!token) {\n return { type: 'redirect', url: new URL(signInPath, request.url).toString() };\n }\n }\n\n if (pathname.startsWith('/api')) {\n if (isPublicMethod(pathname, method, publicApiMethods)) {\n return { type: 'next' };\n }\n const token = getSessionToken(request);\n if (!token) {\n return { type: 'json', status: 401, body: { error: 'Unauthorized' } };\n }\n }\n\n return { type: 'next' };\n };\n}\n","/**\n * Build NextAuth options for credentials auth. App can extend/override via extend().\n */\nimport type { NextAuthOptions } from 'next-auth';\nimport _CredentialsProvider from 'next-auth/providers/credentials';\nconst CredentialsProvider = (_CredentialsProvider as unknown as { default: typeof _CredentialsProvider }).default ?? _CredentialsProvider;\nimport { isSuperAdminGroupName, permissionRowsToRecord } from './permission-entities';\n\nexport interface NextAuthUser {\n id: number;\n email: string;\n name: string | null;\n password: string | null;\n blocked?: boolean;\n deleted?: boolean;\n groupId?: number | null;\n adminAccess?: boolean;\n group?: {\n name?: string;\n permissions?: Array<{\n entity: string;\n canCreate: boolean;\n canRead: boolean;\n canUpdate: boolean;\n canDelete: boolean;\n }>;\n };\n}\n\nexport interface NextAuthOptionsConfig {\n /** Resolve user by email (e.g. from TypeORM). Return null if not found. */\n getUserByEmail: (email: string) => Promise<NextAuthUser | null>;\n comparePassword: (plain: string, hash: string) => Promise<boolean>;\n signInPage?: string;\n secret?: string;\n extend?: (options: NextAuthOptions) => NextAuthOptions;\n}\n\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\n const { getUserByEmail, comparePassword, signInPage = '/admin/signin', secret, extend } = config;\n\n const options: NextAuthOptions = {\n secret: secret ?? process.env.NEXTAUTH_SECRET,\n providers: [\n CredentialsProvider({\n name: 'credentials',\n credentials: {\n email: { label: 'Email', type: 'email' },\n password: { label: 'Password', type: 'password' },\n },\n async authorize(credentials) {\n if (!credentials?.email || !credentials?.password) return null;\n try {\n const user = await getUserByEmail(credentials.email);\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted || !user.password) return null;\n const valid = await comparePassword(credentials.password, user.password);\n if (!valid) return null;\n const g = user.group;\n const isRBACAdmin = isSuperAdminGroupName(g?.name);\n const entityPerms = permissionRowsToRecord(g?.permissions);\n const adminAccess = (user as NextAuthUser & { adminAccess?: boolean }).adminAccess === true;\n return {\n id: user.id.toString(),\n email: user.email,\n name: user.name,\n groupId: user.groupId ?? undefined,\n isRBACAdmin,\n entityPerms,\n adminAccess,\n };\n } catch {\n return null;\n }\n },\n }),\n ],\n session: { strategy: 'jwt' },\n pages: { signIn: signInPage },\n cookies: {\n sessionToken: {\n name: process.env.NEXTAUTH_URL?.startsWith('https')\n ? '__Secure-next-auth.session-token'\n : 'next-auth.session-token',\n options: {\n httpOnly: true,\n sameSite: 'lax',\n path: '/',\n secure: process.env.NEXTAUTH_URL?.startsWith('https') ?? false,\n },\n },\n },\n callbacks: {\n async jwt({ token, user }) {\n if (user) {\n const u = user as unknown as Record<string, unknown>;\n (token as Record<string, unknown>).id = u.id;\n (token as Record<string, unknown>).groupId = u.groupId;\n (token as Record<string, unknown>).isRBACAdmin = u.isRBACAdmin;\n (token as Record<string, unknown>).entityPerms = u.entityPerms;\n (token as Record<string, unknown>).adminAccess = u.adminAccess;\n }\n return token;\n },\n async session({ session, token }) {\n if (session.user) {\n const t = token as Record<string, unknown>;\n (session.user as Record<string, unknown>).id = t.id;\n (session.user as Record<string, unknown>).groupId = t.groupId;\n (session.user as Record<string, unknown>).isRBACAdmin = t.isRBACAdmin;\n (session.user as Record<string, unknown>).entityPerms = t.entityPerms;\n (session.user as Record<string, unknown>).adminAccess = t.adminAccess;\n }\n return session;\n },\n },\n };\n\n return extend ? extend(options) : options;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,qCAAqC,oBAAI,IAAI;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,mBAAmB;AAGzB,SAAS,sBAAsB,MAA0C;AAC9E,SAAO,SAAS;AAClB;AAMO,SAAS,4BAA4B,WAA8C;AACxF,QAAM,UAAU,OAAO,KAAK,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,mCAAmC,IAAI,CAAC,CAAC;AAC/F,QAAM,UAAU,4BAA4B,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;AAC9E,SAAO,CAAC,GAAG,QAAQ,KAAK,GAAG,GAAG,OAAO,EAAE,OAAO,CAAC,GAAG,GAAG,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;AAC/E;AAEO,SAAS,uBACd,MACuC;AACvC,QAAM,MAA6C,CAAC;AACpD,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,MAAM,IAAI;AAAA,MACd,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,MACP,GAAG,CAAC,CAAC,EAAE;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,oBACd,QACA,QACA,QACS;AACT,QAAM,IAAI,SAAS,MAAM;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,WAAW,SAAU,QAAO,EAAE;AAClC,MAAI,WAAW,OAAQ,QAAO,EAAE;AAChC,MAAI,WAAW,SAAU,QAAO,EAAE;AAClC,SAAO,EAAE;AACX;;;ACrEO,IAAM,2BAA2B,oBAAI,IAAI,CAAC,SAAS,eAAe,aAAa,CAAC;AAgBhF,SAAS,uBACd,MACA,QACA,QACS;AACT,MAAI,CAAC,MAAM,MAAO,QAAO;AACzB,MAAI,KAAK,eAAe,yBAAyB,IAAI,MAAM,EAAG,QAAO;AACrE,SAAO,oBAAoB,KAAK,aAAa,QAAQ,MAAM;AAC7D;AAEO,SAAS,eAAe,MAA+C;AAC5E,SAAO,CAAC,EAAE,MAAM,SAAS,KAAK;AAChC;AAIO,IAAM,iBAAkD;AAAA,EAC7D,EAAE,iBAAiB,CAAC,MAAM,EAAE;AAAA,EAC5B,EAAE,yBAAyB,CAAC,MAAM,EAAE;AAAA,EACpC,EAAE,cAAc,CAAC,KAAK,EAAE;AAC1B;AAEO,IAAM,gCAA0D,CAAC;AAEjE,SAAS,eAAe,UAA2B;AACxD,SAAO,eAAe,KAAK,CAAC,aAAa,SAAS,WAAW,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC;AACxF;AAEO,SAAS,sBAAsB,UAAmC;AACvE,SAAO;AACT;AAEO,SAAS,eAAe,UAAkB,QAAyB;AACxE,aAAW,YAAY,gBAAgB;AACrC,UAAM,MAAM,OAAO,KAAK,QAAQ,EAAE,CAAC;AACnC,QAAI,SAAS,WAAW,GAAG,KAAK,SAAS,GAAG,EAAE,SAAS,MAAM,EAAG,QAAO;AAAA,EACzE;AACA,SAAO;AACT;AAUO,SAAS,kBAAkB,YAAwB,cAA8F;AACtJ,SAAO;AAAA,IACL,MAAM,cAAc;AAClB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,oBAAoB;AACxB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,wBAAwB,MAAe,QAAgB,QAA0B;AACrF,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,YAAM,IAAI,QAAQ;AAClB,UAAI,uBAAuB,GAAG,QAAQ,MAAM,EAAG,QAAO;AACtD,aAAO,aAAa,KAAK,EAAE,OAAO,aAAa,QAAQ,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAClF;AAAA,IACA,MAAM,qBAAqB;AACzB,YAAM,UAAU,MAAM,WAAW;AACjC,UAAI,CAAC,SAAS,MAAM,OAAO;AACzB,eAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrE;AACA,YAAM,IAAI,QAAQ;AAClB,UAAI,EAAE,YAAa,QAAO;AAC1B,UAAI,EAAE,gBAAgB,MAAO,QAAO,aAAa,KAAK,EAAE,OAAO,aAAa,QAAQ,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AACrH,aAAO;AAAA,IACT;AAAA,IACA,MAAM,uBAAuB;AAC3B,YAAM,UAAU,MAAM,WAAW;AACjC,aAAQ,SAAS,QAAwB;AAAA,IAC3C;AAAA,EACF;AACF;;;ACnGA,eAAsB,6BACpB,YACA,WACe;AACf,QAAM,WAAW,4BAA4B,SAAS;AACtD,QAAM,YAAY,WAAW,cAAc,UAAU,WAA0C;AAC/F,QAAM,WAAW,WAAW,cAAc,UAAU,WAA0C;AAE9F,QAAM,aAAa,MAAM,UAAU,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,MAAM,EAAE,CAAC;AAChG,MAAI,CAAC,WAAY;AAEjB,QAAM,WAAW,EAAE,WAAW,MAAM,SAAS,MAAM,WAAW,MAAM,WAAW,KAAK;AAEpF,aAAW,UAAU,UAAU;AAC7B,UAAM,WAAW,MAAM,SAAS,QAAQ;AAAA,MACtC,OAAO,EAAE,SAAS,WAAW,IAAI,OAAO;AAAA,IAC1C,CAAC;AACD,QAAI,UAAU;AACZ,eAAS,YAAY;AACrB,eAAS,UAAU;AACnB,eAAS,YAAY;AACrB,eAAS,YAAY;AACrB,YAAM,SAAS,KAAK,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,SAAS;AAAA,QACb,SAAS,OAAO;AAAA,UACd,SAAS,WAAW;AAAA,UACpB;AAAA,UACA,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;;;AChCO,IAAM,0BAAoD;AAAA,EAC/D,iBAAiB,CAAC,MAAM;AAAA,EACxB,yBAAyB,CAAC,MAAM;AAAA,EAChC,cAAc,CAAC,KAAK;AAAA,EACpB,cAAc,CAAC,KAAK;AAAA,EACpB,aAAa,CAAC,OAAO,MAAM;AAAA,EAC3B,eAAe,CAAC,KAAK;AAAA,EACrB,8BAA8B,CAAC,MAAM;AAAA,EACrC,2BAA2B,CAAC,MAAM;AAAA,EAClC,qBAAqB,CAAC,MAAM;AAC9B;AAEA,SAAS,uBAAuB,SAAqG;AACnI,SACE,QAAQ,QAAQ,IAAI,kCAAkC,GAAG,SACzD,QAAQ,QAAQ,IAAI,yBAAyB,GAAG;AAEpD;AAEA,SAASA,gBAAe,UAAkB,QAAgB,kBAAqD;AAC7G,aAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAClE,QAAI,SAAS,WAAW,QAAQ,KAAK,QAAQ,SAAS,MAAM,EAAG,QAAO;AAAA,EACxE;AACA,SAAO;AACT;AAQO,SAAS,oBAAoB,SAA8B,CAAC,GAAG;AACpE,QAAM;AAAA,IACJ,mBAAmB,CAAC,iBAAiB,0BAA0B,yBAAyB,eAAe;AAAA,IACvG,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,kBAAkB;AAAA,EACpB,IAAI;AAEJ,SAAO,SAAS,cAAc,SAK6E;AACzG,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,SAAS,QAAQ;AAEvB,QAAI,iBAAiB,KAAK,CAAC,MAAM,aAAa,KAAK,SAAS,WAAW,IAAI,GAAG,CAAC,GAAG;AAChF,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AAEA,QAAI,SAAS,WAAW,QAAQ,GAAG;AACjC,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,YAAY,KAAK,IAAI,IAAI,YAAY,QAAQ,GAAG,EAAE,SAAS,EAAE;AAAA,MAC9E;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,MAAM,GAAG;AAC/B,UAAIA,gBAAe,UAAU,QAAQ,gBAAgB,GAAG;AACtD,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB;AACA,YAAM,QAAQ,gBAAgB,OAAO;AACrC,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,MAAM,QAAQ,QAAQ,KAAK,MAAM,EAAE,OAAO,eAAe,EAAE;AAAA,MACtE;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AACF;;;AC9EA,yBAAiC;AACjC,IAAM,sBAAuB,mBAAAC,QAA6E,WAAW,mBAAAA;AAiC9G,SAAS,mBAAmB,QAAgD;AACjF,QAAM,EAAE,gBAAgB,iBAAiB,aAAa,iBAAiB,QAAQ,OAAO,IAAI;AAE1F,QAAM,UAA2B;AAAA,IAC/B,QAAQ,UAAU,QAAQ,IAAI;AAAA,IAC9B,WAAW;AAAA,MACT,oBAAoB;AAAA,QAClB,MAAM;AAAA,QACN,aAAa;AAAA,UACX,OAAO,EAAE,OAAO,SAAS,MAAM,QAAQ;AAAA,UACvC,UAAU,EAAE,OAAO,YAAY,MAAM,WAAW;AAAA,QAClD;AAAA,QACA,MAAM,UAAU,aAAa;AAC3B,cAAI,CAAC,aAAa,SAAS,CAAC,aAAa,SAAU,QAAO;AAC1D,cAAI;AACF,kBAAM,OAAO,MAAM,eAAe,YAAY,KAAK;AACnD,gBAAI,CAAC,QAAQ,KAAK,WAAY,KAA+B,WAAW,CAAC,KAAK,SAAU,QAAO;AAC/F,kBAAM,QAAQ,MAAM,gBAAgB,YAAY,UAAU,KAAK,QAAQ;AACvE,gBAAI,CAAC,MAAO,QAAO;AACnB,kBAAM,IAAI,KAAK;AACf,kBAAM,cAAc,sBAAsB,GAAG,IAAI;AACjD,kBAAM,cAAc,uBAAuB,GAAG,WAAW;AACzD,kBAAM,cAAe,KAAkD,gBAAgB;AACvF,mBAAO;AAAA,cACL,IAAI,KAAK,GAAG,SAAS;AAAA,cACrB,OAAO,KAAK;AAAA,cACZ,MAAM,KAAK;AAAA,cACX,SAAS,KAAK,WAAW;AAAA,cACzB;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,UACF,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,SAAS,EAAE,UAAU,MAAM;AAAA,IAC3B,OAAO,EAAE,QAAQ,WAAW;AAAA,IAC5B,SAAS;AAAA,MACP,cAAc;AAAA,QACZ,MAAM,QAAQ,IAAI,cAAc,WAAW,OAAO,IAC9C,qCACA;AAAA,QACJ,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,QAAQ,IAAI,cAAc,WAAW,OAAO,KAAK;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG;AACzB,YAAI,MAAM;AACR,gBAAM,IAAI;AACV,UAAC,MAAkC,KAAK,EAAE;AAC1C,UAAC,MAAkC,UAAU,EAAE;AAC/C,UAAC,MAAkC,cAAc,EAAE;AACnD,UAAC,MAAkC,cAAc,EAAE;AACnD,UAAC,MAAkC,cAAc,EAAE;AAAA,QACrD;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAG;AAChC,YAAI,QAAQ,MAAM;AAChB,gBAAM,IAAI;AACV,UAAC,QAAQ,KAAiC,KAAK,EAAE;AACjD,UAAC,QAAQ,KAAiC,UAAU,EAAE;AACtD,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAC1D,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAC1D,UAAC,QAAQ,KAAiC,cAAc,EAAE;AAAA,QAC5D;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS,OAAO,OAAO,IAAI;AACpC;","names":["isPublicMethod","_CredentialsProvider"]}
package/dist/auth.d.cts CHANGED
@@ -1,30 +1,12 @@
1
+ export { A as ADMIN_GROUP_NAME, a as AuthHelpers, E as EntityCrudAction, b as EntityPermissionFlags, G as GetSession, O as OPEN_ENDPOINTS, P as PERMISSION_REQUIRED_ENDPOINTS, R as RBAC_ADMIN_ONLY_ENTITIES, S as SessionUser, c as canManageRoles, d as createAuthHelpers, g as getPermissionableEntityKeys, e as getRequiredPermission, h as hasEntityPermission, i as isOpenEndpoint, f as isPublicMethod, j as isSuperAdminGroupName, p as permissionRowsToRecord, s as sessionHasEntityAccess } from './helpers-dlrF_49e.cjs';
2
+ import { DataSource } from 'typeorm';
1
3
  import { NextAuthOptions } from 'next-auth';
2
4
 
3
- interface SessionUser {
4
- id?: string;
5
- email?: string | null;
6
- name?: string | null;
7
- groupId?: number;
8
- permissions?: string[];
9
- }
10
- type GetSession = () => Promise<{
11
- user?: SessionUser;
12
- } | null>;
13
- declare const OPEN_ENDPOINTS: Array<Record<string, string[]>>;
14
- declare const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]>;
15
- declare function isOpenEndpoint(pathname: string): boolean;
16
- declare function getRequiredPermission(pathname: string): string[] | null;
17
- declare function isPublicMethod(pathname: string, method: string): boolean;
18
- interface AuthHelpers {
19
- requireAuth(req: Request): Promise<Response | null>;
20
- requirePermission(req: Request, permission: string): Promise<Response | null>;
21
- getAuthenticatedUser(): Promise<SessionUser | null>;
22
- }
23
- declare function createAuthHelpers(getSession: GetSession, NextResponse: {
24
- json: (body: unknown, init?: {
25
- status?: number;
26
- }) => Response;
27
- }): AuthHelpers;
5
+ /**
6
+ * Ensures the Administrator group has full CRUD on every permissionable entity
7
+ * for the given entity map. Idempotent: upserts per entity.
8
+ */
9
+ declare function seedAdministratorPermissions(dataSource: DataSource, entityMap: Record<string, unknown>): Promise<void>;
28
10
 
29
11
  interface CmsMiddlewareConfig {
30
12
  publicAdminPaths?: string[];
@@ -82,8 +64,16 @@ interface NextAuthUser {
82
64
  blocked?: boolean;
83
65
  deleted?: boolean;
84
66
  groupId?: number | null;
67
+ adminAccess?: boolean;
85
68
  group?: {
86
- permissions?: unknown[];
69
+ name?: string;
70
+ permissions?: Array<{
71
+ entity: string;
72
+ canCreate: boolean;
73
+ canRead: boolean;
74
+ canUpdate: boolean;
75
+ canDelete: boolean;
76
+ }>;
87
77
  };
88
78
  }
89
79
  interface NextAuthOptionsConfig {
@@ -96,4 +86,4 @@ interface NextAuthOptionsConfig {
96
86
  }
97
87
  declare function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions;
98
88
 
99
- export { type AuthHelpers, type CmsMiddlewareConfig, type GetSession, type NextAuthOptionsConfig, type NextAuthUser, OPEN_ENDPOINTS, PERMISSION_REQUIRED_ENDPOINTS, type SessionUser, createAuthHelpers, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, getRequiredPermission, isOpenEndpoint, isPublicMethod };
89
+ export { type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
package/dist/auth.d.ts CHANGED
@@ -1,30 +1,12 @@
1
+ export { A as ADMIN_GROUP_NAME, a as AuthHelpers, E as EntityCrudAction, b as EntityPermissionFlags, G as GetSession, O as OPEN_ENDPOINTS, P as PERMISSION_REQUIRED_ENDPOINTS, R as RBAC_ADMIN_ONLY_ENTITIES, S as SessionUser, c as canManageRoles, d as createAuthHelpers, g as getPermissionableEntityKeys, e as getRequiredPermission, h as hasEntityPermission, i as isOpenEndpoint, f as isPublicMethod, j as isSuperAdminGroupName, p as permissionRowsToRecord, s as sessionHasEntityAccess } from './helpers-dlrF_49e.js';
2
+ import { DataSource } from 'typeorm';
1
3
  import { NextAuthOptions } from 'next-auth';
2
4
 
3
- interface SessionUser {
4
- id?: string;
5
- email?: string | null;
6
- name?: string | null;
7
- groupId?: number;
8
- permissions?: string[];
9
- }
10
- type GetSession = () => Promise<{
11
- user?: SessionUser;
12
- } | null>;
13
- declare const OPEN_ENDPOINTS: Array<Record<string, string[]>>;
14
- declare const PERMISSION_REQUIRED_ENDPOINTS: Record<string, string[]>;
15
- declare function isOpenEndpoint(pathname: string): boolean;
16
- declare function getRequiredPermission(pathname: string): string[] | null;
17
- declare function isPublicMethod(pathname: string, method: string): boolean;
18
- interface AuthHelpers {
19
- requireAuth(req: Request): Promise<Response | null>;
20
- requirePermission(req: Request, permission: string): Promise<Response | null>;
21
- getAuthenticatedUser(): Promise<SessionUser | null>;
22
- }
23
- declare function createAuthHelpers(getSession: GetSession, NextResponse: {
24
- json: (body: unknown, init?: {
25
- status?: number;
26
- }) => Response;
27
- }): AuthHelpers;
5
+ /**
6
+ * Ensures the Administrator group has full CRUD on every permissionable entity
7
+ * for the given entity map. Idempotent: upserts per entity.
8
+ */
9
+ declare function seedAdministratorPermissions(dataSource: DataSource, entityMap: Record<string, unknown>): Promise<void>;
28
10
 
29
11
  interface CmsMiddlewareConfig {
30
12
  publicAdminPaths?: string[];
@@ -82,8 +64,16 @@ interface NextAuthUser {
82
64
  blocked?: boolean;
83
65
  deleted?: boolean;
84
66
  groupId?: number | null;
67
+ adminAccess?: boolean;
85
68
  group?: {
86
- permissions?: unknown[];
69
+ name?: string;
70
+ permissions?: Array<{
71
+ entity: string;
72
+ canCreate: boolean;
73
+ canRead: boolean;
74
+ canUpdate: boolean;
75
+ canDelete: boolean;
76
+ }>;
87
77
  };
88
78
  }
89
79
  interface NextAuthOptionsConfig {
@@ -96,4 +86,4 @@ interface NextAuthOptionsConfig {
96
86
  }
97
87
  declare function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions;
98
88
 
99
- export { type AuthHelpers, type CmsMiddlewareConfig, type GetSession, type NextAuthOptionsConfig, type NextAuthUser, OPEN_ENDPOINTS, PERMISSION_REQUIRED_ENDPOINTS, type SessionUser, createAuthHelpers, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, getRequiredPermission, isOpenEndpoint, isPublicMethod };
89
+ export { type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
package/dist/auth.js CHANGED
@@ -1,4 +1,69 @@
1
+ // src/auth/permission-entities.ts
2
+ var PERMISSION_ENTITY_INTERNAL_EXCLUDE = /* @__PURE__ */ new Set([
3
+ "users",
4
+ "password_reset_tokens",
5
+ "user_groups",
6
+ "permissions",
7
+ "comments",
8
+ "form_fields",
9
+ "configs",
10
+ "knowledge_base_chunks",
11
+ "carts",
12
+ "cart_items",
13
+ "wishlists",
14
+ "wishlist_items"
15
+ ]);
16
+ var PERMISSION_LOGICAL_ENTITIES = [
17
+ "users",
18
+ "forms",
19
+ "form_submissions",
20
+ "dashboard",
21
+ "upload",
22
+ "settings",
23
+ "analytics",
24
+ "chat"
25
+ ];
26
+ var ADMIN_GROUP_NAME = "Administrator";
27
+ function isSuperAdminGroupName(name) {
28
+ return name === ADMIN_GROUP_NAME;
29
+ }
30
+ function getPermissionableEntityKeys(entityMap) {
31
+ const fromMap = Object.keys(entityMap).filter((k) => !PERMISSION_ENTITY_INTERNAL_EXCLUDE.has(k));
32
+ const logical = PERMISSION_LOGICAL_ENTITIES.filter((k) => !fromMap.includes(k));
33
+ return [...fromMap.sort(), ...logical].filter((k, i, a) => a.indexOf(k) === i);
34
+ }
35
+ function permissionRowsToRecord(rows) {
36
+ const out = {};
37
+ if (!rows?.length) return out;
38
+ for (const p of rows) {
39
+ out[p.entity] = {
40
+ c: !!p.canCreate,
41
+ r: !!p.canRead,
42
+ u: !!p.canUpdate,
43
+ d: !!p.canDelete
44
+ };
45
+ }
46
+ return out;
47
+ }
48
+ function hasEntityPermission(record, entity, action) {
49
+ const p = record?.[entity];
50
+ if (!p) return false;
51
+ if (action === "create") return p.c;
52
+ if (action === "read") return p.r;
53
+ if (action === "update") return p.u;
54
+ return p.d;
55
+ }
56
+
1
57
  // src/auth/helpers.ts
58
+ var RBAC_ADMIN_ONLY_ENTITIES = /* @__PURE__ */ new Set(["users", "user_groups", "permissions"]);
59
+ function sessionHasEntityAccess(user, entity, action) {
60
+ if (!user?.email) return false;
61
+ if (user.isRBACAdmin && RBAC_ADMIN_ONLY_ENTITIES.has(entity)) return true;
62
+ return hasEntityPermission(user.entityPerms, entity, action);
63
+ }
64
+ function canManageRoles(user) {
65
+ return !!(user?.email && user.isRBACAdmin);
66
+ }
2
67
  var OPEN_ENDPOINTS = [
3
68
  { "/api/contacts": ["POST"] },
4
69
  { "/api/form-submissions": ["POST"] },
@@ -34,6 +99,25 @@ function createAuthHelpers(getSession, NextResponse) {
34
99
  }
35
100
  return null;
36
101
  },
102
+ async requireEntityPermission(_req, entity, action) {
103
+ const session = await getSession();
104
+ if (!session?.user?.email) {
105
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
106
+ }
107
+ const u = session.user;
108
+ if (sessionHasEntityAccess(u, entity, action)) return null;
109
+ return NextResponse.json({ error: "Forbidden", entity, action }, { status: 403 });
110
+ },
111
+ async requireAdminAccess() {
112
+ const session = await getSession();
113
+ if (!session?.user?.email) {
114
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
115
+ }
116
+ const u = session.user;
117
+ if (u.isRBACAdmin) return null;
118
+ if (u.adminAccess === false) return NextResponse.json({ error: "Forbidden", reason: "admin_access" }, { status: 403 });
119
+ return null;
120
+ },
37
121
  async getAuthenticatedUser() {
38
122
  const session = await getSession();
39
123
  return session?.user ?? null;
@@ -41,6 +125,36 @@ function createAuthHelpers(getSession, NextResponse) {
41
125
  };
42
126
  }
43
127
 
128
+ // src/auth/seed-permissions.ts
129
+ async function seedAdministratorPermissions(dataSource, entityMap) {
130
+ const entities = getPermissionableEntityKeys(entityMap);
131
+ const groupRepo = dataSource.getRepository(entityMap.user_groups);
132
+ const permRepo = dataSource.getRepository(entityMap.permissions);
133
+ const adminGroup = await groupRepo.findOne({ where: { name: ADMIN_GROUP_NAME, deleted: false } });
134
+ if (!adminGroup) return;
135
+ const fullCrud = { canCreate: true, canRead: true, canUpdate: true, canDelete: true };
136
+ for (const entity of entities) {
137
+ const existing = await permRepo.findOne({
138
+ where: { groupId: adminGroup.id, entity }
139
+ });
140
+ if (existing) {
141
+ existing.canCreate = true;
142
+ existing.canRead = true;
143
+ existing.canUpdate = true;
144
+ existing.canDelete = true;
145
+ await permRepo.save(existing);
146
+ } else {
147
+ await permRepo.save(
148
+ permRepo.create({
149
+ groupId: adminGroup.id,
150
+ entity,
151
+ ...fullCrud
152
+ })
153
+ );
154
+ }
155
+ }
156
+ }
157
+
44
158
  // src/auth/middleware.ts
45
159
  var defaultPublicApiMethods = {
46
160
  "/api/contacts": ["POST"],
@@ -115,12 +229,18 @@ function getNextAuthOptions(config) {
115
229
  if (!user || user.blocked || user.deleted || !user.password) return null;
116
230
  const valid = await comparePassword(credentials.password, user.password);
117
231
  if (!valid) return null;
232
+ const g = user.group;
233
+ const isRBACAdmin = isSuperAdminGroupName(g?.name);
234
+ const entityPerms = permissionRowsToRecord(g?.permissions);
235
+ const adminAccess = user.adminAccess === true;
118
236
  return {
119
237
  id: user.id.toString(),
120
238
  email: user.email,
121
239
  name: user.name,
122
240
  groupId: user.groupId ?? void 0,
123
- permissions: ["admin"]
241
+ isRBACAdmin,
242
+ entityPerms,
243
+ adminAccess
124
244
  };
125
245
  } catch {
126
246
  return null;
@@ -144,17 +264,23 @@ function getNextAuthOptions(config) {
144
264
  callbacks: {
145
265
  async jwt({ token, user }) {
146
266
  if (user) {
147
- token.id = user.id;
148
- token.groupId = user.groupId;
149
- token.permissions = user.permissions;
267
+ const u = user;
268
+ token.id = u.id;
269
+ token.groupId = u.groupId;
270
+ token.isRBACAdmin = u.isRBACAdmin;
271
+ token.entityPerms = u.entityPerms;
272
+ token.adminAccess = u.adminAccess;
150
273
  }
151
274
  return token;
152
275
  },
153
276
  async session({ session, token }) {
154
277
  if (session.user) {
155
- session.user.id = token.id;
156
- session.user.groupId = token.groupId;
157
- session.user.permissions = token.permissions;
278
+ const t = token;
279
+ session.user.id = t.id;
280
+ session.user.groupId = t.groupId;
281
+ session.user.isRBACAdmin = t.isRBACAdmin;
282
+ session.user.entityPerms = t.entityPerms;
283
+ session.user.adminAccess = t.adminAccess;
158
284
  }
159
285
  return session;
160
286
  }
@@ -163,14 +289,23 @@ function getNextAuthOptions(config) {
163
289
  return extend ? extend(options) : options;
164
290
  }
165
291
  export {
292
+ ADMIN_GROUP_NAME,
166
293
  OPEN_ENDPOINTS,
167
294
  PERMISSION_REQUIRED_ENDPOINTS,
295
+ RBAC_ADMIN_ONLY_ENTITIES,
296
+ canManageRoles,
168
297
  createAuthHelpers,
169
298
  createCmsMiddleware,
170
299
  defaultPublicApiMethods,
171
300
  getNextAuthOptions,
301
+ getPermissionableEntityKeys,
172
302
  getRequiredPermission,
303
+ hasEntityPermission,
173
304
  isOpenEndpoint,
174
- isPublicMethod
305
+ isPublicMethod,
306
+ isSuperAdminGroupName,
307
+ permissionRowsToRecord,
308
+ seedAdministratorPermissions,
309
+ sessionHasEntityAccess
175
310
  };
176
311
  //# sourceMappingURL=auth.js.map