@coffre/core 0.4.6 → 0.5.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.
@@ -0,0 +1,522 @@
1
+ //#region src/access-conversion.ts
2
+ function convertEveryProjectGrants(input) {
3
+ const { role, scope } = input.person ? instanceRoleFor(input.role, input.everyProject) : {
4
+ role: input.role,
5
+ scope: EVERYWHERE
6
+ };
7
+ const before = new Map(input.grants.map((grant) => [placeKey(grant), grant]));
8
+ const held = new Map(before);
9
+ const given = /* @__PURE__ */ new Map();
10
+ const narrowed = [];
11
+ const give = (at, wanted) => {
12
+ const place = {
13
+ projectId: at.project.id,
14
+ environmentId: at.environment?.id ?? null,
15
+ role: wanted.role,
16
+ expiresAt: wanted.expiresAt
17
+ };
18
+ held.set(placeKey(place), place);
19
+ given.set(placeKey(place), {
20
+ ...place,
21
+ replaces: before.get(placeKey(place))?.role ?? null
22
+ });
23
+ };
24
+ const settle = (grant, at, wanted) => {
25
+ const place = at.environment === null ? { projectId: at.project.id } : {
26
+ projectId: at.project.id,
27
+ environmentId: at.environment.id,
28
+ environmentSlug: at.environment.slug
29
+ };
30
+ if (holdsAlready(role, scope, [...held.values()], wanted, place)) return;
31
+ const existing = held.get(at.environment?.id ?? at.project.id);
32
+ if (existing === void 0 || within(existing.role, wanted.role) && outlasts(wanted.expiresAt, existing.expiresAt)) {
33
+ give(at, wanted);
34
+ return;
35
+ }
36
+ if (at.environment === null) {
37
+ const down = [wanted, existing].filter((candidate) => assignableToEnvironment(candidate.role)).sort((a, b) => Number(roleGrants(a.role, "audit.read")) - Number(roleGrants(b.role, "audit.read")))[0];
38
+ if (down !== void 0) {
39
+ if (down === existing) give(at, wanted);
40
+ narrowed.push({
41
+ kind: "environments",
42
+ grant,
43
+ projectId: at.project.id,
44
+ role: down.role
45
+ });
46
+ for (const environment of at.project.environments) settle(grant, {
47
+ project: at.project,
48
+ environment
49
+ }, {
50
+ role: down.role,
51
+ expiresAt: down.expiresAt
52
+ });
53
+ return;
54
+ }
55
+ }
56
+ const keep = !roleGrants(wanted.role, "secret.read") || roleGrants(existing.role, "secret.read") ? existing : wanted;
57
+ const lose = keep === existing ? wanted : existing;
58
+ if (keep === wanted) give(at, wanted);
59
+ narrowed.push({
60
+ kind: "lost",
61
+ grant,
62
+ projectId: at.project.id,
63
+ environmentId: at.environment?.id ?? null,
64
+ lost: lose.role,
65
+ kept: keep.role,
66
+ keptUntil: keep.expiresAt
67
+ });
68
+ };
69
+ for (const grant of input.everyProject) {
70
+ if (byRole(role, scope, grant)) continue;
71
+ narrowed.push({
72
+ kind: "later-projects",
73
+ grant
74
+ });
75
+ for (const project of input.projects) {
76
+ const reached = grant.environmentSlug === null ? [null] : project.environments.filter((environment) => environment.slug === grant.environmentSlug);
77
+ for (const environment of reached) settle(grant, {
78
+ project,
79
+ environment
80
+ }, grant);
81
+ }
82
+ }
83
+ return {
84
+ role,
85
+ scope,
86
+ grants: [...given.values()],
87
+ narrowed
88
+ };
89
+ }
90
+ /**
91
+ * The strongest instance role a person's lasting grants on every project
92
+ * add up to, never more than they held: an owner of 0.4 (an Admin) who
93
+ * read, wrote and retired secrets everywhere is an Owner; anyone else is a
94
+ * Developer or an Auditor where those grants were on all of every project,
95
+ * or on some environment slugs (Developer first), or stays as they were.
96
+ */
97
+ function instanceRoleFor(previous, everyProject) {
98
+ const lasting = everyProject.filter((grant) => grant.expiresAt === null);
99
+ const all = lasting.find((grant) => grant.environmentSlug === null)?.role;
100
+ if (previous === "admin") {
101
+ const adds = all === void 0 ? [] : ROLES[all].permissions;
102
+ return {
103
+ role: INSTANCE_ROLES.owner.permissions.every((permission) => instanceRoleGrants("admin", permission) || adds.includes(permission)) ? "owner" : "admin",
104
+ scope: EVERYWHERE
105
+ };
106
+ }
107
+ if (previous !== "member") return {
108
+ role: previous,
109
+ scope: EVERYWHERE
110
+ };
111
+ const fits = (role, held) => INSTANCE_ROLES[role].permissions.every((permission) => roleGrants(held, permission));
112
+ const candidates = ["developer", "auditor"];
113
+ for (const role of candidates) if (all !== void 0 && fits(role, all)) return {
114
+ role,
115
+ scope: EVERYWHERE
116
+ };
117
+ for (const role of candidates) {
118
+ const only = lasting.filter((grant) => grant.environmentSlug !== null && fits(role, grant.role)).map((grant) => grant.environmentSlug);
119
+ if (only.length > 0) return {
120
+ role,
121
+ scope: normalScope({
122
+ projects: "all",
123
+ environments: { only }
124
+ })
125
+ };
126
+ }
127
+ return {
128
+ role: "member",
129
+ scope: EVERYWHERE
130
+ };
131
+ }
132
+ /** Whether the instance role, in its scope, holds all a grant on every project did, wherever it did, for good. */
133
+ function byRole(role, scope, grant) {
134
+ return scope.projects === "all" && (grant.environmentSlug === null ? scope.environments === "all" : admits(scope.environments, grant.environmentSlug)) && ROLES[grant.role].permissions.every((permission) => instanceRoleGrants(role, permission));
135
+ }
136
+ /** Whether every permission of the grant's role is held at `at` already, by the role or by grants lasting as long. */
137
+ function holdsAlready(role, scope, held, grant, at) {
138
+ const lasting = held.filter((other) => outlasts(other.expiresAt, grant.expiresAt));
139
+ return ROLES[grant.role].permissions.every((permission) => allows({
140
+ isRootAdmin: false,
141
+ role,
142
+ scope,
143
+ grants: lasting
144
+ }, permission, at));
145
+ }
146
+ /** Whether `role` holds nothing `wider` does not. */
147
+ function within(role, wider) {
148
+ return ROLES[role].permissions.every((permission) => roleGrants(wider, permission));
149
+ }
150
+ /** Whether an end of `a` comes no sooner than one of `b`; null is never. */
151
+ function outlasts(a, b) {
152
+ return a === null || b !== null && a >= b;
153
+ }
154
+ function placeKey(place) {
155
+ return place.environmentId ?? place.projectId;
156
+ }
157
+ //#endregion
158
+ //#region src/access.ts
159
+ /**
160
+ * Who may do what: seven permissions, the six project roles a grant names,
161
+ * and the five instance roles a person holds across every project.
162
+ *
163
+ * Deliberately constants and not tables or a policy language. A grant names
164
+ * one role for one member on one place, a person's instance role names one
165
+ * role and its scope, and a check is a lookup in this file, so authorisation
166
+ * stays something you can read end to end.
167
+ */
168
+ const PERMISSIONS = [
169
+ "secret.read",
170
+ "secret.write",
171
+ "secret.archive",
172
+ "audit.read",
173
+ "environment.manage",
174
+ "grant.manage",
175
+ "project.manage"
176
+ ];
177
+ /**
178
+ * Permissions that only make sense on a project. Granting
179
+ * `environment.manage` on one environment would be incoherent: what it
180
+ * authorises is creating that environment's siblings.
181
+ */
182
+ const PROJECT_ONLY_PERMISSIONS = [
183
+ "environment.manage",
184
+ "grant.manage",
185
+ "project.manage"
186
+ ];
187
+ const ROLES = {
188
+ viewer: {
189
+ name: "Viewer",
190
+ description: "Read secret values.",
191
+ permissions: ["secret.read"]
192
+ },
193
+ developer: {
194
+ name: "Developer",
195
+ description: "Read and write secrets.",
196
+ permissions: ["secret.read", "secret.write"]
197
+ },
198
+ maintainer: {
199
+ name: "Maintainer",
200
+ description: "Read, write and retire secrets, and manage environments.",
201
+ permissions: [
202
+ "secret.read",
203
+ "secret.write",
204
+ "secret.archive",
205
+ "environment.manage"
206
+ ]
207
+ },
208
+ "access-manager": {
209
+ name: "Access manager",
210
+ description: "Manage who has access. Cannot read secret values.",
211
+ permissions: ["grant.manage"]
212
+ },
213
+ auditor: {
214
+ name: "Auditor",
215
+ description: "Read the audit log. Cannot read secret values.",
216
+ permissions: ["audit.read"]
217
+ },
218
+ owner: {
219
+ name: "Owner",
220
+ description: "Everything, including reading secret values.",
221
+ permissions: [...PERMISSIONS]
222
+ }
223
+ };
224
+ const ROLE_NAMES = Object.keys(ROLES);
225
+ function isRole(value) {
226
+ return Object.hasOwn(ROLES, value);
227
+ }
228
+ function roleGrants(role, permission) {
229
+ return ROLES[role].permissions.includes(permission);
230
+ }
231
+ /** Roles that hold a project-only permission can only be granted on a project. */
232
+ function assignableToEnvironment(role) {
233
+ return !ROLES[role].permissions.some((permission) => PROJECT_ONLY_PERMISSIONS.includes(permission));
234
+ }
235
+ /**
236
+ * A person's role on the whole instance: what they hold in every project
237
+ * their scope covers, before any grant. Service accounts are always
238
+ * members, and hold only what their grants give. Admins and owners also
239
+ * run the instance (`runsInstance`) when nothing narrows their scope.
240
+ */
241
+ const INSTANCE_ROLES = {
242
+ member: {
243
+ name: "Member",
244
+ description: "Only what projects grant.",
245
+ permissions: []
246
+ },
247
+ auditor: {
248
+ name: "Auditor",
249
+ description: "Read the audit log. No secret values.",
250
+ permissions: ["audit.read"]
251
+ },
252
+ developer: {
253
+ name: "Developer",
254
+ description: "Read and write secrets.",
255
+ permissions: ["secret.read", "secret.write"]
256
+ },
257
+ admin: {
258
+ name: "Admin",
259
+ description: "Manage people, service accounts, projects and access. No secret values.",
260
+ permissions: [
261
+ "audit.read",
262
+ "environment.manage",
263
+ "grant.manage",
264
+ "project.manage"
265
+ ]
266
+ },
267
+ owner: {
268
+ name: "Owner",
269
+ description: "Everything an admin does, and read every secret.",
270
+ permissions: [...PERMISSIONS]
271
+ }
272
+ };
273
+ const INSTANCE_ROLE_NAMES = Object.keys(INSTANCE_ROLES);
274
+ function isInstanceRole(value) {
275
+ return Object.hasOwn(INSTANCE_ROLES, value);
276
+ }
277
+ function instanceRoleGrants(role, permission) {
278
+ return INSTANCE_ROLES[role].permissions.includes(permission);
279
+ }
280
+ /** The roles that manage people and access: Admin, and Owner, which adds every secret. */
281
+ function administers(role) {
282
+ return role === "admin" || role === "owner";
283
+ }
284
+ /** Every project and every environment. */
285
+ const EVERYWHERE = {
286
+ projects: "all",
287
+ environments: "all"
288
+ };
289
+ /** Whether a scope narrows nothing. */
290
+ function unscoped(scope) {
291
+ return scope.projects === "all" && scope.environments === "all";
292
+ }
293
+ /** Whether a filter takes in a project's id or an environment's slug; one not read (null) is in none that lists. */
294
+ function admits(filter, value) {
295
+ if (filter === "all") return true;
296
+ if (value == null) return false;
297
+ return "only" in filter ? filter.only.includes(value) : !filter.except.includes(value);
298
+ }
299
+ /**
300
+ * One way to write each scope, which the vault seals and the log replays:
301
+ * lists sorted, without repeats; an empty `except` is `all`. An empty
302
+ * `only` stays: it takes in nothing.
303
+ */
304
+ function normalScope(scope) {
305
+ const normal = (filter) => {
306
+ if (filter === "all") return "all";
307
+ if ("only" in filter) return { only: [...new Set(filter.only)].sort() };
308
+ return filter.except.length === 0 ? "all" : { except: [...new Set(filter.except)].sort() };
309
+ };
310
+ return {
311
+ projects: normal(scope.projects),
312
+ environments: normal(scope.environments)
313
+ };
314
+ }
315
+ /** Whether `value` is a scope as `normalScope` writes it, and as the vault stores it. */
316
+ function isScope(value) {
317
+ const isFilter = (filter) => {
318
+ if (filter === "all") return true;
319
+ if (typeof filter !== "object" || filter === null || Array.isArray(filter)) return false;
320
+ const keys = Object.keys(filter);
321
+ if (keys.length !== 1 || keys[0] !== "only" && keys[0] !== "except") return false;
322
+ const list = filter[keys[0]];
323
+ return Array.isArray(list) && list.every((item) => typeof item === "string" && item.length > 0);
324
+ };
325
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
326
+ const { projects, environments, ...rest } = value;
327
+ return Object.keys(rest).length === 0 && isFilter(projects) && isFilter(environments);
328
+ }
329
+ /**
330
+ * A scope in words, its projects named as the caller has them (slugs, at
331
+ * the API's edges): `All projects`, `All projects · dev only`,
332
+ * `web, api`, `All projects but billing · all but prod`.
333
+ */
334
+ function scopeInWords(scope) {
335
+ const list = (names) => names.join(", ");
336
+ const { projects, environments } = scope;
337
+ const where = projects === "all" ? "All projects" : "only" in projects ? projects.only.length === 0 ? "No projects" : list(projects.only) : `All projects but ${list(projects.except)}`;
338
+ if (environments === "all") return where;
339
+ if ("only" in environments) return `${where} · ${environments.only.length === 0 ? "no environments" : `${list(environments.only)} only`}`;
340
+ return `${where} · all but ${list(environments.except)}`;
341
+ }
342
+ /**
343
+ * Whether a scope takes in a place. A project as a whole is in it only when
344
+ * the scope keeps all of its environments: a role scoped to `dev` reaches
345
+ * every `dev`, never a project around one.
346
+ */
347
+ function inScope(scope, place) {
348
+ if (!admits(scope.projects, place.projectId)) return false;
349
+ if (place.environmentId == null) return scope.environments === "all";
350
+ return admits(scope.environments, place.environmentSlug);
351
+ }
352
+ /**
353
+ * What a grant's fields say it is on, or null when they say nothing
354
+ * coherent: an environment without its project, as a grant whose
355
+ * environment row is gone reads, or neither, as the grants on every project
356
+ * of 0.4 read until the vault replaces them. Null covers nothing.
357
+ */
358
+ function grantKind(grant) {
359
+ if (grant.projectId === null) return null;
360
+ return grant.environmentId === null ? "project" : "environment";
361
+ }
362
+ /** The most places a scope asked for names: far more than any credential needs, and a bound on what a request makes the app check. */
363
+ const MAX_SCOPE_PLACES = 1e3;
364
+ /**
365
+ * A credential's scope, by path as the API names it, in words, each role
366
+ * with its places: `developer on market/dev, api/dev; viewer on market/prod`.
367
+ */
368
+ function credentialScopeInWords(scope) {
369
+ const byRole = /* @__PURE__ */ new Map();
370
+ for (const [path, role] of Object.entries(scope)) byRole.set(role, [...byRole.get(role) ?? [], path]);
371
+ return [...byRole].map(([role, paths]) => `${role} on ${paths.join(", ")}`).join("; ") || "nothing";
372
+ }
373
+ /** Whether `value` is a credential's scope as the app stores it and the vault reads it: one grant a place, each role one that place takes. */
374
+ function isCredentialScope(value) {
375
+ if (!Array.isArray(value)) return false;
376
+ const places = /* @__PURE__ */ new Set();
377
+ return value.every((grant) => {
378
+ if (typeof grant !== "object" || grant === null || Array.isArray(grant)) return false;
379
+ const { projectId, environmentId, role, ...rest } = grant;
380
+ if (Object.keys(rest).length > 0 || typeof projectId !== "string" || typeof role !== "string" || !isRole(role)) return false;
381
+ if (environmentId !== null && (typeof environmentId !== "string" || !assignableToEnvironment(role))) return false;
382
+ const place = `${projectId}/${environmentId ?? ""}`;
383
+ if (places.has(place)) return false;
384
+ places.add(place);
385
+ return true;
386
+ });
387
+ }
388
+ /** Someone who holds nothing: a stranger, a removed member, a tampered row. */
389
+ const NOTHING = {
390
+ isRootAdmin: false,
391
+ role: "member",
392
+ scope: EVERYWHERE,
393
+ grants: []
394
+ };
395
+ /**
396
+ * Whether a grant at `grant` reaches `place`:
397
+ *
398
+ * market market and each of its environments
399
+ * market/dev market/dev
400
+ *
401
+ * An environment's grant never reaches up to its project.
402
+ */
403
+ function covers(grant, place) {
404
+ switch (grantKind(grant)) {
405
+ case "environment": return grant.projectId === place.projectId && grant.environmentId === (place.environmentId ?? null);
406
+ case "project": return grant.projectId === place.projectId;
407
+ case null: return false;
408
+ }
409
+ }
410
+ /**
411
+ * Whether `holder` may do `permission` at `place`: their instance role
412
+ * gives its permissions inside its scope, a grant that covers the place
413
+ * gives its role's there, and they add up. A Developer scoped to `dev` with
414
+ * `viewer` on `billing` reads and writes every `dev`, and reads all of
415
+ * `billing`.
416
+ */
417
+ function allows(holder, permission, place) {
418
+ if (holder.credentialScope != null && !holdsAt(holder.credentialScope, permission, place)) return false;
419
+ if (holder.isRootAdmin) return true;
420
+ if (instanceRoleGrants(holder.role, permission) && inScope(holder.scope, place)) return true;
421
+ return holdsAt(holder.grants, permission, place);
422
+ }
423
+ /** Whether a grant among `grants` covers `place` with a role that holds `permission`. */
424
+ function holdsAt(grants, permission, place) {
425
+ return grants.some((grant) => covers(grant, place) && roleGrants(grant.role, permission));
426
+ }
427
+ /**
428
+ * Whether the actor may give or take grants at this place: with
429
+ * `grant.manage` there, from a project grant or an instance role whose
430
+ * scope takes it in. A grant on a project reaches all its environments, so
431
+ * an admin scoped to `dev` manages `market/dev`'s grants, not `market`'s.
432
+ */
433
+ function mayManageAccess(actor, place) {
434
+ return allows(actor, "grant.manage", place);
435
+ }
436
+ /**
437
+ * Whether the holder runs the instance: a root admin, or an Admin or Owner
438
+ * whose scope narrows nothing. They add and remove people, set instance
439
+ * roles, and change the instance's settings; they read its whole log too
440
+ * (`readsWholeLog`). A scoped Admin manages only inside its scope, so it
441
+ * does none of these.
442
+ */
443
+ function runsInstance(holder) {
444
+ return holder.isRootAdmin || administers(holder.role) && unscoped(holder.scope);
445
+ }
446
+ /**
447
+ * Whether `holder` may give a service account `role` at `place`: where
448
+ * they manage access, as anyone may; or inside the setting, where they hold
449
+ * everything `role` does. A Developer on `market/dev` gives viewer or
450
+ * developer there, never maintainer; a grant on `market` takes holding it
451
+ * on all of `market`. `setting` is null for a service account, which sets
452
+ * up none.
453
+ */
454
+ function givesService(holder, setting, role, place) {
455
+ if (mayManageAccess(holder, place)) return true;
456
+ return setting !== null && inScope(setting, place) && ROLES[role].permissions.every((permission) => allows(holder, permission, place));
457
+ }
458
+ /** Whether `holder` gives a service account anything anywhere among `places`: whether they set any up. */
459
+ function setsUpServices(holder, setting, places) {
460
+ if (runsInstance(holder)) return true;
461
+ return places.some((place) => ROLE_NAMES.some((role) => givesService(holder, setting, role, place)));
462
+ }
463
+ /**
464
+ * Whether `holder`, the member `actor`, manages a service account: issues
465
+ * and revokes its tokens, adds and removes its trust bindings, changes its
466
+ * grants and removes it. A credential for it reads everything it holds, so
467
+ * they must reach every grant it holds, as if giving it (`givesService`):
468
+ * a Developer on `dev` manages an account on `market/dev`, and not one that
469
+ * holds `market/prod` too. One that holds nothing, only the person who
470
+ * admitted it: anyone else's token for it would read whatever it is given
471
+ * next. Those who run the instance manage every one.
472
+ */
473
+ function managesService(holder, setting, actor, account) {
474
+ if (runsInstance(holder)) return true;
475
+ if (account.grants.length === 0) return setting !== null && account.admittedBy === actor;
476
+ return account.grants.every((grant) => givesService(holder, setting, grant.role, grant.place));
477
+ }
478
+ /**
479
+ * Whether `holder`, the member `actor`, may issue a token or a trust
480
+ * binding of `account` whose scope is `scope`: they manage the account
481
+ * (`managesService`), and each grant of the scope is one they could give it
482
+ * (`givesService`) and one it holds, every permission of its role there.
483
+ * For an account that holds `developer` on `market/dev`, a Developer of
484
+ * `dev` issues `developer` or `viewer` there; nobody issues `market/prod`
485
+ * for it, or `maintainer` on `market/dev`, until the account holds them.
486
+ */
487
+ function issuesCredential(holder, setting, actor, account, scope) {
488
+ if (!managesService(holder, setting, actor, account)) return false;
489
+ const held = {
490
+ ...NOTHING,
491
+ grants: account.grants.map(({ role, place }) => ({
492
+ projectId: place.projectId,
493
+ environmentId: place.environmentId ?? null,
494
+ role
495
+ }))
496
+ };
497
+ return scope.every(({ role, place }) => givesService(holder, setting, role, place) && ROLES[role].permissions.every((permission) => allows(held, permission, place)));
498
+ }
499
+ /**
500
+ * Whether the holder reads the whole log, the instance's own entries
501
+ * (sign-ins, people, service accounts, settings) with every project's, and
502
+ * verifies it: whoever holds `audit.read` by an instance role whose scope
503
+ * narrows nothing, an Auditor's included, and a root admin. Reading, not
504
+ * running: an Auditor of the whole instance changes nothing it reads about.
505
+ * Anyone else reads the places they hold `audit.read` in.
506
+ */
507
+ function readsWholeLog(holder) {
508
+ return holder.isRootAdmin || instanceRoleGrants(holder.role, "audit.read") && unscoped(holder.scope);
509
+ }
510
+ /**
511
+ * Whether the holder may make a project: an Admin or Owner whose scope
512
+ * takes the new one in as a whole. A scope listing only some projects does
513
+ * not list the new one; one that keeps only some environments does not
514
+ * hold the project around them.
515
+ */
516
+ function makesProjects(holder) {
517
+ if (holder.isRootAdmin) return true;
518
+ const { projects, environments } = holder.scope;
519
+ return administers(holder.role) && environments === "all" && (projects === "all" || "except" in projects);
520
+ }
521
+ //#endregion
522
+ export { roleGrants as A, isScope as C, mayManageAccess as D, managesService as E, convertEveryProjectGrants as F, scopeInWords as M, setsUpServices as N, normalScope as O, unscoped as P, isRole as S, makesProjects as T, grantKind as _, NOTHING as a, isCredentialScope as b, ROLES as c, admits as d, allows as f, givesService as g, credentialScopeInWords as h, MAX_SCOPE_PLACES as i, runsInstance as j, readsWholeLog as k, ROLE_NAMES as l, covers as m, INSTANCE_ROLES as n, PERMISSIONS as o, assignableToEnvironment as p, INSTANCE_ROLE_NAMES as r, PROJECT_ONLY_PERMISSIONS as s, EVERYWHERE as t, administers as u, inScope as v, issuesCredential as w, isInstanceRole as x, instanceRoleGrants as y };
package/dist/access.d.ts CHANGED
@@ -1,136 +1,2 @@
1
- //#region src/access.d.ts
2
- /**
3
- * Who may do what: seven permissions and the six roles that bundle them.
4
- *
5
- * Deliberately constants and not tables or a policy language. A grant names
6
- * one role for one member on one place, and a check is a lookup in this file,
7
- * so authorisation stays something you can read end to end.
8
- */
9
- export declare const PERMISSIONS: readonly ["secret.read", "secret.write", "secret.archive", "audit.read", "environment.manage", "grant.manage", "project.manage"];
10
- export type Permission = (typeof PERMISSIONS)[number];
11
- /**
12
- * Permissions that only make sense on a project. Granting
13
- * `environment.manage` on one environment would be incoherent: what it
14
- * authorises is creating that environment's siblings.
15
- */
16
- export declare const PROJECT_ONLY_PERMISSIONS: readonly Permission[];
17
- export declare const ROLES: {
18
- readonly viewer: {
19
- readonly name: "Viewer";
20
- readonly description: "Read secret values.";
21
- readonly permissions: readonly ["secret.read"];
22
- };
23
- readonly developer: {
24
- readonly name: "Developer";
25
- readonly description: "Read and write secrets.";
26
- readonly permissions: readonly ["secret.read", "secret.write"];
27
- };
28
- readonly maintainer: {
29
- readonly name: "Maintainer";
30
- readonly description: "Read, write and retire secrets, and manage environments.";
31
- readonly permissions: readonly ["secret.read", "secret.write", "secret.archive", "environment.manage"];
32
- };
33
- readonly 'access-manager': {
34
- readonly name: "Access manager";
35
- readonly description: "Manage who has access. Cannot read secret values.";
36
- readonly permissions: readonly ["grant.manage"];
37
- };
38
- readonly auditor: {
39
- readonly name: "Auditor";
40
- readonly description: "Read the audit log. Cannot read secret values.";
41
- readonly permissions: readonly ["audit.read"];
42
- };
43
- readonly owner: {
44
- readonly name: "Owner";
45
- readonly description: "Everything, including reading secret values.";
46
- readonly permissions: readonly ["secret.read", "secret.write", "secret.archive", "audit.read", "environment.manage", "grant.manage", "project.manage"];
47
- };
48
- };
49
- export type Role = keyof typeof ROLES;
50
- export declare const ROLE_NAMES: Role[];
51
- export declare function isRole(value: string): value is Role;
52
- export declare function roleGrants(role: Role, permission: Permission): boolean;
53
- /** Roles that hold a project-only permission can only be granted on a project. */
54
- export declare function assignableToEnvironment(role: Role): boolean;
55
- /**
56
- * A place a permission applies to: a project, or one of its environments,
57
- * named by its slug too, which grants on every project match by. A slug
58
- * not read (null) matches no grant on a slug.
59
- */
60
- export type Place = {
61
- projectId: string;
62
- environmentId?: null;
63
- } | {
64
- projectId: string;
65
- environmentId: string;
66
- environmentSlug: string | null;
67
- };
68
- /**
69
- * Where a grant applies, by the fields it fills (`grantKind`):
70
- *
71
- * market projectId
72
- * market/dev projectId and environmentId
73
- * * neither: every project, the ones created later too
74
- * *, on dev environmentSlug only: the environment of that slug in every project
75
- */
76
- export type GrantPlace = {
77
- /** The project, also of an environment's grant; null only on every project. */
78
- projectId: string | null;
79
- environmentId: string | null;
80
- /** On every project, the one environment slug it covers; null for all of them, and on a project's grant. */
81
- environmentSlug: string | null;
82
- };
83
- /**
84
- * What a grant's fields say it is on, or null when they say nothing
85
- * coherent: an environment without its project, as a grant whose
86
- * environment row is gone reads, or a slug beside an id. A grant is on
87
- * every project only when it names no place at all; anything else that
88
- * lacks a field covers nothing.
89
- */
90
- export declare function grantKind(grant: GrantPlace): 'project' | 'environment' | 'every-project' | null;
91
- /** Every project, as a path names it. */
92
- export declare const EVERY_PROJECT = "*";
93
- /** A grant on every project as a path: `*`, or `*` and the environment slug it covers in each. */
94
- export declare function everyProjectPath(environmentSlug: string | null): string;
95
- /**
96
- * What someone holds: everything a permission check reads. The app builds
97
- * it from what the vault says once per request; the vault builds it from
98
- * its own store. Both then ask the same functions below, so the rules
99
- * cannot drift apart.
100
- */
101
- export type Holdings = {
102
- /** Named in the vault's configuration: everything, everywhere. */
103
- isRootAdmin: boolean;
104
- /** Root admins and active users with the instance `owner` role. */
105
- isOwner: boolean;
106
- /** Live grants only. */
107
- grants: readonly (GrantPlace & {
108
- role: Role;
109
- })[];
110
- };
111
- /**
112
- * Whether a grant at `grant` reaches `place`:
113
- *
114
- * market market and each of its environments
115
- * market/dev market/dev
116
- * * every project and every environment
117
- * *, on dev each environment whose slug is dev, in every project
118
- *
119
- * An environment's grant never reaches up to its project, whether it names
120
- * the environment or its slug.
121
- */
122
- export declare function covers(grant: GrantPlace, place: Place): boolean;
123
- /**
124
- * Whether `holder` may do `permission` at `place`: a grant that covers it
125
- * gives the role's permissions there, and grants add up. Owners also manage
126
- * every project. `developer` on `market/prod` does not let anyone rename
127
- * `market`.
128
- */
129
- export declare function allows(holder: Holdings, permission: Permission, place: Place): boolean;
130
- /**
131
- * Whether the actor may manage grants at this place: on a project or its
132
- * environments, with `grant.manage` on the project; on every project, as an
133
- * instance owner or a root admin only.
134
- */
135
- export declare function mayManageAccess(actor: Holdings, place: GrantPlace): boolean;
136
- //#endregion
1
+ import { A as instanceRoleGrants, B as readsWholeLog, C as allows, D as givesService, E as credentialScopeInWords, F as issuesCredential, G as unscoped, H as runsInstance, I as makesProjects, J as ConvertedGrant, K as Conversion, L as managesService, M as isInstanceRole, N as isRole, O as grantKind, P as isScope, R as mayManageAccess, S as admits, T as covers, U as scopeInWords, V as roleGrants, W as setsUpServices, X as Narrowed, Y as EveryProjectGrant, Z as convertEveryProjectGrants, _ as Role, a as Holdings, b as ServiceSetting, c as InstanceRole, d as PERMISSIONS, f as PROJECT_ONLY_PERMISSIONS, g as ROLE_NAMES, h as ROLES, i as GrantPlace, j as isCredentialScope, k as inScope, l as MAX_SCOPE_PLACES, m as Place, n as EVERYWHERE, o as INSTANCE_ROLES, p as Permission, q as ConversionInput, r as Filter, s as INSTANCE_ROLE_NAMES, t as CredentialScope, u as NOTHING, v as Scope, w as assignableToEnvironment, x as administers, y as ServiceAccount, z as normalScope } from "./access-CwsFLfTz.js";
2
+ export { type Conversion, type ConversionInput, type ConvertedGrant, CredentialScope, EVERYWHERE, type EveryProjectGrant, Filter, GrantPlace, Holdings, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, InstanceRole, MAX_SCOPE_PLACES, NOTHING, type Narrowed, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, Permission, Place, ROLES, ROLE_NAMES, Role, Scope, ServiceAccount, ServiceSetting, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped };