Skip to main content

Authorization & Access Control - Domain Specification

First Introduced: MVP.0 Status: Specification Complete Last Updated: 2025-10-25


Overview

Authorization & Access Control define what actions users can take and where they can take them within the platform. This domain combines Membership-based scoping (Org and Account) with Role-Based Access Control (RBAC) to form a clear, auditable, and extensible model. It ensures that every API call, UI action, and data mutation respects the correct organizational and account boundaries while maintaining a comprehensive audit trail.


Responsibilities

This domain IS responsible for:

  • Role-based permission management (RBAC)
  • Permission evaluation and enforcement
  • Session lifecycle management (creation, validation, expiration, revocation)
  • Authentication token handling (JWT issuance and validation)
  • Permission-to-role mapping with allow/deny semantics
  • Scope-based access control (Org-wide vs. Account-level)
  • Session security and rotation
  • OAuth/OIDC integration (Google SSO)

This domain is NOT responsible for:

  • Defining organizational boundaries (→ Identity & Tenancy domain)
  • User identity management (→ Identity & Tenancy domain)
  • Resource ownership tracking (→ Individual resource domains)
  • Audit event logging (→ Analytics domain)
  • Fine-grained resource-level policies (deferred to ABAC implementation)

Relationships

Depends On:

Depended On By:

  • Supply - Enforces access to Spaces/Units
  • Availability - Controls calendar modifications
  • Pricing - Restricts pricing rule changes
  • Bookings - Manages booking operations
  • Payments - Controls financial operations
  • Content - Manages media and description access
  • Channels - Controls channel publishing rights
  • ALL other domains - Every domain consumes permission checks

Related Domains:


Core Entities

Entity: Role

Purpose: Named bundle of permissions representing a functional role within an organization (e.g., admin, owner, manager, viewer).

Key Attributes:

  • id (UUID, primary key)
  • name (VARCHAR, unique, required) - Internal role identifier
  • display_name (VARCHAR) - User-friendly name shown in UI
  • description (TEXT) - Role purpose and capabilities
  • is_system (BOOLEAN, default true) - System-defined vs. custom role
  • org_id (UUID, nullable, foreign key → organizations.id) - NULL for global roles
  • created_at, updated_at (timestamps)

Relationships:

  • Role → RolePermission (1:*) - Maps to permissions
  • Role → Membership (1:*) - Assigned to users via memberships
  • Role → Org (*, many-to-one, optional) - Org-specific custom roles

Lifecycle:

  • Created: Seeded on platform initialization; custom roles created by admins
  • Updated: Permission mappings changed (audit logged)
  • Deleted: Soft delete only (retain for audit); system roles never deleted

Business Rules:

  • System roles (is_system=true) cannot be deleted
  • Role names must be unique within scope (global or per-org)
  • At least one admin role must exist in every Org
  • Role changes trigger session rotation for affected users

Entity: Permission

Purpose: Atomic action registry defining all possible operations in the system (e.g., space.read, booking.manage).

Key Attributes:

  • id (UUID, primary key)
  • resource (VARCHAR, required) - Resource type (e.g., 'space', 'booking', 'pricing')
  • action (VARCHAR, required) - Operation verb (e.g., 'read', 'create', 'update', 'delete', 'manage')
  • description (TEXT) - Human-readable explanation
  • created_at (timestamp)

Relationships:

  • Permission → RolePermission (1:*) - Links to roles

Lifecycle:

  • Created: Seeded on platform initialization; extended for new features
  • Updated: Description changes only
  • Deleted: Never deleted (versioned deprecation)

Business Rules:

  • (resource, action) pair must be unique
  • Permission names follow pattern: {resource}.{action}
  • Wildcard permissions supported: *.read, space.*, *.*
  • Permissions are version-controlled and centrally maintained

Entity: RolePermission

Purpose: Junction table linking Roles to Permissions with allow/deny semantics.

Key Attributes:

  • role_id (UUID, foreign key → roles.id, required)
  • permission_id (UUID, foreign key → permissions.id, required)
  • effect (ENUM: 'allow' | 'deny', required) - Access effect
  • created_at (timestamp)

