/** * React Hook for Permission Checking * Integrates with Stytch B2B session to provide permission-based access control */ 'use client'; import { useMemo } from 'react'; import { useStytchMember } from '@stytch/nextjs/b2b'; import { hasRole, hasAnyRole, hasAllRoles, } from '@/lib/auth/permission-utils'; import type { Permission } from '@/lib/auth/permissions'; import type { ProfileResponseDto } from '@/lib/api/api/dto/profile.dto'; import { useAuthContext } from '@/lib/contexts/auth-context'; export interface UsePermissionsReturn { /** * User profile details supplied by backend */ profile: ProfileResponseDto | null; /** * Array of role names from Stytch session */ roles: string[]; /** * Array of all permissions granted to the user */ permissions: string[]; /** * Check if user has a specific permission * Supports wildcard matching (e.g., "invoice:*") */ hasPermission: (permission: string) => boolean; /** * Check if user has ANY of the specified permissions (OR logic) */ hasAnyPermission: (permissions: string[]) => boolean; /** * Check if user has ALL of the specified permissions (AND logic) */ hasAllPermissions: (permissions: string[]) => boolean; /** * Check if user has a specific role */ hasRole: (role: string) => boolean; /** * Check if user has ANY of the specified roles */ hasAnyRole: (roles: string[]) => boolean; /** * Check if user has ALL of the specified roles */ hasAllRoles: (roles: string[]) => boolean; /** * Whether the Stytch member is initialized */ isInitialized: boolean; /** * Whether the user is authenticated */ isAuthenticated: boolean; /** * Update cached auth state (used when profile changes client-side) */ updateAuthState: (state: { profile?: ProfileResponseDto | null; roles?: string[]; permissions?: Permission[]; }) => void; } /** * Hook to access and check user permissions based on Stytch roles * * @returns Permission checking utilities and user state * * @example * function MyComponent() { * const { hasPermission, hasAnyPermission, isAuthenticated } = usePermissions(); * * if (!isAuthenticated) return ; * * return ( * <> * {hasPermission('invoice:create') && } * {hasAnyPermission(['invoice:view', 'invoice:*']) && } * * ); * } */ export function usePermissions(): UsePermissionsReturn { const context = useAuthContext(); // Safely get member, handling cases where provider might not be available let member, isInitialized; try { const stytchData = useStytchMember(); member = stytchData.member; isInitialized = stytchData.isInitialized; } catch (error) { // If Stytch provider is not available, return empty state member = null; isInitialized = false; } // Extract roles from Stytch member // member.roles can be an array of strings (slugs) or objects const roles = useMemo(() => { const rawRoles = member?.roles; if (!rawRoles) return []; if (Array.isArray(rawRoles)) { return rawRoles .map(role => { if (typeof role === 'string') { return role; } if (role && typeof role === 'object') { return ( (role as any).role_id ?? (role as any).slug ?? (role as any).name ?? (role as any).id ); } return undefined; }) .filter((role): role is string => typeof role === 'string'); } if (typeof rawRoles === 'string') { return [rawRoles]; } return []; }, [member]); // Note: This hook is deprecated - use server-side permissions instead // Permissions should be computed on server and passed down as props const permissions = useMemo(() => { return [] as Permission[]; }, []); // Create memoized permission check functions // Note: These won't work correctly without server-computed permissions const permissionChecks = useMemo(() => ({ hasPermission: (_permission: string) => false, hasAnyPermission: (_perms: string[]) => false, hasAllPermissions: (_perms: string[]) => false, hasRole: (role: string) => hasRole(roles, role), hasAnyRole: (rolesToCheck: string[]) => hasAnyRole(roles, rolesToCheck), hasAllRoles: (rolesToCheck: string[]) => hasAllRoles(roles, rolesToCheck), }), [roles]); if (context) { return { profile: context.profile, roles: context.roles, permissions: context.permissions, hasPermission: context.hasPermission, hasAnyPermission: context.hasAnyPermission, hasAllPermissions: context.hasAllPermissions, hasRole: context.hasRole, hasAnyRole: context.hasAnyRole, hasAllRoles: context.hasAllRoles, isInitialized: context.isInitialized, isAuthenticated: context.isAuthenticated, updateAuthState: context.updateAuthState, }; } return { profile: null, roles, permissions, ...permissionChecks, isInitialized, isAuthenticated: !!member && isInitialized, updateAuthState: () => undefined, }; }