@wtfalch/authz-store 0.1.0
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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/credential-secret.d.ts +70 -0
- package/dist/credential-secret.js +89 -0
- package/dist/grants.d.ts +395 -0
- package/dist/grants.js +68 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.js +41 -0
- package/dist/owners.d.ts +23 -0
- package/dist/owners.js +40 -0
- package/dist/policy-schema.d.ts +812 -0
- package/dist/policy-schema.js +57 -0
- package/dist/resource-schema.d.ts +188 -0
- package/dist/resource-schema.js +22 -0
- package/dist/schema.d.ts +1821 -0
- package/dist/schema.js +193 -0
- package/dist/scoped.d.ts +140 -0
- package/dist/scoped.js +176 -0
- package/migrations/0001_baseline.sql +571 -0
- package/migrations/0002_grants.sql +27 -0
- package/package.json +44 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
-- The store's baseline: every table @wtfalch/authz-store defines, as manage's
|
|
2
|
+
-- live schema has it on 2026-09-22 (manage drizzle/0000-0015 plus scripts/authz-v2).
|
|
3
|
+
-- Left to each app: the offered-permission CHECKs on tenants (they list that
|
|
4
|
+
-- app's catalogue) and break_glass_sessions.operator_id -> profiles (a table the
|
|
5
|
+
-- app owns). authz_events_action_check lists @wtfalch/authz's core events.
|
|
6
|
+
-- Generated once from pg_dump. Never edit it: a change is a new numbered file.
|
|
7
|
+
CREATE OR REPLACE FUNCTION access_team_tree_guard()
|
|
8
|
+
RETURNS trigger
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
AS $function$
|
|
11
|
+
DECLARE current_id uuid; next_id uuid; depth integer := 0; subtree_depth integer := 0;
|
|
12
|
+
BEGIN
|
|
13
|
+
IF TG_OP = 'UPDATE' AND NEW.tenant_id <> OLD.tenant_id THEN
|
|
14
|
+
RAISE EXCEPTION 'a team cannot move between organisations';
|
|
15
|
+
END IF;
|
|
16
|
+
PERFORM 1 FROM tenants WHERE id = NEW.tenant_id FOR UPDATE;
|
|
17
|
+
current_id := NEW.parent_id;
|
|
18
|
+
WHILE current_id IS NOT NULL LOOP
|
|
19
|
+
IF current_id = NEW.id OR depth >= 63 THEN RAISE EXCEPTION 'invalid team hierarchy'; END IF;
|
|
20
|
+
SELECT parent_id INTO next_id FROM access_teams WHERE tenant_id = NEW.tenant_id AND id = current_id;
|
|
21
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'unknown team parent'; END IF;
|
|
22
|
+
current_id := next_id;
|
|
23
|
+
depth := depth + 1;
|
|
24
|
+
END LOOP;
|
|
25
|
+
IF TG_OP = 'UPDATE' AND NEW.parent_id IS DISTINCT FROM OLD.parent_id THEN
|
|
26
|
+
WITH RECURSIVE children AS (
|
|
27
|
+
SELECT id, 0 AS level FROM access_teams WHERE id = NEW.id AND tenant_id = NEW.tenant_id
|
|
28
|
+
UNION ALL
|
|
29
|
+
SELECT t.id, c.level + 1 FROM access_teams t JOIN children c ON t.parent_id = c.id
|
|
30
|
+
WHERE t.tenant_id = NEW.tenant_id AND c.level < 64
|
|
31
|
+
) SELECT coalesce(max(level), 0) INTO subtree_depth FROM children;
|
|
32
|
+
IF depth + subtree_depth >= 64 THEN RAISE EXCEPTION 'invalid team hierarchy'; END IF;
|
|
33
|
+
END IF;
|
|
34
|
+
RETURN NEW;
|
|
35
|
+
END $function$;
|
|
36
|
+
--> statement-breakpoint
|
|
37
|
+
CREATE OR REPLACE FUNCTION authz_event_resource_guard()
|
|
38
|
+
RETURNS trigger
|
|
39
|
+
LANGUAGE plpgsql
|
|
40
|
+
AS $function$
|
|
41
|
+
BEGIN
|
|
42
|
+
IF NEW.team_id IS DISTINCT FROM OLD.team_id OR NEW.subject_id IS DISTINCT FROM OLD.subject_id
|
|
43
|
+
OR NEW.subject_class IS DISTINCT FROM OLD.subject_class THEN
|
|
44
|
+
RAISE EXCEPTION 'audit resource metadata is immutable';
|
|
45
|
+
END IF;
|
|
46
|
+
RETURN NEW;
|
|
47
|
+
END $function$;
|
|
48
|
+
--> statement-breakpoint
|
|
49
|
+
CREATE OR REPLACE FUNCTION authz_events_guard()
|
|
50
|
+
RETURNS trigger
|
|
51
|
+
LANGUAGE plpgsql
|
|
52
|
+
AS $function$
|
|
53
|
+
BEGIN
|
|
54
|
+
IF TG_OP = 'DELETE' THEN
|
|
55
|
+
RAISE EXCEPTION 'authz_events is append-only: delete refused';
|
|
56
|
+
ELSIF TG_OP = 'TRUNCATE' THEN
|
|
57
|
+
RAISE EXCEPTION 'authz_events is append-only: truncate refused';
|
|
58
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
59
|
+
IF NEW."id" IS DISTINCT FROM OLD."id"
|
|
60
|
+
OR NEW."occurred_at" IS DISTINCT FROM OLD."occurred_at"
|
|
61
|
+
OR NEW."tenant_id" IS DISTINCT FROM OLD."tenant_id"
|
|
62
|
+
OR NEW."actor_class" IS DISTINCT FROM OLD."actor_class"
|
|
63
|
+
OR NEW."actor_id" IS DISTINCT FROM OLD."actor_id"
|
|
64
|
+
OR NEW."action" IS DISTINCT FROM OLD."action"
|
|
65
|
+
OR NEW."target_type" IS DISTINCT FROM OLD."target_type"
|
|
66
|
+
OR NEW."target_id" IS DISTINCT FROM OLD."target_id"
|
|
67
|
+
OR NEW."outcome" IS DISTINCT FROM OLD."outcome"
|
|
68
|
+
OR NEW."context" IS DISTINCT FROM OLD."context"
|
|
69
|
+
OR NEW."session_id" IS DISTINCT FROM OLD."session_id"
|
|
70
|
+
OR NEW."reason" IS DISTINCT FROM OLD."reason"
|
|
71
|
+
OR NEW."reference" IS DISTINCT FROM OLD."reference"
|
|
72
|
+
OR NEW."request_id" IS DISTINCT FROM OLD."request_id"
|
|
73
|
+
OR NEW."ip" IS DISTINCT FROM OLD."ip"
|
|
74
|
+
OR NEW."user_agent" IS DISTINCT FROM OLD."user_agent"
|
|
75
|
+
OR NEW."tenant_visible" IS DISTINCT FROM OLD."tenant_visible"
|
|
76
|
+
OR NEW."schema_version" IS DISTINCT FROM OLD."schema_version"
|
|
77
|
+
THEN
|
|
78
|
+
RAISE EXCEPTION 'authz_events is append-only: only actor_display, before, after and erased_at may change';
|
|
79
|
+
END IF;
|
|
80
|
+
END IF;
|
|
81
|
+
RETURN NEW;
|
|
82
|
+
END
|
|
83
|
+
$function$;
|
|
84
|
+
--> statement-breakpoint
|
|
85
|
+
CREATE TABLE access_team_members (
|
|
86
|
+
tenant_id uuid NOT NULL,
|
|
87
|
+
team_id uuid NOT NULL,
|
|
88
|
+
principal_id text NOT NULL,
|
|
89
|
+
principal_class text DEFAULT 'human'::text NOT NULL,
|
|
90
|
+
CONSTRAINT access_team_members_principal_class_check CHECK ((principal_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text])))
|
|
91
|
+
);
|
|
92
|
+
--> statement-breakpoint
|
|
93
|
+
CREATE TABLE access_teams (
|
|
94
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
95
|
+
tenant_id uuid NOT NULL,
|
|
96
|
+
parent_id uuid,
|
|
97
|
+
name text NOT NULL,
|
|
98
|
+
archived_at timestamp with time zone,
|
|
99
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
100
|
+
CONSTRAINT access_teams_check CHECK ((parent_id IS DISTINCT FROM id)),
|
|
101
|
+
CONSTRAINT access_teams_name_check CHECK (((length(btrim(name)) >= 1) AND (length(btrim(name)) <= 100)))
|
|
102
|
+
);
|
|
103
|
+
--> statement-breakpoint
|
|
104
|
+
CREATE TABLE attach_proposals (
|
|
105
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
106
|
+
parent_id uuid NOT NULL,
|
|
107
|
+
child_id uuid NOT NULL,
|
|
108
|
+
status text DEFAULT 'pending'::text NOT NULL,
|
|
109
|
+
reason text NOT NULL,
|
|
110
|
+
reference text NOT NULL,
|
|
111
|
+
proposed_by text NOT NULL,
|
|
112
|
+
parent_accepted_by text,
|
|
113
|
+
parent_accepted_at timestamp with time zone,
|
|
114
|
+
decided_by text,
|
|
115
|
+
decided_at timestamp with time zone,
|
|
116
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
117
|
+
CONSTRAINT attach_proposals_decided_check CHECK (((status = ANY (ARRAY['pending_parent'::text, 'pending'::text])) = (decided_at IS NULL))),
|
|
118
|
+
CONSTRAINT attach_proposals_not_self_check CHECK ((parent_id <> child_id)),
|
|
119
|
+
CONSTRAINT attach_proposals_parent_accepted_check CHECK (((status <> 'pending_parent'::text) OR (parent_accepted_at IS NULL))),
|
|
120
|
+
CONSTRAINT attach_proposals_reason_check CHECK (((length(reason) >= 1) AND (length(reason) <= 512))),
|
|
121
|
+
CONSTRAINT attach_proposals_reference_check CHECK (((length(reference) >= 1) AND (length(reference) <= 512))),
|
|
122
|
+
CONSTRAINT attach_proposals_status_check CHECK ((status = ANY (ARRAY['pending_parent'::text, 'pending'::text, 'accepted'::text, 'declined'::text])))
|
|
123
|
+
);
|
|
124
|
+
--> statement-breakpoint
|
|
125
|
+
CREATE TABLE authz_assignments (
|
|
126
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
127
|
+
tenant_id uuid NOT NULL,
|
|
128
|
+
application_id text NOT NULL,
|
|
129
|
+
platform_id text NOT NULL,
|
|
130
|
+
role_id uuid NOT NULL,
|
|
131
|
+
principal_id text,
|
|
132
|
+
principal_class text,
|
|
133
|
+
team_id uuid,
|
|
134
|
+
recipient_descendants boolean DEFAULT false NOT NULL,
|
|
135
|
+
boundary jsonb NOT NULL,
|
|
136
|
+
scope jsonb NOT NULL,
|
|
137
|
+
source text DEFAULT 'direct'::text NOT NULL,
|
|
138
|
+
via_tenant_id uuid,
|
|
139
|
+
primary_assignment boolean DEFAULT false NOT NULL,
|
|
140
|
+
expires_at timestamp with time zone,
|
|
141
|
+
created_by text,
|
|
142
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
143
|
+
CONSTRAINT authz_assignments_boundary_check CHECK (((jsonb_typeof(boundary) = 'object'::text) AND ((boundary ->> 'kind'::text) = ANY (ARRAY['organisation'::text, 'organisations'::text, 'platform'::text])))),
|
|
144
|
+
CONSTRAINT authz_assignments_check CHECK (((principal_id IS NOT NULL) = (principal_class IS NOT NULL))),
|
|
145
|
+
CONSTRAINT authz_assignments_check1 CHECK (((((principal_id IS NOT NULL))::integer + ((team_id IS NOT NULL))::integer) = 1)),
|
|
146
|
+
CONSTRAINT authz_assignments_check2 CHECK (((NOT recipient_descendants) OR (team_id IS NOT NULL))),
|
|
147
|
+
CONSTRAINT authz_assignments_check3 CHECK (((NOT primary_assignment) OR (principal_id IS NOT NULL))),
|
|
148
|
+
CONSTRAINT authz_assignments_check4 CHECK (((source = 'inherited'::text) = (via_tenant_id IS NOT NULL))),
|
|
149
|
+
CONSTRAINT authz_assignments_principal_class_check CHECK ((principal_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
150
|
+
CONSTRAINT authz_assignments_scope_check CHECK (((jsonb_typeof(scope) = 'object'::text) AND ((scope ->> 'kind'::text) = ANY (ARRAY['organisation'::text, 'team'::text, 'own_teams'::text, 'resource'::text])))),
|
|
151
|
+
CONSTRAINT authz_assignments_source_check CHECK ((source = ANY (ARRAY['direct'::text, 'inherited'::text, 'activation'::text])))
|
|
152
|
+
);
|
|
153
|
+
--> statement-breakpoint
|
|
154
|
+
CREATE TABLE authz_events (
|
|
155
|
+
id bigint NOT NULL,
|
|
156
|
+
occurred_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
157
|
+
tenant_id uuid,
|
|
158
|
+
actor_class text NOT NULL,
|
|
159
|
+
actor_id text NOT NULL,
|
|
160
|
+
actor_display text NOT NULL,
|
|
161
|
+
action text NOT NULL,
|
|
162
|
+
target_type text NOT NULL,
|
|
163
|
+
target_id text NOT NULL,
|
|
164
|
+
outcome text NOT NULL,
|
|
165
|
+
context text NOT NULL,
|
|
166
|
+
session_id text,
|
|
167
|
+
reason text,
|
|
168
|
+
reference text,
|
|
169
|
+
request_id text,
|
|
170
|
+
ip text,
|
|
171
|
+
user_agent text,
|
|
172
|
+
tenant_visible boolean NOT NULL,
|
|
173
|
+
before jsonb,
|
|
174
|
+
after jsonb,
|
|
175
|
+
erased_at timestamp with time zone,
|
|
176
|
+
schema_version smallint DEFAULT 1 NOT NULL,
|
|
177
|
+
team_id uuid,
|
|
178
|
+
subject_id text,
|
|
179
|
+
subject_class text,
|
|
180
|
+
CONSTRAINT authz_events_actor_class_check CHECK ((actor_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
181
|
+
CONSTRAINT authz_events_actor_display_check CHECK (((length(actor_display) >= 1) AND (length(actor_display) <= 256))),
|
|
182
|
+
CONSTRAINT authz_events_actor_id_check CHECK (((length(actor_id) >= 1) AND (length(actor_id) <= 256))),
|
|
183
|
+
CONSTRAINT authz_events_after_check CHECK (((after IS NULL) OR (octet_length((after)::text) <= 65536))),
|
|
184
|
+
CONSTRAINT authz_events_before_check CHECK (((before IS NULL) OR (octet_length((before)::text) <= 65536))),
|
|
185
|
+
CONSTRAINT authz_events_break_glass_check CHECK (((context <> 'break_glass'::text) OR ((session_id IS NOT NULL) AND (reason IS NOT NULL) AND (reference IS NOT NULL) AND (reason = ANY (ARRAY['customer_support'::text, 'incident'::text, 'review'::text, 'migration'::text, 'other'::text]))))),
|
|
186
|
+
CONSTRAINT authz_events_context_check CHECK ((context = ANY (ARRAY['standard'::text, 'operator'::text, 'break_glass'::text]))),
|
|
187
|
+
CONSTRAINT authz_events_ip_check CHECK (((ip IS NULL) OR (length(ip) <= 64))),
|
|
188
|
+
CONSTRAINT authz_events_outcome_check CHECK ((outcome = ANY (ARRAY['success'::text, 'denied'::text, 'error'::text]))),
|
|
189
|
+
CONSTRAINT authz_events_reason_check CHECK (((reason IS NULL) OR (length(reason) <= 512))),
|
|
190
|
+
CONSTRAINT authz_events_reference_check CHECK (((reference IS NULL) OR (length(reference) <= 512))),
|
|
191
|
+
CONSTRAINT authz_events_request_id_check CHECK (((request_id IS NULL) OR (length(request_id) <= 128))),
|
|
192
|
+
CONSTRAINT authz_events_session_id_check CHECK (((session_id IS NULL) OR (length(session_id) <= 256))),
|
|
193
|
+
CONSTRAINT authz_events_subject_class_check CHECK ((subject_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
194
|
+
CONSTRAINT authz_events_subject_pair_check CHECK (((subject_id IS NULL) = (subject_class IS NULL))),
|
|
195
|
+
CONSTRAINT authz_events_target_id_check CHECK (((length(target_id) >= 1) AND (length(target_id) <= 256))),
|
|
196
|
+
CONSTRAINT authz_events_target_type_check CHECK (((length(target_type) >= 1) AND (length(target_type) <= 64))),
|
|
197
|
+
CONSTRAINT authz_events_team_requires_tenant_check CHECK (((team_id IS NULL) OR (tenant_id IS NOT NULL))),
|
|
198
|
+
CONSTRAINT authz_events_user_agent_check CHECK (((user_agent IS NULL) OR (length(user_agent) <= 1024)))
|
|
199
|
+
);
|
|
200
|
+
--> statement-breakpoint
|
|
201
|
+
ALTER TABLE authz_events ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
|
|
202
|
+
SEQUENCE NAME authz_events_id_seq
|
|
203
|
+
START WITH 1
|
|
204
|
+
INCREMENT BY 1
|
|
205
|
+
NO MINVALUE
|
|
206
|
+
NO MAXVALUE
|
|
207
|
+
CACHE 1
|
|
208
|
+
);
|
|
209
|
+
--> statement-breakpoint
|
|
210
|
+
CREATE TABLE authz_policy_version (
|
|
211
|
+
id integer NOT NULL,
|
|
212
|
+
version integer NOT NULL,
|
|
213
|
+
application_id text NOT NULL,
|
|
214
|
+
platform_id text NOT NULL,
|
|
215
|
+
mapping_revision text NOT NULL,
|
|
216
|
+
source_digest text NOT NULL,
|
|
217
|
+
migrated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
218
|
+
CONSTRAINT authz_policy_version_id_check CHECK ((id = 1)),
|
|
219
|
+
CONSTRAINT authz_policy_version_version_check CHECK ((version = 2))
|
|
220
|
+
);
|
|
221
|
+
--> statement-breakpoint
|
|
222
|
+
CREATE TABLE authz_roles (
|
|
223
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
224
|
+
tenant_id uuid NOT NULL,
|
|
225
|
+
application_id text NOT NULL,
|
|
226
|
+
platform_id text NOT NULL,
|
|
227
|
+
key text NOT NULL,
|
|
228
|
+
name text NOT NULL,
|
|
229
|
+
description text DEFAULT ''::text NOT NULL,
|
|
230
|
+
built_in boolean DEFAULT false NOT NULL,
|
|
231
|
+
revision integer DEFAULT 1 NOT NULL,
|
|
232
|
+
entries jsonb NOT NULL,
|
|
233
|
+
guards text[] DEFAULT '{}'::text[] NOT NULL,
|
|
234
|
+
created_by text,
|
|
235
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
236
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
237
|
+
updated_by text,
|
|
238
|
+
CONSTRAINT authz_roles_entries_check CHECK (((jsonb_typeof(entries) = 'array'::text) AND (jsonb_array_length(entries) <= 1000))),
|
|
239
|
+
CONSTRAINT authz_roles_key_check CHECK ((key ~ '^[a-z][a-z0-9_-]{0,63}$'::text)),
|
|
240
|
+
CONSTRAINT authz_roles_name_check CHECK (((length(btrim(name)) >= 1) AND (length(btrim(name)) <= 100))),
|
|
241
|
+
CONSTRAINT authz_roles_revision_check CHECK ((revision > 0))
|
|
242
|
+
);
|
|
243
|
+
--> statement-breakpoint
|
|
244
|
+
CREATE TABLE break_glass_sessions (
|
|
245
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
246
|
+
operator_id text NOT NULL,
|
|
247
|
+
tenant_id uuid NOT NULL,
|
|
248
|
+
read_only boolean DEFAULT true NOT NULL,
|
|
249
|
+
reason_code text NOT NULL,
|
|
250
|
+
reference text NOT NULL,
|
|
251
|
+
started_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
252
|
+
expires_at timestamp with time zone NOT NULL,
|
|
253
|
+
ended_at timestamp with time zone,
|
|
254
|
+
ended_by text,
|
|
255
|
+
role_id uuid NOT NULL,
|
|
256
|
+
CONSTRAINT bg_ended_check CHECK (((ended_at IS NULL) OR (ended_at >= started_at))),
|
|
257
|
+
CONSTRAINT bg_reason_check CHECK ((reason_code = ANY (ARRAY['customer_support'::text, 'incident'::text, 'review'::text, 'migration'::text, 'other'::text]))),
|
|
258
|
+
CONSTRAINT bg_reference_check CHECK (((length(reference) >= 1) AND (length(reference) <= 512))),
|
|
259
|
+
CONSTRAINT bg_ttl_check CHECK (((expires_at > started_at) AND (expires_at <= (started_at + '01:00:00'::interval))))
|
|
260
|
+
);
|
|
261
|
+
--> statement-breakpoint
|
|
262
|
+
CREATE TABLE credentials (
|
|
263
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
264
|
+
tenant_id uuid NOT NULL,
|
|
265
|
+
kind text NOT NULL,
|
|
266
|
+
name text NOT NULL,
|
|
267
|
+
secret_hash text NOT NULL,
|
|
268
|
+
created_by text,
|
|
269
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
270
|
+
expires_at timestamp with time zone,
|
|
271
|
+
revoked_at timestamp with time zone,
|
|
272
|
+
last_used_at timestamp with time zone,
|
|
273
|
+
secret_prefix text,
|
|
274
|
+
issuer_id text NOT NULL,
|
|
275
|
+
issuer_class text NOT NULL,
|
|
276
|
+
CONSTRAINT credentials_expiry_check CHECK (((expires_at IS NULL) OR (expires_at > created_at))),
|
|
277
|
+
CONSTRAINT credentials_issuer_class_check CHECK ((issuer_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
278
|
+
CONSTRAINT credentials_kind_check CHECK ((kind = ANY (ARRAY['api_key'::text, 'agent'::text, 'service'::text]))),
|
|
279
|
+
CONSTRAINT credentials_name_check CHECK (((length(name) >= 1) AND (length(name) <= 120))),
|
|
280
|
+
CONSTRAINT credentials_secret_prefix_check CHECK (((secret_prefix IS NULL) OR ((length(secret_prefix) >= 1) AND (length(secret_prefix) <= 16))))
|
|
281
|
+
);
|
|
282
|
+
--> statement-breakpoint
|
|
283
|
+
CREATE TABLE invitations (
|
|
284
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
285
|
+
tenant_id uuid NOT NULL,
|
|
286
|
+
email text NOT NULL,
|
|
287
|
+
token_hash text NOT NULL,
|
|
288
|
+
status text DEFAULT 'pending'::text NOT NULL,
|
|
289
|
+
mail_state text DEFAULT 'pending'::text NOT NULL,
|
|
290
|
+
invited_by text,
|
|
291
|
+
expires_at timestamp with time zone NOT NULL,
|
|
292
|
+
accepted_by text,
|
|
293
|
+
accepted_at timestamp with time zone,
|
|
294
|
+
revoked_by text,
|
|
295
|
+
revoked_at timestamp with time zone,
|
|
296
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
297
|
+
attach_parent_id uuid,
|
|
298
|
+
mail_state_at timestamp with time zone,
|
|
299
|
+
role_id uuid NOT NULL,
|
|
300
|
+
invited_by_class text,
|
|
301
|
+
CONSTRAINT invitations_accepted_check CHECK (((status = 'accepted'::text) = (accepted_at IS NOT NULL))),
|
|
302
|
+
CONSTRAINT invitations_email_lower_check CHECK ((email = lower(email))),
|
|
303
|
+
CONSTRAINT invitations_expiry_check CHECK ((expires_at > created_at)),
|
|
304
|
+
CONSTRAINT invitations_invited_by_class_check CHECK ((invited_by_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
305
|
+
CONSTRAINT invitations_mail_state_check CHECK ((mail_state = ANY (ARRAY['pending'::text, 'sent'::text, 'failed'::text]))),
|
|
306
|
+
CONSTRAINT invitations_revoked_check CHECK (((status = 'revoked'::text) = (revoked_at IS NOT NULL))),
|
|
307
|
+
CONSTRAINT invitations_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'accepted'::text, 'revoked'::text, 'expired'::text]))),
|
|
308
|
+
CONSTRAINT invitations_typed_inviter CHECK (((status <> 'pending'::text) OR (invited_by IS NULL) OR (invited_by_class IS NOT NULL)))
|
|
309
|
+
);
|
|
310
|
+
--> statement-breakpoint
|
|
311
|
+
CREATE TABLE memberships (
|
|
312
|
+
tenant_id uuid NOT NULL,
|
|
313
|
+
principal_id text NOT NULL,
|
|
314
|
+
principal_class text NOT NULL,
|
|
315
|
+
source text DEFAULT 'direct'::text NOT NULL,
|
|
316
|
+
via_tenant_id uuid,
|
|
317
|
+
tenant_name text NOT NULL,
|
|
318
|
+
granted_by text,
|
|
319
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
320
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
321
|
+
CONSTRAINT memberships_principal_class_check CHECK ((principal_class = ANY (ARRAY['human'::text, 'api_key'::text, 'agent'::text, 'service'::text]))),
|
|
322
|
+
CONSTRAINT memberships_source_check CHECK ((source = ANY (ARRAY['direct'::text, 'inherited'::text]))),
|
|
323
|
+
CONSTRAINT memberships_source_via_check CHECK (((source = 'inherited'::text) = (via_tenant_id IS NOT NULL)))
|
|
324
|
+
);
|
|
325
|
+
--> statement-breakpoint
|
|
326
|
+
CREATE TABLE tenants (
|
|
327
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
328
|
+
kind text DEFAULT 'customer'::text NOT NULL,
|
|
329
|
+
slug text NOT NULL,
|
|
330
|
+
name text NOT NULL,
|
|
331
|
+
state text DEFAULT 'active'::text NOT NULL,
|
|
332
|
+
parent_id uuid,
|
|
333
|
+
ceiling text[] NOT NULL,
|
|
334
|
+
self_denied text[] DEFAULT '{}'::text[] NOT NULL,
|
|
335
|
+
created_by text,
|
|
336
|
+
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
337
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
338
|
+
frozen boolean DEFAULT false NOT NULL,
|
|
339
|
+
CONSTRAINT tenants_frozen_operator_check CHECK (((kind = 'operator'::text) OR (NOT frozen))),
|
|
340
|
+
CONSTRAINT tenants_kind_check CHECK ((kind = ANY (ARRAY['customer'::text, 'operator'::text]))),
|
|
341
|
+
CONSTRAINT tenants_name_check CHECK (((length(name) >= 1) AND (length(name) <= 120))),
|
|
342
|
+
CONSTRAINT tenants_operator_active_check CHECK (((kind <> 'operator'::text) OR (state = 'active'::text))),
|
|
343
|
+
CONSTRAINT tenants_operator_no_parent_check CHECK (((kind <> 'operator'::text) OR (parent_id IS NULL))),
|
|
344
|
+
CONSTRAINT tenants_parent_not_self_check CHECK (((parent_id IS NULL) OR (parent_id <> id))),
|
|
345
|
+
CONSTRAINT tenants_slug_check CHECK ((slug ~ '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'::text)),
|
|
346
|
+
CONSTRAINT tenants_state_check CHECK ((state = ANY (ARRAY['active'::text, 'read_only'::text, 'suspended'::text, 'archived'::text])))
|
|
347
|
+
);
|
|
348
|
+
--> statement-breakpoint
|
|
349
|
+
ALTER TABLE ONLY access_team_members
|
|
350
|
+
ADD CONSTRAINT access_team_members_pkey PRIMARY KEY (team_id, principal_class, principal_id);
|
|
351
|
+
--> statement-breakpoint
|
|
352
|
+
ALTER TABLE ONLY access_teams
|
|
353
|
+
ADD CONSTRAINT access_teams_pkey PRIMARY KEY (id);
|
|
354
|
+
--> statement-breakpoint
|
|
355
|
+
ALTER TABLE ONLY access_teams
|
|
356
|
+
ADD CONSTRAINT access_teams_tenant_id_id_key UNIQUE (tenant_id, id);
|
|
357
|
+
--> statement-breakpoint
|
|
358
|
+
ALTER TABLE ONLY attach_proposals
|
|
359
|
+
ADD CONSTRAINT attach_proposals_pkey PRIMARY KEY (id);
|
|
360
|
+
--> statement-breakpoint
|
|
361
|
+
ALTER TABLE ONLY authz_assignments
|
|
362
|
+
ADD CONSTRAINT authz_assignments_pkey PRIMARY KEY (id);
|
|
363
|
+
--> statement-breakpoint
|
|
364
|
+
ALTER TABLE ONLY authz_events
|
|
365
|
+
ADD CONSTRAINT authz_events_pkey PRIMARY KEY (id);
|
|
366
|
+
--> statement-breakpoint
|
|
367
|
+
ALTER TABLE ONLY authz_policy_version
|
|
368
|
+
ADD CONSTRAINT authz_policy_version_pkey PRIMARY KEY (id);
|
|
369
|
+
--> statement-breakpoint
|
|
370
|
+
ALTER TABLE ONLY authz_roles
|
|
371
|
+
ADD CONSTRAINT authz_roles_boundary_id UNIQUE (tenant_id, application_id, platform_id, id);
|
|
372
|
+
--> statement-breakpoint
|
|
373
|
+
ALTER TABLE ONLY authz_roles
|
|
374
|
+
ADD CONSTRAINT authz_roles_pkey PRIMARY KEY (id);
|
|
375
|
+
--> statement-breakpoint
|
|
376
|
+
ALTER TABLE ONLY authz_roles
|
|
377
|
+
ADD CONSTRAINT authz_roles_tenant_app_key UNIQUE (tenant_id, application_id, key);
|
|
378
|
+
--> statement-breakpoint
|
|
379
|
+
ALTER TABLE ONLY authz_roles
|
|
380
|
+
ADD CONSTRAINT authz_roles_tenant_id_id_key UNIQUE (tenant_id, id);
|
|
381
|
+
--> statement-breakpoint
|
|
382
|
+
ALTER TABLE ONLY break_glass_sessions
|
|
383
|
+
ADD CONSTRAINT break_glass_sessions_pkey PRIMARY KEY (id);
|
|
384
|
+
--> statement-breakpoint
|
|
385
|
+
ALTER TABLE ONLY credentials
|
|
386
|
+
ADD CONSTRAINT credentials_pkey PRIMARY KEY (id);
|
|
387
|
+
--> statement-breakpoint
|
|
388
|
+
ALTER TABLE ONLY credentials
|
|
389
|
+
ADD CONSTRAINT credentials_secret_hash_key UNIQUE (secret_hash);
|
|
390
|
+
--> statement-breakpoint
|
|
391
|
+
ALTER TABLE ONLY invitations
|
|
392
|
+
ADD CONSTRAINT invitations_pkey PRIMARY KEY (id);
|
|
393
|
+
--> statement-breakpoint
|
|
394
|
+
ALTER TABLE ONLY invitations
|
|
395
|
+
ADD CONSTRAINT invitations_token_hash_key UNIQUE (token_hash);
|
|
396
|
+
--> statement-breakpoint
|
|
397
|
+
ALTER TABLE ONLY memberships
|
|
398
|
+
ADD CONSTRAINT memberships_pkey PRIMARY KEY (tenant_id, principal_class, principal_id);
|
|
399
|
+
--> statement-breakpoint
|
|
400
|
+
ALTER TABLE ONLY memberships
|
|
401
|
+
ADD CONSTRAINT memberships_typed_identity UNIQUE (tenant_id, principal_class, principal_id);
|
|
402
|
+
--> statement-breakpoint
|
|
403
|
+
ALTER TABLE ONLY tenants
|
|
404
|
+
ADD CONSTRAINT tenants_pkey PRIMARY KEY (id);
|
|
405
|
+
--> statement-breakpoint
|
|
406
|
+
ALTER TABLE ONLY tenants
|
|
407
|
+
ADD CONSTRAINT tenants_slug_unique UNIQUE (slug);
|
|
408
|
+
--> statement-breakpoint
|
|
409
|
+
CREATE INDEX access_team_members_principal_idx ON access_team_members USING btree (tenant_id, principal_id);
|
|
410
|
+
--> statement-breakpoint
|
|
411
|
+
CREATE INDEX access_teams_parent_idx ON access_teams USING btree (tenant_id, parent_id);
|
|
412
|
+
--> statement-breakpoint
|
|
413
|
+
CREATE INDEX attach_proposals_child_idx ON attach_proposals USING btree (child_id, created_at DESC);
|
|
414
|
+
--> statement-breakpoint
|
|
415
|
+
CREATE INDEX attach_proposals_parent_idx ON attach_proposals USING btree (parent_id, created_at DESC);
|
|
416
|
+
--> statement-breakpoint
|
|
417
|
+
CREATE UNIQUE INDEX attach_proposals_pending_idx ON attach_proposals USING btree (parent_id, child_id) WHERE (status = ANY (ARRAY['pending_parent'::text, 'pending'::text]));
|
|
418
|
+
--> statement-breakpoint
|
|
419
|
+
CREATE UNIQUE INDEX authz_assignments_primary_idx ON authz_assignments USING btree (tenant_id, application_id, principal_class, principal_id) WHERE primary_assignment;
|
|
420
|
+
--> statement-breakpoint
|
|
421
|
+
CREATE INDEX authz_assignments_principal_idx ON authz_assignments USING btree (tenant_id, principal_class, principal_id);
|
|
422
|
+
--> statement-breakpoint
|
|
423
|
+
CREATE INDEX authz_assignments_role_idx ON authz_assignments USING btree (tenant_id, role_id);
|
|
424
|
+
--> statement-breakpoint
|
|
425
|
+
CREATE INDEX authz_assignments_team_idx ON authz_assignments USING btree (tenant_id, team_id);
|
|
426
|
+
--> statement-breakpoint
|
|
427
|
+
CREATE INDEX authz_events_action_time_idx ON authz_events USING btree (action, occurred_at DESC);
|
|
428
|
+
--> statement-breakpoint
|
|
429
|
+
CREATE INDEX authz_events_actor_time_idx ON authz_events USING btree (actor_id, occurred_at DESC);
|
|
430
|
+
--> statement-breakpoint
|
|
431
|
+
CREATE INDEX authz_events_subject_time_idx ON authz_events USING btree (tenant_id, subject_class, subject_id, occurred_at DESC, id DESC);
|
|
432
|
+
--> statement-breakpoint
|
|
433
|
+
CREATE INDEX authz_events_team_time_idx ON authz_events USING btree (tenant_id, team_id, occurred_at DESC, id DESC);
|
|
434
|
+
--> statement-breakpoint
|
|
435
|
+
CREATE INDEX authz_events_tenant_time_idx ON authz_events USING btree (tenant_id, occurred_at DESC, id DESC);
|
|
436
|
+
--> statement-breakpoint
|
|
437
|
+
CREATE INDEX bg_active_idx ON break_glass_sessions USING btree (operator_id, tenant_id) WHERE (ended_at IS NULL);
|
|
438
|
+
--> statement-breakpoint
|
|
439
|
+
CREATE INDEX bg_tenant_idx ON break_glass_sessions USING btree (tenant_id, started_at DESC);
|
|
440
|
+
--> statement-breakpoint
|
|
441
|
+
CREATE INDEX credentials_live_idx ON credentials USING btree (tenant_id, created_at DESC) WHERE (revoked_at IS NULL);
|
|
442
|
+
--> statement-breakpoint
|
|
443
|
+
CREATE INDEX credentials_tenant_idx ON credentials USING btree (tenant_id);
|
|
444
|
+
--> statement-breakpoint
|
|
445
|
+
CREATE UNIQUE INDEX invitations_pending_idx ON invitations USING btree (tenant_id, email) WHERE (status = 'pending'::text);
|
|
446
|
+
--> statement-breakpoint
|
|
447
|
+
CREATE INDEX invitations_tenant_idx ON invitations USING btree (tenant_id, created_at DESC);
|
|
448
|
+
--> statement-breakpoint
|
|
449
|
+
CREATE INDEX memberships_principal_idx ON memberships USING btree (principal_class, principal_id, tenant_name, tenant_id);
|
|
450
|
+
--> statement-breakpoint
|
|
451
|
+
CREATE INDEX memberships_via_idx ON memberships USING btree (via_tenant_id);
|
|
452
|
+
--> statement-breakpoint
|
|
453
|
+
CREATE UNIQUE INDEX tenants_one_operator_idx ON tenants USING btree (kind) WHERE (kind = 'operator'::text);
|
|
454
|
+
--> statement-breakpoint
|
|
455
|
+
CREATE INDEX tenants_parent_idx ON tenants USING btree (parent_id);
|
|
456
|
+
--> statement-breakpoint
|
|
457
|
+
CREATE TRIGGER access_team_tree_guard BEFORE INSERT OR UPDATE ON access_teams FOR EACH ROW EXECUTE FUNCTION access_team_tree_guard();
|
|
458
|
+
--> statement-breakpoint
|
|
459
|
+
CREATE TRIGGER authz_event_resource_guard BEFORE UPDATE ON authz_events FOR EACH ROW EXECUTE FUNCTION authz_event_resource_guard();
|
|
460
|
+
--> statement-breakpoint
|
|
461
|
+
CREATE TRIGGER authz_events_guarded_update BEFORE UPDATE ON authz_events FOR EACH ROW EXECUTE FUNCTION authz_events_guard();
|
|
462
|
+
--> statement-breakpoint
|
|
463
|
+
CREATE TRIGGER authz_events_no_delete BEFORE DELETE ON authz_events FOR EACH ROW EXECUTE FUNCTION authz_events_guard();
|
|
464
|
+
--> statement-breakpoint
|
|
465
|
+
CREATE TRIGGER authz_events_no_truncate BEFORE TRUNCATE ON authz_events FOR EACH STATEMENT EXECUTE FUNCTION authz_events_guard();
|
|
466
|
+
--> statement-breakpoint
|
|
467
|
+
ALTER TABLE ONLY access_team_members
|
|
468
|
+
ADD CONSTRAINT access_team_members_tenant_id_principal_class_principal_id_fkey FOREIGN KEY (tenant_id, principal_class, principal_id) REFERENCES memberships(tenant_id, principal_class, principal_id) ON DELETE CASCADE;
|
|
469
|
+
--> statement-breakpoint
|
|
470
|
+
ALTER TABLE ONLY access_team_members
|
|
471
|
+
ADD CONSTRAINT access_team_members_tenant_id_team_id_fkey FOREIGN KEY (tenant_id, team_id) REFERENCES access_teams(tenant_id, id) ON DELETE CASCADE;
|
|
472
|
+
--> statement-breakpoint
|
|
473
|
+
ALTER TABLE ONLY access_teams
|
|
474
|
+
ADD CONSTRAINT access_teams_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
475
|
+
--> statement-breakpoint
|
|
476
|
+
ALTER TABLE ONLY access_teams
|
|
477
|
+
ADD CONSTRAINT access_teams_tenant_id_parent_id_fkey FOREIGN KEY (tenant_id, parent_id) REFERENCES access_teams(tenant_id, id);
|
|
478
|
+
--> statement-breakpoint
|
|
479
|
+
ALTER TABLE ONLY attach_proposals
|
|
480
|
+
ADD CONSTRAINT attach_proposals_child_id_fkey FOREIGN KEY (child_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
481
|
+
--> statement-breakpoint
|
|
482
|
+
ALTER TABLE ONLY attach_proposals
|
|
483
|
+
ADD CONSTRAINT attach_proposals_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
484
|
+
--> statement-breakpoint
|
|
485
|
+
ALTER TABLE ONLY authz_assignments
|
|
486
|
+
ADD CONSTRAINT authz_assignments_tenant_id_application_id_platform_id_rol_fkey FOREIGN KEY (tenant_id, application_id, platform_id, role_id) REFERENCES authz_roles(tenant_id, application_id, platform_id, id);
|
|
487
|
+
--> statement-breakpoint
|
|
488
|
+
ALTER TABLE ONLY authz_assignments
|
|
489
|
+
ADD CONSTRAINT authz_assignments_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
490
|
+
--> statement-breakpoint
|
|
491
|
+
ALTER TABLE ONLY authz_assignments
|
|
492
|
+
ADD CONSTRAINT authz_assignments_tenant_id_principal_class_principal_id_fkey FOREIGN KEY (tenant_id, principal_class, principal_id) REFERENCES memberships(tenant_id, principal_class, principal_id) ON DELETE CASCADE;
|
|
493
|
+
--> statement-breakpoint
|
|
494
|
+
ALTER TABLE ONLY authz_assignments
|
|
495
|
+
ADD CONSTRAINT authz_assignments_tenant_id_team_id_fkey FOREIGN KEY (tenant_id, team_id) REFERENCES access_teams(tenant_id, id) ON DELETE CASCADE;
|
|
496
|
+
--> statement-breakpoint
|
|
497
|
+
ALTER TABLE ONLY authz_assignments
|
|
498
|
+
ADD CONSTRAINT authz_assignments_via_tenant_id_fkey FOREIGN KEY (via_tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
499
|
+
--> statement-breakpoint
|
|
500
|
+
ALTER TABLE ONLY authz_events
|
|
501
|
+
ADD CONSTRAINT authz_events_team_tenant_fk FOREIGN KEY (tenant_id, team_id) REFERENCES access_teams(tenant_id, id);
|
|
502
|
+
--> statement-breakpoint
|
|
503
|
+
ALTER TABLE ONLY authz_roles
|
|
504
|
+
ADD CONSTRAINT authz_roles_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
505
|
+
--> statement-breakpoint
|
|
506
|
+
ALTER TABLE ONLY break_glass_sessions
|
|
507
|
+
ADD CONSTRAINT break_glass_sessions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE RESTRICT;
|
|
508
|
+
--> statement-breakpoint
|
|
509
|
+
ALTER TABLE ONLY credentials
|
|
510
|
+
ADD CONSTRAINT credentials_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
511
|
+
--> statement-breakpoint
|
|
512
|
+
ALTER TABLE ONLY invitations
|
|
513
|
+
ADD CONSTRAINT invitations_attach_parent_id_fkey FOREIGN KEY (attach_parent_id) REFERENCES tenants(id) ON DELETE SET NULL;
|
|
514
|
+
--> statement-breakpoint
|
|
515
|
+
ALTER TABLE ONLY invitations
|
|
516
|
+
ADD CONSTRAINT invitations_policy_role_fk FOREIGN KEY (tenant_id, role_id) REFERENCES authz_roles(tenant_id, id);
|
|
517
|
+
--> statement-breakpoint
|
|
518
|
+
ALTER TABLE ONLY invitations
|
|
519
|
+
ADD CONSTRAINT invitations_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
520
|
+
--> statement-breakpoint
|
|
521
|
+
ALTER TABLE ONLY memberships
|
|
522
|
+
ADD CONSTRAINT memberships_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
523
|
+
--> statement-breakpoint
|
|
524
|
+
ALTER TABLE ONLY memberships
|
|
525
|
+
ADD CONSTRAINT memberships_via_tenant_id_fkey FOREIGN KEY (via_tenant_id) REFERENCES tenants(id) ON DELETE CASCADE;
|
|
526
|
+
--> statement-breakpoint
|
|
527
|
+
ALTER TABLE ONLY break_glass_sessions
|
|
528
|
+
ADD CONSTRAINT support_policy_role_fk FOREIGN KEY (tenant_id, role_id) REFERENCES authz_roles(tenant_id, id);
|
|
529
|
+
--> statement-breakpoint
|
|
530
|
+
ALTER TABLE ONLY tenants
|
|
531
|
+
ADD CONSTRAINT tenants_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES tenants(id) ON DELETE RESTRICT;
|
|
532
|
+
--> statement-breakpoint
|
|
533
|
+
ALTER TABLE "authz_events" ADD CONSTRAINT "authz_events_action_check" CHECK ("action" IN (
|
|
534
|
+
'activation.approved',
|
|
535
|
+
'activation.denied',
|
|
536
|
+
'activation.expired',
|
|
537
|
+
'activation.requested',
|
|
538
|
+
'activation.revoked',
|
|
539
|
+
'activation.started',
|
|
540
|
+
'audit_log.viewed',
|
|
541
|
+
'break_glass.ended',
|
|
542
|
+
'break_glass.started',
|
|
543
|
+
'break_glass.viewed',
|
|
544
|
+
'credential.minted',
|
|
545
|
+
'credential.revoked',
|
|
546
|
+
'credential.rotated',
|
|
547
|
+
'invitation.accepted',
|
|
548
|
+
'invitation.expired',
|
|
549
|
+
'invitation.revoked',
|
|
550
|
+
'invitation.sent',
|
|
551
|
+
'membership.created',
|
|
552
|
+
'membership.ended',
|
|
553
|
+
'membership.role_changed',
|
|
554
|
+
'operator.bootstrapped',
|
|
555
|
+
'owner.installed',
|
|
556
|
+
'person.erased',
|
|
557
|
+
'platform.frozen',
|
|
558
|
+
'platform.unfrozen',
|
|
559
|
+
'role.created',
|
|
560
|
+
'role.deleted',
|
|
561
|
+
'role.updated',
|
|
562
|
+
'tenant.attach_declined',
|
|
563
|
+
'tenant.attach_proposed',
|
|
564
|
+
'tenant.ceiling_changed',
|
|
565
|
+
'tenant.created',
|
|
566
|
+
'tenant.exported',
|
|
567
|
+
'tenant.parent_attached',
|
|
568
|
+
'tenant.parent_detached',
|
|
569
|
+
'tenant.settings_changed',
|
|
570
|
+
'tenant.state_changed'
|
|
571
|
+
));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- Grants to one person, stored rather than compiled from a role. Guests live
|
|
2
|
+
-- here; see src/grants.ts and https://github.com/wtfalch/authz/issues/42.
|
|
3
|
+
CREATE TABLE "authz_grants" (
|
|
4
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
5
|
+
"tenant_id" uuid NOT NULL,
|
|
6
|
+
"application_id" text NOT NULL,
|
|
7
|
+
"platform_id" text NOT NULL,
|
|
8
|
+
"permission" text NOT NULL,
|
|
9
|
+
"principal_id" text NOT NULL,
|
|
10
|
+
"principal_class" text NOT NULL,
|
|
11
|
+
"boundary" jsonb NOT NULL,
|
|
12
|
+
"scope" jsonb NOT NULL,
|
|
13
|
+
"constraints" jsonb,
|
|
14
|
+
"relation" text DEFAULT 'any' NOT NULL,
|
|
15
|
+
"guest" boolean DEFAULT false NOT NULL,
|
|
16
|
+
"reason" text,
|
|
17
|
+
"expires_at" timestamp with time zone,
|
|
18
|
+
"revoked_at" timestamp with time zone,
|
|
19
|
+
"created_by" text,
|
|
20
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
21
|
+
-- The engine refuses a guest grant with no end date, so the table does too.
|
|
22
|
+
CONSTRAINT "authz_grants_guest_expires" CHECK (NOT "guest" OR "expires_at" IS NOT NULL)
|
|
23
|
+
);
|
|
24
|
+
--> statement-breakpoint
|
|
25
|
+
CREATE INDEX "authz_grants_principal_idx" ON "authz_grants" ("tenant_id", "principal_class", "principal_id");
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE INDEX "authz_grants_guest_idx" ON "authz_grants" ("tenant_id", "guest");
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wtfalch/authz-store",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistence and lifecycle for @wtfalch/authz: the storage a host would otherwise write itself.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"migrations",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@wtfalch/authz": "^0.12.0",
|
|
20
|
+
"drizzle-orm": "^0.39.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.0.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@electric-sql/pglite": "^0.5.8",
|
|
31
|
+
"@types/node": "^22",
|
|
32
|
+
"drizzle-kit": "^0.31.10",
|
|
33
|
+
"drizzle-orm": "^0.39.0",
|
|
34
|
+
"fast-check": "^4.9.0",
|
|
35
|
+
"postgres": "^3.4.5",
|
|
36
|
+
"typescript": "^5.9.0",
|
|
37
|
+
"vitest": "^4.1.6"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"test": "vitest run"
|
|
43
|
+
}
|
|
44
|
+
}
|