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) | ||||
| 
 | ||||
| * 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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<bool, AuthErr> { | ||||
|         self.store.remove_signup_link(id) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<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::{ | ||||
|     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<Context> { | |||
|     // 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<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