import type { NavigationGuardWithThis, RouteLocationNormalized } from 'vue-router' import { useUserStore } from '@v2/stores/user.store' import { useGlobalStore } from '@v2/stores/global.store' import { useCompanyStore } from '@v2/stores/company.store' import { useCustomerPortalStore } from '@v2/features/customer-portal/store' import { handleApiError } from '@v2/utils/error-handling' import { resolveCompanySlug } from '@v2/features/customer-portal/utils/routes' /** * Main authentication and authorization guard. * * Handles: * - Redirecting to the no-company view when no company is selected * (unless in admin mode or the user is a super admin visiting a * super-admin-only route). * - Ability-based access control: redirects to account settings when * the current user lacks the required ability. * - Super admin route protection: redirects non-super-admins to the * dashboard. * - Owner route protection: redirects non-owners to the dashboard. */ export const authGuard: NavigationGuardWithThis = ( to: RouteLocationNormalized ) => { if (to.meta.isCustomerPortal) { return handleCustomerPortalRoute(to) } const userStore = useUserStore() const globalStore = useGlobalStore() const companyStore = useCompanyStore() const { isAppLoaded } = globalStore const ability = to.meta.ability // Guard 1: no company selected -> redirect to no-company view // Skip if the target IS the no-company view, or if we are in admin // mode, or if the route is super-admin-only and the user qualifies. if (isAppLoaded && to.meta.requiresAuth && to.name !== 'no.company') { const isSuperAdminRoute = to.meta.isSuperAdmin === true && currentUserIsSuperAdmin(userStore) if ( !companyStore.selectedCompany && !companyStore.isAdminMode && !isSuperAdminRoute ) { return { name: 'no.company' } } } // Guard 2: ability check if (ability && isAppLoaded && to.meta.requiresAuth) { if (!userStore.hasAbilities(ability)) { return { name: 'settings.account' } } return } // Guard 3: super admin check if (to.meta.isSuperAdmin && isAppLoaded) { if (!currentUserIsSuperAdmin(userStore)) { return { name: 'dashboard' } } return } // Guard 4: owner check if (to.meta.isOwner && isAppLoaded) { if (!currentUserIsOwner(userStore)) { return { name: 'dashboard' } } return } } // ---- helpers ---- async function handleCustomerPortalRoute( to: RouteLocationNormalized ): Promise<{ name: string; params: { company: string } } | void> { const customerPortalStore = useCustomerPortalStore() const companySlug = resolveCompanySlug(to.params.company) if (!companySlug) { return } const isGuestRoute = to.meta.customerPortalGuest === true const shouldBootstrap = customerPortalStore.companySlug !== companySlug || !customerPortalStore.isAppLoaded || customerPortalStore.currentUser === null if (!shouldBootstrap) { if (isGuestRoute) { return { name: 'customer-portal.dashboard', params: { company: companySlug }, } } return } try { await customerPortalStore.bootstrap(companySlug) if (isGuestRoute) { return { name: 'customer-portal.dashboard', params: { company: companySlug }, } } } catch (err: unknown) { customerPortalStore.resetState(companySlug) if (isGuestRoute) { return } const normalizedError = handleApiError(err) if (normalizedError.isUnauthorized || normalizedError.statusCode === 401) { return { name: 'customer-portal.login', params: { company: companySlug }, } } return { name: 'customer-portal.login', params: { company: companySlug }, } } } function currentUserIsSuperAdmin( userStore: ReturnType ): boolean { return userStore.currentUser?.is_super_admin ?? false } function currentUserIsOwner( userStore: ReturnType ): boolean { return userStore.currentUser?.is_owner ?? false }