kanidmd_core/https/views/
cookies.rs

1//! Support Utilities for interacting with cookies.
2
3use crate::https::ServerState;
4use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
5use compact_jwt::{Jws, JwsSigner};
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8
9fn new_cookie<'a>(state: &'_ ServerState, ck_id: &'a str, value: String) -> Cookie<'a> {
10    let mut token_cookie = Cookie::new(ck_id, value);
11    token_cookie.set_secure(state.secure_cookies);
12    token_cookie.set_same_site(SameSite::Lax);
13    // Prevent Document.cookie accessing this. Still works with fetch.
14    token_cookie.set_http_only(true);
15    // We set a domain here because it allows subdomains
16    // of the idm to share the cookie. If domain was incorrect
17    // then webauthn won't work anyway!
18    token_cookie.set_domain(state.domain.clone());
19    token_cookie.set_path("/");
20    token_cookie
21}
22
23#[instrument(name = "views::cookies::destroy", level = "debug", skip(jar, state))]
24pub fn destroy(jar: CookieJar, ck_id: &str, state: &ServerState) -> CookieJar {
25    if let Some(ck) = jar.get(ck_id) {
26        let mut removal_cookie = ck.clone();
27        removal_cookie.make_removal();
28
29        // Need to be set to domain else the cookie isn't removed!
30        removal_cookie.set_domain(state.domain.clone());
31
32        // Need to be set to / to remove on all parent paths.
33        // If you don't set a path, NOTHING IS REMOVED!!!
34        removal_cookie.set_path("/");
35
36        jar.add(removal_cookie)
37    } else {
38        jar
39    }
40}
41
42pub fn make_unsigned<'a>(state: &'_ ServerState, ck_id: &'a str, value: String) -> Cookie<'a> {
43    new_cookie(state, ck_id, value)
44}
45
46pub fn make_signed<'a, T: Serialize>(
47    state: &'_ ServerState,
48    ck_id: &'a str,
49    value: &'_ T,
50) -> Option<Cookie<'a>> {
51    let kref = &state.jws_signer;
52
53    let jws = Jws::into_json(value)
54        .map_err(|e| {
55            error!(?e);
56        })
57        .ok()?;
58
59    // Get the header token ready.
60    let token = kref
61        .sign(&jws)
62        .map(|jwss| jwss.to_string())
63        .map_err(|e| {
64            error!(?e);
65        })
66        .ok()?;
67
68    Some(new_cookie(state, ck_id, token))
69}
70
71pub fn get_signed<T: DeserializeOwned>(
72    state: &ServerState,
73    jar: &CookieJar,
74    ck_id: &str,
75) -> Option<T> {
76    jar.get(ck_id)
77        .map(|c| c.value())
78        .and_then(|s| state.deserialise_from_str::<T>(s))
79}
80
81pub fn get_unsigned<'a>(jar: &'a CookieJar, ck_id: &'_ str) -> Option<&'a str> {
82    jar.get(ck_id).map(|c| c.value())
83}