| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import { getImpersonationSQL, type SafeSqlFragment } from '@supabase/pg-meta'
- import { uuidv4 } from './helpers'
- import type { User } from '@/data/auth/users-infinite-query'
- import { RoleImpersonationState as ValtioRoleImpersonationState } from '@/state/role-impersonation-state'
- type PostgrestImpersonationRole =
- | {
- type: 'postgrest'
- role: 'anon'
- }
- | {
- type: 'postgrest'
- role: 'service_role'
- }
- | {
- type: 'postgrest'
- role: 'authenticated'
- userType: 'native'
- user?: User
- aal?: 'aal1' | 'aal2'
- }
- | {
- type: 'postgrest'
- role: 'authenticated'
- userType: 'external'
- externalAuth?: {
- sub: string
- additionalClaims?: Record<string, any>
- }
- aal?: 'aal1' | 'aal2'
- }
- export type PostgrestRole = PostgrestImpersonationRole['role']
- type CustomImpersonationRole = {
- type: 'custom'
- role: string
- }
- export type ImpersonationRole = PostgrestImpersonationRole | CustomImpersonationRole
- export function getExp1HourFromNow() {
- return Math.floor((Date.now() + 60 * 60 * 1000) / 1000)
- }
- export function getPostgrestClaims(projectRef: string, role: PostgrestImpersonationRole) {
- const exp = getExp1HourFromNow()
- const nowTimestamp = Math.floor(Date.now() / 1000)
- if (role.role === 'authenticated') {
- // Supabase native auth case
- if (role.userType === 'native' && role.user) {
- const user = role.user
- return {
- aal: role.aal ?? 'aal1',
- amr: [{ method: 'password', timestamp: nowTimestamp }],
- app_metadata: user.raw_app_meta_data,
- aud: 'authenticated',
- email: user.email,
- exp,
- iat: nowTimestamp,
- iss: `https://${projectRef}.supabase.co/auth/v1`,
- phone: user.phone,
- role: user.role ?? role.role,
- session_id: uuidv4(),
- sub: user.id,
- user_metadata: user.raw_user_meta_data,
- is_anonymous: user.is_anonymous,
- }
- }
- // External auth case
- if (role.userType === 'external' && role.externalAuth) {
- return {
- aal: role.aal ?? 'aal1',
- aud: 'authenticated',
- exp,
- iat: nowTimestamp,
- role: 'authenticated',
- session_id: uuidv4(),
- sub: role.externalAuth.sub,
- ...role.externalAuth.additionalClaims,
- }
- }
- }
- return {
- iss: 'supabase',
- ref: projectRef,
- role: role.role,
- iat: nowTimestamp,
- exp,
- }
- }
- export type RoleImpersonationState = Pick<ValtioRoleImpersonationState, 'role' | 'claims'>
- export function wrapWithRoleImpersonation(
- sql: SafeSqlFragment,
- state?: RoleImpersonationState
- ): SafeSqlFragment {
- const { role, claims } = state ?? { role: undefined, claims: undefined }
- if (role === undefined) return sql
- const unexpiredClaims =
- claims !== undefined ? { ...claims, exp: getExp1HourFromNow() } : undefined
- const impersonationSql = getImpersonationSQL({ role: role, unexpiredClaims, sql })
- return impersonationSql
- }
- function encodeText(data: string) {
- return new TextEncoder().encode(data)
- }
- function encodeBase64Url(data: ArrayBuffer | Uint8Array | string): string {
- return btoa(
- String.fromCharCode(...new Uint8Array(typeof data === 'string' ? encodeText(data) : data))
- )
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=+$/, '')
- }
- function genKey(rawKey: string) {
- return window.crypto.subtle.importKey(
- 'raw',
- encodeText(rawKey),
- { name: 'HMAC', hash: 'SHA-256' },
- false,
- ['sign', 'verify']
- )
- }
- async function createToken(jwtPayload: object, key: string) {
- const headerAndPayload =
- encodeBase64Url(encodeText(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))) +
- '.' +
- encodeBase64Url(encodeText(JSON.stringify(jwtPayload)))
- const signature = encodeBase64Url(
- new Uint8Array(
- await window.crypto.subtle.sign(
- { name: 'HMAC' },
- await genKey(key),
- encodeText(headerAndPayload)
- )
- )
- )
- return `${headerAndPayload}.${signature}`
- }
- export function getRoleImpersonationJWT(
- projectRef: string,
- jwtSecret: string,
- role: PostgrestImpersonationRole
- ): Promise<string> {
- const claims = {
- ...getPostgrestClaims(projectRef, role),
- exp: getExp1HourFromNow(),
- }
- return createToken(claims, jwtSecret)
- }
|