| 1 |
/** |
| 2 |
* Permission and role checking hook |
| 3 |
* Supports both free and Pro version role systems |
| 4 |
*/ |
| 5 |
|
| 6 |
import { useCallback, useMemo } from "react"; |
| 7 |
|
| 8 |
// Types are now in i18n.ts |
| 9 |
|
| 10 |
interface UsePermissionsReturn { |
| 11 |
can: (capability: string) => boolean; |
| 12 |
hasRole: (role: string) => boolean; |
| 13 |
isPro: boolean; |
| 14 |
permissions: string[]; |
| 15 |
roles: string[]; |
| 16 |
} |
| 17 |
|
| 18 |
/** |
| 19 |
* Hook to check user permissions and roles |
| 20 |
* @returns Permission checking functions |
| 21 |
*/ |
| 22 |
export const usePermissions = (): UsePermissionsReturn => { |
| 23 |
const permissions = useMemo(() => { |
| 24 |
return window.yatraAdmin?.permissions || []; |
| 25 |
}, []); |
| 26 |
|
| 27 |
const roles = useMemo(() => { |
| 28 |
return window.yatraAdmin?.roles || []; |
| 29 |
}, []); |
| 30 |
|
| 31 |
const capabilities = useMemo(() => { |
| 32 |
return window.yatraAdmin?.capabilities || {}; |
| 33 |
}, []); |
| 34 |
|
| 35 |
const isPro = useMemo(() => { |
| 36 |
return window.yatraAdmin?.isPro || false; |
| 37 |
}, []); |
| 38 |
|
| 39 |
/** |
| 40 |
* Check if the current user has a specific capability. |
| 41 |
* |
| 42 |
* Sources, in order: |
| 43 |
* 1. `window.yatraAdmin.capabilities` — server-built map of every |
| 44 |
* `yatra_*` cap the user passes via WP-native `current_user_can`. |
| 45 |
* Built by AdminAssetsProvider from `$current_user->allcaps`, |
| 46 |
* filtered to the `yatra_` prefix. |
| 47 |
* 2. `window.yatraAdmin.userCaps` — Team module's enriched list, |
| 48 |
* includes the same caps + anything added via the user_has_cap |
| 49 |
* filter (admin fallback, per-user grants, derived caps). |
| 50 |
* 3. `window.yatraAdmin.isWpAdmin` — last-resort fallback for site |
| 51 |
* owners so they're never locked out of anything. |
| 52 |
* |
| 53 |
* Default-deny when none match. The previous implementation had a |
| 54 |
* hardcoded "every user gets yatra_view_trips / yatra_edit_trips / |
| 55 |
* yatra_view_bookings" map that broke the Team & Access module — an |
| 56 |
* Accountant calling `can('yatra_edit_trips')` returned true via that |
| 57 |
* defaults map, leaking Tools / Modules / etc. into their UI even |
| 58 |
* though their role doesn't grant edit_trips. |
| 59 |
* |
| 60 |
* @param capability - Capability to check |
| 61 |
* @returns True if user has capability |
| 62 |
*/ |
| 63 |
const can = useCallback( |
| 64 |
(capability: string): boolean => { |
| 65 |
// 1) Server-built per-user cap map (yatra_* only). |
| 66 |
if (capabilities[capability] === true) { |
| 67 |
return true; |
| 68 |
} |
| 69 |
|
| 70 |
// 2) Team module's enriched userCaps list. |
| 71 |
const userCaps = ( |
| 72 |
window.yatraAdmin as { userCaps?: string[] } | undefined |
| 73 |
)?.userCaps; |
| 74 |
if (Array.isArray(userCaps) && userCaps.includes(capability)) { |
| 75 |
return true; |
| 76 |
} |
| 77 |
|
| 78 |
// 3) Legacy permissions array (kept for back-compat with any |
| 79 |
// code path that pre-dates the capabilities map). |
| 80 |
if (permissions.includes(capability)) { |
| 81 |
return true; |
| 82 |
} |
| 83 |
|
| 84 |
// 4) WP admin fallback — site owners pass everything. |
| 85 |
// |
| 86 |
// Belt-and-suspenders: we check THREE signals in case one of |
| 87 |
// them goes missing on a particular install. All three are |
| 88 |
// injected by AdminAssetsProvider, but defense in depth is |
| 89 |
// cheap and protects against: |
| 90 |
// - server-side filters stripping isWpAdmin from localized data |
| 91 |
// - JSON-encoding edge cases where booleans get coerced |
| 92 |
// - roles arrays that contain admin even when capabilities map |
| 93 |
// was built from a stale $current_user (page-cache + role |
| 94 |
// changes) |
| 95 |
const adm = window.yatraAdmin as |
| 96 |
| { |
| 97 |
isWpAdmin?: unknown; |
| 98 |
roles?: unknown; |
| 99 |
capabilities?: Record<string, unknown>; |
| 100 |
} |
| 101 |
| undefined; |
| 102 |
if (adm) { |
| 103 |
if ( |
| 104 |
adm.isWpAdmin === true || |
| 105 |
adm.isWpAdmin === "1" || |
| 106 |
adm.isWpAdmin === 1 |
| 107 |
) { |
| 108 |
return true; |
| 109 |
} |
| 110 |
if (Array.isArray(adm.roles) && adm.roles.includes("administrator")) { |
| 111 |
return true; |
| 112 |
} |
| 113 |
if (adm.capabilities && adm.capabilities["manage_options"] === true) { |
| 114 |
return true; |
| 115 |
} |
| 116 |
} |
| 117 |
|
| 118 |
// 5) Pro-only check stays for completeness. |
| 119 |
if (capability.startsWith("yatra_pro_") && !isPro) { |
| 120 |
return false; |
| 121 |
} |
| 122 |
|
| 123 |
return false; |
| 124 |
}, |
| 125 |
[capabilities, permissions, isPro], |
| 126 |
); |
| 127 |
|
| 128 |
/** |
| 129 |
* Check if user has a specific role |
| 130 |
* @param role - Role to check |
| 131 |
* @returns True if user has role |
| 132 |
*/ |
| 133 |
const hasRole = (role: string): boolean => { |
| 134 |
return roles.includes(role); |
| 135 |
}; |
| 136 |
|
| 137 |
return { |
| 138 |
can, |
| 139 |
hasRole, |
| 140 |
isPro, |
| 141 |
permissions, |
| 142 |
roles, |
| 143 |
}; |
| 144 |
}; |
| 145 |
|