From 722317603da31fbf9073f416bf87cb966835c855 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 28 Aug 2025 14:21:26 +0200 Subject: [PATCH] feat(server): add routes for private sign-up links --- CHANGELOG.md | 3 +- gpodder/src/repository/admin.rs | 5 --- gpodder/src/repository/mod.rs | 11 +++++- otter/src/server/web/auth.rs | 66 ++++++++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac346d..c4ad50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/otter) * CLI command to add new users -* Added public sign-up page (disabled by default) +* Public sign-up page (disabled by default) +* Private sign-up links ## [0.2.1](https://git.rustybever.be/Chewing_Bever/otter/src/tag/0.2.1) diff --git a/gpodder/src/repository/admin.rs b/gpodder/src/repository/admin.rs index 495d982..3c98a52 100644 --- a/gpodder/src/repository/admin.rs +++ b/gpodder/src/repository/admin.rs @@ -29,9 +29,4 @@ impl<'a> AdminRepository<'a> { Ok(link) } - - /// Remove the signup link with the given ID, if it exists - pub fn remove_signup_link(&self, id: i64) -> Result { - self.store.remove_signup_link(id) - } } diff --git a/gpodder/src/repository/mod.rs b/gpodder/src/repository/mod.rs index f850a05..100f49f 100644 --- a/gpodder/src/repository/mod.rs +++ b/gpodder/src/repository/mod.rs @@ -8,7 +8,7 @@ use chrono::{TimeDelta, Utc}; use rand::rngs::OsRng; use crate::{ - models, + SignupLink, models, store::{AuthErr, GpodderStore}, }; @@ -117,4 +117,13 @@ impl GpodderRepository { self.store.remove_old_sessions(min_last_seen) } + + pub fn get_signup_link(&self, id: i64) -> Result, AuthErr> { + self.store.get_signup_link(id) + } + + /// Remove the signup link with the given ID, if it exists + pub fn remove_signup_link(&self, id: i64) -> Result { + self.store.remove_signup_link(id) + } } diff --git a/otter/src/server/web/auth.rs b/otter/src/server/web/auth.rs index 34b84af..198d53a 100644 --- a/otter/src/server/web/auth.rs +++ b/otter/src/server/web/auth.rs @@ -1,7 +1,7 @@ use axum::{ Form, RequestExt, Router, - extract::{Request, State}, - http::HeaderMap, + extract::{Path, Request, State}, + http::{HeaderMap, StatusCode}, middleware::Next, response::{IntoResponse, Redirect, Response}, routing::{get, post}, @@ -35,6 +35,8 @@ pub fn router(ctx: Context) -> Router { // security mistakes if ctx.config.allow_public_signup { router = router.route("/signup", get(get_signup).post(post_signup)) + } else { + router = router.route("/signup/{id}", get(get_signup_link).post(post_signup_link)); } router @@ -261,3 +263,63 @@ async fn post_signup( .into_response()) } } + +async fn get_signup_link( + State(ctx): State, + Path(id): Path, + headers: HeaderMap, + jar: CookieJar, +) -> AppResult { + let ctx_clone = ctx.clone(); + let signup_link = tokio::task::spawn_blocking(move || ctx_clone.store.get_signup_link(id)) + .await + .unwrap()?; + + // Just redirect to / if it's an invalid sign-up link + if signup_link.is_none() + || extract_session(ctx.clone(), &jar) + .await + .ok() + .flatten() + .is_some() + { + Ok(Redirect::to("/").into_response()) + } else { + Ok(View::Signup { + username: None, + username_available: true, + passwords_match: true, + } + .page(&headers) + .response(&ctx.tera) + .into_response()) + } +} + +async fn post_signup_link( + State(ctx): State, + Path(id): Path, + jar: CookieJar, + headers: HeaderMap, + user_agent: Option>, + signup: Form, +) -> AppResult { + let ctx_clone = ctx.clone(); + + if tokio::task::spawn_blocking(move || ctx_clone.store.get_signup_link(id)) + .await + .unwrap()? + .is_some() + { + let response = post_signup(State(ctx.clone()), jar, headers, user_agent, signup).await?; + + // Signup flow was successful, so remove the signup link + tokio::task::spawn_blocking(move || ctx.store.remove_signup_link(id)) + .await + .unwrap()?; + + Ok(response) + } else { + Ok(StatusCode::NOT_FOUND.into_response()) + } +}