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

Database Schema

Porta uses PostgreSQL 16 as its primary data store. The schema is designed around multi-tenant isolation with organization-scoped data.

Entity Relationship Diagram

Tables

organizations

The tenant table. Every user, client, and data point traces back to an organization.

ColumnTypeDescription
iduuidPrimary key (generated)
nametextDisplay name
slugtextURL slug (unique)
statustextactive, suspended, or archived
is_super_adminbooleanWhether this is the super-admin org
logo_urltextBranding: logo URL
favicon_urltextBranding: favicon URL
primary_colortextBranding: primary color hex
company_nametextBranding: display name
custom_csstextBranding: CSS overrides
default_localetextDefault locale
default_login_methodstext[]Default login methods (NOT NULL)
created_attimestamptzCreation timestamp
updated_attimestamptzLast update (auto-trigger)

Constraints:

  • Partial unique index ensures at most one is_super_admin = true row

users

End-user accounts scoped to an organization.

ColumnTypeDescription
iduuidPrimary key
organization_iduuidFK → organizations
emailcitextEmail (unique per org, case-insensitive)
password_hashtextArgon2id hash
statustextactive, invited, suspended, locked, archived
given_nametextFirst name
family_nametextLast name
nicknametextNickname
picturetextProfile picture URL
phone_numbertextPhone number
phone_number_verifiedbooleanPhone verified
email_verifiedbooleanEmail verified
localetextUser locale
login_countintegerTotal login count
last_login_attimestamptzLast successful login
failed_login_countintegerFailed login attempts (resets on success). Used by account lockout.
last_failed_login_attimestamptzTimestamp of last failed login. Used for auto-unlock cooldown.
created_attimestamptzCreation timestamp
updated_attimestamptzLast update

Constraints:

  • Unique: (organization_id, email)

applications

SaaS product definitions that scope clients, roles, and claims.

ColumnTypeDescription
iduuidPrimary key
nametextApplication name
slugtextURL slug (unique)
descriptiontextDescription
statustextactive, inactive, archived
created_attimestamptzCreation timestamp
updated_attimestamptzLast update

application_modules

Logical groupings within an application.

ColumnTypeDescription
iduuidPrimary key
application_iduuidFK → applications
nametextModule name
slugtextModule slug
descriptiontextDescription
statustextactive or inactive

clients

OIDC clients (public or confidential).

ColumnTypeDescription
iduuidPrimary key
client_idtextOIDC client_id (unique)
organization_iduuidFK → organizations
application_iduuidFK → applications
client_nametextDisplay name
client_typetextconfidential or public
application_typetextweb, native, or spa
redirect_uristext[]Allowed redirect URIs
grant_typestext[]Allowed grant types
response_typestext[]Allowed response types
scopetextSpace-separated scopes
token_endpoint_auth_methodtextAuth method
cors_originstext[]CORS origins
require_pkcebooleanRequire PKCE
login_methodstext[]Override login methods (NULL = inherit)
statustextactive, inactive, revoked

client_secrets

Client secrets with Argon2id hashing and SHA-256 pre-hash.

ColumnTypeDescription
iduuidPrimary key
client_iduuidFK → clients
secret_hashtextArgon2id hash
sha256_hashtextSHA-256 hash for OIDC lookup
labeltextOptional label
statustextactive or revoked
created_attimestamptzCreation timestamp
revoked_attimestamptzRevocation timestamp

roles

RBAC roles scoped to an application.

ColumnTypeDescription
iduuidPrimary key
application_iduuidFK → applications
nametextRole name
slugtextRole slug
descriptiontextDescription
is_systembooleanSystem role (cannot be deleted)
statustextactive or archived

permissions

Permissions scoped to an application.

ColumnTypeDescription
iduuidPrimary key
application_iduuidFK → applications
nametextPermission name
slugtextPermission slug
descriptiontextDescription
statustextactive or archived

role_permissions

Maps permissions to roles (many-to-many).

