feat(server): add routes for private sign-up links
parent
69e84b4266
commit
722317603d
|
@ -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)
|
## [Unreleased](https://git.rustybever.be/Chewing_Bever/otter)
|
||||||
|
|
||||||
* CLI command to add new users
|
* 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)
|
## [0.2.1](https://git.rustybever.be/Chewing_Bever/otter/src/tag/0.2.1)
|
||||||
|
|
||||||
|
|
|
@ -29,9 +29,4 @@ impl<'a> AdminRepository<'a> {
|
||||||
|
|
||||||
Ok(link)
|
Ok(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the signup link with the given ID, if it exists
|
|
||||||
pub fn remove_signup_link(&self, id: i64) -> Result<bool, AuthErr> {
|
|
||||||
self.store.remove_signup_link(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use chrono::{TimeDelta, Utc};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models,
|
SignupLink, models,
|
||||||
store::{AuthErr, GpodderStore},
|
store::{AuthErr, GpodderStore},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,4 +117,13 @@ impl GpodderRepository {
|
||||||
|
|
||||||
self.store.remove_old_sessions(min_last_seen)
|
self.store.remove_old_sessions(min_last_seen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_signup_link(&self, id: i64) -> Result<Option<SignupLink>, 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<bool, AuthErr> {
|
||||||
|
self.store.remove_signup_link(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
Form, RequestExt, Router,
|
Form, RequestExt, Router,
|
||||||
extract::{Request, State},
|
extract::{Path, Request, State},
|
||||||
http::HeaderMap,
|
http::{HeaderMap, StatusCode},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
|
@ -35,6 +35,8 @@ pub fn router(ctx: Context) -> Router<Context> {
|
||||||
// security mistakes
|
// security mistakes
|
||||||
if ctx.config.allow_public_signup {
|
if ctx.config.allow_public_signup {
|
||||||
router = router.route("/signup", get(get_signup).post(post_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
|
router
|
||||||
|
@ -261,3 +263,63 @@ async fn post_signup(
|
||||||
.into_response())
|
.into_response())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_signup_link(
|
||||||
|
State(ctx): State<Context>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
jar: CookieJar,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
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<Context>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
jar: CookieJar,
|
||||||
|
headers: HeaderMap,
|
||||||
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
|
signup: Form<SignupForm>,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue