@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,143 @@
|
|
|
1
|
+
/** The model the audit log declares, by Better Auth model name. */
|
|
2
|
+
export declare const AUDIT_MODEL = "accessAudit";
|
|
3
|
+
/** The audit table, in Better Auth's plugin schema form. */
|
|
4
|
+
export declare const auditSchema: {
|
|
5
|
+
readonly accessAudit: {
|
|
6
|
+
readonly fields: {
|
|
7
|
+
readonly at: {
|
|
8
|
+
readonly type: "date";
|
|
9
|
+
readonly required: true;
|
|
10
|
+
readonly index: true;
|
|
11
|
+
};
|
|
12
|
+
readonly kind: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly required: true;
|
|
15
|
+
};
|
|
16
|
+
readonly userId: {
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
readonly required: false;
|
|
19
|
+
readonly index: true;
|
|
20
|
+
};
|
|
21
|
+
readonly resource: {
|
|
22
|
+
readonly type: "string";
|
|
23
|
+
readonly required: false;
|
|
24
|
+
};
|
|
25
|
+
readonly action: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
readonly required: false;
|
|
28
|
+
};
|
|
29
|
+
readonly allowed: {
|
|
30
|
+
readonly type: "boolean";
|
|
31
|
+
readonly required: false;
|
|
32
|
+
};
|
|
33
|
+
readonly reason: {
|
|
34
|
+
readonly type: "string";
|
|
35
|
+
readonly required: false;
|
|
36
|
+
};
|
|
37
|
+
readonly route: {
|
|
38
|
+
readonly type: "string";
|
|
39
|
+
readonly required: false;
|
|
40
|
+
};
|
|
41
|
+
readonly target: {
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
readonly required: false;
|
|
44
|
+
readonly index: true;
|
|
45
|
+
};
|
|
46
|
+
readonly impersonatedBy: {
|
|
47
|
+
readonly type: "string";
|
|
48
|
+
readonly required: false;
|
|
49
|
+
};
|
|
50
|
+
readonly suppressed: {
|
|
51
|
+
readonly type: "number";
|
|
52
|
+
readonly required: false;
|
|
53
|
+
};
|
|
54
|
+
readonly windowStart: {
|
|
55
|
+
readonly type: "date";
|
|
56
|
+
readonly required: false;
|
|
57
|
+
};
|
|
58
|
+
readonly windowEnd: {
|
|
59
|
+
readonly type: "date";
|
|
60
|
+
readonly required: false;
|
|
61
|
+
};
|
|
62
|
+
readonly origin: {
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
readonly required: true;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/** The audit table with the kiosk profile's columns. */
|
|
70
|
+
export declare const kioskAuditSchema: {
|
|
71
|
+
readonly accessAudit: {
|
|
72
|
+
readonly fields: {
|
|
73
|
+
readonly deviceId: {
|
|
74
|
+
readonly type: "string";
|
|
75
|
+
readonly required: false;
|
|
76
|
+
readonly index: true;
|
|
77
|
+
};
|
|
78
|
+
readonly previousUserId: {
|
|
79
|
+
readonly type: "string";
|
|
80
|
+
readonly required: false;
|
|
81
|
+
};
|
|
82
|
+
readonly at: {
|
|
83
|
+
readonly type: "date";
|
|
84
|
+
readonly required: true;
|
|
85
|
+
readonly index: true;
|
|
86
|
+
};
|
|
87
|
+
readonly kind: {
|
|
88
|
+
readonly type: "string";
|
|
89
|
+
readonly required: true;
|
|
90
|
+
};
|
|
91
|
+
readonly userId: {
|
|
92
|
+
readonly type: "string";
|
|
93
|
+
readonly required: false;
|
|
94
|
+
readonly index: true;
|
|
95
|
+
};
|
|
96
|
+
readonly resource: {
|
|
97
|
+
readonly type: "string";
|
|
98
|
+
readonly required: false;
|
|
99
|
+
};
|
|
100
|
+
readonly action: {
|
|
101
|
+
readonly type: "string";
|
|
102
|
+
readonly required: false;
|
|
103
|
+
};
|
|
104
|
+
readonly allowed: {
|
|
105
|
+
readonly type: "boolean";
|
|
106
|
+
readonly required: false;
|
|
107
|
+
};
|
|
108
|
+
readonly reason: {
|
|
109
|
+
readonly type: "string";
|
|
110
|
+
readonly required: false;
|
|
111
|
+
};
|
|
112
|
+
readonly route: {
|
|
113
|
+
readonly type: "string";
|
|
114
|
+
readonly required: false;
|
|
115
|
+
};
|
|
116
|
+
readonly target: {
|
|
117
|
+
readonly type: "string";
|
|
118
|
+
readonly required: false;
|
|
119
|
+
readonly index: true;
|
|
120
|
+
};
|
|
121
|
+
readonly impersonatedBy: {
|
|
122
|
+
readonly type: "string";
|
|
123
|
+
readonly required: false;
|
|
124
|
+
};
|
|
125
|
+
readonly suppressed: {
|
|
126
|
+
readonly type: "number";
|
|
127
|
+
readonly required: false;
|
|
128
|
+
};
|
|
129
|
+
readonly windowStart: {
|
|
130
|
+
readonly type: "date";
|
|
131
|
+
readonly required: false;
|
|
132
|
+
};
|
|
133
|
+
readonly windowEnd: {
|
|
134
|
+
readonly type: "date";
|
|
135
|
+
readonly required: false;
|
|
136
|
+
};
|
|
137
|
+
readonly origin: {
|
|
138
|
+
readonly type: "string";
|
|
139
|
+
readonly required: true;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The audit table, declared through Better Auth's plugin schema: the Access plugin
|
|
2
|
+
// carries it when `audit: true`, so Better Auth's own migration (or its CLI's
|
|
3
|
+
// `generate` for an ORM's schema) creates it beside Better Auth's tables, and events
|
|
4
|
+
// are written through whatever database adapter the app gave Better Auth. With
|
|
5
|
+
// `audit: { sink }` the plugin declares no table.
|
|
6
|
+
//
|
|
7
|
+
// The name follows the registry's tables (and Better Auth's own plugins, `twoFactor`,
|
|
8
|
+
// `jwks`): a camelCase model, which Better Auth's Prisma generator turns into
|
|
9
|
+
// `AccessAudit`.
|
|
10
|
+
//
|
|
11
|
+
// `userId` is a plain column, not a reference to Better Auth's `user`: a row outlives
|
|
12
|
+
// the user it names, and deleting a user never touches the log.
|
|
13
|
+
/** The model the audit log declares, by Better Auth model name. */
|
|
14
|
+
export const AUDIT_MODEL = "accessAudit";
|
|
15
|
+
const optionalString = { type: "string", required: false };
|
|
16
|
+
/** The audit table, in Better Auth's plugin schema form. */
|
|
17
|
+
export const auditSchema = {
|
|
18
|
+
[AUDIT_MODEL]: {
|
|
19
|
+
fields: {
|
|
20
|
+
at: { type: "date", required: true, index: true },
|
|
21
|
+
kind: { type: "string", required: true },
|
|
22
|
+
userId: { type: "string", required: false, index: true },
|
|
23
|
+
resource: optionalString,
|
|
24
|
+
action: optionalString,
|
|
25
|
+
allowed: { type: "boolean", required: false },
|
|
26
|
+
reason: optionalString,
|
|
27
|
+
route: optionalString,
|
|
28
|
+
target: { type: "string", required: false, index: true },
|
|
29
|
+
impersonatedBy: optionalString,
|
|
30
|
+
suppressed: { type: "number", required: false },
|
|
31
|
+
windowStart: { type: "date", required: false },
|
|
32
|
+
windowEnd: { type: "date", required: false },
|
|
33
|
+
origin: { type: "string", required: true },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The audit table's columns for the kiosk profile's events, added to it only on that
|
|
39
|
+
* profile: the device, and the user a switch replaced.
|
|
40
|
+
*/
|
|
41
|
+
const kioskAuditFields = {
|
|
42
|
+
deviceId: { type: "string", required: false, index: true },
|
|
43
|
+
previousUserId: optionalString,
|
|
44
|
+
};
|
|
45
|
+
/** The audit table with the kiosk profile's columns. */
|
|
46
|
+
export const kioskAuditSchema = {
|
|
47
|
+
[AUDIT_MODEL]: {
|
|
48
|
+
fields: { ...auditSchema[AUDIT_MODEL].fields, ...kioskAuditFields },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { type Access, type AccessBetterAuthOptions, type AccessOptions, type AccessPoliciesOptions, type AccessRegistryOptions, type AccessRules, createAccess, } from "../access/access.factory.js";
|
|
2
|
+
export type { AccessClaimsAnswer, AccessClaimsSource, AccessIdentity, } from "../access/claims.mapper.js";
|
|
3
|
+
export type { AccessKioskProfile, AccessProfileKind, AccessProfileOptions, } from "../access/profile.policy.js";
|
|
4
|
+
export type { AccessAuditEvent, AccessAuditOptions, } from "../audit/audit-event.js";
|
|
5
|
+
export type { AccessKiosk, AccessKioskDevice, } from "../kiosk/kiosk.plugin.js";
|
|
6
|
+
export { type AccessAgent, agentOf } from "../mcp/agent.grant.js";
|
|
7
|
+
export type { AccessAgentIdentity, AccessMcpAuthorization, AccessMcpOptions, } from "../mcp/mcp-options.js";
|
|
8
|
+
export type { AccessArgon2idProfile } from "../passwords/argon2id.hasher.js";
|
|
9
|
+
export type { AccessPasswordsOptions } from "../passwords/passwords.options.js";
|
|
10
|
+
export type { AccessNamespaceKeys, AccessPushResult, AccessRegistry, } from "../registry/access.registry.js";
|
|
11
|
+
export type { AccessKeyKind } from "../registry/registry.key.js";
|
|
12
|
+
export type { AccessResourcesOptions } from "../resources/auth-tables.guard.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AccessComparison } from "./policies-contract.js";
|
|
2
|
+
import type { Resolved } from "./scope.resolver.js";
|
|
3
|
+
/** A fragment of Aventara's `where` (core's `AvPolicyWhere`, by shape). */
|
|
4
|
+
export type AccessWhere = {
|
|
5
|
+
readonly [key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
/** The Aventara filter operator a comparison is written with. */
|
|
8
|
+
export declare const operatorOf: (cmp: AccessComparison) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Sets `key` on `target` as its own data member. An assignment would read
|
|
11
|
+
* `__proto__` as the prototype setter and drop the condition.
|
|
12
|
+
*/
|
|
13
|
+
export declare const defineMember: (target: Record<string, unknown>, key: string, value: unknown) => void;
|
|
14
|
+
/** Merges fragments into one object when no key repeats; otherwise `undefined`. */
|
|
15
|
+
export declare const mergeWhere: (fragments: readonly AccessWhere[]) => AccessWhere | undefined;
|
|
16
|
+
/** Translates a row condition that is neither `true` nor `false` at its root. */
|
|
17
|
+
export declare const translateWhere: (node: Resolved) => AccessWhere;
|
|
18
|
+
/** A seam answer: `true` every row, `false` none, or a `where`. */
|
|
19
|
+
export declare const translateAnswer: (node: Resolved) => boolean | AccessWhere;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Folded condition → Aventara's `where` grammar, with explicit operators
|
|
2
|
+
// (`{ equals: … }`), as core validates it against the Core Contract.
|
|
3
|
+
const OPERATOR = {
|
|
4
|
+
eq: "equals",
|
|
5
|
+
ne: "not",
|
|
6
|
+
in: "in",
|
|
7
|
+
notIn: "notIn",
|
|
8
|
+
};
|
|
9
|
+
/** The Aventara filter operator a comparison is written with. */
|
|
10
|
+
export const operatorOf = (cmp) => OPERATOR[cmp];
|
|
11
|
+
const isField = (node) => node.k === "cmp" ||
|
|
12
|
+
node.k === "rel" ||
|
|
13
|
+
node.k === "some" ||
|
|
14
|
+
node.k === "every" ||
|
|
15
|
+
node.k === "none";
|
|
16
|
+
const keyOf = (node) => (node.k === "cmp" ? node.field : node.relation);
|
|
17
|
+
/**
|
|
18
|
+
* Sets `key` on `target` as its own data member. An assignment would read
|
|
19
|
+
* `__proto__` as the prototype setter and drop the condition.
|
|
20
|
+
*/
|
|
21
|
+
export const defineMember = (target, key, value) => {
|
|
22
|
+
Object.defineProperty(target, key, {
|
|
23
|
+
value,
|
|
24
|
+
enumerable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/** An object with one own member. */
|
|
30
|
+
const one = (key, value) => {
|
|
31
|
+
const where = {};
|
|
32
|
+
defineMember(where, key, value);
|
|
33
|
+
return where;
|
|
34
|
+
};
|
|
35
|
+
/** Merges fragments into one object when no key repeats; otherwise `undefined`. */
|
|
36
|
+
export const mergeWhere = (fragments) => {
|
|
37
|
+
const merged = {};
|
|
38
|
+
for (const fragment of fragments) {
|
|
39
|
+
for (const [key, value] of Object.entries(fragment)) {
|
|
40
|
+
if (Object.hasOwn(merged, key)) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
defineMember(merged, key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return merged;
|
|
47
|
+
};
|
|
48
|
+
/** Translates a row condition that is neither `true` nor `false` at its root. */
|
|
49
|
+
export const translateWhere = (node) => {
|
|
50
|
+
switch (node.k) {
|
|
51
|
+
case "true":
|
|
52
|
+
return {};
|
|
53
|
+
case "false":
|
|
54
|
+
// Aventara's `where` has no `false`: the empty `OR` is its one way to match no
|
|
55
|
+
// row, and an ORM reads it so.
|
|
56
|
+
return { OR: [] };
|
|
57
|
+
case "cmp":
|
|
58
|
+
return one(node.field, {
|
|
59
|
+
[operatorOf(node.cmp)]: Array.isArray(node.value)
|
|
60
|
+
? [...node.value]
|
|
61
|
+
: node.value,
|
|
62
|
+
});
|
|
63
|
+
case "rel":
|
|
64
|
+
return one(node.relation, translateWhere(node.where));
|
|
65
|
+
case "some":
|
|
66
|
+
case "every":
|
|
67
|
+
case "none":
|
|
68
|
+
return one(node.relation, { [node.k]: translateWhere(node.where) });
|
|
69
|
+
case "not":
|
|
70
|
+
return { NOT: translateWhere(node.arg) };
|
|
71
|
+
case "or":
|
|
72
|
+
return { OR: node.args.map(translateWhere) };
|
|
73
|
+
case "all":
|
|
74
|
+
return { AND: node.args.map(translateWhere) };
|
|
75
|
+
case "and": {
|
|
76
|
+
// Written by the author as one object: written back as one when every part is
|
|
77
|
+
// a field condition and no field repeats.
|
|
78
|
+
if (node.args.every(isField) &&
|
|
79
|
+
new Set(node.args.map(keyOf)).size === node.args.length) {
|
|
80
|
+
return mergeWhere(node.args.map(translateWhere));
|
|
81
|
+
}
|
|
82
|
+
return { AND: node.args.map(translateWhere) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** A seam answer: `true` every row, `false` none, or a `where`. */
|
|
87
|
+
export const translateAnswer = (node) => node.k === "true" ? true : node.k === "false" ? false : translateWhere(node);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Reads a namespace's beacon row, through the service's own connection. */
|
|
2
|
+
export type AccessBeaconReader = {
|
|
3
|
+
/**
|
|
4
|
+
* The `value` of `namespace`'s row in `_aventara_beacon`, as this service's
|
|
5
|
+
* connection reads it; `null` when there is none.
|
|
6
|
+
*/
|
|
7
|
+
read(namespace: string): Promise<string | null>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* One environment's beacon pins, `avAccess.beacons.<environment>.json` as
|
|
11
|
+
* `avaccess pull --env <environment>` writes it: the hash of the beacon each
|
|
12
|
+
* namespace the rules read as foreign pushed from that environment's database.
|
|
13
|
+
* `format` is typed as a string so the file imported as JSON is one as it is.
|
|
14
|
+
*/
|
|
15
|
+
export type AccessBeaconPins = {
|
|
16
|
+
/** `"aventara-access/beacons@1"`. */
|
|
17
|
+
readonly format: string;
|
|
18
|
+
/** The namespace that reads the others: the lock's. */
|
|
19
|
+
readonly namespace: string;
|
|
20
|
+
/** The environment the pins were pulled for: `production`, `staging`, … */
|
|
21
|
+
readonly environment: string;
|
|
22
|
+
/** Each owner namespace's beacon hash, `sha256:<hex>`. */
|
|
23
|
+
readonly beacons: {
|
|
24
|
+
readonly [namespace: string]: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** The format marker of every beacon pins file. */
|
|
28
|
+
export declare const BEACON_PINS_FORMAT = "aventara-access/beacons@1";
|
|
29
|
+
/**
|
|
30
|
+
* The error a generated client throws when its beacons have not passed, shaped as
|
|
31
|
+
* `AccessError` is (`name`, `code`, a message that starts `Aventara Access:`): the
|
|
32
|
+
* client imports nothing, so it cannot throw the class itself.
|
|
33
|
+
*/
|
|
34
|
+
export declare const beaconMismatch: (message: string) => Error & {
|
|
35
|
+
readonly code: "ACCESS_BEACON_MISMATCH";
|
|
36
|
+
};
|
|
37
|
+
/** A beacon: `avb_` and 32 random bytes in base64url. */
|
|
38
|
+
export declare const BEACON: RegExp;
|
|
39
|
+
/** A pinned beacon hash: `sha256:` and 64 lowercase hex digits. */
|
|
40
|
+
export declare const BEACON_HASH: RegExp;
|
|
41
|
+
/**
|
|
42
|
+
* A beacon's hash, as the registry and the pins name it: `sha256:<hex>` of
|
|
43
|
+
* `<namespace>:<value>`. The namespace is bound in: a value registered under one
|
|
44
|
+
* namespace never matches, or collides with, another namespace's beacon.
|
|
45
|
+
*/
|
|
46
|
+
export declare const beaconHashOf: (namespace: string, value: string) => Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* What is wrong with `pins` as `namespace`'s beacon pins of `environment` for the
|
|
49
|
+
* `owners` its rules read as foreign: its format and members, its namespace, its
|
|
50
|
+
* environment, a `sha256:` hash for every owner; `undefined` when nothing is.
|
|
51
|
+
*/
|
|
52
|
+
export declare const beaconPinsProblem: (namespace: string, owners: readonly string[], pins: unknown, environment: unknown) => string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* What is wrong with the beacons of `owners` (the namespaces `namespace`'s rules
|
|
55
|
+
* read as foreign), held to `pins` (which must be `environment`'s) and read through
|
|
56
|
+
* `reader`; `undefined` when every
|
|
57
|
+
* owner's row is there and hashes to its pin. Each owner is read once, in order.
|
|
58
|
+
*/
|
|
59
|
+
export declare const beaconProblem: (namespace: string, owners: readonly string[], pins: unknown, environment: unknown, reader: AccessBeaconReader) => Promise<string | undefined>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The ownership beacon's check, as a service runs it when it starts: each namespace
|
|
2
|
+
// whose Resources the rules read as `foreign` has one row in `_aventara_beacon`,
|
|
3
|
+
// written once by that namespace's own service with a random value, and the
|
|
4
|
+
// registry knows the value's hash. A database's beacons are its own, so their
|
|
5
|
+
// hashes are pinned per environment, apart from `avAccess.lock.json` (which holds
|
|
6
|
+
// against any registry): `avaccess pull --env <name>` writes them to
|
|
7
|
+
// `avAccess.beacons.<name>.json` (`AccessBeaconPins`), or the deploy hands the same
|
|
8
|
+
// object in from its environment. At startup the service reads each owner's row
|
|
9
|
+
// through its own connection: the row is there and hashes to the pin only when the
|
|
10
|
+
// service reaches the owner's database. Anything else (no row, another value, a
|
|
11
|
+
// value that is no beacon, an owner with no pin) means the foreign facts would come
|
|
12
|
+
// from somewhere the owner does not write, and the service refuses to decide.
|
|
13
|
+
//
|
|
14
|
+
// The check reads through an object the app hands in (`AccessBeaconReader`): the
|
|
15
|
+
// engine and the generated client have no database connection of their own and
|
|
16
|
+
// name no ORM. This module imports nothing and reaches no runtime API but WebCrypto's
|
|
17
|
+
// digest, so `avaccess generate` copies it into a generated client as it is.
|
|
18
|
+
/** The format marker of every beacon pins file. */
|
|
19
|
+
export const BEACON_PINS_FORMAT = "aventara-access/beacons@1";
|
|
20
|
+
/**
|
|
21
|
+
* The error a generated client throws when its beacons have not passed, shaped as
|
|
22
|
+
* `AccessError` is (`name`, `code`, a message that starts `Aventara Access:`): the
|
|
23
|
+
* client imports nothing, so it cannot throw the class itself.
|
|
24
|
+
*/
|
|
25
|
+
export const beaconMismatch = (message) => Object.assign(new Error(`Aventara Access: ${message}`), {
|
|
26
|
+
name: "AccessError",
|
|
27
|
+
code: "ACCESS_BEACON_MISMATCH",
|
|
28
|
+
});
|
|
29
|
+
/** A beacon: `avb_` and 32 random bytes in base64url. */
|
|
30
|
+
export const BEACON = /^avb_[A-Za-z0-9_-]{43}$/;
|
|
31
|
+
/** A pinned beacon hash: `sha256:` and 64 lowercase hex digits. */
|
|
32
|
+
export const BEACON_HASH = /^sha256:[0-9a-f]{64}$/;
|
|
33
|
+
/** A namespace or environment name: a lowercase letter, then lowercase letters, digits, `_` or `-`. */
|
|
34
|
+
const NAME = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
35
|
+
const PIN_MEMBERS = ["format", "namespace", "environment", "beacons"];
|
|
36
|
+
/**
|
|
37
|
+
* A beacon's hash, as the registry and the pins name it: `sha256:<hex>` of
|
|
38
|
+
* `<namespace>:<value>`. The namespace is bound in: a value registered under one
|
|
39
|
+
* namespace never matches, or collides with, another namespace's beacon.
|
|
40
|
+
*/
|
|
41
|
+
export const beaconHashOf = async (namespace, value) => {
|
|
42
|
+
const runtime = globalThis;
|
|
43
|
+
const digest = await runtime.crypto.subtle.digest("SHA-256", new runtime.TextEncoder().encode(`${namespace}:${value}`));
|
|
44
|
+
let hex = "";
|
|
45
|
+
for (const byte of new Uint8Array(digest)) {
|
|
46
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
47
|
+
}
|
|
48
|
+
return `sha256:${hex}`;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* What is wrong with `pins` as `namespace`'s beacon pins of `environment` for the
|
|
52
|
+
* `owners` its rules read as foreign: its format and members, its namespace, its
|
|
53
|
+
* environment, a `sha256:` hash for every owner; `undefined` when nothing is.
|
|
54
|
+
*/
|
|
55
|
+
export const beaconPinsProblem = (namespace, owners, pins, environment) => {
|
|
56
|
+
if (typeof environment !== "string" || !NAME.test(environment)) {
|
|
57
|
+
return `the environment the service runs in is ${JSON.stringify(environment) ?? "missing"}, which is not an environment name (a lowercase letter, then lowercase letters, digits, "_" or "-"): name it, such as "production", and hand in that environment's pins`;
|
|
58
|
+
}
|
|
59
|
+
const file = pins;
|
|
60
|
+
if (typeof file !== "object" || file === null || Array.isArray(file)) {
|
|
61
|
+
return "the beacon pins are an object, as `avaccess pull --env <environment>` writes avAccess.beacons.<environment>.json";
|
|
62
|
+
}
|
|
63
|
+
if (file.format !== BEACON_PINS_FORMAT) {
|
|
64
|
+
return `the beacon pins' format is "${BEACON_PINS_FORMAT}"; it was ${JSON.stringify(file.format) ?? "missing"}`;
|
|
65
|
+
}
|
|
66
|
+
const unknown = Object.keys(file).filter((member) => !PIN_MEMBERS.includes(member));
|
|
67
|
+
if (unknown.length > 0) {
|
|
68
|
+
return `the beacon pins have \`format\`, \`namespace\`, \`environment\` and \`beacons\`; they do not take ${unknown.map((member) => `\`${member}\``).join(", ")}`;
|
|
69
|
+
}
|
|
70
|
+
if (file.namespace !== namespace) {
|
|
71
|
+
return `the beacon pins are namespace ${JSON.stringify(file.namespace)}'s, and the rules are namespace "${namespace}"'s: pull this project's own pins with \`avaccess pull --env <environment>\``;
|
|
72
|
+
}
|
|
73
|
+
if (file.environment !== environment) {
|
|
74
|
+
return `the beacon pins are environment ${JSON.stringify(file.environment)}'s, and the service runs in environment "${environment}": hand in avAccess.beacons.${environment}.json`;
|
|
75
|
+
}
|
|
76
|
+
const beacons = file.beacons;
|
|
77
|
+
if (typeof beacons !== "object" ||
|
|
78
|
+
beacons === null ||
|
|
79
|
+
Array.isArray(beacons) ||
|
|
80
|
+
Object.entries(beacons).some(([owner, hash]) => !NAME.test(owner) ||
|
|
81
|
+
owner === namespace ||
|
|
82
|
+
typeof hash !== "string" ||
|
|
83
|
+
!BEACON_HASH.test(hash))) {
|
|
84
|
+
return "the beacon pins' `beacons` maps each other namespace whose Resources the rules read to its beacon's `sha256:` hash";
|
|
85
|
+
}
|
|
86
|
+
const missing = owners.filter((owner) => !Object.hasOwn(beacons, owner));
|
|
87
|
+
if (missing.length > 0) {
|
|
88
|
+
return `the rules read ${missing.map((owner) => `namespace "${owner}"'s`).join(" and ")} Resources as foreign, and the beacon pins of environment "${file.environment}" hold no beacon of ${missing.length === 1 ? "it" : "them"}: pull again with \`avaccess pull --env ${file.environment} --beacon <module>\``;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* What is wrong with the beacons of `owners` (the namespaces `namespace`'s rules
|
|
94
|
+
* read as foreign), held to `pins` (which must be `environment`'s) and read through
|
|
95
|
+
* `reader`; `undefined` when every
|
|
96
|
+
* owner's row is there and hashes to its pin. Each owner is read once, in order.
|
|
97
|
+
*/
|
|
98
|
+
export const beaconProblem = async (namespace, owners, pins, environment, reader) => {
|
|
99
|
+
const invalid = beaconPinsProblem(namespace, owners, pins, environment);
|
|
100
|
+
if (invalid !== undefined) {
|
|
101
|
+
return invalid;
|
|
102
|
+
}
|
|
103
|
+
if (typeof reader !== "object" ||
|
|
104
|
+
reader === null ||
|
|
105
|
+
typeof reader.read !== "function") {
|
|
106
|
+
return "the beacon reader is `{ read(namespace) }`, which reads a namespace's row in _aventara_beacon through this service's own connection";
|
|
107
|
+
}
|
|
108
|
+
const { beacons } = pins;
|
|
109
|
+
for (const owner of [...owners].sort()) {
|
|
110
|
+
let value;
|
|
111
|
+
try {
|
|
112
|
+
value = await reader.read(owner);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return `reading namespace "${owner}"'s row in _aventara_beacon failed (${String(error?.message ?? error)}): this service cannot show it reaches "${owner}"'s database`;
|
|
116
|
+
}
|
|
117
|
+
if (value === null || value === undefined) {
|
|
118
|
+
return `this service's connection reads no row of namespace "${owner}" in _aventara_beacon, so it does not reach "${owner}"'s database: the rules read "${owner}"'s Resources as foreign, which works only within one database. Point the service at that database, or carry the fact in a JWT claim instead`;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value !== "string" || !BEACON.test(value)) {
|
|
121
|
+
return `namespace "${owner}"'s row in _aventara_beacon holds no beacon (avb_ and 43 base64url characters): it was changed outside the service that writes it`;
|
|
122
|
+
}
|
|
123
|
+
if ((await beaconHashOf(owner, value)) !== beacons[owner]) {
|
|
124
|
+
return `namespace "${owner}"'s beacon, as this service's connection reads it, is not the one the beacon pins of environment "${environment}" hold: the service reaches another database than "${owner}"'s in that environment. Check that the pins are this environment's, pull them again with \`avaccess pull --env ${environment} --beacon <module>\` (it refuses while the databases differ), or carry the fact in a JWT claim instead`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AccessBeaconStore } from "./beacon.store.js";
|
|
2
|
+
/** The beacon table, in Better Auth's plugin schema form. */
|
|
3
|
+
export declare const beaconSchema: {
|
|
4
|
+
readonly aventaraBeacon: {
|
|
5
|
+
readonly fields: {
|
|
6
|
+
readonly namespace: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly required: true;
|
|
9
|
+
readonly unique: true;
|
|
10
|
+
};
|
|
11
|
+
readonly value: {
|
|
12
|
+
readonly type: "string";
|
|
13
|
+
readonly required: true;
|
|
14
|
+
};
|
|
15
|
+
readonly createdAt: {
|
|
16
|
+
readonly type: "date";
|
|
17
|
+
readonly required: true;
|
|
18
|
+
readonly input: false;
|
|
19
|
+
readonly defaultValue: () => Date;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/** The part of Better Auth's adapter the beacon store uses. */
|
|
25
|
+
export type BeaconAdapter = {
|
|
26
|
+
findOne<T>(input: {
|
|
27
|
+
model: string;
|
|
28
|
+
where: {
|
|
29
|
+
field: string;
|
|
30
|
+
value: string;
|
|
31
|
+
}[];
|
|
32
|
+
}): Promise<T | null>;
|
|
33
|
+
create<T>(input: {
|
|
34
|
+
model: string;
|
|
35
|
+
data: {
|
|
36
|
+
readonly [field: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
}): Promise<T>;
|
|
39
|
+
};
|
|
40
|
+
/** The beacon store over the adapter `adapterOf` resolves (Better Auth's, once built). */
|
|
41
|
+
export declare const adapterBeaconStore: (adapterOf: () => Promise<BeaconAdapter>) => AccessBeaconStore;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The Access server's own beacon table, declared through Better Auth's plugin schema:
|
|
2
|
+
// when the registry is on and the instance's rules name a namespace (the Access
|
|
3
|
+
// server is then one of the org's namespaces, its Resources Better Auth's tables),
|
|
4
|
+
// the Access plugin carries it, so Better Auth's own migration (or its CLI's
|
|
5
|
+
// `generate`) creates it beside Better Auth's tables, and `access.beacon` reads and
|
|
6
|
+
// writes its row through whatever adapter the app gave Better Auth.
|
|
7
|
+
//
|
|
8
|
+
// Its name follows Access's other tables (`accessAudit`, `accessKey`): the camelCase
|
|
9
|
+
// model `aventaraBeacon`, which Better Auth's Prisma generator writes as
|
|
10
|
+
// `model AventaraBeacon { … @@map("aventaraBeacon") }`. A service's own table may be
|
|
11
|
+
// called `_aventara_beacon`; Better Auth's generator would write that name as the
|
|
12
|
+
// Prisma model's own (`model _aventara_beacon`), which Prisma refuses, so the Access
|
|
13
|
+
// server's is not. The generated client's `prismaBeacon` reads either through the
|
|
14
|
+
// `AventaraBeacon` model's delegate. The auth-tables guard keeps the table closed and
|
|
15
|
+
// its `value` sealed.
|
|
16
|
+
/** The beacon table's Better Auth model name. */
|
|
17
|
+
const BEACON_MODEL = "aventaraBeacon";
|
|
18
|
+
/** The beacon table, in Better Auth's plugin schema form. */
|
|
19
|
+
export const beaconSchema = {
|
|
20
|
+
[BEACON_MODEL]: {
|
|
21
|
+
fields: {
|
|
22
|
+
namespace: { type: "string", required: true, unique: true },
|
|
23
|
+
value: { type: "string", required: true },
|
|
24
|
+
createdAt: {
|
|
25
|
+
type: "date",
|
|
26
|
+
required: true,
|
|
27
|
+
input: false,
|
|
28
|
+
defaultValue: () => new Date(),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
/** The beacon store over the adapter `adapterOf` resolves (Better Auth's, once built). */
|
|
34
|
+
export const adapterBeaconStore = (adapterOf) => ({
|
|
35
|
+
read: async (namespace) => {
|
|
36
|
+
const row = await (await adapterOf()).findOne({
|
|
37
|
+
model: BEACON_MODEL,
|
|
38
|
+
where: [{ field: "namespace", value: namespace }],
|
|
39
|
+
});
|
|
40
|
+
return typeof row?.value === "string" ? row.value : null;
|
|
41
|
+
},
|
|
42
|
+
create: async (namespace, value) => {
|
|
43
|
+
await (await adapterOf()).create({
|
|
44
|
+
model: BEACON_MODEL,
|
|
45
|
+
data: { namespace, value },
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AccessBeaconReader } from "./beacon.check.js";
|
|
2
|
+
/** The beacon table's name. */
|
|
3
|
+
export declare const BEACON_TABLE = "_aventara_beacon";
|
|
4
|
+
/** Reads and writes beacon rows, through the service's own data layer. */
|
|
5
|
+
export type AccessBeaconStore = AccessBeaconReader & {
|
|
6
|
+
/**
|
|
7
|
+
* Writes `namespace`'s row with `value`, once: when the namespace has a row
|
|
8
|
+
* already, it throws (a unique `namespace`), and the row stays as it was.
|
|
9
|
+
*/
|
|
10
|
+
create(namespace: string, value: string): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
/** A new beacon: 256 random bits from the runtime's CSPRNG, written `avb_…`. */
|
|
13
|
+
export declare const newBeacon: () => string;
|
|
14
|
+
/** Whether `value` is a beacon. */
|
|
15
|
+
export declare const isBeacon: (value: unknown) => value is string;
|
|
16
|
+
/**
|
|
17
|
+
* The namespace's own beacon, for its push: a new row written now when the
|
|
18
|
+
* namespace has none, or the row it has when its hash is the one the registry holds
|
|
19
|
+
* for the namespace (`registered`, `null` when it holds none). A row this service
|
|
20
|
+
* did not write and the registry does not know is never adopted: another service
|
|
21
|
+
* with write access to the table (a co-tenant of a shared schema) could have planted
|
|
22
|
+
* it, so it is refused (`ACCESS_BEACON_MISMATCH`) until an operator verifies it or
|
|
23
|
+
* deletes it, and the next push writes a new one. A row whose value is no beacon is
|
|
24
|
+
* refused too, never overwritten.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ensureBeacon: (store: AccessBeaconStore, namespace: string, registered: string | null) => Promise<{
|
|
27
|
+
readonly value: string;
|
|
28
|
+
readonly created: boolean;
|
|
29
|
+
}>;
|