feat: added refresh route (optional)

This commit is contained in:
əlemi 2024-05-27 07:26:31 +02:00
parent 570b045bf0
commit c5b06cd16b
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 52 additions and 9 deletions

View file

@ -70,6 +70,9 @@ pub struct SecurityConfig {
#[serde_inline_default(true)] #[serde_inline_default(true)]
pub show_reply_ids: bool, pub show_reply_ids: bool,
#[serde(default)]
pub allow_login_refresh: bool,
} }

View file

@ -1,6 +1,6 @@
use axum::{http::StatusCode, extract::State, Json}; use axum::{http::StatusCode, extract::State, Json};
use rand::Rng; 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}}; use crate::{errors::UpubError, model, server::{admin::Administrable, Context}};
@ -18,6 +18,15 @@ pub struct AuthSuccess {
expires: chrono::DateTime<chrono::Utc>, expires: chrono::DateTime<chrono::Utc>,
} }
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( pub async fn login(
State(ctx): State<Context>, State(ctx): State<Context>,
Json(login): Json<LoginForm> Json(login): Json<LoginForm>
@ -32,12 +41,7 @@ pub async fn login(
.await? .await?
{ {
Some(x) => { Some(x) => {
// TODO should probably use crypto-safe rng let token = token();
let token : String = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(128)
.map(char::from)
.collect();
let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6); let expires = chrono::Utc::now() + std::time::Duration::from_secs(3600 * 6);
model::session::Entity::insert( model::session::Entity::insert(
model::session::ActiveModel { 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<Context>,
Json(login): Json<RefreshForm>
) -> crate::Result<Json<AuthSuccess>> {
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)] #[derive(Debug, Clone, serde::Deserialize)]
pub struct RegisterForm { pub struct RegisterForm {
username: String, username: String,

View file

@ -11,7 +11,7 @@ pub mod well_known;
pub mod jsonld; pub mod jsonld;
pub use jsonld::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 { pub trait ActivityPubRouter {
fn ap_routes(self) -> Self; fn ap_routes(self) -> Self;
@ -35,8 +35,9 @@ impl ActivityPubRouter for Router<crate::server::Context> {
.route("/outbox", get(ap::outbox::get)) .route("/outbox", get(ap::outbox::get))
.route("/outbox/page", get(ap::outbox::page)) .route("/outbox/page", get(ap::outbox::page))
// AUTH routes // AUTH routes
.route("/auth", post(ap::auth::login))
.route("/auth", put(ap::auth::register)) .route("/auth", put(ap::auth::register))
.route("/auth", post(ap::auth::login))
.route("/auth", patch(ap::auth::refresh))
// .well-known and discovery // .well-known and discovery
.route("/.well-known/webfinger", get(ap::well_known::webfinger)) .route("/.well-known/webfinger", get(ap::well_known::webfinger))
.route("/.well-known/host-meta", get(ap::well_known::host_meta)) .route("/.well-known/host-meta", get(ap::well_known::host_meta))