forked from alemi/upub
feat: added refresh route (optional)
This commit is contained in:
parent
570b045bf0
commit
c5b06cd16b
3 changed files with 52 additions and 9 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue