@aventara/access 0.0.0-stage → 0.1.0-rc.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/CHANGELOG.md +831 -0
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +2439 -2
- package/dist/access/access-options.d.ts +66 -0
- package/dist/access/access-options.js +5 -0
- package/dist/access/access.error.d.ts +35 -0
- package/dist/access/access.error.js +11 -0
- package/dist/access/access.factory.d.ts +273 -0
- package/dist/access/access.factory.js +440 -0
- package/dist/access/better-auth.hardening.d.ts +120 -0
- package/dist/access/better-auth.hardening.js +420 -0
- package/dist/access/better-auth.loader.d.ts +67 -0
- package/dist/access/better-auth.loader.js +65 -0
- package/dist/access/better-auth.range.d.ts +9 -0
- package/dist/access/better-auth.range.js +28 -0
- package/dist/access/better-auth.require.d.ts +90 -0
- package/dist/access/better-auth.require.js +6 -0
- package/dist/access/better-auth.require.node.d.ts +7 -0
- package/dist/access/better-auth.require.node.js +52 -0
- package/dist/access/claims.mapper.d.ts +79 -0
- package/dist/access/claims.mapper.js +152 -0
- package/dist/access/key-set.cache.d.ts +10 -0
- package/dist/access/key-set.cache.js +63 -0
- package/dist/access/policies-block.checker.d.ts +64 -0
- package/dist/access/policies-block.checker.js +138 -0
- package/dist/access/principal.subject.d.ts +12 -0
- package/dist/access/principal.subject.js +60 -0
- package/dist/access/profile.policy.d.ts +74 -0
- package/dist/access/profile.policy.js +110 -0
- package/dist/access/request.state.d.ts +89 -0
- package/dist/access/request.state.js +196 -0
- package/dist/access/session.hooks.d.ts +114 -0
- package/dist/access/session.hooks.js +258 -0
- package/dist/access/token.verifier.d.ts +35 -0
- package/dist/access/token.verifier.js +69 -0
- package/dist/access-schema.d.ts +48 -0
- package/dist/access-schema.js +5 -0
- package/dist/access-warning.emitter.d.ts +2 -0
- package/dist/access-warning.emitter.js +20 -0
- package/dist/access.builder.d.ts +299 -0
- package/dist/access.builder.js +66 -0
- package/dist/access.engine.d.ts +142 -0
- package/dist/access.engine.js +470 -0
- package/dist/audit/audit-event.d.ts +135 -0
- package/dist/audit/audit-event.js +8 -0
- package/dist/audit/audit.log.d.ts +122 -0
- package/dist/audit/audit.log.js +345 -0
- package/dist/audit/audit.schema.d.ts +143 -0
- package/dist/audit/audit.schema.js +50 -0
- package/dist/auth/index.d.ts +12 -0
- package/dist/auth/index.js +2 -0
- package/dist/aventara-where.translator.d.ts +19 -0
- package/dist/aventara-where.translator.js +87 -0
- package/dist/beacon/beacon.check.d.ts +59 -0
- package/dist/beacon/beacon.check.js +128 -0
- package/dist/beacon/beacon.schema.d.ts +41 -0
- package/dist/beacon/beacon.schema.js +48 -0
- package/dist/beacon/beacon.store.d.ts +29 -0
- package/dist/beacon/beacon.store.js +78 -0
- package/dist/better-auth/access.client-plugin.d.ts +75 -0
- package/dist/better-auth/access.client-plugin.js +133 -0
- package/dist/better-auth/access.plugin.d.ts +164 -0
- package/dist/better-auth/access.plugin.js +348 -0
- package/dist/better-auth/access.provider.d.ts +78 -0
- package/dist/better-auth/access.provider.js +100 -0
- package/dist/better-auth/index.d.ts +7 -0
- package/dist/better-auth/index.js +4 -0
- package/dist/better-auth/organization.bridge.d.ts +66 -0
- package/dist/better-auth/organization.bridge.js +144 -0
- package/dist/canonical-json.writer.d.ts +12 -0
- package/dist/canonical-json.writer.js +30 -0
- package/dist/cli/avaccess.bin.d.ts +2 -0
- package/dist/cli/avaccess.bin.js +26 -0
- package/dist/cli/avaccess.program.d.ts +15 -0
- package/dist/cli/avaccess.program.js +166 -0
- package/dist/cli/beacon-pins.file.d.ts +14 -0
- package/dist/cli/beacon-pins.file.js +62 -0
- package/dist/cli/beacon.loader.d.ts +20 -0
- package/dist/cli/beacon.loader.js +71 -0
- package/dist/cli/cli.context.d.ts +49 -0
- package/dist/cli/cli.context.js +66 -0
- package/dist/cli/diff.command.d.ts +2 -0
- package/dist/cli/diff.command.js +66 -0
- package/dist/cli/generate.command.d.ts +2 -0
- package/dist/cli/generate.command.js +82 -0
- package/dist/cli/keys.command.d.ts +2 -0
- package/dist/cli/keys.command.js +96 -0
- package/dist/cli/lock.file.d.ts +31 -0
- package/dist/cli/lock.file.js +181 -0
- package/dist/cli/module.loader.d.ts +13 -0
- package/dist/cli/module.loader.js +63 -0
- package/dist/cli/policies.diff.d.ts +16 -0
- package/dist/cli/policies.diff.js +83 -0
- package/dist/cli/pull.command.d.ts +2 -0
- package/dist/cli/pull.command.js +234 -0
- package/dist/cli/push.command.d.ts +2 -0
- package/dist/cli/push.command.js +59 -0
- package/dist/cli/registry.client.d.ts +63 -0
- package/dist/cli/registry.client.js +287 -0
- package/dist/cli/schema.command.d.ts +2 -0
- package/dist/cli/schema.command.js +60 -0
- package/dist/cli/serve.command.d.ts +6 -0
- package/dist/cli/serve.command.js +200 -0
- package/dist/cli/status.command.d.ts +2 -0
- package/dist/cli/status.command.js +31 -0
- package/dist/cli/translator.flag.d.ts +6 -0
- package/dist/cli/translator.flag.js +24 -0
- package/dist/cli/verify.command.d.ts +2 -0
- package/dist/cli/verify.command.js +40 -0
- package/dist/client/access-client.factory.d.ts +92 -0
- package/dist/client/access-client.factory.js +269 -0
- package/dist/client/access-verbs.schema.d.ts +66 -0
- package/dist/client/access-verbs.schema.js +159 -0
- package/dist/client/verb-rate.limiter.d.ts +116 -0
- package/dist/client/verb-rate.limiter.js +358 -0
- package/dist/foreign/foreign-facts.d.ts +30 -0
- package/dist/foreign/foreign-facts.js +75 -0
- package/dist/foreign/foreign.merger.d.ts +10 -0
- package/dist/foreign/foreign.merger.js +52 -0
- package/dist/foreign-references.check.d.ts +12 -0
- package/dist/foreign-references.check.js +134 -0
- package/dist/generator/client.emitter.d.ts +28 -0
- package/dist/generator/client.emitter.js +411 -0
- package/dist/generator/copied-modules.reader.d.ts +14 -0
- package/dist/generator/copied-modules.reader.js +80 -0
- package/dist/generator/namespace.facts.d.ts +28 -0
- package/dist/generator/namespace.facts.js +32 -0
- package/dist/generator/orm.translator.d.ts +48 -0
- package/dist/generator/orm.translator.js +29 -0
- package/dist/generator/prisma7/prisma-schema.reader.d.ts +9 -0
- package/dist/generator/prisma7/prisma-schema.reader.js +394 -0
- package/dist/generator/prisma7/prisma-where.translator.d.ts +72 -0
- package/dist/generator/prisma7/prisma-where.translator.js +136 -0
- package/dist/generator/prisma7/prisma7.translator.d.ts +2 -0
- package/dist/generator/prisma7/prisma7.translator.js +87 -0
- package/dist/generator/schema.emitter.d.ts +14 -0
- package/dist/generator/schema.emitter.js +23 -0
- package/dist/generator/tsconfig.reader.d.ts +44 -0
- package/dist/generator/tsconfig.reader.js +110 -0
- package/dist/generator/typescript-literal.writer.d.ts +5 -0
- package/dist/generator/typescript-literal.writer.js +42 -0
- package/dist/guardrails/guardrails-contract.d.ts +45 -0
- package/dist/guardrails/guardrails-contract.js +12 -0
- package/dist/guardrails/guardrails.builder.d.ts +48 -0
- package/dist/guardrails/guardrails.builder.js +32 -0
- package/dist/guardrails/guardrails.compiler.d.ts +10 -0
- package/dist/guardrails/guardrails.compiler.js +254 -0
- package/dist/guardrails/guardrails.composer.d.ts +7 -0
- package/dist/guardrails/guardrails.composer.js +95 -0
- package/dist/guardrails/guardrails.hash.d.ts +10 -0
- package/dist/guardrails/guardrails.hash.js +38 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +6 -0
- package/dist/kiosk/kiosk-verbs.schema.d.ts +19 -0
- package/dist/kiosk/kiosk-verbs.schema.js +61 -0
- package/dist/kiosk/kiosk.plugin.d.ts +217 -0
- package/dist/kiosk/kiosk.plugin.js +877 -0
- package/dist/kiosk/kiosk.schema.d.ts +176 -0
- package/dist/kiosk/kiosk.schema.js +101 -0
- package/dist/kiosk/kiosk.store.d.ts +171 -0
- package/dist/kiosk/kiosk.store.js +478 -0
- package/dist/kiosk/lan-range.policy.d.ts +31 -0
- package/dist/kiosk/lan-range.policy.js +113 -0
- package/dist/kiosk/pin.policy.d.ts +9 -0
- package/dist/kiosk/pin.policy.js +143 -0
- package/dist/kiosk/session-use.marker.d.ts +4 -0
- package/dist/kiosk/session-use.marker.js +16 -0
- package/dist/lock/lock-contract.d.ts +38 -0
- package/dist/lock/lock-contract.js +67 -0
- package/dist/lock/lock.verifier.d.ts +27 -0
- package/dist/lock/lock.verifier.js +62 -0
- package/dist/mcp/agent-scope.gate.d.ts +11 -0
- package/dist/mcp/agent-scope.gate.js +75 -0
- package/dist/mcp/agent.grant.d.ts +36 -0
- package/dist/mcp/agent.grant.js +92 -0
- package/dist/mcp/mcp-options.d.ts +60 -0
- package/dist/mcp/mcp-options.js +72 -0
- package/dist/oauth/access-token.verifier.d.ts +66 -0
- package/dist/oauth/access-token.verifier.js +201 -0
- package/dist/oauth/oauth-provider.hardening.d.ts +62 -0
- package/dist/oauth/oauth-provider.hardening.js +261 -0
- package/dist/passwords/argon2id.hasher.d.ts +97 -0
- package/dist/passwords/argon2id.hasher.js +256 -0
- package/dist/passwords/node-rs-argon2.adapter.d.ts +25 -0
- package/dist/passwords/node-rs-argon2.adapter.js +42 -0
- package/dist/passwords/passwords.options.d.ts +79 -0
- package/dist/passwords/passwords.options.js +212 -0
- package/dist/policies-contract.d.ts +192 -0
- package/dist/policies-contract.js +8 -0
- package/dist/policies.compiler.d.ts +36 -0
- package/dist/policies.compiler.js +1358 -0
- package/dist/policies.decider.d.ts +103 -0
- package/dist/policies.decider.js +382 -0
- package/dist/policies.hash.d.ts +10 -0
- package/dist/policies.hash.js +53 -0
- package/dist/predicate.evaluator.d.ts +62 -0
- package/dist/predicate.evaluator.js +378 -0
- package/dist/random-secret.generator.d.ts +2 -0
- package/dist/random-secret.generator.js +15 -0
- package/dist/reference-reads.check.d.ts +12 -0
- package/dist/reference-reads.check.js +148 -0
- package/dist/registry/access.registry.d.ts +173 -0
- package/dist/registry/access.registry.js +865 -0
- package/dist/registry/beacon.ledger.d.ts +30 -0
- package/dist/registry/beacon.ledger.js +93 -0
- package/dist/registry/ownership.check.d.ts +54 -0
- package/dist/registry/ownership.check.js +215 -0
- package/dist/registry/registry.endpoints.d.ts +50 -0
- package/dist/registry/registry.endpoints.js +193 -0
- package/dist/registry/registry.key.d.ts +7 -0
- package/dist/registry/registry.key.js +13 -0
- package/dist/registry/registry.options.d.ts +13 -0
- package/dist/registry/registry.options.js +28 -0
- package/dist/registry/registry.refusal.d.ts +6 -0
- package/dist/registry/registry.refusal.js +11 -0
- package/dist/registry/registry.schema.d.ts +238 -0
- package/dist/registry/registry.schema.js +127 -0
- package/dist/resources/auth-tables.catalog.d.ts +91 -0
- package/dist/resources/auth-tables.catalog.js +102 -0
- package/dist/resources/auth-tables.guard.d.ts +95 -0
- package/dist/resources/auth-tables.guard.js +405 -0
- package/dist/scope.resolver.d.ts +138 -0
- package/dist/scope.resolver.js +512 -0
- package/dist/sha256.digest.d.ts +2 -0
- package/dist/sha256.digest.js +81 -0
- package/dist/standalone/access.server.d.ts +41 -0
- package/dist/standalone/access.server.js +301 -0
- package/dist/standalone/index.d.ts +1 -0
- package/dist/standalone/index.js +1 -0
- package/package.json +101 -3
- package/src/aventara-where.translator.ts +116 -0
- package/src/beacon/beacon.check.ts +204 -0
- package/src/canonical-json.writer.ts +51 -0
- package/src/generator/prisma7/prisma-where.translator.ts +241 -0
- package/src/policies-contract.ts +184 -0
- package/src/policies.decider.ts +650 -0
- package/src/predicate.evaluator.ts +574 -0
- package/src/scope.resolver.ts +791 -0
- package/src/sha256.digest.ts +85 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Builder → `guardrails@1` file, and the checks every guardrails file passes before
|
|
2
|
+
// a registry stores it or an engine applies it: the format, the members (a `grants`
|
|
3
|
+
// member, or a guardrail's `where`, is refused by name: guardrails only deny, and
|
|
4
|
+
// never read a row), the actions, the Resource lists, the principal conditions and
|
|
5
|
+
// the claims they compare. Errors name the guardrail.
|
|
6
|
+
import { asList, CLAIM_SCALARS, checkActions, compilePrincipalIf, fail, idAllocator, isNamespace, isRecord, ORIGINS, } from "../policies.compiler.js";
|
|
7
|
+
import { POLICIES_FORMAT, } from "../policies-contract.js";
|
|
8
|
+
import { GUARDRAILS_FORMAT, } from "./guardrails-contract.js";
|
|
9
|
+
/** The members of a `guardrails@1` file, and of one guardrail. Anything else is refused. */
|
|
10
|
+
const FILE_KEYS = new Set(["format", "claims", "denies"]);
|
|
11
|
+
const RULE_KEYS = new Set(["id", "actions", "resources", "if"]);
|
|
12
|
+
const GUARDRAIL_OPTIONS = new Set(["name", "if"]);
|
|
13
|
+
const NO_GRANT = "guardrails only deny: a grant belongs in the file of the namespace that owns its Resource";
|
|
14
|
+
const NO_ROW = "a guardrail reads the principal (claims, origin) and never a row; a row condition belongs in the denies of the namespace that owns the Resource";
|
|
15
|
+
/** The author's Resource lists, copied into the file (anything malformed is left for the checks). */
|
|
16
|
+
const resourcesCopy = (resources) => isRecord(resources)
|
|
17
|
+
? Object.fromEntries(Object.entries(resources).map(([namespace, list]) => [
|
|
18
|
+
namespace,
|
|
19
|
+
Array.isArray(list) ? [...list] : list,
|
|
20
|
+
]))
|
|
21
|
+
: resources;
|
|
22
|
+
/** Compiles the builder's guardrails to the `guardrails@1` file and checks it. */
|
|
23
|
+
export const compileGuardrails = (definition) => {
|
|
24
|
+
const claimTypes = {};
|
|
25
|
+
const allocate = idAllocator();
|
|
26
|
+
const denies = definition.denies.map((rule) => {
|
|
27
|
+
const actions = rule.actions === "*" ? "*" : [...asList(rule.actions)];
|
|
28
|
+
const prefix = `guardrail/${actions === "*" ? "*" : actions.join(",")}`;
|
|
29
|
+
for (const key of Object.keys(rule.options)) {
|
|
30
|
+
if (key === "where") {
|
|
31
|
+
fail(prefix, `\`where\` is not a guardrail option: ${NO_ROW}`);
|
|
32
|
+
}
|
|
33
|
+
if (!GUARDRAIL_OPTIONS.has(key)) {
|
|
34
|
+
fail(prefix, `unknown guardrail option "${key}" (name, if)`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const id = allocate(prefix, rule.options.name);
|
|
38
|
+
const condition = rule.options.if === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: compilePrincipalIf(id, rule.options.if, claimTypes);
|
|
41
|
+
if (condition !== undefined) {
|
|
42
|
+
checkDeclared(id, condition, definition.claims);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
actions,
|
|
47
|
+
resources: resourcesCopy(rule.resources),
|
|
48
|
+
...(condition !== undefined ? { if: condition } : {}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const guardrails = {
|
|
52
|
+
format: GUARDRAILS_FORMAT,
|
|
53
|
+
claims: Object.fromEntries(Object.entries(claimTypes).sort(([a], [b]) => a.localeCompare(b))),
|
|
54
|
+
denies,
|
|
55
|
+
};
|
|
56
|
+
checkGuardrails(guardrails);
|
|
57
|
+
return guardrails;
|
|
58
|
+
};
|
|
59
|
+
/** Each claim a guardrail's condition compares is in `defineGuardrails({ claims })`, as the type it is compared as. */
|
|
60
|
+
const checkDeclared = (rule, condition, declared) => {
|
|
61
|
+
switch (condition.op) {
|
|
62
|
+
case "and":
|
|
63
|
+
for (const arg of condition.args) {
|
|
64
|
+
checkDeclared(rule, arg, declared);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
case "origin":
|
|
68
|
+
return;
|
|
69
|
+
case "cmp": {
|
|
70
|
+
const [claim] = condition.claim;
|
|
71
|
+
const scalar = typeof condition.value;
|
|
72
|
+
const type = declared?.[claim];
|
|
73
|
+
if (type === undefined) {
|
|
74
|
+
fail(rule, `compares the claim "${claim}", which defineGuardrails({ claims }) does not declare; declare it as "${scalar}"`);
|
|
75
|
+
}
|
|
76
|
+
if (type !== scalar) {
|
|
77
|
+
fail(rule, `compares the claim "${claim}" with a ${scalar}, and defineGuardrails({ claims }) declares it as ${type}`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Checks every guardrails file passes (a compiled one, or one pulled as JSON)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
const LITERAL_TYPES = new Set(["string", "number", "boolean"]);
|
|
87
|
+
/** A principal condition, member by member, against the file's `claims` block. */
|
|
88
|
+
const checkCondition = (rule, condition, claims) => {
|
|
89
|
+
if (!isRecord(condition)) {
|
|
90
|
+
fail(rule, "`if` is a condition object");
|
|
91
|
+
}
|
|
92
|
+
const node = condition;
|
|
93
|
+
const only = (...keys) => {
|
|
94
|
+
for (const key of Object.keys(node)) {
|
|
95
|
+
if (!keys.includes(key)) {
|
|
96
|
+
fail(rule, `"${key}" is not a member of an "${String(node.op)}" condition (${keys.join(", ")})`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
switch (node.op) {
|
|
101
|
+
case "and": {
|
|
102
|
+
only("op", "args");
|
|
103
|
+
if (!Array.isArray(node.args) || node.args.length === 0) {
|
|
104
|
+
fail(rule, 'an "and" condition takes a non-empty `args` list');
|
|
105
|
+
}
|
|
106
|
+
for (const arg of node.args) {
|
|
107
|
+
checkCondition(rule, arg, claims);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
case "origin":
|
|
112
|
+
only("op", "value");
|
|
113
|
+
if (!ORIGINS.includes(node.value)) {
|
|
114
|
+
fail(rule, `unknown origin ${JSON.stringify(node.value)} (application, client, agent)`);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
case "cmp": {
|
|
118
|
+
only("op", "claim", "cmp", "value");
|
|
119
|
+
const path = node.claim;
|
|
120
|
+
if (!Array.isArray(path) ||
|
|
121
|
+
path.length !== 1 ||
|
|
122
|
+
typeof path[0] !== "string") {
|
|
123
|
+
fail(rule, 'a claim comparison names one claim: `"claim": ["<name>"]`');
|
|
124
|
+
}
|
|
125
|
+
const claim = path[0];
|
|
126
|
+
if (node.cmp !== "eq") {
|
|
127
|
+
fail(rule, `a claim comparison is "eq"; it was ${JSON.stringify(node.cmp)}`);
|
|
128
|
+
}
|
|
129
|
+
const type = typeof node.value;
|
|
130
|
+
if (!LITERAL_TYPES.has(type) ||
|
|
131
|
+
(type === "number" && !Number.isFinite(node.value))) {
|
|
132
|
+
fail(rule, `claim "${claim}" compares with a string, a finite number or a boolean`);
|
|
133
|
+
}
|
|
134
|
+
const declared = Object.hasOwn(claims, claim) ? claims[claim] : undefined;
|
|
135
|
+
if (declared === undefined) {
|
|
136
|
+
fail(rule, `compares the claim "${claim}", which the file's "claims" block does not list`);
|
|
137
|
+
}
|
|
138
|
+
if (declared?.scalar !== type) {
|
|
139
|
+
fail(rule, `compares the claim "${claim}" with a ${type}, and the file's "claims" block lists it as ${declared?.scalar}`);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
default:
|
|
144
|
+
fail(rule, `unknown condition ${JSON.stringify(node.op)}: a guardrail's \`if\` is "and", "cmp" on a claim, or "origin"`);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/** A guardrail's `resources`: `"*"`, or namespaces to `"*"` or a non-empty list of Resource names. */
|
|
148
|
+
const checkResources = (rule, resources) => {
|
|
149
|
+
if (resources === "*") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!isRecord(resources)) {
|
|
153
|
+
fail(rule, '`resources` is "*" or, by namespace, "*" or a list of Resources: { "campaign": ["Note"] }');
|
|
154
|
+
}
|
|
155
|
+
const entries = Object.entries(resources);
|
|
156
|
+
if (entries.length === 0) {
|
|
157
|
+
fail(rule, "`resources` names no namespace");
|
|
158
|
+
}
|
|
159
|
+
for (const [namespace, list] of entries) {
|
|
160
|
+
if (!isNamespace(namespace)) {
|
|
161
|
+
fail(rule, `${JSON.stringify(namespace)} is not a namespace name (a lowercase letter, then lowercase letters, digits, "_" or "-"; at most 63 characters)`);
|
|
162
|
+
}
|
|
163
|
+
if (list === "*") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(list) ||
|
|
167
|
+
list.length === 0 ||
|
|
168
|
+
list.some((name) => typeof name !== "string" || name === "")) {
|
|
169
|
+
fail(rule, `namespace "${namespace}": "*" or a non-empty list of Resource names`);
|
|
170
|
+
}
|
|
171
|
+
if (new Set(list).size !==
|
|
172
|
+
list.length) {
|
|
173
|
+
fail(rule, `namespace "${namespace}" names a Resource twice`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Checks a guardrails file before a registry stores it or an engine applies it.
|
|
179
|
+
* Throws an `Error` naming the guardrail. The file is trusted for nothing it can be
|
|
180
|
+
* checked for.
|
|
181
|
+
*/
|
|
182
|
+
export const checkGuardrails = (guardrails) => {
|
|
183
|
+
if (!isRecord(guardrails)) {
|
|
184
|
+
fail("guardrails", "a guardrails file is a JSON object");
|
|
185
|
+
}
|
|
186
|
+
const file = guardrails;
|
|
187
|
+
if (file.format === POLICIES_FORMAT) {
|
|
188
|
+
fail("guardrails", `this is a policies file ("${POLICIES_FORMAT}"); the org's guardrails are a "${GUARDRAILS_FORMAT}" file. Write them with defineGuardrails(...).contract()`);
|
|
189
|
+
}
|
|
190
|
+
if (file.format !== GUARDRAILS_FORMAT) {
|
|
191
|
+
fail("guardrails", `format is ${JSON.stringify(file.format) ?? "missing"}, expected "${GUARDRAILS_FORMAT}"`);
|
|
192
|
+
}
|
|
193
|
+
for (const key of Object.keys(file)) {
|
|
194
|
+
if (key === "grants") {
|
|
195
|
+
fail("guardrails", `"grants" is not a member of ${GUARDRAILS_FORMAT}: ${NO_GRANT}`);
|
|
196
|
+
}
|
|
197
|
+
if (!FILE_KEYS.has(key)) {
|
|
198
|
+
fail("guardrails", `"${key}" is not a member of ${GUARDRAILS_FORMAT} (format, claims, denies)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!isRecord(file.claims)) {
|
|
202
|
+
fail("guardrails", '"claims" is an object');
|
|
203
|
+
}
|
|
204
|
+
if (!Array.isArray(file.denies)) {
|
|
205
|
+
fail("guardrails", '"denies" is a list');
|
|
206
|
+
}
|
|
207
|
+
for (const [claim, type] of Object.entries(guardrails.claims)) {
|
|
208
|
+
if (!isRecord(type) ||
|
|
209
|
+
Object.keys(type).some((key) => key !== "scalar") ||
|
|
210
|
+
!CLAIM_SCALARS.includes(type.scalar)) {
|
|
211
|
+
fail(`claim "${claim}"`, 'a claim\'s type is { "scalar": "string" | "number" | "boolean" }');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const ids = new Set();
|
|
215
|
+
for (const [index, deny] of guardrails.denies.entries()) {
|
|
216
|
+
if (!isRecord(deny)) {
|
|
217
|
+
fail(`guardrail ${index + 1}`, "a guardrail is an object");
|
|
218
|
+
}
|
|
219
|
+
const rule = deny;
|
|
220
|
+
if (typeof rule.id !== "string" || rule.id === "") {
|
|
221
|
+
fail(`guardrail ${index + 1}`, "`id` is a non-empty string");
|
|
222
|
+
}
|
|
223
|
+
const id = rule.id;
|
|
224
|
+
if (ids.has(id)) {
|
|
225
|
+
fail(id, "two guardrails have this id");
|
|
226
|
+
}
|
|
227
|
+
ids.add(id);
|
|
228
|
+
for (const key of Object.keys(rule)) {
|
|
229
|
+
if (key === "where") {
|
|
230
|
+
fail(id, `"where" is not a member of a guardrail: ${NO_ROW}`);
|
|
231
|
+
}
|
|
232
|
+
if (key === "subjects") {
|
|
233
|
+
fail(id, `"subjects" is not a member of a guardrail: ${NO_GRANT}`);
|
|
234
|
+
}
|
|
235
|
+
if (!RULE_KEYS.has(key)) {
|
|
236
|
+
fail(id, `"${key}" is not a member of a guardrail (id, actions, resources, if)`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (rule.actions !== "*") {
|
|
240
|
+
if (!Array.isArray(rule.actions)) {
|
|
241
|
+
fail(id, '`actions` is "*" or a list of actions');
|
|
242
|
+
}
|
|
243
|
+
checkActions(id, rule.actions);
|
|
244
|
+
if (new Set(rule.actions).size !==
|
|
245
|
+
rule.actions.length) {
|
|
246
|
+
fail(id, "names an action twice");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
checkResources(id, rule.resources);
|
|
250
|
+
if (rule.if !== undefined) {
|
|
251
|
+
checkCondition(id, rule.if, guardrails.claims);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AccessPolicies } from "../policies-contract.js";
|
|
2
|
+
import type { AccessGuardrails } from "./guardrails-contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* The policies file with the guardrails that reach it added as denies. Both files
|
|
5
|
+
* are checked first; a guardrail that cannot be applied as written throws, naming it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const composeGuardrails: (policies: AccessPolicies, guardrails: AccessGuardrails) => AccessPolicies;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// How a service's engine applies the org's guardrails: each guardrail that reaches
|
|
2
|
+
// the service's Resources becomes a deny of the policies file the engine runs from.
|
|
3
|
+
// A deny only narrows (any deny wins over every grant), and a guardrail adds no
|
|
4
|
+
// grant, no role, no governed field and no row condition, so the composition can
|
|
5
|
+
// take away what the service's own grants allow and never add to it.
|
|
6
|
+
//
|
|
7
|
+
// Which Resources a guardrail reaches:
|
|
8
|
+
// - `"*"`: every Resource the service asks about;
|
|
9
|
+
// - a namespace's list: only when the namespace is the file's own. Another
|
|
10
|
+
// namespace's list is that namespace's business and is left alone. A Resource in
|
|
11
|
+
// the file's own list that the file lacks, or reads as another namespace's, is a
|
|
12
|
+
// typo or a wrong owner: it is refused, since the guardrail would otherwise apply
|
|
13
|
+
// to nothing without a word.
|
|
14
|
+
import { fail } from "../policies.compiler.js";
|
|
15
|
+
import { ownOf } from "../scope.resolver.js";
|
|
16
|
+
import { checkGuardrails } from "./guardrails.compiler.js";
|
|
17
|
+
/** The prefix of the deny ids a guardrail becomes, so `explain` tells them from the file's own. */
|
|
18
|
+
const GUARDRAIL_ID = "guardrail:";
|
|
19
|
+
/** The file's own Resources: its schema entries not marked foreign. */
|
|
20
|
+
const ownResources = (policies) => Object.entries(policies.schema)
|
|
21
|
+
.filter(([, facts]) => facts.foreign === undefined)
|
|
22
|
+
.map(([resource]) => resource);
|
|
23
|
+
/** The Resources of `policies` that one guardrail narrows, by name; `"*"` for every one. */
|
|
24
|
+
const reach = (policies, guardrail) => {
|
|
25
|
+
const { resources } = guardrail;
|
|
26
|
+
if (resources === "*") {
|
|
27
|
+
return "*";
|
|
28
|
+
}
|
|
29
|
+
const own = policies.namespace;
|
|
30
|
+
const rule = `guardrail "${guardrail.id}"`;
|
|
31
|
+
if (own === undefined) {
|
|
32
|
+
return fail(rule, `names Resources by namespace (${Object.keys(resources).join(", ")}), and this policies file names no namespace, so it cannot tell which list is its own. Give the rules their namespace: definePolicies(model, { namespace })`);
|
|
33
|
+
}
|
|
34
|
+
const list = Object.hasOwn(resources, own) ? resources[own] : undefined;
|
|
35
|
+
if (list === undefined) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (list === "*") {
|
|
39
|
+
return ownResources(policies);
|
|
40
|
+
}
|
|
41
|
+
for (const resource of list) {
|
|
42
|
+
const facts = ownOf(policies.schema, resource);
|
|
43
|
+
if (facts === undefined) {
|
|
44
|
+
fail(rule, `names ${resource} for namespace "${own}", this file's namespace, and the file has no Resource ${resource}. A guardrail on a misspelled Resource would narrow nothing, so it is refused: correct the guardrail's Resource list`);
|
|
45
|
+
}
|
|
46
|
+
if (facts?.foreign !== undefined) {
|
|
47
|
+
fail(rule, `names ${resource} for namespace "${own}", and this file reads ${resource} as namespace "${facts.foreign}"'s (foreign). List it under "${facts.foreign}" in the guardrail`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return list;
|
|
51
|
+
};
|
|
52
|
+
/** The claim types the rules and the guardrails compare, refusing a claim the two compare as different types. */
|
|
53
|
+
const mergedClaims = (policies, guardrails) => {
|
|
54
|
+
for (const [claim, { scalar }] of Object.entries(guardrails.claims)) {
|
|
55
|
+
const ruled = ownOf(policies.claims, claim);
|
|
56
|
+
if (ruled !== undefined && ruled.scalar !== scalar) {
|
|
57
|
+
fail("guardrails", `the guardrails compare the claim "${claim}" with a ${scalar}, and the rules compare it with a ${ruled.scalar}; make the two agree`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Object.fromEntries(Object.entries({ ...guardrails.claims, ...policies.claims }).sort(([a], [b]) => a.localeCompare(b)));
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The policies file with the guardrails that reach it added as denies. Both files
|
|
64
|
+
* are checked first; a guardrail that cannot be applied as written throws, naming it.
|
|
65
|
+
*/
|
|
66
|
+
export const composeGuardrails = (policies, guardrails) => {
|
|
67
|
+
checkGuardrails(guardrails);
|
|
68
|
+
const denies = [];
|
|
69
|
+
for (const guardrail of guardrails.denies) {
|
|
70
|
+
const condition = guardrail.if === undefined ? {} : { if: guardrail.if };
|
|
71
|
+
const reached = reach(policies, guardrail);
|
|
72
|
+
if (reached === "*") {
|
|
73
|
+
denies.push({
|
|
74
|
+
id: `${GUARDRAIL_ID}${guardrail.id}`,
|
|
75
|
+
actions: guardrail.actions,
|
|
76
|
+
resource: "*",
|
|
77
|
+
...condition,
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const resource of reached) {
|
|
82
|
+
denies.push({
|
|
83
|
+
id: `${GUARDRAIL_ID}${guardrail.id}/${resource}`,
|
|
84
|
+
actions: guardrail.actions,
|
|
85
|
+
resource,
|
|
86
|
+
...condition,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
...policies,
|
|
92
|
+
claims: mergedClaims(policies, guardrails),
|
|
93
|
+
denies: [...policies.denies, ...denies],
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AccessGuardrails } from "./guardrails-contract.js";
|
|
2
|
+
/** The canonical text of a `guardrails@1` file (see the header). The file is not checked here. */
|
|
3
|
+
export declare const canonicalGuardrails: (guardrails: AccessGuardrails) => string;
|
|
4
|
+
/**
|
|
5
|
+
* The canonical hash of a guardrails file: `sha256:` and the SHA-256 of its
|
|
6
|
+
* canonical byte form, in lowercase hex. The same guardrails give the same hash
|
|
7
|
+
* however the file's members and guardrails are ordered. The file is checked first,
|
|
8
|
+
* so a file the engine would refuse has no hash.
|
|
9
|
+
*/
|
|
10
|
+
export declare const guardrailsHash: (guardrails: AccessGuardrails) => string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The canonical byte form of a `guardrails@1` file and its hash, by the same rules as
|
|
2
|
+
// a policies file's (`policies.hash.ts`): the registry keys guardrails revisions by
|
|
3
|
+
// it, and a service's lock pins it.
|
|
4
|
+
//
|
|
5
|
+
// The canonical form is the file's JSON text, written by `canonical-json.writer.ts`
|
|
6
|
+
// (members sorted by key, no whitespace) and encoded as UTF-8, with the lists whose
|
|
7
|
+
// order the format gives no meaning sorted: `denies` by `id`, each guardrail's
|
|
8
|
+
// `actions`, and each namespace's Resource list. A condition's arguments keep their
|
|
9
|
+
// order.
|
|
10
|
+
import { byId, canonicalJson, sortedNames, } from "../canonical-json.writer.js";
|
|
11
|
+
import { sha256Hex } from "../sha256.digest.js";
|
|
12
|
+
import { checkGuardrails } from "./guardrails.compiler.js";
|
|
13
|
+
const canonicalResources = (resources) => resources === "*"
|
|
14
|
+
? "*"
|
|
15
|
+
: Object.fromEntries(Object.entries(resources).map(([namespace, list]) => [
|
|
16
|
+
namespace,
|
|
17
|
+
list === "*" ? "*" : sortedNames(list),
|
|
18
|
+
]));
|
|
19
|
+
const canonicalRule = (rule) => ({
|
|
20
|
+
...rule,
|
|
21
|
+
actions: rule.actions === "*" ? "*" : sortedNames(rule.actions),
|
|
22
|
+
resources: canonicalResources(rule.resources),
|
|
23
|
+
});
|
|
24
|
+
/** The canonical text of a `guardrails@1` file (see the header). The file is not checked here. */
|
|
25
|
+
export const canonicalGuardrails = (guardrails) => canonicalJson({
|
|
26
|
+
...guardrails,
|
|
27
|
+
denies: [...guardrails.denies].map(canonicalRule).sort(byId),
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* The canonical hash of a guardrails file: `sha256:` and the SHA-256 of its
|
|
31
|
+
* canonical byte form, in lowercase hex. The same guardrails give the same hash
|
|
32
|
+
* however the file's members and guardrails are ordered. The file is checked first,
|
|
33
|
+
* so a file the engine would refuse has no hash.
|
|
34
|
+
*/
|
|
35
|
+
export const guardrailsHash = (guardrails) => {
|
|
36
|
+
checkGuardrails(guardrails);
|
|
37
|
+
return `sha256:${sha256Hex(canonicalGuardrails(guardrails))}`;
|
|
38
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { AccessError, type AccessErrorCode } from "./access/access.error.js";
|
|
2
|
+
export type { AccessClaimsDeclaration, AccessClaimsOf, } from "./access/claims.mapper.js";
|
|
3
|
+
export { type AccessBuilder, type AccessClaimsCondition, type AccessDenyOptions, type AccessGrantOptions, type AccessPrincipalRef, type AccessRoleDefinitions, type AccessWhereInput, definePolicies, } from "./access.builder.js";
|
|
4
|
+
export { type AccessEngine, type AccessEngineOptions, type AccessExplanation, createAccessEngine, } from "./access.engine.js";
|
|
5
|
+
export type { AccessSchema } from "./access-schema.js";
|
|
6
|
+
export type { AccessWhere } from "./aventara-where.translator.js";
|
|
7
|
+
export type { AccessBeaconPins, AccessBeaconReader, } from "./beacon/beacon.check.js";
|
|
8
|
+
export type { AccessBeaconStore } from "./beacon/beacon.store.js";
|
|
9
|
+
export { type AccessGuardrailDeny, type AccessGuardrailDenyFunction, type AccessGuardrailOptions, type AccessGuardrailsBuilder, defineGuardrails, } from "./guardrails/guardrails.builder.js";
|
|
10
|
+
export { guardrailsHash } from "./guardrails/guardrails.hash.js";
|
|
11
|
+
export type { AccessGuardrailResources, AccessGuardrailRule, AccessGuardrails, AccessGuardrailsJson, } from "./guardrails/guardrails-contract.js";
|
|
12
|
+
export type { AccessLock, AccessLockPin } from "./lock/lock-contract.js";
|
|
13
|
+
export type { AccessCanQuestion, AccessFields, AccessFieldsQuestion, AccessGrantsQuestion, AccessQuestion, AccessRuleOutcome, AccessScopeQuestion, } from "./policies.decider.js";
|
|
14
|
+
export { policiesHash } from "./policies.hash.js";
|
|
15
|
+
export type { AccessAction, AccessBuiltInSubject, AccessClaimType, AccessComparison, AccessDenyRule, AccessGrantRule, AccessLiteral, AccessOperand, AccessOrigin, AccessPolicies, AccessPoliciesJson, AccessPredicate, AccessPrincipalCondition, AccessPrincipalOperand, AccessRoleDefinition, AccessSchemaRelationFact, AccessSchemaResourceFacts, } from "./policies-contract.js";
|
|
16
|
+
export type { AccessExists, AccessLookup, AccessRow, } from "./predicate.evaluator.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AccessError } from "./access/access.error.js";
|
|
2
|
+
export { definePolicies, } from "./access.builder.js";
|
|
3
|
+
export { createAccessEngine, } from "./access.engine.js";
|
|
4
|
+
export { defineGuardrails, } from "./guardrails/guardrails.builder.js";
|
|
5
|
+
export { guardrailsHash } from "./guardrails/guardrails.hash.js";
|
|
6
|
+
export { policiesHash } from "./policies.hash.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AccessVerbRoute } from "../client/access-verbs.schema.js";
|
|
2
|
+
/** The kiosk verbs, by name. */
|
|
3
|
+
export type AccessKioskVerbName = "switchUser" | "lock" | "setPin" | "enrollDevice" | "revokeDevice";
|
|
4
|
+
/** The PIN switch's path: a route that starts a session. */
|
|
5
|
+
export declare const KIOSK_SWITCH_PATH = "/kiosk/switch-user";
|
|
6
|
+
export declare const KIOSK_LOCK_PATH = "/kiosk/lock";
|
|
7
|
+
export declare const KIOSK_SET_PIN_PATH = "/kiosk/set-pin";
|
|
8
|
+
export declare const KIOSK_ENROLL_PATH = "/kiosk/enroll-device";
|
|
9
|
+
export declare const KIOSK_REVOKE_PATH = "/kiosk/revoke-device";
|
|
10
|
+
/** A manager's device list: a route, and `access.kiosk` on the server, but no verb (the framework's set of verbs is closed). */
|
|
11
|
+
export declare const KIOSK_LIST_PATH = "/kiosk/list-devices";
|
|
12
|
+
/** Every kiosk route lives under this prefix. */
|
|
13
|
+
export declare const KIOSK_PREFIX = "/kiosk/";
|
|
14
|
+
/** The kiosk verbs' routes, in the order `access.client.verbs` lists them after Better Auth's. */
|
|
15
|
+
export declare const KIOSK_VERBS: {
|
|
16
|
+
readonly [V in AccessKioskVerbName]: AccessVerbRoute;
|
|
17
|
+
};
|
|
18
|
+
/** The kiosk's endpoint keys in Better Auth's API (`access.api.kioskSwitchUser`): its verbs', the device list's and the server-only PIN setter's. */
|
|
19
|
+
export declare const KIOSK_ENDPOINT_KEYS: ReadonlySet<string>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The kiosk profile's verbs: Access's own routes under Better Auth's base path, with
|
|
2
|
+
// their arguments and what Access promises of each answer, in the same JSON Schema
|
|
3
|
+
// forms as `$access`'s other verbs (`client/access-verbs.schema.ts`). The answers
|
|
4
|
+
// carry ids and flags only: never a session token, a device credential or a PIN.
|
|
5
|
+
import { BOOLEAN, closed, STRING, USER, } from "../client/access-verbs.schema.js";
|
|
6
|
+
/** The PIN switch's path: a route that starts a session. */
|
|
7
|
+
export const KIOSK_SWITCH_PATH = "/kiosk/switch-user";
|
|
8
|
+
export const KIOSK_LOCK_PATH = "/kiosk/lock";
|
|
9
|
+
export const KIOSK_SET_PIN_PATH = "/kiosk/set-pin";
|
|
10
|
+
export const KIOSK_ENROLL_PATH = "/kiosk/enroll-device";
|
|
11
|
+
export const KIOSK_REVOKE_PATH = "/kiosk/revoke-device";
|
|
12
|
+
/** A manager's device list: a route, and `access.kiosk` on the server, but no verb (the framework's set of verbs is closed). */
|
|
13
|
+
export const KIOSK_LIST_PATH = "/kiosk/list-devices";
|
|
14
|
+
/** Every kiosk route lives under this prefix. */
|
|
15
|
+
export const KIOSK_PREFIX = "/kiosk/";
|
|
16
|
+
/** The device, as an answer names it. */
|
|
17
|
+
const DEVICE = closed({ id: STRING, name: STRING }, ["id", "name"]);
|
|
18
|
+
const SUCCESS = closed({ success: BOOLEAN }, ["success"]);
|
|
19
|
+
/** The kiosk verbs' routes, in the order `access.client.verbs` lists them after Better Auth's. */
|
|
20
|
+
export const KIOSK_VERBS = {
|
|
21
|
+
switchUser: {
|
|
22
|
+
method: "POST",
|
|
23
|
+
path: KIOSK_SWITCH_PATH,
|
|
24
|
+
args: closed({ userId: STRING, pin: STRING }, ["userId", "pin"]),
|
|
25
|
+
result: closed({ user: USER }, ["user"]),
|
|
26
|
+
endpoint: "kioskSwitchUser",
|
|
27
|
+
},
|
|
28
|
+
lock: {
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: KIOSK_LOCK_PATH,
|
|
31
|
+
result: SUCCESS,
|
|
32
|
+
endpoint: "kioskLock",
|
|
33
|
+
},
|
|
34
|
+
setPin: {
|
|
35
|
+
method: "POST",
|
|
36
|
+
path: KIOSK_SET_PIN_PATH,
|
|
37
|
+
args: closed({ pin: STRING }, ["pin"]),
|
|
38
|
+
result: SUCCESS,
|
|
39
|
+
endpoint: "kioskSetPin",
|
|
40
|
+
},
|
|
41
|
+
enrollDevice: {
|
|
42
|
+
method: "POST",
|
|
43
|
+
path: KIOSK_ENROLL_PATH,
|
|
44
|
+
args: closed({ name: STRING }, ["name"]),
|
|
45
|
+
result: closed({ device: DEVICE }, ["device"]),
|
|
46
|
+
endpoint: "kioskEnrollDevice",
|
|
47
|
+
},
|
|
48
|
+
revokeDevice: {
|
|
49
|
+
method: "POST",
|
|
50
|
+
path: KIOSK_REVOKE_PATH,
|
|
51
|
+
args: closed({ deviceId: STRING }, ["deviceId"]),
|
|
52
|
+
result: SUCCESS,
|
|
53
|
+
endpoint: "kioskRevokeDevice",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
/** The kiosk's endpoint keys in Better Auth's API (`access.api.kioskSwitchUser`): its verbs', the device list's and the server-only PIN setter's. */
|
|
57
|
+
export const KIOSK_ENDPOINT_KEYS = new Set([
|
|
58
|
+
...Object.values(KIOSK_VERBS).map((route) => route.endpoint),
|
|
59
|
+
"kioskListDevices",
|
|
60
|
+
"kioskSetUserPin",
|
|
61
|
+
]);
|