Skip to main content

siderite_sessions/
id.rs

1//! Session ids: 256 random bits, and the SHA-256 hash stores see.
2
3use base64::Engine as _;
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use sha2::{Digest, Sha256};
6use std::fmt::Write as _;
7
8/// Random bytes in an id (256 bits).
9const ID_BYTES: usize = 32;
10/// Length of the base64url text of an id.
11const ID_LEN: usize = 43;
12
13/// A new id from the operating system's CSPRNG, as base64url text.
14pub(crate) fn generate() -> Result<String, getrandom::Error> {
15    let mut bytes = [0u8; ID_BYTES];
16    getrandom::fill(&mut bytes)?;
17    Ok(URL_SAFE_NO_PAD.encode(bytes))
18}
19
20/// Whether `id` has the shape of a generated id. Anything else is not
21/// worth a store lookup.
22pub(crate) fn well_formed(id: &str) -> bool {
23    id.len() == ID_LEN
24        && id
25            .bytes()
26            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
27}
28
29/// The SHA-256 of a session id as lowercase hex: the only form of an id
30/// a [`SessionStore`](crate::SessionStore) ever sees.
31#[must_use]
32pub fn id_hash(id: &str) -> String {
33    let digest = Sha256::digest(id.as_bytes());
34    let mut out = String::with_capacity(digest.len() * 2);
35    for byte in digest {
36        // Writing to a String cannot fail.
37        let _ = write!(out, "{byte:02x}");
38    }
39    out
40}