pub struct AuthChallenge {
pub id: i64,
pub user_id: ForeignKey<AuthUser>,
pub purpose: String,
pub secret_hash: String,
pub expires_at: DateTime<Utc>,
pub attempts: i32,
pub used_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}Expand description
One pending challenge. The plaintext (6-digit code or opaque token) is
never stored — only base64(sha256(plaintext)). Single-use (used_at),
time-boxed (expires_at), and (for codes) attempt-capped (attempts).
Fields§
§id: i64§user_id: ForeignKey<AuthUser>§purpose: String§secret_hash: String§expires_at: DateTime<Utc>§attempts: i32§used_at: Option<DateTime<Utc>>§created_at: DateTime<Utc>Implementations§
Source§impl AuthChallenge
impl AuthChallenge
pub const ID: IntCol<Self> = auth_challenge::ID
pub const USER_ID: ForeignKeyCol<Self> = auth_challenge::USER_ID
pub const PURPOSE: StrCol<Self> = auth_challenge::PURPOSE
pub const SECRET_HASH: StrCol<Self> = auth_challenge::SECRET_HASH
pub const EXPIRES_AT: DateTimeCol<Self> = auth_challenge::EXPIRES_AT
pub const ATTEMPTS: IntCol<Self> = auth_challenge::ATTEMPTS
pub const USED_AT: NullableDateTimeCol<Self> = auth_challenge::USED_AT
pub const CREATED_AT: DateTimeCol<Self> = auth_challenge::CREATED_AT
Source§impl AuthChallenge
impl AuthChallenge
Sourcepub async fn issue(
user_id: i64,
purpose: &str,
plaintext: &str,
ttl: Duration,
) -> Result<AuthChallenge, AuthError>
pub async fn issue( user_id: i64, purpose: &str, plaintext: &str, ttl: Duration, ) -> Result<AuthChallenge, AuthError>
Create and persist a new challenge for user_id.
The plaintext (a 6-digit code or opaque token generated by the
caller) is not stored — only its hash_secret digest reaches
the database. The challenge is valid for ttl; attempts starts
at 0; used_at is None.
§Called by
Tasks 8 (email verify) and 9 (password reset) call this.
Sourcepub fn is_live(&self) -> bool
pub fn is_live(&self) -> bool
true when the challenge has not been consumed (used_at IS NULL)
and has not expired (expires_at > now). The in-memory check
complements the SQL USED_AT IS NULL filter: expired-but-unused
rows are filtered out by this method even if they slipped through
(e.g. a race where expires_at passed between the query and the
caller).
Sourcepub async fn find_active_for_user(
user_id: i64,
purpose: &str,
) -> Result<Option<AuthChallenge>, AuthError>
pub async fn find_active_for_user( user_id: i64, purpose: &str, ) -> Result<Option<AuthChallenge>, AuthError>
Return the most-recently-issued active challenge for (user_id, purpose), or None if no unused, unexpired row exists.
“Active” = USED_AT IS NULL in SQL and is_live() in Rust
(the Rust guard catches challenges whose expires_at passed between
the query and this call).
§ORM note
auth_challenge::USED_AT.is_null() is the SQL IS NULL predicate
on the nullable used_at column. order_by takes a single
OrderExpr<T> (not a slice) and can be called multiple times to
append clauses. Mirror the single-arg idiom from umbral-tasks.
Sourcepub async fn find_active_by_secret(
plaintext: &str,
purpose: &str,
) -> Result<Option<AuthChallenge>, AuthError>
pub async fn find_active_by_secret( plaintext: &str, purpose: &str, ) -> Result<Option<AuthChallenge>, AuthError>
Return the challenge whose stored hash matches hash_secret(plaintext)
for the given purpose, or None if no active match exists.
Used by the verification handler: the handler receives the raw plaintext (code or token), this method hashes it and looks up the digest — the plaintext never touches the WHERE clause.
Sourcepub async fn mark_used(&self) -> Result<(), AuthError>
pub async fn mark_used(&self) -> Result<(), AuthError>
Stamp used_at with the current time. After this call,
find_active_for_user and find_active_by_secret will not return
this challenge. Idempotent in the DB sense (a second call just
overwrites with a slightly later timestamp), though callers should
treat the challenge as finished after the first call.
Sourcepub async fn bump_attempts(&self) -> Result<(), AuthError>
pub async fn bump_attempts(&self) -> Result<(), AuthError>
Increment attempts by 1. Call this on each failed verification
attempt so the caller can gate on a maximum before locking the
challenge. The in-memory self.attempts is NOT updated; re-fetch
the row to get the current count.
The increment is an atomic server-side SET attempts = attempts + 1
(via update_expr) — two concurrent calls cannot both read the same
stale value and both write the same result, so the attempt cap used
by the brute-force guard in Task 8 cannot be under-counted.
Trait Implementations§
Source§impl Clone for AuthChallenge
impl Clone for AuthChallenge
Source§fn clone(&self) -> AuthChallenge
fn clone(&self) -> AuthChallenge
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for AuthChallenge
impl Debug for AuthChallenge
Source§impl<'de> Deserialize<'de> for AuthChallenge
impl<'de> Deserialize<'de> for AuthChallenge
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl<'a, R: Row> FromRow<'a, R> for AuthChallengewhere
&'a str: ColumnIndex<R>,
i64: Decode<'a, R::Database> + Type<R::Database>,
ForeignKey<AuthUser>: Decode<'a, R::Database> + Type<R::Database>,
String: Decode<'a, R::Database> + Type<R::Database>,
DateTime<Utc>: Decode<'a, R::Database> + Type<R::Database>,
i32: Decode<'a, R::Database> + Type<R::Database>,
Option<DateTime<Utc>>: Decode<'a, R::Database> + Type<R::Database>,
impl<'a, R: Row> FromRow<'a, R> for AuthChallengewhere
&'a str: ColumnIndex<R>,
i64: Decode<'a, R::Database> + Type<R::Database>,
ForeignKey<AuthUser>: Decode<'a, R::Database> + Type<R::Database>,
String: Decode<'a, R::Database> + Type<R::Database>,
DateTime<Utc>: Decode<'a, R::Database> + Type<R::Database>,
i32: Decode<'a, R::Database> + Type<R::Database>,
Option<DateTime<Utc>>: Decode<'a, R::Database> + Type<R::Database>,
Source§impl HydrateRelated for AuthChallenge
impl HydrateRelated for AuthChallenge
Source§fn fk_id_for(&self, field_name: &str) -> Option<Value>
fn fk_id_for(&self, field_name: &str) -> Option<Value>
field_name,
or None if the field doesn’t exist on this model or is not a FK. Read moreSource§fn hydrate_fk(&mut self, field_name: &str, row: &Value)
fn hydrate_fk(&mut self, field_name: &str, row: &Value)
ForeignKey<U>.resolved for the field named field_name by
deserialising row as the target model type. Read moreSource§fn set_m2m_parent_ids(&mut self)
fn set_m2m_parent_ids(&mut self)
parent_id cache on every M2M<U> field this model
owns. Closes the second BUG-16 gap: without this, m2m.add(&t)
silently writes a junction row with parent_id = 0 because the
macro skips M2M fields in the FromRow decode path. Read moreSource§fn pk_as_json(&self) -> Option<Value>
fn pk_as_json(&self) -> Option<Value>
serde_json::Value, whatever
the PK type — i64, String, uuid::Uuid, a custom newtype.
The relation-hydration paths (prefetch_related, reverse-FK and
reverse-OneToOne collection) bucket children by the parent’s PK,
and keying those buckets on a Value (canonicalised via
crate::orm::pk_key) lets UUID- and slug-PK models flow through
too, not just i64. Read moreSource§fn set_m2m_resolved_json(&mut self, field_name: &str, rows: Vec<Value>)
fn set_m2m_resolved_json(&mut self, field_name: &str, rows: Vec<Value>)
M2M<U>
field’s resolved slot. Called by QuerySet::prefetch_related
(gap #19) after a batched JOIN through the junction table
returns one Vec per parent. Read moreSource§fn set_reverse_fk_resolved_json(&mut self, field_name: &str, rows: Vec<Value>)
fn set_reverse_fk_resolved_json(&mut self, field_name: &str, rows: Vec<Value>)
ReverseSet<C> field’s resolved slot. Counterpart
to set_m2m_resolved_json but for reverse-FK collections
(one parent, many children pointing at it via a FK column). Read moreSource§fn set_one_to_one_resolved_json(&mut self, field_name: &str, row: Option<Value>)
fn set_one_to_one_resolved_json(&mut self, field_name: &str, row: Option<Value>)
set_reverse_fk_resolved_json. Called by prefetch_related
with Some(child_json) when the runtime FK lookup found
exactly one matching child, or None when no child matched
(the slot still flips is_loaded() to true). Read moreSource§fn take_pending_m2m_into(&mut self, _dest: &mut Self)
fn take_pending_m2m_into(&mut self, _dest: &mut Self)
self into dest,
field by field. The typed create() builds its INSERT from the
caller’s instance, then reads a fresh row back from the DB
(carrying the autoincremented PK) — the pending ids staged by the
Form derive live on the caller’s instance, not the readback row.
This hook transfers them across so write_pending_m2m on the
readback row (which has the real parent_id seeded) finds them.
Default: no-op for models with no M2M fields.Source§fn write_pending_m2m<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<(), WriteError>> + Send + 'a>>
fn write_pending_m2m<'a>( &'a mut self, ) -> Pin<Box<dyn Future<Output = Result<(), WriteError>> + Send + 'a>>
parent_id + junction_table
(seeded by set_m2m_parent_ids) and the pending child ids, and
calls set_junction_dynamic. Default: no-op for models with no
M2M fields. Read moreSource§impl Model for AuthChallenge
impl Model for AuthChallenge
Source§const NAME: &'static str = "AuthChallenge"
const NAME: &'static str = "AuthChallenge"
Source§const TABLE: &'static str = "auth_challenge"
const TABLE: &'static str = "auth_challenge"
snake_case of the struct name unless #[umbral(table = "...")]
overrides it.Source§const APP_LABEL: &'static str = "app"
const APP_LABEL: &'static str = "app"
Source§const DISPLAY: &'static str = "AuthChallenge"
const DISPLAY: &'static str = "AuthChallenge"
Self::NAME. Read moreSource§const ICON: &'static str = "database"
const ICON: &'static str = "database"
"database". Any valid Lucide icon name works; unknown
names are silently ignored by Lucide at render time. Read moreSource§const DATABASE: Option<&'static str> = ::core::option::Option::None
const DATABASE: Option<&'static str> = ::core::option::Option::None
AppBuilder::database(...). None (the
default) means “use whatever the owning plugin chose via
Plugin::database(), or \"default\" if neither side
overrode.” Read moreSource§const SINGLETON: bool = false
const SINGLETON: bool = false
true, the admin auto-redirects the
list view to the (sole) row’s edit form, hides the “+ New”
button, and surfaces the model as a settings-style screen.
The single-row settings model pattern. Set via
#[umbral(singleton)] on the struct. Closes BUG-9 in
bugs/tests/testBugs.md. Read moreSource§const SOFT_DELETE: bool = false
const SOFT_DELETE: bool = false
#[umbral(soft_delete)] on the struct. When true, the
framework treats this model as having a deleted_at: Option<DateTime<Utc>> column (which the user MUST declare
— derive macros can’t add fields to the input struct), and: Read moreSource§const UNIQUE_TOGETHER: &'static [&'static [&'static str]]
const UNIQUE_TOGETHER: &'static [&'static [&'static str]]
#[umbral(unique_together = [["a", "b"]])]. Closes BUG-6 in
bugs/tests/testBugs.md. Default empty; the migration engine
emits one UNIQUE (col1, col2) clause per inner group on
CREATE TABLE.Source§const INDEXES: &'static [&'static [&'static str]]
const INDEXES: &'static [&'static [&'static str]]
#[umbral(indexes = [["tenant_id", "created_at"]])]. Closes
BUG-7. Default empty; the migration engine emits
CREATE INDEX IF NOT EXISTS idx_<table>_<col1>_<col2> after
the CREATE TABLE. Single-column indexes stay on the field
attribute (#[umbral(index)]).Source§const ORDERING: &'static [(&'static str, bool)]
const ORDERING: &'static [(&'static str, bool)]
ORDER BY clause, applied when a QuerySet terminates
without an explicit order_by. Each tuple is (column_name, is_descending). Set via
#[umbral(ordering = ["-published_at", "id"])] (leading -
flips to DESC). Closes BUG-8. Default empty.Source§const M2M_RELATIONS: &'static [M2MRelationSpec]
const M2M_RELATIONS: &'static [M2MRelationSpec]
Source§const REVERSE_FK_RELATIONS: &'static [ReverseFkRelationSpec]
const REVERSE_FK_RELATIONS: &'static [ReverseFkRelationSpec]
#[umbral(reverse_fk = "<fk_col>")] pub <name>: ReverseSet<C>.
Each entry tells prefetch_related how to fetch the children:
SELECT * FROM <target_table> WHERE <fk_column> IN (parent_pks)
then group by <fk_column> value, populate each parent’s
ReverseSet.resolved. Default empty; the macro emits one
entry per declared ReverseSet<C> field.Source§const ONE_TO_ONE_RELATIONS: &'static [OneToOneRelationSpec]
const ONE_TO_ONE_RELATIONS: &'static [OneToOneRelationSpec]
pub <name>: OneToOne<C> (no umbral attribute required).
Unlike REVERSE_FK_RELATIONS, the FK column on the child is
not named at macro time — prefetch_related looks it up at
runtime by scanning the child’s FIELDS for the UNIQUE FK
pointing back at this model’s table. Exactly one match
required; 0 or 2+ matches surface a loud error naming the
ambiguity.Source§type PrimaryKey = i64
type PrimaryKey = i64
i64 only; UUID lands later.Source§fn primary_key(&self) -> i64
fn primary_key(&self) -> i64
Auto Trait Implementations§
impl Freeze for AuthChallenge
impl RefUnwindSafe for AuthChallenge
impl Send for AuthChallenge
impl Sync for AuthChallenge
impl Unpin for AuthChallenge
impl UnsafeUnpin for AuthChallenge
impl UnwindSafe for AuthChallenge
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> ReverseRelations for Twhere
T: Model + HydrateRelated,
impl<T> ReverseRelations for Twhere
T: Model + HydrateRelated,
Source§fn reverse<C>(&self) -> Result<QuerySet<C>, ReverseError>where
C: Model + HydrateRelated,
fn reverse<C>(&self) -> Result<QuerySet<C>, ReverseError>where
C: Model + HydrateRelated,
QuerySet<C> of the children of type C
whose foreign key points at THIS instance. The FK column is
discovered from C::FIELDS (the field whose fk_target == Self::TABLE): Read moreSource§fn reverse_via<C>(&self, fk_col: &str) -> Result<QuerySet<C>, ReverseError>where
C: Model + HydrateRelated,
fn reverse_via<C>(&self, fk_col: &str) -> Result<QuerySet<C>, ReverseError>where
C: Model + HydrateRelated,
reverse, but with the FK column on C named explicitly
— the escape hatch for when C has more than one FK to this
parent. The column is validated to exist AND to be an FK to
Self::TABLE.