UserRepository¶
The adapter between CRUDAuth's logical field names and your user model's actual columns.
Configure the mapping with column_map= on CRUDAuth.
crudauth.repository.UserRepository
¶
UserRepository(
model: type[Any],
column_map: dict[str, str] | None = None,
register_extra_fields: Iterable[str] | None = None,
login_fields: Iterable[str] | None = None,
recovery: str | None = "email",
)
Logical-field data access over the app's user model.
The single boundary to the user table: other layers read and write through
these methods (get_by_email, resolve_login, create, update,
token_version, ...), addressing logical fields (email,
hashed_password, ...) that the column_map resolves to real columns.
Reachable as auth.repo.
Example
is_unique_column
¶
Whether the resolved column for logical is single-field unique.
Detects column-level unique=True, a single-column UniqueConstraint,
and a single-column unique Index - all through column_map (the
resolved column name). Composite uniqueness does NOT count: a multi-column
unique key doesn't make one field a safe first-match-wins login key, so a
composite-only field is treated as non-unique (and the construction check
raises for it).
get
¶
Read a logical field off user, or default if the column is absent.
set_field
¶
Set a logical field on user by its resolved column name.
get_by_id
async
¶
Fetch the user by primary key, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
AsyncSession
|
Active async session. |
required |
user_id
|
Any
|
PK value; coerced to the column's Python type first (so a
string |
required |
Returns:
| Type | Description |
|---|---|
Any | None
|
The user row, or |
get_by_field
async
¶
Fetch the user whose logical field equals value, or None.
Email is canonicalized before matching (the column is stored canonical); every other field matches as-is.
get_by_email
async
¶
Fetch the user by (canonicalized) email, or None.
get_by_username
async
¶
Fetch the user by username, or None.
resolve_login
async
¶
Resolve a login identifier against login_fields, in order; first match wins.
Replaces the old @-heuristic: the contract decides which fields a login
identifier may match. Safe because every login field is asserted unique at
construction, so a match is unambiguous.
get_by_oauth
async
¶
Look up by {provider}_id.
Note
Assumes provider is a validated/registered provider name (the
OAuth router checks it before this is reached) - it builds an
attribute name from the argument, so an unvalidated value would
probe arbitrary columns (it returns None for any column the
model lacks, so the blast radius is a missed lookup, not a leak).
gated_register_fields
¶
Which of schema_fields are crudauth privileged fields (always dropped).
droppable_register_fields
¶
Which of schema_fields map to a real model column but are not
privileged and not opted in, so registration silently drops them.
These are the fields a developer most likely expects to persist (e.g. a
full_name column they added to register_schema) and won't, until
they add the name to register_extra_fields.
filter_registration_data
¶
Keep only the allowlisted registration fields; drop everything else.
A field survives only if it is in :data:REGISTRATION_ALLOWED_FIELDS or
was opted in via register_extra_fields (matched by logical or mapped
column name) - and is never one of crudauth's privileged logical fields
(is_superuser, email_verified, hashed_password, the oauth
linkage, the PK), which stay gated even if mistakenly opted in. Unknown
app columns (role, credits, ...) are dropped unless explicitly
opted in, so a custom register_schema can't turn /register into a
privilege-escalation or mass-assignment endpoint.
filter_provisioning_data
¶
Keep only app columns from a new_user_fields callback.
Drops any key that is a crudauth logical field (by logical or mapped
column name) and logs a warning, so the callback can fill the app's own
columns at signup but can never override identity, privilege, or state
crudauth owns (email, hashed_password, is_superuser,
email_verified, the oauth linkage, the PK). The dropped key keeps
crudauth's authoritative value.
Note
The callback runs per signup, so a misconfigured one would drop the
same key on every registration. Each distinct dropped field is warned
only ONCE per process (deduped across the constant new_user_defaults
and the runtime callback) so a standing misconfiguration can't flood
the logs.
create
async
¶
Insert a user from logical-field data and return the row.
Note
Owns the transaction boundary - commits and refreshes on the passed-in session. Apps using a request-scoped "commit at the end" pattern should know auth writes commit eagerly.
Note
Email is canonicalized off the resolved column: kwargs is keyed
by actual column names, so a column_map that renames email
would otherwise be stored un-normalized.
update
async
¶
Apply logical-field data to user and return it.
Note
Commits and refreshes eagerly on the passed-in session (see create).
recovery_verified
¶
Whether the contract's recovery factor is proven controlled.
Email recovery reads email_verified; another factor reads
{factor}_verified; recovery=None is never verified. This is the
general meaning of "verified" - email is the special case, not the concept.
mark_recovery_verified
async
¶
Set the contract's recovery-factor verified flag (the verify-flow write).
token_version
¶
The user's credential epoch (0 if the model has no such column).
increment_token_version
async
¶
Bump the credential epoch, revoking outstanding bearer tokens.
A no-op when the model has no token_version column (bearer tokens
then simply aren't epoch-revocable; the limitation is documented).
to_dict
¶
Project a user row onto the logical contract (for hooks).
Note
Contract-only by design - the dict holds the crudauth logical fields
(id, email, ...), not your app's own columns. A hook that
needs full_name should re-load the row via db using the
id, not expect it in this dict.