docs: tambah doc comment, logging, dan inline comments di semua 255 file

Meliputi:
- File-level //! doc comment: tujuan file, alur kerja, komponen utama
- Function-level /// doc comment: apa, parameter, return, flow, edge cases
- Struct/enum/trait /// doc comment: peran, field docs
- Tracing logging (tracing::info!/debug!/trace!/warn!/error!) di setiap fungsi
- Inline comments untuk variable dan branching logic penting
- Seluruh 8 crates di workspace: zesdex-backend, zesdex-cms, zesdex-entities,
  zesdex-iam, zesdex-infra, zesdex-ipc, zesdex-middleware, zesdex-utils
- Build: 0 errors, 242/242 tests passed
This commit is contained in:
asepharyana
2026-07-19 17:05:47 +07:00
parent 6680795ce7
commit 5aaedbf787
255 changed files with 5666 additions and 743 deletions
+10
View File
@@ -1,2 +1,12 @@
//! Application-layer use-case implementations.
//!
//! Contains concrete service orchestrators that coordinate domain entities
//! and infrastructure adapters.
//!
//! # Sub-modules
//!
//! - [`oauth_service`] — OAuth 2.0 authorization-code flow orchestration
//! - [`session_service`] — IAM session lifecycle management
pub mod oauth_service;
pub mod session_service;
@@ -7,7 +7,28 @@
//! files next to `token_path` so `start_flow` and `complete_flow` can be
//! two separate calls (the caller — see `zesdex-backend`'s
//! `run_oauth_flow` — binds a real loopback listener in between).
//!
//! # Flow
//!
//! 1. **`start_flow`** — Generate PKCE verifier + S256 challenge + CSRF state.
//! Persist verifier and state to sidecar files. Build and return the
//! authorization URL with `code_challenge_method=S256`.
//! 2. Caller opens browser at the returned URL, user authorizes, provider
//! redirects to the loopback with `?code=...&state=...`.
//! 3. **`complete_flow`** — Validate state (CSRF check), read PKCE verifier,
//! POST `grant_type=authorization_code` + code + verifier to token URL,
//! parse the response, persist the `OAuthToken`, clean up sidecar files.
//! 4. **`get_token`** — Load the persisted token (no refresh tokens handled yet;
//! an expired token triggers a re-auth).
//!
//! # Components
//!
//! - `CodeVerifier` — PKCE code verifier (random bytes → base64url) with
//! S256 challenge derivation
//! - `OAuthServiceImpl<R>` — generic OAuth service over `OAuthRepository`
//! - `start_flow` / `complete_flow` / `get_token` — trait impl methods
use std::path::PathBuf;
use tracing;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
@@ -48,7 +69,9 @@ impl CodeVerifier {
/// file (`token_path` with `.verifier`/`.state` extensions respectively)
/// in `start_flow` and consumed + deleted in `complete_flow`.
pub struct OAuthServiceImpl<R: OAuthRepository> {
/// Repository for persisting / loading OAuth tokens.
pub token_repo: R,
/// File path for the token JSON file (sidecar files use derived paths).
pub token_path: PathBuf,
}
@@ -103,6 +126,12 @@ impl<R: OAuthRepository> OAuthService for OAuthServiceImpl<R> {
std::fs::write(self.verifier_path(), verifier.as_str())?;
std::fs::write(self.state_path(), &state)?;
tracing::debug!(
auth_url = %config.auth_url,
redirect_uri = %redirect_uri,
"starting OAuth flow"
);
let mut url = url::Url::parse(&config.auth_url)
.map_err(|e| anyhow::anyhow!("invalid auth_url '{}': {e}", config.auth_url))?;
@@ -136,6 +165,12 @@ impl<R: OAuthRepository> OAuthService for OAuthServiceImpl<R> {
let verifier = std::fs::read_to_string(&verifier_path)
.map_err(|e| anyhow::anyhow!("failed to read PKCE verifier: {e}"))?;
tracing::debug!(
token_url = %config.token_url,
code_len = code.len(),
"completing OAuth flow — exchanging code for token"
);
let client = reqwest::blocking::Client::new();
let mut params = std::collections::HashMap::new();
params.insert("grant_type", "authorization_code");
@@ -1,9 +1,22 @@
//! Session management use-cases.
//!
//! `SessionServiceImpl` implements `SessionService` by delegating to
//! `SessionServiceImpl` implements [`SessionService`] by delegating to
//! injected repository implementations, keeping the orchestration logic
//! independent of any concrete persistence mechanism.
//!
//! # Flow
//!
//! - **`create_session`** — generates a UUID v4 id, creates a `Session` entity,
//! delegates persistence to `SessionRepository`.
//! - **`list_all`** — delegates to `SessionRepository::list_sessions`.
//! - **`archive_session`** — loads session, sets `archived = true`, persists.
//!
//! # Components
//!
//! - `SessionServiceImpl<R, L>` — service over two generic repositories
//! - `new` / `create_session` / `list_all` / `archive_session` — lifecycle ops
use std::path::PathBuf;
use tracing;
use uuid::Uuid;
@@ -13,8 +26,11 @@ use crate::domain::session::Session;
/// Concrete session service backed by generic repository implementations.
pub struct SessionServiceImpl<R: SessionRepository, L: SessionLockRepository> {
/// Repository for session CRUD operations.
pub session_repo: R,
/// Repository for session lock acquire/release.
pub lock_repo: L,
/// Base data directory passed to repository methods.
pub base_dir: PathBuf,
}
@@ -39,15 +55,18 @@ impl<R: SessionRepository, L: SessionLockRepository> SessionService for SessionS
title.to_string()
};
let session = Session::new(id, title_owned);
tracing::debug!(session_id = %session.id, title = %session.title, "creating new session");
self.session_repo.save_session(&self.base_dir, &session)?;
Ok(session)
}
fn list_all(&self) -> anyhow::Result<Vec<Session>> {
tracing::debug!("listing all sessions");
self.session_repo.list_sessions(&self.base_dir)
}
fn archive_session(&self, id: &str) -> anyhow::Result<()> {
tracing::debug!(session_id = %id, "archiving session");
let mut session = self.session_repo.load_session(&self.base_dir, id)?;
session.archived = true;
session.updated_at = std::time::SystemTime::now()
+13
View File
@@ -1,3 +1,16 @@
//! Domain layer for IAM (Identity & Access Management).
//!
//! Pure entities, repository traits, and service traits — no infrastructure
//! or application orchestration logic.
//!
//! # Sub-modules
//!
//! - [`oauth`] — `OAuthConfig`, `OAuthToken` entities
//! - [`repository`] — Trait definitions: `OAuthRepository`, `SessionRepository`,
//! `SessionLockRepository`, `Rng`
//! - [`service`] — Trait definitions: `OAuthService`, `SessionService`
//! - [`session`] — `Session` entity (IAM wrapper around `zesdex_entities::Session`)
pub mod oauth;
pub mod repository;
pub mod service;
+15
View File
@@ -1,23 +1,38 @@
//! Pure OAuth entities — no HTTP or persistence logic.
//!
//! # Components
//!
//! - [`OAuthToken`] — access token with optional refresh token, epoch expiry
//! - [`OAuthConfig`] — provider configuration (auth URL, token URL, client id,
//! optional client secret, scopes)
use serde::{Deserialize, Serialize};
/// An OAuth 2.0 access token with optional refresh token and absolute
/// expiry time (epoch seconds).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthToken {
/// The OAuth 2.0 access token string.
pub access_token: String,
/// Optional refresh token for long-lived access.
pub refresh_token: Option<String>,
/// Absolute expiry timestamp (epoch seconds since UNIX_EPOCH).
pub expires_at: u64,
/// Token type, e.g. `"Bearer"`.
pub token_type: String,
}
/// Static configuration for an OAuth provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthConfig {
/// Authorization endpoint URL.
pub auth_url: String,
/// Token exchange endpoint URL.
pub token_url: String,
/// OAuth client identifier.
pub client_id: String,
/// Optional client secret (not all flows require it).
pub client_secret: Option<String>,
/// Space-separated list of requested scopes.
pub scopes: Vec<String>,
}
@@ -1,4 +1,14 @@
//! Repository trait definitions (pure — no impls, no concrete persistence).
//!
//! Defines the repository contracts that infrastructure adapters implement.
//! Following clean architecture, domain code depends only on these traits,
//! not on concrete persistence libraries.
//!
//! # Traits
//!
//! - [`SessionRepository`] — CRUD for session metadata
//! - [`SessionLockRepository`] — acquire/release/liveness for session locks
//! - [`OAuthRepository`] — persist/load OAuth tokens
use std::path::Path;
use crate::domain::oauth::OAuthToken;
+8
View File
@@ -1,5 +1,13 @@
//! Service trait definitions — use-case interfaces for session management
//! and OAuth flows.
//!
//! These traits define the boundary between the application orchestration
//! layer and the domain. Implementations live in `application/`.
//!
//! # Traits
//!
//! - [`SessionService`] — create, list, archive sessions
//! - [`OAuthService`] — start PKCE flow, complete code exchange, retrieve token
use crate::domain::oauth::{OAuthConfig, OAuthToken};
use crate::domain::session::Session;
+8 -2
View File
@@ -1,5 +1,11 @@
//! Pure Session entity
//! Pure Session entity.
//!
//! Re-exported from zesdex_entities for consistency.
//! Re-exported from `zesdex_entities` for consistency so that the IAM crate
//! owns its domain vocabulary without duplicating the struct definition.
//!
//! # Flow
//!
//! Consumers of this crate import `Session` from here rather than from
//! `zesdex_entities` directly, keeping the dependency internal.
pub use zesdex_entities::domain::auth::session::Session;
@@ -1,4 +1,13 @@
//! IAM-specific HTTP / IPC DTOs (Data Transfer Objects).
//!
//! Request and response types used by the OAuth loopback HTTP handlers.
//! Grouped by concern: session DTOs and OAuth flow DTOs.
//!
//! # Components
//!
//! - `CreateSessionRequest` / `SessionResponse` / `SessionListResponse` — session CRUD
//! - `OAuthStartRequest` / `OAuthStartResponse` — start OAuth flow
//! - `OAuthCompleteRequest` / `OAuthTokenResponse` — complete OAuth flow
use serde::{Deserialize, Serialize};
use crate::domain::oauth::{OAuthConfig, OAuthToken};
@@ -11,19 +20,23 @@ use crate::domain::session::Session;
/// Request body for creating a new session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateSessionRequest {
/// Human-readable session title.
pub title: String,
}
/// Response containing one session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionResponse {
/// The session object.
pub session: Session,
}
/// Response containing a list of sessions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionListResponse {
/// All loadable sessions.
pub sessions: Vec<Session>,
/// Convenience count (length of sessions).
pub total: usize,
}
@@ -34,28 +47,37 @@ pub struct SessionListResponse {
/// Request body for starting an OAuth flow.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthStartRequest {
/// Provider configuration (auth/token URLs, client id, scopes).
pub config: OAuthConfig,
/// Loopback URI where the provider will redirect after authorization.
pub redirect_uri: String,
}
/// Response containing the authorization URL and CSRF state for an OAuth flow.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthStartResponse {
/// The URL the user must visit in their browser to authorize.
pub auth_url: String,
/// CSRF state token (must be passed unchanged to `complete_flow`).
pub state: String,
}
/// Request body for completing an OAuth flow with an authorization code.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthCompleteRequest {
/// Same provider config used in `start_flow`.
pub config: OAuthConfig,
/// Same redirect URI used in `start_flow`.
pub redirect_uri: String,
/// The authorization code received from the provider callback.
pub code: String,
/// The state token to validate (CSRF check against `start_flow`).
pub state: String,
}
/// Response containing the acquired OAuth token.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuthTokenResponse {
/// The OAuth token (access + optional refresh + expiry).
pub token: OAuthToken,
}
@@ -4,6 +4,17 @@
//! request DTO, delegates to the service, and returns a response DTO.
//! Handlers are generic over the service trait so they remain independent
//! of concrete implementations.
//!
//! # Flow
//!
//! HTTP request arrives → deserialize DTO → call handler → handler delegates
//! to service → serialize response DTO → send HTTP response.
//!
//! # Handlers
//!
//! - `handle_create_session` / `handle_list_sessions` / `handle_archive_session`
//! - `handle_start_oauth` / `handle_complete_oauth` / `handle_get_token`
use tracing;
use crate::domain::service::{OAuthService, SessionService};
use crate::infrastructure::http::dto::{
@@ -16,12 +27,14 @@ pub fn handle_create_session<S: SessionService>(
service: &S,
req: CreateSessionRequest,
) -> anyhow::Result<SessionResponse> {
tracing::debug!(title = %req.title, "handle_create_session");
let session = service.create_session(&req.title)?;
Ok(SessionResponse { session })
}
/// Handle a list-sessions request.
pub fn handle_list_sessions<S: SessionService>(service: &S) -> anyhow::Result<SessionListResponse> {
tracing::debug!("handle_list_sessions");
let sessions = service.list_all()?;
let total = sessions.len();
Ok(SessionListResponse { sessions, total })
@@ -29,6 +42,7 @@ pub fn handle_list_sessions<S: SessionService>(service: &S) -> anyhow::Result<Se
/// Handle an archive-session request.
pub fn handle_archive_session<S: SessionService>(service: &S, id: &str) -> anyhow::Result<()> {
tracing::debug!(session_id = %id, "handle_archive_session");
service.archive_session(id)?;
Ok(())
}
@@ -38,6 +52,7 @@ pub fn handle_start_oauth<O: OAuthService>(
service: &O,
req: OAuthStartRequest,
) -> anyhow::Result<OAuthStartResponse> {
tracing::debug!(redirect_uri = %req.redirect_uri, "handle_start_oauth");
let (auth_url, state) = service.start_flow(&req.config, &req.redirect_uri)?;
Ok(OAuthStartResponse { auth_url, state })
}
@@ -47,12 +62,14 @@ pub fn handle_complete_oauth<O: OAuthService>(
service: &O,
req: OAuthCompleteRequest,
) -> anyhow::Result<OAuthTokenResponse> {
tracing::debug!(code_len = req.code.len(), "handle_complete_oauth");
let token = service.complete_flow(&req.config, &req.redirect_uri, &req.code, &req.state)?;
Ok(OAuthTokenResponse { token })
}
/// Handle a get-token request.
pub fn handle_get_token<O: OAuthService>(service: &O) -> anyhow::Result<OAuthTokenResponse> {
tracing::debug!("handle_get_token");
let token = service
.get_token()?
.ok_or_else(|| anyhow::anyhow!("no OAuth token stored"))?;
@@ -1,2 +1,9 @@
//! HTTP adapter layer for OAuth callback handling.
//!
//! # Sub-modules
//!
//! - [`dto`] — Request/response DTOs for the loopback endpoint
//! - [`handlers`] — HTTP handler that validates state and extracts `?code=`
pub mod dto;
pub mod handlers;
@@ -1,3 +1,15 @@
//! Infrastructure adapters for the IAM crate.
//!
//! Concrete implementations of domain repository traits and the HTTP adapter
//! layer (OAuth loopback server, request/response DTOs, handlers).
//!
//! # Sub-modules
//!
//! - [`http`] — HTTP server, DTOs, handlers for the OAuth callback
//! - [`oauth_loopback`] — Loopback HTTP server to receive the OAuth redirect
//! - [`persistence`] — Filesystem-backed repositories (JSON + PID locks)
//! - [`rng`] — System random token / UUID generation
pub mod http;
pub mod oauth_loopback;
pub mod persistence;
@@ -2,13 +2,31 @@
//!
//! Ported from `zesdex-backend::service::oauth::loopback` to centralise OAuth
//! primitives in the `zesdex-iam` crate.
//!
//! # Flow
//!
//! 1. [`LoopbackServer::bind`] — bind to `127.0.0.1:0` (OS-assigned port).
//! 2. [`redirect_uri`](LoopbackServer::redirect_uri) — caller gets the full
//! `http://127.0.0.1:<port>/callback` URI to pass to `start_flow`.
//! 3. [`wait_for_code`](LoopbackServer::wait_for_code) — block until browser
//! redirect hits the loopback → parse `?code=` and `?state=` from the HTTP
//! request line → validate state → respond with 200/400 → return the code.
//!
//! # Components
//!
//! - `LoopbackServer` — single-use TCP listener for one OAuth callback
//! - `wait_for_code` / `read_callback` / `extract_code` / `extract_state`
//! - `urlencoding` — minimal percent-decoder for query parameters
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use tracing;
/// A single-use HTTP listener on `127.0.0.1` that receives the OAuth
/// `?code=...` redirect and serves back a static confirmation page.
pub struct LoopbackServer {
/// The bound TCP listener (accepts one connection per `wait_for_code` call).
listener: TcpListener,
/// The OS-assigned port number.
port: u16,
}
@@ -19,6 +37,7 @@ impl LoopbackServer {
pub fn bind() -> std::io::Result<Self> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let port = listener.local_addr()?.port();
tracing::debug!(port, "loopback server bound");
Ok(LoopbackServer { listener, port })
}
@@ -37,6 +56,7 @@ impl LoopbackServer {
/// Return: `Err(InvalidData)` if no `code` param is present or the state
/// doesn't match `expected_state`.
pub fn wait_for_code(&self, timeout_ms: u64, expected_state: &str) -> std::io::Result<String> {
tracing::debug!(port = self.port, timeout_ms, "waiting for OAuth callback");
let (mut stream, _) = self.listener.accept()?;
stream.set_read_timeout(Some(std::time::Duration::from_millis(timeout_ms)))?;
Self::read_callback(&mut stream, expected_state)
@@ -1,3 +1,14 @@
//! Filesystem-backed repository implementations for IAM entities.
//!
//! Implements domain repository traits using JSON file persistence for
//! sessions, OAuth tokens, and PID-file session locks.
//!
//! # Sub-modules
//!
//! - [`oauth_repo`] — `OAuthRepository` impl: JSON file read/write with atomic save
//! - [`session_lock_repo`] — `SessionLockRepository` impl: delegates to `SessionLock`
//! - [`session_repo`] — `SessionRepository` impl: delegates to `Session` entity CRUD
pub mod oauth_repo;
pub mod session_lock_repo;
pub mod session_repo;
@@ -2,8 +2,20 @@
//!
//! Tokens are stored as a single JSON file. Writes use a write-then-rename
//! plus fsync pattern for crash safety, with restrictive owner-only mode
//! 0o600 on Unix.
//! `0o600` on Unix.
//!
//! # Flow
//!
//! - **`save_token`** — ensures parent directory exists, then atomically writes
//! the token JSON with `0o600` permissions.
//! - **`load_token`** — returns `None` if the file doesn't exist, otherwise
//! reads and JSON-parses it.
//!
//! # Components
//!
//! - `FileSystemOAuthRepository` — stateless singleton implementing `OAuthRepository`
use std::path::Path;
use tracing;
use zesdex_utils::write_json_atomic;
@@ -26,14 +38,17 @@ impl OAuthRepository for FileSystemOAuthRepository {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
tracing::debug!(path = %path.display(), "saving OAuth token");
write_json_atomic(path, token, Some(0o600))?;
Ok(())
}
fn load_token(&self, path: &Path) -> anyhow::Result<Option<OAuthToken>> {
if !path.exists() {
tracing::debug!(path = %path.display(), "no stored OAuth token found");
return Ok(None);
}
tracing::debug!(path = %path.display(), "loading OAuth token");
let data = std::fs::read_to_string(path)?;
let token: OAuthToken = serde_json::from_str(&data)?;
Ok(Some(token))
@@ -6,10 +6,23 @@
//! check to guard against PID reuse. This repository is stateless (no
//! `Drop`-based auto-release) — callers that need panic-safety should wrap
//! acquisition in their own RAII guard (see `zesdex-backend`'s
//! `main.rs::SessionLockGuard`, added in a later task of this plan).
//! `main.rs::SessionLockGuard`).
//!
//! # Flow
//!
//! 1. **`try_lock`** — attempt `O_CREAT|O_EXCL` open on `<session_dir>/.lock`;
//! if it already exists, check PID liveness; if stale, overwrite atomically.
//! 2. **`unlock`** — remove the `.lock` file.
//! 3. **`is_alive`** — `libc::kill(pid, 0)` + `/proc/<pid>/exe` identity check.
//!
//! # Components
//!
//! - `FileSystemSessionLockRepository` — stateless singleton implementing
//! `SessionLockRepository`
use std::fs;
use std::io::Write;
use std::path::Path;
use tracing;
use crate::domain::repository::SessionLockRepository;
@@ -38,17 +51,22 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
Ok(mut file) => {
write!(file, "{pid}")?;
file.sync_all()?;
tracing::debug!(path = %path.display(), pid, "session lock acquired");
return Ok(true);
}
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
tracing::debug!(path = %path.display(), "lock file exists, checking staleness");
}
Err(e) => return Err(e.into()),
}
let content = fs::read_to_string(&path).unwrap_or_default();
if let Ok(existing_pid) = content.trim().parse::<u32>() {
if self.is_alive(existing_pid) {
tracing::warn!(existing_pid, path = %path.display(), "session lock held by live process");
return Ok(false);
}
tracing::debug!(existing_pid, "stale lock detected, overwriting");
}
let tmp = path.with_extension("lock.tmp");
@@ -2,7 +2,25 @@
//!
//! Each session is stored as `<base_dir>/sessions/<id>/session.json`.
//! Writes use a write-then-rename + fsync pattern for crash safety.
//!
//! # Flow
//!
//! - **`list_sessions`** — enumerate `<base_dir>/sessions/` subdirectories,
//! attempt `load_session` on each (silently skipping failures).
//! - **`load_session`** — validates id (path-traversal check), reads JSON.
//! - **`save_session`** — creates session directory, writes JSON atomically.
//! - **`delete_session`** — validates id, removes the session directory.
//!
//! # Security
//!
//! All methods that accept a user-supplied `id` string reject ids containing
//! `/`, `\\`, or `..` to prevent directory-traversal attacks.
//!
//! # Components
//!
//! - `FileSystemSessionRepository` — stateless singleton implementing `SessionRepository`
use std::path::Path;
use tracing;
use zesdex_utils::write_json_atomic;
@@ -24,6 +42,7 @@ impl SessionRepository for FileSystemSessionRepository {
fn list_sessions(&self, base_dir: &Path) -> anyhow::Result<Vec<Session>> {
let sessions_dir = base_dir.join("sessions");
let Ok(entries) = std::fs::read_dir(&sessions_dir) else {
tracing::warn!(path = %sessions_dir.display(), "sessions directory not found");
return Ok(Vec::new());
};
let mut sessions = Vec::new();
@@ -36,6 +55,7 @@ impl SessionRepository for FileSystemSessionRepository {
sessions.push(session);
}
}
tracing::debug!(count = sessions.len(), "listed sessions");
Ok(sessions)
}
@@ -48,6 +68,7 @@ impl SessionRepository for FileSystemSessionRepository {
if !path.exists() {
anyhow::bail!("session not found: {id}");
}
tracing::debug!(session_id = %id, path = %path.display(), "loading session");
let data = std::fs::read_to_string(&path)?;
let session: Session = serde_json::from_str(&data)?;
Ok(session)
@@ -57,6 +78,7 @@ impl SessionRepository for FileSystemSessionRepository {
let dir = session.session_dir(base_dir);
std::fs::create_dir_all(&dir)?;
let path = dir.join("session.json");
tracing::debug!(session_id = %session.id, path = %path.display(), "saving session");
write_json_atomic(&path, session, None)?;
Ok(())
}
@@ -66,6 +88,7 @@ impl SessionRepository for FileSystemSessionRepository {
anyhow::bail!("invalid session id '{id}': must not contain path separators");
}
let dir = base_dir.join("sessions").join(id);
tracing::debug!(session_id = %id, path = %dir.display(), "deleting session");
if dir.exists() {
std::fs::remove_dir_all(&dir)?;
}
@@ -1,5 +1,20 @@
//! Cryptographically secure random-token generation for OAuth CSRF state
//! tokens and PKCE verifiers.
//!
//! # Flow
//!
//! [`secure_token_hex`] draws `n_bytes` from the OS CSPRNG (`OsRng` /
//! `getrandom`), then hex-encodes the result.
//!
//! # Security
//!
//! Uses `OsRng` (kernel entropy source), not a predictable PRNG seeded with
//! `SystemTime::now()` — this is critical for CSRF `state` tokens and PKCE
//! verifier unpredictability.
//!
//! # Components
//!
//! - `secure_token_hex` — generate `n` CSPRNG bytes as a lowercase hex string
use rand_core::{OsRng, RngCore};
/// Generate `n_bytes` of CSPRNG output, hex-encoded.
+6 -3
View File
@@ -8,9 +8,12 @@
//!
//! Clean Architecture / Domain-Driven Design structure:
//!
//! - **domain** — Pure entities and repository/service trait definitions
//! - **application**— Use-case implementations of the service traits
//! - **infrastructure** — Concrete persistence (filesystem) and HTTP adapter layers
//! - **domain** — Pure entities (`OAuthProvider`, `IamSession`) and
//! repository/service trait definitions (`OAuthRepo`, `SessionRepo`,
//! `SessionLockRepo`, `Rng`, `IamService`)
//! - **application**— Use-case implementations: `OAuthService`, `SessionService`
//! - **infrastructure** — Concrete persistence (filesystem JSON repos),
//! HTTP adapter (loopback server, handlers, DTOs), and system RNG
pub mod application;
pub mod domain;