diff options
| author | tv <tv@krebsco.de> | 2026-11-07 22:35:22 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-07 22:35:22 +0100 |
| commit | 56a3dca1874fba17c3d174be451af9478b3bfb9d (patch) | |
| tree | 819e3a1d4902c6847b429dd69269ef475d067a45 /src/api/login.rs | |
| parent | 7323e8ed8598cda9784493aa3334d947120b53d8 (diff) | |
dashboard: a token handed over to another member's dashboard
Opening another member's dashboard passes a token reading only, for
five minutes, in the URL's fragment; its login page begins a session
with it. Tokens may be asked for with a shorter lifetime.
Auth plan M3 (hand-off).
Diffstat (limited to 'src/api/login.rs')
| -rw-r--r-- | src/api/login.rs | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/src/api/login.rs b/src/api/login.rs index 1907bac..3b4ee32 100644 --- a/src/api/login.rs +++ b/src/api/login.rs @@ -146,6 +146,9 @@ pub(crate) async fn bearer( pub(crate) struct LimitRequest { #[serde(default)] limit: Option<Vec<CapRequest>>, + /// A shorter life than `--token-lifetime`, in seconds: a token to hand over, say. + #[serde(default)] + lifetime: Option<u64>, } impl LimitRequest { @@ -209,7 +212,8 @@ pub(crate) async fn login( Some("log in with a password or through the proxy; a token is refreshed at /auth/refresh".to_owned()), )); } - let limit = body.map(|Json(body)| body).unwrap_or_default().limit()?; + let body = body.map(|Json(body)| body).unwrap_or_default(); + let limit = body.limit()?; let (swarm, now) = issuing(&state).await?; { let replica = state.doc.read().await; @@ -222,11 +226,17 @@ pub(crate) async fn login( )); } } - let claims = new_login(&state, swarm, now, &user.0, via.0, limit); + let claims = new_login(&state, swarm, now, &user.0, via.0, limit, body.lifetime); info!(user = %user.0, login = via.0, session = %claims.session, "Logged in"); answer(&state, &claims) } +/// How long a token lives: `--token-lifetime`, or less if `asked` (seconds) is less. +fn lifetime_ms(state: &AppState, asked: Option<u64>) -> u64 { + let lifetime = state.token_lifetime.as_millis() as u64; + asked.map_or(lifetime, |secs| secs.saturating_mul(1000).min(lifetime)) +} + /// The claims of a new login of `user`, by `login`, now: a session of its own. fn new_login( state: &AppState, @@ -235,6 +245,7 @@ fn new_login( user: &str, login: &str, limit: Option<Vec<Capability>>, + lifetime: Option<u64>, ) -> Claims { let session_exp = now.saturating_add(state.session_lifetime.as_millis() as u64); Claims { @@ -245,7 +256,7 @@ fn new_login( login: login.to_owned(), iat: now, exp: now - .saturating_add(state.token_lifetime.as_millis() as u64) + .saturating_add(lifetime_ms(state, lifetime)) .min(session_exp), session: token::new_session(), session_exp, @@ -292,6 +303,7 @@ pub(crate) async fn login_key( ) -> Result<Response, ApiError> { let limit = LimitRequest { limit: request.limit, + lifetime: None, } .limit()?; let (swarm, now) = issuing(&state).await?; @@ -322,7 +334,7 @@ pub(crate) async fn login_key( ApiError(StatusCode::UNAUTHORIZED, Some(why)) })?; } - let claims = new_login(&state, swarm, now, &request.user, "key", limit); + let claims = new_login(&state, swarm, now, &request.user, "key", limit, None); info!(user = %request.user, login = "key", session = %claims.session, "Logged in"); answer(&state, &claims) } @@ -396,7 +408,9 @@ async fn refreshed( Some("the login ended: log in again".to_owned()), )); } - let limit = match body.map(|Json(body)| body).unwrap_or_default().limit()? { + let body = body.map(|Json(body)| body).unwrap_or_default(); + let lifetime = body.lifetime; + let limit = match body.limit()? { None => earlier.limit.clone(), Some(limit) => { if earlier @@ -418,7 +432,7 @@ async fn refreshed( issuer: state.local_id().to_owned(), iat: now, exp: now - .saturating_add(state.token_lifetime.as_millis() as u64) + .saturating_add(lifetime_ms(state, lifetime)) .min(earlier.session_exp), limit, ..(**earlier).clone() |
