Login is maybe working

This commit is contained in:
Daniel Flanagan 2023-11-14 17:08:18 -06:00
parent 10d57a83be
commit 6579332013
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
5 changed files with 42 additions and 16 deletions

1
Cargo.lock generated
View file

@ -1411,6 +1411,7 @@ dependencies = [
"serde", "serde",
"thiserror", "thiserror",
"tokio", "tokio",
"tower",
"tower-http", "tower-http",
"tower-livereload", "tower-livereload",
"tracing", "tracing",

View file

@ -34,3 +34,4 @@ sea-orm-migration = { version = "0.12.6", features = ["sqlx-sqlite"] }
uuid = { version = "1.5.0", features = ["v7", "atomic", "fast-rng", "macro-diagnostics"] } uuid = { version = "1.5.0", features = ["v7", "atomic", "fast-rng", "macro-diagnostics"] }
password-hash = "0.5.0" password-hash = "0.5.0"
axum-login = "0.7.3" axum-login = "0.7.3"
tower = "0.4.13"

View file

@ -4,18 +4,21 @@ use crate::{error::AppError, views};
use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString; use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHasher}; use argon2::{Argon2, PasswordHasher};
use axum::async_trait; use axum::error_handling::HandleErrorLayer;
use axum::extract::State; use axum::extract::State;
use axum::{async_trait, BoxError};
use axum::{http::StatusCode, response::Html, routing::get, Form, Router}; use axum::{http::StatusCode, response::Html, routing::get, Form, Router};
use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken}; use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken};
use axum_login::{AuthUser, AuthnBackend, UserId}; use axum_login::tower_sessions::{MemoryStore, SessionManagerLayer};
use axum_login::{AuthManagerLayer, AuthUser, AuthnBackend, UserId};
use base64::prelude::*; use base64::prelude::*;
use maud::html; use maud::html;
use notify::Watcher; use notify::Watcher;
use password_hash::{PasswordHash, PasswordVerifier};
use sea_orm::*; use sea_orm::*;
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc;
use std::{env, path::Path}; use std::{env, path::Path};
use tower::ServiceBuilder;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use tower_livereload::LiveReloadLayer; use tower_livereload::LiveReloadLayer;
use tracing::{info, instrument}; use tracing::{info, instrument};
@ -41,6 +44,16 @@ pub async fn new() -> Result<Router, anyhow::Error> {
let cookie_key = cookie::Key::from(&cookie_key_bytes); let cookie_key = cookie::Key::from(&cookie_key_bytes);
let csrf_config = CsrfConfig::default().with_key(Some(cookie_key)); let csrf_config = CsrfConfig::default().with_key(Some(cookie_key));
let session_store = MemoryStore::default();
let login_session_manager =
SessionManagerLayer::new(session_store).with_name("login_sessions.sid");
let auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_: BoxError| async {
StatusCode::BAD_REQUEST
}))
.layer(AuthManagerLayer::new(state.clone(), login_session_manager));
let router = Router::new() let router = Router::new()
.fallback(|| async { (StatusCode::NOT_FOUND, "404 page not found") }) .fallback(|| async { (StatusCode::NOT_FOUND, "404 page not found") })
.nest("/app", app_router) .nest("/app", app_router)
@ -51,6 +64,7 @@ pub async fn new() -> Result<Router, anyhow::Error> {
.route("/all_users", get(views::all_users)) .route("/all_users", get(views::all_users))
.with_state(state) .with_state(state)
.layer(CsrfLayer::new(csrf_config)) .layer(CsrfLayer::new(csrf_config))
.layer(auth_service)
.layer(live_reload_layer); .layer(live_reload_layer);
Ok(router) Ok(router)
@ -94,13 +108,18 @@ where
.to_string()) .to_string())
} }
fn password_verify<S>(password: S, current_digest: S) -> Result<(), password_hash::Error>
where
S: AsRef<str>,
{
let password_bytes = password.as_ref().as_bytes();
let current = PasswordHash::new(current_digest.as_ref())?;
Ok(Argon2::default().verify_password(password_bytes, &current)?)
}
impl user::ActiveModel {} impl user::ActiveModel {}
async fn register( async fn register(c: CsrfToken, State(s): State<state::State>, Form(f): Form<Register>) -> AppRes {
c: CsrfToken,
State(s): State<Arc<state::State>>,
Form(f): Form<Register>,
) -> AppRes {
csrf_verify(c, &f.authenticity_token)?; csrf_verify(c, &f.authenticity_token)?;
// TODO: handle duplicate username // TODO: handle duplicate username
@ -151,12 +170,18 @@ impl AuthnBackend for state::State {
type Error = AppError; type Error = AppError;
async fn authenticate(&self, l: Self::Credentials) -> Result<Option<Self::User>, Self::Error> { async fn authenticate(&self, l: Self::Credentials) -> Result<Option<Self::User>, Self::Error> {
Ok(User::find() match User::find()
.filter(user::Column::Username.eq(l.username)) .filter(user::Column::Username.eq(l.username))
// TODO: will this have index problems since I'm searching over the password digest? // TODO: will this have index problems since I'm searching over the password digest?
.filter(user::Column::PasswordDigest.eq(password_digest(l.password)?))
.one(&self.db) .one(&self.db)
.await?) .await?
{
Some(user) => match password_verify(&l.password, &user.password_digest) {
Ok(()) => Ok(Some(user)),
Err(e) => Err(e.into()),
},
None => Ok(None),
}
} }
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> { async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {

View file

@ -1,4 +1,4 @@
use std::{env, sync::Arc}; use std::env;
use sea_orm::{Database, DatabaseConnection}; use sea_orm::{Database, DatabaseConnection};
use sea_orm_migration::MigratorTrait; use sea_orm_migration::MigratorTrait;
@ -11,12 +11,12 @@ pub struct State {
} }
impl State { impl State {
pub async fn new() -> Result<Arc<Self>, anyhow::Error> { pub async fn new() -> Result<Self, anyhow::Error> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let db = Database::connect(database_url).await?; let db = Database::connect(database_url).await?;
Migrator::refresh(&db).await?; Migrator::refresh(&db).await?;
Ok(Arc::new(State { db })) Ok(State { db })
} }
} }

View file

@ -12,7 +12,6 @@ use axum::{
use axum_csrf::CsrfToken; use axum_csrf::CsrfToken;
use maud::html; use maud::html;
use sea_orm::EntityTrait; use sea_orm::EntityTrait;
use std::sync::Arc;
use tracing::instrument; use tracing::instrument;
pub async fn csrf<F>(csrf: CsrfToken, cb: F) -> impl IntoResponse pub async fn csrf<F>(csrf: CsrfToken, cb: F) -> impl IntoResponse
@ -108,7 +107,7 @@ pub async fn login(t: CsrfToken) -> impl IntoResponse {
.await .await
} }
pub async fn all_users(State(s): State<Arc<state::State>>) -> AppRes { pub async fn all_users(State(s): State<state::State>) -> AppRes {
let users: Vec<user::Model> = User::find().all(&s.db).await?; let users: Vec<user::Model> = User::find().all(&s.db).await?;
Ok(( Ok((