feat: Implement magic link authentication

This commit introduces a new magic link authentication flow.
The desktop client can now request a temporary, one-time-use token from
the server.
This token is then used to open a URL in the system browser, which
redirects to the server.
The server consumes the token, installs a regular web session, and
redirects the user
This commit is contained in:
2026-04-19 16:00:12 +02:00
parent 0d5c2f5888
commit 76e8ee18e9
13 changed files with 690 additions and 39 deletions
+34 -1
View File
@@ -1,7 +1,10 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use std::time::{Duration, Instant};
use axum_extra::extract::cookie::{Cookie, SameSite};
use rand::Rng;
use rand::distributions::Alphanumeric;
use tokio::sync::RwLock;
pub struct WebSession {
@@ -15,3 +18,33 @@ pub type SessionStore = Arc<RwLock<HashMap<String, WebSession>>>;
pub fn new_store() -> SessionStore {
Arc::new(RwLock::new(HashMap::new()))
}
/// Cookie name shared by login and magic-link flows.
pub const SESSION_COOKIE: &str = "session";
/// 43 alphanumeric chars ≈ 256 bits of entropy — enough for a session
/// or short-lived magic-link token.
pub const TOKEN_LEN: usize = 43;
/// CSPRNG-backed token. Used for both long-lived session cookies and
/// short-lived magic-link tokens; both need the same entropy.
pub fn generate_token() -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(TOKEN_LEN)
.map(char::from)
.collect()
}
/// Builds a `HttpOnly + SameSite=Strict + Path=/` cookie for the session
/// token. `secure` is wired from `Config::cookie_secure` so local HTTP dev
/// can opt out — browsers refuse `Secure` cookies on non-HTTPS origins.
pub fn build_session_cookie(value: String, max_age: Duration, secure: bool) -> Cookie<'static> {
Cookie::build((SESSION_COOKIE, value))
.http_only(true)
.secure(secure)
.same_site(SameSite::Strict)
.path("/")
.max_age(time::Duration::seconds(max_age.as_secs() as i64))
.build()
}