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}