Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/lib/auth/acl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { User, UserRole, Permission } from '@/types/api';

/**
* Minimal object required by many auth checks: only the role is required.
* This allows callers that only have a partial user (AuthUser) to pass through.
*/
type RoleHolder = { role: UserRole };

/**
* Mapping of roles to their granted permissions.
*/
Expand All @@ -19,9 +25,9 @@ export const ROLES_PERMISSIONS: Record<UserRole, Permission[]> = {
};

/**
* Check if a user has a specific permission based on their role.
* Check if a user (or any object that contains a role) has a specific permission.
*/
export function hasPermission(user: User | null | undefined, permission: Permission): boolean {
export function hasPermission(user: RoleHolder | null | undefined, permission: Permission): boolean {
if (!user) return false;

const permissions = ROLES_PERMISSIONS[user.role] ?? [];
Expand All @@ -32,7 +38,7 @@ export function hasPermission(user: User | null | undefined, permission: Permiss
* Check if a user has any of the provided permissions.
*/
export function hasAnyPermission(
user: User | null | undefined,
user: RoleHolder | null | undefined,
permissions: Permission[],
): boolean {
if (!user) return false;
Expand All @@ -44,7 +50,7 @@ export function hasAnyPermission(
* Check if a user has all of the provided permissions.
*/
export function hasAllPermissions(
user: User | null | undefined,
user: RoleHolder | null | undefined,
permissions: Permission[],
): boolean {
if (!user) return false;
Expand All @@ -56,7 +62,7 @@ export function hasAllPermissions(
* Check if a user has at least the minimum required role.
* Roles are hierarchical: ADMIN > INSTRUCTOR > STUDENT > GUEST
*/
export function isAtLeast(user: User | null | undefined, role: UserRole): boolean {
export function isAtLeast(user: RoleHolder | null | undefined, role: UserRole): boolean {
if (!user) return false;

return isAtLeastRole(user.role, role);
Expand All @@ -74,4 +80,4 @@ export function isAtLeastRole(userRole: UserRole | null | undefined, role: UserR
const requiredRoleIndex = hierarchy.indexOf(role);

return userRoleIndex >= requiredRoleIndex;
}
}
7 changes: 5 additions & 2 deletions src/lib/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export async function requireAuth(request: NextRequest): Promise<NextResponse |
* Extract user from request using Bearer token or user-role cookie.
* Returns null if user cannot be determined.
*/
export function getUserFromRequest(request: NextRequest): User | null {
// Narrow the returned type to only the fields this helper provides.
type AuthUser = Pick<User, 'id' | 'name' | 'email' | 'role' | 'referralCount'>;

export function getUserFromRequest(request: NextRequest): AuthUser | null {
// Try to get user from Bearer token
const authHeader = request.headers.get('authorization');
if (authHeader?.startsWith('Bearer ')) {
Expand Down Expand Up @@ -68,4 +71,4 @@ export function getUserFromRequest(request: NextRequest): User | null {
}

return null;
}
}
3 changes: 3 additions & 0 deletions src/schemas/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const UserSchema = z.object({
referralCode: z.string().optional(),
referredBy: z.string().optional(),
referralCount: z.number().default(0),
createdAt: z.string(), // ISO 8601 date string
updatedAt: z.string(), // ISO 8601 date string
emailVerified: z.boolean().default(false),
});

export type User = z.infer<typeof UserSchema>;
Expand Down
Loading