⚠️ Porta is in beta — APIs and features may change before v1.0
Skip to content

Data Model

Last Updated: 2026-04-25

Overview

Porta's data model is defined across 19 PostgreSQL migrations in migrations/. The schema implements multi-tenant isolation at the database level through foreign key relationships to the organizations table. All tables use UUIDs as primary keys and include created_at/updated_at timestamps.

Entity Relationship Diagram

Core Entities

Organizations

The root tenant entity. Every user, client, and data record is scoped to an organization.

ColumnTypeDescription
idUUIDPrimary key
nameVARCHAR(255)Display name
slugCITEXTURL-safe identifier, unique, used in OIDC issuer path
statusVARCHAR(20)active, suspended, archived
is_super_adminBOOLEANOnly one org can be super-admin (partial unique index)
default_localeVARCHAR(10)Default locale for auth UI
two_factor_policyVARCHAR(20)disabled, optional, required
default_login_methodsTEXT[]{password,magic_link} — NOT NULL with DB default
branding_*VariousLogo URL, favicon URL, primary color, company name, custom CSS
created_at / updated_atTIMESTAMPTZAuto-managed timestamps

Key constraint: Partial unique index on is_super_admin WHERE is_super_admin = TRUE — ensures exactly one super-admin organization.

Applications

SaaS product definitions. Applications group roles, permissions, and claim definitions.

ColumnTypeDescription
idUUIDPrimary key
nameVARCHAR(255)Display name
slugCITEXTUnique identifier
descriptionTEXTOptional description
statusVARCHAR(20)active, inactive, archived

Application Modules (application_modules): Logical groupings within an application. Composite unique key (application_id, slug).

Clients

OIDC client registrations, scoped to an organization and optionally to an application.

ColumnTypeDescription
idUUIDPrimary key
client_idVARCHAR(64)OIDC client identifier, unique
organization_idUUIDFK → organizations
application_idUUIDFK → applications (nullable)
nameVARCHAR(255)Display name
statusVARCHAR(20)active, suspended, revoked
grant_typesTEXT[]Allowed OIDC grant types
response_typesTEXT[]Allowed response types
redirect_urisTEXT[]Registered redirect URIs
post_logout_redirect_urisTEXT[]Post-logout redirect URIs
token_endpoint_auth_methodVARCHAR(50)client_secret_post, none, etc.
login_methodsTEXT[]Per-client login method override (NULL = inherit from org)
id_token_signed_response_algVARCHAR(10)Default ES256
scopeTEXTAllowed scopes
require_pkceBOOLEANPKCE enforcement

Client Secrets

Hashed client secrets with lifecycle management.

ColumnTypeDescription
idUUIDPrimary key
client_idUUIDFK → clients
secret_hashTEXTArgon2id hash of the secret
secret_prefixVARCHAR(8)First 8 chars for identification
secret_sha256VARCHAR(64)SHA-256 pre-hash for client_secret_post
labelVARCHAR(255)Human-readable label
statusVARCHAR(20)active, revoked
expires_atTIMESTAMPTZOptional expiry
last_used_atTIMESTAMPTZUsage tracking

Users

User accounts, scoped to an organization.

ColumnTypeDescription
idUUIDPrimary key
organization_idUUIDFK → organizations
emailCITEXTUnique within organization (composite unique index)
email_verifiedBOOLEANEmail verification status
password_hashTEXTArgon2id hash (nullable for passwordless users)
nameVARCHAR(255)Display name
given_name / family_nameVARCHAR(255)Name components
statusVARCHAR(20)active, inactive, suspended, locked, archived, invited
failed_login_countINTEGERBrute-force tracking
last_login_atTIMESTAMPTZLogin tracking
localeVARCHAR(10)User's preferred locale
metadataJSONBExtensible metadata

Key constraint: Composite unique index on (organization_id, email) — ensures email uniqueness per tenant.

Status lifecycle: invitedactivesuspendedactive, activelockedactive, active|suspendedarchivedactive, activeinactiveactive.

RBAC Entities

Roles

Application-scoped role definitions.

ColumnTypeDescription
idUUIDPrimary key
application_idUUIDFK → applications
nameVARCHAR(255)Display name
slugCITEXTUnique within application
descriptionTEXTOptional description
statusVARCHAR(20)active, archived
is_systemBOOLEANSystem roles cannot be deleted

Permissions

Application-scoped permission definitions.

ColumnTypeDescription
idUUIDPrimary key
application_idUUIDFK → applications
nameVARCHAR(255)Display name
slugCITEXTUnique within application
descriptionTEXTOptional description
statusVARCHAR(20)active, archived

Role-Permission Mappings

Many-to-many relationship between roles and permissions.

ColumnTypeDescription
role_idUUIDFK → roles
permission_idUUIDFK → permissions

Composite primary key (role_id, permission_id).

User-Role Assignments

Many-to-many relationship between users and roles, scoped to an organization.

ColumnTypeDescription
user_idUUIDFK → users
role_idUUIDFK → roles
organization_idUUIDFK → organizations

Composite primary key (user_id, role_id, organization_id).

Custom Claims

Claim Definitions

Application-scoped claim type definitions.

ColumnTypeDescription
idUUIDPrimary key
application_idUUIDFK → applications
nameVARCHAR(255)Claim name (unique per app)
slugCITEXTURL-safe identifier
descriptionTEXTOptional description
value_typeVARCHAR(20)string, number, boolean, json
validation_rulesJSONBOptional validation constraints
statusVARCHAR(20)active, archived

