Skip to main content

use_authn/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3#![allow(clippy::module_name_repetitions)]
4
5use core::{fmt, str::FromStr};
6use std::error::Error;
7
8/// Error returned when an authentication label cannot be parsed.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum AuthnParseError {
11    Empty,
12    Unknown,
13}
14
15impl fmt::Display for AuthnParseError {
16    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Empty => formatter.write_str("authentication label cannot be empty"),
19            Self::Unknown => formatter.write_str("unknown authentication label"),
20        }
21    }
22}
23
24impl Error for AuthnParseError {}
25
26macro_rules! label_enum {
27    ($name:ident { $($variant:ident => $label:literal),+ $(,)? }) => {
28        impl $name {
29            /// Returns the stable label.
30            #[must_use]
31            pub const fn as_str(self) -> &'static str {
32                match self {
33                    $(Self::$variant => $label,)+
34                }
35            }
36        }
37
38        impl fmt::Display for $name {
39            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40                formatter.write_str(self.as_str())
41            }
42        }
43
44        impl FromStr for $name {
45            type Err = AuthnParseError;
46
47            fn from_str(input: &str) -> Result<Self, Self::Err> {
48                let trimmed = input.trim();
49                if trimmed.is_empty() {
50                    return Err(AuthnParseError::Empty);
51                }
52                let normalized = trimmed.to_ascii_lowercase();
53                match normalized.as_str() {
54                    $($label => Ok(Self::$variant),)+
55                    _ => Err(AuthnParseError::Unknown),
56                }
57            }
58        }
59    };
60}
61
62/// Authentication method labels.
63#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
64pub enum AuthenticationMethod {
65    Password,
66    Passkey,
67    Totp,
68    Hotp,
69    SmsOtp,
70    EmailOtp,
71    MagicLink,
72    OAuth2,
73    OpenIdConnect,
74    Sso,
75    Certificate,
76    ApiKey,
77    BearerToken,
78    MutualTls,
79}
80
81label_enum!(AuthenticationMethod {
82    Password => "password",
83    Passkey => "passkey",
84    Totp => "totp",
85    Hotp => "hotp",
86    SmsOtp => "sms-otp",
87    EmailOtp => "email-otp",
88    MagicLink => "magic-link",
89    OAuth2 => "oauth2",
90    OpenIdConnect => "openid-connect",
91    Sso => "sso",
92    Certificate => "certificate",
93    ApiKey => "api-key",
94    BearerToken => "bearer-token",
95    MutualTls => "mutual-tls",
96});
97
98/// Authentication factor labels.
99#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
100pub enum AuthenticationFactor {
101    Knowledge,
102    Possession,
103    Inherence,
104    Location,
105    Behavior,
106}
107
108label_enum!(AuthenticationFactor {
109    Knowledge => "knowledge",
110    Possession => "possession",
111    Inherence => "inherence",
112    Location => "location",
113    Behavior => "behavior",
114});
115
116/// HTTP or application authentication scheme labels.
117#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
118pub enum AuthenticationScheme {
119    Basic,
120    Bearer,
121    Digest,
122    Mutual,
123    Negotiate,
124    ApiKey,
125    Custom,
126}
127
128label_enum!(AuthenticationScheme {
129    Basic => "basic",
130    Bearer => "bearer",
131    Digest => "digest",
132    Mutual => "mutual",
133    Negotiate => "negotiate",
134    ApiKey => "api-key",
135    Custom => "custom",
136});
137
138/// Credential kind labels.
139#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
140pub enum CredentialKind {
141    Password,
142    ApiKey,
143    AccessToken,
144    RefreshToken,
145    IdToken,
146    ClientSecret,
147    Certificate,
148    PrivateKey,
149    Passkey,
150}
151
152label_enum!(CredentialKind {
153    Password => "password",
154    ApiKey => "api-key",
155    AccessToken => "access-token",
156    RefreshToken => "refresh-token",
157    IdToken => "id-token",
158    ClientSecret => "client-secret",
159    Certificate => "certificate",
160    PrivateKey => "private-key",
161    Passkey => "passkey",
162});
163
164/// Session kind labels.
165#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
166pub enum SessionKind {
167    Browser,
168    Api,
169    Service,
170    Device,
171    Sso,
172    Unknown,
173}
174
175label_enum!(SessionKind {
176    Browser => "browser",
177    Api => "api",
178    Service => "service",
179    Device => "device",
180    Sso => "sso",
181    Unknown => "unknown",
182});
183
184/// Token binding kind labels.
185#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
186pub enum TokenBindingKind {
187    None,
188    Cookie,
189    Header,
190    MutualTls,
191    Dpop,
192    HolderOfKey,
193    Unknown,
194}
195
196label_enum!(TokenBindingKind {
197    None => "none",
198    Cookie => "cookie",
199    Header => "header",
200    MutualTls => "mutual-tls",
201    Dpop => "dpop",
202    HolderOfKey => "holder-of-key",
203    Unknown => "unknown",
204});
205
206/// Password policy strength labels.
207#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
208pub enum PasswordPolicyLevel {
209    None,
210    Basic,
211    Strong,
212    Strict,
213}
214
215label_enum!(PasswordPolicyLevel {
216    None => "none",
217    Basic => "basic",
218    Strong => "strong",
219    Strict => "strict",
220});
221
222/// MFA status labels.
223#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
224pub enum MfaStatus {
225    Disabled,
226    Optional,
227    Required,
228    Enforced,
229}
230
231label_enum!(MfaStatus {
232    Disabled => "disabled",
233    Optional => "optional",
234    Required => "required",
235    Enforced => "enforced",
236});
237
238#[cfg(test)]
239mod tests {
240    use super::{AuthenticationFactor, AuthenticationMethod, AuthenticationScheme, MfaStatus};
241
242    #[test]
243    fn displays_authentication_labels() {
244        assert_eq!(AuthenticationMethod::Passkey.to_string(), "passkey");
245        assert_eq!(AuthenticationFactor::Knowledge.to_string(), "knowledge");
246        assert_eq!(MfaStatus::Enforced.to_string(), "enforced");
247    }
248
249    #[test]
250    fn parses_authentication_labels() {
251        assert_eq!(
252            "bearer".parse::<AuthenticationScheme>().expect("scheme"),
253            AuthenticationScheme::Bearer
254        );
255        assert_eq!(
256            "api-key".parse::<AuthenticationMethod>().expect("method"),
257            AuthenticationMethod::ApiKey
258        );
259    }
260}