/** * The grant a practice gave this account's agency, read from the access token's `agency_grants` * claim. The pre-token trigger mints it as a JSON string, `{}` for ordinary staff. Never throws: a * token it cannot read simply holds no grant. */ export function agencyGrantFor( accessToken: string | null | undefined, organizationId: string | undefined, ): { contractorId: string } | undefined { if (!accessToken || !organizationId) return undefined; try { const payload = accessToken.split('.')[1]; if (!payload) return undefined; const claims = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); const raw = claims?.agency_grants; const grants = typeof raw === 'string' ? JSON.parse(raw) : raw; const contractorId = grants?.[organizationId]?.contractorId; return typeof contractorId === 'string' ? { contractorId } : undefined; } catch { return undefined; } } /** * Whether the access token still names this practice in `organization_roles` with at least one role. * `false` is a firm "no longer a member here"; `undefined` means the token cannot say (no token, no * practice, no claim, unreadable), which decides nothing. */ export function tokenNamesPractice( accessToken: string | null | undefined, organizationId: string | undefined, ): boolean | undefined { if (!accessToken || !organizationId) return undefined; try { const payload = accessToken.split('.')[1]; if (!payload) return undefined; const claims = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); const raw = claims?.organization_roles; if (raw === undefined || raw === null) return undefined; const roles = typeof raw === 'string' ? JSON.parse(raw) : raw; const named = roles?.[organizationId]; return Array.isArray(named) && named.length > 0; } catch { return undefined; } } /** The roles the access token lists for this practice, or `undefined` when it cannot say. Never throws. */ export function tokenRolesAtPractice( accessToken: string | null | undefined, organizationId: string | undefined, ): string[] | undefined { if (!accessToken || !organizationId) return undefined; try { const payload = accessToken.split('.')[1]; if (!payload) return undefined; const claims = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); const raw = claims?.organization_roles; const roles = typeof raw === 'string' ? JSON.parse(raw) : raw; const named = roles?.[organizationId]; return Array.isArray(named) ? named.filter((role): role is string => typeof role === 'string') : undefined; } catch { return undefined; } }