login
This commit is contained in:
@@ -115,10 +115,10 @@ impl FileSystemMetrics for MetricsDebugLogger {
|
||||
tracing::debug!("[Metrics] File deletion failed");
|
||||
}
|
||||
|
||||
async fn record_entry_rename_success(&self) -> () {
|
||||
async fn record_entry_rename_success(&self) {
|
||||
tracing::debug!("[Metrics] Entry rename succeeded");
|
||||
}
|
||||
async fn record_entry_rename_failure(&self) -> () {
|
||||
async fn record_entry_rename_failure(&self) {
|
||||
tracing::debug!("[Metrics] Entry rename failed");
|
||||
}
|
||||
}
|
||||
@@ -130,4 +130,18 @@ impl AuthMetrics for MetricsDebugLogger {
|
||||
async fn record_user_registration_failure(&self) {
|
||||
tracing::debug!("[Metrics] User registration failed");
|
||||
}
|
||||
|
||||
async fn record_user_login_success(&self) {
|
||||
tracing::debug!("[Metrics] User login succeeded");
|
||||
}
|
||||
async fn record_user_login_failure(&self) {
|
||||
tracing::debug!("[Metrics] User login failed");
|
||||
}
|
||||
|
||||
async fn record_auth_session_creation_success(&self) {
|
||||
tracing::debug!("[Metrics] Auth session creation succeeded");
|
||||
}
|
||||
async fn record_auth_session_creation_failure(&self) {
|
||||
tracing::debug!("[Metrics] Auth session creation failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::{
|
||||
models::{
|
||||
file::{File, FilePath},
|
||||
@@ -118,4 +120,12 @@ impl AuthNotifier for NotifierDebugLogger {
|
||||
async fn user_registered(&self, user: &User) {
|
||||
tracing::debug!("[Notifier] Registered user {}", user.name());
|
||||
}
|
||||
|
||||
async fn user_logged_in(&self, user: &User) {
|
||||
tracing::debug!("[Notifier] Logged in user {}", user.name());
|
||||
}
|
||||
|
||||
async fn auth_session_created(&self, user_id: &Uuid) {
|
||||
tracing::debug!("[Notifier] Created auth session for user {}", user_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use argon2::{
|
||||
Argon2,
|
||||
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
||||
Argon2, PasswordHash, PasswordVerifier,
|
||||
password_hash::{
|
||||
PasswordHasher, SaltString,
|
||||
rand_core::{OsRng, RngCore as _},
|
||||
},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use sqlx::{
|
||||
ConnectOptions as _, Connection as _, PgConnection, PgPool,
|
||||
postgres::{PgConnectOptions, PgPoolOptions},
|
||||
@@ -13,7 +17,14 @@ use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::{
|
||||
models::{
|
||||
user::{RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword},
|
||||
auth_session::{
|
||||
AuthSession,
|
||||
requests::{CreateAuthSessionError, CreateAuthSessionRequest, SessionExpirationTime},
|
||||
},
|
||||
user::{
|
||||
RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword,
|
||||
VerifyUserPasswordError, VerifyUserPasswordRequest,
|
||||
},
|
||||
warren::{
|
||||
FetchWarrenError, FetchWarrenRequest, ListWarrensError, ListWarrensRequest, Warren,
|
||||
},
|
||||
@@ -151,6 +162,89 @@ impl Postgres {
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
async fn get_user_from_email(
|
||||
&self,
|
||||
connection: &mut PgConnection,
|
||||
email: &UserEmail,
|
||||
) -> Result<User, sqlx::Error> {
|
||||
let user: User = sqlx::query_as(
|
||||
"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
email = $1
|
||||
",
|
||||
)
|
||||
.bind(email)
|
||||
.fetch_one(connection)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
fn check_user_password_against_hash(
|
||||
&self,
|
||||
password: &UserPassword,
|
||||
hash: &str,
|
||||
) -> Result<(), VerifyUserPasswordError> {
|
||||
let argon = Argon2::default();
|
||||
let hash =
|
||||
PasswordHash::new(hash).map_err(|e| VerifyUserPasswordError::Unknown(anyhow!(e)))?;
|
||||
|
||||
argon
|
||||
.verify_password(password.as_str().as_bytes(), &hash)
|
||||
.map_err(|e| match e {
|
||||
argon2::password_hash::Error::Password => {
|
||||
VerifyUserPasswordError::IncorrectPassword
|
||||
}
|
||||
_ => VerifyUserPasswordError::Unknown(anyhow!(e)),
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_session(
|
||||
&self,
|
||||
connection: &mut PgConnection,
|
||||
user: &User,
|
||||
expiration: &SessionExpirationTime,
|
||||
) -> anyhow::Result<AuthSession> {
|
||||
let mut rng = OsRng;
|
||||
let mut bytes = vec![0_u8; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
|
||||
let session_id = hex::encode(bytes);
|
||||
|
||||
let expiration_time = Utc::now().timestamp_millis() + i64::try_from(expiration.millis())?;
|
||||
|
||||
let mut tx = connection.begin().await?;
|
||||
|
||||
let session: AuthSession = sqlx::query_as(
|
||||
"
|
||||
INSERT INTO auth_sessions (
|
||||
session_id,
|
||||
user_id,
|
||||
expires_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
TO_TIMESTAMP($3::double precision / 1000)
|
||||
)
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(session_id)
|
||||
.bind(user.id())
|
||||
.bind(expiration_time)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
}
|
||||
|
||||
impl WarrenRepository for Postgres {
|
||||
@@ -217,6 +311,50 @@ impl AuthRepository for Postgres {
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
async fn verify_user_password(
|
||||
&self,
|
||||
request: VerifyUserPasswordRequest,
|
||||
) -> Result<User, VerifyUserPasswordError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
let user = self
|
||||
.get_user_from_email(&mut connection, request.email())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if is_not_found_error(&e) {
|
||||
VerifyUserPasswordError::NotFound(request.email().clone())
|
||||
} else {
|
||||
VerifyUserPasswordError::Unknown(anyhow!(e))
|
||||
}
|
||||
})?;
|
||||
|
||||
self.check_user_password_against_hash(request.password(), user.password_hash())?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
async fn create_auth_session(
|
||||
&self,
|
||||
request: CreateAuthSessionRequest,
|
||||
) -> Result<AuthSession, CreateAuthSessionError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
let session = self
|
||||
.create_session(&mut connection, request.user(), request.expiration())
|
||||
.await
|
||||
.context("Failed to create session")?;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_not_found_error(err: &sqlx::Error) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user