get complete session from login request instead of fetching again

This commit is contained in:
2025-07-18 14:05:19 +02:00
parent fe6cf2fb53
commit 4c3e54daca
9 changed files with 102 additions and 76 deletions

View File

@@ -1,8 +1,8 @@
use thiserror::Error; use thiserror::Error;
use crate::domain::warren::models::auth_session::requests::CreateAuthSessionError; use crate::domain::warren::models::auth_session::{AuthSession, requests::CreateAuthSessionError};
use super::{UserEmail, UserName}; use super::{User, UserEmail, UserName};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegisterUserRequest { pub struct RegisterUserRequest {
@@ -125,6 +125,26 @@ pub struct LoginUserRequest {
password: UserPassword, password: UserPassword,
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LoginUserResponse {
session: AuthSession,
user: User,
}
impl LoginUserResponse {
pub fn new(session: AuthSession, user: User) -> Self {
Self { session, user }
}
pub fn session(&self) -> &AuthSession {
&self.session
}
pub fn user(&self) -> &User {
&self.user
}
}
impl LoginUserRequest { impl LoginUserRequest {
pub fn new(email: UserEmail, password: UserPassword) -> Self { pub fn new(email: UserEmail, password: UserPassword) -> Self {
Self { email, password } Self { email, password }

View File

@@ -19,7 +19,10 @@ use super::models::{
DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, DeleteFileRequest, File, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, DeleteFileRequest, File,
FilePath, ListFilesError, ListFilesRequest, RenameEntryError, RenameEntryRequest, FilePath, ListFilesError, ListFilesRequest, RenameEntryError, RenameEntryRequest,
}, },
user::{LoginUserError, LoginUserRequest, RegisterUserError, RegisterUserRequest, User}, user::{
LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError,
RegisterUserRequest, User,
},
warren::{ warren::{
CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError, CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError,
DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest, DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest,
@@ -108,7 +111,7 @@ pub trait AuthService: Clone + Send + Sync + 'static {
fn login_user( fn login_user(
&self, &self,
request: LoginUserRequest, request: LoginUserRequest,
) -> impl Future<Output = Result<AuthSession, LoginUserError>> + Send; ) -> impl Future<Output = Result<LoginUserResponse, LoginUserError>> + Send;
fn create_auth_session( fn create_auth_session(
&self, &self,
request: CreateAuthSessionRequest, request: CreateAuthSessionRequest,

View File

@@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::domain::warren::models::{ use crate::domain::warren::models::{
auth_session::requests::FetchAuthSessionResponse, auth_session::requests::FetchAuthSessionResponse,
file::{AbsoluteFilePath, File, FilePath}, file::{AbsoluteFilePath, File, FilePath},
user::User, user::{LoginUserResponse, User},
warren::Warren, warren::Warren,
}; };
@@ -75,7 +75,7 @@ pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
pub trait AuthNotifier: Clone + Send + Sync + 'static { pub trait AuthNotifier: Clone + Send + Sync + 'static {
fn user_registered(&self, user: &User) -> impl Future<Output = ()> + Send; fn user_registered(&self, user: &User) -> impl Future<Output = ()> + Send;
fn user_logged_in(&self, user: &User) -> impl Future<Output = ()> + Send; fn user_logged_in(&self, response: &LoginUserResponse) -> impl Future<Output = ()> + Send;
fn auth_session_created(&self, user_id: &Uuid) -> impl Future<Output = ()> + Send; fn auth_session_created(&self, user_id: &Uuid) -> impl Future<Output = ()> + Send;
fn auth_session_fetched( fn auth_session_fetched(
&self, &self,

View File

@@ -10,7 +10,8 @@ use crate::{
}, },
}, },
user::{ user::{
LoginUserError, LoginUserRequest, RegisterUserError, RegisterUserRequest, User, LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError,
RegisterUserRequest, User,
}, },
}, },
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService}, ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService},
@@ -93,7 +94,10 @@ where
result result
} }
async fn login_user(&self, request: LoginUserRequest) -> Result<AuthSession, LoginUserError> { async fn login_user(
&self,
request: LoginUserRequest,
) -> Result<LoginUserResponse, LoginUserError> {
let user = self let user = self
.repository .repository
.verify_user_password(request.into()) .verify_user_password(request.into())
@@ -105,11 +109,12 @@ where
user.clone(), user.clone(),
self.config.session_lifetime(), self.config.session_lifetime(),
)) ))
.await; .await
.map(|session| LoginUserResponse::new(session, user));
if result.as_ref().is_ok() { if let Ok(response) = result.as_ref() {
self.metrics.record_user_login_success().await; self.metrics.record_user_login_success().await;
self.notifier.user_logged_in(&user).await; self.notifier.user_logged_in(response).await;
} else { } else {
self.metrics.record_user_login_failure().await; self.metrics.record_user_login_failure().await;
} }

View File

@@ -3,45 +3,22 @@ use axum::{
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
}; };
use serde::Serialize; use serde::Serialize;
use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::auth_session::{
auth_session::{ AuthSessionId,
AuthSessionId, requests::{FetchAuthSessionRequest, FetchAuthSessionResponse},
requests::{FetchAuthSessionRequest, FetchAuthSessionResponse},
},
user::User,
}, },
ports::{AuthService, WarrenService}, ports::{AuthService, WarrenService},
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::SessionUser,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct SessionUser {
id: Uuid,
name: String,
email: String,
admin: bool,
}
impl From<User> for SessionUser {
fn from(value: User) -> Self {
Self {
id: *value.id(),
name: value.name().to_string(),
email: value.email().to_string(),
admin: value.admin(),
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FetchSessionResponseBody { pub struct FetchSessionResponseBody {

View File

@@ -4,14 +4,15 @@ use thiserror::Error;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::user::{
auth_session::AuthSession, LoginUserRequest, LoginUserResponse, UserEmail, UserEmailError, UserPassword,
user::{LoginUserRequest, UserEmail, UserEmailError, UserPassword, UserPasswordError}, UserPasswordError,
}, },
ports::{AuthService, WarrenService}, ports::{AuthService, WarrenService},
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::SessionUser,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -66,16 +67,19 @@ impl LoginUserHttpRequestBody {
} }
} }
// TODO: Include `user` and `expires_at` fields
#[derive(Debug, Clone, PartialEq, Serialize)] #[derive(Debug, Clone, PartialEq, Serialize)]
pub struct LoginResponseBody { pub struct LoginResponseBody {
token: String, token: String,
user: SessionUser,
expires_at: i64,
} }
impl From<AuthSession> for LoginResponseBody { impl From<LoginUserResponse> for LoginResponseBody {
fn from(value: AuthSession) -> Self { fn from(value: LoginUserResponse) -> Self {
Self { Self {
token: value.session_id().to_string(), token: value.session().session_id().to_string(),
user: value.user().to_owned().into(),
expires_at: value.session().expires_at().and_utc().timestamp_millis(),
} }
} }
} }

View File

@@ -1,2 +1,28 @@
use serde::Serialize;
use uuid::Uuid;
use crate::domain::warren::models::user::User;
pub mod auth; pub mod auth;
pub mod warrens; pub mod warrens;
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
/// A session user that can be safely sent to the client
pub struct SessionUser {
id: Uuid,
name: String,
email: String,
admin: bool,
}
impl From<User> for SessionUser {
fn from(value: User) -> Self {
Self {
id: *value.id(),
name: value.name().to_string(),
email: value.email().to_string(),
admin: value.admin(),
}
}
}

View File

@@ -4,7 +4,7 @@ use crate::domain::warren::{
models::{ models::{
auth_session::requests::FetchAuthSessionResponse, auth_session::requests::FetchAuthSessionResponse,
file::{File, FilePath}, file::{File, FilePath},
user::User, user::{LoginUserResponse, User},
warren::Warren, warren::Warren,
}, },
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier}, ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
@@ -122,8 +122,8 @@ impl AuthNotifier for NotifierDebugLogger {
tracing::debug!("[Notifier] Registered user {}", user.name()); tracing::debug!("[Notifier] Registered user {}", user.name());
} }
async fn user_logged_in(&self, user: &User) { async fn user_logged_in(&self, response: &LoginUserResponse) {
tracing::debug!("[Notifier] Logged in user {}", user.name()); tracing::debug!("[Notifier] Logged in user {}", response.user().name());
} }
async fn auth_session_created(&self, user_id: &Uuid) { async fn auth_session_created(&self, user_id: &Uuid) {

View File

@@ -1,25 +1,25 @@
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import type { ApiResponse } from '~/types/api'; import type { ApiResponse } from '~/types/api';
import { getAuthSessionData } from './getSession'; import { getAuthSessionData } from './getSession';
import type { AuthUser } from '~/types/auth';
export async function loginUser( export async function loginUser(
email: string, email: string,
password: string password: string
): Promise<{ success: boolean }> { ): Promise<{ success: boolean }> {
const { data, error } = await useFetch<ApiResponse<{ token: string }>>( const { data, error } = await useFetch<
getApiUrl('auth/login'), ApiResponse<{ token: string; user: AuthUser; expiresAt: number }>
{ >(getApiUrl('auth/login'), {
method: 'POST', method: 'POST',
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
email: email, email: email,
password: password, password: password,
}), }),
responseType: 'json', responseType: 'json',
} });
);
if (data.value == null) { if (data.value == null) {
toast.error('Login', { toast.error('Login', {
@@ -31,23 +31,14 @@ export async function loginUser(
}; };
} }
// TODO: Get this data directly from the login request const token = data.value.data.token;
const sessionData = await getAuthSessionData({ const { user, expiresAt } = data.value.data;
sessionType: 'WarrenAuth',
sessionId: data.value.data.token,
});
if (!sessionData.success) {
return {
success: false,
};
}
useAuthSession().value = { useAuthSession().value = {
type: 'WarrenAuth', type: 'WarrenAuth',
id: data.value.data.token, id: token,
user: sessionData.user, user,
expiresAt: sessionData.expiresAt, expiresAt,
}; };
toast.success('Login', { toast.success('Login', {