working user signup!
This commit is contained in:
		
							parent
							
								
									c5c884f069
								
							
						
					
					
						commit
						dd17a68cf2
					
				
					 5 changed files with 107 additions and 25 deletions
				
			
		|  | @ -49,6 +49,17 @@ impl From<i32> for Role { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Into<i32> for Role { | ||||
|     fn into(self) -> i32 { | ||||
|         match self { | ||||
|             Self::Viewer => 1, | ||||
|             Self::Editor => 2, | ||||
|             Self::Manager => 3, | ||||
|             Self::Admin => 4, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<&str> for Role { | ||||
|     type Error = eyre::Error; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| use std::{collections::BTreeMap, sync::Arc}; | ||||
| 
 | ||||
| use axum::{ | ||||
|     body::BoxBody, | ||||
|     body::{BoxBody, Full}, | ||||
|     extract::{Path, State}, | ||||
|     http::header::{CONTENT_TYPE, SET_COOKIE}, | ||||
|     response::{IntoResponse, Response}, | ||||
|     Form, | ||||
| }; | ||||
|  | @ -11,10 +12,11 @@ use sqlx::{query, query_as}; | |||
| use tracing::{error, info}; | ||||
| 
 | ||||
| use crate::{ | ||||
|     extractor::ExtractUserToken, | ||||
|     extractor::{ExtractUserToken, TOKEN_COOKIE_NAME}, | ||||
|     model::user::{Role, User}, | ||||
|     state::AppState, | ||||
|     templates::{Page, PageData}, | ||||
|     token::Claims, | ||||
| }; | ||||
| 
 | ||||
| pub async fn get_user( | ||||
|  | @ -59,19 +61,24 @@ pub async fn get_user_new( | |||
|         if user.role == Role::Admin { | ||||
|             tracing::debug!("Authenticated user is admin"); | ||||
| 
 | ||||
|             return Page::new( | ||||
|                 state | ||||
|                     .hbs | ||||
|                     .render::<PageData>("new_user.hbs", &Default::default()), | ||||
|             ); | ||||
|             return Page::new(state.hbs.render( | ||||
|                 "new_user.hbs", | ||||
|                 &PageData { | ||||
|                     authed_user: Some(user), | ||||
|                     ..Default::default() | ||||
|                 }, | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
|         tracing::debug!("Authenticated user is not an admin"); | ||||
|         return Page::new( | ||||
|             state | ||||
|                 .hbs | ||||
|                 .render::<PageData>("error.hbs", &Default::default()), | ||||
|         ); | ||||
|         return Page::new(state.hbs.render::<PageData>( | ||||
|             "error.hbs", | ||||
|             &PageData { | ||||
|                 authed_user: Some(user), | ||||
|                 message: Some("You are not an admin".into()), | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     tracing::debug!("There is no authenticated user"); | ||||
|  | @ -189,11 +196,17 @@ pub async fn post_user_new( | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let role = match existing_user.is_some() { | ||||
|         true => Role::Viewer, | ||||
|         false => Role::Admin, | ||||
|     }; | ||||
| 
 | ||||
|     let user = match query_as!( | ||||
|         User, | ||||
|         r#"INSERT INTO users (username, password) VALUES ($1, $2) RETURNING *"#, | ||||
|         r#"INSERT INTO users (username, password, role) VALUES ($1, $2, $3) RETURNING *"#, | ||||
|         form_data.username, | ||||
|         hashed_password, | ||||
|         <Role as Into<i32>>::into(role), | ||||
|     ) | ||||
|     .fetch_one(&state.pool) | ||||
|     .await | ||||
|  | @ -226,11 +239,51 @@ pub async fn post_user_new( | |||
|         .into_response(); | ||||
|     } | ||||
| 
 | ||||
|     Page::new(state.hbs.render( | ||||
|         "new_user.hbs", | ||||
|     // Else, create cookie
 | ||||
|     let token = match Claims::new(&user).encode(&state.encoding_key) { | ||||
|         Ok(token) => token, | ||||
|         Err(err) => { | ||||
|             error!("Encoding token: {}", err); | ||||
| 
 | ||||
|             return Page::new(state.hbs.render("error.hbs", &PageData{ 
 | ||||
|                 message: Some("Internal server error. Note: your account was created successfully, please log in manually.".into()), | ||||
|                 ..Default::default() | ||||
|             })).into_response(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let content = match state.hbs.render( | ||||
|         "new_user_confirm.hbs", | ||||
|         &PageData { | ||||
|             ..Default::default() | ||||
|             authed_user: Some(user.clone()), | ||||
|             user: Some(user), | ||||
|             message: Some("Your account has been created!".into()), | ||||
|         }, | ||||
|     )) | ||||
|     .into_response() | ||||
|     ) { | ||||
|         Ok(content) => content, | ||||
|         Err(err) => { | ||||
|             error!("Rendering content: {}", err); | ||||
| 
 | ||||
|             return Page::new(state.hbs.render("error.hbs", &PageData{ 
 | ||||
|                 message: Some("Internal server error. Note: your account was created successfully, please log in manually.".into()), | ||||
|                 ..Default::default() | ||||
|             })).into_response(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return match Response::builder() | ||||
|         .header(SET_COOKIE, format!("{}={}", TOKEN_COOKIE_NAME, token)) | ||||
|         .header(CONTENT_TYPE, "text/html; charset=utf-8") | ||||
|         .body(Full::from(content)) | ||||
|     { | ||||
|         Ok(resp) => resp.into_response(), | ||||
|         Err(err) => { | ||||
|             error!("Building response: {}", err); | ||||
| 
 | ||||
|             return Page::new(state.hbs.render("error.hbs", &PageData{ 
 | ||||
|                 message: Some("Internal server error. Note: your account was created successfully, please log in manually.".into()), | ||||
|                 ..Default::default() | ||||
|             })).into_response(); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| use axum::{ | ||||
|     http::{header, StatusCode}, | ||||
|     body::Full, | ||||
|     http::{header, Response, StatusCode}, | ||||
|     response::IntoResponse, | ||||
| }; | ||||
| use eyre::{Context, Result}; | ||||
|  | @ -42,10 +43,19 @@ impl Page { | |||
| impl IntoResponse for Page { | ||||
|     fn into_response(self) -> axum::response::Response { | ||||
|         match self.0 { | ||||
|             Ok(s) => ([(header::CONTENT_TYPE, "text/html; charset=utf-8")], s).into_response(), | ||||
|             Ok(s) => Response::builder() | ||||
|                 .header(header::CONTENT_TYPE, "text/html; charset=utf-8") | ||||
|                 .body(Full::from(s)) | ||||
|                 .unwrap() | ||||
|                 .into_response(), | ||||
|             Err(err) => { | ||||
|                 error!("Error rendering page: {}", err); | ||||
|                 (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response() | ||||
|                 error!("Rendering page: {}", err); | ||||
| 
 | ||||
|                 return Response::builder() | ||||
|                     .status(StatusCode::INTERNAL_SERVER_ERROR) | ||||
|                     .body(Full::from("Internal server error")) | ||||
|                     .unwrap() | ||||
|                     .into_response(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -57,4 +67,6 @@ pub struct PageData { | |||
| 
 | ||||
|     // An optional flash message. Not all templates will handle this.
 | ||||
|     pub message: Option<String>, | ||||
| 
 | ||||
|     pub authed_user: Option<User>, | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| use chrono::{Duration, Utc}; | ||||
| use jsonwebtoken::{errors::Error, DecodingKey, EncodingKey, Header, Validation}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
|  | @ -12,8 +13,13 @@ pub struct Claims { | |||
| 
 | ||||
| impl Claims { | ||||
|     pub fn new(user: &User) -> Self { | ||||
|         // Expire tokens after 90 days
 | ||||
|         // TODO: give tokens an ID to expire them earlier if necessary? Or a salt that's changed on
 | ||||
|         // password update
 | ||||
|         let now = Utc::now() + Duration::days(90); | ||||
| 
 | ||||
|         Self { | ||||
|             exp: 0, | ||||
|             exp: now.timestamp() as usize, | ||||
|             uid: user.id, | ||||
|             role: user.role.into(), | ||||
|         } | ||||
|  |  | |||
|  | @ -6,8 +6,8 @@ | |||
| </div> | ||||
| {{/if}} | ||||
| <ul> | ||||
|     <li>ID: {{new_user.id}}</li> | ||||
|     <li>Username: {{new_user.username}}</li> | ||||
|     <li>ID: {{user.id}}</li> | ||||
|     <li>Username: {{user.username}}</li> | ||||
| </ul> | ||||
| {{/inline}} | ||||
| {{> root.hbs}} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue