arcly_http_identity/
model.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum AccountStatus {
9 Pending,
11 Active,
13 Locked,
15 Suspended,
17 Deleted,
19}
20
21impl AccountStatus {
22 pub fn can_authenticate(self) -> bool {
23 matches!(self, AccountStatus::Active)
24 }
25}
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct MfaState {
30 pub totp_enrolled: bool,
32 pub recovery_codes_remaining: u32,
34 pub passkeys: u32,
36}
37
38impl MfaState {
39 pub fn is_enrolled(&self) -> bool {
41 self.totp_enrolled || self.passkeys > 0
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Identity {
50 pub id: String,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub tenant: Option<String>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub email: Option<String>,
56 #[serde(default)]
57 pub email_verified: bool,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub phone: Option<String>,
60 #[serde(default)]
61 pub phone_verified: bool,
62 pub status: AccountStatus,
63 #[serde(default)]
64 pub roles: Vec<String>,
65 #[serde(default)]
67 pub perms: Vec<String>,
68 #[serde(default)]
69 pub mfa: MfaState,
70 #[serde(default)]
72 pub attributes: serde_json::Map<String, serde_json::Value>,
73}
74
75impl Identity {
76 pub fn primary_role(&self) -> &str {
78 self.roles.first().map(|s| s.as_str()).unwrap_or("user")
79 }
80}
81
82#[derive(Debug, Clone, Deserialize)]
84pub struct RegisterRequest {
85 pub email: String,
86 #[serde(default)]
87 pub password: Option<String>,
88 #[serde(default)]
89 pub tenant: Option<String>,
90 #[serde(default)]
91 pub attributes: serde_json::Map<String, serde_json::Value>,
92}
93
94#[derive(Debug, Clone, Serialize)]
97pub struct TokenResponse {
98 pub access_token: String,
99 pub refresh_token: String,
100 pub token_type: &'static str,
101 pub expires_in: u64,
102}
103
104impl TokenResponse {
105 pub fn bearer(access_token: String, refresh_token: String, expires_in: u64) -> Self {
106 Self {
107 access_token,
108 refresh_token,
109 token_type: "Bearer",
110 expires_in,
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize)]
118#[serde(untagged)]
119pub enum LoginOutcome {
120 Authenticated(TokenResponse),
122 MfaChallenge {
125 mfa_required: bool,
126 challenge_id: String,
127 methods: Vec<String>,
129 },
130}