Core Docs

Account Model

Account Model



How user identity is structured in core.auth — the master account, identity tracking, and data boundaries.

The Master Account



Every user in the UNVRSL ecosystem has exactly one master account stored in core.auth. This is the single source of truth for identity. No app stores credentials, email addresses, or personal data — only a reference to the master account.

core.auth (identity provider)
┌─────────────────────────────────────┐
│ users (master) │
├─────────────────────────────────────┤
│ id (PK, UUID) │
│ email (unique, verified) │
│ password_hash (bcrypt/argon2) │
│ display_name │
│ phone (optional, verified) │
│ avatar_url │
│ status (active/suspended/deleted) │
│ 2fa_enabled (bool) │
│ 2fa_secret (encrypted, nullable) │
│ 2fa_type (totp/webauthn/null) │
│ recovery_codes (encrypted, JSON) │
│ created_at │
│ updated_at │
│ last_login_at │
│ account_type_id (FK → account_types)│
│ account_type_expires_at (nullable) │
│ -- Quotas enforced via account_types│
│ -- + user_quota_usage (see L2) │
└─────────────────────────────────────┘


Design Decisions



  • UUID primary keys — avoids sequential ID exposure, safe for public-facing APIs

  • Argon2id for password hashing — PHP's PASSWORD_ARGON2ID default. Minimum 10 characters. No breach database checking.

  • Encrypted 2FA secrets — stored encrypted at rest (AES-256), decrypted only during verification

  • Soft deletesdeleted_at timestamp instead of hard deletes. Preserves referential integrity for app data. User can be restored within grace period.

  • Account types — users are assigned an account_type_id referencing the account_types table. Four quota types: storage (MB), AI credits, hypermedia credits, and special general credits (overflow pool). Quotas are defined per type, not per user. Actual usage is tracked in user_quota_usage.

  • Credit-based AI quotas — AI usage is measured in credits, not calls. Input tokens cost 100 credits each, output tokens 250, cached tokens 5. This allows fine-grained cost control across different models and operations.

  • Special general credits — a universal overflow pool. When a user exhausts their storage, AI, or hypermedia quota, they can continue using the resource by consuming special credits. Conversion rates are defined per resource type. This lets users self-allocate based on their actual needs (e.g. heavy storage users can borrow from their credit pool).

  • account_type_expires_at — nullable. For temporary tier assignments (promotions, trials). When expired, user reverts to free tier.

Identity Data (Tracking)



Separate from the account itself, core.auth tracks identifying metadata for security purposes — device fingerprints, IP history, location data. This powers anomaly detection ("new device login" alerts) and session auditing.

core.auth SQL:
┌─────────────────────────────────────┐
│ ident_events │
├─────────────────────────────────────┤
│ id (PK) │
│ user_id (FK → users) │
│ event_type (login/refresh/revoke) │
│ ip_address │
│ user_agent │
│ geo_location (resolved from IP) │
│ device_fingerprint │
│ session_id (FK, nullable) │
│ created_at │
└─────────────────────────────────────┘


What Gets Tracked



EventLogged WhenPurpose








loginSuccessful authenticationSession creation audit
logoutUser logs outSession termination audit
refreshAccess token refreshedActivity tracking
refresh_replay_detectedRevoked refresh token reusedAttack detection
2fa_verify2FA challenge completedCritical action audit
password_changePassword modifiedSecurity audit
email_changeEmail modifiedSecurity audit

Retention



ident_events accumulate fast. A cleanup job prunes events older than a configurable retention period (default: 90 days). Recent events (last 30 days) are always available for the "recent activity" view in the account dashboard.

Data Boundaries



The separation between core.auth and apps is strict:


Data TypeStored InAccessed By






Email, password, 2FAcore.auth onlycore.auth internally
Profile (name, avatar)core.authApps via API (with user consent)
Ident events (IP, device)core.auth onlycore.auth internally
App-specific dataApp's own databaseApp only
Quotascore.authcore.services via API

Why This Matters



  • Breach isolation — if an app is compromised, no credentials leak. The app has no passwords to steal.

  • Single source of truth — user changes email once in core.auth, all apps see the change via API. No stale data across apps.

  • GDPR/data minimization — apps store only what they need for their functionality. Identity data lives in one place, deletable in one place.

  • App independence — an app can be rebuilt, replaced, or decommissioned without touching user identity. The linked_users table is trivially portable.




Next: App Integration — how apps register and link users.