summaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-09-04 01:25:31 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-09-04 01:25:54 -0400
commit072dfa9a0bae5b7e9ea1caa97f6a90bd576a5d95 (patch)
tree3194c56bbf1b9729d07198973815c0cb88a9e5c6 /src/login
parent2965a788cfcf4a0386cb8832e0d96491bf54c1d3 (diff)
Expire sessions after 90 days.
Diffstat (limited to 'src/login')
-rw-r--r--src/login/extract/login.rs3
-rw-r--r--src/login/repo/tokens.rs18
-rw-r--r--src/login/routes.rs6
3 files changed, 23 insertions, 4 deletions
diff --git a/src/login/extract/login.rs b/src/login/extract/login.rs
index f49933a..ce820f1 100644
--- a/src/login/extract/login.rs
+++ b/src/login/extract/login.rs
@@ -6,6 +6,7 @@ use axum::{
use sqlx::sqlite::SqlitePool;
use crate::{
+ clock::RequestedAt,
error::InternalError,
login::{
extract::IdentityToken,
@@ -22,11 +23,13 @@ impl FromRequestParts<SqlitePool> for Login {
state: &SqlitePool,
) -> Result<Self, Self::Rejection> {
let identity_token = IdentityToken::from_request_parts(parts, state).await?;
+ let requested_at = RequestedAt::from_request_parts(parts, state).await?;
let token = identity_token.token().ok_or(LoginError::Forbidden)?;
let db = State::<SqlitePool>::from_request_parts(parts, state).await?;
let mut tx = db.begin().await?;
+ tx.tokens().expire(requested_at.timestamp()).await?;
let login = tx.tokens().validate(token).await?;
tx.commit().await?;
diff --git a/src/login/repo/tokens.rs b/src/login/repo/tokens.rs
index 584f6dc..3ec3d63 100644
--- a/src/login/repo/tokens.rs
+++ b/src/login/repo/tokens.rs
@@ -1,3 +1,4 @@
+use chrono::TimeDelta;
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use uuid::Uuid;
@@ -62,6 +63,23 @@ impl<'c> Tokens<'c> {
Ok(())
}
+ pub async fn expire(&mut self, expire_at: DateTime) -> Result<(), BoxedError> {
+ // Somewhat arbitrarily, expire after 90 days.
+ let expired_issue_at = expire_at - TimeDelta::days(90);
+ sqlx::query!(
+ r#"
+ delete
+ from token
+ where issued_at < $1
+ "#,
+ expired_issue_at,
+ )
+ .execute(&mut *self.0)
+ .await?;
+
+ Ok(())
+ }
+
/// Validate a token by its secret, retrieving the associated Login record.
/// Will return [None] if the token is not valid.
pub async fn validate(&mut self, secret: &str) -> Result<Option<Login>, BoxedError> {
diff --git a/src/login/routes.rs b/src/login/routes.rs
index a00982d..2269ea6 100644
--- a/src/login/routes.rs
+++ b/src/login/routes.rs
@@ -5,10 +5,9 @@ use axum::{
routing::post,
Router,
};
-use chrono::Utc;
use sqlx::sqlite::SqlitePool;
-use crate::error::InternalError;
+use crate::{clock::RequestedAt, error::InternalError};
use super::{
extract::IdentityToken,
@@ -29,11 +28,10 @@ struct Login {
async fn on_login(
State(db): State<SqlitePool>,
+ RequestedAt(now): RequestedAt,
identity: IdentityToken,
Form(form): Form<Login>,
) -> Result<impl IntoResponse, InternalError> {
- let now = Utc::now();
-
if identity.token().is_some() {
return Ok((StatusCode::BAD_REQUEST, identity, "already logged in"));
}