Skip to main content

better_auth_core/session/
view.rs

1use 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    /// Synchronize representable output values; retain other runtime shapes in the wire overlay.
151    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}