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 { | impl TryFrom<&str> for Role { | ||||||
|     type Error = eyre::Error; |     type Error = eyre::Error; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| use std::{collections::BTreeMap, sync::Arc}; | use std::{collections::BTreeMap, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use axum::{ | use axum::{ | ||||||
|     body::BoxBody, |     body::{BoxBody, Full}, | ||||||
|     extract::{Path, State}, |     extract::{Path, State}, | ||||||
|  |     http::header::{CONTENT_TYPE, SET_COOKIE}, | ||||||
|     response::{IntoResponse, Response}, |     response::{IntoResponse, Response}, | ||||||
|     Form, |     Form, | ||||||
| }; | }; | ||||||
|  | @ -11,10 +12,11 @@ use sqlx::{query, query_as}; | ||||||
| use tracing::{error, info}; | use tracing::{error, info}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     extractor::ExtractUserToken, |     extractor::{ExtractUserToken, TOKEN_COOKIE_NAME}, | ||||||
|     model::user::{Role, User}, |     model::user::{Role, User}, | ||||||
|     state::AppState, |     state::AppState, | ||||||
|     templates::{Page, PageData}, |     templates::{Page, PageData}, | ||||||
|  |     token::Claims, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub async fn get_user( | pub async fn get_user( | ||||||
|  | @ -59,19 +61,24 @@ pub async fn get_user_new( | ||||||
|         if user.role == Role::Admin { |         if user.role == Role::Admin { | ||||||
|             tracing::debug!("Authenticated user is admin"); |             tracing::debug!("Authenticated user is admin"); | ||||||
| 
 | 
 | ||||||
|             return Page::new( |             return Page::new(state.hbs.render( | ||||||
|                 state |                 "new_user.hbs", | ||||||
|                     .hbs |                 &PageData { | ||||||
|                     .render::<PageData>("new_user.hbs", &Default::default()), |                     authed_user: Some(user), | ||||||
|             ); |                     ..Default::default() | ||||||
|  |                 }, | ||||||
|  |             )); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         tracing::debug!("Authenticated user is not an admin"); |         tracing::debug!("Authenticated user is not an admin"); | ||||||
|         return Page::new( |         return Page::new(state.hbs.render::<PageData>( | ||||||
|             state |             "error.hbs", | ||||||
|                 .hbs |             &PageData { | ||||||
|                 .render::<PageData>("error.hbs", &Default::default()), |                 authed_user: Some(user), | ||||||
|         ); |                 message: Some("You are not an admin".into()), | ||||||
|  |                 ..Default::default() | ||||||
|  |             }, | ||||||
|  |         )); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     tracing::debug!("There is no authenticated user"); |     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!( |     let user = match query_as!( | ||||||
|         User, |         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, |         form_data.username, | ||||||
|         hashed_password, |         hashed_password, | ||||||
|  |         <Role as Into<i32>>::into(role), | ||||||
|     ) |     ) | ||||||
|     .fetch_one(&state.pool) |     .fetch_one(&state.pool) | ||||||
|     .await |     .await | ||||||
|  | @ -226,11 +239,51 @@ pub async fn post_user_new( | ||||||
|         .into_response(); |         .into_response(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Page::new(state.hbs.render( |     // Else, create cookie
 | ||||||
|         "new_user.hbs", |     let token = match Claims::new(&user).encode(&state.encoding_key) { | ||||||
|         &PageData { |         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() |                 ..Default::default() | ||||||
|  |             })).into_response(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let content = match state.hbs.render( | ||||||
|  |         "new_user_confirm.hbs", | ||||||
|  |         &PageData { | ||||||
|  |             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::{ | use axum::{ | ||||||
|     http::{header, StatusCode}, |     body::Full, | ||||||
|  |     http::{header, Response, StatusCode}, | ||||||
|     response::IntoResponse, |     response::IntoResponse, | ||||||
| }; | }; | ||||||
| use eyre::{Context, Result}; | use eyre::{Context, Result}; | ||||||
|  | @ -42,10 +43,19 @@ impl Page { | ||||||
| impl IntoResponse for Page { | impl IntoResponse for Page { | ||||||
|     fn into_response(self) -> axum::response::Response { |     fn into_response(self) -> axum::response::Response { | ||||||
|         match self.0 { |         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) => { |             Err(err) => { | ||||||
|                 error!("Error rendering page: {}", err); |                 error!("Rendering page: {}", err); | ||||||
|                 (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response() | 
 | ||||||
|  |                 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.
 |     // An optional flash message. Not all templates will handle this.
 | ||||||
|     pub message: Option<String>, |     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 jsonwebtoken::{errors::Error, DecodingKey, EncodingKey, Header, Validation}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
|  | @ -12,8 +13,13 @@ pub struct Claims { | ||||||
| 
 | 
 | ||||||
| impl Claims { | impl Claims { | ||||||
|     pub fn new(user: &User) -> Self { |     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 { |         Self { | ||||||
|             exp: 0, |             exp: now.timestamp() as usize, | ||||||
|             uid: user.id, |             uid: user.id, | ||||||
|             role: user.role.into(), |             role: user.role.into(), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ | ||||||
| </div> | </div> | ||||||
| {{/if}} | {{/if}} | ||||||
| <ul> | <ul> | ||||||
|     <li>ID: {{new_user.id}}</li> |     <li>ID: {{user.id}}</li> | ||||||
|     <li>Username: {{new_user.username}}</li> |     <li>Username: {{user.username}}</li> | ||||||
| </ul> | </ul> | ||||||
| {{/inline}} | {{/inline}} | ||||||
| {{> root.hbs}} | {{> root.hbs}} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue