diff options
Diffstat (limited to 'src/api/login.rs')
| -rw-r--r-- | src/api/login.rs | 62 |
1 files changed, 56 insertions, 6 deletions
diff --git a/src/api/login.rs b/src/api/login.rs index cc70c18..ec0c9cf 100644 --- a/src/api/login.rs +++ b/src/api/login.rs @@ -113,7 +113,19 @@ pub(crate) async fn bearer( let verified = { let replica = state.doc.read().await; token::verify(&replica.doc, &text, &swarm, now) - }; + } + .and_then(|claims| { + // Ended here where the document can't record it (no registry). + let ended = state + .ended_sessions + .lock_or_recover() + .contains_key(&claims.session); + if ended { + Err(token::Refused::Ended) + } else { + Ok(claims) + } + }); match verified { Ok(claims) => { request @@ -419,17 +431,55 @@ fn over_tls(headers: &axum::http::HeaderMap) -> bool { }) } -/// `DELETE /auth/session`: the session cookie forgotten (the token itself stays valid -/// until it expires; `POST /auth/logout` ends every one). A stale one is ignored anyway. -pub(crate) async fn end_session() -> Response { - ( +/// `DELETE /auth/session`: the session ended, with the token it comes with (the cookie, +/// or `Authorization: Bearer`): recorded in the document (`registry::session_path`), so +/// no node takes its tokens any more, where the swarm has a registry; else on this node +/// only (`AppState::ended_sessions`), others taking them until they expire. And the +/// session cookie forgotten. Without a token, only that. +pub(crate) async fn end_session( + State(state): State<AppState>, + token: Option<Extension<Token>>, +) -> Result<Response, ApiError> { + if let Some(Extension(token)) = token { + end(&state, &token.claims, &token.claims.user).await?; + } + Ok(( StatusCode::NO_CONTENT, [( axum::http::header::SET_COOKIE, format!("{SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0"), )], ) - .into_response() + .into_response()) +} + +/// Ends the session of `claims`, as `by`: recorded in the document where the swarm has a +/// registry, else on this node only. +async fn end(state: &AppState, claims: &Claims, by: &str) -> Result<(), ApiError> { + let path = registry::session_path(&claims.user, &claims.session); + let until = claims.session_exp; + let recorded = if state.is_ready().await { + state + .mutate_doc_for(Some(by.to_owned()), Sign::Protocol, move |doc| { + if !registry::exists(&*doc) { + return Ok::<_, ApiError>(false); + } + crate::document::store::write_path(doc, &path, &json!(until))?; + Ok(true) + }) + .await? + .0 + } else { + false + }; + if !recorded { + let now = state.clock.now_millis().as_u64(); + let mut ended = state.ended_sessions.lock_or_recover(); + ended.retain(|_, until| *until > now); + ended.insert(claims.session.clone(), until); + } + info!(user = %claims.user, session = %claims.session, by = %by, recorded, "Ended a session"); + Ok(()) } /// The claims a refresh of `token` gives (`limit` within its own). |
