Skip to main content

rustauth_core/api/
response_helpers.rs

1use http::{header, HeaderValue, StatusCode};
2use serde::Serialize;
3use time::OffsetDateTime;
4
5use crate::context::request_state::{has_request_state, set_current_new_session};
6use crate::context::AuthContext;
7use crate::cookies::{
8    set_cookie_cache, set_session_cookie, Cookie, CookieCachePayload, CookieOptions,
9    SessionCookieOptions,
10};
11use crate::db::{Session, User};
12use crate::error::RustAuthError;
13
14use super::ApiResponse;
15
16pub fn json_response<T>(
17    status: StatusCode,
18    body: &T,
19    cookies: Vec<Cookie>,
20) -> Result<ApiResponse, RustAuthError>
21where
22    T: Serialize,
23{
24    let body = serde_json::to_vec(body).map_err(|error| RustAuthError::Serialization {
25        context: "serializing JSON response body",
26        message: error.to_string(),
27    })?;
28    let mut response = http::Response::builder()
29        .status(status)
30        .header(header::CONTENT_TYPE, "application/json")
31        .body(body)
32        .map_err(|error| RustAuthError::Serialization {
33            context: "building JSON response",
34            message: error.to_string(),
35        })?;
36    append_cookies(response.headers_mut(), cookies)?;
37    Ok(response)
38}
39
40pub fn redirect_response(
41    location: &str,
42    cookies: Vec<Cookie>,
43) -> Result<ApiResponse, RustAuthError> {
44    let mut response = http::Response::builder()
45        .status(StatusCode::FOUND)
46        .header(header::LOCATION, location)
47        .body(Vec::new())
48        .map_err(|error| RustAuthError::Serialization {
49            context: "building redirect response",
50            message: error.to_string(),
51        })?;
52    append_cookies(response.headers_mut(), cookies)?;
53    Ok(response)
54}
55
56pub fn redirect_with_error_response(
57    location: &str,
58    error: &str,
59) -> Result<ApiResponse, RustAuthError> {
60    let separator = if location.contains('?') { '&' } else { '?' };
61    redirect_response(
62        &format!(
63            "{location}{separator}error={}",
64            url::form_urlencoded::byte_serialize(error.as_bytes()).collect::<String>()
65        ),
66        Vec::new(),
67    )
68}
69
70pub fn session_cookies(
71    context: &AuthContext,
72    session: &Session,
73    user: &User,
74    dont_remember: bool,
75) -> Result<Vec<Cookie>, RustAuthError> {
76    if has_request_state() {
77        set_current_new_session(session.clone(), user.clone())?;
78    }
79    let mut cookies = set_session_cookie(
80        &context.auth_cookies,
81        &context.secret,
82        &session.token,
83        SessionCookieOptions {
84            dont_remember,
85            overrides: CookieOptions::default(),
86        },
87    )?;
88    if context.options.session.cookie_cache.enabled {
89        let max_age = context
90            .options
91            .session
92            .cookie_cache
93            .max_age
94            .unwrap_or(time::Duration::minutes(5));
95        cookies.extend(set_cookie_cache(
96            &context.auth_cookies,
97            &context.secret,
98            &CookieCachePayload {
99                session: session.clone(),
100                user: user.clone(),
101                updated_at: OffsetDateTime::now_utc().unix_timestamp(),
102                version: context
103                    .options
104                    .session
105                    .cookie_cache
106                    .version
107                    .clone()
108                    .unwrap_or_else(|| "1".to_owned()),
109            },
110            context.options.session.cookie_cache.strategy,
111            max_age.whole_seconds() as u64,
112        )?);
113    }
114    Ok(cookies)
115}
116
117pub fn append_cookies(
118    headers: &mut http::HeaderMap,
119    cookies: Vec<Cookie>,
120) -> Result<(), RustAuthError> {
121    for cookie in cookies {
122        headers.append(
123            header::SET_COOKIE,
124            HeaderValue::from_str(&serialize_cookie(&cookie))
125                .map_err(|error| RustAuthError::Cookie(error.to_string()))?,
126        );
127    }
128    Ok(())
129}
130
131pub fn serialize_cookie(cookie: &Cookie) -> String {
132    let mut parts = vec![format!("{}={}", cookie.name, cookie.value)];
133    if let Some(max_age) = cookie.attributes.max_age {
134        parts.push(format!("Max-Age={max_age}"));
135    }
136    if let Some(expires) = &cookie.attributes.expires {
137        parts.push(format!("Expires={expires}"));
138    }
139    if let Some(domain) = &cookie.attributes.domain {
140        parts.push(format!("Domain={domain}"));
141    }
142    if let Some(path) = &cookie.attributes.path {
143        parts.push(format!("Path={path}"));
144    }
145    if cookie.attributes.secure == Some(true) {
146        parts.push("Secure".to_owned());
147    }
148    if cookie.attributes.http_only == Some(true) {
149        parts.push("HttpOnly".to_owned());
150    }
151    if let Some(same_site) = &cookie.attributes.same_site {
152        parts.push(format!("SameSite={same_site}"));
153    }
154    if cookie.attributes.partitioned == Some(true) {
155        parts.push("Partitioned".to_owned());
156    }
157    parts.join("; ")
158}