okengine 0.15.2 → 0.16.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/package.json +1 -1
- package/site/content/docs/elements/flow.mdx +6 -0
- package/site/content/docs/elements/gate.mdx +55 -0
- package/site/content/docs/elements/store.mdx +13 -15
- package/site/content/docs/elements/vault.mdx +3 -0
- package/site/content/docs/reference/client.mdx +3 -0
- package/site/content/docs/reference/fx.mdx +29 -18
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +254 -0
- package/src/auth/api-keys.ts +273 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/config.ts +5 -5
- package/src/auth/index.ts +25 -1
- package/src/auth/schema.ts +8 -0
- package/src/cli/attach-host-console.test.ts +37 -0
- package/src/cli/attach-host-console.ts +6 -0
- package/src/compiler/effects-infer.ts +17 -2
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +1 -0
- package/src/console/server/dev-identities.test.ts +127 -0
- package/src/console/server/dev-identities.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +21 -4
- package/src/console/server/flows.ts +113 -58
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +8 -21
- package/src/console/server/index.ts +1 -0
- package/src/console/server/live.test.ts +26 -1
- package/src/console/server/live.ts +4 -2
- package/src/console/server/operator-db.ts +2 -0
- package/src/console/server/state.ts +29 -55
- package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-C0X1fbWF.js → agent-disclosure-BP0Y0Sux.js} +1 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CS_O8L-r.js → highlighted-json-Awq7gYdu.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
- package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
- package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-BLbZhzyF.js → use-vault-list-uk4WVboC.js} +1 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
- package/src/console/ui-next/dist/index.html +11 -9
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +167 -0
- package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
- package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
- package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
- package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
- package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
- package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
- package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
- package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
- package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
- package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
- package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
- package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
- package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
- package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
- package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
- package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
- package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
- package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
- package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
- package/src/console/ui-next/src/lib/document-meta.ts +2 -1
- package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
- package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
- package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
- package/src/console/ui-next/src/lib/shortcut.ts +3 -0
- package/src/console/ui-next/src/router.tsx +10 -1
- package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
- package/src/console/ui-next/ui-next-seed-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/elements/gate/declare.ts +2 -0
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/runtime.ts +14 -0
- package/src/elements/store/schema-decl.ts +99 -41
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +54 -32
- package/src/elements/store.ts +3 -0
- package/src/index.ts +1 -0
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app.ts +14 -2
- package/src/kernel/auth-resolve.ts +10 -5
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +198 -0
- package/src/kernel/fx.test.ts +17 -1
- package/src/kernel/fx.ts +36 -18
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline.ts +4 -2
- package/src/manifest/types.ts +9 -3
- package/src/release/build-lib.ts +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-DEDCl_j3.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-3ptDzXbz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-BvYrygyO.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +0 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +0 -66
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +0 -2
- package/src/console/ui-next/dist/assets/link-COeyggt-.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +0 -4
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +0 -54
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +0 -2
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default invoke-as identities — ten rungs from owner to guest.
|
|
3
|
+
*
|
|
4
|
+
* `user_demo` (owner) and `user_member` keep their historic ids so Call API
|
|
5
|
+
* tests and Access fixtures stay stable. Owner scopes follow the Manifest;
|
|
6
|
+
* lower rungs intersect a named profile with that catalog. Keel (`app ===
|
|
7
|
+
* "keel"`) always unions {@link KEEL_SCOPES} so seeding is not missing a
|
|
8
|
+
* declared Gate. No leftover app catalog is invented for other apps.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { setRoleGrants, upsertRole, type RoleStore } from "../../auth/index.ts";
|
|
12
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every Keel user-plane scope — `gate.policy("member")` plus `gate.scope`.
|
|
16
|
+
* Seeded onto the owner → guest ladder when the Manifest app is `keel`.
|
|
17
|
+
*/
|
|
18
|
+
export const KEEL_SCOPES = [
|
|
19
|
+
"member",
|
|
20
|
+
"task:write",
|
|
21
|
+
"comment:write",
|
|
22
|
+
"files:write",
|
|
23
|
+
"project:admin",
|
|
24
|
+
"member:admin",
|
|
25
|
+
"webhook:admin",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/** One seeded development identity. */
|
|
29
|
+
export interface SeededDevIdentity {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly email: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly status: "active";
|
|
34
|
+
readonly scopes: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Stable person for one authorization rung. */
|
|
38
|
+
export interface DevIdentityPerson {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly email: string;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
/** Workspace / Access role label, owner → guest. */
|
|
43
|
+
readonly role: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Ten people, owner → guest. Demo and Member keep historic ids.
|
|
48
|
+
*/
|
|
49
|
+
export const DEV_IDENTITY_LADDER = [
|
|
50
|
+
{ id: "user_demo", email: "demo@example.com", name: "Demo User", role: "owner" },
|
|
51
|
+
{ id: "user_admin", email: "admin@example.com", name: "Admin", role: "admin" },
|
|
52
|
+
{
|
|
53
|
+
id: "user_workspace",
|
|
54
|
+
email: "workspace@example.com",
|
|
55
|
+
name: "Workspace Admin",
|
|
56
|
+
role: "workspace_admin",
|
|
57
|
+
},
|
|
58
|
+
{ id: "user_pm", email: "pm@example.com", name: "Project Manager", role: "project_manager" },
|
|
59
|
+
{ id: "user_lead", email: "lead@example.com", name: "Lead", role: "lead" },
|
|
60
|
+
{ id: "user_dev", email: "developer@example.com", name: "Developer", role: "developer" },
|
|
61
|
+
{ id: "user_writer", email: "writer@example.com", name: "Contributor", role: "contributor" },
|
|
62
|
+
{ id: "user_member", email: "member@example.com", name: "Member", role: "member" },
|
|
63
|
+
{ id: "user_commenter", email: "commenter@example.com", name: "Commenter", role: "commenter" },
|
|
64
|
+
{ id: "user_guest", email: "guest@example.com", name: "Guest", role: "guest" },
|
|
65
|
+
] as const satisfies readonly DevIdentityPerson[];
|
|
66
|
+
|
|
67
|
+
/** One Keel ladder role and the scopes it holds. */
|
|
68
|
+
export type KeelLadderRole = (typeof DEV_IDENTITY_LADDER)[number]["role"];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Owner → guest grants. Owner and admin hold {@link KEEL_SCOPES}.
|
|
72
|
+
*/
|
|
73
|
+
export const KEEL_LADDER_GRANTS: Readonly<Record<KeelLadderRole, readonly string[]>> = {
|
|
74
|
+
owner: KEEL_SCOPES,
|
|
75
|
+
admin: KEEL_SCOPES,
|
|
76
|
+
workspace_admin: [
|
|
77
|
+
"member",
|
|
78
|
+
"task:write",
|
|
79
|
+
"comment:write",
|
|
80
|
+
"files:write",
|
|
81
|
+
"project:admin",
|
|
82
|
+
"member:admin",
|
|
83
|
+
],
|
|
84
|
+
project_manager: ["member", "task:write", "comment:write", "files:write", "project:admin"],
|
|
85
|
+
lead: ["member", "task:write", "comment:write", "project:admin"],
|
|
86
|
+
developer: ["member", "task:write", "comment:write", "files:write"],
|
|
87
|
+
contributor: ["member", "task:write", "comment:write"],
|
|
88
|
+
member: ["member"],
|
|
89
|
+
commenter: ["member", "comment:write"],
|
|
90
|
+
guest: [],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** `*` = every catalog scope (owner). Other rungs follow {@link KEEL_LADDER_GRANTS}. */
|
|
94
|
+
const LADDER_SCOPE_PROFILES: readonly (readonly string[] | "*")[] = [
|
|
95
|
+
"*",
|
|
96
|
+
...DEV_IDENTITY_LADDER.slice(1).map((person) => KEEL_LADDER_GRANTS[person.role]),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whether this Manifest is the Keel example (full scope seed applies).
|
|
101
|
+
*
|
|
102
|
+
* @param manifest - Optional Manifest
|
|
103
|
+
*/
|
|
104
|
+
export function isKeelManifest(manifest?: Manifest | null): boolean {
|
|
105
|
+
return manifest?.app === "keel";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Application scopes implied by a Manifest — gate `scopes`, scoped gate
|
|
110
|
+
* ids, and flow gate refs. Always includes `member`.
|
|
111
|
+
*
|
|
112
|
+
* @param manifest - Optional Manifest
|
|
113
|
+
*/
|
|
114
|
+
export function demoScopesFromManifest(manifest?: Manifest | null): string[] {
|
|
115
|
+
const scopes = new Set<string>(["member"]);
|
|
116
|
+
if (isKeelManifest(manifest)) {
|
|
117
|
+
for (const scope of KEEL_SCOPES) scopes.add(scope);
|
|
118
|
+
}
|
|
119
|
+
if (!manifest) return [...scopes].sort();
|
|
120
|
+
for (const [gateId, gate] of Object.entries(manifest.gates ?? {})) {
|
|
121
|
+
for (const scope of gate.scopes ?? []) scopes.add(scope);
|
|
122
|
+
if (gateId.includes(":") && !gateId.startsWith("rate:")) scopes.add(gateId);
|
|
123
|
+
}
|
|
124
|
+
for (const flow of Object.values(manifest.flows ?? {})) {
|
|
125
|
+
for (const gate of flow.gates ?? []) {
|
|
126
|
+
if (gate.includes(":") && !gate.startsWith("rate:")) scopes.add(gate);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return [...scopes].sort();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Scopes for one ladder rung — owner is the full catalog; others intersect
|
|
134
|
+
* their profile. `member` stays on every signed-in rung.
|
|
135
|
+
*
|
|
136
|
+
* @param rung - 0 (owner) … 9 (guest)
|
|
137
|
+
* @param catalog - Manifest-derived scopes
|
|
138
|
+
*/
|
|
139
|
+
export function scopesForDevIdentityRung(rung: number, catalog: readonly string[]): string[] {
|
|
140
|
+
const profile = LADDER_SCOPE_PROFILES[rung];
|
|
141
|
+
if (profile === undefined) return [];
|
|
142
|
+
if (profile === "*") return [...catalog].sort((a, b) => a.localeCompare(b));
|
|
143
|
+
const allowed = new Set(catalog);
|
|
144
|
+
return [...new Set(profile.filter((scope) => scope === "member" || allowed.has(scope)))].sort(
|
|
145
|
+
(a, b) => a.localeCompare(b),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Default development identities for the invoke-as picker.
|
|
151
|
+
*
|
|
152
|
+
* @param manifest - Optional Manifest used to derive application scopes
|
|
153
|
+
*/
|
|
154
|
+
export function defaultDevIdentities(manifest?: Manifest | null): SeededDevIdentity[] {
|
|
155
|
+
const catalog = demoScopesFromManifest(manifest);
|
|
156
|
+
return DEV_IDENTITY_LADDER.map((person, rung) => ({
|
|
157
|
+
id: person.id,
|
|
158
|
+
email: person.email,
|
|
159
|
+
name: person.name,
|
|
160
|
+
status: "active",
|
|
161
|
+
scopes: scopesForDevIdentityRung(rung, catalog),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Whether the live list is still the built-in ladder (safe to refresh).
|
|
167
|
+
*
|
|
168
|
+
* @param identities - Live identity list
|
|
169
|
+
*/
|
|
170
|
+
export function isDefaultIdentitySeed(identities: readonly { readonly id: string }[]): boolean {
|
|
171
|
+
if (identities.length !== DEV_IDENTITY_LADDER.length) return false;
|
|
172
|
+
const ids = new Set(identities.map((row) => row.id));
|
|
173
|
+
return DEV_IDENTITY_LADDER.every((person) => ids.has(person.id));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Signed-in ladder ids (guest excluded) — default `role_member` holders.
|
|
178
|
+
*/
|
|
179
|
+
export function defaultMemberIdentityIds(): string[] {
|
|
180
|
+
return DEV_IDENTITY_LADDER.filter((person) => person.role !== "guest").map((person) => person.id);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Replace the default ladder when a Manifest arrives.
|
|
185
|
+
*
|
|
186
|
+
* @param identities - Live identity list
|
|
187
|
+
* @param manifest - New Manifest
|
|
188
|
+
*/
|
|
189
|
+
export function refreshSeededIdentities(
|
|
190
|
+
identities: Array<{
|
|
191
|
+
id: string;
|
|
192
|
+
email: string;
|
|
193
|
+
name: string;
|
|
194
|
+
status: "active" | "disabled";
|
|
195
|
+
scopes: readonly string[];
|
|
196
|
+
}>,
|
|
197
|
+
manifest: Manifest | null,
|
|
198
|
+
): void {
|
|
199
|
+
if (!isDefaultIdentitySeed(identities)) return;
|
|
200
|
+
const next = defaultDevIdentities(manifest);
|
|
201
|
+
identities.splice(0, identities.length, ...next);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Seed Access roles for Keel — every {@link KEEL_SCOPES} pair lands on the
|
|
206
|
+
* owner / admin rung; lower rungs use {@link KEEL_LADDER_GRANTS}.
|
|
207
|
+
*
|
|
208
|
+
* @param roles - Role store
|
|
209
|
+
* @param roleMembers - roleId → principal ids
|
|
210
|
+
*/
|
|
211
|
+
export function seedKeelAccessRoles(roles: RoleStore, roleMembers: Map<string, string[]>): void {
|
|
212
|
+
for (const person of DEV_IDENTITY_LADDER) {
|
|
213
|
+
const roleId = `role_${person.role}`;
|
|
214
|
+
upsertRole(roles, {
|
|
215
|
+
id: roleId,
|
|
216
|
+
name: person.role,
|
|
217
|
+
plane: "user",
|
|
218
|
+
description:
|
|
219
|
+
person.role === "owner"
|
|
220
|
+
? "Full workspace authorization"
|
|
221
|
+
: person.role === "guest"
|
|
222
|
+
? "Least authorization — no write scopes"
|
|
223
|
+
: `Keel ${person.role.replaceAll("_", " ")}`,
|
|
224
|
+
});
|
|
225
|
+
setRoleGrants(roles, roleId, KEEL_LADDER_GRANTS[person.role]);
|
|
226
|
+
roleMembers.set(roleId, person.role === "guest" ? [] : [person.id]);
|
|
227
|
+
}
|
|
228
|
+
roleMembers.set("role_member", defaultMemberIdentityIds());
|
|
229
|
+
}
|
|
@@ -15,12 +15,29 @@ import { http } from "../../kernel/triggers.ts";
|
|
|
15
15
|
import { field, store } from "../../elements/store.ts";
|
|
16
16
|
import { PII_MASK } from "../../elements/store/classify.ts";
|
|
17
17
|
import { createTestApp } from "../../test/create-test-app.ts";
|
|
18
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
18
19
|
import { FLOWS_TEST_MANIFEST } from "../ui-next/src/features/flows/fixture.ts";
|
|
19
20
|
import { bindHostInvokeUserFlow } from "./invoke-user-flow.ts";
|
|
20
21
|
import { startConsoleApp } from "./serve.ts";
|
|
21
22
|
import { setManifest } from "./state.ts";
|
|
22
23
|
import type { InvokeUserFlowInput } from "./invoke-user-flow.ts";
|
|
23
24
|
|
|
25
|
+
/** Fixture plus the booking gate the host actually enforces. */
|
|
26
|
+
const BOOKING_INVOKE_MANIFEST: Manifest = {
|
|
27
|
+
...FLOWS_TEST_MANIFEST,
|
|
28
|
+
gates: {
|
|
29
|
+
member: { kind: "policy", scopes: ["member"] },
|
|
30
|
+
"booking:create": { kind: "policy", scopes: ["booking:create"] },
|
|
31
|
+
},
|
|
32
|
+
flows: {
|
|
33
|
+
...FLOWS_TEST_MANIFEST.flows,
|
|
34
|
+
"bookings.create": {
|
|
35
|
+
...FLOWS_TEST_MANIFEST.flows!["bookings.create"]!,
|
|
36
|
+
gates: ["member", "booking:create"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
24
41
|
const memoryDrivers = {
|
|
25
42
|
store: { kv: { dev: "memory", test: "memory", prod: "memory" } },
|
|
26
43
|
signal: { dev: "memory", test: "memory", prod: "memory" },
|
|
@@ -147,11 +164,11 @@ describe("console flows invoke", () => {
|
|
|
147
164
|
secret: "test-secret-flows",
|
|
148
165
|
silentClaim: true,
|
|
149
166
|
production: false,
|
|
150
|
-
manifest:
|
|
167
|
+
manifest: BOOKING_INVOKE_MANIFEST,
|
|
151
168
|
});
|
|
152
169
|
const host = await bootInvokeHost();
|
|
153
170
|
try {
|
|
154
|
-
setManifest(handle.state,
|
|
171
|
+
setManifest(handle.state, BOOKING_INVOKE_MANIFEST);
|
|
155
172
|
handle.state.invokeUserFlow = bindHostInvokeUserFlow(host);
|
|
156
173
|
const auth = await claimOperator(handle);
|
|
157
174
|
|
|
@@ -214,12 +231,12 @@ describe("console flows invoke", () => {
|
|
|
214
231
|
secret: "test-secret-flows-adv",
|
|
215
232
|
silentClaim: true,
|
|
216
233
|
production: false,
|
|
217
|
-
manifest:
|
|
234
|
+
manifest: BOOKING_INVOKE_MANIFEST,
|
|
218
235
|
});
|
|
219
236
|
const host = await bootInvokeHost();
|
|
220
237
|
const seen: InvokeUserFlowInput[] = [];
|
|
221
238
|
try {
|
|
222
|
-
setManifest(handle.state,
|
|
239
|
+
setManifest(handle.state, BOOKING_INVOKE_MANIFEST);
|
|
223
240
|
const bound = bindHostInvokeUserFlow(host);
|
|
224
241
|
handle.state.invokeUserFlow = async (input) => {
|
|
225
242
|
seen.push(input);
|
|
@@ -36,6 +36,7 @@ import { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
|
|
|
36
36
|
import { resolveInvokeAs } from "./invoke-user-flow.ts";
|
|
37
37
|
import { resolveRlsIdentity } from "../../elements/store.ts";
|
|
38
38
|
import type { RlsIdentity } from "../../drivers/pg-rls.ts";
|
|
39
|
+
import { accessUserCeiling } from "./access.ts";
|
|
39
40
|
import { isKnownConsoleSqlGate, StoreFileNotFoundError, tenancyDeclared } from "./store.ts";
|
|
40
41
|
import {
|
|
41
42
|
PG_STAT_STATEMENTS_NOT_CREATED,
|
|
@@ -1164,6 +1165,22 @@ const GatesPowersOut = z.object({
|
|
|
1164
1165
|
deniedFlowIds: z.array(z.string()),
|
|
1165
1166
|
});
|
|
1166
1167
|
|
|
1168
|
+
const AccessKeyRowOut = z.object({
|
|
1169
|
+
id: z.string(),
|
|
1170
|
+
name: z.string(),
|
|
1171
|
+
plane: z.enum(["user", "operator"]),
|
|
1172
|
+
creatorId: z.string(),
|
|
1173
|
+
creatorScopes: z.array(z.string()),
|
|
1174
|
+
scopes: z.array(z.string()),
|
|
1175
|
+
createdAt: z.number(),
|
|
1176
|
+
lastUsedAt: z.number().nullable(),
|
|
1177
|
+
expiresAt: z.number().nullable(),
|
|
1178
|
+
revokedAt: z.number().nullable(),
|
|
1179
|
+
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable(),
|
|
1180
|
+
ipAllowlist: z.array(z.string()),
|
|
1181
|
+
unused90d: z.boolean(),
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1167
1184
|
const AccessPlaneSection = z.object({
|
|
1168
1185
|
plane: z.enum(["user", "operator"]),
|
|
1169
1186
|
operators: z
|
|
@@ -1202,21 +1219,7 @@ const AccessPlaneSection = z.object({
|
|
|
1202
1219
|
memberCount: z.number(),
|
|
1203
1220
|
}),
|
|
1204
1221
|
),
|
|
1205
|
-
keys: z.array(
|
|
1206
|
-
z.object({
|
|
1207
|
-
id: z.string(),
|
|
1208
|
-
name: z.string(),
|
|
1209
|
-
plane: z.enum(["user", "operator"]),
|
|
1210
|
-
scopes: z.array(z.string()),
|
|
1211
|
-
createdAt: z.number(),
|
|
1212
|
-
lastUsedAt: z.number().nullable(),
|
|
1213
|
-
expiresAt: z.number().nullable(),
|
|
1214
|
-
revokedAt: z.number().nullable(),
|
|
1215
|
-
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable(),
|
|
1216
|
-
ipAllowlist: z.array(z.string()),
|
|
1217
|
-
unused90d: z.boolean(),
|
|
1218
|
-
}),
|
|
1219
|
-
),
|
|
1222
|
+
keys: z.array(AccessKeyRowOut),
|
|
1220
1223
|
invites: z
|
|
1221
1224
|
.array(
|
|
1222
1225
|
z.object({
|
|
@@ -1236,21 +1239,7 @@ const AccessListOut = z.object({
|
|
|
1236
1239
|
operatorPlane: AccessPlaneSection,
|
|
1237
1240
|
userPlane: AccessPlaneSection,
|
|
1238
1241
|
hygiene: z.object({
|
|
1239
|
-
unusedKeys: z.array(
|
|
1240
|
-
z.object({
|
|
1241
|
-
id: z.string(),
|
|
1242
|
-
name: z.string(),
|
|
1243
|
-
plane: z.enum(["user", "operator"]),
|
|
1244
|
-
scopes: z.array(z.string()),
|
|
1245
|
-
createdAt: z.number(),
|
|
1246
|
-
lastUsedAt: z.number().nullable(),
|
|
1247
|
-
expiresAt: z.number().nullable(),
|
|
1248
|
-
revokedAt: z.number().nullable(),
|
|
1249
|
-
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable(),
|
|
1250
|
-
ipAllowlist: z.array(z.string()),
|
|
1251
|
-
unused90d: z.boolean(),
|
|
1252
|
-
}),
|
|
1253
|
-
),
|
|
1242
|
+
unusedKeys: z.array(AccessKeyRowOut),
|
|
1254
1243
|
neverSignedInOperators: z.array(
|
|
1255
1244
|
z.object({
|
|
1256
1245
|
id: z.string(),
|
|
@@ -1317,25 +1306,28 @@ const AccessCreateKeyIn = z.object({
|
|
|
1317
1306
|
plane: z.enum(["user", "operator"]),
|
|
1318
1307
|
name: z.string().min(1),
|
|
1319
1308
|
scopes: z.array(z.string()),
|
|
1309
|
+
/** Required for user-plane keys — issuer whose grants are the ceiling. */
|
|
1310
|
+
creatorUserId: z.string().min(1).optional(),
|
|
1311
|
+
expiresAt: z.number().nullable().optional(),
|
|
1312
|
+
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable().optional(),
|
|
1313
|
+
ipAllowlist: z.array(z.string()).optional(),
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
const AccessUpdateKeyIn = z.object({
|
|
1317
|
+
keyId: z.string().min(1),
|
|
1318
|
+
name: z.string().min(1).optional(),
|
|
1319
|
+
scopes: z.array(z.string()).optional(),
|
|
1320
1320
|
expiresAt: z.number().nullable().optional(),
|
|
1321
1321
|
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable().optional(),
|
|
1322
1322
|
ipAllowlist: z.array(z.string()).optional(),
|
|
1323
1323
|
});
|
|
1324
1324
|
|
|
1325
|
+
const AccessUpdateKeyOut = z.object({
|
|
1326
|
+
key: AccessKeyRowOut,
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1325
1329
|
const AccessCreateKeyOut = z.object({
|
|
1326
|
-
key:
|
|
1327
|
-
id: z.string(),
|
|
1328
|
-
name: z.string(),
|
|
1329
|
-
plane: z.enum(["user", "operator"]),
|
|
1330
|
-
scopes: z.array(z.string()),
|
|
1331
|
-
createdAt: z.number(),
|
|
1332
|
-
lastUsedAt: z.number().nullable(),
|
|
1333
|
-
expiresAt: z.number().nullable(),
|
|
1334
|
-
revokedAt: z.number().nullable(),
|
|
1335
|
-
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable(),
|
|
1336
|
-
ipAllowlist: z.array(z.string()),
|
|
1337
|
-
unused90d: z.boolean(),
|
|
1338
|
-
}),
|
|
1330
|
+
key: AccessKeyRowOut,
|
|
1339
1331
|
/** Raw secret — returned exactly once. */
|
|
1340
1332
|
secret: z.string(),
|
|
1341
1333
|
});
|
|
@@ -1347,19 +1339,7 @@ const AccessRevokeKeyIn = z.object({
|
|
|
1347
1339
|
});
|
|
1348
1340
|
|
|
1349
1341
|
const AccessRevokeKeyOut = z.object({
|
|
1350
|
-
key:
|
|
1351
|
-
id: z.string(),
|
|
1352
|
-
name: z.string(),
|
|
1353
|
-
plane: z.enum(["user", "operator"]),
|
|
1354
|
-
scopes: z.array(z.string()),
|
|
1355
|
-
createdAt: z.number(),
|
|
1356
|
-
lastUsedAt: z.number().nullable(),
|
|
1357
|
-
expiresAt: z.number().nullable(),
|
|
1358
|
-
revokedAt: z.number().nullable(),
|
|
1359
|
-
rateLimit: z.object({ max: z.number(), per: z.string() }).nullable(),
|
|
1360
|
-
ipAllowlist: z.array(z.string()),
|
|
1361
|
-
unused90d: z.boolean(),
|
|
1362
|
-
}),
|
|
1342
|
+
key: AccessKeyRowOut,
|
|
1363
1343
|
blastRadius: AccessKeyBlastOut,
|
|
1364
1344
|
});
|
|
1365
1345
|
|
|
@@ -1855,6 +1835,7 @@ export function createConsoleBindings(state: ConsoleState): {
|
|
|
1855
1835
|
readonly createKey: AnyFlowDef;
|
|
1856
1836
|
readonly revokeKey: AnyFlowDef;
|
|
1857
1837
|
readonly rotateKey: AnyFlowDef;
|
|
1838
|
+
readonly updateKey: AnyFlowDef;
|
|
1858
1839
|
readonly setRoleGrants: AnyFlowDef;
|
|
1859
1840
|
};
|
|
1860
1841
|
readonly diff: {
|
|
@@ -1928,6 +1909,7 @@ export function createConsoleBindings(state: ConsoleState): {
|
|
|
1928
1909
|
const accessCreateKeyFlow = createAccessCreateKey(state);
|
|
1929
1910
|
const accessRevokeKeyFlow = createAccessRevokeKey(state);
|
|
1930
1911
|
const accessRotateKeyFlow = createAccessRotateKey(state);
|
|
1912
|
+
const accessUpdateKeyFlow = createAccessUpdateKey(state);
|
|
1931
1913
|
const accessSetRoleGrantsFlow = createAccessSetRoleGrants(state);
|
|
1932
1914
|
const diffList = createDiffList(state);
|
|
1933
1915
|
const pluginsList = createPluginsList(state);
|
|
@@ -1990,6 +1972,7 @@ export function createConsoleBindings(state: ConsoleState): {
|
|
|
1990
1972
|
bindHttp(http.post("/console/access/keys"), accessCreateKeyFlow),
|
|
1991
1973
|
bindHttp(http.post("/console/access/keys/revoke"), accessRevokeKeyFlow),
|
|
1992
1974
|
bindHttp(http.post("/console/access/keys/rotate"), accessRotateKeyFlow),
|
|
1975
|
+
bindHttp(http.post("/console/access/keys/update"), accessUpdateKeyFlow),
|
|
1993
1976
|
bindHttp(http.post("/console/access/roles/grants"), accessSetRoleGrantsFlow),
|
|
1994
1977
|
bindHttp(http.get("/console/diff"), diffList),
|
|
1995
1978
|
bindHttp(http.get("/console/plugins"), pluginsList),
|
|
@@ -2059,6 +2042,7 @@ export function createConsoleBindings(state: ConsoleState): {
|
|
|
2059
2042
|
createKey: accessCreateKeyFlow,
|
|
2060
2043
|
revokeKey: accessRevokeKeyFlow,
|
|
2061
2044
|
rotateKey: accessRotateKeyFlow,
|
|
2045
|
+
updateKey: accessUpdateKeyFlow,
|
|
2062
2046
|
setRoleGrants: accessSetRoleGrantsFlow,
|
|
2063
2047
|
},
|
|
2064
2048
|
diff: { list: diffList },
|
|
@@ -3854,6 +3838,46 @@ function actorScopesOf(state: ConsoleState, operatorId: string): string[] {
|
|
|
3854
3838
|
return [...new Set([...fromRoles, "console:*"])];
|
|
3855
3839
|
}
|
|
3856
3840
|
|
|
3841
|
+
/**
|
|
3842
|
+
* Derive the issuer for a Console-minted key.
|
|
3843
|
+
* Operator-plane: the acting operator. User-plane: required `creatorUserId`.
|
|
3844
|
+
*
|
|
3845
|
+
* @param state - Console state
|
|
3846
|
+
* @param operatorId - Acting operator
|
|
3847
|
+
* @param input - Create payload
|
|
3848
|
+
*/
|
|
3849
|
+
export function resolveAccessKeyIssuer(
|
|
3850
|
+
state: ConsoleState,
|
|
3851
|
+
operatorId: string,
|
|
3852
|
+
input: { readonly plane: "user" | "operator"; readonly creatorUserId?: string },
|
|
3853
|
+
):
|
|
3854
|
+
| { readonly creatorId: string; readonly creatorScopes: readonly string[] }
|
|
3855
|
+
| { readonly error: string } {
|
|
3856
|
+
if (input.plane === "operator") {
|
|
3857
|
+
return { creatorId: operatorId, creatorScopes: actorScopesOf(state, operatorId) };
|
|
3858
|
+
}
|
|
3859
|
+
const userId = input.creatorUserId;
|
|
3860
|
+
if (!userId) {
|
|
3861
|
+
return { error: "creatorUserId is required for user-plane keys" };
|
|
3862
|
+
}
|
|
3863
|
+
const identity = state.identities.find((row) => row.id === userId);
|
|
3864
|
+
const roleIds = [...state.roleMembers.entries()]
|
|
3865
|
+
.filter(([, members]) => members.includes(userId))
|
|
3866
|
+
.map(([id]) => id);
|
|
3867
|
+
if (!identity && roleIds.length === 0) {
|
|
3868
|
+
return { error: `unknown user: ${userId}` };
|
|
3869
|
+
}
|
|
3870
|
+
return {
|
|
3871
|
+
creatorId: userId,
|
|
3872
|
+
creatorScopes: accessUserCeiling({
|
|
3873
|
+
userId,
|
|
3874
|
+
identityScopes: identity?.scopes,
|
|
3875
|
+
roles: state.roles,
|
|
3876
|
+
roleMembers: state.roleMembers,
|
|
3877
|
+
}),
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3857
3881
|
function createAccessList(state: ConsoleState) {
|
|
3858
3882
|
return flow("console.access.list", {
|
|
3859
3883
|
plane: "operator",
|
|
@@ -3916,12 +3940,16 @@ function createAccessCreateKey(state: ConsoleState) {
|
|
|
3916
3940
|
do: async (input: z.infer<typeof AccessCreateKeyIn>, fx) => {
|
|
3917
3941
|
if (!fx.operator.id) return fail("AuthFailed", {});
|
|
3918
3942
|
try {
|
|
3943
|
+
const issued = resolveAccessKeyIssuer(state, fx.operator.id, input);
|
|
3944
|
+
if ("error" in issued) {
|
|
3945
|
+
return fail("AccessGrantDenied", { reason: issued.error });
|
|
3946
|
+
}
|
|
3919
3947
|
const created = await state.accessCreateKey({
|
|
3920
3948
|
plane: input.plane,
|
|
3921
3949
|
name: input.name,
|
|
3922
3950
|
scopes: input.scopes,
|
|
3923
|
-
creatorId:
|
|
3924
|
-
creatorScopes:
|
|
3951
|
+
creatorId: issued.creatorId,
|
|
3952
|
+
creatorScopes: issued.creatorScopes,
|
|
3925
3953
|
expiresAt: input.expiresAt,
|
|
3926
3954
|
rateLimit: input.rateLimit,
|
|
3927
3955
|
ipAllowlist: input.ipAllowlist,
|
|
@@ -4004,6 +4032,33 @@ function createAccessRotateKey(state: ConsoleState) {
|
|
|
4004
4032
|
});
|
|
4005
4033
|
}
|
|
4006
4034
|
|
|
4035
|
+
function createAccessUpdateKey(state: ConsoleState) {
|
|
4036
|
+
return flow("console.access.updateKey", {
|
|
4037
|
+
plane: "operator",
|
|
4038
|
+
in: AccessUpdateKeyIn,
|
|
4039
|
+
out: AccessUpdateKeyOut,
|
|
4040
|
+
errors: { AuthFailed, AccessKeyNotFound, AccessGrantDenied },
|
|
4041
|
+
do: async (input: z.infer<typeof AccessUpdateKeyIn>, fx) => {
|
|
4042
|
+
if (!fx.operator.id) return fail("AuthFailed", {});
|
|
4043
|
+
try {
|
|
4044
|
+
const key = await state.accessUpdateKey(input);
|
|
4045
|
+
if (!key) {
|
|
4046
|
+
return fail("AccessKeyNotFound", { keyId: input.keyId });
|
|
4047
|
+
}
|
|
4048
|
+
fx.log.info("console.access.updateKey", {
|
|
4049
|
+
keyId: key.id,
|
|
4050
|
+
operatorId: fx.operator.id,
|
|
4051
|
+
});
|
|
4052
|
+
return { key: { ...key } };
|
|
4053
|
+
} catch (err) {
|
|
4054
|
+
return fail("AccessGrantDenied", {
|
|
4055
|
+
reason: err instanceof Error ? err.message : "grant denied",
|
|
4056
|
+
});
|
|
4057
|
+
}
|
|
4058
|
+
},
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4007
4062
|
function createAccessSetRoleGrants(state: ConsoleState) {
|
|
4008
4063
|
return flow("console.access.setRoleGrants", {
|
|
4009
4064
|
plane: "operator",
|
|
@@ -78,6 +78,14 @@ async function liveRuntime(): Promise<{
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
describe("createDefaultGateAuthStores", () => {
|
|
82
|
+
test("user roles grant member only — no leftover app catalog", () => {
|
|
83
|
+
const auth = createDefaultGateAuthStores();
|
|
84
|
+
expect([...(auth.roles.grants.get("role_member") ?? [])]).toEqual(["member"]);
|
|
85
|
+
expect([...(auth.roles.grants.get("role_staff") ?? [])]).toEqual(["member"]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
81
89
|
describe("isApplicationScope", () => {
|
|
82
90
|
test("console:* is not application; everything else is", () => {
|
|
83
91
|
expect(isApplicationScope("console:store.sql:write")).toBe(false);
|
|
@@ -91,6 +99,21 @@ describe("projectGatesPanel", () => {
|
|
|
91
99
|
const auth = createDefaultGateAuthStores();
|
|
92
100
|
// Poison an operator role with an application scope.
|
|
93
101
|
setRoleGrants(auth.roles, "role_ops", ["console:store.sql:read", "booking:create"]);
|
|
102
|
+
auth.apiKeys.keys.set("key_fixture", {
|
|
103
|
+
id: "key_fixture",
|
|
104
|
+
plane: "user",
|
|
105
|
+
hash: "fixture",
|
|
106
|
+
name: "Fixture",
|
|
107
|
+
scopes: ["member"],
|
|
108
|
+
expiresAt: null,
|
|
109
|
+
rateLimit: null,
|
|
110
|
+
ipAllowlist: [],
|
|
111
|
+
creatorId: "user_demo",
|
|
112
|
+
creatorScopes: ["member"],
|
|
113
|
+
createdAt: 0,
|
|
114
|
+
lastUsedAt: null,
|
|
115
|
+
revokedAt: null,
|
|
116
|
+
});
|
|
94
117
|
|
|
95
118
|
const projection = projectGatesPanel({
|
|
96
119
|
manifest: MANIFEST,
|
|
@@ -318,6 +341,7 @@ describe("simulateGates — evaluate only", () => {
|
|
|
318
341
|
test("role principal resolves scopes from RoleStore", async () => {
|
|
319
342
|
const { runtime, close } = await liveRuntime();
|
|
320
343
|
const auth = createDefaultGateAuthStores();
|
|
344
|
+
setRoleGrants(auth.roles, "role_member", ["member", "booking:create"]);
|
|
321
345
|
try {
|
|
322
346
|
const ok = await simulateGates({
|
|
323
347
|
flowId: "bookings.create",
|
|
@@ -33,6 +33,7 @@ import { statusForFailure } from "../../compiler/response.ts";
|
|
|
33
33
|
import { gateDenialFailure } from "../../kernel/index.ts";
|
|
34
34
|
import { diffManifest } from "../../manifest/diff.ts";
|
|
35
35
|
import type { Gate as ManifestGate, Manifest, ManifestChange } from "../../manifest/types.ts";
|
|
36
|
+
import { defaultMemberIdentityIds } from "./dev-identities.ts";
|
|
36
37
|
|
|
37
38
|
/** Identity slice needed for Gates (avoids circular import with state). */
|
|
38
39
|
export interface GatesIdentity {
|
|
@@ -413,8 +414,8 @@ export function createDefaultGateAuthStores(): {
|
|
|
413
414
|
plane: "operator",
|
|
414
415
|
description: "Console operators",
|
|
415
416
|
});
|
|
416
|
-
setRoleGrants(roles, "role_member", ["member"
|
|
417
|
-
setRoleGrants(roles, "role_staff", ["member"
|
|
417
|
+
setRoleGrants(roles, "role_member", ["member"]);
|
|
418
|
+
setRoleGrants(roles, "role_staff", ["member"]);
|
|
418
419
|
setRoleGrants(roles, "role_ops", [
|
|
419
420
|
"console:store.sql:read",
|
|
420
421
|
"console:store.sql:write",
|
|
@@ -422,24 +423,9 @@ export function createDefaultGateAuthStores(): {
|
|
|
422
423
|
]);
|
|
423
424
|
|
|
424
425
|
const apiKeys = createApiKeyStore();
|
|
425
|
-
apiKeys.keys.set("key_demo", {
|
|
426
|
-
id: "key_demo",
|
|
427
|
-
plane: "user",
|
|
428
|
-
hash: "demo",
|
|
429
|
-
name: "Demo key",
|
|
430
|
-
scopes: ["member", "booking:create"],
|
|
431
|
-
expiresAt: null,
|
|
432
|
-
rateLimit: null,
|
|
433
|
-
ipAllowlist: [],
|
|
434
|
-
creatorId: "user_demo",
|
|
435
|
-
creatorScopes: ["member", "booking:create"],
|
|
436
|
-
createdAt: 0,
|
|
437
|
-
lastUsedAt: null,
|
|
438
|
-
revokedAt: null,
|
|
439
|
-
});
|
|
440
426
|
|
|
441
427
|
const roleMembers = new Map<string, string[]>([
|
|
442
|
-
["role_member",
|
|
428
|
+
["role_member", defaultMemberIdentityIds()],
|
|
443
429
|
["role_staff", []],
|
|
444
430
|
["role_ops", []],
|
|
445
431
|
]);
|
|
@@ -706,7 +692,7 @@ function resolvePrincipalContext(options: SimulateGatesOptions): {
|
|
|
706
692
|
return {
|
|
707
693
|
ctx: {
|
|
708
694
|
auth: { userId: null, scopes: new Set(), verified: false },
|
|
709
|
-
operator: { id:
|
|
695
|
+
operator: { id: key.creatorId },
|
|
710
696
|
meta: { ip: options.meta?.ip },
|
|
711
697
|
},
|
|
712
698
|
};
|
|
@@ -714,12 +700,13 @@ function resolvePrincipalContext(options: SimulateGatesOptions): {
|
|
|
714
700
|
return {
|
|
715
701
|
ctx: {
|
|
716
702
|
auth: {
|
|
717
|
-
userId:
|
|
703
|
+
userId: key.creatorId,
|
|
718
704
|
scopes: new Set(key.scopes),
|
|
719
705
|
verified: true,
|
|
706
|
+
apiKeyId: key.id,
|
|
720
707
|
},
|
|
721
708
|
operator: { id: null },
|
|
722
|
-
meta: { ip: options.meta?.ip, userId:
|
|
709
|
+
meta: { ip: options.meta?.ip, userId: key.creatorId },
|
|
723
710
|
},
|
|
724
711
|
};
|
|
725
712
|
}
|