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
}
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<AuthUserSummary>; 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<string, string> }>(
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<SupabaseAuthInput> & Record<string, unknown> = {},
) {
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,
})
}