better_auth_core/session/
view.rs1use crate::{
2 AuthRecordFields, AuthResult, FieldDate, FieldMap, FieldValue, FromFieldMap, SchemaField,
3 SchemaValue, wire::SessionView,
4};
5
6impl From<SessionView> for FieldMap {
7 fn from(session: SessionView) -> Self {
8 let mut fields = FieldMap::from_iter([
9 ("id".into(), session.id.into_field_value()),
10 ("token".into(), session.token.into()),
11 ("expiresAt".into(), session.expires_at.into()),
12 ("createdAt".into(), session.created_at.into()),
13 ("updatedAt".into(), session.updated_at.into()),
14 ("ipAddress".into(), session.ip_address.into_field()),
15 ("userAgent".into(), session.user_agent.into_field()),
16 ("userId".into(), session.user_id.into_field_value()),
17 ]);
18 for (name, value) in [
19 ("impersonatedBy", session.impersonated_by),
20 ("activeTeamId", session.active_team_id),
21 ("activeOrganizationId", session.active_organization_id),
22 ] {
23 if session
24 .visible_fields
25 .as_ref()
26 .is_none_or(|fields| fields.contains(name))
27 {
28 let _ = fields.insert(name.into(), value.into_field());
29 }
30 }
31 fields.extend(session.additional_fields);
32 fields
33 }
34}
35
36impl AuthRecordFields for SessionView {
37 fn field_values(&self) -> AuthResult<FieldMap> {
38 let mut fields: FieldMap = self.clone().into();
39 let _ = fields.insert("active".into(), self.active.into());
40 Ok(fields)
41 }
42
43 fn structured_clone(&self, context: &mut crate::StructuredCloneContext) -> AuthResult<Self> {
44 let mut output = self.clone();
45 output.id = context.clone_field(&self.id)?;
46 output.user_id = context.clone_field(&self.user_id)?;
47 output.expires_at = context.clone_field(&self.expires_at)?;
48 output.created_at = context.clone_field(&self.created_at)?;
49 output.updated_at = context.clone_field(&self.updated_at)?;
50 output.additional_fields = context.clone_map(&self.additional_fields);
51 Ok(output)
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn snapshots_preserve_private_fields_and_cross_field_date_aliases() -> AuthResult<()> {
61 let date = FieldDate::from_milliseconds(1_000.0);
62 let mut session = SessionView::from_field_values(FieldMap::from([
63 ("id".into(), "session".into()),
64 ("userId".into(), "user".into()),
65 ("token".into(), "native-token".into()),
66 ("expiresAt".into(), date.clone().into()),
67 ("createdAt".into(), date.clone().into()),
68 ("updatedAt".into(), date.clone().into()),
69 ("active".into(), true.into()),
70 ]))?;
71 let _ = session
72 .additional_fields
73 .insert("alias".into(), date.clone().into());
74 let _ = session.additional_fields.insert("token".into(), 7.into());
75 let snapshot = session.structured_clone(&mut crate::StructuredCloneContext::new())?;
76 assert!(snapshot.active);
77 assert_eq!(snapshot.token, "native-token");
78 assert_eq!(snapshot.visible_fields, session.visible_fields);
79 assert_eq!(snapshot.additional_fields["token"], FieldValue::Number(7.0));
80 assert!(
81 snapshot
82 .expires_at
83 .same_object(snapshot.additional_fields["alias"].as_date().unwrap())
84 );
85 assert!(snapshot.expires_at.same_object(&snapshot.created_at));
86 assert!(!snapshot.expires_at.same_object(&date));
87 assert!(serde_json::to_value(&snapshot)?.get("active").is_none());
88 Ok(())
89 }
90}
91
92impl FromFieldMap for SessionView {
93 fn from_field_values(mut fields: FieldMap) -> AuthResult<Self> {
94 fn take<T: SchemaField>(fields: &mut FieldMap, name: &str) -> AuthResult<T> {
95 fields
96 .remove(name)
97 .unwrap_or(FieldValue::Undefined)
98 .decode()
99 }
100 let visible_fields = Some(
101 ["impersonatedBy", "activeOrganizationId", "activeTeamId"]
102 .into_iter()
103 .filter(|name| fields.contains_key(*name))
104 .map(str::to_owned)
105 .collect(),
106 );
107 Ok(Self {
108 id: SchemaValue::from_field(fields.remove("id").unwrap_or_default()),
109 token: take(&mut fields, "token")?,
110 expires_at: take(&mut fields, "expiresAt")?,
111 created_at: take(&mut fields, "createdAt")?,
112 updated_at: take(&mut fields, "updatedAt")?,
113 ip_address: take(&mut fields, "ipAddress")?,
114 user_agent: take(&mut fields, "userAgent")?,
115 user_id: SchemaValue::from_field(fields.remove("userId").unwrap_or_default()),
116 impersonated_by: take(&mut fields, "impersonatedBy")?,
117 active_organization_id: take(&mut fields, "activeOrganizationId")?,
118 active_team_id: take(&mut fields, "activeTeamId")?,
119 active: fields.remove("active").unwrap_or(false.into()).decode()?,
120 visible_fields,
121 additional_fields: fields,
122 })
123 }
124}
125
126impl serde::Serialize for SessionView {
127 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
128 crate::field_value::serde::map::serialize(&self.clone().into(), serializer)
129 }
130}
131
132impl<'de> serde::Deserialize<'de> for SessionView {
133 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
134 let mut fields = crate::field_value::serde::map::deserialize(deserializer)?;
135 for name in ["expiresAt", "createdAt", "updatedAt"] {
136 if let Some(FieldValue::String(text)) = fields.get(name) {
137 let date =
138 chrono::DateTime::parse_from_rfc3339(text).map_err(serde::de::Error::custom)?;
139 let _ = fields.insert(
140 name.into(),
141 FieldDate::from(date.with_timezone(&chrono::Utc)).into(),
142 );
143 }
144 }
145 Self::from_field_values(fields).map_err(serde::de::Error::custom)
146 }
147}
148
149impl SessionView {
150 pub(crate) fn into_projected_fields(mut self) -> Self {
152 for name in [
153 "id",
154 "userId",
155 "token",
156 "expiresAt",
157 "createdAt",
158 "updatedAt",
159 "ipAddress",
160 "userAgent",
161 "impersonatedBy",
162 "activeOrganizationId",
163 "activeTeamId",
164 ] {
165 let Some(value) = self.additional_fields.get(name) else {
166 continue;
167 };
168 let applied = match (name, value) {
169 ("id", value) => {
170 self.id = SchemaValue::from_field(value.clone());
171 true
172 }
173 ("userId", value) => {
174 self.user_id = SchemaValue::from_field(value.clone());
175 true
176 }
177 ("token", FieldValue::String(value)) => {
178 self.token = value.clone();
179 true
180 }
181 ("expiresAt", FieldValue::Date(value)) => {
182 self.expires_at = value.clone();
183 true
184 }
185 ("createdAt", FieldValue::Date(value)) => {
186 self.created_at = value.clone();
187 true
188 }
189 ("updatedAt", FieldValue::Date(value)) => {
190 self.updated_at = value.clone();
191 true
192 }
193 (name, FieldValue::String(_) | FieldValue::Null) => {
194 let target = match name {
195 "ipAddress" => Some(&mut self.ip_address),
196 "userAgent" => Some(&mut self.user_agent),
197 "impersonatedBy" => Some(&mut self.impersonated_by),
198 "activeOrganizationId" => Some(&mut self.active_organization_id),
199 "activeTeamId" => Some(&mut self.active_team_id),
200 _ => None,
201 };
202 if let Some(target) = target {
203 *target = value.as_str().map(str::to_owned);
204 if matches!(
205 name,
206 "impersonatedBy" | "activeOrganizationId" | "activeTeamId"
207 ) && let Some(visible) = &mut self.visible_fields
208 {
209 let _ = visible.insert(name.into());
210 }
211 true
212 } else {
213 false
214 }
215 }
216 _ => false,
217 };
218 if applied {
219 let _ = self.additional_fields.remove(name);
220 }
221 }
222 self
223 }
224}