Primary Key: (role_id, permission_id)

Relationships:

  • RolePermission → Role (*, many-to-one)
  • RolePermission → Permission (*, many-to-one)

Lifecycle:

  • Created: When building role permission sets
  • Updated: Effect can be changed from allow to deny or vice versa
  • Deleted: Cascade delete when role is removed

Business Rules:

  • Deny always overrides allow (deny-wins semantics)
  • Same permission can be both allowed and denied (explicit deny takes precedence)
  • Permission checks evaluate: (1) find all denies → reject if any; (2) find all allows → accept if any; (3) default deny

Entity: Session

Purpose: Server-side session record tracking authenticated user sessions with security metadata.

Key Attributes:

  • id (UUID, primary key) - Session identifier (returned to client as JWT)
  • user_id (UUID, foreign key → users.id, required)
  • token_hash (VARCHAR, unique) - SHA-256 hash of session token for validation
  • issued_at (TIMESTAMP, default NOW()) - Session creation time
  • expires_at (TIMESTAMP, required) - Absolute expiration (24 hours from issued_at)
  • last_activity_at (TIMESTAMP) - Last request timestamp (for idle timeout)
  • revoked_at (TIMESTAMP, nullable) - Session revocation time
  • user_agent (TEXT) - Browser/client user agent string
  • ip_address (INET) - Client IP address at session creation
  • created_at (timestamp)

Relationships:

  • Session → User (*, many-to-one)

Lifecycle:

  • Created: On successful authentication (Google SSO callback)
  • Updated: last_activity_at refreshed on each request
  • Revoked: Set revoked_at on logout, role change, or security event
  • Expired: Automatically invalid when expires_at < NOW() or revoked_at IS NOT NULL

Business Rules:

  • Sessions expire 24 hours after creation (absolute timeout)
  • Active sessions limited to 10 per user (oldest revoked automatically)
  • Session tokens stored client-side as HttpOnly, Secure, SameSite cookies
  • User agent changes trigger security warning (potential hijacking)
  • Sessions revoked on: logout, role change, password change (future), privilege escalation

Entity: PolicyRule (Future ABAC)

Purpose: Conditional access rules for attribute-based access control (ABAC), enabling context-aware permissions.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id, required)
  • name (VARCHAR, required)
  • description (TEXT)
  • subject (JSONB, required) - Who: {role, user_id, account_id}
  • resource (JSONB, required) - What: {type, id, attributes}
  • action (VARCHAR, required) - Which operation
  • condition (JSONB) - When/Where: {channel, region, date_range, time_of_day}
  • effect (ENUM: 'allow' | 'deny', required)
  • priority (INT, default 100) - Evaluation order (higher = evaluated first)
  • is_active (BOOLEAN, default false) - Disabled for MVP
  • created_at, updated_at (timestamps)

Relationships:

  • PolicyRule → Org (*, many-to-one)

Lifecycle:

  • Created: Schema ready but rules not created in MVP
  • Updated: Condition or effect changes
  • Deleted: Soft delete via archival

Business Rules:

  • is_active=false by default (not evaluated in MVP)
  • Future activation requires policy evaluation engine
  • Policies evaluated after RBAC (can override role permissions)
  • Higher priority policies evaluated first
  • Conditions support: channel, region, time, IP range, resource attributes

Workflows

