@ankhorage/infra 0.3.2 → 0.3.4

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/dist/adapters/minikube/auth/supabase/index.d.ts.map +1 -1
  4. package/dist/adapters/minikube/auth/supabase/index.js +14 -163
  5. package/dist/adapters/minikube/auth/supabase/index.js.map +1 -1
  6. package/dist/adapters/minikube/auth/supabase/profile.d.ts +21 -0
  7. package/dist/adapters/minikube/auth/supabase/profile.d.ts.map +1 -0
  8. package/dist/adapters/minikube/auth/supabase/profile.js +452 -0
  9. package/dist/adapters/minikube/auth/supabase/profile.js.map +1 -0
  10. package/dist/adapters/minikube/authz/cerbos/index.d.ts +1 -1
  11. package/dist/adapters/minikube/authz/cerbos/index.d.ts.map +1 -1
  12. package/dist/adapters/minikube/authz/cerbos/index.js +5 -3
  13. package/dist/adapters/minikube/authz/cerbos/index.js.map +1 -1
  14. package/dist/adapters/minikube/authz/index.d.ts +1 -1
  15. package/dist/adapters/minikube/authz/index.d.ts.map +1 -1
  16. package/dist/adapters/minikube/authz/index.js +1 -1
  17. package/dist/adapters/minikube/authz/index.js.map +1 -1
  18. package/dist/adapters/minikube/base/index.d.ts.map +1 -1
  19. package/dist/adapters/minikube/base/index.js +850 -54
  20. package/dist/adapters/minikube/base/index.js.map +1 -1
  21. package/dist/adapters/minikube/index.js +2 -1
  22. package/dist/adapters/minikube/index.js.map +1 -1
  23. package/dist/infraValidation.js +1 -1
  24. package/dist/infraValidation.js.map +1 -1
  25. package/dist/project.d.ts.map +1 -1
  26. package/dist/project.js +1 -18
  27. package/dist/project.js.map +1 -1
  28. package/dist/testSupport.d.ts.map +1 -1
  29. package/dist/testSupport.js +0 -6
  30. package/dist/testSupport.js.map +1 -1
  31. package/dist/types.d.ts +1 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/adapters/minikube/auth/supabase/index.test.ts +152 -18
  36. package/src/adapters/minikube/auth/supabase/index.ts +18 -205
  37. package/src/adapters/minikube/auth/supabase/profile.integration.test.ts +860 -0
  38. package/src/adapters/minikube/auth/supabase/profile.ts +526 -0
  39. package/src/adapters/minikube/authz/cerbos/index.ts +7 -5
  40. package/src/adapters/minikube/authz/index.ts +2 -2
  41. package/src/adapters/minikube/base/index.test.ts +100 -6
  42. package/src/adapters/minikube/base/index.ts +880 -54
  43. package/src/adapters/minikube/index.ts +3 -1
  44. package/src/index.test.ts +13 -2
  45. package/src/infraValidation.ts +1 -1
  46. package/src/project.ts +2 -22
  47. package/src/testSupport.ts +0 -6
  48. package/src/types.ts +1 -1
