Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/supabase

src/list-users.ts

110 lines · 3.3 KB · TypeScript
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,
	})
}