Workflow: Google SSO Authentication

  1. User initiates login → Frontend redirects to /auth/login
  2. Generate PKCE parameters:
    • code_verifier = random 43-128 character string (base64url)
    • code_challenge = SHA256(code_verifier) encoded as base64url
    • state = random 32-byte hex string
  3. Store PKCE state in Redis with 5-minute TTL: auth:state:{state}{code_verifier, redirect_uri}
  4. Redirect to Google with authorization URL:
    • client_id, redirect_uri, response_type=code
    • scope=openid email profile
    • state, code_challenge, code_challenge_method=S256
  5. User authenticates with Google and approves consent
  6. Google redirects to callback URL with code and state
  7. Validate state parameter → fetch from Redis, verify match, delete key
  8. Exchange authorization code for tokens:
    • POST to Google token endpoint with code, client_id, client_secret, redirect_uri, code_verifier
    • Receive id_token, access_token, refresh_token
  9. Verify ID token (JWT signature, issuer, audience, expiration)
  10. Extract user claims from ID token: sub, email, name, picture
  11. Upsert user record in database:
    • Find by email or create new
    • Update google_sub, display_name, avatar_url, last_login_at
  12. Create session record:
    • Generate session UUID
    • Store user_id, expires_at (NOW + 24 hours), user_agent, ip_address
  13. Generate JWT token with payload: {user_id, session_id, email}, signed with JWT_SECRET, expires 24h
  14. Cache session in Redis: session:{session_id}{user_id, email, permissions}, TTL 24 hours
  15. Set secure cookie with session_id: HttpOnly, Secure, SameSite=Lax, maxAge=86400000
  16. Return success with user profile and organization memberships
  17. Redirect to dashboard

Postconditions:

  • User authenticated with valid session
  • JWT token returned to client
  • Session cached for fast validation
  • User can access authorized resources

Workflow: Permission Check

  1. Extract JWT from Authorization header or session cookie
  2. Validate JWT signature and expiration
  3. Extract session_id from JWT payload
  4. Check session cache in Redis: session:{session_id}
  5. If cache miss:
    • Query sessions table for id, user_id, expires_at, revoked_at
    • Reject if expired (expires_at < NOW()) or revoked (revoked_at IS NOT NULL)
    • Load user memberships for current Org
    • Query role permissions for user's role
    • Build permission set and cache in Redis (5-minute TTL)
  6. Extract org_id and account_id from request context (header or session)
  7. Verify membership scope:
    • If membership has account_id=NULL → Org-wide access (all accounts)
    • If membership has account_id={value} → Verify request targets that account
  8. Check requested permission: {resource}.{action}
  9. Evaluate deny rules first:
    • Find any role permissions with effect='deny' matching (resource, action)
    • If found → DENY access, log denial, return 403
  10. Evaluate allow rules:
    • Find any role permissions with effect='allow' matching (resource, action)
    • Support wildcards: *.action, resource.*, *.*
    • If found → ALLOW access, proceed
  11. Default deny: If no allow found → DENY access, log denial, return 403

Postconditions:

  • Request either proceeds (allowed) or rejected (denied)
  • All denials logged to audit trail
  • Permission cache warmed for subsequent requests

Workflow: Session Rotation on Role Change

  1. Admin updates user role in Membership record
  2. Identify affected useruser_id, org_id
  3. Invalidate permission cache: Delete Redis key permissions:{user_id}:{org_id}
  4. Find all active sessions for user:
    • Query sessions: user_id = ? AND expires_at > NOW() AND revoked_at IS NULL
  5. For each active session:
    • Set revoked_at = NOW()
    • Delete Redis cache: session:{session_id}
  6. Create new session for user:
    • Generate new session UUID
    • Copy user_agent, ip_address from old session
    • Set new expires_at
  7. Log rotation event to audit trail:
    • Event type: session.rotated
    • Metadata: {reason: 'role_change', old_role_id, new_role_id, old_session_ids, new_session_id}
  8. Notify user (future): Push notification or email about role change
  9. Return new JWT token to client

Postconditions:

  • Old sessions invalidated immediately
  • New session created with updated permissions
  • User must re-authenticate or use new token
  • Role change audit trail complete

Business Rules

  1. Deny-Wins Semantics: Any explicit deny permission overrides all allow permissions
  2. Default Deny: If no explicit allow found, access is denied
  3. Org Isolation: Permission checks must validate org_id matches user's active membership
  4. Account Scoping: Account-level memberships restrict access to that account's resources only
  5. Session Expiration: Sessions expire 24 hours after creation (absolute timeout)
  6. Session Revocation: Sessions must be revoked on: logout, role change, privilege escalation, security events
  7. Permission Caching: Permission sets cached 5 minutes; invalidated on role/membership changes
  8. Wildcard Permissions: Supported patterns: *.action, resource.*, *.* (admin only)
  9. System Roles: Core roles (admin, ops, owner_admin, manager, viewer) cannot be deleted
  10. Audit Requirement: All access denials and role changes must be logged to audit trail

