anvilsign in

collin/anvil

main / crates / anvil-core / src / sessions.rs
1//! Web login sessions: opaque random tokens stored server-side and referenced
2//! by a cookie.
3
4use rand::{
5 Rng,
6 rand_core::UnwrapErr,
7 rngs::SysRng,
8};
9
10use crate::{
11 error::Result,
12 models::{
13 Session,
14 User,
15 },
16};
17
18/// Default session lifetime: 30 days.
19const SESSION_TTL_SECS: i64 = 60 * 60 * 24 * 30;
20
21/// Generate a 256-bit random session token, hex-encoded.
22fn generate_token() -> String {
23 let mut bytes = [0u8; 32];
24 let mut rng = UnwrapErr(SysRng);
25 rng.fill_bytes(&mut bytes);
26 bytes.iter().map(|b| format!("{b:02x}")).collect()
27}
28
29/// Create a new session for `user_id`, returning it (the `token` goes in a cookie).
30pub async fn create(db: &toasty::Db, user_id: i64) -> Result<Session> {
31 let mut conn = db.clone();
32 let now = crate::now();
33 let session = toasty::create!(Session {
34 token: generate_token(),
35 user_id: user_id,
36 created_at: now,
37 expires_at: now + SESSION_TTL_SECS,
38 })
39 .exec(&mut conn)
40 .await?;
41 Ok(session)
42}
43
44/// Resolve a session token to its user, or `None` if missing/expired.
45/// Expired sessions are deleted as a side effect.
46pub async fn lookup_user(db: &toasty::Db, token: &str) -> Result<Option<User>> {
47 let mut conn = db.clone();
48 let Some(session) = Session::filter(Session::fields().token().eq(token))
49 .first()
50 .exec(&mut conn)
51 .await?
52 else {
53 return Ok(None);
54 };
55
56 if session.expires_at < crate::now() {
57 let mut conn = db.clone();
58 let _ = session.delete().exec(&mut conn).await;
59 return Ok(None);
60 }
61
62 let mut conn = db.clone();
63 let user = User::filter(User::fields().id().eq(session.user_id))
64 .first()
65 .exec(&mut conn)
66 .await?;
67 Ok(user)
68}
69
70/// Delete a session (logout). No-op if the token is unknown.
71pub async fn delete(db: &toasty::Db, token: &str) -> Result<()> {
72 let mut conn = db.clone();
73 if let Some(session) = Session::filter(Session::fields().token().eq(token))
74 .first()
75 .exec(&mut conn)
76 .await?
77 {
78 let mut conn = db.clone();
79 session.delete().exec(&mut conn).await?;
80 }
81 Ok(())
82}