pub struct AdminRecoveryCode {
pub id: String,
pub user_id: String,
pub code_hash: String,
pub created_at: i64,
pub used_at: Option<i64>,
}Expand description
One single-use recovery code of an crate::sqlite::admin_user::AdminUser.
This layer never sees a plaintext code. code_hash arrives already
hashed from admin::mfa, through the very same
crate::admin::password this crate hashes operator passwords with – a
recovery code is only ever compared, never needed back, so a read of this
table yields nothing replayable. That is the opposite of
admin_users.totp_secret, which verification needs in the clear.
§Methods
replace_all: mint a set, superseding the previous one, in one transactionlist_unused/count_unused: what verification walks, and what the panel showsconsume: spend one, single-use decided by the databasedelete_for_user: what removing the factor takes with it
Fields§
§id: String§user_id: String§code_hash: String<algo>$<params>$<salt>$<hash>, exactly a password_hash.
created_at: i64§used_at: Option<i64>When it was spent. None is the only usable state.
Implementations§
Source§impl AdminRecoveryCode
impl AdminRecoveryCode
Sourcepub async fn replace_all(
user_id: &str,
hashes: &[String],
database: &Database,
) -> Result<(), Error>
pub async fn replace_all( user_id: &str, hashes: &[String], database: &Database, ) -> Result<(), Error>
Replaces this user’s whole set with hashes.
One transaction, and that is the point: a regeneration that failed halfway between the DELETE and the INSERTs would leave the operator holding a printed set that no longer works, or – worse – half the old set still live alongside the new one.
Deletes the used rows too. They are an audit trail of the set being superseded, not of anything still reachable, and keeping them would make “10 minted, 7 remaining” a sum over sets.
Sourcepub async fn list_unused(
user_id: &str,
database: &Database,
) -> Result<Vec<AdminRecoveryCode>, Error>
pub async fn list_unused( user_id: &str, database: &Database, ) -> Result<Vec<AdminRecoveryCode>, Error>
The unspent codes, oldest first – what admin::mfa walks one PBKDF2
run at a time when a submission is not a TOTP code.
Sourcepub async fn count_unused(
user_id: &str,
database: &Database,
) -> Result<i64, Error>
pub async fn count_unused( user_id: &str, database: &Database, ) -> Result<i64, Error>
How many are left – the “7 of 10 remaining” the panel and
admin user totp status both show.
Sourcepub async fn consume(id: &str, database: &Database) -> Result<bool, Error>
pub async fn consume(id: &str, database: &Database) -> Result<bool, Error>
Spends one code. false means it was already spent, or gone.
The used_at IS NULL test lives in the WHERE clause rather than in a
read the caller does first: two submissions of one code racing must not
both succeed, and rows_affected is what decides which one did. Same
primitive as Nonce::verify.
Stamps rather than deletes – see the migration’s comment: “this code was spent, at T” is the audit trail a recovery-code use exists to leave.
Trait Implementations§
Source§impl Clone for AdminRecoveryCode
impl Clone for AdminRecoveryCode
Source§fn clone(&self) -> AdminRecoveryCode
fn clone(&self) -> AdminRecoveryCode
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for AdminRecoveryCode
impl RefUnwindSafe for AdminRecoveryCode
impl Send for AdminRecoveryCode
impl Sync for AdminRecoveryCode
impl Unpin for AdminRecoveryCode
impl UnsafeUnpin for AdminRecoveryCode
impl UnwindSafe for AdminRecoveryCode
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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 more