AccessToken.utils.ts 4.3 KB

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