Skip to main content

nythos_core/ports/
session.rs

1use crate::{NythosResult, RefreshToken, Session, SessionId, TenantId, UserId};
2
3/// Domain-facing session payload used when auth services persist or reload a
4/// session together with its current opaque refresh token.
5///
6/// This keeps the contract focused on core session state instead of storage
7/// rows, token tables, or transport-layer cookie shapes.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct SessionRecord {
10    session: Session,
11    refresh_token: RefreshToken,
12}
13
14impl SessionRecord {
15    pub fn new(session: Session, refresh_token: RefreshToken) -> Self {
16        Self {
17            session,
18            refresh_token,
19        }
20    }
21
22    pub fn session(&self) -> &Session {
23        &self.session
24    }
25
26    pub fn refresh_token(&self) -> &RefreshToken {
27        &self.refresh_token
28    }
29
30    pub fn into_parts(self) -> (Session, RefreshToken) {
31        (self.session, self.refresh_token)
32    }
33}
34
35/// Refresh-token rotation command.
36///
37/// Makes one-time rotation semantics explicit at the contract boundary.
38/// This is a domain-facing command, not a storage-specific mutation DTO.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct RefreshTokenRotation {
41    session_id: SessionId,
42    previous: RefreshToken,
43    next: RefreshToken,
44}
45
46impl RefreshTokenRotation {
47    pub fn new(session_id: SessionId, previous: RefreshToken, next: RefreshToken) -> Self {
48        Self {
49            session_id,
50            previous,
51            next,
52        }
53    }
54
55    pub const fn session_id(&self) -> SessionId {
56        self.session_id
57    }
58
59    pub fn previous(&self) -> &RefreshToken {
60        &self.previous
61    }
62
63    pub fn next(&self) -> &RefreshToken {
64        &self.next
65    }
66
67    pub fn into_parts(self) -> (SessionId, RefreshToken, RefreshToken) {
68        (self.session_id, self.previous, self.next)
69    }
70}
71
72/// Session store contract used by register, login, refresh, logout, and revoke flows.
73///
74/// Rotation semantics are explicit at the API boundary:
75/// - sessions are created together with one opaque refresh token
76/// - refresh-token lookup returns the owning session context
77/// - successful rotation invalidates the previous refresh token in favor of the next one
78/// - revoke-all is always tenant-scoped
79pub trait SessionStore {
80    /// Persists a newly issued session together with its initial refresh token.
81    async fn create_session(&self, record: SessionRecord) -> NythosResult<()>;
82
83    /// Finds the session currently associated with an opaque refresh token.
84    async fn find_by_refresh_token(
85        &self,
86        refresh_token: &RefreshToken,
87    ) -> NythosResult<Option<SessionRecord>>;
88
89    /// Rotates a refresh token for a specific session.
90    ///
91    /// Implementations should treat the `previous` token as invalid after a
92    /// successful rotation.
93    async fn rotate_refresh_token(&self, rotation: RefreshTokenRotation) -> NythosResult<()>;
94
95    /// Revokes a single session by ID.
96    async fn revoke_session(&self, session_id: SessionId) -> NythosResult<()>;
97
98    /// Revokes all sessions owned by a user within a specific tenant.
99    async fn revoke_all_for_user(&self, tenant_id: TenantId, user_id: UserId) -> NythosResult<()>;
100}