@aventara/access 0.0.0-stage → 1.0.0-rc.1
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 +877 -0
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +2536 -2
- package/dist/access/access-options.d.ts +74 -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 +275 -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 +22 -0
- package/dist/access/principal.subject.js +89 -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 +266 -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 +4 -0
- package/dist/access-warning.emitter.js +24 -0
- package/dist/access.builder.d.ts +340 -0
- package/dist/access.builder.js +75 -0
- package/dist/access.engine.d.ts +155 -0
- package/dist/access.engine.js +477 -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 +80 -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 +101 -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 +6 -0
- package/dist/foreign-references.check.js +131 -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 +73 -0
- package/dist/generator/prisma7/prisma-where.translator.js +138 -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 +13 -0
- package/dist/mcp/agent-scope.gate.js +75 -0
- package/dist/mcp/agent.grant.d.ts +40 -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 +219 -0
- package/dist/policies-contract.js +8 -0
- package/dist/policies.compiler.d.ts +38 -0
- package/dist/policies.compiler.js +1433 -0
- package/dist/policies.decider.d.ts +103 -0
- package/dist/policies.decider.js +390 -0
- package/dist/policies.hash.d.ts +10 -0
- package/dist/policies.hash.js +54 -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/read-governed-unique.check.d.ts +3 -0
- package/dist/read-governed-unique.check.js +21 -0
- package/dist/reference-reads.check.d.ts +16 -0
- package/dist/reference-reads.check.js +158 -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 +221 -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 +96 -0
- package/dist/resources/auth-tables.catalog.js +110 -0
- package/dist/resources/auth-tables.guard.d.ts +106 -0
- package/dist/resources/auth-tables.guard.js +462 -0
- package/dist/scope.resolver.d.ts +144 -0
- package/dist/scope.resolver.js +529 -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 +102 -3
- package/skill/SKILL.md +170 -0
- package/skill/reference/callers.md +99 -0
- package/skill/reference/errors.md +89 -0
- package/skill/reference/recipes.md +281 -0
- package/skill/reference/rules.md +153 -0
- package/skill/reference/setup.md +334 -0
- 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 +243 -0
- package/src/policies-contract.ts +215 -0
- package/src/policies.decider.ts +662 -0
- package/src/predicate.evaluator.ts +574 -0
- package/src/scope.resolver.ts +819 -0
- package/src/sha256.digest.ts +85 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Verifies the JWTs Better Auth's `jwt()` plugin signs, against the key set it
|
|
2
|
+
// publishes (`key-set.cache.ts`: read once and kept, read again when stale or when a
|
|
3
|
+
// token names a key the set does not hold, the last good set kept while the key
|
|
4
|
+
// store is down).
|
|
5
|
+
//
|
|
6
|
+
// A token is Access's own only when it carries the instance's issuer and audience
|
|
7
|
+
// (the one string `createAccess` fixed, never a list holding it), `sub`, `exp` and
|
|
8
|
+
// `iat`, and is no older than `tokenTtl`, nor issued in the future (a few seconds of
|
|
9
|
+
// clock skew aside). An access token Better Auth's OAuth provider issued (`typ:
|
|
10
|
+
// "at+jwt"`, RFC 9068) is never one, whatever it carries: it is for one resource
|
|
11
|
+
// alone (`oauth/access-token.verifier.ts`).
|
|
12
|
+
import { errors, jwtVerify } from "jose";
|
|
13
|
+
import { acceptedClaims, } from "./claims.mapper.js";
|
|
14
|
+
import { createKeySetCache } from "./key-set.cache.js";
|
|
15
|
+
/** The clock skew allowed between the issuer and the verifier, in seconds. */
|
|
16
|
+
export const CLOCK_TOLERANCE_S = 5;
|
|
17
|
+
/**
|
|
18
|
+
* The algorithms a token may be signed with: those Better Auth's `jwt()` signs with
|
|
19
|
+
* (its `keyPairConfig.alg`), all asymmetric. A token signed otherwise is refused
|
|
20
|
+
* before its key is looked up, whatever key the set holds.
|
|
21
|
+
*/
|
|
22
|
+
export const JWT_ALGORITHMS = Object.freeze([
|
|
23
|
+
"EdDSA",
|
|
24
|
+
"ES256",
|
|
25
|
+
"ES512",
|
|
26
|
+
"PS256",
|
|
27
|
+
"RS256",
|
|
28
|
+
]);
|
|
29
|
+
/** Whether a JWT header's `typ` names an OAuth access token (RFC 9068, section 2.1: `at+jwt`, or `application/at+jwt`). */
|
|
30
|
+
export const isAccessTokenType = (typ) => typeof typ === "string" && /^(?:application\/)?at\+jwt$/i.test(typ.trim());
|
|
31
|
+
export const createTokenVerifier = (source) => {
|
|
32
|
+
const now = source.now ?? (() => Date.now());
|
|
33
|
+
const keySet = createKeySetCache(source.keys, now);
|
|
34
|
+
return {
|
|
35
|
+
async verify(token) {
|
|
36
|
+
let verified;
|
|
37
|
+
try {
|
|
38
|
+
verified = await keySet.withKeys((keys) => jwtVerify(token, keys, {
|
|
39
|
+
requiredClaims: ["sub", "exp", "iat"],
|
|
40
|
+
issuer: source.issuer,
|
|
41
|
+
algorithms: [...JWT_ALGORITHMS],
|
|
42
|
+
audience: source.issuer,
|
|
43
|
+
maxTokenAge: source.tokenTtl,
|
|
44
|
+
clockTolerance: CLOCK_TOLERANCE_S,
|
|
45
|
+
currentDate: new Date(now()),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof errors.JOSEError) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
const { payload, protectedHeader } = verified;
|
|
55
|
+
if (isAccessTokenType(protectedHeader.typ)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const { sub, aud } = payload;
|
|
59
|
+
const claims = acceptedClaims(payload, source.claims);
|
|
60
|
+
if (typeof sub !== "string" ||
|
|
61
|
+
sub === "" ||
|
|
62
|
+
aud !== source.issuer ||
|
|
63
|
+
claims === undefined) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { sub, claims };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** A scalar field as discovery states it. */
|
|
2
|
+
export type AccessSchemaScalarField = {
|
|
3
|
+
readonly kind: "scalar";
|
|
4
|
+
readonly type: {
|
|
5
|
+
readonly scalar: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly enum: string;
|
|
8
|
+
};
|
|
9
|
+
readonly nullable: boolean;
|
|
10
|
+
readonly list: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** A relation field as discovery states it, with the `references` it holds. */
|
|
13
|
+
export type AccessSchemaRelationField = {
|
|
14
|
+
readonly kind: "relation";
|
|
15
|
+
readonly target: string;
|
|
16
|
+
readonly cardinality: "one" | "many";
|
|
17
|
+
readonly nullable: boolean;
|
|
18
|
+
readonly inverse?: string;
|
|
19
|
+
readonly referenceFields?: readonly [string, ...string[]];
|
|
20
|
+
readonly references?: readonly [string, ...string[]];
|
|
21
|
+
};
|
|
22
|
+
/** One field of a Resource. */
|
|
23
|
+
export type AccessSchemaField = AccessSchemaScalarField | AccessSchemaRelationField;
|
|
24
|
+
/** One Resource: its fields and the field tuples that identify a row. */
|
|
25
|
+
export type AccessSchemaResource = {
|
|
26
|
+
readonly fields: {
|
|
27
|
+
readonly [field: string]: AccessSchemaField;
|
|
28
|
+
};
|
|
29
|
+
readonly identifiers: readonly (readonly string[])[];
|
|
30
|
+
/**
|
|
31
|
+
* The namespace that owns this Resource, when another one does (`{ foreign: "campaign" }`).
|
|
32
|
+
* Rules read a foreign Resource in conditions and never grant on it.
|
|
33
|
+
*/
|
|
34
|
+
readonly foreign?: string;
|
|
35
|
+
};
|
|
36
|
+
/** The schema facts Access reads: the enums and the Resources. */
|
|
37
|
+
export type AccessSchema = {
|
|
38
|
+
readonly enums: {
|
|
39
|
+
readonly [name: string]: {
|
|
40
|
+
readonly values: readonly string[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
readonly resources: {
|
|
44
|
+
readonly [resource: string]: AccessSchemaResource;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/** The TypeScript value a built-in scalar compares with. Unknown scalars compare with `unknown`. */
|
|
48
|
+
export type AccessScalarValue<Scalar extends string> = Scalar extends "string" ? string : Scalar extends "boolean" ? boolean : Scalar extends "int" | "float" ? number : Scalar extends "bigint" ? bigint : Scalar extends "decimal" ? number | string : Scalar extends "datetime" ? Date | string : unknown;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The schema shape Access is typed from and compiles against. It is a structural
|
|
2
|
+
// subset of core's `AvAdapterModel` (a Prisma discovery artifact's `model`), so an
|
|
3
|
+
// Aventara app passes its discovery model in and Access imports nothing from core.
|
|
4
|
+
// The type test `test/core-seam.test-d.ts` holds the two together.
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** `names` as a warning lists them: `a`, `a and b`, `a, b and c`. */
|
|
2
|
+
export declare const listed: (names: readonly string[]) => string;
|
|
3
|
+
/** Emits `message` under `code`, once per process and `namespace`. */
|
|
4
|
+
export declare const emitAccessWarning: (code: string, namespace: string | undefined, message: string) => void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// How the engine's checks against a hosting framework warn: Node.js's
|
|
2
|
+
// `process.emitWarning` (type `AventaraAccessWarning`, the check's code, silenced
|
|
3
|
+
// with `--disable-warning=<code>`), else `console.warn`. Each message is emitted
|
|
4
|
+
// once per process and namespace, however many Frameworks host the rules.
|
|
5
|
+
const emitted = new Set();
|
|
6
|
+
/** `names` as a warning lists them: `a`, `a and b`, `a, b and c`. */
|
|
7
|
+
export const listed = (names) => names.length < 2
|
|
8
|
+
? names.join("")
|
|
9
|
+
: `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
10
|
+
/** Emits `message` under `code`, once per process and `namespace`. */
|
|
11
|
+
export const emitAccessWarning = (code, namespace, message) => {
|
|
12
|
+
const key = `${code}\u0000${namespace ?? ""}\u0000${message}`;
|
|
13
|
+
if (emitted.has(key)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
emitted.add(key);
|
|
17
|
+
const runtime = globalThis.process;
|
|
18
|
+
if (typeof runtime?.emitWarning === "function") {
|
|
19
|
+
runtime.emitWarning(message, { type: "AventaraAccessWarning", code });
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
globalThis.console?.warn(`${code}: ${message}`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { type AccessEngine, type AccessEngineOptions } from "./access.engine.js";
|
|
2
|
+
import type { AccessScalarValue, AccessSchema } from "./access-schema.js";
|
|
3
|
+
import type { AccessAction, AccessBuiltInSubject, AccessOrigin, AccessPolicies } from "./policies-contract.js";
|
|
4
|
+
declare const principalRefBrand: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* A value read off the principal when a question is asked (`me.id`). `T` is the
|
|
7
|
+
* value's type, so `me.id` compares only with a field of that type.
|
|
8
|
+
*/
|
|
9
|
+
export type AccessPrincipalRef<T = unknown> = {
|
|
10
|
+
readonly [principalRefBrand]: T;
|
|
11
|
+
readonly path: readonly [string, ...string[]];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The principal, symbolically, typed from `.principal<P>()`: one key per key of
|
|
15
|
+
* `P`, so `me.profleId` is a tsc error. `me.id` is a token the compiler writes
|
|
16
|
+
* into the file as `{ principal: ["id"] }`, never a value. A rule about `me` is
|
|
17
|
+
* false for an anonymous (null) principal, and for one that lacks the value.
|
|
18
|
+
*/
|
|
19
|
+
export type AccessMe<P> = {
|
|
20
|
+
readonly [K in Extract<keyof NonNullable<P>, string>]-?: AccessPrincipalRef<NonNullable<NonNullable<P>[K]>>;
|
|
21
|
+
};
|
|
22
|
+
type Simplify<T> = {
|
|
23
|
+
[K in keyof T]: T[K];
|
|
24
|
+
} & {};
|
|
25
|
+
/** A field's `govern` mode: governed for writing only, reading only, or both. */
|
|
26
|
+
type GovernMode = "read" | "write" | "both";
|
|
27
|
+
/** The `govern` block, by Resource: a mode per governed field (the list form governs each for "both"). */
|
|
28
|
+
type GovernedShape = {
|
|
29
|
+
readonly [resource: string]: {
|
|
30
|
+
readonly [field: string]: GovernMode;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/** One Resource's governed fields, whatever their mode. */
|
|
34
|
+
type GovernedFieldOf<G> = keyof G & string;
|
|
35
|
+
/** `govern(R, fields)` added to the block, as a mode per field. */
|
|
36
|
+
type Governing<Gov, R extends string, New> = Simplify<{
|
|
37
|
+
readonly [K in keyof Gov as K extends R ? never : K]: Gov[K];
|
|
38
|
+
} & {
|
|
39
|
+
readonly [K in R]: K extends keyof Gov ? Simplify<Gov[K] & New> : New;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* The object form's check, at each key: a field of the Resource, not governed
|
|
43
|
+
* already in another mode. The type at a wrong key is the sentence that says what
|
|
44
|
+
* to change.
|
|
45
|
+
*/
|
|
46
|
+
type CheckedModes<Field extends string, R extends string, Prior, M> = {
|
|
47
|
+
readonly [K in keyof M]: K extends Field ? M[K] extends GovernMode ? K extends keyof Prior ? Prior[K] extends M[K] ? M[K] : `Error: "${R}.${K & string}" is already governed as "${Prior[K] & string}"; govern each field once, with one mode` : M[K] : `Error: "${R}.${K & string}" takes a mode, "read", "write" or "both"` : `Error: "${R}" has no field "${K & string}"`;
|
|
48
|
+
};
|
|
49
|
+
/** The list form's check: a field governed already in a mode other than "both" is the sentence that says so. */
|
|
50
|
+
type CheckedList<R extends string, Prior, F> = {
|
|
51
|
+
readonly [I in keyof F]: F[I] extends keyof Prior ? Prior[F[I]] extends "both" ? F[I] : `Error: "${R}.${F[I] & string}" is already governed as "${Prior[F[I]] & string}"; govern each field once, with one mode` : F[I];
|
|
52
|
+
};
|
|
53
|
+
/** The modes `Gov` already gives Resource `R`'s fields; none when it is not governed. */
|
|
54
|
+
type PriorModes<Gov, R> = R extends keyof Gov ? Gov[R] : unknown;
|
|
55
|
+
/** A row role as the builder's type records it: the Resource it is held on. */
|
|
56
|
+
type RoleShape = {
|
|
57
|
+
readonly on: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly global: unknown;
|
|
60
|
+
};
|
|
61
|
+
/** The roles in scope inside a role's own `where`: any name (the compiler checks them). */
|
|
62
|
+
type AnyRoles = {
|
|
63
|
+
readonly [role: string]: {
|
|
64
|
+
readonly on: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
type ResourceOf<S extends AccessSchema> = Extract<keyof S["resources"], string>;
|
|
68
|
+
/**
|
|
69
|
+
* The file's own Resources: all but the foreign ones (`{ foreign: "<namespace>" }`).
|
|
70
|
+
* Grants, denies and governed fields name only these; a foreign Resource is read in
|
|
71
|
+
* conditions alone.
|
|
72
|
+
*/
|
|
73
|
+
type OwnResourceOf<S extends AccessSchema> = {
|
|
74
|
+
[R in ResourceOf<S>]: S["resources"][R] extends {
|
|
75
|
+
readonly foreign: string;
|
|
76
|
+
} ? never : R;
|
|
77
|
+
}[ResourceOf<S>];
|
|
78
|
+
type FieldMap<S extends AccessSchema, R extends ResourceOf<S>> = S["resources"][R]["fields"];
|
|
79
|
+
/** A Resource's field names. */
|
|
80
|
+
export type AccessFieldOf<S extends AccessSchema, R extends ResourceOf<S>> = Extract<keyof FieldMap<S, R>, string>;
|
|
81
|
+
type NullIf<F> = F extends {
|
|
82
|
+
readonly nullable: true;
|
|
83
|
+
} ? null : never;
|
|
84
|
+
/** The literal a scalar field compares with: its enum's values, or its scalar's TS type. */
|
|
85
|
+
type ScalarValue<S extends AccessSchema, F> = F extends {
|
|
86
|
+
readonly list: true;
|
|
87
|
+
} ? never : F extends {
|
|
88
|
+
readonly type: {
|
|
89
|
+
readonly enum: infer E extends keyof S["enums"];
|
|
90
|
+
};
|
|
91
|
+
} ? S["enums"][E]["values"][number] | NullIf<F> : F extends {
|
|
92
|
+
readonly type: {
|
|
93
|
+
readonly scalar: infer T extends string;
|
|
94
|
+
};
|
|
95
|
+
} ? AccessScalarValue<T> | NullIf<F> : never;
|
|
96
|
+
type Operand<V> = V | AccessPrincipalRef<V>;
|
|
97
|
+
type ScalarCondition<V> = Operand<V> | {
|
|
98
|
+
readonly equals?: Operand<V>;
|
|
99
|
+
readonly not?: Operand<V>;
|
|
100
|
+
readonly in?: readonly Operand<V>[];
|
|
101
|
+
readonly notIn?: readonly Operand<V>[];
|
|
102
|
+
};
|
|
103
|
+
type RelationCondition<S extends AccessSchema, F, Roles> = F extends {
|
|
104
|
+
readonly cardinality: "one";
|
|
105
|
+
readonly target: infer T extends ResourceOf<S>;
|
|
106
|
+
} ? AccessWhereInput<S, T, Roles> : F extends {
|
|
107
|
+
readonly cardinality: "many";
|
|
108
|
+
readonly target: infer T extends ResourceOf<S>;
|
|
109
|
+
} ? {
|
|
110
|
+
readonly some?: AccessWhereInput<S, T, Roles>;
|
|
111
|
+
readonly every?: AccessWhereInput<S, T, Roles>;
|
|
112
|
+
readonly none?: AccessWhereInput<S, T, Roles>;
|
|
113
|
+
} : never;
|
|
114
|
+
type FieldCondition<S extends AccessSchema, F, Roles> = F extends {
|
|
115
|
+
readonly kind: "relation";
|
|
116
|
+
} ? RelationCondition<S, F, Roles> : ScalarCondition<ScalarValue<S, F>>;
|
|
117
|
+
/** The row roles held on Resource `R`. */
|
|
118
|
+
type RoleOn<Roles, R extends string> = string extends keyof Roles ? string : {
|
|
119
|
+
[K in keyof Roles]: Roles[K] extends {
|
|
120
|
+
readonly on: R;
|
|
121
|
+
} ? K : never;
|
|
122
|
+
}[keyof Roles] & string;
|
|
123
|
+
/**
|
|
124
|
+
* A row condition on Resource `R`, in Aventara's `where` vocabulary: fields by
|
|
125
|
+
* name (a value is `equals`), `AND` / `OR` / `NOT`, a to-one relation by direct
|
|
126
|
+
* match, a to-many by `some` / `every` / `none`, and `$role`, a row role held on
|
|
127
|
+
* `R`.
|
|
128
|
+
*/
|
|
129
|
+
export type AccessWhereInput<S extends AccessSchema, R extends ResourceOf<S>, Roles> = {
|
|
130
|
+
readonly [F in AccessFieldOf<S, R>]?: FieldCondition<S, FieldMap<S, R>[F], Roles>;
|
|
131
|
+
} & {
|
|
132
|
+
readonly AND?: readonly AccessWhereInput<S, R, Roles>[];
|
|
133
|
+
readonly OR?: readonly AccessWhereInput<S, R, Roles>[];
|
|
134
|
+
readonly NOT?: AccessWhereInput<S, R, Roles>;
|
|
135
|
+
readonly $role?: RoleOn<Roles, R>;
|
|
136
|
+
};
|
|
137
|
+
type ClaimsOf<P> = P extends {
|
|
138
|
+
readonly claims?: infer C;
|
|
139
|
+
} ? NonNullable<C> : never;
|
|
140
|
+
/** A condition on the principal's claims: each named claim equals the value. */
|
|
141
|
+
export type AccessClaimsCondition<P> = [ClaimsOf<P>] extends [never] ? never : {
|
|
142
|
+
readonly [K in keyof ClaimsOf<P>]?: Exclude<ClaimsOf<P>[K], undefined>;
|
|
143
|
+
};
|
|
144
|
+
type RowRoleDefinition<S extends AccessSchema> = {
|
|
145
|
+
[R in ResourceOf<S>]: {
|
|
146
|
+
readonly on: R;
|
|
147
|
+
readonly where: AccessWhereInput<S, R, AnyRoles>;
|
|
148
|
+
};
|
|
149
|
+
}[ResourceOf<S>];
|
|
150
|
+
type GlobalRoleDefinition<P> = {
|
|
151
|
+
readonly global: {
|
|
152
|
+
readonly claims: AccessClaimsCondition<P>;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
/** The `.roles({...})` argument: global roles from claims, row roles on one Resource. */
|
|
156
|
+
export type AccessRoleDefinitions<S extends AccessSchema, P> = {
|
|
157
|
+
readonly [role: string]: GlobalRoleDefinition<P> | RowRoleDefinition<S>;
|
|
158
|
+
};
|
|
159
|
+
/** Each role of `D` checked against its own shape; built-in subject names refused. */
|
|
160
|
+
type CheckedRoles<S extends AccessSchema, P, D> = {
|
|
161
|
+
readonly [K in keyof D]: K extends AccessBuiltInSubject ? never : D[K] extends {
|
|
162
|
+
readonly on: infer R extends ResourceOf<S>;
|
|
163
|
+
readonly where: infer W;
|
|
164
|
+
} ? {
|
|
165
|
+
readonly on: R;
|
|
166
|
+
readonly where: NoExtraKeys<W, AccessWhereInput<S, R, AnyRoles>, "a field, relation or operator">;
|
|
167
|
+
} : D[K] extends {
|
|
168
|
+
readonly global: {
|
|
169
|
+
readonly claims: infer C;
|
|
170
|
+
};
|
|
171
|
+
} ? {
|
|
172
|
+
readonly global: {
|
|
173
|
+
readonly claims: NoExtraKeys<C, AccessClaimsCondition<P>, "a claim of the principal">;
|
|
174
|
+
};
|
|
175
|
+
} : AccessRoleDefinitions<S, P>[string];
|
|
176
|
+
};
|
|
177
|
+
/** Every key of every object member of a union. */
|
|
178
|
+
type KeysOfUnion<U> = U extends unknown ? keyof U : never;
|
|
179
|
+
/** The type of key `K` across the object members of a union that have it. */
|
|
180
|
+
type MemberOf<U, K> = U extends unknown ? K extends keyof U ? U[K] : never : never;
|
|
181
|
+
/**
|
|
182
|
+
* `T` checked against `Shape`, at any depth: each key `Shape` does not have is
|
|
183
|
+
* typed as the sentence that says so, and each value as `Shape` allows it.
|
|
184
|
+
*/
|
|
185
|
+
type NoExtraKeys<T, Shape, Noun extends string> = T extends string | number | boolean | bigint | symbol | null | undefined | AccessPrincipalRef ? Shape : T extends readonly (infer E)[] ? Shape extends unknown ? readonly NoExtraKeys<E, NonNullable<Shape> extends readonly (infer SE)[] ? SE : never, Noun>[] : never : {
|
|
186
|
+
readonly [K in keyof T]: K extends KeysOfUnion<Exclude<Extract<NonNullable<Shape>, object>, AccessPrincipalRef>> ? NoExtraKeys<T[K], MemberOf<Exclude<Extract<NonNullable<Shape>, object>, AccessPrincipalRef>, K>, Noun> : `Error: "${K & string}" is not ${Noun} here`;
|
|
187
|
+
};
|
|
188
|
+
/** Who a grant is for: a built-in subject or a role. */
|
|
189
|
+
type Subject<Roles> = AccessBuiltInSubject | Extract<keyof Roles, string>;
|
|
190
|
+
type OneOrMany<T> = T | readonly [T, ...T[]];
|
|
191
|
+
type Each<T> = T extends readonly (infer X)[] ? X : T;
|
|
192
|
+
/** The Resource every subject's row role is held on; `never` when one subject is not a row role. */
|
|
193
|
+
type SubjectsOn<Roles, Sub> = [Sub] extends [never] ? never : Sub extends keyof Roles ? Roles[Sub] extends {
|
|
194
|
+
readonly on: infer On extends string;
|
|
195
|
+
} ? On : "~not-a-row-role" : "~not-a-row-role";
|
|
196
|
+
/** The to-one relations of `R` that lead to `On`. */
|
|
197
|
+
type InRelation<S extends AccessSchema, R extends ResourceOf<S>, On> = {
|
|
198
|
+
[F in AccessFieldOf<S, R>]: FieldMap<S, R>[F] extends {
|
|
199
|
+
readonly kind: "relation";
|
|
200
|
+
readonly cardinality: "one";
|
|
201
|
+
readonly target: infer T;
|
|
202
|
+
} ? T extends On ? F : never : never;
|
|
203
|
+
}[AccessFieldOf<S, R>];
|
|
204
|
+
type InOption<S extends AccessSchema, R, Roles, Sub> = R extends ResourceOf<S> ? "~not-a-row-role" extends SubjectsOn<Roles, Each<Sub>> ? never : InRelation<S, R, SubjectsOn<Roles, Each<Sub>>> : never;
|
|
205
|
+
/**
|
|
206
|
+
* A governed field named `Resource.field`. On `"*"`, any Resource's governed fields.
|
|
207
|
+
* A Resource with no governed field has none to name: the type is the sentence that
|
|
208
|
+
* says to govern one first, so naming a field reads as what to change.
|
|
209
|
+
*/
|
|
210
|
+
type GovernedRef<Gov, R> = R extends "*" ? [keyof Gov] extends [never] ? "Error: no field is governed; govern one first, .govern(<Resource>, [<field>]), and a grant can then open it" : {
|
|
211
|
+
[K in keyof Gov]: `${K & string}.${GovernedFieldOf<Gov[K]>}`;
|
|
212
|
+
}[keyof Gov] : R extends keyof Gov ? `${R & string}.${GovernedFieldOf<Gov[R]>}` : `Error: no field of "${R & string}" is governed; govern it first, .govern("${R & string}", [<field>]), and a grant can then open it`;
|
|
213
|
+
type ResourceWhere<S extends AccessSchema, R, Roles> = R extends ResourceOf<S> ? AccessWhereInput<S, R, Roles> : never;
|
|
214
|
+
/** The options of `grant(subjects, actions, resource, options)`. */
|
|
215
|
+
export type AccessGrantOptions<S extends AccessSchema, Roles, Gov, R, Sub, A> = {
|
|
216
|
+
/** A name for the rule's id (else `Resource/actions/subjects#n`). */
|
|
217
|
+
readonly name?: string;
|
|
218
|
+
/** The rows the grant covers. */
|
|
219
|
+
readonly where?: ResourceWhere<S, R, Roles>;
|
|
220
|
+
/** `in: "campaign"`: each subject holds its row role on this row's `campaign`. */
|
|
221
|
+
readonly in?: InOption<S, R, Roles, Sub>;
|
|
222
|
+
/** Governed fields the grant opens, as `Resource.field`. */
|
|
223
|
+
readonly fields?: readonly GovernedRef<Gov, R>[];
|
|
224
|
+
/** Fields a create or update by this grant may not write. */
|
|
225
|
+
readonly write?: Extract<Each<A>, "create" | "update"> extends never ? never : R extends ResourceOf<S> ? {
|
|
226
|
+
readonly except: readonly AccessFieldOf<S, R>[];
|
|
227
|
+
} : never;
|
|
228
|
+
/** What the row an update leaves must still meet. */
|
|
229
|
+
readonly after?: "update" extends Each<A> ? ResourceWhere<S, R, Roles> : never;
|
|
230
|
+
};
|
|
231
|
+
/** The options of `deny(actions, resource, options)`. */
|
|
232
|
+
export type AccessDenyOptions<S extends AccessSchema, P, Roles, R> = {
|
|
233
|
+
/** A name for the rule's id (else `Resource/actions/deny#n`). */
|
|
234
|
+
readonly name?: string;
|
|
235
|
+
/** The rows the deny covers. */
|
|
236
|
+
readonly where?: ResourceWhere<S, R, Roles>;
|
|
237
|
+
/** A condition on the principal alone: its claims and the request's origin. */
|
|
238
|
+
readonly if?: {
|
|
239
|
+
readonly claims?: AccessClaimsCondition<P>;
|
|
240
|
+
readonly origin?: AccessOrigin;
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
declare const grantBrand: unique symbol;
|
|
244
|
+
declare const denyBrand: unique symbol;
|
|
245
|
+
/** A grant, as data, before compilation. */
|
|
246
|
+
export type AccessGrant = {
|
|
247
|
+
readonly [grantBrand]: true;
|
|
248
|
+
readonly subjects: OneOrMany<string>;
|
|
249
|
+
readonly actions: OneOrMany<AccessAction>;
|
|
250
|
+
readonly resource: string;
|
|
251
|
+
readonly options: {
|
|
252
|
+
readonly [option: string]: unknown;
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
/** A deny, as data, before compilation. */
|
|
256
|
+
export type AccessDeny = {
|
|
257
|
+
readonly [denyBrand]: true;
|
|
258
|
+
readonly actions: "*" | OneOrMany<AccessAction>;
|
|
259
|
+
readonly resource: string;
|
|
260
|
+
readonly options: {
|
|
261
|
+
readonly [option: string]: unknown;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
/** `grant(subjects, actions, resource, options?)`: any grant allows. */
|
|
265
|
+
export type AccessGrantFunction<S extends AccessSchema, Roles, Gov> = <const Sub extends OneOrMany<Subject<Roles>>, const A extends OneOrMany<AccessAction>, const R extends OwnResourceOf<S> | "*">(subjects: Sub, actions: A, resource: R, options?: AccessGrantOptions<S, Roles, Gov, NoInfer<R>, NoInfer<Sub>, NoInfer<A>>) => AccessGrant;
|
|
266
|
+
/** `deny(actions, resource, options?)`: any deny wins. */
|
|
267
|
+
export type AccessDenyFunction<S extends AccessSchema, P, Roles> = <const A extends "*" | OneOrMany<AccessAction>, const R extends OwnResourceOf<S> | "*">(actions: A, resource: R, options?: AccessDenyOptions<S, P, Roles, NoInfer<R>>) => AccessDeny;
|
|
268
|
+
/** The rules as written, before compilation. */
|
|
269
|
+
export type AccessDefinition = {
|
|
270
|
+
readonly schema: AccessSchema;
|
|
271
|
+
readonly namespace?: string;
|
|
272
|
+
readonly roles: {
|
|
273
|
+
readonly [role: string]: unknown;
|
|
274
|
+
};
|
|
275
|
+
/** Each `govern` call's fields, with their modes, in order: the compiler refuses a field given two. */
|
|
276
|
+
readonly govern: {
|
|
277
|
+
readonly [resource: string]: readonly {
|
|
278
|
+
readonly field: string;
|
|
279
|
+
readonly mode: GovernMode;
|
|
280
|
+
}[];
|
|
281
|
+
};
|
|
282
|
+
readonly grants: readonly AccessGrant[];
|
|
283
|
+
readonly denies: readonly AccessDeny[];
|
|
284
|
+
};
|
|
285
|
+
/** The builder `definePolicies` returns. Each call returns a new builder; none has side effects. */
|
|
286
|
+
export interface AccessBuilder<S extends AccessSchema, P, Roles extends {
|
|
287
|
+
readonly [role: string]: RoleShape;
|
|
288
|
+
}, Gov extends GovernedShape> {
|
|
289
|
+
/** Types the principal. Anonymous is `null`; rules about `me` are false for it. */
|
|
290
|
+
principal<T>(): AccessBuilder<S, T, Roles, Gov>;
|
|
291
|
+
/** Global roles (`global: { claims }`) and roles held on a row (`on`, `where`). */
|
|
292
|
+
roles<const D extends {
|
|
293
|
+
readonly [role: string]: RoleShape;
|
|
294
|
+
}>(definitions: (tools: {
|
|
295
|
+
readonly me: AccessMe<P>;
|
|
296
|
+
}) => D extends CheckedRoles<S, P, D> ? D : CheckedRoles<S, P, D>): AccessBuilder<S, P, Simplify<Roles & D>, Gov>;
|
|
297
|
+
/**
|
|
298
|
+
* Governed fields: only a grant that names one opens it. A list governs each field
|
|
299
|
+
* for reading and writing: out of every default projection, and written by no grant
|
|
300
|
+
* that does not name it. An object gives each field a mode: `"write"` (read as any
|
|
301
|
+
* other field; written only through a grant that names it: a role your rules read),
|
|
302
|
+
* `"read"` (written as any other field; read only through a grant that names it: a
|
|
303
|
+
* government id) or `"both"` (as the list).
|
|
304
|
+
*/
|
|
305
|
+
govern<R extends OwnResourceOf<S>, const F extends readonly [AccessFieldOf<S, R>, ...AccessFieldOf<S, R>[]]>(resource: R, fields: F extends CheckedList<R, PriorModes<Gov, R>, F> ? F : CheckedList<R, PriorModes<Gov, R>, F>): AccessBuilder<S, P, Roles, Governing<Gov, R, {
|
|
306
|
+
readonly [K in F[number]]: "both";
|
|
307
|
+
}>>;
|
|
308
|
+
govern<R extends OwnResourceOf<S>, const M extends {
|
|
309
|
+
readonly [field: string]: string;
|
|
310
|
+
}>(resource: R, fields: keyof M extends never ? 'Error: name at least one field, { <field>: "read" | "write" | "both" }' : M extends CheckedModes<AccessFieldOf<S, R>, R, PriorModes<Gov, R>, M> ? M : CheckedModes<AccessFieldOf<S, R>, R, PriorModes<Gov, R>, M>): AccessBuilder<S, P, Roles, Governing<Gov, R, M>>;
|
|
311
|
+
/** Grants. Default deny; any grant allows; order never matters. */
|
|
312
|
+
grants(rules: (tools: {
|
|
313
|
+
readonly grant: AccessGrantFunction<S, Roles, Gov>;
|
|
314
|
+
readonly me: AccessMe<P>;
|
|
315
|
+
}) => readonly AccessGrant[]): AccessBuilder<S, P, Roles, Gov>;
|
|
316
|
+
/** Denies. Any deny wins over every grant; order never matters. */
|
|
317
|
+
denies(rules: (tools: {
|
|
318
|
+
readonly deny: AccessDenyFunction<S, P, Roles>;
|
|
319
|
+
readonly me: AccessMe<P>;
|
|
320
|
+
}) => readonly AccessDeny[]): AccessBuilder<S, P, Roles, Gov>;
|
|
321
|
+
/** The compiled `@1` policies file. */
|
|
322
|
+
contract(): AccessPolicies<Gov>;
|
|
323
|
+
/**
|
|
324
|
+
* The engine, run from the compiled file and, with `guardrails`, the org's
|
|
325
|
+
* guardrails on top of it. With `lock`, the guardrails are held to the hash
|
|
326
|
+
* `avAccess.lock.json` pins, and the rules' namespace to its namespace.
|
|
327
|
+
* Satisfies core's `AvPolicyEngine` by shape.
|
|
328
|
+
*/
|
|
329
|
+
engine(options?: AccessEngineOptions): AccessEngine<Gov>;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Starts the rules for an app, typed from its schema (an Aventara discovery
|
|
333
|
+
* artifact's `model`, or any object of the {@link AccessSchema} shape).
|
|
334
|
+
* `namespace` names the namespace that owns these Resources; it is written into
|
|
335
|
+
* the compiled file.
|
|
336
|
+
*/
|
|
337
|
+
export declare const definePolicies: <const S extends AccessSchema>(schema: S, options?: {
|
|
338
|
+
readonly namespace?: string;
|
|
339
|
+
}) => AccessBuilder<S, unknown, Record<never, never>, Record<never, never>>;
|
|
340
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// `definePolicies(model)`: the typed authoring API. It captures the author's
|
|
2
|
+
// rules as plain data and compiles them to the `@1` policies file; the engine runs
|
|
3
|
+
// from that file alone. Every name a rule uses (Resource, field,
|
|
4
|
+
// relation, enum value, role, governed field) is checked at its key against the
|
|
5
|
+
// schema the builder was created from.
|
|
6
|
+
import { engineOf, } from "./access.engine.js";
|
|
7
|
+
import { compilePolicies, PRINCIPAL_REF } from "./policies.compiler.js";
|
|
8
|
+
/** The one runtime `me`: any key reads as a principal reference. */
|
|
9
|
+
const me = new Proxy({}, {
|
|
10
|
+
get(_target, key) {
|
|
11
|
+
if (typeof key !== "string") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
[PRINCIPAL_REF]: true,
|
|
16
|
+
path: Object.freeze([key]),
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
const grant = (subjects, actions, resource, options = {}) => Object.freeze({
|
|
21
|
+
subjects,
|
|
22
|
+
actions,
|
|
23
|
+
resource,
|
|
24
|
+
options,
|
|
25
|
+
});
|
|
26
|
+
const deny = (actions, resource, options = {}) => Object.freeze({ actions, resource, options });
|
|
27
|
+
const builderOf = (definition) => ({
|
|
28
|
+
principal: () => builderOf(definition),
|
|
29
|
+
roles: (definitions) => builderOf({
|
|
30
|
+
...definition,
|
|
31
|
+
roles: { ...definition.roles, ...definitions({ me }) },
|
|
32
|
+
}),
|
|
33
|
+
govern: (resource, fields) => builderOf({
|
|
34
|
+
...definition,
|
|
35
|
+
govern: {
|
|
36
|
+
...definition.govern,
|
|
37
|
+
[resource]: [
|
|
38
|
+
...(definition.govern[resource] ?? []),
|
|
39
|
+
// The list form governs each field for both; the compiler checks the rest.
|
|
40
|
+
...(Array.isArray(fields)
|
|
41
|
+
? fields.map((field) => ({
|
|
42
|
+
field,
|
|
43
|
+
mode: "both",
|
|
44
|
+
}))
|
|
45
|
+
: Object.entries(fields).map(([field, mode]) => ({ field, mode }))),
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
grants: (rules) => builderOf({
|
|
50
|
+
...definition,
|
|
51
|
+
grants: [...definition.grants, ...rules({ grant, me })],
|
|
52
|
+
}),
|
|
53
|
+
denies: (rules) => builderOf({
|
|
54
|
+
...definition,
|
|
55
|
+
denies: [...definition.denies, ...rules({ deny, me })],
|
|
56
|
+
}),
|
|
57
|
+
contract: () => compilePolicies(definition),
|
|
58
|
+
engine: (options) => engineOf(compilePolicies(definition), options, "rules"),
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* Starts the rules for an app, typed from its schema (an Aventara discovery
|
|
62
|
+
* artifact's `model`, or any object of the {@link AccessSchema} shape).
|
|
63
|
+
* `namespace` names the namespace that owns these Resources; it is written into
|
|
64
|
+
* the compiled file.
|
|
65
|
+
*/
|
|
66
|
+
export const definePolicies = (schema, options) => builderOf({
|
|
67
|
+
schema,
|
|
68
|
+
...(options?.namespace !== undefined
|
|
69
|
+
? { namespace: options.namespace }
|
|
70
|
+
: {}),
|
|
71
|
+
roles: {},
|
|
72
|
+
govern: {},
|
|
73
|
+
grants: [],
|
|
74
|
+
denies: [],
|
|
75
|
+
});
|