1use serde::Serialize;
4use serde_json::Value;
5use time::OffsetDateTime;
6
7use crate::api::additional_fields::{db_value_to_json, insert_returned_fields_http};
8use crate::api::http_json::{session_to_http_value, snake_to_camel, user_to_http_value};
9use crate::context::AuthContext;
10use crate::cookies::{
11 set_cookie_cache, set_session_cookie, Cookie, CookieCachePayload, CookieOptions,
12 SessionCookieOptions,
13};
14use crate::db::{filter_output_fields, DbAdapter, DbRecord, DbValue, FindOne, Session, User};
15use crate::error::RustAuthError;
16
17#[derive(Debug, Serialize)]
18pub struct SessionUserOutput {
19 pub session: Value,
20 pub user: Value,
21}
22
23pub async fn session_user_output(
24 adapter: &dyn DbAdapter,
25 context: &AuthContext,
26 session: &Session,
27 user: &User,
28) -> Result<SessionUserOutput, RustAuthError> {
29 Ok(SessionUserOutput {
30 session: session_output_value(adapter, context, session).await?,
31 user: user_output_value(adapter, context, user).await?,
32 })
33}
34
35pub fn user_output_value_from_fields(
40 context: &AuthContext,
41 user: &User,
42 additional_fields: &DbRecord,
43) -> Result<Value, RustAuthError> {
44 let mut value = user_to_http_value(user)?;
45 if context.options.user.additional_fields.is_empty() {
46 return Ok(value);
47 }
48 let Some(object) = value.as_object_mut() else {
49 return Err(RustAuthError::Serialization {
50 context: "serializing user output",
51 message: "expected JSON object".to_owned(),
52 });
53 };
54 insert_returned_fields_http(
55 object,
56 &context.options.user.additional_fields,
57 additional_fields,
58 )?;
59 Ok(value)
60}
61
62pub async fn user_output_value(
63 adapter: &dyn DbAdapter,
64 context: &AuthContext,
65 user: &User,
66) -> Result<Value, RustAuthError> {
67 let users = context.schema().table("user")?;
68 let record = adapter
69 .find_one(
70 FindOne::new(users.model())
71 .where_clause(users.where_eq("id", DbValue::String(user.id.clone()))?),
72 )
73 .await?
74 .map(|record| users.map_record(record))
75 .transpose()?;
76 let mut value = user_to_http_value(user)?;
77 let Some(object) = value.as_object_mut() else {
78 return Err(RustAuthError::Serialization {
79 context: "serializing user output",
80 message: "expected JSON object".to_owned(),
81 });
82 };
83 if let Some(record) = record {
84 insert_returned_fields_http(object, &context.options.user.additional_fields, &record)?;
85 insert_schema_returned_fields(context, "user", object, &record)?;
86 }
87 Ok(value)
88}
89
90pub async fn session_output_value(
91 adapter: &dyn DbAdapter,
92 context: &AuthContext,
93 session: &Session,
94) -> Result<Value, RustAuthError> {
95 let record =
96 if let Some(sessions) = context.schema().try_table("session") {
97 adapter
98 .find_one(FindOne::new(sessions.model()).where_clause(
99 sessions.where_eq("token", DbValue::String(session.token.clone()))?,
100 ))
101 .await?
102 .map(|record| sessions.map_record(record))
103 .transpose()?
104 } else {
105 None
106 };
107 match record {
108 Some(record) => session_value_from_record(context, &record, session),
109 None => session_to_http_value(session),
110 }
111}
112
113pub fn session_value_from_record(
114 context: &AuthContext,
115 record: &DbRecord,
116 session: &Session,
117) -> Result<Value, RustAuthError> {
118 let mut value = session_to_http_value(session)?;
119 let Some(object) = value.as_object_mut() else {
120 return Err(RustAuthError::Serialization {
121 context: "serializing session output",
122 message: "expected JSON object".to_owned(),
123 });
124 };
125 insert_returned_fields_http(object, &context.options.session.additional_fields, record)?;
126 insert_schema_returned_fields(context, "session", object, record)?;
127 Ok(value)
128}
129
130fn insert_schema_returned_fields(
131 context: &AuthContext,
132 table: &str,
133 object: &mut serde_json::Map<String, Value>,
134 record: &DbRecord,
135) -> Result<(), RustAuthError> {
136 let Some(table) = context.db_schema.table(table) else {
137 return Ok(());
138 };
139 for (logical_name, value) in filter_output_fields(record, &table.fields) {
140 let http_key = snake_to_camel(&logical_name);
141 if object.contains_key(&http_key) || object.contains_key(&logical_name) {
142 continue;
143 }
144 object.insert(http_key, db_value_to_json(&value)?);
145 }
146 Ok(())
147}
148
149pub fn session_response_cookies(
150 context: &AuthContext,
151 session: &Session,
152 user: &User,
153 dont_remember: bool,
154) -> Result<Vec<Cookie>, RustAuthError> {
155 let mut cookies = set_session_cookie(
156 &context.auth_cookies,
157 &context.secret,
158 &session.token,
159 SessionCookieOptions {
160 dont_remember,
161 overrides: CookieOptions::default(),
162 },
163 )?;
164 if context.options.session.cookie_cache.enabled {
165 let max_age = context
166 .options
167 .session
168 .cookie_cache
169 .max_age
170 .unwrap_or(time::Duration::minutes(5));
171 cookies.extend(set_cookie_cache(
172 &context.auth_cookies,
173 &context.secret,
174 &CookieCachePayload {
175 session: session.clone(),
176 user: user.clone(),
177 updated_at: OffsetDateTime::now_utc().unix_timestamp(),
178 version: context
179 .options
180 .session
181 .cookie_cache
182 .version
183 .clone()
184 .unwrap_or_else(|| "1".to_owned()),
185 },
186 context.options.session.cookie_cache.strategy,
187 max_age.whole_seconds() as u64,
188 )?);
189 }
190 Ok(cookies)
191}