1use chrono::{DateTime, SecondsFormat, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum RoleInput {
9 One(String),
11 Many(Vec<String>),
13}
14
15impl RoleInput {
16 pub(crate) fn joined(&self) -> String {
17 match self {
18 Self::One(role) => role.clone(),
19 Self::Many(roles) => roles.join(","),
20 }
21 }
22
23 pub(crate) fn roles(&self) -> Vec<&str> {
24 match self {
25 Self::One(role) => vec![role.as_str()],
26 Self::Many(roles) => roles.iter().map(String::as_str).collect(),
27 }
28 }
29}
30
31#[derive(Debug, Serialize, Deserialize)]
33pub struct SetRoleRequest {
34 #[serde(rename = "userId")]
35 pub user_id: String,
37 pub role: RoleInput,
39}
40
41#[derive(Debug, Deserialize)]
42pub(crate) struct GetUserQuery {
43 pub id: String,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
48pub struct CreateUserRequest {
49 pub email: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub password: Option<String>,
54 pub name: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub role: Option<RoleInput>,
59 #[serde(
61 default,
62 skip_serializing_if = "Option::is_none",
63 deserialize_with = "alibi_core::utils::json::deserialize_optional_map"
64 )]
65 pub data: Option<serde_json::Map<String, serde_json::Value>>,
66}
67impl CreateUserRequest {
68 #[must_use]
70 pub fn new(email: impl Into<String>, name: impl Into<String>) -> Self {
71 Self {
72 email: email.into(),
73 name: name.into(),
74 password: None,
75 role: None,
76 data: None,
77 }
78 }
79}
80
81#[derive(Debug, Serialize, Deserialize)]
83pub struct AdminUpdateUserRequest {
84 #[serde(rename = "userId")]
86 pub user_id: String,
87 #[serde(deserialize_with = "alibi_core::utils::json::deserialize_map")]
89 pub data: serde_json::Map<String, serde_json::Value>,
90}
91impl AdminUpdateUserRequest {
92 #[must_use]
94 pub fn new(
95 user_id: impl Into<String>,
96 data: serde_json::Map<String, serde_json::Value>,
97 ) -> Self {
98 Self {
99 user_id: user_id.into(),
100 data,
101 }
102 }
103}
104
105#[derive(Debug, Deserialize)]
106pub(crate) struct UserIdRequest {
107 #[serde(rename = "userId")]
108 pub user_id: String,
109}
110
111#[derive(Debug, Serialize, Deserialize)]
113pub struct BanUserRequest {
114 #[serde(rename = "userId")]
115 pub user_id: String,
117 #[serde(rename = "banReason", skip_serializing_if = "Option::is_none")]
118 pub ban_reason: Option<String>,
120 #[serde(
121 rename = "banExpiresIn",
122 skip_serializing_if = "Option::is_none",
123 default,
124 deserialize_with = "finite_optional_duration"
125 )]
126 pub ban_expires_in: Option<f64>,
128}
129impl BanUserRequest {
130 #[must_use]
132 pub fn new(user_id: impl Into<String>) -> Self {
133 Self {
134 user_id: user_id.into(),
135 ban_reason: None,
136 ban_expires_in: None,
137 }
138 }
139}
140
141#[derive(Debug, Deserialize)]
142pub(crate) struct RevokeSessionRequest {
143 #[serde(rename = "sessionToken")]
144 pub session_token: String,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
149pub struct SetUserPasswordRequest {
150 #[serde(rename = "userId")]
152 pub user_id: String,
153 #[serde(rename = "newPassword")]
155 pub new_password: String,
156}
157impl SetUserPasswordRequest {
158 #[must_use]
160 pub fn new(user_id: impl Into<String>, new_password: impl Into<String>) -> Self {
161 Self {
162 user_id: user_id.into(),
163 new_password: new_password.into(),
164 }
165 }
166}
167
168#[derive(Debug, Serialize, Deserialize)]
170pub struct HasPermissionRequest {
171 #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
173 pub user_id: Option<String>,
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub role: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub permission: Option<HashMap<String, Vec<String>>>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub permissions: Option<HashMap<String, Vec<String>>>,
183}
184
185impl HasPermissionRequest {
186 #[must_use]
188 pub fn new(permissions: HashMap<String, Vec<String>>) -> Self {
189 Self {
190 permissions: Some(permissions),
191 user_id: None,
192 role: None,
193 permission: None,
194 }
195 }
196
197 pub(crate) fn requested_permissions(&self) -> Option<&HashMap<String, Vec<String>>> {
198 self.permissions.as_ref().or(self.permission.as_ref())
199 }
200}
201
202#[derive(Debug, Serialize)]
203pub(crate) struct PhysicalAdminUserView {
204 pub id: String,
205 pub name: Option<String>,
206 pub email: Option<String>,
207 #[serde(rename = "emailVerified")]
208 pub email_verified: bool,
209 pub image: Option<String>,
210 #[serde(rename = "createdAt")]
211 #[serde(serialize_with = "alibi_core::utils::datetime::serialize")]
212 pub created_at: DateTime<Utc>,
213 #[serde(rename = "updatedAt")]
214 #[serde(serialize_with = "alibi_core::utils::datetime::serialize")]
215 pub updated_at: DateTime<Utc>,
216 pub username: Option<String>,
217 #[serde(rename = "displayUsername")]
218 pub display_username: Option<String>,
219 #[serde(rename = "twoFactorEnabled")]
220 pub two_factor_enabled: bool,
221 pub role: Option<String>,
222 pub banned: bool,
223 #[serde(rename = "banReason")]
224 pub ban_reason: Option<String>,
225 #[serde(rename = "banExpires")]
226 pub ban_expires: Option<String>,
227}
228
229impl<T: alibi_core::entity::AuthUser> From<&T> for PhysicalAdminUserView {
230 fn from(user: &T) -> Self {
231 Self {
232 id: user.id().into_owned(),
233 name: user.name().map(str::to_owned),
234 email: user.email().map(str::to_owned),
235 email_verified: user.email_verified(),
236 image: user.image().map(str::to_owned),
237 created_at: user.created_at(),
238 updated_at: user.updated_at(),
239 username: user.username().map(str::to_owned),
240 display_username: user.display_username().map(str::to_owned),
241 two_factor_enabled: user.two_factor_enabled(),
242 role: user.role().map(str::to_owned),
243 banned: user.banned(),
244 ban_reason: user.ban_reason().map(str::to_owned),
245 ban_expires: user
246 .ban_expires()
247 .map(|value| value.to_rfc3339_opts(SecondsFormat::Millis, true)),
248 }
249 }
250}
251
252#[derive(Debug, Serialize, Deserialize)]
254#[serde(transparent)]
255pub struct AdminUserView(serde_json::Map<String, serde_json::Value>);
256
257impl AdminUserView {
258 #[must_use]
260 pub const fn values(&self) -> &serde_json::Map<String, serde_json::Value> {
261 &self.0
262 }
263
264 pub(crate) fn from_output<S: alibi_core::AuthSchema>(
265 ctx: &alibi_core::AuthContext<S>,
266 user: &alibi_core::AdapterRecord<S::User>,
267 ) -> alibi_core::AuthResult<Self> {
268 let serde_json::Value::Object(mut output) =
269 serde_json::to_value(PhysicalAdminUserView::from(user))?
270 else {
271 return Err(alibi_core::AuthError::internal(
272 "Admin user output must be an object",
273 ));
274 };
275 let registered = ctx.extensions.get::<alibi_core::field_policy::UserFields>();
276 let fields = registered
277 .as_ref()
278 .map_or(&ctx.config.user.additional_fields, |fields| &fields.0.0);
279 for (name, field) in fields {
280 _ = output.remove(name);
281 if field.returned
282 && let Some(value) = user.raw_snapshot().values().get(name)
283 {
284 _ = output.insert(name.clone(), value.clone());
285 }
286 }
287 let view = ctx.user_view(user);
288 if let Some(value) = view.is_anonymous {
289 _ = output.insert("isAnonymous".into(), serde_json::Value::Bool(value));
290 }
291 output.extend(view.extension_fields);
292 Ok(Self(output))
293 }
294}
295
296#[derive(Debug, Serialize, Deserialize)]
298pub struct UserResponse<U> {
299 pub user: U,
301}
302
303#[derive(Debug, Serialize, Deserialize)]
305pub struct SessionUserResponse<S, U> {
306 pub session: S,
308 pub user: U,
310}
311
312#[derive(Debug, Serialize, Deserialize)]
314pub struct ListUsersResponse<U> {
315 pub users: Vec<U>,
317 pub total: usize,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub limit: Option<f64>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub offset: Option<f64>,
325}
326
327#[derive(Debug, Serialize, Deserialize)]
329pub struct ListSessionsResponse<S> {
330 pub sessions: Vec<S>,
332}
333
334#[derive(Debug, Serialize, Deserialize)]
336pub struct SuccessResponse {
337 pub success: bool,
339}
340
341#[derive(Debug, Serialize, Deserialize)]
343pub struct PermissionResponse {
344 pub error: Option<String>,
346 pub success: bool,
348}
349
350#[derive(Debug, Default, Deserialize)]
352pub(crate) struct ListUsersQueryParams {
353 pub source_limit: Option<f64>,
354 pub source_offset: Option<f64>,
355 pub limit: Option<usize>,
356 pub offset: Option<usize>,
357 #[serde(rename = "searchField")]
358 pub search_field: Option<String>,
359 #[serde(rename = "searchValue")]
360 pub search_value: Option<String>,
361 #[serde(rename = "searchOperator")]
362 pub search_operator: Option<String>,
363 #[serde(rename = "sortBy")]
364 pub sort_by: Option<String>,
365 #[serde(rename = "sortDirection")]
366 pub sort_direction: Option<String>,
367 #[serde(rename = "filterField")]
368 pub filter_field: Option<String>,
369 #[serde(rename = "filterValue")]
370 pub filter_value: Option<alibi_core::UserFilterValue>,
371 #[serde(rename = "filterOperator")]
372 pub filter_operator: Option<String>,
373}
374
375fn finite_optional_duration<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
376where
377 D: serde::Deserializer<'de>,
378{
379 let duration = Option::<f64>::deserialize(deserializer)?;
380 if duration.is_some_and(|value| !value.is_finite()) {
381 return Err(serde::de::Error::custom("banExpiresIn must be finite"));
382 }
383 Ok(duration)
384}
385
386#[derive(Debug, Default, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct AdminListUsersQuery {
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub limit: Option<serde_json::Value>,
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub offset: Option<serde_json::Value>,
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub search_value: Option<String>,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub search_field: Option<String>,
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub search_operator: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub sort_by: Option<String>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub sort_direction: Option<String>,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub filter_field: Option<String>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub filter_value: Option<serde_json::Value>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub filter_operator: Option<String>,
420}