1use crate::store::schema::resolve_field_name;
2use crate::{AuthRecordFields, FromFieldMap, SchemaField};
3use crate::{AuthResult, entity::AuthUser, plugin::MetadataMap, wire::UserView};
4use crate::{FieldMap, FieldValue as Value};
5
6const PLUGIN_FIELDS: &[(&str, &[&str])] = &[
7 ("anonymous.enabled", &["isAnonymous"]),
8 (
9 "phone-number.enabled",
10 &["phoneNumber", "phoneNumberVerified"],
11 ),
12 ("username.enabled", &["username"]),
13 ("username.display.enabled", &["displayUsername"]),
14 ("two_factor.enabled", &["twoFactorEnabled"]),
15 (
16 "admin.enabled",
17 &["role", "banned", "banReason", "banExpires"],
18 ),
19];
20
21impl UserView {
22 pub(crate) fn active_plugin_fields(
23 metadata: &MetadataMap,
24 ) -> impl Iterator<Item = &'static str> + '_ {
25 PLUGIN_FIELDS
26 .iter()
27 .filter(move |(plugin, _)| {
28 metadata.get(*plugin).and_then(serde_json::Value::as_bool) == Some(true)
29 })
30 .flat_map(|(_, fields)| fields.iter().copied())
31 }
32
33 pub fn synthetic_output(
35 data: FieldMap,
36 config: &super::UserConfig,
37 metadata: &MetadataMap,
38 ) -> AuthResult<FieldMap> {
39 let mut output = FieldMap::new();
40 for name in [
41 "id",
42 "name",
43 "email",
44 "emailVerified",
45 "image",
46 "createdAt",
47 "updatedAt",
48 ] {
49 if let Some(value) = data.get(name) {
50 let _ = output.insert(name.into(), value.clone());
51 }
52 }
53 for (plugin, names) in PLUGIN_FIELDS {
54 if metadata.get(*plugin).and_then(serde_json::Value::as_bool) != Some(true) {
55 continue;
56 }
57 for name in *names {
58 let value = data.get(*name).cloned().unwrap_or_else(|| {
59 if ["isAnonymous", "twoFactorEnabled", "banned"].contains(name) {
60 Value::Bool(false)
61 } else {
62 Value::Null
63 }
64 });
65 let _ = output.insert((*name).into(), value);
66 }
67 }
68 for (name, field) in config.fields() {
69 if !field.returned() {
70 let _ = output.remove(name);
71 continue;
72 }
73 let value = match data.get(name) {
74 Some(value) => Some(value.clone()),
75 None => field.default_value()?,
76 }
77 .or_else(|| (field.required != Some(true)).then_some(Value::Null));
78 if let Some(value) = value {
79 let _ = output.insert(name.clone(), value);
80 }
81 }
82 Ok(output)
83 }
84
85 pub(crate) fn filter_cached_fields(&mut self, config: &super::UserConfig) {
88 for (name, field) in config.fields() {
89 if field.returned() {
90 continue;
91 }
92 if let Some(fields) = &mut self.visible_fields {
93 let _ = fields.remove(name);
94 }
95 match name.as_str() {
96 "username" => self.username = None,
97 "displayUsername" => self.display_username = None,
98 _ => {}
99 }
100 }
101 self.additional_fields.retain(|name, _| {
102 config
103 .fields()
104 .get(name)
105 .is_none_or(|field| field.returned())
106 });
107 }
108
109 pub async fn with_fields<T: AuthUser>(
112 user: &T,
113 config: &super::UserConfig,
114 metadata: &MetadataMap,
115 ) -> AuthResult<Self> {
116 Self::project(user, config, metadata, true, true).await
117 }
118
119 pub async fn with_fields_for_adapter<T: AuthUser>(
120 user: &T,
121 config: &super::UserConfig,
122 metadata: &MetadataMap,
123 supports_native_json: bool,
124 ) -> AuthResult<Self> {
125 Self::project(user, config, metadata, true, supports_native_json).await
126 }
127
128 pub async fn with_field_policies<T: AuthUser>(
130 user: &T,
131 adapter: &super::UserConfig,
132 endpoint: &super::UserConfig,
133 metadata: &MetadataMap,
134 supports_native_json: bool,
135 ) -> AuthResult<Self> {
136 let mut view = Self::project(user, adapter, metadata, false, supports_native_json).await?;
137 view.filter_cached_fields(endpoint);
138 Ok(view)
139 }
140
141 pub async fn with_internal_fields<T: AuthUser>(
144 user: &T,
145 config: &super::UserConfig,
146 metadata: &MetadataMap,
147 ) -> AuthResult<Self> {
148 Self::project(user, config, metadata, false, true).await
149 }
150
151 pub async fn with_internal_fields_for_adapter<T: AuthUser>(
152 user: &T,
153 config: &super::UserConfig,
154 metadata: &MetadataMap,
155 supports_native_json: bool,
156 ) -> AuthResult<Self> {
157 Self::project(user, config, metadata, false, supports_native_json).await
158 }
159
160 pub async fn with_internal_fields_many_for_adapter<T: AuthUser>(
162 users: &[T],
163 config: &super::UserConfig,
164 metadata: &MetadataMap,
165 supports_native_json: bool,
166 ) -> AuthResult<Vec<Self>> {
167 Self::project_many(
168 users,
169 config,
170 metadata,
171 false,
172 super::FieldOutputCapabilities::json_only(supports_native_json),
173 |_, _, _| Ok(None),
174 )
175 .await
176 }
177
178 pub async fn with_internal_fields_many_for_adapter_using<T: AuthUser>(
183 users: &[T],
184 config: &super::UserConfig,
185 metadata: &MetadataMap,
186 capabilities: super::FieldOutputCapabilities,
187 read_extra: impl Fn(&T, &str, &super::UserFieldConfig) -> AuthResult<Option<Value>> + Sync,
188 ) -> AuthResult<Vec<Self>> {
189 Self::project_many(users, config, metadata, false, capabilities, read_extra).await
190 }
191
192 async fn project<T: AuthUser>(
193 user: &T,
194 config: &super::UserConfig,
195 metadata: &MetadataMap,
196 public: bool,
197 supports_native_json: bool,
198 ) -> AuthResult<Self> {
199 Ok(Self::project_many(
201 std::slice::from_ref(user),
202 config,
203 metadata,
204 public,
205 super::FieldOutputCapabilities::json_only(supports_native_json),
206 |_, _, _| Ok(None),
207 )
208 .await?
209 .remove(0))
210 }
211
212 async fn project_many<T: AuthUser>(
213 users: &[T],
214 config: &super::UserConfig,
215 metadata: &MetadataMap,
216 public: bool,
217 capabilities: super::FieldOutputCapabilities,
218 read_extra: impl Fn(&T, &str, &super::UserFieldConfig) -> AuthResult<Option<Value>> + Sync,
219 ) -> AuthResult<Vec<Self>> {
220 let mut rows = users
221 .iter()
222 .map(|user| {
223 let mut view = Self::from_model(user)?;
224 view.visible_fields = Some(
225 Self::active_plugin_fields(metadata)
226 .filter(|name| {
227 user.field_presence()
228 .is_none_or(|fields| fields.contains(*name))
229 })
230 .map(str::to_owned)
231 .chain(
232 ["name", "email", "image"]
233 .into_iter()
234 .filter(|name| {
235 user.field_presence()
236 .is_none_or(|fields| fields.contains(*name))
237 })
238 .map(str::to_owned),
239 )
240 .collect(),
241 );
242 view.additional_fields.clear();
243 let model = if !config.fields().is_empty() && user.projected_fields().is_none() {
244 Some(user.field_values()?)
245 } else {
246 None
247 };
248 Ok((user, view, model))
249 })
250 .collect::<AuthResult<Vec<_>>>()?;
251 let read_extra = &read_extra;
252 super::batch::project_fields(
253 &mut rows,
254 config.fields(),
255 |(user, view, model), name, field| {
256 Box::pin(async move {
257 let value = if let Some(projected) = user.projected_fields() {
258 match name {
259 "id" => Some(view.id.field_value()),
260 "name" => Some(view.name.field_value()),
261 "image" => Some(view.image.field_value()),
262 _ => projected.get(name).cloned(),
263 }
264 .unwrap_or_default()
265 } else {
266 let storage_name = resolve_field_name(field.field_name.as_deref(), name);
267 let value = if Self::NATIVE_FIELDS.contains(&name) {
268 None
269 } else {
270 read_extra(user, name, field)?
271 };
272 let raw_extra = value.is_some() && field.references.is_none();
273 let value = value
274 .or_else(|| {
275 model
276 .as_ref()
277 .and_then(|model| model.get(storage_name))
278 .cloned()
279 })
280 .or_else(|| match (name, storage_name == name) {
281 ("username", true) => {
282 Some(user.username().map(str::to_owned).into_field())
283 }
284 ("displayUsername", true) => {
285 Some(user.display_username().map(str::to_owned).into_field())
286 }
287 _ => None,
288 });
289 if raw_extra {
290 field
291 .adapter_output_from_raw(value.unwrap_or_default(), capabilities)
292 .await?
293 } else {
294 field
295 .adapter_output(
296 value.unwrap_or_default(),
297 capabilities.supports_native_json,
298 )
299 .await?
300 }
301 };
302 if name == "username" || name == "displayUsername" {
303 if let Some(fields) = &mut view.visible_fields {
304 if !public || field.returned() {
305 let _ = fields.insert(name.to_owned());
306 } else {
307 let _ = fields.remove(name);
308 }
309 }
310 let typed = if !public || field.returned() {
311 value.decode()?
312 } else {
313 None
314 };
315 if name == "username" {
316 view.username = typed;
317 } else {
318 view.display_username = typed;
319 }
320 }
321 if name == "name" || name == "image" {
322 let target = if name == "name" {
323 &mut view.name
324 } else {
325 &mut view.image
326 };
327 *target = crate::SchemaValue::from_field(value);
328 if public
329 && !field.returned()
330 && let Some(fields) = &mut view.visible_fields
331 {
332 let _ = fields.remove(name);
333 }
334 return Ok(());
335 }
336 {
337 let mut value = value;
338 if !field.uses_id_output() {
339 field.normalize_date(&mut value)?;
340 }
341 if !public || field.returned() {
342 let _ = view.additional_fields.insert(name.to_owned(), value);
343 }
344 }
345 Ok(())
346 })
347 },
348 )
349 .await?;
350 Ok(rows.into_iter().map(|(_, view, _)| view).collect())
351 }
352}
353
354impl UserView {
355 pub(crate) const NATIVE_FIELDS: &[&str] = &[
356 "id",
357 "name",
358 "email",
359 "emailVerified",
360 "image",
361 "createdAt",
362 "updatedAt",
363 "isAnonymous",
364 "phoneNumber",
365 "phoneNumberVerified",
366 "username",
367 "displayUsername",
368 "twoFactorEnabled",
369 "role",
370 "banned",
371 "banReason",
372 "banExpires",
373 ];
374
375 pub(crate) fn native_field_value(&self, name: &str) -> Option<Value> {
376 if matches!(name, "name" | "email" | "image")
377 && self
378 .visible_fields
379 .as_ref()
380 .is_some_and(|fields| !fields.contains(name))
381 {
382 return None;
383 }
384 let value = match name {
385 "id" if !self.id.is_undefined() => self.id.clone().into_field(),
386 "name" if !self.name.is_undefined() => self.name.clone().into_field(),
387 "email" => self.email.clone().into_field(),
388 "emailVerified" => self.email_verified.into_field(),
389 "image" if !self.image.is_undefined() => self.image.clone().into_field(),
390 "createdAt" => self.created_at.clone().into_field(),
391 "updatedAt" => self.updated_at.clone().into_field(),
392 "id" | "name" | "image" => return None,
393 _ => {
394 if self
395 .visible_fields
396 .as_ref()
397 .is_some_and(|fields| !fields.contains(name))
398 {
399 return None;
400 }
401 let value = match name {
402 "isAnonymous" => self.is_anonymous.into_field(),
403 "phoneNumber" => self.phone_number.clone().into_field(),
404 "phoneNumberVerified" => self.phone_number_verified.into_field(),
405 "username" => self.username.clone().into_field(),
406 "displayUsername" => self.display_username.clone().into_field(),
407 "twoFactorEnabled" => self.two_factor_enabled.into_field(),
408 "role" => self.role.clone().into_field(),
409 "banned" => self.banned.into_field(),
410 "banReason" => self.ban_reason.clone().into_field(),
411 "banExpires" => self.ban_expires.clone().into_field(),
412 _ => return None,
413 };
414 if name == "isAnonymous" && value.is_null() && self.visible_fields.is_some() {
415 return Some(Value::Bool(false));
416 }
417 if self.visible_fields.is_none()
418 && value.is_null()
419 && ["isAnonymous", "phoneNumber", "phoneNumberVerified"].contains(&name)
420 {
421 return None;
422 }
423 value
424 }
425 };
426 Some(value)
427 }
428}
429
430impl From<UserView> for FieldMap {
431 fn from(user: UserView) -> Self {
432 let mut result: FieldMap = UserView::NATIVE_FIELDS
433 .iter()
434 .filter_map(|name| {
435 user.native_field_value(name)
436 .map(|value| ((*name).into(), value))
437 })
438 .collect();
439 result.extend(user.additional_fields);
440 result
441 }
442}
443
444impl TryFrom<FieldMap> for UserView {
445 type Error = crate::AuthError;
446
447 fn try_from(mut fields: FieldMap) -> Result<Self, Self::Error> {
448 fn take<T: crate::SchemaField>(fields: &mut FieldMap, name: &str) -> AuthResult<T> {
449 fields.remove(name).unwrap_or_default().decode()
450 }
451 let visible_fields = Some(
452 PLUGIN_FIELDS
453 .iter()
454 .flat_map(|(_, names)| names.iter())
455 .filter(|name| fields.contains_key(**name))
456 .map(|name| (*name).to_owned())
457 .chain(
458 ["name", "email", "image"]
459 .into_iter()
460 .filter(|name| fields.contains_key(*name))
461 .map(str::to_owned),
462 )
463 .collect(),
464 );
465 Ok(Self {
466 id: crate::SchemaValue::from_field(fields.remove("id").unwrap_or_default()),
467 name: crate::SchemaValue::from_field(fields.remove("name").unwrap_or_default()),
468 email: take(&mut fields, "email")?,
469 email_verified: take(&mut fields, "emailVerified")?,
470 image: crate::SchemaValue::from_field(fields.remove("image").unwrap_or_default()),
471 created_at: take(&mut fields, "createdAt")?,
472 updated_at: take(&mut fields, "updatedAt")?,
473 is_anonymous: take(&mut fields, "isAnonymous")?,
474 phone_number: take(&mut fields, "phoneNumber")?,
475 phone_number_verified: take(&mut fields, "phoneNumberVerified")?,
476 username: fields
478 .get("username")
479 .cloned()
480 .unwrap_or_default()
481 .decode()?,
482 display_username: fields
483 .get("displayUsername")
484 .cloned()
485 .unwrap_or_default()
486 .decode()?,
487 two_factor_enabled: take(&mut fields, "twoFactorEnabled")?,
488 role: take(&mut fields, "role")?,
489 banned: take::<Option<bool>>(&mut fields, "banned")?.unwrap_or(false),
490 ban_reason: take(&mut fields, "banReason")?,
491 ban_expires: take(&mut fields, "banExpires")?,
492 metadata: fields.remove("metadata").unwrap_or(Value::Null),
493 visible_fields,
494 additional_fields: fields,
495 })
496 }
497}
498
499impl AuthRecordFields for UserView {
500 fn field_values(&self) -> AuthResult<FieldMap> {
501 let mut fields = FieldMap::from(self.clone());
502 let _ = fields.insert("metadata".into(), self.metadata.clone());
503 Ok(fields)
504 }
505
506 fn structured_clone(&self, context: &mut crate::StructuredCloneContext) -> AuthResult<Self> {
507 let mut user = self.clone();
508 user.id = context.clone_field(&self.id)?;
509 user.name = context.clone_field(&self.name)?;
510 user.image = context.clone_field(&self.image)?;
511 user.created_at = context.clone_field(&self.created_at)?;
512 user.updated_at = context.clone_field(&self.updated_at)?;
513 user.ban_expires = context.clone_field(&self.ban_expires)?;
514 user.metadata = context.clone_value(&self.metadata);
515 user.additional_fields = context.clone_map(&self.additional_fields);
516 Ok(user)
517 }
518}
519
520impl FromFieldMap for UserView {
521 fn from_field_values(fields: FieldMap) -> AuthResult<Self> {
522 Self::try_from(fields)
523 }
524}
525
526impl serde::Serialize for UserView {
527 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
528 crate::field_value::serde::map::serialize(&FieldMap::from(self.clone()), serializer)
529 }
530}
531
532impl<'de> serde::Deserialize<'de> for UserView {
533 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
534 use serde::de::Error;
535 let mut fields = crate::field_value::serde::map::deserialize(deserializer)?;
536 for name in ["createdAt", "updatedAt", "banExpires"] {
537 if let Some(Value::String(text)) = fields.get(name) {
538 let date = crate::utils::date::parse_adapter_date(text)
539 .ok_or_else(|| D::Error::custom(format!("Invalid user date field `{name}`")))?;
540 let _ = fields.insert(name.into(), crate::FieldDate::from(date).into());
541 }
542 }
543 Self::from_field_values(fields).map_err(D::Error::custom)
544 }
545}