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#[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 #[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#[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#[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#[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#[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#[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#[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#[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#[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}