Login is maybe working
This commit is contained in:
parent
10d57a83be
commit
6579332013
5 changed files with 42 additions and 16 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1411,6 +1411,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-livereload",
|
"tower-livereload",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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, ¤t)?)
|
||||||
|
}
|
||||||
|
|
||||||
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> {
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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((
|
||||||
|
|
Loading…
Reference in a new issue