adding user authentication
This commit is contained in:
@@ -50,6 +50,22 @@ pub struct File {
|
||||
|
||||
// Ya no necesitamos este módulo, ahora usamos un String directamente
|
||||
|
||||
impl Default for File {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: "stub-id".to_string(),
|
||||
name: "stub-file.txt".to_string(),
|
||||
storage_path: StoragePath::from_string("/"),
|
||||
path_string: "/".to_string(),
|
||||
size: 0,
|
||||
mime_type: "application/octet-stream".to_string(),
|
||||
folder_id: None,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// Crea un nuevo archivo con validación
|
||||
pub fn new(
|
||||
|
||||
@@ -44,6 +44,20 @@ pub struct Folder {
|
||||
|
||||
// Ya no necesitamos este módulo, ahora usamos un String directamente
|
||||
|
||||
impl Default for Folder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: "stub-id".to_string(),
|
||||
name: "stub-folder".to_string(),
|
||||
storage_path: StoragePath::from_string("/"),
|
||||
path_string: "/".to_string(),
|
||||
parent_id: None,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
/// Creates a new folder with validation
|
||||
pub fn new(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod file;
|
||||
pub mod folder;
|
||||
pub mod user;
|
||||
pub mod session;
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc, Duration};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub refresh_token: String,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
pub ip_address: Option<String>,
|
||||
pub user_agent: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub revoked: bool,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(
|
||||
user_id: String,
|
||||
refresh_token: String,
|
||||
ip_address: Option<String>,
|
||||
user_agent: Option<String>,
|
||||
expires_in_days: i64,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
user_id,
|
||||
refresh_token,
|
||||
expires_at: now + Duration::days(expires_in_days),
|
||||
ip_address,
|
||||
user_agent,
|
||||
created_at: now,
|
||||
revoked: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn user_id(&self) -> &str {
|
||||
&self.user_id
|
||||
}
|
||||
|
||||
pub fn refresh_token(&self) -> &str {
|
||||
&self.refresh_token
|
||||
}
|
||||
|
||||
pub fn expires_at(&self) -> DateTime<Utc> {
|
||||
self.expires_at
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> DateTime<Utc> {
|
||||
self.created_at
|
||||
}
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
Utc::now() > self.expires_at
|
||||
}
|
||||
|
||||
pub fn is_revoked(&self) -> bool {
|
||||
self.revoked
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self) {
|
||||
self.revoked = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use argon2::password_hash::SaltString;
|
||||
use rand_core::OsRng;
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UserError {
|
||||
#[error("Username inválido: {0}")]
|
||||
InvalidUsername(String),
|
||||
|
||||
#[error("Password inválido: {0}")]
|
||||
InvalidPassword(String),
|
||||
|
||||
#[error("Error en la validación: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
#[error("Error en la autenticación: {0}")]
|
||||
AuthenticationError(String),
|
||||
}
|
||||
|
||||
pub type UserResult<T> = Result<T, UserError>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
pub enum UserRole {
|
||||
Admin,
|
||||
User,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserRole {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
UserRole::Admin => write!(f, "admin"),
|
||||
UserRole::User => write!(f, "user"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
id: String,
|
||||
username: String,
|
||||
email: String,
|
||||
#[serde(skip_serializing)]
|
||||
password_hash: String,
|
||||
role: UserRole,
|
||||
storage_quota_bytes: i64,
|
||||
storage_used_bytes: i64,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
last_login_at: Option<DateTime<Utc>>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(
|
||||
username: String,
|
||||
email: String,
|
||||
password: String,
|
||||
role: UserRole,
|
||||
storage_quota_bytes: i64,
|
||||
) -> UserResult<Self> {
|
||||
// Validaciones
|
||||
if username.is_empty() || username.len() < 3 || username.len() > 32 {
|
||||
return Err(UserError::InvalidUsername(format!(
|
||||
"Username debe tener entre 3 y 32 caracteres"
|
||||
)));
|
||||
}
|
||||
|
||||
if !email.contains('@') || email.len() < 5 {
|
||||
return Err(UserError::ValidationError(format!(
|
||||
"Email inválido"
|
||||
)));
|
||||
}
|
||||
|
||||
if password.len() < 8 {
|
||||
return Err(UserError::InvalidPassword(format!(
|
||||
"Password debe tener al menos 8 caracteres"
|
||||
)));
|
||||
}
|
||||
|
||||
// Generar hash con Argon2id (recomendado para 2023+)
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
let password_hash = argon2.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|e| UserError::ValidationError(format!("Error al generar hash: {}", e)))?
|
||||
.to_string();
|
||||
|
||||
let now = Utc::now();
|
||||
|
||||
Ok(Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
role,
|
||||
storage_quota_bytes,
|
||||
storage_used_bytes: 0,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
last_login_at: None,
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Crear desde valores existentes (para reconstrucción desde BD)
|
||||
pub fn from_data(
|
||||
id: String,
|
||||
username: String,
|
||||
email: String,
|
||||
password_hash: String,
|
||||
role: UserRole,
|
||||
storage_quota_bytes: i64,
|
||||
storage_used_bytes: i64,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
last_login_at: Option<DateTime<Utc>>,
|
||||
active: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
role,
|
||||
storage_quota_bytes,
|
||||
storage_used_bytes,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_login_at,
|
||||
active,
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
||||
pub fn email(&self) -> &str {
|
||||
&self.email
|
||||
}
|
||||
|
||||
pub fn role(&self) -> UserRole {
|
||||
self.role
|
||||
}
|
||||
|
||||
pub fn storage_quota_bytes(&self) -> i64 {
|
||||
self.storage_quota_bytes
|
||||
}
|
||||
|
||||
pub fn storage_used_bytes(&self) -> i64 {
|
||||
self.storage_used_bytes
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> DateTime<Utc> {
|
||||
self.created_at
|
||||
}
|
||||
|
||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||
self.updated_at
|
||||
}
|
||||
|
||||
pub fn last_login_at(&self) -> Option<DateTime<Utc>> {
|
||||
self.last_login_at
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
pub fn password_hash(&self) -> &str {
|
||||
&self.password_hash
|
||||
}
|
||||
|
||||
// Verificación de password
|
||||
pub fn verify_password(&self, password: &str) -> UserResult<bool> {
|
||||
let parsed_hash = PasswordHash::new(&self.password_hash)
|
||||
.map_err(|e| UserError::AuthenticationError(format!("Error al procesar hash: {}", e)))?;
|
||||
|
||||
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok())
|
||||
}
|
||||
|
||||
// Cambiar contraseña
|
||||
pub fn update_password(&mut self, new_password: String) -> UserResult<()> {
|
||||
if new_password.len() < 8 {
|
||||
return Err(UserError::InvalidPassword(format!(
|
||||
"Password debe tener al menos 8 caracteres"
|
||||
)));
|
||||
}
|
||||
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
self.password_hash = argon2.hash_password(new_password.as_bytes(), &salt)
|
||||
.map_err(|e| UserError::ValidationError(format!("Error al generar hash: {}", e)))?
|
||||
.to_string();
|
||||
|
||||
self.updated_at = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Actualizar uso de almacenamiento
|
||||
pub fn update_storage_used(&mut self, storage_used_bytes: i64) {
|
||||
self.storage_used_bytes = storage_used_bytes;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
// Registrar login
|
||||
pub fn register_login(&mut self) {
|
||||
let now = Utc::now();
|
||||
self.last_login_at = Some(now);
|
||||
self.updated_at = now;
|
||||
}
|
||||
|
||||
// Desactivar usuario
|
||||
pub fn deactivate(&mut self) {
|
||||
self.active = false;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
// Activar usuario
|
||||
pub fn activate(&mut self) {
|
||||
self.active = true;
|
||||
self.updated_at = Utc::now();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::common::errors::DomainError;
|
||||
use futures::Stream;
|
||||
use bytes::Bytes;
|
||||
|
||||
@@ -23,9 +24,15 @@ pub enum FileRepositoryError {
|
||||
#[error("Mapping error: {0}")]
|
||||
MappingError(String),
|
||||
|
||||
#[error("ID Mapping error: {0}")]
|
||||
IdMappingError(String),
|
||||
|
||||
#[error("Timeout error: {0}")]
|
||||
Timeout(String),
|
||||
|
||||
#[error("Domain error: {0}")]
|
||||
DomainError(#[from] DomainError),
|
||||
|
||||
#[error("Other error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::domain::entities::folder::Folder;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Error types for folder repository operations
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -24,6 +25,9 @@ pub enum FolderRepositoryError {
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
#[error("Domain error: {0}")]
|
||||
DomainError(#[from] DomainError),
|
||||
|
||||
#[error("Other error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod file_repository;
|
||||
pub mod folder_repository;
|
||||
pub mod user_repository;
|
||||
pub mod session_repository;
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::domain::entities::session::Session;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SessionRepositoryError {
|
||||
#[error("Sesión no encontrada: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Error de base de datos: {0}")]
|
||||
DatabaseError(String),
|
||||
|
||||
#[error("Error de tiempo de espera: {0}")]
|
||||
Timeout(String),
|
||||
}
|
||||
|
||||
pub type SessionRepositoryResult<T> = Result<T, SessionRepositoryError>;
|
||||
|
||||
// Conversión de SessionRepositoryError a DomainError
|
||||
impl From<SessionRepositoryError> for DomainError {
|
||||
fn from(err: SessionRepositoryError) -> Self {
|
||||
match err {
|
||||
SessionRepositoryError::NotFound(msg) => {
|
||||
DomainError::not_found("Session", msg)
|
||||
},
|
||||
SessionRepositoryError::DatabaseError(msg) => {
|
||||
DomainError::internal_error("Database", msg)
|
||||
},
|
||||
SessionRepositoryError::Timeout(msg) => {
|
||||
DomainError::timeout("Database", msg)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait SessionRepository: Send + Sync + 'static {
|
||||
/// Crea una nueva sesión
|
||||
async fn create_session(&self, session: Session) -> SessionRepositoryResult<Session>;
|
||||
|
||||
/// Obtiene una sesión por ID
|
||||
async fn get_session_by_id(&self, id: &str) -> SessionRepositoryResult<Session>;
|
||||
|
||||
/// Obtiene una sesión por token de actualización
|
||||
async fn get_session_by_refresh_token(&self, refresh_token: &str) -> SessionRepositoryResult<Session>;
|
||||
|
||||
/// Obtiene todas las sesiones de un usuario
|
||||
async fn get_sessions_by_user_id(&self, user_id: &str) -> SessionRepositoryResult<Vec<Session>>;
|
||||
|
||||
/// Revoca una sesión específica
|
||||
async fn revoke_session(&self, session_id: &str) -> SessionRepositoryResult<()>;
|
||||
|
||||
/// Revoca todas las sesiones de un usuario
|
||||
async fn revoke_all_user_sessions(&self, user_id: &str) -> SessionRepositoryResult<u64>;
|
||||
|
||||
/// Elimina sesiones expiradas
|
||||
async fn delete_expired_sessions(&self) -> SessionRepositoryResult<u64>;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::domain::entities::user::{User, UserRole};
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UserRepositoryError {
|
||||
#[error("Usuario no encontrado: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Usuario ya existe: {0}")]
|
||||
AlreadyExists(String),
|
||||
|
||||
#[error("Error de base de datos: {0}")]
|
||||
DatabaseError(String),
|
||||
|
||||
#[error("Error de validación: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
#[error("Error de tiempo de espera: {0}")]
|
||||
Timeout(String),
|
||||
|
||||
#[error("Operación no permitida: {0}")]
|
||||
OperationNotAllowed(String),
|
||||
}
|
||||
|
||||
pub type UserRepositoryResult<T> = Result<T, UserRepositoryError>;
|
||||
|
||||
// Conversión de UserRepositoryError a DomainError
|
||||
impl From<UserRepositoryError> for DomainError {
|
||||
fn from(err: UserRepositoryError) -> Self {
|
||||
match err {
|
||||
UserRepositoryError::NotFound(msg) => {
|
||||
DomainError::not_found("User", msg)
|
||||
},
|
||||
UserRepositoryError::AlreadyExists(msg) => {
|
||||
DomainError::already_exists("User", msg)
|
||||
},
|
||||
UserRepositoryError::DatabaseError(msg) => {
|
||||
DomainError::internal_error("Database", msg)
|
||||
},
|
||||
UserRepositoryError::ValidationError(msg) => {
|
||||
DomainError::validation_error("User", msg)
|
||||
},
|
||||
UserRepositoryError::Timeout(msg) => {
|
||||
DomainError::timeout("Database", msg)
|
||||
},
|
||||
UserRepositoryError::OperationNotAllowed(msg) => {
|
||||
DomainError::access_denied("User", msg)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserRepository: Send + Sync + 'static {
|
||||
/// Crea un nuevo usuario
|
||||
async fn create_user(&self, user: User) -> UserRepositoryResult<User>;
|
||||
|
||||
/// Obtiene un usuario por ID
|
||||
async fn get_user_by_id(&self, id: &str) -> UserRepositoryResult<User>;
|
||||
|
||||
/// Obtiene un usuario por nombre de usuario
|
||||
async fn get_user_by_username(&self, username: &str) -> UserRepositoryResult<User>;
|
||||
|
||||
/// Obtiene un usuario por correo electrónico
|
||||
async fn get_user_by_email(&self, email: &str) -> UserRepositoryResult<User>;
|
||||
|
||||
/// Actualiza un usuario existente
|
||||
async fn update_user(&self, user: User) -> UserRepositoryResult<User>;
|
||||
|
||||
/// Actualiza solo el uso de almacenamiento de un usuario
|
||||
async fn update_storage_usage(&self, user_id: &str, usage_bytes: i64) -> UserRepositoryResult<()>;
|
||||
|
||||
/// Actualiza la fecha de último inicio de sesión
|
||||
async fn update_last_login(&self, user_id: &str) -> UserRepositoryResult<()>;
|
||||
|
||||
/// Lista usuarios con paginación
|
||||
async fn list_users(&self, limit: i64, offset: i64) -> UserRepositoryResult<Vec<User>>;
|
||||
|
||||
/// Activa o desactiva un usuario
|
||||
async fn set_user_active_status(&self, user_id: &str, active: bool) -> UserRepositoryResult<()>;
|
||||
|
||||
/// Cambia la contraseña de un usuario
|
||||
async fn change_password(&self, user_id: &str, password_hash: &str) -> UserRepositoryResult<()>;
|
||||
|
||||
/// Cambia el rol de un usuario
|
||||
async fn change_role(&self, user_id: &str, role: UserRole) -> UserRepositoryResult<()>;
|
||||
|
||||
/// Elimina un usuario
|
||||
async fn delete_user(&self, user_id: &str) -> UserRepositoryResult<()>;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use uuid::Uuid;
|
||||
use chrono::{Utc, DateTime};
|
||||
|
||||
use crate::domain::entities::user::{User, UserRole};
|
||||
use crate::common::errors::{DomainError, ErrorKind};
|
||||
|
||||
// Reclamaciones JWT
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TokenClaims {
|
||||
pub sub: String, // user ID
|
||||
pub exp: i64, // expiration timestamp
|
||||
pub iat: i64, // issued at timestamp
|
||||
pub jti: String, // JWT ID
|
||||
pub username: String, // username
|
||||
pub email: String, // email
|
||||
pub role: String, // role as string
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
#[error("Credenciales inválidas")]
|
||||
InvalidCredentials,
|
||||
|
||||
#[error("Token expirado")]
|
||||
TokenExpired,
|
||||
|
||||
#[error("Token inválido: {0}")]
|
||||
InvalidToken(String),
|
||||
|
||||
#[error("Acceso denegado: {0}")]
|
||||
AccessDenied(String),
|
||||
|
||||
#[error("Operación no permitida: {0}")]
|
||||
OperationNotAllowed(String),
|
||||
|
||||
#[error("Error interno: {0}")]
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
impl From<AuthError> for DomainError {
|
||||
fn from(err: AuthError) -> Self {
|
||||
match err {
|
||||
AuthError::InvalidCredentials => {
|
||||
DomainError::new(ErrorKind::AccessDenied, "Auth", "Credenciales inválidas")
|
||||
},
|
||||
AuthError::TokenExpired => {
|
||||
DomainError::new(ErrorKind::AccessDenied, "Auth", "Token expirado")
|
||||
},
|
||||
AuthError::InvalidToken(msg) => {
|
||||
DomainError::new(ErrorKind::AccessDenied, "Auth", format!("Token inválido: {}", msg))
|
||||
},
|
||||
AuthError::AccessDenied(msg) => {
|
||||
DomainError::new(ErrorKind::AccessDenied, "Auth", msg)
|
||||
},
|
||||
AuthError::OperationNotAllowed(msg) => {
|
||||
DomainError::new(ErrorKind::AccessDenied, "Auth", msg)
|
||||
},
|
||||
AuthError::InternalError(msg) => {
|
||||
DomainError::new(ErrorKind::InternalError, "Auth", msg)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthService {
|
||||
jwt_secret: String,
|
||||
access_token_expiry: i64, // segundos
|
||||
refresh_token_expiry: i64, // segundos
|
||||
}
|
||||
|
||||
impl AuthService {
|
||||
pub fn new(jwt_secret: String, access_token_expiry_secs: i64, refresh_token_expiry_secs: i64) -> Self {
|
||||
Self {
|
||||
jwt_secret,
|
||||
access_token_expiry: access_token_expiry_secs,
|
||||
refresh_token_expiry: refresh_token_expiry_secs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_access_token(&self, user: &User) -> Result<String, AuthError> {
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
let claims = TokenClaims {
|
||||
sub: user.id().to_string(),
|
||||
exp: now + self.access_token_expiry,
|
||||
iat: now,
|
||||
jti: Uuid::new_v4().to_string(),
|
||||
username: user.username().to_string(),
|
||||
email: user.email().to_string(),
|
||||
role: format!("{}", user.role()),
|
||||
};
|
||||
|
||||
encode(
|
||||
&Header::default(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(self.jwt_secret.as_bytes())
|
||||
)
|
||||
.map_err(|e| AuthError::InternalError(format!("Error al generar token: {}", e)))
|
||||
}
|
||||
|
||||
pub fn generate_refresh_token(&self) -> String {
|
||||
Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
pub fn validate_token(&self, token: &str) -> Result<TokenClaims, AuthError> {
|
||||
let validation = Validation::new(Algorithm::HS256);
|
||||
|
||||
let token_data = decode::<TokenClaims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(self.jwt_secret.as_bytes()),
|
||||
&validation
|
||||
)
|
||||
.map_err(|e| {
|
||||
match e.kind() {
|
||||
jsonwebtoken::errors::ErrorKind::ExpiredSignature => AuthError::TokenExpired,
|
||||
_ => AuthError::InvalidToken(format!("Error al validar token: {}", e)),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
|
||||
// Duración del refresh token en segundos
|
||||
pub fn refresh_token_expiry_secs(&self) -> i64 {
|
||||
self.refresh_token_expiry
|
||||
}
|
||||
|
||||
// Duración del refresh token en días (para la entidad Session)
|
||||
pub fn refresh_token_expiry_days(&self) -> i64 {
|
||||
self.refresh_token_expiry / (24 * 3600)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod i18n_service;
|
||||
pub mod path_service;
|
||||
pub mod path_service;
|
||||
pub mod auth_service;
|
||||
Reference in New Issue
Block a user