feat: refresh token

This commit is contained in:
2026-08-29 12:58:34 +02:00
parent ec1bbf729f
commit a9194fa66c
22 changed files with 537 additions and 15 deletions

View File

@@ -0,0 +1,4 @@
pub mod ports;
pub mod refresh_session;
pub use refresh_session::{RefreshSession, RefreshSessionId};

View File

@@ -0,0 +1,14 @@
use async_trait::async_trait;
use super::RefreshSession;
use crate::errors::DomainResult;
use crate::user::UserId;
#[async_trait]
pub trait RefreshSessionRepository: Send + Sync {
async fn create(&self, session: &RefreshSession) -> DomainResult<()>;
async fn find_by_token(&self, token: &str) -> DomainResult<Option<RefreshSession>>;
async fn revoke(&self, token: &str) -> DomainResult<()>;
async fn revoke_all_for_user(&self, user_id: &UserId) -> DomainResult<()>;
async fn delete_expired(&self) -> DomainResult<u64>;
}

View File

@@ -0,0 +1,83 @@
use chrono::{DateTime, Duration, Utc};
use uuid::Uuid;
use crate::user::UserId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RefreshSessionId(Uuid);
impl RefreshSessionId {
pub fn generate() -> Self {
Self(Uuid::new_v4())
}
pub fn from_uuid(id: Uuid) -> Self {
Self(id)
}
pub fn as_uuid(self) -> Uuid {
self.0
}
}
#[derive(Debug, Clone)]
pub struct RefreshSession {
id: RefreshSessionId,
user_id: UserId,
token: String,
expires_at: DateTime<Utc>,
created_at: DateTime<Utc>,
}
impl RefreshSession {
pub fn new(user_id: UserId, ttl_seconds: i64) -> Self {
let now = Utc::now();
Self {
id: RefreshSessionId::generate(),
user_id,
token: Uuid::new_v4().to_string(),
expires_at: now + Duration::seconds(ttl_seconds),
created_at: now,
}
}
pub fn from_persistence(
id: RefreshSessionId,
user_id: UserId,
token: String,
expires_at: DateTime<Utc>,
created_at: DateTime<Utc>,
) -> Self {
Self {
id,
user_id,
token,
expires_at,
created_at,
}
}
pub fn id(&self) -> RefreshSessionId {
self.id
}
pub fn user_id(&self) -> UserId {
self.user_id
}
pub fn token(&self) -> &str {
&self.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
}
}

View File

@@ -8,6 +8,8 @@ pub enum DomainError {
Conflict(String),
#[error("forbidden: {0}")]
Forbidden(String),
#[error("unauthorized: {0}")]
Unauthorized(String),
#[error("validation: {0}")]
Validation(String),
#[error("repository: {0}")]

View File

@@ -1,3 +1,4 @@
pub mod auth;
pub mod errors;
pub mod events;
pub mod note;