add passkey support
This commit is contained in:
+39
-5
@@ -7,6 +7,7 @@ mod seed;
|
||||
mod services;
|
||||
mod sqlite;
|
||||
mod views;
|
||||
mod webauthn;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path as FilePath;
|
||||
@@ -19,16 +20,17 @@ use crate::http::{AppState, build_router};
|
||||
use crate::hub::InMemoryHub;
|
||||
use crate::ports::{
|
||||
CategoryRepository, InvitationRepository, ItemRepository, ListRepository,
|
||||
MealIngredientRepository, MealRepository, PasswordHasher, RealtimeNotifier, SessionRepository,
|
||||
TokenGenerator, UserRepository,
|
||||
MealIngredientRepository, MealRepository, PasskeyRepository, PasswordHasher, RealtimeNotifier,
|
||||
SessionRepository, TokenGenerator, UserRepository,
|
||||
};
|
||||
use crate::security::{Argon2PasswordHasher, RandomTokenGenerator};
|
||||
use crate::services::{AuthService, InvitationService, ListService, MealService, RegistrationMode};
|
||||
use crate::sqlite::{
|
||||
SqliteCategoryRepository, SqliteDatabase, SqliteInvitationRepository, SqliteItemRepository,
|
||||
SqliteListRepository, SqliteMealIngredientRepository, SqliteMealRepository,
|
||||
SqliteSessionRepository, SqliteUserRepository,
|
||||
SqlitePasskeyRepository, SqliteSessionRepository, SqliteUserRepository,
|
||||
};
|
||||
use crate::webauthn::{AppWebauthnConfig, WebAuthnService};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -40,8 +42,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let database_path = env::var("DATABASE_PATH").unwrap_or_else(|_| "sustenance.db".into());
|
||||
let bind_address = env::var("BIND_ADDRESS").unwrap_or_else(|_| "127.0.0.1:3000".into());
|
||||
let public_base_url =
|
||||
env::var("PUBLIC_BASE_URL").unwrap_or_else(|_| format!("http://{}", bind_address));
|
||||
// For loopback hosts, advertise `localhost` so WebAuthn works locally (browsers
|
||||
// reject IP addresses as RP IDs). Access the app via http://localhost:PORT.
|
||||
let bind_host = bind_address.split(':').next().unwrap_or("127.0.0.1");
|
||||
let is_loopback = bind_host == "127.0.0.1" || bind_host == "::1" || bind_host == "localhost";
|
||||
let public_host = if is_loopback { "localhost" } else { bind_host };
|
||||
let public_base_url = env::var("PUBLIC_BASE_URL").unwrap_or_else(|_| {
|
||||
let port = bind_address.rsplit(':').next().unwrap_or("3000");
|
||||
format!("http://{}:{}", public_host, port)
|
||||
});
|
||||
let cookie_secure = env::var("COOKIE_SECURE")
|
||||
.map(|value| value == "1" || value.eq_ignore_ascii_case("true"))
|
||||
.unwrap_or(false);
|
||||
@@ -69,6 +78,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let meal_ingredients: Arc<dyn MealIngredientRepository> =
|
||||
Arc::new(SqliteMealIngredientRepository);
|
||||
let invitations: Arc<dyn InvitationRepository> = Arc::new(SqliteInvitationRepository);
|
||||
let passkeys: Arc<dyn PasskeyRepository> = Arc::new(SqlitePasskeyRepository);
|
||||
let hasher: Arc<dyn PasswordHasher> = Arc::new(Argon2PasswordHasher);
|
||||
let tokens: Arc<dyn TokenGenerator> = Arc::new(RandomTokenGenerator);
|
||||
let realtime: Arc<dyn RealtimeNotifier> = Arc::new(InMemoryHub::default());
|
||||
@@ -102,6 +112,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Arc::clone(&realtime),
|
||||
));
|
||||
|
||||
// WebAuthn config from env vars. RP_ID must match the host users access the site from.
|
||||
let rp_id = env::var("RP_ID").unwrap_or_else(|_| {
|
||||
let host = public_base_url
|
||||
.trim_start_matches("http://")
|
||||
.trim_start_matches("https://")
|
||||
.split('/')
|
||||
.next()
|
||||
.unwrap_or("localhost")
|
||||
.split(':')
|
||||
.next()
|
||||
.unwrap_or("localhost")
|
||||
.to_owned();
|
||||
host
|
||||
});
|
||||
let rp_name = env::var("RP_NAME").unwrap_or_else(|_| "Sustenance".into());
|
||||
let origin =
|
||||
url::Url::parse(&public_base_url).map_err(|e| format!("invalid PUBLIC_BASE_URL: {e}"))?;
|
||||
let webauthn_service = Arc::new(WebAuthnService::new(
|
||||
db.clone(),
|
||||
AppWebauthnConfig::new(rp_id, rp_name, origin),
|
||||
Arc::clone(&passkeys),
|
||||
));
|
||||
|
||||
let seed_path = env::var("SEED_CONFIG").unwrap_or_else(|_| "seed.json".into());
|
||||
seed::seed_if_needed(&db, &users, &hasher, FilePath::new(&seed_path)).await;
|
||||
|
||||
@@ -110,6 +143,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
lists: lists_service,
|
||||
meals: meals_service,
|
||||
invitations: invitations_service,
|
||||
webauthn: webauthn_service,
|
||||
realtime,
|
||||
cookie_secure,
|
||||
public_base_url,
|
||||
|
||||
Reference in New Issue
Block a user