AccessToken.utils.ts149 lines · main
| 1 | import dayjs from 'dayjs' |
| 2 | |
| 3 | import { PERMISSION_LIST, ScopedAccessTokenPermission } from './AccessToken.constants' |
| 4 | import { |
| 5 | AccessTokenSort, |
| 6 | AccessTokenSortColumn, |
| 7 | AccessTokenSortOrder, |
| 8 | BaseToken, |
| 9 | } from './AccessToken.types' |
| 10 | |
| 11 | export const handleSortChange = ( |
| 12 | currentSort: AccessTokenSort, |
| 13 | column: AccessTokenSortColumn, |
| 14 | setSort: (sort: AccessTokenSort) => void |
| 15 | ) => { |
| 16 | const [currentCol, currentOrder] = currentSort.split(':') as [ |
| 17 | AccessTokenSortColumn, |
| 18 | AccessTokenSortOrder, |
| 19 | ] |
| 20 | if (currentCol === column) { |
| 21 | if (currentOrder === 'asc') { |
| 22 | setSort(`${column}:desc` as AccessTokenSort) |
| 23 | } else { |
| 24 | setSort('created_at:desc') |
| 25 | } |
| 26 | } else { |
| 27 | setSort(`${column}:asc` as AccessTokenSort) |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | export const filterAndSortTokens = <T extends BaseToken>( |
| 32 | tokens: T[] | undefined, |
| 33 | searchString: string, |
| 34 | sort: AccessTokenSort |
| 35 | ): T[] | undefined => { |
| 36 | const filtered = !searchString |
| 37 | ? tokens |
| 38 | : tokens?.filter((token) => token.name.toLowerCase().includes(searchString.toLowerCase())) |
| 39 | |
| 40 | if (!filtered) return filtered |
| 41 | |
| 42 | const [sortCol, sortOrder] = sort.split(':') as [AccessTokenSortColumn, AccessTokenSortOrder] |
| 43 | const orderMultiplier = sortOrder === 'asc' ? 1 : -1 |
| 44 | |
| 45 | return [...filtered].sort((a, b) => { |
| 46 | if (sortCol === 'created_at') { |
| 47 | return (new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) * orderMultiplier |
| 48 | } |
| 49 | if (sortCol === 'last_used_at') { |
| 50 | if (!a.last_used_at && !b.last_used_at) return 0 |
| 51 | if (!a.last_used_at) return 1 |
| 52 | if (!b.last_used_at) return -1 |
| 53 | return ( |
| 54 | (new Date(a.last_used_at).getTime() - new Date(b.last_used_at).getTime()) * orderMultiplier |
| 55 | ) |
| 56 | } |
| 57 | if (sortCol === 'expires_at') { |
| 58 | if (!a.expires_at && !b.expires_at) return 0 |
| 59 | if (!a.expires_at) return 1 |
| 60 | if (!b.expires_at) return -1 |
| 61 | return (new Date(a.expires_at).getTime() - new Date(b.expires_at).getTime()) * orderMultiplier |
| 62 | } |
| 63 | return 0 |
| 64 | }) |
| 65 | } |
| 66 | |
| 67 | export const mapPermissionToFGA = ( |
| 68 | resourceKey: string, |
| 69 | action: string |
| 70 | ): ScopedAccessTokenPermission[] => { |
| 71 | const [scope, resource] = resourceKey.split(':') |
| 72 | const match = PERMISSION_LIST.find( |
| 73 | (p) => p.scope === scope && p.resource === resource && p.action === action |
| 74 | ) |
| 75 | return match ? [match.id as ScopedAccessTokenPermission] : [] |
| 76 | } |
| 77 | |
| 78 | // [kemal]: Not sure how efficient this will be, but it should get permissions from shared types and transform them whenever @supabase/shared-types updates. |
| 79 | export const getResourcePermissions = ( |
| 80 | resourceKey: string |
| 81 | ): Record<string, ScopedAccessTokenPermission[]> => { |
| 82 | const [scope, resource] = resourceKey.split(':') |
| 83 | const result: Record<string, ScopedAccessTokenPermission[]> = { 'no access': [] } |
| 84 | |
| 85 | PERMISSION_LIST.filter((p) => p.scope === scope && p.resource === resource).forEach((p) => { |
| 86 | result[p.action] = [p.id as ScopedAccessTokenPermission] |
| 87 | }) |
| 88 | |
| 89 | if (result['read'] && result['write']) { |
| 90 | result['read-write'] = [...result['read'], ...result['write']] |
| 91 | } |
| 92 | |
| 93 | return result |
| 94 | } |
| 95 | |
| 96 | export const getRealAccess = (resource: string, tokenPermissions: string[]) => { |
| 97 | const resourcePermissions = getResourcePermissions(resource) |
| 98 | const actionTypes = ['read', 'write', 'create', 'delete'] as const |
| 99 | const grantedActions = actionTypes.filter((action) => |
| 100 | resourcePermissions[action]?.some((p) => tokenPermissions.includes(p)) |
| 101 | ) |
| 102 | |
| 103 | if (grantedActions.length === 0) { |
| 104 | return 'no access' |
| 105 | } |
| 106 | |
| 107 | if (grantedActions.length === 1) { |
| 108 | return grantedActions[0] |
| 109 | } |
| 110 | |
| 111 | if ( |
| 112 | grantedActions.length === 2 && |
| 113 | grantedActions[0] === 'read' && |
| 114 | grantedActions[1] === 'write' |
| 115 | ) { |
| 116 | return 'read-write' |
| 117 | } |
| 118 | |
| 119 | return grantedActions.join('-') |
| 120 | } |
| 121 | |
| 122 | export const formatAccessText = (action: string): string => { |
| 123 | switch (action) { |
| 124 | case 'no access': |
| 125 | return 'No access' |
| 126 | default: |
| 127 | return action |
| 128 | .split('-') |
| 129 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) |
| 130 | .join('-') |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | export const getExpirationDate = (key: string): string | undefined => { |
| 135 | switch (key) { |
| 136 | case 'hour': |
| 137 | return dayjs().add(1, 'hours').toISOString() |
| 138 | case 'day': |
| 139 | return dayjs().add(1, 'day').toISOString() |
| 140 | case 'week': |
| 141 | return dayjs().add(7, 'days').toISOString() |
| 142 | case 'month': |
| 143 | return dayjs().add(30, 'days').toISOString() |
| 144 | case 'never': |
| 145 | return undefined |
| 146 | default: |
| 147 | return undefined |
| 148 | } |
| 149 | } |