auth/actor_search_queries.ts

Prefix-based actor search.

Sibling to actor_lookup_queries.ts β€” that resolves a batch of ids to labels; this resolves a partial name to candidate actors. Same row shape (ActorLookupRow) so the labels arc on the consumer side stays uniform.

Case-insensitive LIKE-prefix on actor.name backed by the idx_actor_name_lower functional index. LIKE wildcards (%, _, \) in the query string are escaped at the JS layer so the prefix-only contract is enforceable β€” an unescaped %xyz would widen the surface to full-LIKE and defeat the per-call cap as a binding bound.

Auth filtering β€” scope_ids

When scope_ids is non-empty, the result is filtered to actors holding an active role_grant on one of the supplied scopes. Active means revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()). Stale (revoked / expired) role_grants do not confer membership for search-visibility purposes β€” otherwise a removed student would remain visible to teachers indefinitely.

The DISTINCT on actor.id collapses the case where an actor holds multiple matching role_grants in the supplied scope set into one row.

When scope_ids is omitted (admin-only global path; the handler gates), no role_grant join β€” every actor with a matching prefix is returned.

Info-leak posture (see actor_search_action_specs.ts Β§audit)

- Row shape omits account_id β€” the join is control-plane, not wire-visible. Identical to actor_lookup_queries.ts. - Hard-deleted actors (cascade-orphaned via actor.account_id FK) drop out silently. - No created_at / updated_at projected (timing-oracle avoidance). - Scope-membership uses ANY on the supplied scope_ids array but never surfaces "which scope matched" β€” the result row carries only the actor's wire shape. An attacker passing a random scope_id learns at most "this scope has at least one member matching X" if a match exists, indistinguishable from a no-match search; the caller-passes-scope_ids design (handler trusts the array as a filter, not as authority) means the attacker had to obtain the scope_id from somewhere else first.

Caller bounds limit (the action-spec layer enforces ACTOR_SEARCH_LIMIT_MAX); SQL clamps to that cap on the call site before reaching this query.

Declarations
#

2 declarations

view source

ActorSearchQueryInput
#

auth/actor_search_queries.ts view source

ActorSearchQueryInput

Inputs for query_actor_search.

query

Case-insensitive prefix string. Must be non-empty (action layer enforces min(1)).

type string

scope_ids

When non-empty, restrict to actors holding an active role_grant on one of these scope ids. When empty / omitted, no scope filter is applied β€” the handler is responsible for the admin gate.

type ReadonlyArray<Uuid>

limit

Maximum rows to return. The handler clamps to ACTOR_SEARCH_LIMIT_MAX.

type number

query_actor_search
#

Imported by
#