@infuro/cms-core 1.0.12 → 1.0.14

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.cjs CHANGED
@@ -64,7 +64,8 @@ var PERMISSION_ENTITY_INTERNAL_EXCLUDE = /* @__PURE__ */ new Set([
64
64
  "carts",
65
65
  "cart_items",
66
66
  "wishlists",
67
- "wishlist_items"
67
+ "wishlist_items",
68
+ "message_templates"
68
69
  ]);
69
70
  var PERMISSION_LOGICAL_ENTITIES = [
70
71
  "users",
@@ -264,11 +265,35 @@ function createCmsMiddleware(config = {}) {
264
265
  // src/auth/nextauth-options.ts
265
266
  var import_credentials = __toESM(require("next-auth/providers/credentials"), 1);
266
267
  var CredentialsProvider = import_credentials.default.default ?? import_credentials.default;
268
+ function sessionUserFromNextAuthUser(user) {
269
+ const g = user.group;
270
+ const isRBACAdmin = isSuperAdminGroupName(g?.name);
271
+ const entityPerms = permissionRowsToRecord(g?.permissions);
272
+ const adminAccess = user.adminAccess === true;
273
+ return {
274
+ id: user.id.toString(),
275
+ email: user.email,
276
+ name: user.name,
277
+ groupId: user.groupId ?? void 0,
278
+ isRBACAdmin,
279
+ entityPerms,
280
+ adminAccess
281
+ };
282
+ }
267
283
  function getNextAuthOptions(config) {
268
- const { getUserByEmail, comparePassword, signInPage = "/admin/signin", secret, extend } = config;
269
- const options = {
270
- secret: secret ?? process.env.NEXTAUTH_SECRET,
271
- providers: [
284
+ const {
285
+ getUserByEmail,
286
+ comparePassword,
287
+ signInPage = "/admin/signin",
288
+ secret,
289
+ extend,
290
+ enablePasswordLogin = true,
291
+ enableOtpLogin = false,
292
+ authorizeOtp
293
+ } = config;
294
+ const providers = [];
295
+ if (enablePasswordLogin) {
296
+ providers.push(
272
297
  CredentialsProvider({
273
298
  name: "credentials",
274
299
  credentials: {
@@ -282,25 +307,43 @@ function getNextAuthOptions(config) {
282
307
  if (!user || user.blocked || user.deleted || !user.password) return null;
283
308
  const valid = await comparePassword(credentials.password, user.password);
284
309
  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;
289
- return {
290
- id: user.id.toString(),
291
- email: user.email,
292
- name: user.name,
293
- groupId: user.groupId ?? void 0,
294
- isRBACAdmin,
295
- entityPerms,
296
- adminAccess
297
- };
310
+ return sessionUserFromNextAuthUser(user);
298
311
  } catch {
299
312
  return null;
300
313
  }
301
314
  }
302
315
  })
303
- ],
316
+ );
317
+ }
318
+ if (enableOtpLogin && authorizeOtp) {
319
+ providers.push(
320
+ CredentialsProvider({
321
+ id: "otp",
322
+ name: "otp",
323
+ credentials: {
324
+ identifier: { label: "Email or phone", type: "text" },
325
+ code: { label: "Code", type: "text" },
326
+ channel: { label: "Channel", type: "text" }
327
+ },
328
+ async authorize(credentials) {
329
+ const identifier = typeof credentials?.identifier === "string" ? credentials.identifier.trim() : "";
330
+ const code = typeof credentials?.code === "string" ? credentials.code.trim() : "";
331
+ const ch = credentials?.channel === "sms" ? "sms" : "email";
332
+ if (!identifier || !code) return null;
333
+ try {
334
+ const user = await authorizeOtp({ identifier, channel: ch, code });
335
+ if (!user || user.blocked || user.deleted) return null;
336
+ return sessionUserFromNextAuthUser(user);
337
+ } catch {
338
+ return null;
339
+ }
340
+ }
341
+ })
342
+ );
343
+ }
344
+ const options = {
345
+ secret: secret ?? process.env.NEXTAUTH_SECRET,
346
+ providers,
304
347
  session: { strategy: "jwt" },
305
348
  pages: { signIn: signInPage },
306
349
  cookies: {
package/dist/auth.cjs.map CHANGED
@@ -1 +1 @@
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":"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","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, AuthorizeOtpInput } 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 'message_templates',\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 AuthorizeOtpInput {\n identifier: string;\n channel: 'email' | 'sms';\n code: string;\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 /** When false, password CredentialsProvider is omitted. Default true. */\n enablePasswordLogin?: boolean;\n /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */\n enableOtpLogin?: boolean;\n /** Validate OTP and return user (same shape as password flow) or null. */\n authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;\n}\n\nfunction sessionUserFromNextAuthUser(user: NextAuthUser) {\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}\n\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\n const {\n getUserByEmail,\n comparePassword,\n signInPage = '/admin/signin',\n secret,\n extend,\n enablePasswordLogin = true,\n enableOtpLogin = false,\n authorizeOtp,\n } = config;\n\n const providers: NextAuthOptions['providers'] = [];\n\n if (enablePasswordLogin) {\n providers.push(\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 sessionUserFromNextAuthUser(user);\n } catch {\n return null;\n }\n },\n })\n );\n }\n\n if (enableOtpLogin && authorizeOtp) {\n providers.push(\n CredentialsProvider({\n id: 'otp',\n name: 'otp',\n credentials: {\n identifier: { label: 'Email or phone', type: 'text' },\n code: { label: 'Code', type: 'text' },\n channel: { label: 'Channel', type: 'text' },\n },\n async authorize(credentials) {\n const identifier = typeof credentials?.identifier === 'string' ? credentials.identifier.trim() : '';\n const code = typeof credentials?.code === 'string' ? credentials.code.trim() : '';\n const ch = credentials?.channel === 'sms' ? 'sms' : 'email';\n if (!identifier || !code) return null;\n try {\n const user = await authorizeOtp({ identifier, channel: ch, code });\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted) return null;\n return sessionUserFromNextAuthUser(user);\n } catch {\n return null;\n }\n },\n })\n );\n }\n\n const options: NextAuthOptions = {\n secret: secret ?? process.env.NEXTAUTH_SECRET,\n providers,\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":"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","names":["isPublicMethod","_CredentialsProvider"]}
package/dist/auth.d.cts CHANGED
@@ -76,6 +76,11 @@ interface NextAuthUser {
76
76
  }>;
77
77
  };