Version Evolution

MVP.0: Core RBAC with Google SSO

Scope:

  • 4 system roles: admin, owner, channel_publisher, content_manager, viewer
  • Core permissions: 30+ covering account, space, unit, media, availability, pricing, booking, payment, settings, channel operations
  • Google OAuth 2.0 with OIDC + PKCE
  • Server-side session management (PostgreSQL + Redis)
  • JWT token-based authentication
  • Role-permission mapping with allow/deny
  • Org-wide vs. Account-level membership scoping
  • Permission caching (Redis, 5-minute TTL)
  • Session cookies (HttpOnly, Secure, SameSite=Lax)
  • Basic audit logging of denials and role changes

Included Permissions:

  • account.* - Account management
  • space.{read,create,update,delete} - Space operations
  • unit.{read,create,update,delete} - Unit operations
  • media.{read,write,delete} - Media asset management
  • availability.{read,create,update,delete} - Calendar operations
  • pricing.{read,create,update,delete} - Pricing rule management
  • booking.{read,create,update,delete,manage} - Booking operations
  • payment.{read,create,update,delete} - Payment operations
  • financials.read - Financial reporting
  • users.{read,create,update,delete} - Team management
  • settings.{read,update} - Organization settings
  • channel.{read,manage} - Channel publishing

Role Assignments:

  • admin: All permissions (*.*)
  • owner: Space, unit, pricing, booking, payment management; financial read; settings
  • channel_publisher: Space/unit read, channel management, booking read
  • content_manager: Space/unit/media write, availability management
  • viewer: Read-only access to all resources

Deferred:

  • ABAC policy rules (schema present, is_active=false)
  • Idle session timeout (future: 30-minute inactivity)
  • Multi-factor authentication (MFA)
  • Session device management UI
  • Refresh token support (sessions require re-authentication)
  • Property-level permission scoping (currently org/account level only)

V1: Enhanced Session Management

Additions:

  • Idle timeout (30 minutes of inactivity)
  • Session device management (view active sessions, revoke remotely)
  • Refresh token support (60-day lifetime, rotated on use)
  • Enhanced user agent tracking and anomaly detection
  • IP-based session validation with geolocation alerts
  • Session activity feed (last 10 actions per session)

Changes:

  • sessions table adds last_activity_at, device_name, location
  • Session rotation triggers on IP change beyond threshold
  • Grace period for mobile users switching networks

V2: Multi-Provider Authentication

Additions:

  • Microsoft OIDC integration
  • GitHub OAuth integration
  • Email/password authentication with email verification
  • Provider configuration table (auth_providers)
  • Multi-provider account linking
  • Provider-specific token storage (encrypted refresh tokens)

Changes:

  • users table adds email_verified, email_verified_at
  • New table: user_identities (provider-specific profiles)
  • New table: email_verification_tokens
  • Authentication flow branches by provider type

V3: Attribute-Based Access Control (ABAC)

Additions:

  • PolicyRule evaluation engine
  • Context-aware permissions (channel, region, time, IP range)
  • Policy management UI (create, test, activate policies)
  • Policy priority and conflict resolution
  • Condition DSL for complex rules

Changes:

  • policy_rules.is_active defaults to true for new policies
  • Permission evaluation order: RBAC → ABAC overrides
  • Policy cache (Redis) with 5-minute TTL
  • Audit logging for policy evaluation results

Example Policy:

{
"name": "Restrict booking creation to business hours",
"subject": {"role": "manager"},
"resource": {"type": "booking"},
"action": "create",
"condition": {
"time_range": {"start": "09:00", "end": "17:00", "timezone": "America/New_York"}
},
"effect": "deny",
"priority": 200
}

Implementation Notes

MVP.0 Scope

Authentication:

  • Google OAuth 2.0 only
  • PKCE flow for authorization code security
  • State parameter validation (5-minute TTL in Redis)
  • ID token verification (signature, claims, expiration)

Session Management:

  • 24-hour absolute timeout (no idle timeout in MVP)
  • Server-side storage (PostgreSQL primary, Redis cache)
  • Session limit: 10 active sessions per user
  • Cookie-based session ID transmission

