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();
 | 
				
			||||||
    ))
 | 
					        }
 | 
				
			||||||
    .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()),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        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