Skip to content

AUTH_ARCHITECTURE.md — Authentication Architecture

Status

  • Phase: 8
  • Authority: Normative
  • Depends on: AUTH_VISION.md
  • Date: 2026-02-06

1. Overview

This document defines the technical architecture for AeroDB's authentication and authorization system.


2. Module Structure

src/auth/
├── mod.rs           # Module exports and public API
├── user.rs          # User model, storage, and CRUD
├── session.rs       # Session and refresh token management
├── jwt.rs           # JWT generation, validation, claims
├── crypto.rs        # Password hashing (Argon2id)
├── email.rs         # Email sending abstraction
├── api.rs           # HTTP API endpoints
├── rls.rs           # Row-Level Security enforcement
├── api_key.rs       # API key management
└── errors.rs        # Auth-specific error types

3. Data Models

3.1 User Model

Users are stored as AeroDB documents in the _users collection:

pub struct User {
    pub id: Uuid,
    pub email: String,
    pub email_verified: bool,
    pub password_hash: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub metadata: Option<serde_json::Value>,
}

Constraints: - Email must be unique (enforced by unique index) - Password hash uses Argon2id (never stored plaintext) - ID is UUIDv4 (generated on creation)

3.2 Session Model

Sessions are stored in the _sessions collection:

pub struct Session {
    pub id: Uuid,
    pub user_id: Uuid,
    pub refresh_token_hash: String,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub revoked: bool,
}

3.3 JWT Claims

Access tokens contain:

pub struct JwtClaims {
    pub sub: Uuid,           // User ID
    pub email: String,       // User email
    pub iat: i64,            // Issued at
    pub exp: i64,            // Expires at (15 min)
    pub aud: String,         // Audience (project ID)
    pub iss: String,         // Issuer (aerodb)
}

4. Security Architecture

4.1 Password Hashing

// src/auth/crypto.rs
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::SaltString;

pub fn hash_password(password: &str) -> Result<String, AuthError> {
    let salt = SaltString::generate(&mut OsRng);
    let argon2 = Argon2::default();
    let hash = argon2.hash_password(password.as_bytes(), &salt)?;
    Ok(hash.to_string())
}

pub fn verify_password(password: &str, hash: &str) -> Result<bool, AuthError> {
    let parsed = PasswordHash::new(hash)?;
    Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
}

4.2 JWT Signing

  • Algorithm: HS256 (HMAC-SHA256)
  • Secret: 256-bit random key (configured per deployment)
  • Access token TTL: 15 minutes
  • Refresh token TTL: 30 days

4.3 Refresh Token Storage

Refresh tokens are: 1. Generated as 256-bit random values 2. Hashed with SHA-256 before storage 3. Returned to client as base64-encoded raw value 4. Validated by hashing client-provided value and comparing


5. Row-Level Security (RLS)

5.1 RLS Context

Every authenticated request carries an RLS context:

pub struct RlsContext {
    pub user_id: Uuid,
    pub is_authenticated: bool,
    pub is_service_role: bool,  // API key with service role
}

5.2 RLS Enforcement Points

RLS is enforced at query planning time, not execution:

// src/auth/rls.rs
pub trait RlsEnforcer {
    /// Inject RLS filters into a read query
    fn enforce_read(&self, query: &Query, ctx: &RlsContext) -> Result<Query, RlsError>;

    /// Validate write operation against RLS policy
    fn enforce_write(&self, doc: &Document, ctx: &RlsContext) -> Result<(), RlsError>;
}

5.3 Default RLS Policy

Unless overridden, the default policy is: - Users can only read/write documents where owner_id == user_id - Service role keys bypass RLS (explicit opt-in)


6. API Endpoints

Method Path Description
POST /auth/signup Register new user
POST /auth/login Authenticate user
POST /auth/logout Invalidate session
POST /auth/refresh Refresh access token
POST /auth/forgot-password Request password reset
POST /auth/reset-password Reset password with token
GET /auth/user Get current user info
PUT /auth/user Update user profile

7. Integration with Core

7.1 No Core Modifications

Phase 8 MUST NOT modify: - WAL format or behavior - MVCC mechanics - Replication protocol - Core query execution

7.2 Planner Extension

RLS extends the planner via composition:

// RLS wraps the existing planner
pub struct RlsPlanner<P: Planner> {
    inner: P,
    enforcer: Box<dyn RlsEnforcer>,
}

impl<P: Planner> Planner for RlsPlanner<P> {
    fn plan(&self, query: &Query, ctx: &RlsContext) -> Result<Plan, PlanError> {
        let filtered_query = self.enforcer.enforce_read(query, ctx)?;
        self.inner.plan(&filtered_query)
    }
}

8. Observability

All authentication events emit structured logs:

pub enum AuthEvent {
    SignupAttempt { email: String, success: bool },
    LoginAttempt { email: String, success: bool },
    TokenRefresh { user_id: Uuid },
    PasswordReset { user_id: Uuid },
    RlsViolation { user_id: Uuid, collection: String },
}

9. Configuration

[auth]
enabled = true
jwt_secret = "your-256-bit-secret"
jwt_access_ttl_seconds = 900       # 15 minutes
jwt_refresh_ttl_days = 30

[auth.password]
min_length = 8
require_uppercase = false
require_number = false

[auth.email]
smtp_host = "smtp.example.com"
smtp_port = 587
from_address = "noreply@example.com"

10. Constraints

Phase 8 MUST: - Never log passwords (even hashed) - Never store plaintext passwords - Always use constant-time comparison for secrets - Fail closed on any validation error

Phase 8 MUST NOT: - Modify Phase 0-7 behavior - Add WAL records for auth operations - Introduce background cleanup threads


END OF DOCUMENT