@stratum-hq/lib 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crypto.d.ts +3 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +23 -5
- package/dist/crypto.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +61 -0
- package/dist/migrate.js.map +1 -0
- package/dist/migrations/001_init.sql +136 -0
- package/dist/migrations/002_schema_isolation.sql +6 -0
- package/dist/migrations/003_db_isolation.sql +12 -0
- package/dist/migrations/004_webhooks.sql +53 -0
- package/dist/migrations/005_audit_logs.sql +21 -0
- package/dist/migrations/006_api_key_scopes.sql +2 -0
- package/dist/migrations/007_sensitive_config.sql +1 -0
- package/dist/migrations/008_api_key_management.sql +7 -0
- package/dist/migrations/009_consent.sql +18 -0
- package/dist/migrations/010_multi_region.sql +19 -0
- package/dist/migrations/011_demo_bootstrap.sql +5 -0
- package/dist/migrations/012_roles.sql +18 -0
- package/dist/migrations/013_api_key_hmac.sql +4 -0
- package/dist/migrations/014_sort_order.sql +5 -0
- package/dist/migrations/015_region_url_encryption.sql +5 -0
- package/dist/migrations/016_migration_alias.sql +8 -0
- package/dist/migrations/migrations/001_init.sql +145 -0
- package/dist/migrations/migrations/002_schema_isolation.sql +6 -0
- package/dist/migrations/migrations/003_db_isolation.sql +12 -0
- package/dist/migrations/migrations/004_webhooks.sql +53 -0
- package/dist/migrations/migrations/005_audit_logs.sql +21 -0
- package/dist/migrations/migrations/006_api_key_scopes.sql +2 -0
- package/dist/migrations/migrations/007_sensitive_config.sql +1 -0
- package/dist/migrations/migrations/008_api_key_management.sql +7 -0
- package/dist/migrations/migrations/009_consent.sql +18 -0
- package/dist/migrations/migrations/010_multi_region.sql +19 -0
- package/dist/migrations/migrations/011_demo_bootstrap.sql +5 -0
- package/dist/migrations/migrations/012_roles.sql +18 -0
- package/dist/migrations/migrations/013_api_key_hmac.sql +4 -0
- package/dist/migrations/migrations/014_sort_order.sql +5 -0
- package/dist/migrations/migrations/015_region_url_encryption.sql +5 -0
- package/dist/migrations/migrations/016_migration_alias.sql +8 -0
- package/dist/migrations/migrations/017_abac_policies.sql +16 -0
- package/dist/services/__tests__/abac-service.test.d.ts +2 -0
- package/dist/services/__tests__/abac-service.test.d.ts.map +1 -0
- package/dist/services/__tests__/abac-service.test.js +108 -0
- package/dist/services/__tests__/abac-service.test.js.map +1 -0
- package/dist/services/__tests__/retention-service.test.js +7 -4
- package/dist/services/__tests__/retention-service.test.js.map +1 -1
- package/dist/services/abac-service.d.ts +41 -0
- package/dist/services/abac-service.d.ts.map +1 -0
- package/dist/services/abac-service.js +268 -0
- package/dist/services/abac-service.js.map +1 -0
- package/dist/services/config-service.d.ts.map +1 -1
- package/dist/services/config-service.js +8 -5
- package/dist/services/config-service.js.map +1 -1
- package/dist/services/event-service.d.ts.map +1 -1
- package/dist/services/event-service.js +1 -0
- package/dist/services/event-service.js.map +1 -1
- package/dist/services/key-rotation-service.d.ts +5 -5
- package/dist/services/key-rotation-service.d.ts.map +1 -1
- package/dist/services/key-rotation-service.js +42 -29
- package/dist/services/key-rotation-service.js.map +1 -1
- package/dist/services/retention-service.d.ts.map +1 -1
- package/dist/services/retention-service.js +3 -1
- package/dist/services/retention-service.js.map +1 -1
- package/dist/services/tenant-service.js +4 -4
- package/dist/services/tenant-service.js.map +1 -1
- package/dist/services/webhook-service.js +1 -1
- package/dist/stratum.d.ts +35 -1
- package/dist/stratum.d.ts.map +1 -1
- package/dist/stratum.js +170 -0
- package/dist/stratum.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
-- Extensions (require superuser or CREATE privilege; gracefully warn if unavailable)
|
|
2
|
+
DO $$ BEGIN
|
|
3
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
4
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
5
|
+
RAISE WARNING 'Cannot create uuid-ossp extension — insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";';
|
|
6
|
+
END $$;
|
|
7
|
+
|
|
8
|
+
DO $$ BEGIN
|
|
9
|
+
CREATE EXTENSION IF NOT EXISTS "ltree";
|
|
10
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
11
|
+
RAISE WARNING 'Cannot create ltree extension — insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "ltree";';
|
|
12
|
+
END $$;
|
|
13
|
+
|
|
14
|
+
-- Warn if application role has BYPASSRLS privilege.
|
|
15
|
+
-- In production, set stratum.enforce_rls = 'on' to make this a hard error.
|
|
16
|
+
DO $$ BEGIN
|
|
17
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = current_user AND rolbypassrls) THEN
|
|
18
|
+
IF current_setting('stratum.enforce_rls', true) = 'on' THEN
|
|
19
|
+
RAISE EXCEPTION 'SECURITY: Application role "%" has BYPASSRLS privilege. Create a dedicated role without BYPASSRLS, or set stratum.enforce_rls=off for development.', current_user;
|
|
20
|
+
ELSE
|
|
21
|
+
RAISE WARNING 'Role "%" has BYPASSRLS privilege. This is fine for development, but production should use a role without BYPASSRLS. Set stratum.enforce_rls=on to enforce.', current_user;
|
|
22
|
+
END IF;
|
|
23
|
+
END IF;
|
|
24
|
+
END $$;
|
|
25
|
+
|
|
26
|
+
-- Tenant nodes table
|
|
27
|
+
CREATE TABLE IF NOT EXISTS tenants (
|
|
28
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
29
|
+
parent_id UUID REFERENCES tenants(id) ON DELETE RESTRICT,
|
|
30
|
+
ancestry_path TEXT NOT NULL,
|
|
31
|
+
ancestry_ltree ltree,
|
|
32
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
slug TEXT NOT NULL UNIQUE
|
|
35
|
+
CHECK (slug ~ '^[a-z][a-z0-9_]{0,62}$'),
|
|
36
|
+
config JSONB NOT NULL DEFAULT '{}',
|
|
37
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
38
|
+
isolation_strategy TEXT NOT NULL DEFAULT 'SHARED_RLS'
|
|
39
|
+
CHECK (isolation_strategy IN ('SHARED_RLS')),
|
|
40
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
41
|
+
CHECK (status IN ('active', 'archived')),
|
|
42
|
+
deleted_at TIMESTAMPTZ,
|
|
43
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
44
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Auto-update updated_at function
|
|
48
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
49
|
+
RETURNS TRIGGER AS $$
|
|
50
|
+
BEGIN
|
|
51
|
+
NEW.updated_at = now();
|
|
52
|
+
RETURN NEW;
|
|
53
|
+
END;
|
|
54
|
+
$$ language 'plpgsql';
|
|
55
|
+
|
|
56
|
+
DROP TRIGGER IF EXISTS update_tenants_updated_at ON tenants;
|
|
57
|
+
CREATE TRIGGER update_tenants_updated_at
|
|
58
|
+
BEFORE UPDATE ON tenants
|
|
59
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
60
|
+
|
|
61
|
+
-- Trigger to maintain ancestry_ltree from parent's ltree + own slug
|
|
62
|
+
CREATE OR REPLACE FUNCTION maintain_ancestry_ltree()
|
|
63
|
+
RETURNS TRIGGER AS $$
|
|
64
|
+
BEGIN
|
|
65
|
+
IF NEW.parent_id IS NULL THEN
|
|
66
|
+
NEW.ancestry_ltree = NEW.slug::ltree;
|
|
67
|
+
ELSE
|
|
68
|
+
SELECT ancestry_ltree || NEW.slug::ltree INTO NEW.ancestry_ltree
|
|
69
|
+
FROM tenants WHERE id = NEW.parent_id;
|
|
70
|
+
END IF;
|
|
71
|
+
RETURN NEW;
|
|
72
|
+
END;
|
|
73
|
+
$$ language 'plpgsql';
|
|
74
|
+
|
|
75
|
+
DROP TRIGGER IF EXISTS maintain_tenant_ancestry_ltree ON tenants;
|
|
76
|
+
CREATE TRIGGER maintain_tenant_ancestry_ltree
|
|
77
|
+
BEFORE INSERT OR UPDATE OF parent_id, slug ON tenants
|
|
78
|
+
FOR EACH ROW EXECUTE FUNCTION maintain_ancestry_ltree();
|
|
79
|
+
|
|
80
|
+
-- Indexes for tree queries
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_tenant_parent ON tenants(parent_id);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_tenant_ancestry ON tenants USING GIST (ancestry_ltree);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_tenant_depth ON tenants(depth);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_tenant_slug ON tenants(slug);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_tenant_status ON tenants(status) WHERE status = 'active';
|
|
86
|
+
|
|
87
|
+
-- Permission policies table
|
|
88
|
+
CREATE TABLE IF NOT EXISTS permission_policies (
|
|
89
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
90
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE RESTRICT,
|
|
91
|
+
key TEXT NOT NULL,
|
|
92
|
+
value JSONB NOT NULL DEFAULT 'true',
|
|
93
|
+
mode TEXT NOT NULL DEFAULT 'INHERITED'
|
|
94
|
+
CHECK (mode IN ('LOCKED', 'INHERITED', 'DELEGATED')),
|
|
95
|
+
revocation_mode TEXT NOT NULL DEFAULT 'CASCADE'
|
|
96
|
+
CHECK (revocation_mode IN ('CASCADE', 'SOFT', 'PERMANENT')),
|
|
97
|
+
source_tenant_id UUID NOT NULL REFERENCES tenants(id),
|
|
98
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
99
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
100
|
+
UNIQUE(tenant_id, key)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_permission_source ON permission_policies(source_tenant_id);
|
|
104
|
+
|
|
105
|
+
DROP TRIGGER IF EXISTS update_permission_policies_updated_at ON permission_policies;
|
|
106
|
+
CREATE TRIGGER update_permission_policies_updated_at
|
|
107
|
+
BEFORE UPDATE ON permission_policies
|
|
108
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
109
|
+
|
|
110
|
+
-- Config entries table
|
|
111
|
+
CREATE TABLE IF NOT EXISTS config_entries (
|
|
112
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
113
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
114
|
+
key TEXT NOT NULL,
|
|
115
|
+
value JSONB NOT NULL,
|
|
116
|
+
inherited BOOLEAN NOT NULL DEFAULT false,
|
|
117
|
+
source_tenant_id UUID NOT NULL REFERENCES tenants(id),
|
|
118
|
+
locked BOOLEAN NOT NULL DEFAULT false,
|
|
119
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
120
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
121
|
+
UNIQUE(tenant_id, key)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
DROP TRIGGER IF EXISTS update_config_entries_updated_at ON config_entries;
|
|
125
|
+
CREATE TRIGGER update_config_entries_updated_at
|
|
126
|
+
BEFORE UPDATE ON config_entries
|
|
127
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
128
|
+
|
|
129
|
+
-- Config entries uses ON DELETE CASCADE (unlike permission_policies which uses RESTRICT)
|
|
130
|
+
-- because config values are safe to delete with a tenant — they carry no semantic guarantees
|
|
131
|
+
-- like PERMANENT revocation mode. If soft-delete is used (v1 default), CASCADE never fires.
|
|
132
|
+
|
|
133
|
+
-- API keys table (for control plane auth)
|
|
134
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
135
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
136
|
+
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
|
137
|
+
key_hash TEXT NOT NULL,
|
|
138
|
+
key_prefix VARCHAR(16),
|
|
139
|
+
name TEXT,
|
|
140
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
141
|
+
last_used_at TIMESTAMPTZ,
|
|
142
|
+
revoked_at TIMESTAMPTZ
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys (key_hash);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Migration 002: Allow SCHEMA_PER_TENANT in isolation_strategy check constraint
|
|
2
|
+
-- Extends the tenants table to support schema-per-tenant isolation (v1.1).
|
|
3
|
+
|
|
4
|
+
ALTER TABLE tenants DROP CONSTRAINT IF EXISTS tenants_isolation_strategy_check;
|
|
5
|
+
ALTER TABLE tenants ADD CONSTRAINT tenants_isolation_strategy_check
|
|
6
|
+
CHECK (isolation_strategy IN ('SHARED_RLS', 'SCHEMA_PER_TENANT'));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Migration 003: DB_PER_TENANT isolation support
|
|
2
|
+
-- Expands the isolation_strategy constraint and adds connection_config column.
|
|
3
|
+
|
|
4
|
+
-- Allow DB_PER_TENANT in addition to existing strategies.
|
|
5
|
+
-- Drop the old constraint first (name from 001_init.sql), then re-add with all three values.
|
|
6
|
+
ALTER TABLE tenants DROP CONSTRAINT IF EXISTS tenants_isolation_strategy_check;
|
|
7
|
+
ALTER TABLE tenants ADD CONSTRAINT tenants_isolation_strategy_check
|
|
8
|
+
CHECK (isolation_strategy IN ('SHARED_RLS', 'SCHEMA_PER_TENANT', 'DB_PER_TENANT'));
|
|
9
|
+
|
|
10
|
+
-- Optional JSONB column to store per-tenant database connection overrides
|
|
11
|
+
-- (e.g. custom host/port for fully isolated DB_PER_TENANT deployments).
|
|
12
|
+
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS connection_config JSONB DEFAULT NULL;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
-- Webhook registrations
|
|
2
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
3
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
4
|
+
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
|
5
|
+
url TEXT NOT NULL,
|
|
6
|
+
secret_hash TEXT NOT NULL,
|
|
7
|
+
events TEXT[] NOT NULL DEFAULT '{}',
|
|
8
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
9
|
+
description TEXT,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_webhooks_tenant ON webhooks(tenant_id);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(active) WHERE active = true;
|
|
16
|
+
|
|
17
|
+
DROP TRIGGER IF EXISTS update_webhooks_updated_at ON webhooks;
|
|
18
|
+
CREATE TRIGGER update_webhooks_updated_at
|
|
19
|
+
BEFORE UPDATE ON webhooks
|
|
20
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
21
|
+
|
|
22
|
+
-- Webhook events log
|
|
23
|
+
CREATE TABLE IF NOT EXISTS webhook_events (
|
|
24
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
25
|
+
type TEXT NOT NULL,
|
|
26
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
27
|
+
data JSONB NOT NULL DEFAULT '{}',
|
|
28
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_events_tenant ON webhook_events(tenant_id);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(type);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_events_created ON webhook_events(created_at);
|
|
34
|
+
|
|
35
|
+
-- Webhook delivery tracking
|
|
36
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
37
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
38
|
+
webhook_id UUID NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
39
|
+
event_id UUID NOT NULL REFERENCES webhook_events(id) ON DELETE CASCADE,
|
|
40
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
41
|
+
CHECK (status IN ('pending', 'success', 'failed')),
|
|
42
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
next_retry_at TIMESTAMPTZ,
|
|
44
|
+
last_error TEXT,
|
|
45
|
+
response_code INTEGER,
|
|
46
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
47
|
+
completed_at TIMESTAMPTZ
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_webhook ON webhook_deliveries(webhook_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_event ON webhook_deliveries(event_id);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_status ON webhook_deliveries(status) WHERE status = 'pending';
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_deliveries_retry ON webhook_deliveries(next_retry_at) WHERE status = 'pending';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
2
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
3
|
+
actor_id TEXT NOT NULL,
|
|
4
|
+
actor_type TEXT NOT NULL CHECK (actor_type IN ('api_key', 'jwt', 'system')),
|
|
5
|
+
action TEXT NOT NULL,
|
|
6
|
+
resource_type TEXT NOT NULL,
|
|
7
|
+
resource_id TEXT,
|
|
8
|
+
tenant_id UUID REFERENCES tenants(id) ON DELETE SET NULL,
|
|
9
|
+
source_ip INET,
|
|
10
|
+
request_id TEXT,
|
|
11
|
+
before_state JSONB,
|
|
12
|
+
after_state JSONB,
|
|
13
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
14
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_audit_tenant ON audit_logs(tenant_id);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_logs(actor_id);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_audit_resource ON audit_logs(resource_type, resource_id);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE config_entries ADD COLUMN IF NOT EXISTS sensitive BOOLEAN NOT NULL DEFAULT false;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Add expiration and rate limit columns to api_keys
|
|
2
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;
|
|
3
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS rate_limit_max INTEGER;
|
|
4
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS rate_limit_window TEXT;
|
|
5
|
+
|
|
6
|
+
CREATE INDEX idx_api_keys_expires ON api_keys(expires_at) WHERE expires_at IS NOT NULL;
|
|
7
|
+
CREATE INDEX idx_api_keys_last_used ON api_keys(last_used_at);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS consent_records (
|
|
2
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
4
|
+
subject_id TEXT NOT NULL,
|
|
5
|
+
purpose TEXT NOT NULL,
|
|
6
|
+
granted BOOLEAN NOT NULL DEFAULT true,
|
|
7
|
+
granted_at TIMESTAMPTZ DEFAULT now(),
|
|
8
|
+
revoked_at TIMESTAMPTZ,
|
|
9
|
+
expires_at TIMESTAMPTZ,
|
|
10
|
+
metadata JSONB DEFAULT '{}',
|
|
11
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
12
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
13
|
+
UNIQUE(tenant_id, subject_id, purpose)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_consent_tenant ON consent_records(tenant_id);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_consent_subject ON consent_records(subject_id);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_consent_purpose ON consent_records(purpose);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS regions (
|
|
2
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3
|
+
display_name TEXT NOT NULL,
|
|
4
|
+
slug TEXT NOT NULL UNIQUE,
|
|
5
|
+
control_plane_url TEXT,
|
|
6
|
+
database_url TEXT,
|
|
7
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
8
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'draining', 'inactive')),
|
|
9
|
+
metadata JSONB DEFAULT '{}',
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_regions_slug ON regions(slug);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_regions_status ON regions(status);
|
|
16
|
+
|
|
17
|
+
-- Add region_id to tenants (nullable for backward compat)
|
|
18
|
+
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS region_id UUID REFERENCES regions(id);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_tenants_region ON tenants(region_id);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Migration 011: Demo bootstrap
|
|
2
|
+
-- REMOVED: Demo API key seeding moved to packages/demo/api/src/seed.ts
|
|
3
|
+
-- The original migration inserted a hardcoded API key (sk_live_demo_key)
|
|
4
|
+
-- with global admin access, which is a security risk in production.
|
|
5
|
+
-- Demo data is now seeded via the demo seed script only.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- 012: Role-Based Access Control (RBAC)
|
|
2
|
+
-- Roles define named collections of scopes that can be assigned to API keys.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS roles (
|
|
5
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
+
name TEXT NOT NULL UNIQUE,
|
|
7
|
+
description TEXT,
|
|
8
|
+
scopes TEXT[] NOT NULL DEFAULT ARRAY['read'],
|
|
9
|
+
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
-- Index for tenant-scoped role lookups
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_roles_tenant_id ON roles (tenant_id);
|
|
16
|
+
|
|
17
|
+
-- Add role_id column to api_keys
|
|
18
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS role_id UUID REFERENCES roles(id) ON DELETE SET NULL;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- Add hash_version column to distinguish SHA-256 (legacy) from HMAC-SHA256 hashes.
|
|
2
|
+
-- 1 = SHA-256 (legacy), 2 = HMAC-SHA256 (current).
|
|
3
|
+
-- Existing keys default to version 1; new keys will be created with version 2.
|
|
4
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS hash_version SMALLINT NOT NULL DEFAULT 1;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Encrypt database_url column in regions table
|
|
2
|
+
-- Existing plaintext URLs will need to be migrated via the key rotation service
|
|
3
|
+
ALTER TABLE regions RENAME COLUMN database_url TO database_url_encrypted;
|
|
4
|
+
-- Add a comment to remind developers this column is encrypted
|
|
5
|
+
COMMENT ON COLUMN regions.database_url_encrypted IS 'AES-256-GCM encrypted PostgreSQL connection string. Use encrypt()/decrypt() from @stratum-hq/lib.';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Handle migration renaming: 002_sort_order.sql → 014_sort_order.sql
|
|
2
|
+
-- If an existing deployment already applied 002_sort_order.sql, record the
|
|
3
|
+
-- new name so the migration runner doesn't try to re-apply it.
|
|
4
|
+
INSERT INTO _migrations (name, applied_at)
|
|
5
|
+
SELECT '014_sort_order.sql', applied_at
|
|
6
|
+
FROM _migrations
|
|
7
|
+
WHERE name = '002_sort_order.sql'
|
|
8
|
+
ON CONFLICT DO NOTHING;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS abac_policies (
|
|
2
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3
|
+
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
4
|
+
name TEXT NOT NULL,
|
|
5
|
+
resource_type TEXT NOT NULL,
|
|
6
|
+
action TEXT NOT NULL,
|
|
7
|
+
effect TEXT NOT NULL CHECK (effect IN ('allow', 'deny')),
|
|
8
|
+
conditions JSONB NOT NULL DEFAULT '[]',
|
|
9
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
mode TEXT NOT NULL DEFAULT 'INHERITED' CHECK (mode IN ('LOCKED', 'INHERITED', 'DELEGATED')),
|
|
11
|
+
source_tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
12
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
13
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
14
|
+
);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_abac_policies_tenant_id ON abac_policies(tenant_id);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_abac_policies_resource_action ON abac_policies(resource_type, action);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abac-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/abac-service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const abac_service_js_1 = require("../abac-service.js");
|
|
5
|
+
function makePolicy(overrides = {}) {
|
|
6
|
+
return {
|
|
7
|
+
id: "pol-1",
|
|
8
|
+
tenant_id: "t-1",
|
|
9
|
+
name: "test-policy",
|
|
10
|
+
resource_type: "document",
|
|
11
|
+
action: "read",
|
|
12
|
+
effect: "allow",
|
|
13
|
+
conditions: [],
|
|
14
|
+
priority: 0,
|
|
15
|
+
mode: "INHERITED",
|
|
16
|
+
source_tenant_id: "t-1",
|
|
17
|
+
created_at: new Date(),
|
|
18
|
+
updated_at: new Date(),
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// evaluateCondition — operator tests
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
(0, vitest_1.describe)("evaluateCondition", () => {
|
|
26
|
+
(0, vitest_1.it)("eq: matches when values are equal", () => {
|
|
27
|
+
const c = { attribute: "role", operator: "eq", value: "admin" };
|
|
28
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { role: "admin" })).toBe(true);
|
|
29
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { role: "viewer" })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)("neq: matches when values differ", () => {
|
|
32
|
+
const c = { attribute: "role", operator: "neq", value: "admin" };
|
|
33
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { role: "viewer" })).toBe(true);
|
|
34
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { role: "admin" })).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)("in: matches when value is in array", () => {
|
|
37
|
+
const c = { attribute: "dept", operator: "in", value: ["eng", "product"] };
|
|
38
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { dept: "eng" })).toBe(true);
|
|
39
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { dept: "sales" })).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.it)("not_in: matches when value is not in array", () => {
|
|
42
|
+
const c = { attribute: "dept", operator: "not_in", value: ["eng", "product"] };
|
|
43
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { dept: "sales" })).toBe(true);
|
|
44
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { dept: "eng" })).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)("contains: matches when array attribute contains value", () => {
|
|
47
|
+
const c = { attribute: "tags", operator: "contains", value: "urgent" };
|
|
48
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { tags: ["urgent", "important"] })).toBe(true);
|
|
49
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { tags: ["normal"] })).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)("gt: numeric greater than", () => {
|
|
52
|
+
const c = { attribute: "level", operator: "gt", value: 5 };
|
|
53
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { level: 10 })).toBe(true);
|
|
54
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { level: 5 })).toBe(false);
|
|
55
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { level: 3 })).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)("gte: numeric greater than or equal", () => {
|
|
58
|
+
const c = { attribute: "level", operator: "gte", value: 5 };
|
|
59
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { level: 5 })).toBe(true);
|
|
60
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { level: 4 })).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.it)("lt: numeric less than", () => {
|
|
63
|
+
const c = { attribute: "risk", operator: "lt", value: 3 };
|
|
64
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { risk: 1 })).toBe(true);
|
|
65
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { risk: 3 })).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.it)("lte: numeric less than or equal", () => {
|
|
68
|
+
const c = { attribute: "risk", operator: "lte", value: 3 };
|
|
69
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { risk: 3 })).toBe(true);
|
|
70
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { risk: 4 })).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("returns false when attribute is missing from context", () => {
|
|
73
|
+
const c = { attribute: "missing", operator: "eq", value: "x" };
|
|
74
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluateCondition)(c, { other: "y" })).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("throws for unknown operator", () => {
|
|
77
|
+
const c = { attribute: "x", operator: "nope", value: "y" };
|
|
78
|
+
(0, vitest_1.expect)(() => (0, abac_service_js_1.evaluateCondition)(c, { x: "y" })).toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// evaluatePolicy
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
(0, vitest_1.describe)("evaluatePolicy", () => {
|
|
85
|
+
(0, vitest_1.it)("empty conditions → always matches", () => {
|
|
86
|
+
const policy = makePolicy({ conditions: [] });
|
|
87
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluatePolicy)(policy, {})).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)("all conditions true → matches", () => {
|
|
90
|
+
const policy = makePolicy({
|
|
91
|
+
conditions: [
|
|
92
|
+
{ attribute: "role", operator: "eq", value: "admin" },
|
|
93
|
+
{ attribute: "level", operator: "gte", value: 3 },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluatePolicy)(policy, { role: "admin", level: 5 })).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)("one condition false → does not match (AND semantics)", () => {
|
|
99
|
+
const policy = makePolicy({
|
|
100
|
+
conditions: [
|
|
101
|
+
{ attribute: "role", operator: "eq", value: "admin" },
|
|
102
|
+
{ attribute: "level", operator: "gte", value: 10 },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.expect)((0, abac_service_js_1.evaluatePolicy)(policy, { role: "admin", level: 5 })).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=abac-service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abac-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/abac-service.test.ts"],"names":[],"mappings":";;AAAA,mCAA8C;AAC9C,wDAAuE;AAGvE,SAAS,UAAU,CAAC,YAAiC,EAAE;IACrD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,aAAa;QACnB,aAAa,EAAE,UAAU;QACzB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,OAAO;QACf,UAAU,EAAE,EAAE;QACd,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,WAAW;QACjB,gBAAgB,EAAE,KAAK;QACvB,UAAU,EAAE,IAAI,IAAI,EAAE;QACtB,UAAU,EAAE,IAAI,IAAI,EAAE;QACtB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,qCAAqC;AACrC,8EAA8E;AAC9E,IAAA,iBAAQ,EAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAA,WAAE,EAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAC/E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAChF,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;QAC1F,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;QAC9F,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QACtF,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC1E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC3E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACzE,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC1E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC9E,IAAA,eAAM,EAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAa,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAClE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAiB,EAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAC9E,IAAA,iBAAQ,EAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAA,WAAE,EAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9C,IAAA,eAAM,EAAC,IAAA,gCAAc,EAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,MAAM,GAAG,UAAU,CAAC;YACxB,UAAU,EAAE;gBACV,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE;gBACrD,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;aAClD;SACF,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,IAAA,gCAAc,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,MAAM,GAAG,UAAU,CAAC;YACxB,UAAU,EAAE;gBACV,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE;gBACrD,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;aACnD;SACF,CAAC,CAAC;QACH,IAAA,eAAM,EAAC,IAAA,gCAAc,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -88,17 +88,20 @@ function makeMockPool() {
|
|
|
88
88
|
return fn(client);
|
|
89
89
|
});
|
|
90
90
|
await retentionService.purgeTenant(pool, "tenant-123");
|
|
91
|
-
// 1 child check +
|
|
91
|
+
// 1 child check + 11 deletes (config, permissions by tenant_id,
|
|
92
|
+
// permissions by source_tenant_id, api_keys, roles, webhook_deliveries,
|
|
92
93
|
// webhook_events, webhooks, consent_records, audit_logs, tenants)
|
|
93
|
-
(0, vitest_1.expect)(mockQuery).toHaveBeenCalledTimes(
|
|
94
|
+
(0, vitest_1.expect)(mockQuery).toHaveBeenCalledTimes(12);
|
|
94
95
|
// First query is the child count check
|
|
95
96
|
(0, vitest_1.expect)(mockQuery.mock.calls[0][0]).toContain("COUNT");
|
|
96
97
|
// Then FK-ordered deletes
|
|
97
98
|
(0, vitest_1.expect)(mockQuery.mock.calls[1][0]).toContain("config_entries");
|
|
98
99
|
(0, vitest_1.expect)(mockQuery.mock.calls[2][0]).toContain("permission_policies");
|
|
99
|
-
(0, vitest_1.expect)(mockQuery.mock.calls[3][0]).toContain("
|
|
100
|
+
(0, vitest_1.expect)(mockQuery.mock.calls[3][0]).toContain("permission_policies");
|
|
101
|
+
(0, vitest_1.expect)(mockQuery.mock.calls[4][0]).toContain("api_keys");
|
|
102
|
+
(0, vitest_1.expect)(mockQuery.mock.calls[5][0]).toContain("roles");
|
|
100
103
|
// Last delete is the tenant itself
|
|
101
|
-
(0, vitest_1.expect)(mockQuery.mock.calls[
|
|
104
|
+
(0, vitest_1.expect)(mockQuery.mock.calls[11][0]).toContain("tenants");
|
|
102
105
|
// Verify tenant_id parameter
|
|
103
106
|
(0, vitest_1.expect)(mockQuery.mock.calls[1][1]).toEqual(["tenant-123"]);
|
|
104
107
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retention-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/retention-service.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"retention-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/retention-service.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mCAA8D;AAE9D,WAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,UAAU,EAAE,WAAE,CAAC,EAAE,EAAE;IACnB,eAAe,EAAE,WAAE,CAAC,EAAE,EAAE;CACzB,CAAC,CAAC,CAAC;AAEJ,mEAAqD;AACrD,0EAA4D;AAE5D,SAAS,YAAY;IACnB,OAAO,EAAuB,CAAC;AACjC,CAAC;AAED,IAAA,mBAAU,EAAC,GAAG,EAAE;IACd,WAAE,CAAC,aAAa,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAA,WAAE,EAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE;aACtB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAE,qBAAqB;aAC7D,qBAAqB,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAE,iBAAiB;aACzD,qBAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa;QAEzD,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC5E,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;YAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAEjE,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9C,IAAA,eAAM,EAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACnE,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAE7D,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC5E,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;YAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAE9C,IAAA,eAAM,EAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAA,WAAE,EAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE;YACvB,6BAA6B;aAC5B,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YAChD,oCAAoC;aACnC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAEtC,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YAC5E,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;YAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,gBAAgB,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAEvD,gEAAgE;QAChE,wEAAwE;QACxE,kEAAkE;QAClE,IAAA,eAAM,EAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAC5C,uCAAuC;QACvC,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACtD,0BAA0B;QAC1B,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QACpE,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QACpE,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACzD,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACtD,mCAAmC;QACnC,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACzD,6BAA6B;QAC7B,IAAA,eAAM,EAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAA,WAAE,EAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE;aACtB,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAG,UAAU;aACzD,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,iBAAiB;aACjE,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,sBAAsB;aACtE,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,WAAW;aAC3D,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,WAAW;aAC3D,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,iBAAiB;aACjE,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,qBAAqB;aACrE,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAa,aAAa;aAC7D,qBAAqB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAY,kBAAkB;QAErE,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YACvE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;YAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAE3E,IAAA,eAAM,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzC,IAAA,eAAM,EAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAA,eAAM,EAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAA,eAAM,EAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAA,eAAM,EAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAA,eAAM,EAAC,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtC,IAAA,eAAM,EAAC,MAAM,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,WAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAE1D,WAAE,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YACvE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAwC,CAAC;YAC1E,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAE5E,IAAA,eAAM,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QACjC,8DAA8D;QAC9D,kEAAkE;QAClE,IAAA,eAAM,EAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import { type AbacCondition, type AbacPolicy, type CreateAbacPolicyInput, type AbacEvaluationRequest, type AbacEvaluationResult, type ResolvedAbacPolicy } from "@stratum-hq/core";
|
|
3
|
+
/**
|
|
4
|
+
* Evaluate a single ABAC condition against an attribute context.
|
|
5
|
+
* Returns false when the attribute is missing from context.
|
|
6
|
+
* Throws InvalidAbacOperatorError for unknown operators.
|
|
7
|
+
*/
|
|
8
|
+
export declare function evaluateCondition(condition: AbacCondition, context: Record<string, unknown>): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate all conditions for a policy against a context (AND semantics).
|
|
11
|
+
* Empty conditions array → true (open policy).
|
|
12
|
+
*/
|
|
13
|
+
export declare function evaluatePolicy(policy: AbacPolicy, context: Record<string, unknown>): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve effective ABAC policies for a tenant by batch-loading all ancestor
|
|
16
|
+
* policies in a single query and walking root→leaf (same pattern as resolvePermissions).
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveAbacPolicies(pool: pg.Pool, tenantId: string): Promise<ResolvedAbacPolicy[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Evaluate an ABAC request against the resolved policies for a tenant.
|
|
21
|
+
* - Deny overrides allow.
|
|
22
|
+
* - Policies sorted by priority (higher first).
|
|
23
|
+
* - Default deny if no policy matches.
|
|
24
|
+
* - Supports '*' wildcard for resource_type or action.
|
|
25
|
+
*/
|
|
26
|
+
export declare function evaluateAbac(pool: pg.Pool, tenantId: string, request: AbacEvaluationRequest): Promise<AbacEvaluationResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a new ABAC policy for a tenant.
|
|
29
|
+
* Rejects if an ancestor policy with the same composite key is LOCKED.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createAbacPolicy(pool: pg.Pool, tenantId: string, input: CreateAbacPolicyInput): Promise<AbacPolicy>;
|
|
32
|
+
/**
|
|
33
|
+
* Get all ABAC policies owned by a tenant (non-resolved, own policies only).
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAbacPolicies(pool: pg.Pool, tenantId: string): Promise<AbacPolicy[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Delete an ABAC policy, with CASCADE: removes descendant policies sharing
|
|
38
|
+
* the same (resource_type, action, name) composite.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deleteAbacPolicy(pool: pg.Pool, tenantId: string, policyId: string): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=abac-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abac-service.d.ts","sourceRoot":"","sources":["../../src/services/abac-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EAMxB,MAAM,kBAAkB,CAAC;AAM1B;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAsCT;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAKT;AAMD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAkF/B;AAMD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA4D/B;AAMD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,UAAU,CAAC,CA8CrB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAoCf"}
|