ColumnTypeDescription
role_iduuidFK → roles
permission_iduuidFK → permissions

user_roles

Maps roles to users (many-to-many).

ColumnTypeDescription
user_iduuidFK → users
role_iduuidFK → roles

claim_definitions

Custom claim definitions scoped to an application.

ColumnTypeDescription
iduuidPrimary key
application_iduuidFK → applications
nametextClaim name
slugtextClaim slug
claim_typetextstring, number, boolean, json
validation_rulesjsonbType-specific validation rules
descriptiontextDescription
statustextactive or archived

user_claim_values

Custom claim values assigned to users.

ColumnTypeDescription
iduuidPrimary key
claim_definition_iduuidFK → claim_definitions
user_iduuidFK → users
valuejsonbThe claim value

system_config

System configuration key-value store with 60s in-memory cache.

ColumnTypeDescription
keytextConfiguration key (PK)
valuetextConfiguration value
descriptiontextDescription
updated_attimestamptzLast update

Notable configuration keys:

KeyDefaultDescription
access_token_ttl3600Access token TTL in seconds
refresh_token_ttl1209600Refresh token TTL in seconds (14 days)
account_lockout_threshold5Failed login attempts before auto-lock
account_lockout_cooldown_minutes15Minutes before auto-unlock
audit_retention_days365Days to retain audit log entries before cleanup

audit_log

Immutable audit trail of all administrative and security events.

ColumnTypeDescription
iduuidPrimary key
actiontextEvent type (e.g., organization.created)
entity_typetextEntity type
entity_iduuidEntity ID
actor_iduuidWho performed the action
actor_emailtextActor's email
organization_iduuidOrganization context
metadatajsonbAdditional event data
created_attimestamptzEvent timestamp

oidc_payloads

PostgreSQL storage for long-lived OIDC artifacts (access tokens, refresh tokens, grants).

ColumnTypeDescription
idtextArtifact ID
typetextModel type
payloadjsonbFull OIDC payload
grant_idtextAssociated grant ID
user_codetextDevice code (if applicable)
uidtextUnique identifier
expires_attimestamptzExpiration
consumed_attimestamptzConsumption timestamp

admin_sessions

PostgreSQL mirror of Redis OIDC sessions for admin listing, filtering, and revocation management.

ColumnTypeDescription
session_idvarchar(128)Primary key (OIDC session ID)
user_iduuidFK → users
client_iduuidFK → clients
organization_iduuidFK → organizations
grant_idvarchar(128)Associated grant ID
ip_addressinetClient IP address
user_agenttextClient user agent string
created_attimestamptzSession creation time
expires_attimestamptzSession expiry time
last_activity_attimestamptzLast activity timestamp
revoked_attimestamptzRevocation timestamp (NULL = active)

Indexes: Partial indexes on user_id, organization_id, client_id, and expires_at where revoked_at IS NULL for efficient active session queries.

branding_assets

Organization logo and favicon image storage.

ColumnTypeDescription
iduuidPrimary key
organization_iduuidFK → organizations (CASCADE)
asset_typevarchar(20)logo or favicon
content_typevarchar(50)MIME type (e.g., image/png)
databyteaBinary image data
file_sizeintegerFile size in bytes
created_attimestamptzCreation timestamp
updated_attimestamptzLast update

Constraints:

  • Unique: (organization_id, asset_type) — one logo and one favicon per org
  • Check: asset_type IN ('logo', 'favicon')
  • Check: file_size > 0 AND file_size <= 524288 (max 512 KB)

two_factor_*

Two-factor authentication tables for TOTP secrets (AES-256-GCM encrypted), email OTP codes, and recovery codes (Argon2id hashed).

Extensions

Porta uses two PostgreSQL extensions:

  • pgcrypto — UUID generation via gen_random_uuid()
  • citext — Case-insensitive text type for email addresses

Auto-Updated Timestamps

All tables with an updated_at column use a database trigger (trigger_set_updated_at) that automatically sets updated_at = NOW() on every UPDATE.

Released under the MIT License.