Permission Evaluation:

  • In-memory permission check after cache load
  • Deny-wins semantics
  • Wildcard support for admin role
  • Cache invalidation on role/membership changes

Performance:

  • Redis cache hit rate target: >95%
  • Permission check latency: <10ms (cached), <50ms (cache miss)
  • Session validation: <5ms (Redis), <20ms (PostgreSQL fallback)

Database Indexes

Critical for performance:

-- Roles
CREATE UNIQUE INDEX idx_roles_name ON roles(name);
CREATE INDEX idx_roles_org ON roles(org_id) WHERE org_id IS NOT NULL;

-- Permissions
CREATE UNIQUE INDEX idx_permissions_resource_action ON permissions(resource, action);
CREATE INDEX idx_permissions_resource ON permissions(resource);

-- RolePermissions
CREATE INDEX idx_role_permissions_role ON role_permissions(role_id);
CREATE INDEX idx_role_permissions_permission ON role_permissions(permission_id);

-- Sessions
CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_expires ON sessions(expires_at) WHERE revoked_at IS NULL;
CREATE INDEX idx_sessions_token_hash ON sessions(token_hash);

-- PolicyRules (future)
CREATE INDEX idx_policy_rules_org ON policy_rules(org_id);
CREATE INDEX idx_policy_rules_active ON policy_rules(is_active, priority) WHERE is_active = true;

Constraints

Data integrity:

-- Roles
ALTER TABLE roles ADD CONSTRAINT roles_name_unique UNIQUE (name);
ALTER TABLE roles ADD CONSTRAINT roles_org_name_unique UNIQUE (org_id, name) WHERE org_id IS NOT NULL;

-- Permissions
ALTER TABLE permissions ADD CONSTRAINT permissions_resource_action_unique UNIQUE (resource, action);

-- RolePermissions
ALTER TABLE role_permissions ADD CONSTRAINT role_permissions_effect_check CHECK (effect IN ('allow', 'deny'));

-- Sessions
ALTER TABLE sessions ADD CONSTRAINT sessions_expires_valid CHECK (expires_at > issued_at);

Security Considerations

JWT Tokens:

  • Signed with HS256 using secret key (minimum 256 bits)
  • Short-lived: 24 hours
  • Payload: {user_id, session_id, email, exp, iat}
  • Stored in HttpOnly cookie (prevents XSS)

PKCE Flow:

  • code_verifier: 43-128 random characters
  • code_challenge: SHA256(code_verifier) base64url-encoded
  • State parameter: 32-byte random hex
  • All temporary state stored in Redis with 5-minute TTL

Session Security:

  • Token hash stored (SHA-256), not plaintext
  • User agent binding (detect hijacking)
  • IP address logging (audit trail)
  • Automatic revocation on suspicious activity

Physical Schema

See 001_initial_schema.sql for complete CREATE TABLE statements.

Summary:

  • 4 tables: roles, permissions, role_permissions, sessions
  • 1 future table: policy_rules (schema present, not enforced)
  • 10+ indexes for query performance
  • 8+ constraints for data integrity
  • Redis cache keys: session:{uuid}, permissions:{user_id}:{org_id}, auth:state:{state}

Operational Notes

Monitoring:

  • Track permission cache hit rate (target >95%)
  • Monitor session creation rate and concurrent sessions
  • Alert on high denial rates (potential attack or misconfiguration)
  • Track session revocation reasons (manual vs. automatic)

Cache Management:

  • Session cache: 24-hour TTL, matches session expiration
  • Permission cache: 5-minute TTL, invalidate on role changes
  • PKCE state: 5-minute TTL, one-time use
  • Redis memory limit: size sessions + permissions cache appropriately

Cleanup Jobs:

  • Daily: Delete expired sessions older than 7 days
  • Weekly: Archive revoked sessions to cold storage
  • Monthly: Rotate JWT signing keys (with grace period)

Audit Requirements:

  • Log all access denials with user, resource, action, timestamp
  • Log all role changes with old/new roles, changed by, reason
  • Log all session revocations with reason and metadata
  • Retain audit logs for 7 years (compliance)