diff --git a/api/assets/new-user-confirmation.html b/api/assets/new-user-confirmation.html index 1d45cfd..058b0be 100644 --- a/api/assets/new-user-confirmation.html +++ b/api/assets/new-user-confirmation.html @@ -137,7 +137,7 @@
- Confirm Email Address + Confirm Email Address
@@ -152,7 +152,7 @@

If that doesn't work, copy and paste the following link in your browser:

-

$AUTH_TOKEN

+

$VERIFICATION_TOKEN

diff --git a/api/src/db/user.rs b/api/src/db/user.rs index e600a5d..fc4f13d 100644 --- a/api/src/db/user.rs +++ b/api/src/db/user.rs @@ -1,10 +1,13 @@ +use std::fmt::Debug; + use sqlx::prelude::FromRow; +use tracing::error; use crate::models::AppError; use super::DbPool; -#[derive(Debug)] +#[derive(Clone)] pub struct NewUserEntity { pub username: String, pub password: String, @@ -12,6 +15,17 @@ pub struct NewUserEntity { pub name: String, } +impl Debug for NewUserEntity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NewUserEntity") + .field("username", &self.username) + .field("password", &"********") + .field("email", &self.email) + .field("name", &self.name) + .finish() + } +} + #[allow(dead_code)] #[derive(Debug, FromRow)] pub struct UserEntity { @@ -30,7 +44,7 @@ pub async fn insert_new_user( password, email, name, - } = new_user; + } = new_user.clone(); sqlx::query_as::<_, UserEntity>("INSERT INTO public.user (username, email, password, name) VALUES ($1, $2, $3, $4) RETURNING id, username, email, name, status_id;") .bind(username) @@ -39,7 +53,8 @@ pub async fn insert_new_user( .bind(name) .fetch_one(pool).await .map_err(|err| { - eprintln!("Error inserting NewUserEntity {err:?}"); + error!(%err, record = ?new_user, "Cannot insert new user record"); + AppError::from(err) }) } diff --git a/api/src/models/error.rs b/api/src/models/error.rs index ff85385..4131187 100644 --- a/api/src/models/error.rs +++ b/api/src/models/error.rs @@ -26,6 +26,7 @@ impl AppError { Self::new(ErrorKind::InvalidToken) } + #[allow(dead_code)] pub fn invalid_token_audience(audience: &str) -> Self { Self::new(ErrorKind::InvalidTokenAudience(audience.to_owned())) } @@ -116,7 +117,7 @@ enum ErrorKind { impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { - if let ErrorKind::DuplicateRecord = &self.kind { } + if let ErrorKind::DuplicateRecord = &self.kind {} StatusCode::INTERNAL_SERVER_ERROR.into_response() } diff --git a/api/src/requests/mod.rs b/api/src/requests/mod.rs index deb4e5a..baa7aa9 100644 --- a/api/src/requests/mod.rs +++ b/api/src/requests/mod.rs @@ -52,7 +52,7 @@ pub async fn start_app(pool: DbPool, env: Environment) -> Result<(), AppError> { let path = request .extensions() .get::() - .map(MatchedPath::as_str).unwrap(); + .map(MatchedPath::as_str).unwrap_or(request.uri().path()); info_span!("api_request", request_id = %Ulid::new(), method = %request.method(), %path, status = tracing::field::Empty) }) diff --git a/api/src/requests/user/create/handler.rs b/api/src/requests/user/create/handler.rs index 6f4ac41..ec949ba 100644 --- a/api/src/requests/user/create/handler.rs +++ b/api/src/requests/user/create/handler.rs @@ -69,13 +69,13 @@ async fn register_new_user_request( } })?; - let (auth_token, expiration) = generate_new_user_token(signing_key, user_id); + let (verification_token, expiration) = generate_new_user_token(signing_key, user_id); let response_body = if send_verification_email { let new_user_confirmation_message = UserConfirmationMessage { email, name, - auth_token: auth_token.clone(), + verification_token: verification_token.clone(), }; let _ = email_sender @@ -87,13 +87,13 @@ async fn register_new_user_request( UserRegistrationResponse { id: user_id, expiration, - auth_token: None, + verification_token: None, } } else { UserRegistrationResponse { id: user_id, expiration, - auth_token: Some(auth_token), + verification_token: Some(verification_token), } }; diff --git a/api/src/requests/user/create/models/registration_response.rs b/api/src/requests/user/create/models/registration_response.rs index d25a20f..dca7bc4 100644 --- a/api/src/requests/user/create/models/registration_response.rs +++ b/api/src/requests/user/create/models/registration_response.rs @@ -13,5 +13,5 @@ pub struct UserRegistrationResponse { #[serde(serialize_with = "humantime_serde::serialize")] pub expiration: SystemTime, - pub auth_token: Option, + pub verification_token: Option, } diff --git a/api/src/requests/user/mod.rs b/api/src/requests/user/mod.rs index 999db39..175d394 100644 --- a/api/src/requests/user/mod.rs +++ b/api/src/requests/user/mod.rs @@ -1,14 +1,19 @@ -use axum::{routing::post, Router}; +mod create; +mod verify; + +use axum::{ + routing::{get, post}, + Router, +}; + use create::user_registration_post_handler; +use verify::user_verification_get_handler; use super::AppState; -pub mod create; -// pub mod verify; - pub fn requests(app_state: AppState) -> Router { - Router::new().route( - "/user", - post(user_registration_post_handler).with_state(app_state.clone()), - ) + Router::new() + .route("/user", post(user_registration_post_handler)) + .route("/user/:user_id/verify", get(user_verification_get_handler)) + .with_state(app_state.clone()) } diff --git a/api/src/requests/user/verify/handler.rs b/api/src/requests/user/verify/handler.rs index 542ab1a..a6e183e 100644 --- a/api/src/requests/user/verify/handler.rs +++ b/api/src/requests/user/verify/handler.rs @@ -1,28 +1,43 @@ use axum::{ + debug_handler, extract::{Path, Query, State}, response::{IntoResponse, Response}, - routing::get, - Json, Router, + Json, }; use http::StatusCode; -use pasetors::claims::ClaimsValidationRules; +use pasetors::{claims::ClaimsValidationRules, keys::SymmetricKey, version4::V4}; +use tracing::{debug, error}; use crate::{ - db::verify_user, models::ApiResponse, requests::AppState, services::auth_token::verify_token, + db::{verify_user, DbPool}, + models::ApiResponse, + requests::AppState, + services::auth_token::verify_token, }; -use super::{UserVerifyGetRequest, UserVerifyGetResponse}; +use super::{UserVerifyGetParams, UserVerifyGetResponse}; -pub fn request(app_state: AppState) -> Router { - Router::new().route("/:user_id/verify", get(get_handler).with_state(app_state)) +#[debug_handler] +pub async fn user_verification_get_handler( + State(state): State, + Path(user_id): Path, + Query(query): Query, +) -> Result { + let pool = state.pool(); + let env = state.env(); + + let UserVerifyGetParams { verification_token } = query; + let token_key = env.token_key(); + verify_new_user_request(pool, user_id, verification_token, token_key).await } -async fn get_handler( - State(app_state): State, - Path(user_id): Path, - Query(request): Query, +async fn verify_new_user_request( + pool: &DbPool, + user_id: i32, + verification_token: String, + token_key: &SymmetricKey, ) -> Result { - let UserVerifyGetRequest { auth_token } = request; + debug!(user_id); let validation_rules = { let mut rules = ClaimsValidationRules::new(); @@ -31,13 +46,18 @@ async fn get_handler( rules }; - let key = app_state.env().token_key(); - let response = verify_token(key, auth_token.as_str(), Some(validation_rules)) - .map(|_| UserVerifyGetResponse::new(key, user_id)) - .map_err(|err| err.into_response())?; + let response = verify_token( + token_key, + verification_token.as_str(), + Some(validation_rules), + ) + .map(|_| UserVerifyGetResponse::new(token_key, user_id)) + .inspect_err(|err| error!(?err)) + .map_err(|err| err.into_response())?; - verify_user(app_state.pool(), user_id) + verify_user(pool, user_id) .await + .inspect_err(|err| error!(?err)) .map_err(|err| err.into_response())?; Ok(( diff --git a/api/src/requests/user/verify/mod.rs b/api/src/requests/user/verify/mod.rs index 8f2640d..72001c4 100644 --- a/api/src/requests/user/verify/mod.rs +++ b/api/src/requests/user/verify/mod.rs @@ -1,5 +1,5 @@ +mod handler; mod models; -mod request; +pub use handler::*; pub use models::*; -pub use request::*; diff --git a/api/src/requests/user/verify/models/mod.rs b/api/src/requests/user/verify/models/mod.rs index b8be632..e063d1b 100644 --- a/api/src/requests/user/verify/models/mod.rs +++ b/api/src/requests/user/verify/models/mod.rs @@ -1,5 +1,5 @@ -mod request; +mod params; mod response; -pub use request::*; +pub use params::*; pub use response::*; diff --git a/api/src/requests/user/verify/models/request.rs b/api/src/requests/user/verify/models/params.rs similarity index 54% rename from api/src/requests/user/verify/models/request.rs rename to api/src/requests/user/verify/models/params.rs index b7a1ad2..90931d4 100644 --- a/api/src/requests/user/verify/models/request.rs +++ b/api/src/requests/user/verify/models/params.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] -pub struct UserVerifyGetRequest { +pub struct UserVerifyGetParams { #[serde(alias = "t")] - pub auth_token: String, + pub verification_token: String, } diff --git a/api/src/services/auth_token.rs b/api/src/services/auth_token.rs index 8fa496c..939cbc3 100644 --- a/api/src/services/auth_token.rs +++ b/api/src/services/auth_token.rs @@ -8,6 +8,7 @@ use pasetors::{ token::UntrustedToken, version4::V4, }; +use tracing::error; use uuid::Uuid; use crate::models::AppError; @@ -21,7 +22,9 @@ pub fn verify_token( token: &str, validation_rules: Option, ) -> Result<(), AppError> { - let token = UntrustedToken::try_from(token).map_err(|_| AppError::invalid_token())?; + let token = UntrustedToken::try_from(token) + .inspect_err(|err| error!(?err)) + .map_err(|_| AppError::invalid_token())?; let validation_rules = if let Some(validation_rules) = validation_rules { validation_rules @@ -42,6 +45,7 @@ pub fn verify_token( Some(&footer), Some("TODO_ENV_NAME_HERE".as_bytes()), ) + .inspect_err(|err| error!(?err)) .map_err(|_| AppError::invalid_token())?; Ok(()) @@ -60,7 +64,7 @@ pub fn generate_new_user_token(key: &SymmetricKey, user_id: i32) -> (String, key, user_id, Some(FIFTEEN_MINUTES), - Some(format!("api.debtpirate.bikeshedengineering.internal/user/{user_id}/verify").as_str()), + Some(format!("/user/{user_id}/verify").as_str()), ) } diff --git a/api/src/services/mailer/service.rs b/api/src/services/mailer/service.rs index c836207..f3be611 100644 --- a/api/src/services/mailer/service.rs +++ b/api/src/services/mailer/service.rs @@ -52,7 +52,7 @@ pub fn start_emailer_service( let UserConfirmationMessage { email: recipient_email, name, - auth_token, + verification_token, } = message; runtime.spawn(async move { @@ -60,7 +60,7 @@ pub fn start_emailer_service( recipient_email.as_str(), new_user_confirmation_template_text.as_str(), name.as_str(), - auth_token.as_str(), + verification_token.as_str(), ) .await; }); @@ -72,11 +72,11 @@ async fn send_new_user_confirmation_email( recipient_email: &str, new_user_confirmation_template_text: &str, name: &str, - auth_token: &str, + verification_token: &str, ) { let body = new_user_confirmation_template_text .replace("$NAME", name) - .replace("$AUTH_TOKEN", auth_token); + .replace("$VERIFICATION_TOKEN", verification_token); let message = Message::builder() .from(FROM_MAILBOX.clone()) diff --git a/api/src/services/mailer/user_confirmation_message.rs b/api/src/services/mailer/user_confirmation_message.rs index d99a567..d49613e 100644 --- a/api/src/services/mailer/user_confirmation_message.rs +++ b/api/src/services/mailer/user_confirmation_message.rs @@ -1,5 +1,6 @@ +#[derive(Debug)] pub struct UserConfirmationMessage { pub email: String, pub name: String, - pub auth_token: String, + pub verification_token: String, }