feat(server): add config for enabling public signup view
							parent
							
								
									5017bfb710
								
							
						
					
					
						commit
						69e84b4266
					
				|  | @ -8,6 +8,7 @@ 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) | ||||||
| 
 | 
 | ||||||
| ## [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) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| data_dir = "./data" | data_dir = "./data" | ||||||
| log_level = "debug" | log_level = "debug" | ||||||
| 
 | 
 | ||||||
|  | allow_public_signup = true | ||||||
|  | 
 | ||||||
| [net] | [net] | ||||||
| type = "tcp" | type = "tcp" | ||||||
| domain = "127.0.0.1" | domain = "127.0.0.1" | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ pub fn serve(config: &crate::config::Config) -> Result<(), CliError> { | ||||||
|     let ctx = server::Context { |     let ctx = server::Context { | ||||||
|         store, |         store, | ||||||
|         tera: Arc::new(tera), |         tera: Arc::new(tera), | ||||||
|  |         config: config.clone(), | ||||||
|     }; |     }; | ||||||
|     let app = server::app(ctx.clone()); |     let app = server::app(ctx.clone()); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ impl From<LogLevel> for tracing::Level { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize)] | #[derive(Serialize, Deserialize, Clone)] | ||||||
| #[serde(rename_all = "lowercase")] | #[serde(rename_all = "lowercase")] | ||||||
| #[serde(tag = "type")] | #[serde(tag = "type")] | ||||||
| pub enum NetConfig { | pub enum NetConfig { | ||||||
|  | @ -31,12 +31,13 @@ pub enum NetConfig { | ||||||
|     Unix { path: PathBuf }, |     Unix { path: PathBuf }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize)] | #[derive(Serialize, Deserialize, Clone)] | ||||||
| pub struct Config { | pub struct Config { | ||||||
|     pub net: NetConfig, |     pub net: NetConfig, | ||||||
|     pub data_dir: PathBuf, |     pub data_dir: PathBuf, | ||||||
|     pub session_cleanup_interval: u64, |     pub session_cleanup_interval: u64, | ||||||
|     pub log_level: LogLevel, |     pub log_level: LogLevel, | ||||||
|  |     pub allow_public_signup: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for Config { | impl Default for Config { | ||||||
|  | @ -50,6 +51,7 @@ impl Default for Config { | ||||||
|             // Once per day
 |             // Once per day
 | ||||||
|             session_cleanup_interval: 60 * 60 * 24, |             session_cleanup_interval: 60 * 60 * 24, | ||||||
|             log_level: LogLevel::Warn, |             log_level: LogLevel::Warn, | ||||||
|  |             allow_public_signup: false, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ use tower_http::trace::TraceLayer; | ||||||
| pub struct Context { | pub struct Context { | ||||||
|     pub store: ::gpodder::GpodderRepository, |     pub store: ::gpodder::GpodderRepository, | ||||||
|     pub tera: Arc<tera::Tera>, |     pub tera: Arc<tera::Tera>, | ||||||
|  |     pub config: crate::config::Config, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn app(ctx: Context) -> Router { | pub fn app(ctx: Context) -> Router { | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ use crate::{ | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn router(ctx: Context) -> Router<Context> { | pub fn router(ctx: Context) -> Router<Context> { | ||||||
|     Router::new() |     let mut router = Router::new() | ||||||
|         // .layer(middleware::from_fn_with_state(
 |         // .layer(middleware::from_fn_with_state(
 | ||||||
|         //     ctx.clone(),
 |         //     ctx.clone(),
 | ||||||
|         //     auth_web_middleware,
 |         //     auth_web_middleware,
 | ||||||
|  | @ -29,8 +29,15 @@ pub fn router(ctx: Context) -> Router<Context> { | ||||||
|         // Login route needs to be handled differently, as the middleware turns it into a redirect
 |         // Login route needs to be handled differently, as the middleware turns it into a redirect
 | ||||||
|         // loop
 |         // loop
 | ||||||
|         .route("/login", get(get_login).post(post_login)) |         .route("/login", get(get_login).post(post_login)) | ||||||
|         .route("/logout", post(post_logout)) |         .route("/logout", post(post_logout)); | ||||||
|         .route("/signup", get(get_signup).post(post_signup)) | 
 | ||||||
|  |     // If public signups aren't allowed, we don't even register the route to prevent any dumb
 | ||||||
|  |     // security mistakes
 | ||||||
|  |     if ctx.config.allow_public_signup { | ||||||
|  |         router = router.route("/signup", get(get_signup).post(post_signup)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     router | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Middleware that authenticates the current user via the session token. If the credentials are
 | /// Middleware that authenticates the current user via the session token. If the credentials are
 | ||||||
|  | @ -87,10 +94,12 @@ async fn get_login(State(ctx): State<Context>, headers: HeaderMap, jar: CookieJa | ||||||
|     { |     { | ||||||
|         Redirect::to("/").into_response() |         Redirect::to("/").into_response() | ||||||
|     } else { |     } else { | ||||||
|         View::Login |         View::Login { | ||||||
|             .page(&headers) |             signup_note: ctx.config.allow_public_signup, | ||||||
|             .response(&ctx.tera) |         } | ||||||
|             .into_response() |         .page(&headers) | ||||||
|  |         .response(&ctx.tera) | ||||||
|  |         .into_response() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ pub fn initialize_tera() -> tera::Result<tera::Tera> { | ||||||
|             include_str!("templates/views/index.html"), |             include_str!("templates/views/index.html"), | ||||||
|         ), |         ), | ||||||
|         ( |         ( | ||||||
|             View::Login.template(), |             View::Login { signup_note: false }.template(), | ||||||
|             include_str!("templates/views/login.html"), |             include_str!("templates/views/login.html"), | ||||||
|         ), |         ), | ||||||
|         ( |         ( | ||||||
|  |  | ||||||
|  | @ -6,4 +6,8 @@ | ||||||
|         <input type="password" id="password" name="password"> |         <input type="password" id="password" name="password"> | ||||||
|         <input type="submit" value="Login"> |         <input type="submit" value="Login"> | ||||||
|     </form> |     </form> | ||||||
|  | 
 | ||||||
|  |     {% if signup_note %} | ||||||
|  |     <p>Don't have an account yet? <a hx-get="/signup" hx-target="#inner" hx-push-url="/signup">Create one here</a>!</p> | ||||||
|  |     {% endif %} | ||||||
| </article> | </article> | ||||||
|  |  | ||||||
|  | @ -5,7 +5,9 @@ use super::{Query, Template}; | ||||||
| 
 | 
 | ||||||
| pub enum View { | pub enum View { | ||||||
|     Index, |     Index, | ||||||
|     Login, |     Login { | ||||||
|  |         signup_note: bool, | ||||||
|  |     }, | ||||||
|     Sessions(Vec<gpodder::Session>, i64, Option<Query>), |     Sessions(Vec<gpodder::Session>, i64, Option<Query>), | ||||||
|     Users(Vec<gpodder::User>, i64, Option<Query>), |     Users(Vec<gpodder::User>, i64, Option<Query>), | ||||||
|     Signup { |     Signup { | ||||||
|  | @ -32,7 +34,7 @@ impl Template for View { | ||||||
|     fn template(&self) -> &'static str { |     fn template(&self) -> &'static str { | ||||||
|         match self { |         match self { | ||||||
|             Self::Index => "views/index.html", |             Self::Index => "views/index.html", | ||||||
|             Self::Login => "views/login.html", |             Self::Login { .. } => "views/login.html", | ||||||
|             Self::Sessions(..) => "views/sessions.html", |             Self::Sessions(..) => "views/sessions.html", | ||||||
|             Self::Users(..) => "views/users.html", |             Self::Users(..) => "views/users.html", | ||||||
|             Self::Signup { .. } => "views/signup.html", |             Self::Signup { .. } => "views/signup.html", | ||||||
|  | @ -76,6 +78,9 @@ impl Template for View { | ||||||
|                 ctx.insert("username_available", &username_available); |                 ctx.insert("username_available", &username_available); | ||||||
|                 ctx.insert("passwords_match", &passwords_match); |                 ctx.insert("passwords_match", &passwords_match); | ||||||
|             } |             } | ||||||
|  |             Self::Login { signup_note } => { | ||||||
|  |                 ctx.insert("signup_note", &signup_note); | ||||||
|  |             } | ||||||
|             _ => {} |             _ => {} | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue