From c5b06cd16b9c600f96db9160e792b7f7bc5793de Mon Sep 17 00:00:00 2001 From: alemi Date: Mon, 27 May 2024 07:26:31 +0200 Subject: [PATCH] feat: added refresh route (optional) --- src/config.rs | 3 ++ src/routes/activitypub/auth.rs | 53 +++++++++++++++++++++++++++++----- src/routes/activitypub/mod.rs | 5 ++-- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index aba6a41e..5ebd2476 100644 --- a/src/config.rs +++ b/src/config.rs @@ -70,6 +70,9 @@ pub struct SecurityConfig { #[serde_inline_default(true)] pub show_reply_ids: bool, + + #[serde(default)] + pub allow_login_refresh: bool, } diff --git a/src/routes/activitypub/auth.rs b/src/routes/activitypub/auth.rs index c1928f00..46081cfb 100644 --- a/src/routes/activitypub/auth.rs +++ b/src/routes/activitypub/auth.rs @@ -1,6 +1,6 @@ use axum::{http::StatusCode, extract::State, Json}; use rand::Rng; -use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter}; +use sea_orm::{ActiveValue::{Set, NotSet}, ColumnTrait, Condition, EntityTrait, QueryFilter}; use crate::{errors::UpubError, model, server::{admin::Administrable, Context}}; @@ -18,6 +18,15 @@ pub struct AuthSuccess { expires: chrono::DateTime, } +fn token() -> String { + // TODO should probably use crypto-safe rng + rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(128) + .map(char::from) + .collect() +} + pub async fn login( State(ctx): State, Json(login): Json @@ -32,12 +41,7 @@ pub async fn login( .await? { Some(x) => { - // TODO should probably use crypto-safe rng - let token : String = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(128) - .map(char::from) - .collect(); + let token = token(); let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6); model::session::Entity::insert( model::session::ActiveModel { @@ -58,6 +62,41 @@ pub async fn login( } } +#[derive(Debug, Clone, serde::Deserialize)] +pub struct RefreshForm { + token: String, +} + +pub async fn refresh( + State(ctx): State, + Json(login): Json +) -> crate::Result> { + if !ctx.cfg().security.allow_login_refresh { + return Err(UpubError::forbidden()); + } + + let prev = model::session::Entity::find() + .filter(model::session::Column::Secret.eq(login.token)) + .one(ctx.db()) + .await? + .ok_or_else(UpubError::unauthorized)?; + + let token = token(); + let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6); + let user = prev.actor; + let new_session = model::session::ActiveModel { + internal: NotSet, + actor: Set(user.clone()), + secret: Set(token.clone()), + expires: Set(expires), + }; + model::session::Entity::insert(new_session) + .exec(ctx.db()) + .await?; + + Ok(Json(AuthSuccess { token, expires, user })) +} + #[derive(Debug, Clone, serde::Deserialize)] pub struct RegisterForm { username: String, diff --git a/src/routes/activitypub/mod.rs b/src/routes/activitypub/mod.rs index 743b84c8..035e0dc3 100644 --- a/src/routes/activitypub/mod.rs +++ b/src/routes/activitypub/mod.rs @@ -11,7 +11,7 @@ pub mod well_known; pub mod jsonld; pub use jsonld::JsonLD; -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post, put}, Router}; +use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router}; pub trait ActivityPubRouter { fn ap_routes(self) -> Self; @@ -35,8 +35,9 @@ impl ActivityPubRouter for Router { .route("/outbox", get(ap::outbox::get)) .route("/outbox/page", get(ap::outbox::page)) // AUTH routes - .route("/auth", post(ap::auth::login)) .route("/auth", put(ap::auth::register)) + .route("/auth", post(ap::auth::login)) + .route("/auth", patch(ap::auth::refresh)) // .well-known and discovery .route("/.well-known/webfinger", get(ap::well_known::webfinger)) .route("/.well-known/host-meta", get(ap::well_known::host_meta))