dioxus_clerk/core/reverification.rs
1//! Step-up reverification: prompting a signed-in user to re-assert a fresh
2//! authentication factor before a sensitive action proceeds.
3//!
4//! This is distinct from token [`verification`](super::verification), which
5//! checks a request's existing credentials. Reverification asks the user to
6//! authenticate *again*, at a required factor [`ReverificationLevel`], before a
7//! gated action runs.
8
9use serde::{Deserialize, Serialize};
10
11/// The authentication-factor level a step-up reverification requires, mirroring
12/// clerk-js's `SessionVerificationLevel`.
13///
14/// Serializes as the raw clerk-js level string. The enum is
15/// `#[non_exhaustive]`; levels this crate has not named yet round-trip through
16/// [`ReverificationLevel::Other`], mirroring
17/// [`SessionTaskKey`](super::SessionTaskKey).
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(from = "String", into = "String")]
20#[non_exhaustive]
21pub enum ReverificationLevel {
22 /// A single first-factor credential (e.g. password) must be re-verified.
23 FirstFactor,
24 /// A single second-factor credential (e.g. TOTP) must be re-verified.
25 SecondFactor,
26 /// Both a first and a second factor must be re-verified.
27 MultiFactor,
28 /// A level string this crate has not named yet.
29 ///
30 /// Only produced by the `From<&str>`/`From<String>`/`FromStr` conversions, which
31 /// canonicalize known levels to their named variants first. The payload is
32 /// an [`OtherReverificationLevel`] with no public constructor, so an `Other`
33 /// can never alias a named variant.
34 Other(OtherReverificationLevel),
35}
36
37/// A clerk-js reverification-level string with no named [`ReverificationLevel`]
38/// variant.
39///
40/// Obtained by matching on [`ReverificationLevel::Other`]; read the raw string
41/// with [`OtherReverificationLevel::as_str`]. It has no public constructor, so
42/// it never holds a value a named variant would represent.
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct OtherReverificationLevel(String);
45
46impl OtherReverificationLevel {
47 /// The raw clerk-js level string.
48 pub fn as_str(&self) -> &str {
49 &self.0
50 }
51}
52
53impl std::fmt::Display for OtherReverificationLevel {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_str(&self.0)
56 }
57}
58
59impl ReverificationLevel {
60 /// The raw clerk-js level string.
61 pub fn as_str(&self) -> &str {
62 match self {
63 Self::FirstFactor => "first_factor",
64 Self::SecondFactor => "second_factor",
65 Self::MultiFactor => "multi_factor",
66 Self::Other(level) => level.as_str(),
67 }
68 }
69
70 fn from_known(level: &str) -> Option<Self> {
71 Some(match level {
72 "first_factor" => Self::FirstFactor,
73 "second_factor" => Self::SecondFactor,
74 "multi_factor" => Self::MultiFactor,
75 _ => return None,
76 })
77 }
78}
79
80impl From<&str> for ReverificationLevel {
81 fn from(level: &str) -> Self {
82 Self::from_known(level)
83 .unwrap_or_else(|| Self::Other(OtherReverificationLevel(level.to_owned())))
84 }
85}
86
87impl From<String> for ReverificationLevel {
88 fn from(level: String) -> Self {
89 // Move the owned buffer into `Other` instead of re-allocating it.
90 Self::from_known(&level).unwrap_or(Self::Other(OtherReverificationLevel(level)))
91 }
92}
93
94impl From<ReverificationLevel> for String {
95 fn from(level: ReverificationLevel) -> Self {
96 level.as_str().to_owned()
97 }
98}
99
100impl std::str::FromStr for ReverificationLevel {
101 type Err = std::convert::Infallible;
102
103 fn from_str(level: &str) -> Result<Self, Self::Err> {
104 Ok(Self::from(level))
105 }
106}
107
108impl std::fmt::Display for ReverificationLevel {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.write_str(self.as_str())
111 }
112}