@@ -0,0 +1,452 @@
1
+ import { createHash } from 'node:crypto';
2
+ const DEFAULT_PROFILE_FIELDS = ['email', 'firstName', 'lastName'];
3
+ const DEFAULT_PROFILE_PRIMARY_KEY = 'authUserId';
4
+ const DEFAULT_PROFILE_CREATE_STRATEGY = 'trigger';
5
+ const DEFAULT_PROFILE_UPDATE_STRATEGY = 'api';
6
+ const PROFILE_POLICY_VERSION = 1;
7
+ const PROFILE_RECONCILER_VERSION = 1;
8
+ const PROFILE_ARTIFACT_KEY = 'auth.profile';
9
+ export const MANAGED_PROFILE_COLUMNS = [
10
+ 'email',
11
+ 'display_name',
12
+ 'first_name',
13
+ 'last_name',
14
+ 'avatar_url',
15
+ 'username',
16
+ 'phone',
17
+ ];
18
+ export function resolveSupabaseProfileModel(manifest) {
19
+ const table = normalizeIdentifier(manifest.auth?.profile?.table ?? '');
20
+ const fields = normalizeFieldList(manifest.auth?.profile?.fields);
21
+ const primaryKey = manifest.auth?.profile?.primaryKey ?? DEFAULT_PROFILE_PRIMARY_KEY;
22
+ const createStrategy = manifest.auth?.profile?.createStrategy ?? DEFAULT_PROFILE_CREATE_STRATEGY;
23
+ const updateStrategy = manifest.auth?.profile?.updateStrategy ?? DEFAULT_PROFILE_UPDATE_STRATEGY;
24
+ const columns = resolveProfileColumns(fields);
25
+ const hash = table
26
+ ? getProfileDesiredStateHash({
27
+ table,
28
+ primaryKey,
29
+ createStrategy,
30
+ updateStrategy,
31
+ columns,
32
+ })
33
+ : '';
34
+ return {
35
+ enabled: table.length > 0,
36
+ table,
37
+ primaryKey,
38
+ createStrategy,
39
+ updateStrategy,
40
+ columns,
41
+ desiredStateHash: hash,
42
+ };
43
+ }
44
+ export function getSupabaseProfileReconciliation(profileModel) {
45
+ const table = quoteIdentifier(profileModel.table);
46
+ const functionName = quoteIdentifier(`handle_new_${profileModel.table}_user`);
47
+ const triggerName = quoteIdentifier(`on_auth_user_created_${profileModel.table}`);
48
+ const configuredColumns = new Set(profileModel.columns.map((column) => column.column));
49
+ const addColumnSql = profileModel.columns
50
+ .map((column) => `alter table public.${table} add column if not exists ${quoteIdentifier(column.column)} ${column.sqlType};`)
51
+ .join('\n');
52
+ const dropStaleManagedColumnSql = MANAGED_PROFILE_COLUMNS.filter((column) => !configuredColumns.has(column))
53
+ .map((column) => `alter table public.${table} drop column if exists ${quoteIdentifier(column)};`)
54
+ .join('\n');
55
+ const insertColumns = ['id', ...profileModel.columns.map((column) => column.column)]
56
+ .map(quoteIdentifier)
57
+ .join(', ');
58
+ const insertValues = ['new.id', ...profileModel.columns.map((column) => column.fromNewUser)].join(', ');
59
+ const updateAssignments = profileModel.columns
60
+ .filter((column) => column.field === 'email')
61
+ .map((column) => `${quoteIdentifier(column.column)} = excluded.${quoteIdentifier(column.column)}`);
62
+ const conflictUpdate = [...updateAssignments, 'updated_at = now()'].join(',\n ');
63
+ const updateGrantColumns = profileModel.columns.map((column) => quoteIdentifier(column.column));
64
+ const updateGrantSql = updateGrantColumns.length > 0
65
+ ? `grant update (${updateGrantColumns.join(', ')}) on table public.${table} to authenticated;`
66
+ : '';
67
+ const triggerSql = profileModel.createStrategy === 'trigger'
68
+ ? `
69
+ create or replace function public.${functionName}()
70
+ returns trigger
71
+ language plpgsql
72
+ security definer
73
+ set search_path = pg_catalog, pg_temp
74
+ as $$
75
+ begin
76
+ insert into public.${table} (${insertColumns})
77
+ values (${insertValues})
78
+ on conflict (id) do update set
79
+ ${conflictUpdate};
80
+
81
+ return new;
82
+ end;
83
+ $$;
84
+
85
+ revoke execute on function public.${functionName}() from PUBLIC;
86
+ revoke execute on function public.${functionName}() from anon;
87
+ revoke execute on function public.${functionName}() from authenticated;
88
+
89
+ drop trigger if exists ${triggerName} on auth.users;
90
+ create trigger ${triggerName}
91
+ after insert on auth.users
92
+ for each row execute function public.${functionName}();
93
+ `
94
+ : `
95
+ drop trigger if exists ${triggerName} on auth.users;
96
+ drop function if exists public.${functionName}();
97
+ `;
98
+ return `-- Generated by @ankhorage/infra from manifest.auth.profile.
99
+ -- Desired-state reconciliation for local Supabase; not a migration-history entry.
100
+
101
+ begin;
102
+
103
+ create schema if not exists ankhorage_internal;
104
+
105
+ create table if not exists ankhorage_internal.generated_schema_state (
106
+ artifact_key text primary key,
107
+ table_name text,
108
+ content_hash text not null,
109
+ applied_at timestamptz not null
110
+ );
111
+
112
+ alter table ankhorage_internal.generated_schema_state
113
+ add column if not exists table_name text;
114
+
115
+ do $$
116
+ declare
117
+ previous_table_name text;
118
+ has_previous_state boolean;
119
+ begin
120
+ select table_name, true
121
+ into previous_table_name, has_previous_state
122
+ from ankhorage_internal.generated_schema_state
123
+ where artifact_key = '${PROFILE_ARTIFACT_KEY}';
124
+
125
+ if has_previous_state and previous_table_name is null then
126
+ raise exception 'existing auth.profile generated state is missing table identity; reset local Supabase or add an explicit cleanup migration before applying generated profile reconciliation';
127
+ end if;
128
+
129
+ if has_previous_state and previous_table_name <> '${escapeSqlLiteral(profileModel.table)}' then
130
+ raise exception 'profile.table changed from % to %; reset local Supabase or add an explicit cleanup migration before applying generated profile reconciliation', previous_table_name, '${escapeSqlLiteral(profileModel.table)}';
131
+ end if;
132
+ end;
133
+ $$;
134
+
135
+ create table if not exists public.${table} (
136
+ id uuid primary key references auth.users(id) on delete cascade,
137
+ created_at timestamptz not null default now(),
138
+ updated_at timestamptz not null default now()
139
+ );
140
+
141
+ alter table public.${table} add column if not exists id uuid;
142
+ alter table public.${table} add column if not exists created_at timestamptz not null default now();
143
+ alter table public.${table} add column if not exists updated_at timestamptz not null default now();
144
+ ${addColumnSql}
145
+ alter table public.${table} drop column if exists role;
146
+ ${dropStaleManagedColumnSql}
147
+
148
+ do $$
149
+ begin
150
+ if exists (
151
+ select 1
152
+ from pg_constraint c
153
+ where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
154
+ and c.contype = 'p'
155
+ and c.conkey <> array[
156
+ (
157
+ select a.attnum
158
+ from pg_attribute a
159
+ where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
160
+ and a.attname = 'id'
161
+ and not a.attisdropped
162
+ )
163
+ ]::smallint[]
164
+ ) then
165
+ raise exception 'profile table public.${escapeSqlLiteral(profileModel.table)} has a primary key that is not exactly id';
166
+ end if;
167
+
168
+ if not exists (
169
+ select 1
170
+ from pg_constraint
171
+ where conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
172
+ and contype = 'p'
173
+ ) then
174
+ alter table public.${table}
175
+ add constraint ${quoteIdentifier(`${profileModel.table}_pkey`)} primary key (id);
176
+ end if;
177
+ end;
178
+ $$;
179
+
180
+ do $$
181
+ begin
182
+ if exists (
183
+ select 1
184
+ from pg_constraint c
185
+ where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
186
+ and c.contype = 'f'
187
+ and c.confrelid = 'auth.users'::regclass
188
+ and c.conkey && array[
189
+ (
190
+ select a.attnum
191
+ from pg_attribute a
192
+ where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
193
+ and a.attname = 'id'
194
+ and not a.attisdropped
195
+ )
196
+ ]::smallint[]
197
+ and (
198
+ c.confdeltype <> 'c'
199
+ or c.conkey <> array[
200
+ (
201
+ select a.attnum
202
+ from pg_attribute a
203
+ where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
204
+ and a.attname = 'id'
205
+ and not a.attisdropped
206
+ )
207
+ ]::smallint[]
208
+ or c.confkey <> array[
209
+ (
210
+ select a.attnum
211
+ from pg_attribute a
212
+ where a.attrelid = 'auth.users'::regclass
213
+ and a.attname = 'id'
214
+ and not a.attisdropped
215
+ )
216
+ ]::smallint[]
217
+ )
218
+ ) then
219
+ raise exception 'profile table public.${escapeSqlLiteral(profileModel.table)} has an auth.users foreign key that is not exactly id -> auth.users(id) on delete cascade';
220
+ end if;
221
+
222
+ if not exists (
223
+ select 1
224
+ from pg_constraint c
225
+ where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
226
+ and c.confrelid = 'auth.users'::regclass
227
+ and c.contype = 'f'
228
+ and c.confdeltype = 'c'
229
+ and c.conkey = array[
230
+ (
231
+ select a.attnum
232
+ from pg_attribute a
233
+ where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass
234
+ and a.attname = 'id'
235
+ and not a.attisdropped
236
+ )
237
+ ]::smallint[]
238
+ and c.confkey = array[
239
+ (
240
+ select a.attnum
241
+ from pg_attribute a
242
+ where a.attrelid = 'auth.users'::regclass
243
+ and a.attname = 'id'
244
+ and not a.attisdropped
245
+ )
246
+ ]::smallint[]
247
+ ) then
248
+ alter table public.${table}
249
+ add constraint ${quoteIdentifier(`${profileModel.table}_id_auth_users_fkey`)}
250
+ foreign key (id) references auth.users(id) on delete cascade;
251
+ end if;
252
+ end;
253
+ $$;
254
+
255
+ do $$
256
+ begin
257
+ if not exists (
258
+ select 1
259
+ from information_schema.columns c
260
+ where c.table_schema = 'public'
261
+ and c.table_name = '${escapeSqlLiteral(profileModel.table)}'
262
+ and c.column_name = 'id'
263
+ and c.udt_name = 'uuid'
264
+ and c.is_nullable = 'NO'
265
+ ) then
266
+ raise exception 'profile id column must be uuid not null';
267
+ end if;
268
+
269
+ if not exists (
270
+ select 1
271
+ from information_schema.columns c
272
+ where c.table_schema = 'public'
273
+ and c.table_name = '${escapeSqlLiteral(profileModel.table)}'
274
+ and c.column_name = 'created_at'
275
+ and c.data_type = 'timestamp with time zone'
276
+ and c.is_nullable = 'NO'
277
+ and c.column_default = 'now()'
278
+ ) then
279
+ raise exception 'profile created_at column must be timestamptz not null default now()';
280
+ end if;
281
+
282
+ if not exists (
283
+ select 1
284
+ from information_schema.columns c
285
+ where c.table_schema = 'public'
286
+ and c.table_name = '${escapeSqlLiteral(profileModel.table)}'
287
+ and c.column_name = 'updated_at'
288
+ and c.data_type = 'timestamp with time zone'
289
+ and c.is_nullable = 'NO'
290
+ and c.column_default = 'now()'
291
+ ) then
292
+ raise exception 'profile updated_at column must be timestamptz not null default now()';
293
+ end if;
294
+ end;
295
+ $$;
296
+
297
+ alter table public.${table} enable row level security;
298
+
299
+ revoke all on table public.${table} from anon, authenticated;
300
+ grant select on table public.${table} to authenticated;
301
+ ${updateGrantSql}
302
+
303
+ drop policy if exists ${quoteIdentifier(`${profileModel.table}_select_own`)} on public.${table};
304
+ create policy ${quoteIdentifier(`${profileModel.table}_select_own`)}
305
+ on public.${table}
306
+ for select
307
+ to authenticated
308
+ using ((select auth.uid()) = id);
309
+
310
+ drop policy if exists ${quoteIdentifier(`${profileModel.table}_update_own`)} on public.${table};
311
+ create policy ${quoteIdentifier(`${profileModel.table}_update_own`)}
312
+ on public.${table}
313
+ for update
314
+ to authenticated
315
+ using ((select auth.uid()) = id)
316
+ with check ((select auth.uid()) = id);
317
+ ${triggerSql}
318
+ insert into ankhorage_internal.generated_schema_state (
319
+ artifact_key,
320
+ table_name,
321
+ content_hash,
322
+ applied_at
323
+ )
324
+ values (
325
+ '${PROFILE_ARTIFACT_KEY}',
326
+ '${escapeSqlLiteral(profileModel.table)}',
327
+ '${profileModel.desiredStateHash}',
328
+ now()
329
+ )
330
+ on conflict (artifact_key)
331
+ do update set
332
+ table_name = excluded.table_name,
333
+ content_hash = excluded.content_hash,
334
+ applied_at = excluded.applied_at;
335
+
336
+ commit;
337
+ `;
338
+ }
339
+ function mapProfileFieldToColumn(field) {
340
+ switch (field) {
341
+ case 'email':
342
+ return { field, column: 'email', sqlType: 'text', fromNewUser: 'new.email' };
343
+ case 'displayName':
344
+ return {
345
+ field,
346
+ column: 'display_name',
347
+ sqlType: 'text',
348
+ fromNewUser: "coalesce(new.raw_user_meta_data->>'displayName', new.raw_user_meta_data->>'display_name', split_part(new.email, '@', 1))",
349
+ };
350
+ case 'firstName':
351
+ return {
352
+ field,
353
+ column: 'first_name',
354
+ sqlType: 'text',
355
+ fromNewUser: "coalesce(new.raw_user_meta_data->>'firstName', new.raw_user_meta_data->>'first_name')",
356
+ };
357
+ case 'lastName':
358
+ return {
359
+ field,
360
+ column: 'last_name',
361
+ sqlType: 'text',
362
+ fromNewUser: "coalesce(new.raw_user_meta_data->>'lastName', new.raw_user_meta_data->>'last_name')",
363
+ };
364
+ case 'avatarUrl':
365
+ return {
366
+ field,
367
+ column: 'avatar_url',
368
+ sqlType: 'text',
369
+ fromNewUser: "coalesce(new.raw_user_meta_data->>'avatarUrl', new.raw_user_meta_data->>'avatar_url')",
370
+ };
371
+ case 'username':
372
+ return {
373
+ field,
374
+ column: 'username',
375
+ sqlType: 'text',
376
+ fromNewUser: "new.raw_user_meta_data->>'username'",
377
+ };
378
+ case 'phone':
379
+ return { field, column: 'phone', sqlType: 'text', fromNewUser: 'new.phone' };
380
+ default:
381
+ return null;
382
+ }
383
+ }
384
+ function quoteIdentifier(value) {
385
+ return `"${value.replaceAll('"', '""')}"`;
386
+ }
387
+ function getProfileDesiredStateHash(args) {
388
+ const state = {
389
+ artifactKey: PROFILE_ARTIFACT_KEY,
390
+ table: args.table,
391
+ primaryKey: args.primaryKey,
392
+ createStrategy: args.createStrategy,
393
+ updateStrategy: args.updateStrategy,
394
+ managedColumns: args.columns.map((column) => ({
395
+ field: column.field,
396
+ column: column.column,
397
+ sqlType: column.sqlType,
398
+ })),
399
+ policyVersion: PROFILE_POLICY_VERSION,
400
+ reconcilerVersion: PROFILE_RECONCILER_VERSION,
401
+ };
402
+ return createHash('sha256').update(JSON.stringify(state)).digest('hex');
403
+ }
404
+ function resolveProfileColumns(fields) {
405
+ const columns = new Map();
406
+ for (const field of fields) {
407
+ const column = mapProfileFieldToColumn(field);
408
+ if (!column) {
409
+ throw new Error(`Unsupported Supabase profile field "${field}". Supported fields: ${getSupportedProfileFields().join(', ')}.`);
410
+ }
411
+ if (columns.has(column.column)) {
412
+ continue;
413
+ }
414
+ columns.set(column.column, column);
415
+ }
416
+ return [...columns.values()].sort((left, right) => MANAGED_PROFILE_COLUMNS.indexOf(left.column) -
417
+ MANAGED_PROFILE_COLUMNS.indexOf(right.column));
418
+ }
419
+ function normalizeFieldList(values) {
420
+ const source = values ?? DEFAULT_PROFILE_FIELDS;
421
+ const next = [];
422
+ for (const value of source) {
423
+ const normalized = value.trim();
424
+ if (!normalized) {
425
+ throw new Error('Invalid Supabase profile field "". Profile fields must be non-empty.');
426
+ }
427
+ if (next.includes(normalized)) {
428
+ continue;
429
+ }
430
+ next.push(normalized);
431
+ }
432
+ return next;
433
+ }
434
+ function getSupportedProfileFields() {
435
+ return ['email', 'displayName', 'firstName', 'lastName', 'avatarUrl', 'username', 'phone'];
436
+ }
437
+ function normalizeIdentifier(value) {
438
+ const trimmed = value.trim();
439
+ if (!trimmed)
440
+ return '';
441
+ if (trimmed === 'users') {
442
+ throw new Error('Invalid Supabase profile table identifier "users". public.users is reserved for identity conflict detection; use a dedicated profile table such as "profiles".');
443
+ }
444
+ if (!/^[a-z_][a-z0-9_]*$/.test(trimmed)) {
445
+ throw new Error(`Invalid Supabase profile table identifier "${trimmed}". Use snake_case letters, numbers, and underscores.`);
446
+ }
447
+ return trimmed;
448
+ }
449
+ function escapeSqlLiteral(value) {
450
+ return value.replaceAll("'", "''");
451
+ }
452
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../../../../src/adapters/minikube/auth/supabase/profile.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { InfraManifestInput } from '../../../../types';\n\nconst DEFAULT_PROFILE_FIELDS = ['email', 'firstName', 'lastName'];\nconst DEFAULT_PROFILE_PRIMARY_KEY = 'authUserId';\nconst DEFAULT_PROFILE_CREATE_STRATEGY = 'trigger';\nconst DEFAULT_PROFILE_UPDATE_STRATEGY = 'api';\nconst PROFILE_POLICY_VERSION = 1;\nconst PROFILE_RECONCILER_VERSION = 1;\nconst PROFILE_ARTIFACT_KEY = 'auth.profile';\n\nexport const MANAGED_PROFILE_COLUMNS = [\n 'email',\n 'display_name',\n 'first_name',\n 'last_name',\n 'avatar_url',\n 'username',\n 'phone',\n] as const;\n\ninterface ProfileColumnSpec {\n readonly field: string;\n readonly column: string;\n readonly sqlType: string;\n readonly fromNewUser: string;\n}\n\nexport interface ResolvedProfileModel {\n readonly enabled: boolean;\n readonly table: string;\n readonly primaryKey: string;\n readonly createStrategy: string;\n readonly updateStrategy: string;\n readonly columns: readonly ProfileColumnSpec[];\n readonly desiredStateHash: string;\n}\n\nexport function resolveSupabaseProfileModel(manifest: InfraManifestInput): ResolvedProfileModel {\n const table = normalizeIdentifier(manifest.auth?.profile?.table ?? '');\n const fields = normalizeFieldList(manifest.auth?.profile?.fields);\n const primaryKey = manifest.auth?.profile?.primaryKey ?? DEFAULT_PROFILE_PRIMARY_KEY;\n const createStrategy = manifest.auth?.profile?.createStrategy ?? DEFAULT_PROFILE_CREATE_STRATEGY;\n const updateStrategy = manifest.auth?.profile?.updateStrategy ?? DEFAULT_PROFILE_UPDATE_STRATEGY;\n const columns = resolveProfileColumns(fields);\n\n const hash = table\n ? getProfileDesiredStateHash({\n table,\n primaryKey,\n createStrategy,\n updateStrategy,\n columns,\n })\n : '';\n\n return {\n enabled: table.length > 0,\n table,\n primaryKey,\n createStrategy,\n updateStrategy,\n columns,\n desiredStateHash: hash,\n };\n}\n\nexport function getSupabaseProfileReconciliation(profileModel: ResolvedProfileModel): string {\n const table = quoteIdentifier(profileModel.table);\n const functionName = quoteIdentifier(`handle_new_${profileModel.table}_user`);\n const triggerName = quoteIdentifier(`on_auth_user_created_${profileModel.table}`);\n const configuredColumns = new Set(profileModel.columns.map((column) => column.column));\n const addColumnSql = profileModel.columns\n .map(\n (column) =>\n `alter table public.${table} add column if not exists ${quoteIdentifier(column.column)} ${column.sqlType};`,\n )\n .join('\\n');\n const dropStaleManagedColumnSql = MANAGED_PROFILE_COLUMNS.filter(\n (column) => !configuredColumns.has(column),\n )\n .map(\n (column) => `alter table public.${table} drop column if exists ${quoteIdentifier(column)};`,\n )\n .join('\\n');\n const insertColumns = ['id', ...profileModel.columns.map((column) => column.column)]\n .map(quoteIdentifier)\n .join(', ');\n const insertValues = ['new.id', ...profileModel.columns.map((column) => column.fromNewUser)].join(\n ', ',\n );\n const updateAssignments = profileModel.columns\n .filter((column) => column.field === 'email')\n .map(\n (column) => `${quoteIdentifier(column.column)} = excluded.${quoteIdentifier(column.column)}`,\n );\n const conflictUpdate = [...updateAssignments, 'updated_at = now()'].join(',\\n ');\n const updateGrantColumns = profileModel.columns.map((column) => quoteIdentifier(column.column));\n const updateGrantSql =\n updateGrantColumns.length > 0\n ? `grant update (${updateGrantColumns.join(', ')}) on table public.${table} to authenticated;`\n : '';\n const triggerSql =\n profileModel.createStrategy === 'trigger'\n ? `\ncreate or replace function public.${functionName}()\nreturns trigger\nlanguage plpgsql\nsecurity definer\nset search_path = pg_catalog, pg_temp\nas $$\nbegin\n insert into public.${table} (${insertColumns})\n values (${insertValues})\n on conflict (id) do update set\n ${conflictUpdate};\n\n return new;\nend;\n$$;\n\nrevoke execute on function public.${functionName}() from PUBLIC;\nrevoke execute on function public.${functionName}() from anon;\nrevoke execute on function public.${functionName}() from authenticated;\n\ndrop trigger if exists ${triggerName} on auth.users;\ncreate trigger ${triggerName}\n after insert on auth.users\n for each row execute function public.${functionName}();\n`\n : `\ndrop trigger if exists ${triggerName} on auth.users;\ndrop function if exists public.${functionName}();\n`;\n\n return `-- Generated by @ankhorage/infra from manifest.auth.profile.\n-- Desired-state reconciliation for local Supabase; not a migration-history entry.\n\nbegin;\n\ncreate schema if not exists ankhorage_internal;\n\ncreate table if not exists ankhorage_internal.generated_schema_state (\n artifact_key text primary key,\n table_name text,\n content_hash text not null,\n applied_at timestamptz not null\n);\n\nalter table ankhorage_internal.generated_schema_state\n add column if not exists table_name text;\n\ndo $$\ndeclare\n previous_table_name text;\n has_previous_state boolean;\nbegin\n select table_name, true\n into previous_table_name, has_previous_state\n from ankhorage_internal.generated_schema_state\n where artifact_key = '${PROFILE_ARTIFACT_KEY}';\n\n if has_previous_state and previous_table_name is null then\n raise exception 'existing auth.profile generated state is missing table identity; reset local Supabase or add an explicit cleanup migration before applying generated profile reconciliation';\n end if;\n\n if has_previous_state and previous_table_name <> '${escapeSqlLiteral(profileModel.table)}' then\n raise exception 'profile.table changed from % to %; reset local Supabase or add an explicit cleanup migration before applying generated profile reconciliation', previous_table_name, '${escapeSqlLiteral(profileModel.table)}';\n end if;\nend;\n$$;\n\ncreate table if not exists public.${table} (\n id uuid primary key references auth.users(id) on delete cascade,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now()\n);\n\nalter table public.${table} add column if not exists id uuid;\nalter table public.${table} add column if not exists created_at timestamptz not null default now();\nalter table public.${table} add column if not exists updated_at timestamptz not null default now();\n${addColumnSql}\nalter table public.${table} drop column if exists role;\n${dropStaleManagedColumnSql}\n\ndo $$\nbegin\n if exists (\n select 1\n from pg_constraint c\n where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and c.contype = 'p'\n and c.conkey <> array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n ) then\n raise exception 'profile table public.${escapeSqlLiteral(profileModel.table)} has a primary key that is not exactly id';\n end if;\n\n if not exists (\n select 1\n from pg_constraint\n where conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and contype = 'p'\n ) then\n alter table public.${table}\n add constraint ${quoteIdentifier(`${profileModel.table}_pkey`)} primary key (id);\n end if;\nend;\n$$;\n\ndo $$\nbegin\n if exists (\n select 1\n from pg_constraint c\n where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and c.contype = 'f'\n and c.confrelid = 'auth.users'::regclass\n and c.conkey && array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n and (\n c.confdeltype <> 'c'\n or c.conkey <> array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n or c.confkey <> array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'auth.users'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n )\n ) then\n raise exception 'profile table public.${escapeSqlLiteral(profileModel.table)} has an auth.users foreign key that is not exactly id -> auth.users(id) on delete cascade';\n end if;\n\n if not exists (\n select 1\n from pg_constraint c\n where c.conrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and c.confrelid = 'auth.users'::regclass\n and c.contype = 'f'\n and c.confdeltype = 'c'\n and c.conkey = array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'public.${escapeSqlLiteral(profileModel.table)}'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n and c.confkey = array[\n (\n select a.attnum\n from pg_attribute a\n where a.attrelid = 'auth.users'::regclass\n and a.attname = 'id'\n and not a.attisdropped\n )\n ]::smallint[]\n ) then\n alter table public.${table}\n add constraint ${quoteIdentifier(`${profileModel.table}_id_auth_users_fkey`)}\n foreign key (id) references auth.users(id) on delete cascade;\n end if;\nend;\n$$;\n\ndo $$\nbegin\n if not exists (\n select 1\n from information_schema.columns c\n where c.table_schema = 'public'\n and c.table_name = '${escapeSqlLiteral(profileModel.table)}'\n and c.column_name = 'id'\n and c.udt_name = 'uuid'\n and c.is_nullable = 'NO'\n ) then\n raise exception 'profile id column must be uuid not null';\n end if;\n\n if not exists (\n select 1\n from information_schema.columns c\n where c.table_schema = 'public'\n and c.table_name = '${escapeSqlLiteral(profileModel.table)}'\n and c.column_name = 'created_at'\n and c.data_type = 'timestamp with time zone'\n and c.is_nullable = 'NO'\n and c.column_default = 'now()'\n ) then\n raise exception 'profile created_at column must be timestamptz not null default now()';\n end if;\n\n if not exists (\n select 1\n from information_schema.columns c\n where c.table_schema = 'public'\n and c.table_name = '${escapeSqlLiteral(profileModel.table)}'\n and c.column_name = 'updated_at'\n and c.data_type = 'timestamp with time zone'\n and c.is_nullable = 'NO'\n and c.column_default = 'now()'\n ) then\n raise exception 'profile updated_at column must be timestamptz not null default now()';\n end if;\nend;\n$$;\n\nalter table public.${table} enable row level security;\n\nrevoke all on table public.${table} from anon, authenticated;\ngrant select on table public.${table} to authenticated;\n${updateGrantSql}\n\ndrop policy if exists ${quoteIdentifier(`${profileModel.table}_select_own`)} on public.${table};\ncreate policy ${quoteIdentifier(`${profileModel.table}_select_own`)}\n on public.${table}\n for select\n to authenticated\n using ((select auth.uid()) = id);\n\ndrop policy if exists ${quoteIdentifier(`${profileModel.table}_update_own`)} on public.${table};\ncreate policy ${quoteIdentifier(`${profileModel.table}_update_own`)}\n on public.${table}\n for update\n to authenticated\n using ((select auth.uid()) = id)\n with check ((select auth.uid()) = id);\n${triggerSql}\ninsert into ankhorage_internal.generated_schema_state (\n artifact_key,\n table_name,\n content_hash,\n applied_at\n)\nvalues (\n '${PROFILE_ARTIFACT_KEY}',\n '${escapeSqlLiteral(profileModel.table)}',\n '${profileModel.desiredStateHash}',\n now()\n)\non conflict (artifact_key)\ndo update set\n table_name = excluded.table_name,\n content_hash = excluded.content_hash,\n applied_at = excluded.applied_at;\n\ncommit;\n`;\n}\n\nfunction mapProfileFieldToColumn(field: string): ProfileColumnSpec | null {\n switch (field) {\n case 'email':\n return { field, column: 'email', sqlType: 'text', fromNewUser: 'new.email' };\n case 'displayName':\n return {\n field,\n column: 'display_name',\n sqlType: 'text',\n fromNewUser:\n \"coalesce(new.raw_user_meta_data->>'displayName', new.raw_user_meta_data->>'display_name', split_part(new.email, '@', 1))\",\n };\n case 'firstName':\n return {\n field,\n column: 'first_name',\n sqlType: 'text',\n fromNewUser:\n \"coalesce(new.raw_user_meta_data->>'firstName', new.raw_user_meta_data->>'first_name')\",\n };\n case 'lastName':\n return {\n field,\n column: 'last_name',\n sqlType: 'text',\n fromNewUser:\n \"coalesce(new.raw_user_meta_data->>'lastName', new.raw_user_meta_data->>'last_name')\",\n };\n case 'avatarUrl':\n return {\n field,\n column: 'avatar_url',\n sqlType: 'text',\n fromNewUser:\n \"coalesce(new.raw_user_meta_data->>'avatarUrl', new.raw_user_meta_data->>'avatar_url')\",\n };\n case 'username':\n return {\n field,\n column: 'username',\n sqlType: 'text',\n fromNewUser: \"new.raw_user_meta_data->>'username'\",\n };\n case 'phone':\n return { field, column: 'phone', sqlType: 'text', fromNewUser: 'new.phone' };\n default:\n return null;\n }\n}\n\nfunction quoteIdentifier(value: string): string {\n return `\"${value.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction getProfileDesiredStateHash(args: {\n readonly table: string;\n readonly primaryKey: string;\n readonly createStrategy: string;\n readonly updateStrategy: string;\n readonly columns: readonly ProfileColumnSpec[];\n}): string {\n const state = {\n artifactKey: PROFILE_ARTIFACT_KEY,\n table: args.table,\n primaryKey: args.primaryKey,\n createStrategy: args.createStrategy,\n updateStrategy: args.updateStrategy,\n managedColumns: args.columns.map((column) => ({\n field: column.field,\n column: column.column,\n sqlType: column.sqlType,\n })),\n policyVersion: PROFILE_POLICY_VERSION,\n reconcilerVersion: PROFILE_RECONCILER_VERSION,\n };\n\n return createHash('sha256').update(JSON.stringify(state)).digest('hex');\n}\n\nfunction resolveProfileColumns(fields: readonly string[]): ProfileColumnSpec[] {\n const columns = new Map<string, ProfileColumnSpec>();\n\n for (const field of fields) {\n const column = mapProfileFieldToColumn(field);\n if (!column) {\n throw new Error(\n `Unsupported Supabase profile field \"${field}\". Supported fields: ${getSupportedProfileFields().join(', ')}.`,\n );\n }\n\n if (columns.has(column.column)) {\n continue;\n }\n columns.set(column.column, column);\n }\n\n return [...columns.values()].sort(\n (left, right) =>\n MANAGED_PROFILE_COLUMNS.indexOf(left.column as (typeof MANAGED_PROFILE_COLUMNS)[number]) -\n MANAGED_PROFILE_COLUMNS.indexOf(right.column as (typeof MANAGED_PROFILE_COLUMNS)[number]),\n );\n}\n\nfunction normalizeFieldList(values: readonly string[] | undefined): string[] {\n const source = values ?? DEFAULT_PROFILE_FIELDS;\n const next: string[] = [];\n\n for (const value of source) {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error('Invalid Supabase profile field \"\". Profile fields must be non-empty.');\n }\n\n if (next.includes(normalized)) {\n continue;\n }\n next.push(normalized);\n }\n\n return next;\n}\n\nfunction getSupportedProfileFields(): string[] {\n return ['email', 'displayName', 'firstName', 'lastName', 'avatarUrl', 'username', 'phone'];\n}\n\nfunction normalizeIdentifier(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return '';\n\n if (trimmed === 'users') {\n throw new Error(\n 'Invalid Supabase profile table identifier \"users\". public.users is reserved for identity conflict detection; use a dedicated profile table such as \"profiles\".',\n );\n }\n\n if (!/^[a-z_][a-z0-9_]*$/.test(trimmed)) {\n throw new Error(\n `Invalid Supabase profile table identifier \"${trimmed}\". Use snake_case letters, numbers, and underscores.`,\n );\n }\n\n return trimmed;\n}\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replaceAll(\"'\", \"''\");\n}\n"]}
@@ -4,6 +4,6 @@ import type { MinikubeAdapterArtifacts } from '../../contracts';
4
4
  export declare function generateCerbosAuthzArtifacts(args: {
5
5
  manifest: InfraManifestInput;
6
6
  namespace: string;
7
- appManifest?: Pick<AppManifest, 'metadata' | 'navigator' | 'screens' | 'settings'>;
7
+ appManifest?: Pick<AppManifest, 'infra' | 'metadata' | 'navigator' | 'screens'>;
8
8
  }): MinikubeAdapterArtifacts;
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/minikube/authz/cerbos/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkC,MAAM,sBAAsB,CAAC;AAExF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;CACpF,GAAG,wBAAwB,CAmC3B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/adapters/minikube/authz/cerbos/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkC,MAAM,sBAAsB,CAAC;AAGxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC,CAAC;CACjF,GAAG,wBAAwB,CAmC3B"}
@@ -1,3 +1,4 @@
1
+ import { resolveAuthFlow } from '@ankhorage/contracts/auth';
1
2
  export function generateCerbosAuthzArtifacts(args) {
2
3
  const { manifest, namespace, appManifest } = args;
3
4
  const root = 'infra/minikube/k8s/authz/cerbos';
@@ -239,9 +240,10 @@ function buildCerbosPolicyIntent(args) {
239
240
  const { manifest, appManifest } = args;
240
241
  const authScope = manifest.auth?.scope ?? 'none';
241
242
  const routes = appManifest?.navigator ? flattenNavigatorRoutes(appManifest.navigator) : [];
242
- const signInRoute = appManifest?.settings.authFlow.signInRoute;
243
- const signUpRoute = appManifest?.settings.authFlow.signUpRoute;
244
- const unauthorizedRoute = appManifest?.settings.authFlow.unauthorizedRoute;
243
+ const flow = resolveAuthFlow(appManifest?.infra.auth?.flow);
244
+ const { signInRoute } = flow;
245
+ const { signUpRoute } = flow;
246
+ const { unauthorizedRoute } = flow;
245
247
  const publicRouteSet = new Set();
246
248
  const protectedRouteSet = new Set();
247
249
  for (const route of routes) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/adapters/minikube/authz/cerbos/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppManifest, NavigatorSpec, RouteDefinition } from '@ankhorage/contracts';\n\nimport type { InfraManifestInput } from '../../../../types';\nimport type { MinikubeAdapterArtifacts } from '../../contracts';\n\nexport function generateCerbosAuthzArtifacts(args: {\n manifest: InfraManifestInput;\n namespace: string;\n appManifest?: Pick<AppManifest, 'metadata' | 'navigator' | 'screens' | 'settings'>;\n}): MinikubeAdapterArtifacts {\n const { manifest, namespace, appManifest } = args;\n\n const root = 'infra/minikube/k8s/authz/cerbos';\n const resourceRoot = 'authz/cerbos';\n const intent = buildCerbosPolicyIntent({ manifest, appManifest });\n\n return {\n files: [\n {\n path: `${root}/cerbos.configmap.yaml`,\n content: getCerbosConfigMap(namespace),\n },\n {\n path: `${root}/cerbos.policy.configmap.yaml`,\n content: getCerbosPolicyConfigMap({ namespace, intent }),\n },\n {\n path: `${root}/cerbos.deployment.yaml`,\n content: getCerbosDeployment(namespace),\n },\n {\n path: `${root}/cerbos.service.yaml`,\n content: getCerbosService(namespace),\n },\n ],\n resources: [\n `${resourceRoot}/cerbos.configmap.yaml`,\n `${resourceRoot}/cerbos.policy.configmap.yaml`,\n `${resourceRoot}/cerbos.deployment.yaml`,\n `${resourceRoot}/cerbos.service.yaml`,\n ],\n envEntries: ['CERBOS_URL=http://cerbos:3592'],\n warnings: [],\n };\n}\n\nfunction getCerbosConfigMap(namespace: string) {\n return `apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cerbos-config\n namespace: ${namespace}\ndata:\n config.yaml: |\n server:\n httpListenAddr: \":3592\"\n storage:\n driver: \"disk\"\n disk:\n directory: /policies\n`;\n}\n\nfunction getCerbosPolicyConfigMap(args: { namespace: string; intent: CerbosPolicyIntent }) {\n const { namespace, intent } = args;\n const entries = getCerbosPolicyEntries(intent);\n const renderedEntries = entries\n .map((entry) => {\n const indented = indentLines(entry.content, 4);\n return ` ${entry.key}: |\n${indented}`;\n })\n .join('\\n');\n\n return `apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cerbos-policy\n namespace: ${namespace}\ndata:\n${renderedEntries}\n`;\n}\n\nfunction getCerbosDeployment(namespace: string) {\n return `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: cerbos\n namespace: ${namespace}\n labels:\n app.kubernetes.io/name: cerbos\nspec:\n replicas: 1\n selector:\n matchLabels:\n app.kubernetes.io/name: cerbos\n template:\n metadata:\n labels:\n app.kubernetes.io/name: cerbos\n spec:\n containers:\n - name: cerbos\n image: ghcr.io/cerbos/cerbos:0.40.0\n args:\n - server\n - --config=/config/config.yaml\n ports:\n - containerPort: 3592\n volumeMounts:\n - name: cerbos-config\n mountPath: /config\n - name: cerbos-policy\n mountPath: /policies\n volumes:\n - name: cerbos-config\n configMap:\n name: cerbos-config\n - name: cerbos-policy\n configMap:\n name: cerbos-policy\n`;\n}\n\nfunction getCerbosService(namespace: string) {\n return `apiVersion: v1\nkind: Service\nmetadata:\n name: cerbos\n namespace: ${namespace}\nspec:\n selector:\n app.kubernetes.io/name: cerbos\n ports:\n - name: http\n port: 3592\n targetPort: 3592\n`;\n}\n\nconst PUBLIC_GUARD_HINTS = new Set(['public', 'guest', 'anonymous', 'unauthenticated']);\nconst AUTH_GUARD_HINTS = new Set([\n 'auth',\n 'authenticated',\n 'private',\n 'protected',\n 'requiresauth',\n 'requires-auth',\n]);\n\ntype AuthScope = NonNullable<InfraManifestInput['auth']>['scope'] | 'none';\n\ninterface CerbosRouteIntent {\n name: string;\n guards: string[];\n screenId?: string;\n}\n\ninterface CerbosPolicyIntent {\n authScope: AuthScope;\n publicRoutes: string[];\n protectedRoutes: string[];\n publicScreens: string[];\n protectedScreens: string[];\n}\n\ninterface CerbosPolicyEntry {\n key: string;\n content: string;\n}\n\nfunction getCerbosPolicyEntries(intent: CerbosPolicyIntent): CerbosPolicyEntry[] {\n const entries: CerbosPolicyEntry[] = [\n {\n key: 'app.resource_policy.yaml',\n content: getAppPolicy(intent),\n },\n ];\n\n if (intent.publicRoutes.length > 0 || intent.protectedRoutes.length > 0) {\n entries.push({\n key: 'route.resource_policy.yaml',\n content: getRoutePolicy(intent),\n });\n }\n\n if (intent.publicScreens.length > 0 || intent.protectedScreens.length > 0) {\n entries.push({\n key: 'screen.resource_policy.yaml',\n content: getScreenPolicy(intent),\n });\n }\n\n return entries;\n}\n\nfunction getAppPolicy(intent: CerbosPolicyIntent): string {\n const roles = intent.authScope === 'global' ? ['authenticated'] : ['anonymous', 'authenticated'];\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"app\"\n rules:\n${indentLines(renderRule({ actions: ['*'], roles }), 4)}\n`;\n}\n\nfunction getRoutePolicy(intent: CerbosPolicyIntent): string {\n const rules: string[] = [];\n\n if (intent.publicRoutes.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'navigate'],\n roles: ['anonymous', 'authenticated'],\n conditionExpr: getInConditionExpr('route', intent.publicRoutes),\n }),\n );\n }\n\n if (intent.protectedRoutes.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'navigate'],\n roles: ['authenticated'],\n conditionExpr: getInConditionExpr('route', intent.protectedRoutes),\n }),\n );\n }\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"route\"\n rules:\n${indentLines(rules.join('\\n'), 4)}\n`;\n}\n\nfunction getScreenPolicy(intent: CerbosPolicyIntent): string {\n const rules: string[] = [];\n\n if (intent.publicScreens.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'render'],\n roles: ['anonymous', 'authenticated'],\n conditionExpr: getInConditionExpr('screen_id', intent.publicScreens),\n }),\n );\n }\n\n if (intent.protectedScreens.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'render'],\n roles: ['authenticated'],\n conditionExpr: getInConditionExpr('screen_id', intent.protectedScreens),\n }),\n );\n }\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"screen\"\n rules:\n${indentLines(rules.join('\\n'), 4)}\n`;\n}\n\nfunction renderRule(args: { actions: string[]; roles: string[]; conditionExpr?: string }): string {\n const { actions, roles, conditionExpr } = args;\n\n const lines = [\n `- actions: ${JSON.stringify(actions)}`,\n ' effect: EFFECT_ALLOW',\n ` roles: ${JSON.stringify(roles)}`,\n ];\n\n if (conditionExpr) {\n lines.push(' condition:');\n lines.push(' match:');\n lines.push(` expr: ${JSON.stringify(conditionExpr)}`);\n }\n\n return lines.join('\\n');\n}\n\nfunction getInConditionExpr(attribute: string, values: string[]): string {\n return `request.resource.attr.${attribute} in ${JSON.stringify(values)}`;\n}\n\nfunction indentLines(content: string, spaces: number): string {\n const prefix = ' '.repeat(spaces);\n return content\n .trimEnd()\n .split('\\n')\n .map((line) => `${prefix}${line}`)\n .join('\\n');\n}\n\nfunction buildCerbosPolicyIntent(args: {\n manifest: InfraManifestInput;\n appManifest?: Pick<AppManifest, 'metadata' | 'navigator' | 'screens' | 'settings'>;\n}): CerbosPolicyIntent {\n const { manifest, appManifest } = args;\n const authScope = manifest.auth?.scope ?? 'none';\n\n const routes = appManifest?.navigator ? flattenNavigatorRoutes(appManifest.navigator) : [];\n const signInRoute = appManifest?.settings.authFlow.signInRoute;\n const signUpRoute = appManifest?.settings.authFlow.signUpRoute;\n const unauthorizedRoute = appManifest?.settings.authFlow.unauthorizedRoute;\n\n const publicRouteSet = new Set<string>();\n const protectedRouteSet = new Set<string>();\n\n for (const route of routes) {\n const isPublic = isPublicRoute({\n route,\n authScope,\n signInRoute,\n signUpRoute,\n unauthorizedRoute,\n });\n\n if (isPublic) {\n publicRouteSet.add(route.name);\n } else {\n protectedRouteSet.add(route.name);\n }\n }\n\n const screenVisibility = new Map<string, 'public' | 'protected'>();\n for (const route of routes) {\n if (!route.screenId) continue;\n const isPublic = publicRouteSet.has(route.name);\n\n const current = screenVisibility.get(route.screenId);\n if (current === 'protected') continue;\n screenVisibility.set(route.screenId, isPublic ? 'public' : 'protected');\n }\n\n if (appManifest?.screens) {\n const defaultVisibility: 'public' | 'protected' =\n authScope === 'global' ? 'protected' : 'public';\n\n for (const screenId of Object.keys(appManifest.screens)) {\n if (!screenVisibility.has(screenId)) {\n screenVisibility.set(screenId, defaultVisibility);\n }\n }\n }\n\n const publicScreens = [...screenVisibility.entries()]\n .filter(([, visibility]) => visibility === 'public')\n .map(([screenId]) => screenId)\n .sort();\n const protectedScreens = [...screenVisibility.entries()]\n .filter(([, visibility]) => visibility === 'protected')\n .map(([screenId]) => screenId)\n .sort();\n\n return {\n authScope,\n publicRoutes: [...publicRouteSet].sort(),\n protectedRoutes: [...protectedRouteSet].sort(),\n publicScreens,\n protectedScreens,\n };\n}\n\nfunction flattenNavigatorRoutes(navigator: NavigatorSpec): CerbosRouteIntent[] {\n const deduped = new Map<string, CerbosRouteIntent>();\n\n const walk = (current: NavigatorSpec) => {\n for (const route of current.routes) {\n const normalizedGuards = normalizeGuards(route.guards);\n const existing = deduped.get(route.name);\n deduped.set(route.name, {\n name: route.name,\n guards: unique([...(existing?.guards ?? []), ...normalizedGuards]),\n screenId: route.screenId ?? existing?.screenId,\n });\n\n if (route.navigator) {\n walk(route.navigator);\n }\n }\n };\n\n walk(navigator);\n return [...deduped.values()];\n}\n\nfunction normalizeGuards(guards?: RouteDefinition['guards']): string[] {\n return (guards ?? []).map((guard) => guard.trim().toLowerCase()).filter(Boolean);\n}\n\nfunction isPublicRoute(args: {\n route: CerbosRouteIntent;\n authScope: AuthScope;\n signInRoute?: string;\n signUpRoute?: string;\n unauthorizedRoute?: string;\n}): boolean {\n const { route, authScope, signInRoute, signUpRoute, unauthorizedRoute } = args;\n const guardSet = new Set(route.guards);\n const signInRouteName = authFlowPathToRouteName(signInRoute);\n const signUpRouteName = authFlowPathToRouteName(signUpRoute);\n const unauthorizedRouteName = authFlowPathToRouteName(unauthorizedRoute);\n\n if (\n route.name === signInRouteName ||\n route.name === signUpRouteName ||\n route.name === unauthorizedRouteName\n ) {\n return true;\n }\n\n if (hasAny(guardSet, PUBLIC_GUARD_HINTS)) {\n return true;\n }\n\n if (hasAny(guardSet, AUTH_GUARD_HINTS)) {\n return false;\n }\n\n switch (authScope) {\n case 'none':\n return true;\n case 'global':\n default:\n return false;\n }\n}\n\nfunction hasAny(values: Set<string>, candidates: Set<string>): boolean {\n for (const candidate of candidates) {\n if (values.has(candidate)) return true;\n }\n return false;\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction authFlowPathToRouteName(routePath: string | undefined): string | undefined {\n if (!routePath) {\n return undefined;\n }\n\n const normalized = routePath.trim().replace(/^\\/+/, '').replace(/\\/+$/, '');\n return normalized === '' ? 'index' : normalized;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/adapters/minikube/authz/cerbos/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AppManifest, NavigatorSpec, RouteDefinition } from '@ankhorage/contracts';\nimport { resolveAuthFlow } from '@ankhorage/contracts/auth';\n\nimport type { InfraManifestInput } from '../../../../types';\nimport type { MinikubeAdapterArtifacts } from '../../contracts';\n\nexport function generateCerbosAuthzArtifacts(args: {\n manifest: InfraManifestInput;\n namespace: string;\n appManifest?: Pick<AppManifest, 'infra' | 'metadata' | 'navigator' | 'screens'>;\n}): MinikubeAdapterArtifacts {\n const { manifest, namespace, appManifest } = args;\n\n const root = 'infra/minikube/k8s/authz/cerbos';\n const resourceRoot = 'authz/cerbos';\n const intent = buildCerbosPolicyIntent({ manifest, appManifest });\n\n return {\n files: [\n {\n path: `${root}/cerbos.configmap.yaml`,\n content: getCerbosConfigMap(namespace),\n },\n {\n path: `${root}/cerbos.policy.configmap.yaml`,\n content: getCerbosPolicyConfigMap({ namespace, intent }),\n },\n {\n path: `${root}/cerbos.deployment.yaml`,\n content: getCerbosDeployment(namespace),\n },\n {\n path: `${root}/cerbos.service.yaml`,\n content: getCerbosService(namespace),\n },\n ],\n resources: [\n `${resourceRoot}/cerbos.configmap.yaml`,\n `${resourceRoot}/cerbos.policy.configmap.yaml`,\n `${resourceRoot}/cerbos.deployment.yaml`,\n `${resourceRoot}/cerbos.service.yaml`,\n ],\n envEntries: ['CERBOS_URL=http://cerbos:3592'],\n warnings: [],\n };\n}\n\nfunction getCerbosConfigMap(namespace: string) {\n return `apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cerbos-config\n namespace: ${namespace}\ndata:\n config.yaml: |\n server:\n httpListenAddr: \":3592\"\n storage:\n driver: \"disk\"\n disk:\n directory: /policies\n`;\n}\n\nfunction getCerbosPolicyConfigMap(args: { namespace: string; intent: CerbosPolicyIntent }) {\n const { namespace, intent } = args;\n const entries = getCerbosPolicyEntries(intent);\n const renderedEntries = entries\n .map((entry) => {\n const indented = indentLines(entry.content, 4);\n return ` ${entry.key}: |\n${indented}`;\n })\n .join('\\n');\n\n return `apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cerbos-policy\n namespace: ${namespace}\ndata:\n${renderedEntries}\n`;\n}\n\nfunction getCerbosDeployment(namespace: string) {\n return `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: cerbos\n namespace: ${namespace}\n labels:\n app.kubernetes.io/name: cerbos\nspec:\n replicas: 1\n selector:\n matchLabels:\n app.kubernetes.io/name: cerbos\n template:\n metadata:\n labels:\n app.kubernetes.io/name: cerbos\n spec:\n containers:\n - name: cerbos\n image: ghcr.io/cerbos/cerbos:0.40.0\n args:\n - server\n - --config=/config/config.yaml\n ports:\n - containerPort: 3592\n volumeMounts:\n - name: cerbos-config\n mountPath: /config\n - name: cerbos-policy\n mountPath: /policies\n volumes:\n - name: cerbos-config\n configMap:\n name: cerbos-config\n - name: cerbos-policy\n configMap:\n name: cerbos-policy\n`;\n}\n\nfunction getCerbosService(namespace: string) {\n return `apiVersion: v1\nkind: Service\nmetadata:\n name: cerbos\n namespace: ${namespace}\nspec:\n selector:\n app.kubernetes.io/name: cerbos\n ports:\n - name: http\n port: 3592\n targetPort: 3592\n`;\n}\n\nconst PUBLIC_GUARD_HINTS = new Set(['public', 'guest', 'anonymous', 'unauthenticated']);\nconst AUTH_GUARD_HINTS = new Set([\n 'auth',\n 'authenticated',\n 'private',\n 'protected',\n 'requiresauth',\n 'requires-auth',\n]);\n\ntype AuthScope = NonNullable<InfraManifestInput['auth']>['scope'] | 'none';\n\ninterface CerbosRouteIntent {\n name: string;\n guards: string[];\n screenId?: string;\n}\n\ninterface CerbosPolicyIntent {\n authScope: AuthScope;\n publicRoutes: string[];\n protectedRoutes: string[];\n publicScreens: string[];\n protectedScreens: string[];\n}\n\ninterface CerbosPolicyEntry {\n key: string;\n content: string;\n}\n\nfunction getCerbosPolicyEntries(intent: CerbosPolicyIntent): CerbosPolicyEntry[] {\n const entries: CerbosPolicyEntry[] = [\n {\n key: 'app.resource_policy.yaml',\n content: getAppPolicy(intent),\n },\n ];\n\n if (intent.publicRoutes.length > 0 || intent.protectedRoutes.length > 0) {\n entries.push({\n key: 'route.resource_policy.yaml',\n content: getRoutePolicy(intent),\n });\n }\n\n if (intent.publicScreens.length > 0 || intent.protectedScreens.length > 0) {\n entries.push({\n key: 'screen.resource_policy.yaml',\n content: getScreenPolicy(intent),\n });\n }\n\n return entries;\n}\n\nfunction getAppPolicy(intent: CerbosPolicyIntent): string {\n const roles = intent.authScope === 'global' ? ['authenticated'] : ['anonymous', 'authenticated'];\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"app\"\n rules:\n${indentLines(renderRule({ actions: ['*'], roles }), 4)}\n`;\n}\n\nfunction getRoutePolicy(intent: CerbosPolicyIntent): string {\n const rules: string[] = [];\n\n if (intent.publicRoutes.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'navigate'],\n roles: ['anonymous', 'authenticated'],\n conditionExpr: getInConditionExpr('route', intent.publicRoutes),\n }),\n );\n }\n\n if (intent.protectedRoutes.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'navigate'],\n roles: ['authenticated'],\n conditionExpr: getInConditionExpr('route', intent.protectedRoutes),\n }),\n );\n }\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"route\"\n rules:\n${indentLines(rules.join('\\n'), 4)}\n`;\n}\n\nfunction getScreenPolicy(intent: CerbosPolicyIntent): string {\n const rules: string[] = [];\n\n if (intent.publicScreens.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'render'],\n roles: ['anonymous', 'authenticated'],\n conditionExpr: getInConditionExpr('screen_id', intent.publicScreens),\n }),\n );\n }\n\n if (intent.protectedScreens.length > 0) {\n rules.push(\n renderRule({\n actions: ['view', 'render'],\n roles: ['authenticated'],\n conditionExpr: getInConditionExpr('screen_id', intent.protectedScreens),\n }),\n );\n }\n\n return `apiVersion: api.cerbos.dev/v1\nresourcePolicy:\n version: \"default\"\n resource: \"screen\"\n rules:\n${indentLines(rules.join('\\n'), 4)}\n`;\n}\n\nfunction renderRule(args: { actions: string[]; roles: string[]; conditionExpr?: string }): string {\n const { actions, roles, conditionExpr } = args;\n\n const lines = [\n `- actions: ${JSON.stringify(actions)}`,\n ' effect: EFFECT_ALLOW',\n ` roles: ${JSON.stringify(roles)}`,\n ];\n\n if (conditionExpr) {\n lines.push(' condition:');\n lines.push(' match:');\n lines.push(` expr: ${JSON.stringify(conditionExpr)}`);\n }\n\n return lines.join('\\n');\n}\n\nfunction getInConditionExpr(attribute: string, values: string[]): string {\n return `request.resource.attr.${attribute} in ${JSON.stringify(values)}`;\n}\n\nfunction indentLines(content: string, spaces: number): string {\n const prefix = ' '.repeat(spaces);\n return content\n .trimEnd()\n .split('\\n')\n .map((line) => `${prefix}${line}`)\n .join('\\n');\n}\n\nfunction buildCerbosPolicyIntent(args: {\n manifest: InfraManifestInput;\n appManifest?: Pick<AppManifest, 'infra' | 'metadata' | 'navigator' | 'screens'>;\n}): CerbosPolicyIntent {\n const { manifest, appManifest } = args;\n const authScope = manifest.auth?.scope ?? 'none';\n\n const routes = appManifest?.navigator ? flattenNavigatorRoutes(appManifest.navigator) : [];\n const flow = resolveAuthFlow(appManifest?.infra.auth?.flow);\n const { signInRoute } = flow;\n const { signUpRoute } = flow;\n const { unauthorizedRoute } = flow;\n\n const publicRouteSet = new Set<string>();\n const protectedRouteSet = new Set<string>();\n\n for (const route of routes) {\n const isPublic = isPublicRoute({\n route,\n authScope,\n signInRoute,\n signUpRoute,\n unauthorizedRoute,\n });\n\n if (isPublic) {\n publicRouteSet.add(route.name);\n } else {\n protectedRouteSet.add(route.name);\n }\n }\n\n const screenVisibility = new Map<string, 'public' | 'protected'>();\n for (const route of routes) {\n if (!route.screenId) continue;\n const isPublic = publicRouteSet.has(route.name);\n\n const current = screenVisibility.get(route.screenId);\n if (current === 'protected') continue;\n screenVisibility.set(route.screenId, isPublic ? 'public' : 'protected');\n }\n\n if (appManifest?.screens) {\n const defaultVisibility: 'public' | 'protected' =\n authScope === 'global' ? 'protected' : 'public';\n\n for (const screenId of Object.keys(appManifest.screens)) {\n if (!screenVisibility.has(screenId)) {\n screenVisibility.set(screenId, defaultVisibility);\n }\n }\n }\n\n const publicScreens = [...screenVisibility.entries()]\n .filter(([, visibility]) => visibility === 'public')\n .map(([screenId]) => screenId)\n .sort();\n const protectedScreens = [...screenVisibility.entries()]\n .filter(([, visibility]) => visibility === 'protected')\n .map(([screenId]) => screenId)\n .sort();\n\n return {\n authScope,\n publicRoutes: [...publicRouteSet].sort(),\n protectedRoutes: [...protectedRouteSet].sort(),\n publicScreens,\n protectedScreens,\n };\n}\n\nfunction flattenNavigatorRoutes(navigator: NavigatorSpec): CerbosRouteIntent[] {\n const deduped = new Map<string, CerbosRouteIntent>();\n\n const walk = (current: NavigatorSpec) => {\n for (const route of current.routes) {\n const normalizedGuards = normalizeGuards(route.guards);\n const existing = deduped.get(route.name);\n deduped.set(route.name, {\n name: route.name,\n guards: unique([...(existing?.guards ?? []), ...normalizedGuards]),\n screenId: route.screenId ?? existing?.screenId,\n });\n\n if (route.navigator) {\n walk(route.navigator);\n }\n }\n };\n\n walk(navigator);\n return [...deduped.values()];\n}\n\nfunction normalizeGuards(guards?: RouteDefinition['guards']): string[] {\n return (guards ?? []).map((guard) => guard.trim().toLowerCase()).filter(Boolean);\n}\n\nfunction isPublicRoute(args: {\n route: CerbosRouteIntent;\n authScope: AuthScope;\n signInRoute?: string;\n signUpRoute?: string;\n unauthorizedRoute?: string;\n}): boolean {\n const { route, authScope, signInRoute, signUpRoute, unauthorizedRoute } = args;\n const guardSet = new Set(route.guards);\n const signInRouteName = authFlowPathToRouteName(signInRoute);\n const signUpRouteName = authFlowPathToRouteName(signUpRoute);\n const unauthorizedRouteName = authFlowPathToRouteName(unauthorizedRoute);\n\n if (\n route.name === signInRouteName ||\n route.name === signUpRouteName ||\n route.name === unauthorizedRouteName\n ) {\n return true;\n }\n\n if (hasAny(guardSet, PUBLIC_GUARD_HINTS)) {\n return true;\n }\n\n if (hasAny(guardSet, AUTH_GUARD_HINTS)) {\n return false;\n }\n\n switch (authScope) {\n case 'none':\n return true;\n case 'global':\n default:\n return false;\n }\n}\n\nfunction hasAny(values: Set<string>, candidates: Set<string>): boolean {\n for (const candidate of candidates) {\n if (values.has(candidate)) return true;\n }\n return false;\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction authFlowPathToRouteName(routePath: string | undefined): string | undefined {\n if (!routePath) {\n return undefined;\n }\n\n const normalized = routePath.trim().replace(/^\\/+/, '').replace(/\\/+$/, '');\n return normalized === '' ? 'index' : normalized;\n}\n"]}