78
78
  }
79
+ interface AuthorizeOtpInput {
80
+ identifier: string;
81
+ channel: 'email' | 'sms';
82
+ code: string;
83
+ }
79
84
  interface NextAuthOptionsConfig {
80
85
  /** Resolve user by email (e.g. from TypeORM). Return null if not found. */
81
86
  getUserByEmail: (email: string) => Promise<NextAuthUser | null>;
@@ -83,7 +88,13 @@ interface NextAuthOptionsConfig {
83
88
  signInPage?: string;
84
89
  secret?: string;
85
90
  extend?: (options: NextAuthOptions) => NextAuthOptions;
91
+ /** When false, password CredentialsProvider is omitted. Default true. */
92
+ enablePasswordLogin?: boolean;
93
+ /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */
94
+ enableOtpLogin?: boolean;
95
+ /** Validate OTP and return user (same shape as password flow) or null. */
96
+ authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;
86
97
  }
87
98
  declare function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions;
88
99
 
89
- export { type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
100
+ export { type AuthorizeOtpInput, type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
package/dist/auth.d.ts CHANGED
@@ -76,6 +76,11 @@ interface NextAuthUser {
76
76
  }>;
77
77
  };
78
78
  }
79
+ interface AuthorizeOtpInput {
80
+ identifier: string;
81
+ channel: 'email' | 'sms';
82
+ code: string;
83
+ }
79
84
  interface NextAuthOptionsConfig {
80
85
  /** Resolve user by email (e.g. from TypeORM). Return null if not found. */
81
86
  getUserByEmail: (email: string) => Promise<NextAuthUser | null>;
@@ -83,7 +88,13 @@ interface NextAuthOptionsConfig {
83
88
  signInPage?: string;
84
89
  secret?: string;
85
90
  extend?: (options: NextAuthOptions) => NextAuthOptions;
91
+ /** When false, password CredentialsProvider is omitted. Default true. */
92
+ enablePasswordLogin?: boolean;
93
+ /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */
94
+ enableOtpLogin?: boolean;
95
+ /** Validate OTP and return user (same shape as password flow) or null. */
96
+ authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;
86
97
  }
87
98
  declare function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions;
88
99
 
89
- export { type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
100
+ export { type AuthorizeOtpInput, type CmsMiddlewareConfig, type NextAuthOptionsConfig, type NextAuthUser, createCmsMiddleware, defaultPublicApiMethods, getNextAuthOptions, seedAdministratorPermissions };
package/dist/auth.js CHANGED
@@ -11,7 +11,8 @@ var PERMISSION_ENTITY_INTERNAL_EXCLUDE = /* @__PURE__ */ new Set([
11
11
  "carts",
12
12
  "cart_items",
13
13
  "wishlists",
14
- "wishlist_items"
14
+ "wishlist_items",
15
+ "message_templates"
15
16
  ]);
16
17
  var PERMISSION_LOGICAL_ENTITIES = [
17
18
  "users",
@@ -211,11 +212,35 @@ function createCmsMiddleware(config = {}) {
211
212
  // src/auth/nextauth-options.ts
212
213
  import _CredentialsProvider from "next-auth/providers/credentials";
213
214
  var CredentialsProvider = _CredentialsProvider.default ?? _CredentialsProvider;
215
+ function sessionUserFromNextAuthUser(user) {
216
+ const g = user.group;
217
+ const isRBACAdmin = isSuperAdminGroupName(g?.name);
218
+ const entityPerms = permissionRowsToRecord(g?.permissions);
219
+ const adminAccess = user.adminAccess === true;
220
+ return {
221
+ id: user.id.toString(),
222
+ email: user.email,
223
+ name: user.name,
224
+ groupId: user.groupId ?? void 0,
225
+ isRBACAdmin,
226
+ entityPerms,
227
+ adminAccess
228
+ };
229
+ }
214
230
  function getNextAuthOptions(config) {
215
- const { getUserByEmail, comparePassword, signInPage = "/admin/signin", secret, extend } = config;
216
- const options = {
217
- secret: secret ?? process.env.NEXTAUTH_SECRET,
218
- providers: [
231
+ const {
232
+ getUserByEmail,
233
+ comparePassword,
234
+ signInPage = "/admin/signin",
235
+ secret,
236
+ extend,
237
+ enablePasswordLogin = true,
238
+ enableOtpLogin = false,
239
+ authorizeOtp
240
+ } = config;
241
+ const providers = [];
242
+ if (enablePasswordLogin) {
243
+ providers.push(
219
244
  CredentialsProvider({
220
245
  name: "credentials",
221
246
  credentials: {
@@ -229,25 +254,43 @@ function getNextAuthOptions(config) {
229
254
  if (!user || user.blocked || user.deleted || !user.password) return null;
230
255
  const valid = await comparePassword(credentials.password, user.password);
231
256
  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;
236
- return {
237
- id: user.id.toString(),
238
- email: user.email,
239
- name: user.name,
240
- groupId: user.groupId ?? void 0,
241
- isRBACAdmin,
242
- entityPerms,
243
- adminAccess
244
- };
257
+ return sessionUserFromNextAuthUser(user);
245
258
  } catch {
246
259
  return null;
247
260
  }
248
261
  }
249
262
  })
250
- ],
263
+ );
264
+ }
265
+ if (enableOtpLogin && authorizeOtp) {
266
+ providers.push(
267
+ CredentialsProvider({
268
+ id: "otp",
269
+ name: "otp",
270
+ credentials: {
271
+ identifier: { label: "Email or phone", type: "text" },
272
+ code: { label: "Code", type: "text" },
273
+ channel: { label: "Channel", type: "text" }
274
+ },
275
+ async authorize(credentials) {
276
+ const identifier = typeof credentials?.identifier === "string" ? credentials.identifier.trim() : "";
277
+ const code = typeof credentials?.code === "string" ? credentials.code.trim() : "";
278
+ const ch = credentials?.channel === "sms" ? "sms" : "email";
279
+ if (!identifier || !code) return null;
280
+ try {
281
+ const user = await authorizeOtp({ identifier, channel: ch, code });
282
+ if (!user || user.blocked || user.deleted) return null;
283
+ return sessionUserFromNextAuthUser(user);
284
+ } catch {
285
+ return null;
286
+ }
287
+ }
288
+ })
289
+ );
290
+ }
291
+ const options = {
292
+ secret: secret ?? process.env.NEXTAUTH_SECRET,
293
+ providers,
251
294
  session: { strategy: "jwt" },
252
295
  pages: { signIn: signInPage },
253
296
  cookies: {
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/permission-entities.ts","../src/auth/helpers.ts","../src/auth/seed-permissions.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["/** 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":"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","names":["isPublicMethod"]}
1
+ {"version":3,"sources":["../src/auth/permission-entities.ts","../src/auth/helpers.ts","../src/auth/seed-permissions.ts","../src/auth/middleware.ts","../src/auth/nextauth-options.ts"],"sourcesContent":["/** 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 'message_templates',\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 AuthorizeOtpInput {\n identifier: string;\n channel: 'email' | 'sms';\n code: string;\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 /** When false, password CredentialsProvider is omitted. Default true. */\n enablePasswordLogin?: boolean;\n /** When true, registers CredentialsProvider id `otp`. Requires authorizeOtp. Default false. */\n enableOtpLogin?: boolean;\n /** Validate OTP and return user (same shape as password flow) or null. */\n authorizeOtp?: (input: AuthorizeOtpInput) => Promise<NextAuthUser | null>;\n}\n\nfunction sessionUserFromNextAuthUser(user: NextAuthUser) {\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}\n\nexport function getNextAuthOptions(config: NextAuthOptionsConfig): NextAuthOptions {\n const {\n getUserByEmail,\n comparePassword,\n signInPage = '/admin/signin',\n secret,\n extend,\n enablePasswordLogin = true,\n enableOtpLogin = false,\n authorizeOtp,\n } = config;\n\n const providers: NextAuthOptions['providers'] = [];\n\n if (enablePasswordLogin) {\n providers.push(\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 sessionUserFromNextAuthUser(user);\n } catch {\n return null;\n }\n },\n })\n );\n }\n\n if (enableOtpLogin && authorizeOtp) {\n providers.push(\n CredentialsProvider({\n id: 'otp',\n name: 'otp',\n credentials: {\n identifier: { label: 'Email or phone', type: 'text' },\n code: { label: 'Code', type: 'text' },\n channel: { label: 'Channel', type: 'text' },\n },\n async authorize(credentials) {\n const identifier = typeof credentials?.identifier === 'string' ? credentials.identifier.trim() : '';\n const code = typeof credentials?.code === 'string' ? credentials.code.trim() : '';\n const ch = credentials?.channel === 'sms' ? 'sms' : 'email';\n if (!identifier || !code) return null;\n try {\n const user = await authorizeOtp({ identifier, channel: ch, code });\n if (!user || user.blocked || (user as { deleted?: boolean }).deleted) return null;\n return sessionUserFromNextAuthUser(user);\n } catch {\n return null;\n }\n },\n })\n );\n }\n\n const options: NextAuthOptions = {\n secret: secret ?? process.env.NEXTAUTH_SECRET,\n providers,\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":"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","names":["isPublicMethod"]}
@@ -37,7 +37,7 @@ interface EmailTemplateResult {
37
37
  type TemplateContext<T = unknown> = T & {
38
38
  companyDetails: CompanyDetails;
39
39
  };
40
- declare const EMAIL_TEMPLATE_NAMES: readonly ["signup", "passwordReset", "passwordChange", "orderPlaced", "returnInitiated", "shippingUpdate", "invite", "formSubmission"];
40
+ declare const EMAIL_TEMPLATE_NAMES: readonly ["signup", "passwordReset", "passwordChange", "orderPlaced", "returnInitiated", "shippingUpdate", "invite", "formSubmission", "otp"];
41
41
  type EmailTemplateName = (typeof EMAIL_TEMPLATE_NAMES)[number];
42
42
  declare function getCompanyDetailsFromSettings(settingsGroup: Record<string, string>): CompanyDetails;
43
43
 
@@ -55,6 +55,10 @@ interface CrudHandlerOptions {
55
55
  status?: number;
56
56
  }) => Response;
57
57
  requireEntityPermission?: (req: Request, entity: string, action: EntityCrudAction) => Promise<Response | null>;
58
+ /** When set, contact create/update enqueues ERP `create-contact` (non-fatal). */
59
+ getCms?: () => Promise<{
60
+ getPlugin: (name: string) => unknown;
61
+ }>;
58
62
  }
59
63
  declare function createCrudHandler(dataSource: DataSource, entityMap: EntityMap, options: CrudHandlerOptions): {
60
64
  GET(req: Request, resource: string): Promise<Response>;
@@ -265,9 +269,17 @@ interface SettingsApiConfig extends CmsHandlersBase {
265
269
  /** Groups in this list are readable without auth (GET returns all keys for the group). */
266
270
  publicGetGroups?: string[];
267
271
  }
272
+ /**
273
+ * Structural types so apps with their own `typeorm` install (e.g. npm link) typecheck without duplicate-package errors.
274
+ */
275
+ type GetPublicSettingsGroupDataSource = {
276
+ getRepository(entity: unknown): {
277
+ find(options: object): Promise<unknown[]>;
278
+ };
279
+ };
268
280
  interface GetPublicSettingsGroupConfig {
269
- dataSource: DataSource;
270
- entityMap: EntityMap;
281
+ dataSource: GetPublicSettingsGroupDataSource;
282
+ entityMap: Record<string, unknown>;
271
283
  encryptionKey?: string;
272
284
  }
273
285
  /** Same rows as unauthenticated GET /api/settings/:group when the group is in publicGetGroups. */
@@ -343,6 +355,11 @@ declare function createCmsApiHandler(config: CmsApiHandlerConfig): {
343
355
  handle(method: string, path: string[], req: Request): Promise<Response>;
344
356
  };
345
357
 
358
+ interface StorefrontOtpFlags {
359
+ login?: boolean;
360
+ verifyEmail?: boolean;
361
+ verifyPhone?: boolean;
362
+ }
346
363
  interface StorefrontApiConfig {
347
364
  dataSource: DataSource;
348
365
  entityMap: EntityMap;
@@ -361,9 +378,16 @@ interface StorefrontApiConfig {
361
378
  getCompanyDetails?: () => Promise<CompanyDetails>;
362
379
  /** Origin for verify links (e.g. process.env.NEXTAUTH_URL). Required with getCms for verification URLs. */
363
380
  publicSiteUrl?: string;
381
+ /** When a flag is true, the corresponding OTP storefront route is enabled. */
382
+ otpFlags?: StorefrontOtpFlags;
383
+ /** Defaults to OTP_PEPPER or NEXTAUTH_SECRET. */
384
+ otpPepper?: string;
385
+ defaultPhoneCountryCode?: string;
386
+ /** When false, login OTP send accepts email only (no phone). Default true. */
387
+ otpAllowPhoneLogin?: boolean;
364
388
  }
365
389
  declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
366
390
  handle(method: string, path: string[], req: Request): Promise<Response>;
367
391
  };
368
392
 
369
- export { type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createSetPasswordHandler as H, type InviteAcceptConfig as I, createSettingsApiHandlers as J, createStorefrontApiHandler as K, createUploadHandler as L, createUserAuthApiRouter as M, createUserAvatarHandler as N, type OrderPlacedLineItem as O, createUserProfileHandler as P, createUsersApiHandlers as Q, getCompanyDetailsFromSettings as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, getPublicSettingsGroup as V, mergeEmailLayoutCompanyDetails as W, type EmailTemplateName as a, type AuthHandlersConfig as b, type ChangePasswordConfig as c, type CmsApiHandlerConfig as d, type CmsGetter as e, type CrudHandlerOptions as f, type EntityMap as g, type FormBySlugConfig as h, type SetPasswordConfig as i, type SettingsApiConfig as j, type SocialLinkItem as k, type StorefrontApiConfig as l, type UserAuthApiConfig as m, type UserAvatarConfig as n, type UserProfileConfig as o, type UsersApiConfig as p, createAnalyticsHandlers as q, createBlogBySlugHandler as r, createChangePasswordHandler as s, createCmsApiHandler as t, createCrudByIdHandler as u, createCrudHandler as v, createDashboardStatsHandler as w, createForgotPasswordHandler as x, createFormBySlugHandler as y, createInviteAcceptHandler as z };
393
+ export { type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createFormBySlugHandler as H, type InviteAcceptConfig as I, createInviteAcceptHandler as J, createSetPasswordHandler as K, createSettingsApiHandlers as L, createStorefrontApiHandler as M, createUploadHandler as N, type OrderPlacedLineItem as O, createUserAuthApiRouter as P, createUserAvatarHandler as Q, createUserProfileHandler as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createUsersApiHandlers as V, getCompanyDetailsFromSettings as W, getPublicSettingsGroup as X, mergeEmailLayoutCompanyDetails as Y, type EmailTemplateName as a, type EntityMap as b, type AuthHandlersConfig as c, type ChangePasswordConfig as d, type CmsApiHandlerConfig as e, type CmsGetter as f, type CrudHandlerOptions as g, type FormBySlugConfig as h, type GetPublicSettingsGroupDataSource as i, type SetPasswordConfig as j, type SettingsApiConfig as k, type SocialLinkItem as l, type StorefrontApiConfig as m, type StorefrontOtpFlags as n, type UserAuthApiConfig as o, type UserAvatarConfig as p, type UserProfileConfig as q, type UsersApiConfig as r, createAnalyticsHandlers as s, createBlogBySlugHandler as t, createChangePasswordHandler as u, createCmsApiHandler as v, createCrudByIdHandler as w, createCrudHandler as x, createDashboardStatsHandler as y, createForgotPasswordHandler as z };