Imagy Identity — Modelo de Datos
Base de Datos
- Nombre:
imagy_identity - Usuario:
imagy_identity_app(NOBYPASSRLS) - RLS: Habilitado en tablas de datos de tenant. Tablas de control plane sin RLS.
- Compartida: Ambos servicios (Gateway y Management) acceden a la misma BD
Diagrama ER
Tablas
tenants (Control Plane — sin RLS)
Tabla principal de tenants. Gestionada por platform admins y delegated admins.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador unico del tenant |
name | VARCHAR(200) | Nombre comercial del tenant |
slug | VARCHAR(50) | Slug unico para URLs (ej: fintech-abc) |
realm_name | VARCHAR(100) | Nombre del realm en Keycloak |
status | VARCHAR(20) | active, suspended, onboarding, deleted |
config | JSONB | Configuracion general (timezone, locale, currency, etc.) |
theme | JSONB | Branding (logo_url, primary_color, secondary_color, favicon_url, css_override) |
modules | JSONB | Modulos habilitados con configuracion por modulo |
custom_domain | VARCHAR(255) | Dominio custom del tenant (nullable) |
plan | VARCHAR(50) | Plan contratado (free, starter, professional, enterprise) |
max_users | INT | Limite de usuarios segun plan |
created_at | TIMESTAMPTZ | Fecha de creacion |
updated_at | TIMESTAMPTZ | Ultima modificacion |
suspended_at | TIMESTAMPTZ | Fecha de suspension (nullable) |
Constraints:
- UNIQUE(
slug) - UNIQUE(
realm_name) - UNIQUE(
custom_domain) WHERE custom_domain IS NOT NULL
Ejemplo de config:
json
{
"timezone": "America/Guayaquil",
"locale": "es-EC",
"currency": "USD",
"date_format": "DD/MM/YYYY",
"session_timeout_minutes": 30,
"mfa_required": false,
"allowed_origins": ["https://fintech-abc.reimaginetech.io"]
}Ejemplo de theme:
json
{
"logo_url": "https://cdn.reimaginetech.io/tenants/fintech-abc/logo.svg",
"favicon_url": "https://cdn.reimaginetech.io/tenants/fintech-abc/favicon.ico",
"primary_color": "#1E40AF",
"secondary_color": "#3B82F6",
"font_family": "Inter",
"css_override": null
}users (con RLS por tenant_id)
Usuarios registrados en la plataforma. Sincronizados con Keycloak.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador interno |
tenant_id | UUID FK | Tenant al que pertenece (RLS) |
keycloak_id | VARCHAR(255) | ID del usuario en Keycloak (sub claim) |
email | VARCHAR(255) | Email del usuario |
first_name | VARCHAR(100) | Nombre |
last_name | VARCHAR(100) | Apellido |
phone | VARCHAR(50) | Telefono (nullable) |
status | VARCHAR(20) | active, inactive, suspended, pending_verification |
roles | TEXT[] | Array de codigos de rol asignados |
organization_id | UUID FK | Organizacion dentro del tenant (nullable) |
last_login_at | TIMESTAMPTZ | Ultimo login registrado |
created_at | TIMESTAMPTZ | Fecha de creacion |
updated_at | TIMESTAMPTZ | Ultima modificacion |
Constraints:
- UNIQUE(
tenant_id,keycloak_id) - UNIQUE(
tenant_id,email)
organizations (con RLS por tenant_id)
Sub-agrupaciones dentro de un tenant. Mapeadas a Keycloak Organizations.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador interno |
tenant_id | UUID FK | Tenant propietario (RLS) |
keycloak_org_id | VARCHAR(255) | ID de la organizacion en Keycloak |
name | VARCHAR(200) | Nombre de la organizacion |
description | TEXT | Descripcion (nullable) |
is_active | BOOLEAN | Si esta activa |
created_at | TIMESTAMPTZ | Fecha de creacion |
updated_at | TIMESTAMPTZ | Ultima modificacion |
roles (con RLS por tenant_id)
Roles con permisos granulares. Pueden ser de sistema (no editables) o custom del tenant.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador interno |
tenant_id | UUID FK | Tenant propietario (RLS). NULL para roles de sistema |
code | VARCHAR(50) | Codigo unico del rol (ej: tenant_admin, operator, viewer) |
name | VARCHAR(100) | Nombre legible |
description | TEXT | Descripcion del rol |
permissions | TEXT[] | Array de permisos (ej: ["subject:read", "subject:lists:manage"]) |
is_system | BOOLEAN | Si es un rol de sistema (no editable por tenant) |
created_at | TIMESTAMPTZ | Fecha de creacion |
updated_at | TIMESTAMPTZ | Ultima modificacion |
Roles de sistema (is_system = true):
platform_admin— Acceso total a la plataformadelegated_admin— Administra tenants asignadostenant_admin— Administra su propio tenantoperator— Opera dentro del tenantviewer— Solo lecturaapplicant— Usuario final (creado por pipeline de credito)
modules (sin RLS — catalogo global)
Catalogo de modulos disponibles en la plataforma.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador |
code | VARCHAR(50) | Codigo unico (imagflow, imaglend, imagsign, imagid) |
name | VARCHAR(100) | Nombre del modulo |
description | TEXT | Descripcion |
is_active | BOOLEAN | Si esta disponible para activacion |
tenant_modules (con RLS por tenant_id)
Relacion tenant-modulo con configuracion especifica.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador |
tenant_id | UUID FK | Tenant (RLS) |
module_id | UUID FK | Modulo activado |
is_active | BOOLEAN | Si esta activo para este tenant |
config | JSONB | Configuracion especifica del modulo para este tenant |
activated_at | TIMESTAMPTZ | Fecha de activacion |
oidc_clients (con RLS por tenant_id)
Clientes OIDC/SAML configurados por tenant.
| Columna | Tipo | Descripcion |
|---|---|---|
id | UUID PK | Identificador |
tenant_id | UUID FK | Tenant propietario (RLS) |
client_id | VARCHAR(255) | Client ID en Keycloak |
client_name | VARCHAR(200) | Nombre descriptivo |
protocol | VARCHAR(20) | oidc, saml |
config | JSONB | Configuracion (redirect_uris, scopes, etc.) |
is_active | BOOLEAN | Si esta activo |
created_at | TIMESTAMPTZ | Fecha de creacion |
RLS Policies
sql
-- Users: aislamiento por tenant
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON users
FOR ALL USING (tenant_id = get_current_tenant_id());
-- Control plane (platform admin sin tenant context)
CREATE POLICY control_plane ON users
FOR ALL USING (
current_setting('app.current_tenant_id', true) IS NULL
OR current_setting('app.current_tenant_id', true) = ''
);
-- Misma politica para organizations, roles, tenant_modules, oidc_clients
-- tenants NO tiene RLS (es tabla de control plane)Indices
sql
-- Tenants
CREATE UNIQUE INDEX idx_tenants_slug ON tenants(slug);
CREATE UNIQUE INDEX idx_tenants_realm ON tenants(realm_name);
CREATE UNIQUE INDEX idx_tenants_domain ON tenants(custom_domain) WHERE custom_domain IS NOT NULL;
CREATE INDEX idx_tenants_status ON tenants(status);
-- Users
CREATE UNIQUE INDEX idx_users_tenant_keycloak ON users(tenant_id, keycloak_id);
CREATE UNIQUE INDEX idx_users_tenant_email ON users(tenant_id, email);
CREATE INDEX idx_users_tenant_status ON users(tenant_id, status);
CREATE INDEX idx_users_tenant_org ON users(tenant_id, organization_id);
CREATE INDEX idx_users_last_login ON users(tenant_id, last_login_at DESC);
-- Organizations
CREATE UNIQUE INDEX idx_orgs_tenant_keycloak ON organizations(tenant_id, keycloak_org_id);
CREATE INDEX idx_orgs_tenant_active ON organizations(tenant_id, is_active) WHERE is_active = true;
-- Roles
CREATE UNIQUE INDEX idx_roles_tenant_code ON roles(tenant_id, code);
CREATE INDEX idx_roles_system ON roles(is_system) WHERE is_system = true;
-- Tenant Modules
CREATE UNIQUE INDEX idx_tenant_modules_unique ON tenant_modules(tenant_id, module_id);Tablas de Soporte
sessions (Redis — no en PostgreSQL)
Las sesiones se almacenan en Redis/Valkey, no en la base de datos relacional:
Key: session:{session_id}
Value: {
"user_id": "uuid",
"tenant_id": "uuid",
"access_token": "encrypted:...",
"refresh_token": "encrypted:...",
"id_token": "encrypted:...",
"expires_at": 1716134400,
"created_at": 1716048000
}
TTL: 1800 (30 minutos, configurable por tenant)idempotency_keys
sql
CREATE TABLE idempotency_keys (
key VARCHAR(255) NOT NULL,
tenant_id UUID NOT NULL,
response_status INT NOT NULL,
response_body JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '24 hours',
PRIMARY KEY (key, tenant_id)
);