Refactor step one of user registration
This commit is contained in:
parent
5596724ba9
commit
29dca09ac7
13 changed files with 119 additions and 59 deletions
6
api/.env
6
api/.env
|
@ -1,8 +1,8 @@
|
|||
HOSTNAME=localhost
|
||||
PORT=42068
|
||||
DOMAIN=http://localhost:42069
|
||||
RP_ID=localhost
|
||||
DOMAIN=http://api.debtpirate.app
|
||||
RP_ID=debtpirate.app
|
||||
TOKEN_KEY=k4.local.hWoS2ZulK9xPEATtXH1Dvj_iynzqfUv5ER5_IFTg5-Q
|
||||
DATABASE_URL=postgres://debt-pirate:test@192.168.122.215/debt_pirate
|
||||
DATABASE_URL=postgres://debt_pirate:HRURqlUmtjIy@192.168.122.251/debt_pirate
|
||||
ASSETS_DIR=/home/zcdziura/Documents/Projects/debt-pirate/api/assets
|
||||
MAINTENANCE_USER_ACCOUNT=debt_pirate:HRURqlUmtjIy
|
||||
|
|
45
api/.vscode/launch.json
vendored
Normal file
45
api/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'auth-test'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=auth-test",
|
||||
"--package=auth-test"
|
||||
],
|
||||
"filter": {
|
||||
"name": "auth-test",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'auth-test'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=auth-test",
|
||||
"--package=auth-test"
|
||||
],
|
||||
"filter": {
|
||||
"name": "auth-test",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -3,7 +3,7 @@ mod user;
|
|||
use sqlx::{self, postgres::PgPoolOptions, Pool, Postgres};
|
||||
pub use user::*;
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::models::AppError;
|
||||
|
||||
pub type DbPool = Pool<Postgres>;
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use sqlx::prelude::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::AppError;
|
||||
|
||||
use super::DbPool;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -3,7 +3,6 @@ use std::{process, sync::mpsc::channel};
|
|||
use models::Environment;
|
||||
|
||||
mod db;
|
||||
mod error;
|
||||
mod models;
|
||||
mod requests;
|
||||
mod services;
|
||||
|
|
|
@ -7,14 +7,22 @@ use std::{
|
|||
use once_cell::sync::Lazy;
|
||||
use pasetors::{keys::SymmetricKey, version4::V4};
|
||||
|
||||
use crate::{error::AppError, services::UserConfirmationMessage};
|
||||
use crate::services::UserConfirmationMessage;
|
||||
|
||||
static REQUIRED_ENV_VARS: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||
HashSet::<String>::from_iter(
|
||||
["TOKEN_KEY", "DATABASE_URL", "ASSETS_DIR"]
|
||||
use super::AppError;
|
||||
|
||||
static REQUIRED_ENV_VARS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
[
|
||||
"HOSTNAME",
|
||||
"PORT",
|
||||
"DOMAIN",
|
||||
"RP_ID",
|
||||
"TOKEN_KEY",
|
||||
"DATABASE_URL",
|
||||
"ASSETS_DIR",
|
||||
]
|
||||
.into_iter()
|
||||
.map(ToString::to_string),
|
||||
)
|
||||
.collect()
|
||||
});
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -35,10 +43,12 @@ impl Environment {
|
|||
dotenvy::dotenv_iter()
|
||||
.expect("Missing .env file")
|
||||
.filter_map(|item| item.ok())
|
||||
.filter(|(key, _)| REQUIRED_ENV_VARS.contains(key))
|
||||
.filter(|(key, _)| REQUIRED_ENV_VARS.contains(key.as_str()))
|
||||
.for_each(|(key, value)| match key.as_str() {
|
||||
"HOSTNAME" => builder.with_hostname(value),
|
||||
"PORT" => builder.with_port(value),
|
||||
"DOMAIN" => builder.with_domain(value),
|
||||
"RP_ID" => builder.with_rp_id(value),
|
||||
"TOKEN_KEY" => builder.with_token_key(value),
|
||||
"DATABASE_URL" => builder.with_database_url(value),
|
||||
"ASSETS_DIR" => builder.with_assets_dir(value),
|
||||
|
@ -65,6 +75,10 @@ impl Environment {
|
|||
self.domain.as_str()
|
||||
}
|
||||
|
||||
pub fn rp_id(&self) -> &str {
|
||||
self.rp_id.as_str()
|
||||
}
|
||||
|
||||
pub fn token_key(&self) -> &SymmetricKey<V4> {
|
||||
&self.token_key
|
||||
}
|
||||
|
@ -108,7 +122,7 @@ impl From<EnvironmentObjectBuilder> for Environment {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EnvironmentObjectBuilder {
|
||||
pub hostname: Option<String>,
|
||||
pub port: Option<u32>,
|
||||
|
@ -136,7 +150,7 @@ impl EnvironmentObjectBuilder {
|
|||
("DATABASE_URL", self.database_url.as_deref()),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(key, value)| value.xor(Some(key)))
|
||||
.filter_map(|(key, value)| value.map(|_| key).xor(Some(key)))
|
||||
.collect::<Vec<&'static str>>();
|
||||
|
||||
if self.token_key.is_none() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod api_response;
|
||||
mod environment;
|
||||
mod error;
|
||||
|
||||
pub use api_response::*;
|
||||
pub use environment::*;
|
||||
pub use error::*;
|
||||
|
|
|
@ -4,9 +4,12 @@ use std::sync::Arc;
|
|||
|
||||
use axum::Router;
|
||||
use tokio::net::TcpListener;
|
||||
use webauthn_rs::Webauthn;
|
||||
use webauthn_rs::prelude::*;
|
||||
|
||||
use crate::{db::DbPool, error::AppError, models::Environment};
|
||||
use crate::{
|
||||
db::DbPool,
|
||||
models::{AppError, Environment},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
|
@ -17,9 +20,25 @@ pub struct AppState {
|
|||
|
||||
impl AppState {
|
||||
pub fn new(pool: DbPool, env: Environment) -> Self {
|
||||
let rp_id =
|
||||
let rp_id = env.rp_id();
|
||||
let rp_origin = env
|
||||
.domain()
|
||||
.parse::<Url>()
|
||||
.expect("RP_ORIGIN must be in a valid domain name format");
|
||||
|
||||
Self { pool, env }
|
||||
let webauthn = Arc::new(
|
||||
WebauthnBuilder::new(rp_id, &rp_origin)
|
||||
.map(|builder| builder.allow_any_port(true))
|
||||
.and_then(WebauthnBuilder::build)
|
||||
.inspect_err(|err| eprintln!("{err}"))
|
||||
.expect("Unable to build authenticator"),
|
||||
);
|
||||
|
||||
Self {
|
||||
pool,
|
||||
env,
|
||||
webauthn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pool(&self) -> &DbPool {
|
||||
|
@ -29,6 +48,10 @@ impl AppState {
|
|||
pub fn env(&self) -> &Environment {
|
||||
&self.env
|
||||
}
|
||||
|
||||
pub fn webauthn(&self) -> Arc<Webauthn> {
|
||||
Arc::clone(&self.webauthn)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_app(pool: DbPool, env: Environment) -> Result<(), AppError> {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
mod registration_request;
|
||||
mod registration_response;
|
||||
|
||||
pub use registration_request::*;
|
||||
pub use registration_response::*;
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use humantime::format_rfc3339_seconds;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserRegistrationResponse {
|
||||
user_id: i32,
|
||||
auth: UserPostResponseToken,
|
||||
}
|
||||
|
||||
impl UserRegistrationResponse {
|
||||
pub fn new(user_id: i32, token: String, expiration: SystemTime) -> Self {
|
||||
let auth = UserPostResponseToken {
|
||||
token,
|
||||
expiration: format_rfc3339_seconds(expiration).to_string(),
|
||||
};
|
||||
|
||||
Self { user_id, auth }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserPostResponseToken {
|
||||
pub token: String,
|
||||
pub expiration: String,
|
||||
}
|
|
@ -8,6 +8,8 @@ use axum::{
|
|||
};
|
||||
use http::StatusCode;
|
||||
use tower_sessions::Session;
|
||||
use uuid::Uuid;
|
||||
use webauthn_rs::prelude::CreationChallengeResponse;
|
||||
|
||||
use crate::{
|
||||
db::NewUserEntity,
|
||||
|
@ -16,7 +18,7 @@ use crate::{
|
|||
services::{auth_token::generate_token, UserConfirmationMessage},
|
||||
};
|
||||
|
||||
use super::models::{UserRegistrationRequest, UserRegistrationResponse};
|
||||
use super::models::UserRegistrationRequest;
|
||||
|
||||
static FIFTEEN_MINUTES: u64 = 60 * 15;
|
||||
|
||||
|
@ -31,9 +33,17 @@ async fn user_registration_post_handler(
|
|||
session: Session,
|
||||
Json(request): Json<UserRegistrationRequest>,
|
||||
) -> Result<Response, Response> {
|
||||
session.remove_value("reg_state");
|
||||
let _ = session.remove_value("reg_state").await;
|
||||
|
||||
let UserRegistrationRequest { name, email } = request;
|
||||
let user_uuid = Uuid::now_v7();
|
||||
|
||||
// TODO: Fetch any already saved credentials for the given user for exclusion
|
||||
let (creation_challenge_response, reg) = app_state
|
||||
.webauthn()
|
||||
.start_passkey_registration(user_uuid, email.as_str(), name.as_str(), None)
|
||||
.expect("Invalid passkey registration");
|
||||
|
||||
// let new_user = NewUserEntity::new(email.clone(), name.clone());
|
||||
// let UserEntity { id: user_id, name, email , ..} = insert_new_user(app_state.pool(), new_user).await
|
||||
// .map_err(|err| {
|
||||
|
@ -55,12 +65,12 @@ async fn user_registration_post_handler(
|
|||
// eprintln!("Got the rollowing error while sending across the channel: {err}");
|
||||
// });
|
||||
|
||||
let new_user_entity = NewUserEntity::new(name, email);
|
||||
// let new_user_entity = NewUserEntity::new(name, email);
|
||||
|
||||
let response = (
|
||||
StatusCode::CREATED,
|
||||
Json(ApiResponse::<UserRegistrationResponse>::new(
|
||||
UserRegistrationResponse::new(user_id, auth_token, expiration),
|
||||
StatusCode::OK,
|
||||
Json(ApiResponse::<CreationChallengeResponse>::new(
|
||||
creation_challenge_response,
|
||||
)),
|
||||
);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use pasetors::{
|
|||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::models::AppError;
|
||||
|
||||
static FOURTY_FIVE_DAYS: u64 = 3_888_000; // 60 * 60 * 24 * 45
|
||||
static ONE_HOUR: u64 = 3_600;
|
||||
|
|
Loading…
Add table
Reference in a new issue