All files / src/composables usePermissions.ts

53.84% Statements 21/39
52.38% Branches 11/21
69.23% Functions 9/13
58.62% Lines 17/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                      159x 159x 159x     159x 164x   164x 123x                 159x 145x     159x   159x 1x 1x 1x                                                               242x 1x     159x    
import { computed, watch } from 'vue'
import { useFlowsStore } from '@/stores/flows'
import { useAuthStore } from '@/stores/auth'
import { useRolesStore } from '@/stores/roles'
 
/**
 * Provides permission checking for the current active flow.
 * Mirrors the wildcard matching logic from the backend PermissionChecker.
 * Automatically fetches roles for the current flow if not already loaded.
 */
export function usePermissions() {
  const flowsStore = useFlowsStore()
  const authStore = useAuthStore()
  const rolesStore = useRolesStore()
 
  // Ensure roles are loaded for the current flow so permission checks are accurate
  watch(
    () => authStore.currentFlow,
    (flowId) => {
      if (flowId && !rolesStore.roles.length && !rolesStore.loading) {
        rolesStore.fetchRoles(flowId).catch(() => {
          // Background fetch — silently ignore errors; permission checks will
          // fall back to isOwner only until roles are eventually loaded.
        })
      }
    },
    { immediate: true }
  )
 
  const currentMembership = computed(() =>
    flowsStore.myFlows.find((f) => f.flowId === authStore.currentFlow)
  )
 
  const isOwner = computed(() => currentMembership.value?.isOwner ?? false)
 
  const userPermissions = computed((): string[] => {
    Iif (isOwner.value) return ['*:*']
    const membership = currentMembership.value
    Eif (!membership?.roleIds?.length) return []
 
    const perms = new Set<string>()
    for (const roleId of membership.roleIds) {
      const role = rolesStore.roles.find((r) => r.roleId === roleId)
      if (role?.permissions) {
        role.permissions.forEach((p) => perms.add(p))
      }
    }
    return Array.from(perms)
  })
 
  /**
   * Checks whether a granted permission (which may contain wildcards) covers a required permission.
   * Mirrors backend matchesPermission logic.
   */
  function matchesPermission(granted: string, required: string): boolean {
    if (!granted || !required) return false
    if (granted === required) return true
    const pattern = granted.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')
    try {
      return new RegExp(`^${pattern}$`).test(required)
    } catch {
      return false
    }
  }
 
  /**
   * Returns true if the current user has the required permission in the active flow.
   * Flow owners always return true.
   */
  function hasPermission(required: string): boolean {
    if (isOwner.value) return true
    return userPermissions.value.some((granted) => matchesPermission(granted, required))
  }
 
  return { hasPermission, isOwner, userPermissions }
}