import { projectAuth } from './setup.ts' import { asLive, projectRequest } from './client.ts' import { clampInt, isRecord, optionalString, type SupabaseAuthInput, } from './types.ts' export type AuthUserSummary = { id: string role?: string createdAt?: string lastSignInAt?: string emailConfirmed?: boolean phoneConfirmed?: boolean bannedUntil?: string isAnonymous?: boolean email?: string phone?: string } /** * List Auth users for a Supabase project (service role). * Omits email/phone unless `includeIdentifiers: true`. * * @param input.projectRef - Supabase project ref. * @param input.page - Page number (default 1). * @returns `{ items, count, page }` of auth user summaries. * * @example * import listUsers from 'kody:@kody/supabase/list-users' * const { items } = await listUsers({ projectRef: 'abcdefghijklmnop', page: 1 }) */ export function summarizeAuthUser( value: unknown, includeIdentifiers = false, ): AuthUserSummary | null { if (!isRecord(value)) return null const id = optionalString(value.id, 'id') if (!id) return null const summary: AuthUserSummary = { id, role: optionalString(value.role, 'role'), createdAt: optionalString(value.created_at, 'created_at'), lastSignInAt: optionalString(value.last_sign_in_at, 'last_sign_in_at'), emailConfirmed: value.email_confirmed_at != null, phoneConfirmed: value.phone_confirmed_at != null, bannedUntil: optionalString(value.banned_until, 'banned_until'), isAnonymous: value.is_anonymous === true, } if (includeIdentifiers) { summary.email = optionalString(value.email, 'email') summary.phone = optionalString(value.phone, 'phone') } return summary } /** * List Auth users for a project (service role). Omits email/phone unless * `includeIdentifiers: true`. * @example * import listUsers from 'kody:@kody/supabase/list-users' * const { items } = await listUsers({ projectRef: 'abcdefghijklmnop', page: 1 }) */ export async function listUsers( input: SupabaseAuthInput & { page?: number perPage?: number includeIdentifiers?: boolean }, ): Promise<{ items: Array; count: number; page: number }> { const page = clampInt(input.page, 1, 10_000, 1, 'page') const perPage = clampInt(input.perPage, 1, 200, 50, 'perPage') const live = asLive<{ data: unknown; headers: Record }>( await projectRequest({ auth: projectAuth(input), method: 'GET', path: '/auth/v1/admin/users', query: { page, per_page: perPage }, }), ) const payload = live.data const rows = Array.isArray(payload) ? payload : isRecord(payload) && Array.isArray(payload.users) ? payload.users : [] const items = rows .map((row) => summarizeAuthUser(row, input.includeIdentifiers === true)) .filter((item): item is AuthUserSummary => item !== null) const total = Number(live.headers['x-total-count'] ?? items.length) return { items, count: Number.isFinite(total) ? total : items.length, page, } } /** * List Auth users for a project (service role). * @example * import listUsers from 'kody:@kody/supabase/list-users' * const result = await listUsers({ projectRef: 'abcdefghijklmnop' }) */ export default async function listUsersEntrypoint( params: Partial & Record = {}, ) { return await listUsers({ account: optionalString(params.account, 'account'), secretName: optionalString(params.secretName, 'secretName'), serviceRoleSecretName: optionalString( params.serviceRoleSecretName, 'serviceRoleSecretName', ), projectRef: optionalString(params.projectRef, 'projectRef'), projectUrl: optionalString(params.projectUrl, 'projectUrl'), page: typeof params.page === 'number' ? params.page : undefined, perPage: typeof params.perPage === 'number' ? params.perPage : undefined, includeIdentifiers: params.includeIdentifiers === true, }) }