auth/account_queries.ts

Account and actor database queries.

Provides CRUD operations for the account and actor tables. For v1, every account has exactly one actor (1:1).

Declarations
#

14 declarations

view source

AdminAccountListOptions
#

query_account_by_email
#

auth/account_queries.ts view source

(deps: QueryDeps, email: string): Promise<Account | undefined>

Find an account by email (case-insensitive).

deps

email

type string

returns

Promise<Account | undefined>

query_account_by_id
#

query_account_by_username
#

auth/account_queries.ts view source

(deps: QueryDeps, username: string): Promise<Account | undefined>

Find an account by username (case-insensitive).

deps

username

type string

returns

Promise<Account | undefined>

query_account_by_username_or_email
#

auth/account_queries.ts view source

(deps: QueryDeps, input: string): Promise<Account | undefined>

Find an account by username or email.

If the input contains @, tries email lookup first then username. Otherwise tries username first then email. This supports a single login field that accepts either format.

deps

query dependencies

input

username or email address

type string

returns

Promise<Account | undefined>

the matching account, or undefined

query_account_has_any
#

query_actor_by_id
#

query_actors_by_account
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Actor[]>

List every actor on an account, ordered by created_at.

Used by resolve_acting_actor to resolve the acting actor for a request: 1 actor picks transparently, multiple require an explicit acting field on the request payload. For lookups by id, use query_actor_by_id instead.

deps

account_id

type string

returns

Promise<Actor[]>

query_admin_account_list
#

auth/account_queries.ts view source

(deps: QueryDeps, options?: AdminAccountListOptions | undefined): Promise<{ account: { id: string & $brand<"Uuid">; username: string; ... 4 more ...; updated_by: (string & $brand<...>) | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]>

List accounts with their actors, active role_grants, and pending inbound role_grant offers for admin display.

Pages the accounts query (one round-trip), then fans out three parallel lookups scoped to the page's account_ids (one round-trip). The role_grants and offers queries use a subquery on actor.account_id so the page bound pushes through to the DB without round-tripping actor.ids back to the application. Pending offers surface the "offer pending — awaiting acceptance" UX; message is intentionally excluded (cross-admin visibility of grantor notes would expand beyond what the audit log discloses).

deps

query dependencies

options?

optional {limit, offset}. Default limit is ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT; pass limit: null to disable.

type AdminAccountListOptions | undefined
optional

returns

Promise<{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<"Uuid">) | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]>

admin account entries sorted by creation date (oldest first)

query_create_account
#

query_create_account_with_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, input: CreateAccountInput): Promise<{ account: Account; actor: Actor; }>

Create an account and its actor in a single operation.

For v1, every account gets exactly one actor with the same name as the username.

deps

query dependencies

input

the account fields

returns

Promise<{ account: Account; actor: Actor; }>

the created account and actor

query_create_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string, name: string): Promise<Actor>

Create a new actor for an account.

deps

query dependencies

account_id

the owning account

type string

name

display name (defaults to account username)

type string

returns

Promise<Actor>

the created actor

query_delete_account
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<boolean>

Delete an account. Cascades to actors, role_grants, sessions, and tokens.

deps

id

type string

returns

Promise<boolean>

query_update_account_password
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, password_hash: string, updated_by: string | null, expected_hash: string): Promise<boolean>

Update the password hash for an account, conditional on the current stored hash matching expected_hash — the verify-write atomic guard.

The condition closes the race where two concurrent password changes both verify against the pre-update hash (loaded by the authorization phase outside the route's transaction) and would otherwise both UPDATE, silently clobbering whichever lands first. With the conditional WHERE, the second UPDATE matches zero rows; the route reads the boolean return and surfaces 401 instead of pretending success.

Pass the same hash the verify ran against — typically ctx.account.password_hash from the request context.

deps

id

type string

password_hash

type string

updated_by

type string | null

expected_hash

type string

returns

Promise<boolean>

true if the row was updated, false if expected_hash no longer matched (concurrent change won — caller should treat as a stale-credential failure).

Depends on
#

Imported by
#