Skip to main content

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}