role-impersonation.test.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import { safeSql } from '@supabase/pg-meta'
  2. import { describe, expect, it } from 'vitest'
  3. import type { RoleImpersonationState } from './role-impersonation'
  4. import {
  5. getExp1HourFromNow,
  6. getPostgrestClaims,
  7. wrapWithRoleImpersonation,
  8. } from './role-impersonation'
  9. const createBaseUser = (overrides = {}) => ({
  10. id: 'user123',
  11. email: 'test@example.com',
  12. phone: undefined,
  13. role: 'authenticated',
  14. is_anonymous: false,
  15. raw_app_meta_data: { provider: 'email' },
  16. raw_user_meta_data: { name: 'Tester' },
  17. ...overrides,
  18. })
  19. const createTestClaims = (overrides = {}) => ({
  20. ref: 'test-project',
  21. exp: getExp1HourFromNow(),
  22. iat: Math.floor(Date.now() / 1000),
  23. iss: 'https://test-project.supabase.co/auth/v1',
  24. role: 'authenticated' as const,
  25. ...overrides,
  26. })
  27. describe('getExp1HourFromNow', () => {
  28. it('returns a timestamp 1 hour in the future', () => {
  29. const now = Math.floor(Date.now() / 1000)
  30. const exp = getExp1HourFromNow()
  31. expect(exp).toBeGreaterThan(now)
  32. expect(exp).toBeLessThanOrEqual(now + 3600)
  33. })
  34. })
  35. describe('getPostgrestClaims', () => {
  36. describe('native user claims', () => {
  37. it('returns basic user claims', () => {
  38. const claims = getPostgrestClaims('test-project', {
  39. type: 'postgrest',
  40. role: 'authenticated',
  41. userType: 'native',
  42. user: createBaseUser() as any,
  43. })
  44. expect(claims.aud).toBe('authenticated')
  45. expect(claims.email).toBe('test@example.com')
  46. expect(claims.sub).toBe('user123')
  47. })
  48. it('handles missing user data gracefully', () => {
  49. const claims = getPostgrestClaims('test-project', {
  50. type: 'postgrest',
  51. role: 'authenticated',
  52. userType: 'native',
  53. } as any)
  54. expect(claims.role).toBe('authenticated')
  55. expect(claims.ref).toBe('test-project')
  56. })
  57. })
  58. describe('external user claims', () => {
  59. it('returns claims with additional data', () => {
  60. const claims = getPostgrestClaims('test-project', {
  61. type: 'postgrest',
  62. role: 'authenticated',
  63. userType: 'external',
  64. externalAuth: {
  65. sub: 'ext123',
  66. additionalClaims: { foo: 'bar', custom: 'value' },
  67. },
  68. })
  69. expect(claims.sub).toBe('ext123')
  70. expect((claims as any).foo).toBe('bar')
  71. expect((claims as any).custom).toBe('value')
  72. })
  73. it('handles missing additional claims', () => {
  74. const claims = getPostgrestClaims('test-project', {
  75. type: 'postgrest',
  76. role: 'authenticated',
  77. userType: 'external',
  78. externalAuth: {
  79. sub: 'ext123',
  80. },
  81. })
  82. expect(claims.sub).toBe('ext123')
  83. expect((claims as any).foo).toBeUndefined()
  84. })
  85. })
  86. describe('system roles', () => {
  87. it('returns basic claims for anon role', () => {
  88. const claims = getPostgrestClaims('test-project', {
  89. type: 'postgrest',
  90. role: 'anon',
  91. })
  92. expect(claims.role).toBe('anon')
  93. expect(claims.ref).toBe('test-project')
  94. })
  95. it('returns basic claims for service_role', () => {
  96. const claims = getPostgrestClaims('test-project', {
  97. type: 'postgrest',
  98. role: 'service_role',
  99. })
  100. expect(claims.role).toBe('service_role')
  101. expect(claims.ref).toBe('test-project')
  102. })
  103. })
  104. })
  105. describe('wrapWithRoleImpersonation', () => {
  106. const sql = safeSql`select * from colors;`
  107. const ref = 'default'
  108. describe('postgres role (undefined)', () => {
  109. it('returns SQL as is when no role is selected', () => {
  110. const roleImpersonationState: RoleImpersonationState = {
  111. role: undefined,
  112. claims: undefined,
  113. }
  114. const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
  115. expect(result).toBe(sql)
  116. })
  117. })
  118. describe('anon role', () => {
  119. it('wraps SQL with anon user configuration', () => {
  120. const claims = createTestClaims({
  121. iss: 'briven',
  122. ref,
  123. role: 'anon' as const,
  124. })
  125. const roleImpersonationState: RoleImpersonationState = {
  126. role: { type: 'postgrest', role: 'anon' },
  127. claims,
  128. }
  129. const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
  130. expect(result).toContain("set_config('role', 'anon', true)")
  131. expect(result).toContain('request.jwt.claims')
  132. expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
  133. expect(result).toContain(sql)
  134. })
  135. })
  136. describe('authenticated user', () => {
  137. it('wraps SQL with native user configuration', () => {
  138. const claims = createTestClaims({
  139. iss: `https://${ref}.supabase.co/auth/v1`,
  140. role: 'authenticated' as const,
  141. })
  142. const roleImpersonationState: RoleImpersonationState = {
  143. role: {
  144. type: 'postgrest',
  145. role: 'authenticated',
  146. aal: 'aal1',
  147. userType: 'native',
  148. user: {
  149. email: 'test@email.com',
  150. id: 'abc',
  151. providers: [],
  152. },
  153. },
  154. claims,
  155. }
  156. const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
  157. expect(result).toContain("set_config('role', 'authenticated', true)")
  158. expect(result).toContain('request.jwt.claims')
  159. expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
  160. expect(result).toContain(sql)
  161. })
  162. it('wraps SQL with external user configuration', () => {
  163. const claims = createTestClaims({
  164. aal: 'aal1' as const,
  165. aud: 'authenticated',
  166. role: 'authenticated' as const,
  167. session_id: 'ecab6bfd-3707-4e63-9b3b-d37af69449d9',
  168. sub: 'user123',
  169. })
  170. const roleImpersonationState: RoleImpersonationState = {
  171. role: {
  172. type: 'postgrest',
  173. role: 'authenticated',
  174. userType: 'external',
  175. externalAuth: {
  176. sub: 'user123',
  177. additionalClaims: {},
  178. },
  179. aal: 'aal1',
  180. },
  181. claims,
  182. }
  183. const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
  184. expect(result).toContain("set_config('role', 'authenticated', true)")
  185. expect(result).toContain('request.jwt.claims')
  186. expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
  187. expect(result).toContain(sql)
  188. })
  189. })
  190. describe('custom role', () => {
  191. it('wraps SQL with custom role configuration', () => {
  192. const customRole = 'test'
  193. const roleImpersonationState: RoleImpersonationState = {
  194. role: { type: 'custom', role: customRole },
  195. claims: undefined,
  196. }
  197. const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
  198. expect(result).toContain(`set local role '${customRole}'`)
  199. expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
  200. expect(result).toContain(sql)
  201. })
  202. })
  203. })