User Claim Values

Per-user claim values, referencing a claim definition.

ColumnTypeDescription
idUUIDPrimary key
user_idUUIDFK → users
claim_definition_idUUIDFK → claim_definitions
valueTEXTStored value (validated against definition rules)

Composite unique index (user_id, claim_definition_id).

Two-Factor Authentication

Two-Factor Settings

Per-user 2FA configuration, scoped to an organization.

ColumnTypeDescription
idUUIDPrimary key
user_idUUIDFK → users
organization_idUUIDFK → organizations
methodVARCHAR(20)totp, email
is_enabledBOOLEANWhether 2FA is active
totp_secretTEXTAES-256-GCM encrypted TOTP secret
totp_verifiedBOOLEANWhether TOTP setup is confirmed
recovery_codesTEXT[]Argon2id-hashed recovery codes
email_otp_last_sent_atTIMESTAMPTZRate limiting for email OTP

Composite unique index (user_id, organization_id).

Authentication & Token Tables

Auth Tokens

Secure tokens for magic links, password resets, and invitations.

ColumnTypeDescription
idUUIDPrimary key
token_hashVARCHAR(128)SHA-256 hash of the token
typeVARCHAR(30)magic_link, password_reset, invitation
user_idUUIDFK → users
organization_idUUIDFK → organizations
expires_atTIMESTAMPTZToken expiry
used_atTIMESTAMPTZSingle-use enforcement
metadataJSONBAdditional context (e.g., invitation details)

OIDC Adapter Tables

oidc_payloads (PostgreSQL — long-lived)

Stores AccessToken, RefreshToken, and Grant data in PostgreSQL for durability.

ColumnTypeDescription
idVARCHAR(255)Composite: model_name:uid
model_nameVARCHAR(50)AccessToken, RefreshToken, Grant
payloadJSONBFull OIDC artifact data
uidVARCHAR(255)Unique identifier
grant_idVARCHAR(255)Associated grant (indexed for cascade deletion)
user_codeVARCHAR(255)Device flow user code
expires_atTIMESTAMPTZArtifact expiry
consumed_atTIMESTAMPTZRotation tracking

Short-lived artifacts (Session, Interaction, AuthorizationCode, ReplayDetection, ClientCredentials, PushedAuthorizationRequest) are stored in Redis for performance.

Infrastructure Tables

System Config

Key-value configuration store with 60-second in-memory cache.

ColumnTypeDescription
keyVARCHAR(255)Config key (primary key)
valueJSONBConfig value
descriptionTEXTHuman-readable description

Signing Keys

ES256 (ECDSA P-256) signing key pairs for JWT tokens.

ColumnTypeDescription
idUUIDPrimary key
kidVARCHAR(255)Key ID (for JWKS)
public_keyTEXTPEM-encoded public key
private_keyTEXTPEM-encoded private key (encrypted at rest)
statusVARCHAR(20)active, rotated, revoked
created_atTIMESTAMPTZKey creation time

Audit Log

Immutable audit trail for all administrative actions.

ColumnTypeDescription
idUUIDPrimary key
actionVARCHAR(100)Action identifier (e.g., organization.created)
actor_idUUIDWho performed the action
actor_typeVARCHAR(50)user, system, cli
resource_typeVARCHAR(50)Entity type
resource_idUUIDEntity ID
organization_idUUIDTenant context
detailsJSONBAction-specific details
created_atTIMESTAMPTZEvent timestamp

Includes an automated retention policy (migration 017) with a cleanup function triggered by a cron-like mechanism.

Branding Assets (Migration 018)

Binary storage for organization logos and favicons.

ColumnTypeDescription
idUUIDPrimary key
organization_idUUIDFK → organizations
asset_typeVARCHAR(20)logo or favicon
dataBYTEABinary image data (max 512 KB)
mime_typeVARCHAR(100)Image MIME type
created_at / updated_atTIMESTAMPTZAuto-managed timestamps

Key constraint: Unique index on (organization_id, asset_type) — one logo and one favicon per organization.

Admin Sessions (Migration 018)

OIDC session tracking for the admin session viewer and revocation UI.

ColumnTypeDescription
idUUIDPrimary key
session_uidVARCHAR(255)OIDC session unique identifier
user_idUUIDFK → users (nullable)
organization_idUUIDFK → organizations (nullable)
client_idVARCHAR(64)OIDC client identifier
ip_addressINETClient IP address
user_agentTEXTClient user-agent string
last_activity_atTIMESTAMPTZLast session activity
expires_atTIMESTAMPTZSession expiry time
created_atTIMESTAMPTZSession creation time

Mirrors Redis session data to PostgreSQL for admin viewing and revocation.

Invitation Details (Migration 019)

Adds invitation metadata to the auth_tokens table:

ColumnTypeDescription
detailsJSONBPre-assignment metadata: roles, claims, personalMessage, inviterName
invited_byUUIDFK → users — the admin who created the invitation

These columns are added to the existing auth_tokens table (not a new table).

Migration Strategy

Migrations are managed programmatically via src/lib/migrator.ts using node-pg-migrate:

  • Forward-only in production — Migrations run automatically on startup
  • CLI managementporta migrate up/down/status for manual control
  • Numbered sequencing001_ through 019_ prefix ensures deterministic order
  • Idempotent patternsIF NOT EXISTS used where possible

Released under the MIT License.