import type { Permission, DataScope, AuthContext, Role } from "@/shared/types/permissions" import { isPermission } from "@/shared/lib/type-guards" import { getSession } from "@/shared/lib/session" import { PermissionDeniedError } from "@/shared/lib/errors" // Re-export for backward compatibility (other modules still import from here) export { PermissionDeniedError } from "@/shared/lib/errors" /** * Resolve the data scope for a user based on their roles. * * Delegates to the RBAC module's configuration-driven resolver via dynamic * import, so the shared layer never statically depends on modules/*. * This is the same pattern used by shared/lib/session.ts to break the * shared ↔ auth circular dependency. * * P1-5/P1-6 audit fix: removed direct DB queries against classes, * classEnrollments, classSubjectTeachers, grades, parentStudentRelations * tables. The resolver now calls module data-access functions and is * driven by a configuration array (DATA_SCOPE_RULES) instead of hardcoded * role-name checks. */ async function resolveDataScope(userId: string, roleNames: Role[]): Promise { const { resolveDataScopeFromConfig } = await import("@/modules/rbac/lib/data-scope-resolver") return resolveDataScopeFromConfig(userId, roleNames) } /** * Get the full authentication context for the current user. * Throws if not authenticated. */ export async function getAuthContext(): Promise { const session = await getSession() const userId = session?.user?.id if (!userId) throw new PermissionDeniedError("auth_required") // Prefer session data (already resolved in JWT callback) // Use type guards to safely coerce session values const roleNames = (session.user.roles ?? []).filter( (r): r is Role => typeof r === "string" ) const permissions = (session.user.permissions ?? []).filter(isPermission) // Resolve data scope from DB (not cached in JWT since it can change) const dataScope = await resolveDataScope(userId, roleNames) return { userId, roles: roleNames, permissions, dataScope } } /** * Assert the current user has the specified permission. * Returns AuthContext on success, throws PermissionDeniedError on failure. */ export async function requirePermission(permission: Permission): Promise { const ctx = await getAuthContext() if (!ctx.permissions.includes(permission)) { throw new PermissionDeniedError(permission) } return ctx } /** * Check permission without throwing. Useful for conditional logic. */ export async function checkPermission( permission: Permission ): Promise<{ allowed: boolean; ctx: AuthContext }> { const ctx = await getAuthContext() return { allowed: ctx.permissions.includes(permission), ctx } } /** * Convenience: assert the user is authenticated (has any role). * Returns AuthContext on success. */ export async function requireAuth(): Promise { return getAuthContext() }