Skip to main content

openauth_plugins/admin/
models.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use openauth_core::db::{DbRecord, DbValue};
5use openauth_core::error::OpenAuthError;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct AdminUser {
10    pub id: String,
11    pub name: String,
12    pub email: String,
13    pub email_verified: bool,
14    pub image: Option<String>,
15    pub created_at: OffsetDateTime,
16    pub updated_at: OffsetDateTime,
17    pub role: Option<String>,
18    pub banned: bool,
19    pub ban_reason: Option<String>,
20    pub ban_expires: Option<OffsetDateTime>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct AdminSession {
26    pub id: String,
27    pub user_id: String,
28    pub expires_at: OffsetDateTime,
29    pub token: String,
30    pub ip_address: Option<String>,
31    pub user_agent: Option<String>,
32    pub created_at: OffsetDateTime,
33    pub updated_at: OffsetDateTime,
34    pub impersonated_by: Option<String>,
35}
36
37#[derive(Debug, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct UserIdBody {
40    pub user_id: String,
41}
42
43#[derive(Debug, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct SetRoleBody {
46    pub user_id: String,
47    pub role: RoleInput,
48}
49
50#[derive(Debug, Clone, Deserialize)]
51#[serde(untagged)]
52pub enum RoleInput {
53    One(String),
54    Many(Vec<String>),
55}
56
57impl RoleInput {
58    pub fn roles(&self) -> Vec<String> {
59        match self {
60            Self::One(role) => vec![role.clone()],
61            Self::Many(roles) => roles.clone(),
62        }
63    }
64
65    pub fn joined(&self) -> String {
66        self.roles().join(",")
67    }
68}
69
70#[derive(Debug, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct CreateUserBody {
73    pub email: String,
74    pub password: Option<String>,
75    pub name: String,
76    pub role: Option<RoleInput>,
77    #[serde(default)]
78    pub data: serde_json::Map<String, serde_json::Value>,
79}
80
81#[derive(Debug, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct UpdateUserBody {
84    pub user_id: String,
85    pub data: serde_json::Map<String, serde_json::Value>,
86}
87
88#[derive(Debug, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct BanUserBody {
91    pub user_id: String,
92    pub ban_reason: Option<String>,
93    pub ban_expires_in: Option<i64>,
94}
95
96#[derive(Debug, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct RevokeSessionBody {
99    pub session_token: String,
100}
101
102#[derive(Debug, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct SetPasswordBody {
105    pub user_id: String,
106    pub new_password: String,
107}
108
109#[derive(Debug, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct HasPermissionBody {
112    pub user_id: Option<String>,
113    pub role: Option<String>,
114    #[serde(default, alias = "permission")]
115    pub permissions: crate::admin::PermissionMap,
116}
117
118pub(crate) fn admin_user_from_record(record: DbRecord) -> Result<AdminUser, OpenAuthError> {
119    Ok(AdminUser {
120        id: string(&record, "id")?.to_owned(),
121        name: string(&record, "name")?.to_owned(),
122        email: string(&record, "email")?.to_owned(),
123        email_verified: bool_field(&record, "email_verified")?,
124        image: optional_string(&record, "image")?,
125        created_at: timestamp(&record, "created_at")?,
126        updated_at: timestamp(&record, "updated_at")?,
127        role: optional_string(&record, "role")?,
128        banned: optional_bool(&record, "banned")?.unwrap_or(false),
129        ban_reason: optional_string(&record, "ban_reason")?,
130        ban_expires: optional_timestamp(&record, "ban_expires")?,
131    })
132}
133
134pub(crate) fn admin_session_from_record(record: DbRecord) -> Result<AdminSession, OpenAuthError> {
135    Ok(AdminSession {
136        id: string(&record, "id")?.to_owned(),
137        user_id: string(&record, "user_id")?.to_owned(),
138        expires_at: timestamp(&record, "expires_at")?,
139        token: string(&record, "token")?.to_owned(),
140        ip_address: optional_string(&record, "ip_address")?,
141        user_agent: optional_string(&record, "user_agent")?,
142        created_at: timestamp(&record, "created_at")?,
143        updated_at: timestamp(&record, "updated_at")?,
144        impersonated_by: optional_string(&record, "impersonated_by")?,
145    })
146}
147
148fn string<'a>(record: &'a DbRecord, field: &str) -> Result<&'a str, OpenAuthError> {
149    match record.get(field) {
150        Some(DbValue::String(value)) => Ok(value),
151        _ => Err(OpenAuthError::Adapter(format!(
152            "missing string field `{field}`"
153        ))),
154    }
155}
156
157fn bool_field(record: &DbRecord, field: &str) -> Result<bool, OpenAuthError> {
158    match record.get(field) {
159        Some(DbValue::Boolean(value)) => Ok(*value),
160        _ => Err(OpenAuthError::Adapter(format!(
161            "missing boolean field `{field}`"
162        ))),
163    }
164}
165
166fn timestamp(record: &DbRecord, field: &str) -> Result<OffsetDateTime, OpenAuthError> {
167    match record.get(field) {
168        Some(DbValue::Timestamp(value)) => Ok(*value),
169        _ => Err(OpenAuthError::Adapter(format!(
170            "missing timestamp field `{field}`"
171        ))),
172    }
173}
174
175fn optional_string(record: &DbRecord, field: &str) -> Result<Option<String>, OpenAuthError> {
176    match record.get(field) {
177        Some(DbValue::String(value)) => Ok(Some(value.clone())),
178        Some(DbValue::Null) | None => Ok(None),
179        _ => Err(OpenAuthError::Adapter(format!(
180            "invalid string field `{field}`"
181        ))),
182    }
183}
184
185fn optional_bool(record: &DbRecord, field: &str) -> Result<Option<bool>, OpenAuthError> {
186    match record.get(field) {
187        Some(DbValue::Boolean(value)) => Ok(Some(*value)),
188        Some(DbValue::Null) | None => Ok(None),
189        _ => Err(OpenAuthError::Adapter(format!(
190            "invalid boolean field `{field}`"
191        ))),
192    }
193}
194
195fn optional_timestamp(
196    record: &DbRecord,
197    field: &str,
198) -> Result<Option<OffsetDateTime>, OpenAuthError> {
199    match record.get(field) {
200        Some(DbValue::Timestamp(value)) => Ok(Some(*value)),
201        Some(DbValue::Null) | None => Ok(None),
202        _ => Err(OpenAuthError::Adapter(format!(
203            "invalid timestamp field `{field}`"
204        ))),
205    }
206}