aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/login.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/login.rs')
-rw-r--r--src/api/login.rs62
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).