Ctrl K
ComunidadGuías…

tests/channel-read-history-rbac.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { Database } from '../supabase/functions/_backend/utils/supabase.types.ts'
import { randomUUID } from 'node:crypto'
import { createClient } from '@supabase/supabase-js'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import {
  executeSQL,
  fetchTestRequest,
  getAuthHeaders,
  getEndpointUrl,
  getSupabaseClient,
  resetAndSeedAppData,
  resetAppData,
  SUPABASE_ANON_KEY,
  SUPABASE_BASE_URL,
  USER_ID,
} from './test-utils.ts'

const APPNAME = `com.channel_history_rbac_${randomUUID().replaceAll('-', '').slice(0, 12)}`

let authHeaders: Record<string, string>
let deniedChannelId: number
let allowedChannelId: number

function userClient() {
  return createClient<Database>(SUPABASE_BASE_URL, SUPABASE_ANON_KEY, {
    auth: { persistSession: false },
    global: { headers: { Authorization: authHeaders.Authorization ?? authHeaders.authorization } },
  })
}

async function readDeployHistory(channelId: number) {
  const { data, error } = await userClient()
    .from('deploy_history')
    .select('id, channel_id')
    .eq('app_id', APPNAME)
    .eq('channel_id', channelId)
  expect(error).toBeNull()
  return data ?? []
}

function postJson(path: string, body: Record<string, unknown>) {
  return fetchTestRequest(getEndpointUrl(path), {
    method: 'POST',
    retryUnsafe: true,
    headers: { ...authHeaders, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  })
}

async function setHistoryOverride(channelId: number, isAllowed: boolean | null) {
  if (isAllowed === null) {
    await executeSQL(
      `DELETE FROM public.channel_permission_overrides
       WHERE principal_type = public.rbac_principal_user()
         AND principal_id = $1
         AND channel_id = $2
         AND permission_key = public.rbac_perm_channel_read_history()`,
      [USER_ID, channelId],
    )
    return
  }
  await executeSQL(
    `INSERT INTO public.channel_permission_overrides (
       principal_type, principal_id, channel_id, permission_key, is_allowed
     ) VALUES (
       public.rbac_principal_user(), $1, $2, public.rbac_perm_channel_read_history(), $3
     )
     ON CONFLICT (principal_type, principal_id, channel_id, permission_key)
     DO UPDATE SET is_allowed = excluded.is_allowed`,
    [USER_ID, channelId, isAllowed],
  )
}

beforeAll(async () => {
  await resetAndSeedAppData(APPNAME)
  authHeaders = await getAuthHeaders()

  const { data: channels, error } = await getSupabaseClient()
    .from('channels')
    .select('id, name')
    .eq('app_id', APPNAME)
    .in('name', ['production', 'beta'])
  expect(error).toBeNull()
  deniedChannelId = channels!.find(channel => channel.name === 'production')!.id
  allowedChannelId = channels!.find(channel => channel.name === 'beta')!.id
})

afterAll(async () => {
  if (deniedChannelId)
    await setHistoryOverride(deniedChannelId, null)
  await resetAppData(APPNAME)
})

describe('channel.read_history enforcement', () => {
  it('lets an app reader read channel history without an override', async () => {
    expect((await readDeployHistory(deniedChannelId)).length).toBeGreaterThan(0)

    const response = await postJson('/private/channel_stats', { app_id: APPNAME, channel_id: deniedChannelId, days: 7 })
    const body = await response.json() as Record<string, unknown>
    expect(response.status, JSON.stringify(body)).toBe(200)
    expect(Array.isArray(body.deploymentHistory)).toBe(true)
  })

  it('hides history of a channel with a channel.read_history deny override', async () => {
    await setHistoryOverride(deniedChannelId, false)
    try {
      expect(await readDeployHistory(deniedChannelId)).toEqual([])
      // Other channels of the same app stay readable.
      expect((await readDeployHistory(allowedChannelId)).length).toBeGreaterThan(0)

      const denied = await postJson('/private/channel_stats', { app_id: APPNAME, channel_id: deniedChannelId, days: 7 })
      const deniedBody = await denied.json() as { error?: string }
      expect(denied.status, JSON.stringify(deniedBody)).toBe(403)
      expect(deniedBody.error).toBe('channel_access_denied')

      const allowed = await postJson('/private/channel_stats', { app_id: APPNAME, channel_id: allowedChannelId, days: 7 })
      expect(allowed.status).toBe(200)

      const installStats = await postJson('/private/bundle_install_stats', { app_id: APPNAME, channel_id: deniedChannelId, days: 7 })
      const installStatsBody = await installStats.json() as { error?: string }
      expect(installStats.status, JSON.stringify(installStatsBody)).toBe(403)
      expect(installStatsBody.error).toBe('channel_access_denied')

      type ReleaseLiveBody = { error?: string, channel?: { id: number } | null, channels?: Array<{ id: number }>, recent_deployments?: Array<{ channel_id: number | null }> }
      const releaseLive = await postJson('/private/release_live', { app_id: APPNAME })
      const releaseLiveBody = await releaseLive.json() as ReleaseLiveBody
      expect(releaseLive.status, JSON.stringify(releaseLiveBody)).toBe(200)
      expect(releaseLiveBody.channel?.id).not.toBe(deniedChannelId)
      expect((releaseLiveBody.channels ?? []).map(channel => channel.id)).not.toContain(deniedChannelId)
      expect((releaseLiveBody.recent_deployments ?? []).some(deployment => deployment.channel_id === deniedChannelId)).toBe(false)

      const releaseLiveDenied = await postJson('/private/release_live', { app_id: APPNAME, channel_id: deniedChannelId })
      const releaseLiveDeniedBody = await releaseLiveDenied.json() as ReleaseLiveBody
      expect(releaseLiveDenied.status, JSON.stringify(releaseLiveDeniedBody)).toBe(403)
      expect(releaseLiveDeniedBody.error).toBe('channel_access_denied')

      // History of permitted channels is still returned.
      const releaseLiveAllowed = await postJson('/private/release_live', { app_id: APPNAME, channel_id: allowedChannelId })
      const releaseLiveAllowedBody = await releaseLiveAllowed.json() as ReleaseLiveBody
      expect(releaseLiveAllowed.status, JSON.stringify(releaseLiveAllowedBody)).toBe(200)
      expect(releaseLiveAllowedBody.channel?.id).toBe(allowedChannelId)
      expect((releaseLiveAllowedBody.recent_deployments ?? []).some(deployment => deployment.channel_id === allowedChannelId)).toBe(true)
    }
    finally {
      await setHistoryOverride(deniedChannelId, null)
    }

    expect((await readDeployHistory(deniedChannelId)).length).toBeGreaterThan(0)
  })
})