Files
matrix/cygnus-cloud-service/target/classes/db/identity/002_multitenant_licensing.sql
2026-07-26 19:52:28 +05:30

154 lines
6.4 KiB
PL/PgSQL

BEGIN;
-- DMLP phase 2: introduce the tenant, installation and license source of truth.
-- The fixed legacy tenant is a migration bridge for the identity data copied by
-- 001_identity_login_schema.sql. Replace its migration license through the
-- administration workflow before commercial enforcement is enabled.
CREATE TABLE IF NOT EXISTS identity.client_account (
tenant_id uuid PRIMARY KEY,
client_slug character varying(80) NOT NULL,
client_name character varying(200) NOT NULL,
status character varying(20) NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT uq_identity_client_account_slug UNIQUE (client_slug),
CONSTRAINT ck_identity_client_account_slug
CHECK (client_slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$'),
CONSTRAINT ck_identity_client_account_status
CHECK (status IN ('ACTIVE', 'SUSPENDED', 'CANCELLED'))
);
CREATE TABLE IF NOT EXISTS identity.client_installation (
installation_id uuid PRIMARY KEY,
tenant_id uuid NOT NULL,
client_id character varying(80) NOT NULL,
installation_code character varying(100) NOT NULL,
assertion_public_key text NOT NULL,
allowed_scopes text[] NOT NULL DEFAULT ARRAY[]::text[],
enabled boolean NOT NULL DEFAULT true,
security_version integer NOT NULL DEFAULT 1,
last_authenticated_at timestamp with time zone,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT fk_identity_installation_tenant
FOREIGN KEY (tenant_id)
REFERENCES identity.client_account (tenant_id),
CONSTRAINT uq_identity_installation_client_code
UNIQUE (client_id, installation_code),
CONSTRAINT ck_identity_installation_client_id
CHECK (client_id ~ '^[A-Za-z0-9][A-Za-z0-9_-]*$'),
CONSTRAINT ck_identity_installation_code
CHECK (installation_code ~ '^[A-Za-z0-9][A-Za-z0-9_-]*$'),
CONSTRAINT ck_identity_installation_security_version
CHECK (security_version > 0)
);
CREATE TABLE IF NOT EXISTS identity.client_license (
license_id uuid PRIMARY KEY,
tenant_id uuid NOT NULL,
license_type character varying(30) NOT NULL,
package_code character varying(50) NOT NULL,
valid_from timestamp with time zone NOT NULL,
valid_until timestamp with time zone NOT NULL,
status character varying(20) NOT NULL,
max_users integer,
max_installations integer,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT fk_identity_license_tenant
FOREIGN KEY (tenant_id)
REFERENCES identity.client_account (tenant_id),
CONSTRAINT ck_identity_license_period
CHECK (valid_until > valid_from),
CONSTRAINT ck_identity_license_status
CHECK (status IN ('ACTIVE', 'SUSPENDED', 'EXPIRED', 'CANCELLED')),
CONSTRAINT ck_identity_license_limits
CHECK ((max_users IS NULL OR max_users > 0)
AND (max_installations IS NULL OR max_installations > 0))
);
CREATE INDEX IF NOT EXISTS ix_identity_installation_tenant_enabled
ON identity.client_installation (tenant_id, enabled);
CREATE INDEX IF NOT EXISTS ix_identity_installation_lookup
ON identity.client_installation (client_id, installation_code, enabled);
CREATE INDEX IF NOT EXISTS ix_identity_license_tenant_period
ON identity.client_license (tenant_id, status, valid_from, valid_until);
-- Add tenant ownership without changing any legacy primary-key values.
ALTER TABLE identity.company
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.company_branch
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.user_group
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.app_user
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.permission
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.denied_pages
ADD COLUMN IF NOT EXISTS tenant_id uuid;
ALTER TABLE identity.user_loginhistory
ADD COLUMN IF NOT EXISTS tenant_id uuid;
-- Seed a stable bridge tenant for all identity data that already exists.
INSERT INTO identity.client_account
(tenant_id, client_slug, client_name, status)
VALUES ('00000000-0000-4000-8000-000000000001', 'matrix', 'Matrix', 'ACTIVE')
ON CONFLICT (tenant_id) DO UPDATE SET
client_slug = EXCLUDED.client_slug,
client_name = EXCLUDED.client_name,
updated_at = now();
-- A non-expiring migration bridge keeps current users operational. It must be
-- replaced by a commercial license before license administration goes live.
INSERT INTO identity.client_license
(license_id, tenant_id, license_type, package_code,
valid_from, valid_until, status)
VALUES ('00000000-0000-4000-8000-000000000002',
'00000000-0000-4000-8000-000000000001',
'MIGRATION', 'LEGACY_FULL',
'2020-01-01 00:00:00+00', '2099-12-31 23:59:59+00', 'ACTIVE')
ON CONFLICT (license_id) DO NOTHING;
UPDATE identity.company
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.company_branch
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.user_group
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.app_user
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.permission
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.denied_pages
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
UPDATE identity.user_loginhistory
SET tenant_id = '00000000-0000-4000-8000-000000000001'
WHERE tenant_id IS NULL;
CREATE INDEX IF NOT EXISTS ix_identity_company_tenant
ON identity.company (tenant_id, company_id);
CREATE INDEX IF NOT EXISTS ix_identity_branch_tenant
ON identity.company_branch (tenant_id, company_id, branch_id);
CREATE INDEX IF NOT EXISTS ix_identity_group_tenant
ON identity.user_group (tenant_id, group_id);
CREATE INDEX IF NOT EXISTS ix_identity_user_tenant_login
ON identity.app_user (tenant_id, upper(loginid));
CREATE INDEX IF NOT EXISTS ix_identity_permission_tenant_group_page
ON identity.permission (tenant_id, group_id, page_id)
WHERE permission <> '000';
CREATE INDEX IF NOT EXISTS ix_identity_denied_tenant_user_page
ON identity.denied_pages (tenant_id, user_id, page_id)
WHERE isdenied = 1;
CREATE INDEX IF NOT EXISTS ix_identity_login_history_tenant_user_time
ON identity.user_loginhistory (tenant_id, user_id, logintime DESC);
COMMIT;