@atlasauth/backend 0.24.0 → 0.26.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 +39 -1
- package/dist/client/abac.d.ts +521 -0
- package/dist/client/abac.js +146 -0
- package/dist/client/abac.js.map +1 -0
- package/dist/client/access-policies.d.ts +100 -0
- package/dist/client/access-policies.js +29 -0
- package/dist/client/access-policies.js.map +1 -0
- package/dist/client/action-gallery.d.ts +1 -1
- package/dist/client/action-gallery.js.map +1 -1
- package/dist/client/actions.d.ts +129 -6
- package/dist/client/actions.js +4 -0
- package/dist/client/actions.js.map +1 -1
- package/dist/client/analytics-reports.d.ts +215 -0
- package/dist/client/analytics-reports.js +56 -0
- package/dist/client/analytics-reports.js.map +1 -0
- package/dist/client/app-catalog.d.ts +74 -0
- package/dist/client/app-catalog.js +30 -0
- package/dist/client/app-catalog.js.map +1 -0
- package/dist/client/attack-protection.d.ts +10 -0
- package/dist/client/attack-protection.js +8 -0
- package/dist/client/attack-protection.js.map +1 -1
- package/dist/client/audit-events.d.ts +71 -0
- package/dist/client/audit-events.js +28 -0
- package/dist/client/audit-events.js.map +1 -1
- package/dist/client/authz-decisions.d.ts +108 -0
- package/dist/client/authz-decisions.js +40 -0
- package/dist/client/authz-decisions.js.map +1 -0
- package/dist/client/connections.d.ts +507 -0
- package/dist/client/connections.js +164 -0
- package/dist/client/connections.js.map +1 -0
- package/dist/client/credentials.d.ts +8 -0
- package/dist/client/credentials.js.map +1 -1
- package/dist/client/custom-oauth-providers.d.ts +15 -0
- package/dist/client/custom-oauth-providers.js.map +1 -1
- package/dist/client/delegated-ui-proxy.d.ts +25 -0
- package/dist/client/delegated-ui-proxy.js +46 -0
- package/dist/client/delegated-ui-proxy.js.map +1 -0
- package/dist/client/desktop-mfa.d.ts +104 -0
- package/dist/client/desktop-mfa.js +44 -0
- package/dist/client/desktop-mfa.js.map +1 -0
- package/dist/client/devices.d.ts +44 -0
- package/dist/client/devices.js +26 -0
- package/dist/client/devices.js.map +1 -0
- package/dist/client/email-templates.d.ts +1 -1
- package/dist/client/email-templates.js.map +1 -1
- package/dist/client/fga.d.ts +125 -4
- package/dist/client/fga.js +40 -0
- package/dist/client/fga.js.map +1 -1
- package/dist/client/flags.d.ts +101 -0
- package/dist/client/flags.js +104 -0
- package/dist/client/flags.js.map +1 -0
- package/dist/client/groups.d.ts +146 -0
- package/dist/client/groups.js +67 -0
- package/dist/client/groups.js.map +1 -0
- package/dist/client/identity-verification.d.ts +62 -0
- package/dist/client/identity-verification.js +39 -0
- package/dist/client/identity-verification.js.map +1 -0
- package/dist/client/iga.d.ts +436 -0
- package/dist/client/iga.js +82 -0
- package/dist/client/iga.js.map +1 -0
- package/dist/client/index.d.ts +1433 -4
- package/dist/client/index.js +96 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/journeys.d.ts +108 -0
- package/dist/client/journeys.js +35 -0
- package/dist/client/journeys.js.map +1 -0
- package/dist/client/least-privilege.d.ts +95 -0
- package/dist/client/least-privilege.js +28 -0
- package/dist/client/least-privilege.js.map +1 -0
- package/dist/client/log-streams.d.ts +1 -1
- package/dist/client/managed-grants.d.ts +52 -0
- package/dist/client/managed-grants.js +28 -0
- package/dist/client/managed-grants.js.map +1 -0
- package/dist/client/marketing.d.ts +67 -0
- package/dist/client/marketing.js +26 -0
- package/dist/client/marketing.js.map +1 -0
- package/dist/client/mcp-gateways.d.ts +126 -0
- package/dist/client/mcp-gateways.js +47 -0
- package/dist/client/mcp-gateways.js.map +1 -0
- package/dist/client/messaging.d.ts +14 -3
- package/dist/client/messaging.js.map +1 -1
- package/dist/client/mfa-factors.d.ts +120 -0
- package/dist/client/mfa-factors.js +73 -0
- package/dist/client/mfa-factors.js.map +1 -0
- package/dist/client/migrations.d.ts +1 -1
- package/dist/client/migrations.js.map +1 -1
- package/dist/client/network-acls.d.ts +6 -1
- package/dist/client/network-acls.js.map +1 -1
- package/dist/client/network-zones.d.ts +56 -0
- package/dist/client/network-zones.js +29 -0
- package/dist/client/network-zones.js.map +1 -0
- package/dist/client/notifications.d.ts +1 -1
- package/dist/client/notifications.js.map +1 -1
- package/dist/client/oauth-clients.d.ts +187 -3
- package/dist/client/oauth-clients.js +64 -0
- package/dist/client/oauth-clients.js.map +1 -1
- package/dist/client/openfga.d.ts +90 -3
- package/dist/client/openfga.js +27 -1
- package/dist/client/openfga.js.map +1 -1
- package/dist/client/org-role-grants.d.ts +57 -0
- package/dist/client/org-role-grants.js +40 -0
- package/dist/client/org-role-grants.js.map +1 -0
- package/dist/client/organizations.d.ts +52 -0
- package/dist/client/organizations.js +44 -0
- package/dist/client/organizations.js.map +1 -1
- package/dist/client/page-templates.d.ts +78 -0
- package/dist/client/page-templates.js +31 -0
- package/dist/client/page-templates.js.map +1 -0
- package/dist/client/passkey-api.d.ts +73 -0
- package/dist/client/passkey-api.js +34 -0
- package/dist/client/passkey-api.js.map +1 -0
- package/dist/client/policy-bundles.d.ts +105 -0
- package/dist/client/policy-bundles.js +38 -0
- package/dist/client/policy-bundles.js.map +1 -0
- package/dist/client/resources.d.ts +150 -0
- package/dist/client/resources.js +73 -0
- package/dist/client/resources.js.map +1 -0
- package/dist/client/risk-based-mfa.d.ts +147 -0
- package/dist/client/risk-based-mfa.js +30 -0
- package/dist/client/risk-based-mfa.js.map +1 -1
- package/dist/client/roles.d.ts +4 -0
- package/dist/client/roles.js.map +1 -1
- package/dist/client/service-connectors.d.ts +83 -0
- package/dist/client/service-connectors.js +33 -0
- package/dist/client/service-connectors.js.map +1 -0
- package/dist/client/ssf.d.ts +112 -0
- package/dist/client/ssf.js +49 -0
- package/dist/client/ssf.js.map +1 -0
- package/dist/client/sso-onboarding.d.ts +6 -0
- package/dist/client/sso-onboarding.js.map +1 -1
- package/dist/client/users.d.ts +302 -0
- package/dist/client/users.js +125 -0
- package/dist/client/users.js.map +1 -1
- package/dist/client/vault.d.ts +85 -0
- package/dist/client/vault.js +50 -0
- package/dist/client/vault.js.map +1 -0
- package/dist/client/vc.d.ts +136 -0
- package/dist/client/vc.js +43 -0
- package/dist/client/vc.js.map +1 -0
- package/dist/client/webhooks.d.ts +31 -2
- package/dist/client/webhooks.js.map +1 -1
- package/dist/client/workflows.d.ts +131 -0
- package/dist/client/workflows.js +36 -0
- package/dist/client/workflows.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/magic-did.d.ts +5 -0
- package/dist/magic-did.js +49 -0
- package/dist/magic-did.js.map +1 -0
- package/dist/migration-bridge.d.ts +9 -0
- package/dist/migration-bridge.js +28 -2
- package/dist/migration-bridge.js.map +1 -1
- package/dist/migration-presets-g3.d.ts +2 -0
- package/dist/migration-presets-g3.js +254 -0
- package/dist/migration-presets-g3.js.map +1 -0
- package/dist/migration-presets-g4/authentik.d.ts +14 -0
- package/dist/migration-presets-g4/authentik.js +37 -0
- package/dist/migration-presets-g4/authentik.js.map +1 -0
- package/dist/migration-presets-g4/fusionauth.d.ts +2 -0
- package/dist/migration-presets-g4/fusionauth.js +52 -0
- package/dist/migration-presets-g4/fusionauth.js.map +1 -0
- package/dist/migration-presets-g4/hanko.d.ts +12 -0
- package/dist/migration-presets-g4/hanko.js +46 -0
- package/dist/migration-presets-g4/hanko.js.map +1 -0
- package/dist/migration-presets-g4/keycloak.d.ts +16 -0
- package/dist/migration-presets-g4/keycloak.js +37 -0
- package/dist/migration-presets-g4/keycloak.js.map +1 -0
- package/dist/migration-presets-g4/logto.d.ts +12 -0
- package/dist/migration-presets-g4/logto.js +35 -0
- package/dist/migration-presets-g4/logto.js.map +1 -0
- package/dist/migration-presets-g4/ory.d.ts +2 -0
- package/dist/migration-presets-g4/ory.js +67 -0
- package/dist/migration-presets-g4/ory.js.map +1 -0
- package/dist/migration-presets-g4/supertokens.d.ts +13 -0
- package/dist/migration-presets-g4/supertokens.js +35 -0
- package/dist/migration-presets-g4/supertokens.js.map +1 -0
- package/dist/migration-presets-g4/zitadel.d.ts +2 -0
- package/dist/migration-presets-g4/zitadel.js +36 -0
- package/dist/migration-presets-g4/zitadel.js.map +1 -0
- package/dist/migration-presets-g4.d.ts +10 -0
- package/dist/migration-presets-g4.js +30 -0
- package/dist/migration-presets-g4.js.map +1 -0
- package/dist/migration-presets.d.ts +6 -0
- package/dist/migration-presets.js +102 -6
- package/dist/migration-presets.js.map +1 -1
- package/dist/policy-bundle.d.ts +110 -0
- package/dist/policy-bundle.js +289 -0
- package/dist/policy-bundle.js.map +1 -0
- package/dist/request-auth.d.ts +32 -0
- package/dist/request-auth.js +72 -0
- package/dist/request-auth.js.map +1 -0
- package/dist/uma.d.ts +77 -0
- package/dist/uma.js +95 -0
- package/dist/uma.js.map +1 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.js.map +1 -1
- package/dist/workos-session.d.ts +30 -0
- package/dist/workos-session.js +123 -0
- package/dist/workos-session.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PolicyBundleError = void 0;
|
|
4
|
+
exports.verifyPolicyBundle = verifyPolicyBundle;
|
|
5
|
+
const jose_1 = require("jose");
|
|
6
|
+
class PolicyBundleError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.name = 'PolicyBundleError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.PolicyBundleError = PolicyBundleError;
|
|
15
|
+
const MAX_BYTES = 16 * 1024 * 1024;
|
|
16
|
+
const MAX_FILES = 5000;
|
|
17
|
+
const TYP = 'atlas-policy-bundle+jws';
|
|
18
|
+
const BLOCK = 512;
|
|
19
|
+
const td = new TextDecoder('utf-8', { fatal: true });
|
|
20
|
+
const te = new TextEncoder();
|
|
21
|
+
const toBytes = (b) => {
|
|
22
|
+
if (typeof b !== 'string')
|
|
23
|
+
return b;
|
|
24
|
+
const bin = atob(b.replace(/-/g, '+').replace(/_/g, '/'));
|
|
25
|
+
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
26
|
+
};
|
|
27
|
+
const b64url = (b) => btoa(Array.from(b, (c) => String.fromCharCode(c)).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
28
|
+
const hex = (b) => Array.from(new Uint8Array(b), (x) => x.toString(16).padStart(2, '0')).join('');
|
|
29
|
+
const sha256 = async (b) => hex(await crypto.subtle.digest('SHA-256', typeof b === 'string' ? te.encode(b) : b));
|
|
30
|
+
/** JSON with sorted keys and no whitespace — must match what the signer hashed. */
|
|
31
|
+
function canonicalJson(v) {
|
|
32
|
+
if (v === null || typeof v !== 'object')
|
|
33
|
+
return JSON.stringify(v) ?? 'null';
|
|
34
|
+
if (Array.isArray(v))
|
|
35
|
+
return `[${v.map(canonicalJson).join(',')}]`;
|
|
36
|
+
const o = v;
|
|
37
|
+
return `{${Object.keys(o)
|
|
38
|
+
.filter((k) => o[k] !== undefined)
|
|
39
|
+
.sort()
|
|
40
|
+
.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`)
|
|
41
|
+
.join(',')}}`;
|
|
42
|
+
}
|
|
43
|
+
async function gunzip(bytes) {
|
|
44
|
+
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'));
|
|
45
|
+
const reader = stream.getReader();
|
|
46
|
+
const chunks = [];
|
|
47
|
+
let total = 0;
|
|
48
|
+
try {
|
|
49
|
+
for (;;) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done)
|
|
52
|
+
break;
|
|
53
|
+
total += value.length;
|
|
54
|
+
if (total > MAX_BYTES) {
|
|
55
|
+
await reader.cancel();
|
|
56
|
+
throw new PolicyBundleError('invalid_archive', 'The bundle expands beyond the allowed size.');
|
|
57
|
+
}
|
|
58
|
+
chunks.push(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err instanceof PolicyBundleError)
|
|
63
|
+
throw err;
|
|
64
|
+
throw new PolicyBundleError('invalid_archive', 'The bundle is not a valid gzip archive.');
|
|
65
|
+
}
|
|
66
|
+
const out = new Uint8Array(total);
|
|
67
|
+
let off = 0;
|
|
68
|
+
for (const c of chunks) {
|
|
69
|
+
out.set(c, off);
|
|
70
|
+
off += c.length;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function untar(tar) {
|
|
75
|
+
const bad = (m) => new PolicyBundleError('invalid_archive', m);
|
|
76
|
+
const str = (h, off, len) => {
|
|
77
|
+
let end = off;
|
|
78
|
+
while (end < off + len && h[end] !== 0)
|
|
79
|
+
end++;
|
|
80
|
+
try {
|
|
81
|
+
return td.decode(h.subarray(off, end));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw bad('tar header is not valid UTF-8');
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const oct = (h, off, len) => {
|
|
88
|
+
const s = str(h, off, len).trim();
|
|
89
|
+
if (!/^[0-7]+$/.test(s))
|
|
90
|
+
throw bad('malformed numeric field in tar header');
|
|
91
|
+
return parseInt(s, 8);
|
|
92
|
+
};
|
|
93
|
+
const files = new Map();
|
|
94
|
+
let off = 0;
|
|
95
|
+
while (off + BLOCK <= tar.length) {
|
|
96
|
+
const h = tar.subarray(off, off + BLOCK);
|
|
97
|
+
if (h.every((b) => b === 0))
|
|
98
|
+
return files;
|
|
99
|
+
let sum = 0;
|
|
100
|
+
for (let i = 0; i < BLOCK; i++)
|
|
101
|
+
sum += i >= 148 && i < 156 ? 0x20 : h[i];
|
|
102
|
+
if (oct(h, 148, 8) !== sum)
|
|
103
|
+
throw bad('tar header checksum mismatch');
|
|
104
|
+
if (h[156] !== 0x30 && h[156] !== 0)
|
|
105
|
+
throw bad('tar contains an entry that is not a regular file');
|
|
106
|
+
const path = str(h, 0, 100);
|
|
107
|
+
if (!path || path.startsWith('/') || path.includes('\\') || path.split('/').some((p) => p === '' || p === '.' || p === '..')) {
|
|
108
|
+
throw bad(`unsafe path in archive: '${path.slice(0, 60)}'`);
|
|
109
|
+
}
|
|
110
|
+
if (files.has(path))
|
|
111
|
+
throw bad(`duplicate path in archive: '${path}'`);
|
|
112
|
+
if (files.size + 1 > MAX_FILES)
|
|
113
|
+
throw bad('archive has too many files');
|
|
114
|
+
const size = oct(h, 124, 12);
|
|
115
|
+
off += BLOCK;
|
|
116
|
+
if (off + size > tar.length)
|
|
117
|
+
throw bad('tar is truncated');
|
|
118
|
+
files.set(path, tar.slice(off, off + size));
|
|
119
|
+
off += Math.ceil(size / BLOCK) * BLOCK;
|
|
120
|
+
}
|
|
121
|
+
throw bad('tar is missing its end marker');
|
|
122
|
+
}
|
|
123
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
124
|
+
function readPolicy(v, path) {
|
|
125
|
+
const bad = (why) => new PolicyBundleError('invalid_content', `${path}: ${why}`);
|
|
126
|
+
if (!isObj(v) || typeof v.name !== 'string' || !v.name)
|
|
127
|
+
throw bad('a policy needs a name');
|
|
128
|
+
if (typeof v.source !== 'string')
|
|
129
|
+
throw bad('source must be a string');
|
|
130
|
+
if (typeof v.enabled !== 'boolean')
|
|
131
|
+
throw bad('enabled must be a boolean');
|
|
132
|
+
if (v.description !== null && typeof v.description !== 'string')
|
|
133
|
+
throw bad('description must be a string or null');
|
|
134
|
+
if (v.fga_store !== null && typeof v.fga_store !== 'string')
|
|
135
|
+
throw bad('fga_store must be a store name or null');
|
|
136
|
+
if (!Array.isArray(v.tests))
|
|
137
|
+
throw bad('tests must be an array');
|
|
138
|
+
return v;
|
|
139
|
+
}
|
|
140
|
+
function readStore(v, path) {
|
|
141
|
+
const bad = (why) => new PolicyBundleError('invalid_content', `${path}: ${why}`);
|
|
142
|
+
if (!isObj(v) || typeof v.name !== 'string' || !v.name)
|
|
143
|
+
throw bad('a store needs a name');
|
|
144
|
+
if (v.model !== null && (!isObj(v.model) || !Array.isArray(v.model.type_definitions) || typeof v.model.schema_version !== 'string')) {
|
|
145
|
+
throw bad('model must be null or { schema_version, type_definitions }');
|
|
146
|
+
}
|
|
147
|
+
return v;
|
|
148
|
+
}
|
|
149
|
+
function readTemplate(v, path) {
|
|
150
|
+
const bad = (why) => new PolicyBundleError('invalid_content', `${path}: ${why}`);
|
|
151
|
+
if (!isObj(v) || typeof v.name !== 'string' || !v.name)
|
|
152
|
+
throw bad('a template needs a name');
|
|
153
|
+
if (typeof v.source !== 'string')
|
|
154
|
+
throw bad('source must be a string');
|
|
155
|
+
if (v.description !== null && typeof v.description !== 'string')
|
|
156
|
+
throw bad('description must be a string or null');
|
|
157
|
+
if (v.fga_store !== null && typeof v.fga_store !== 'string')
|
|
158
|
+
throw bad('fga_store must be a store name or null');
|
|
159
|
+
return v;
|
|
160
|
+
}
|
|
161
|
+
function readLink(v, path) {
|
|
162
|
+
const bad = (why) => new PolicyBundleError('invalid_content', `${path}: ${why}`);
|
|
163
|
+
const entity = (e) => e === null || (isObj(e) && typeof e.type === 'string' && !!e.type && typeof e.id === 'string' && !!e.id);
|
|
164
|
+
if (!isObj(v) || typeof v.name !== 'string' || !v.name)
|
|
165
|
+
throw bad('a link needs a name');
|
|
166
|
+
if (typeof v.template !== 'string' || !v.template)
|
|
167
|
+
throw bad('template must be a template name');
|
|
168
|
+
if (typeof v.enabled !== 'boolean')
|
|
169
|
+
throw bad('enabled must be a boolean');
|
|
170
|
+
if (!entity(v.principal) || !entity(v.resource))
|
|
171
|
+
throw bad('principal and resource must be null or { type, id }');
|
|
172
|
+
return v;
|
|
173
|
+
}
|
|
174
|
+
const byName = (xs) => [...xs].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
175
|
+
/**
|
|
176
|
+
* Verify and open a signed policy bundle. Throws {@link PolicyBundleError} with a
|
|
177
|
+
* stable `code` on any failure; returns the content only when everything checks out.
|
|
178
|
+
*/
|
|
179
|
+
async function verifyPolicyBundle(opts) {
|
|
180
|
+
const archive = toBytes(opts.bundle);
|
|
181
|
+
if (archive.length > MAX_BYTES)
|
|
182
|
+
throw new PolicyBundleError('invalid_archive', 'The bundle is too large.');
|
|
183
|
+
// 1. authenticity: the detached JWS over the archive bytes.
|
|
184
|
+
const parts = opts.signature.split('.');
|
|
185
|
+
if (parts.length !== 3 || parts[1] !== '' || !parts[0] || !parts[2]) {
|
|
186
|
+
throw new PolicyBundleError('invalid_signature', 'The signature must be a detached compact JWS ("header..signature").');
|
|
187
|
+
}
|
|
188
|
+
const keys = Array.isArray(opts.jwks) ? opts.jwks : opts.jwks.keys;
|
|
189
|
+
let header;
|
|
190
|
+
try {
|
|
191
|
+
header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
throw new PolicyBundleError('invalid_signature', 'The signature header is not valid.');
|
|
195
|
+
}
|
|
196
|
+
if (header.kid && !keys.some((k) => k.kid === header.kid)) {
|
|
197
|
+
throw new PolicyBundleError('unknown_key', `The bundle was signed with key ${header.kid}, which is not in the trusted keys.`);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const { protectedHeader } = await (0, jose_1.compactVerify)(`${parts[0]}.${b64url(archive)}.${parts[2]}`, (0, jose_1.createLocalJWKSet)({ keys }), {
|
|
201
|
+
algorithms: ['RS256', 'ES256'],
|
|
202
|
+
});
|
|
203
|
+
if (protectedHeader.typ !== TYP)
|
|
204
|
+
throw new Error('not a policy-bundle signature');
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
throw new PolicyBundleError('invalid_signature', `The bundle's signature does not verify: ${err instanceof Error ? err.message : 'failed'}.`);
|
|
208
|
+
}
|
|
209
|
+
// 2 + 3. integrity.
|
|
210
|
+
const files = untar(await gunzip(archive));
|
|
211
|
+
const manifestBytes = files.get('manifest.json');
|
|
212
|
+
if (!manifestBytes)
|
|
213
|
+
throw new PolicyBundleError('invalid_manifest', 'The bundle has no manifest.json.');
|
|
214
|
+
let manifest;
|
|
215
|
+
try {
|
|
216
|
+
manifest = JSON.parse(td.decode(manifestBytes));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
throw new PolicyBundleError('invalid_manifest', 'manifest.json is not valid JSON.');
|
|
220
|
+
}
|
|
221
|
+
if (!isObj(manifest) || manifest.format !== 'atlas.policy-bundle' || !Array.isArray(manifest.files) || typeof manifest.revision !== 'string') {
|
|
222
|
+
throw new PolicyBundleError('invalid_manifest', 'manifest.json is not an Atlas policy bundle manifest.');
|
|
223
|
+
}
|
|
224
|
+
if (manifest.format_version !== 1)
|
|
225
|
+
throw new PolicyBundleError('unsupported_version', `Unsupported bundle format_version ${String(manifest.format_version)}.`);
|
|
226
|
+
files.delete('manifest.json');
|
|
227
|
+
const listed = new Set();
|
|
228
|
+
for (const f of manifest.files) {
|
|
229
|
+
if (!isObj(f) || typeof f.path !== 'string' || typeof f.sha256 !== 'string')
|
|
230
|
+
throw new PolicyBundleError('invalid_manifest', 'manifest.json has a malformed file entry.');
|
|
231
|
+
if (listed.has(f.path))
|
|
232
|
+
throw new PolicyBundleError('invalid_manifest', `manifest.json lists ${f.path} twice.`);
|
|
233
|
+
listed.add(f.path);
|
|
234
|
+
const data = files.get(f.path);
|
|
235
|
+
if (!data)
|
|
236
|
+
throw new PolicyBundleError('missing_file', `${f.path} is listed in the manifest but missing from the archive.`);
|
|
237
|
+
if ((await sha256(data)) !== f.sha256)
|
|
238
|
+
throw new PolicyBundleError('digest_mismatch', `${f.path} does not match its digest in the manifest.`);
|
|
239
|
+
}
|
|
240
|
+
for (const p of files.keys()) {
|
|
241
|
+
if (!listed.has(p))
|
|
242
|
+
throw new PolicyBundleError('unlisted_file', `${p} is in the archive but not in the manifest.`);
|
|
243
|
+
}
|
|
244
|
+
const policies = [];
|
|
245
|
+
const fgaStores = [];
|
|
246
|
+
const templates = [];
|
|
247
|
+
const links = [];
|
|
248
|
+
let schema = null;
|
|
249
|
+
for (const [path, data] of files) {
|
|
250
|
+
let json;
|
|
251
|
+
try {
|
|
252
|
+
json = JSON.parse(td.decode(data));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
throw new PolicyBundleError('invalid_content', `${path} is not valid JSON.`);
|
|
256
|
+
}
|
|
257
|
+
if (path.startsWith('abac/policies/'))
|
|
258
|
+
policies.push(readPolicy(json, path));
|
|
259
|
+
else if (path.startsWith('fga/stores/'))
|
|
260
|
+
fgaStores.push(readStore(json, path));
|
|
261
|
+
else if (path === 'abac/schema.json') {
|
|
262
|
+
if (!isObj(json))
|
|
263
|
+
throw new PolicyBundleError('invalid_content', `${path}: the schema must be an object.`);
|
|
264
|
+
schema = json;
|
|
265
|
+
}
|
|
266
|
+
else if (path.startsWith('abac/templates/'))
|
|
267
|
+
templates.push(readTemplate(json, path));
|
|
268
|
+
else if (path.startsWith('abac/links/'))
|
|
269
|
+
links.push(readLink(json, path));
|
|
270
|
+
else
|
|
271
|
+
throw new PolicyBundleError('invalid_content', `${path} is not a recognised bundle file.`);
|
|
272
|
+
}
|
|
273
|
+
// Same normal form as the server: empty template/link lists and a missing schema are left out.
|
|
274
|
+
const normal = {
|
|
275
|
+
abac_policies: byName(policies),
|
|
276
|
+
fga_stores: byName(fgaStores),
|
|
277
|
+
...(schema ? { abac_schema: schema } : {}),
|
|
278
|
+
...(templates.length ? { abac_templates: byName(templates) } : {}),
|
|
279
|
+
...(links.length ? { abac_links: byName(links) } : {}),
|
|
280
|
+
};
|
|
281
|
+
const recomputed = `rev_${(await sha256(canonicalJson(normal))).slice(0, 32)}`;
|
|
282
|
+
if (recomputed !== manifest.revision)
|
|
283
|
+
throw new PolicyBundleError('revision_mismatch', "The bundle's content does not match the revision in its manifest.");
|
|
284
|
+
if (opts.expectedRevision && opts.expectedRevision !== manifest.revision) {
|
|
285
|
+
throw new PolicyBundleError('unexpected_revision', `Expected revision ${opts.expectedRevision} but the bundle is ${manifest.revision}.`);
|
|
286
|
+
}
|
|
287
|
+
return { revision: manifest.revision, manifest, policies: byName(policies), fgaStores: byName(fgaStores), abacSchema: schema, abacTemplates: byName(templates), abacLinks: byName(links), keyId: header.kid, algorithm: header.alg ?? '' };
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=policy-bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-bundle.js","sourceRoot":"","sources":["../src/policy-bundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ProtectCondition } from '@atlasauth/authz';
|
|
2
|
+
import type { AtlasBackend, SessionClaims } from './verify';
|
|
3
|
+
/**
|
|
4
|
+
* Framework-neutral request authentication, shared by the Koa, Hapi and GraphQL
|
|
5
|
+
* adapters: read the `__session` cookie or `Authorization: Bearer` header
|
|
6
|
+
* (the header wins), verify locally against the JWKS and return the auth state.
|
|
7
|
+
* It never throws: a missing or bad token is the signed-out state, whose
|
|
8
|
+
* `has()` is `false` and `protect()` throws.
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestAuth {
|
|
11
|
+
userId: string | null;
|
|
12
|
+
sessionId: string | null;
|
|
13
|
+
orgId: string | null;
|
|
14
|
+
orgRole: string | null;
|
|
15
|
+
claims: SessionClaims | null;
|
|
16
|
+
isSignedIn: boolean;
|
|
17
|
+
/** The impersonator (RFC 8693 `act`) or null. */
|
|
18
|
+
actor: {
|
|
19
|
+
sub: string;
|
|
20
|
+
} | null;
|
|
21
|
+
has(condition?: ProtectCondition): boolean;
|
|
22
|
+
/** Assert a condition; returns the claims or throws `ForbiddenError`. */
|
|
23
|
+
protect(condition?: ProtectCondition): SessionClaims;
|
|
24
|
+
}
|
|
25
|
+
export declare function signedOutAuth(): RequestAuth;
|
|
26
|
+
export declare function signedInAuth(claims: SessionClaims): RequestAuth;
|
|
27
|
+
type HeaderValue = string | string[] | undefined;
|
|
28
|
+
/** The raw session token a request carries (bearer header first, then the `__session` cookie), or null. */
|
|
29
|
+
export declare function sessionTokenFromHeaders(headers: Record<string, HeaderValue>): string | null;
|
|
30
|
+
/** Verify a request's headers into a {@link RequestAuth}. Never throws. */
|
|
31
|
+
export declare function authenticateHeaders(backend: AtlasBackend, headers: Record<string, HeaderValue>): Promise<RequestAuth>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signedOutAuth = signedOutAuth;
|
|
4
|
+
exports.signedInAuth = signedInAuth;
|
|
5
|
+
exports.sessionTokenFromHeaders = sessionTokenFromHeaders;
|
|
6
|
+
exports.authenticateHeaders = authenticateHeaders;
|
|
7
|
+
const authz_1 = require("@atlasauth/authz");
|
|
8
|
+
function signedOutAuth() {
|
|
9
|
+
return {
|
|
10
|
+
userId: null,
|
|
11
|
+
sessionId: null,
|
|
12
|
+
orgId: null,
|
|
13
|
+
orgRole: null,
|
|
14
|
+
claims: null,
|
|
15
|
+
isSignedIn: false,
|
|
16
|
+
actor: null,
|
|
17
|
+
has: () => false,
|
|
18
|
+
protect: (condition = {}) => {
|
|
19
|
+
throw new authz_1.ForbiddenError('signed_out', condition);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function signedInAuth(claims) {
|
|
24
|
+
return {
|
|
25
|
+
userId: claims.sub,
|
|
26
|
+
sessionId: claims.sid,
|
|
27
|
+
orgId: claims.org_id ?? null,
|
|
28
|
+
orgRole: claims.org_role ?? null,
|
|
29
|
+
claims,
|
|
30
|
+
isSignedIn: true,
|
|
31
|
+
actor: claims.act && typeof claims.act.sub === 'string' && claims.act.sub ? { sub: claims.act.sub } : null,
|
|
32
|
+
has: (condition = {}) => (0, authz_1.hasFromClaims)(claims, condition),
|
|
33
|
+
protect: (condition = {}) => {
|
|
34
|
+
const outcome = (0, authz_1.evaluate)(claims, condition);
|
|
35
|
+
if (!outcome.allowed)
|
|
36
|
+
throw new authz_1.ForbiddenError(outcome.reason, condition);
|
|
37
|
+
return claims;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function first(v) {
|
|
42
|
+
return Array.isArray(v) ? v[0] : v;
|
|
43
|
+
}
|
|
44
|
+
/** The raw session token a request carries (bearer header first, then the `__session` cookie), or null. */
|
|
45
|
+
function sessionTokenFromHeaders(headers) {
|
|
46
|
+
const authz = first(headers.authorization ?? headers.Authorization);
|
|
47
|
+
if (authz?.startsWith('Bearer '))
|
|
48
|
+
return authz.slice(7) || null;
|
|
49
|
+
const cookie = first(headers.cookie ?? headers.Cookie);
|
|
50
|
+
if (cookie) {
|
|
51
|
+
for (const part of cookie.split(';')) {
|
|
52
|
+
const [key, ...rest] = part.trim().split('=');
|
|
53
|
+
if (key === '__session')
|
|
54
|
+
return rest.join('=') || null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/** Verify a request's headers into a {@link RequestAuth}. Never throws. */
|
|
60
|
+
async function authenticateHeaders(backend, headers) {
|
|
61
|
+
try {
|
|
62
|
+
const token = sessionTokenFromHeaders(headers);
|
|
63
|
+
if (!token)
|
|
64
|
+
return signedOutAuth();
|
|
65
|
+
const result = await backend.verify(token);
|
|
66
|
+
return result.ok ? signedInAuth(result.claims) : signedOutAuth();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return signedOutAuth();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=request-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-auth.js","sourceRoot":"","sources":["../src/request-auth.ts"],"names":[],"mappings":"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"}
|
package/dist/uma.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Managed Access 2.0 for a RESOURCE SERVER: register what your users own, ask Atlas for permission tickets,
|
|
3
|
+
* answer a request without a token with the UMA challenge, and read the permissions of a Requesting Party Token.
|
|
4
|
+
* Atlas is the authorization server; the protection API needs a Protection API Access Token (PAT), which this
|
|
5
|
+
* client fetches and refreshes with your confidential client's credentials (client_credentials, scope uma_protection).
|
|
6
|
+
*/
|
|
7
|
+
export declare const UMA_PROTECTION_SCOPE = "uma_protection";
|
|
8
|
+
export interface UmaResourceBody {
|
|
9
|
+
name: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
uri?: string;
|
|
12
|
+
icon_uri?: string;
|
|
13
|
+
/** The scopes a requesting party can be granted on this resource. */
|
|
14
|
+
resource_scopes?: string[];
|
|
15
|
+
/** The Atlas user id of the resource owner, who can then share it from the hosted account page. */
|
|
16
|
+
owner?: string;
|
|
17
|
+
/** A resource-scoped roles resource whose role assignments decide who holds each scope. */
|
|
18
|
+
rbac_resource_id?: string;
|
|
19
|
+
/** Let a requesting party with no access ask the owner (the grant answers request_submitted until they decide). */
|
|
20
|
+
owner_approval?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface UmaResource extends UmaResourceBody {
|
|
23
|
+
_id: string;
|
|
24
|
+
}
|
|
25
|
+
export interface UmaPermission {
|
|
26
|
+
resource_id: string;
|
|
27
|
+
resource_scopes: string[];
|
|
28
|
+
exp?: number;
|
|
29
|
+
iat?: number;
|
|
30
|
+
nbf?: number;
|
|
31
|
+
}
|
|
32
|
+
/** The RPT introspection answer (UMA Federated Authorization 5.1.1): no `scope`, with `permissions`. */
|
|
33
|
+
export interface UmaIntrospection {
|
|
34
|
+
active: boolean;
|
|
35
|
+
sub?: string;
|
|
36
|
+
exp?: number;
|
|
37
|
+
iat?: number;
|
|
38
|
+
client_id?: string;
|
|
39
|
+
permissions?: UmaPermission[];
|
|
40
|
+
}
|
|
41
|
+
export declare class UmaProtectionError extends Error {
|
|
42
|
+
readonly status: number;
|
|
43
|
+
readonly code: string;
|
|
44
|
+
constructor(status: number, code: string, message: string);
|
|
45
|
+
}
|
|
46
|
+
export interface UmaProtectionOptions {
|
|
47
|
+
/** Your Atlas sign-in origin, e.g. `https://auth.example.com`. */
|
|
48
|
+
issuer: string;
|
|
49
|
+
clientId: string;
|
|
50
|
+
clientSecret: string;
|
|
51
|
+
/** For tests or a proxy; defaults to the global fetch. */
|
|
52
|
+
fetch?: typeof fetch;
|
|
53
|
+
}
|
|
54
|
+
export declare function createUmaProtection(options: UmaProtectionOptions): {
|
|
55
|
+
/** A Protection API Access Token (cached until shortly before it expires). */
|
|
56
|
+
getPat: () => Promise<string>;
|
|
57
|
+
resources: {
|
|
58
|
+
create: (body: UmaResourceBody) => Promise<{
|
|
59
|
+
_id: string;
|
|
60
|
+
}>;
|
|
61
|
+
list: () => Promise<string[]>;
|
|
62
|
+
get: (id: string) => Promise<UmaResource>;
|
|
63
|
+
update: (id: string, body: UmaResourceBody) => Promise<UmaResource>;
|
|
64
|
+
remove: (id: string) => Promise<void>;
|
|
65
|
+
};
|
|
66
|
+
/** Ask for a permission ticket covering one or more resources and scopes. */
|
|
67
|
+
requestTicket(permissions: Array<{
|
|
68
|
+
resource_id: string;
|
|
69
|
+
resource_scopes?: string[];
|
|
70
|
+
}>): Promise<string>;
|
|
71
|
+
/** The `WWW-Authenticate` value for a 401 that hands the client a ticket. */
|
|
72
|
+
challenge(ticket: string, realm?: string): string;
|
|
73
|
+
/** Introspect a Requesting Party Token with the PAT. */
|
|
74
|
+
introspect(rpt: string): Promise<UmaIntrospection>;
|
|
75
|
+
};
|
|
76
|
+
/** Whether an introspected RPT grants `scope` on `resourceId` (and has not expired). */
|
|
77
|
+
export declare function umaAllows(introspection: UmaIntrospection, resourceId: string, scope: string, nowSeconds?: number): boolean;
|
package/dist/uma.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* User-Managed Access 2.0 for a RESOURCE SERVER: register what your users own, ask Atlas for permission tickets,
|
|
4
|
+
* answer a request without a token with the UMA challenge, and read the permissions of a Requesting Party Token.
|
|
5
|
+
* Atlas is the authorization server; the protection API needs a Protection API Access Token (PAT), which this
|
|
6
|
+
* client fetches and refreshes with your confidential client's credentials (client_credentials, scope uma_protection).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.UmaProtectionError = exports.UMA_PROTECTION_SCOPE = void 0;
|
|
10
|
+
exports.createUmaProtection = createUmaProtection;
|
|
11
|
+
exports.umaAllows = umaAllows;
|
|
12
|
+
exports.UMA_PROTECTION_SCOPE = 'uma_protection';
|
|
13
|
+
class UmaProtectionError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
constructor(status, code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.name = 'UmaProtectionError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.UmaProtectionError = UmaProtectionError;
|
|
24
|
+
function createUmaProtection(options) {
|
|
25
|
+
const issuer = options.issuer.replace(/\/+$/, '');
|
|
26
|
+
const doFetch = options.fetch ?? fetch;
|
|
27
|
+
let pat;
|
|
28
|
+
const getPat = async () => {
|
|
29
|
+
if (pat && pat.expiresAt - 30_000 > Date.now())
|
|
30
|
+
return pat.token;
|
|
31
|
+
const res = await doFetch(`${issuer}/oauth2/token`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded', authorization: `Basic ${btoa(`${encodeURIComponent(options.clientId)}:${encodeURIComponent(options.clientSecret)}`)}` },
|
|
34
|
+
body: new URLSearchParams({ grant_type: 'client_credentials', scope: exports.UMA_PROTECTION_SCOPE }).toString(),
|
|
35
|
+
});
|
|
36
|
+
const body = (await res.json().catch(() => ({})));
|
|
37
|
+
if (!res.ok || !body.access_token)
|
|
38
|
+
throw new UmaProtectionError(res.status, body.error ?? 'invalid_client', body.error_description ?? 'Could not obtain a Protection API Access Token.');
|
|
39
|
+
pat = { token: body.access_token, expiresAt: Date.now() + (body.expires_in ?? 3600) * 1000 };
|
|
40
|
+
return pat.token;
|
|
41
|
+
};
|
|
42
|
+
const call = async (method, path, body) => {
|
|
43
|
+
const res = await doFetch(`${issuer}${path}`, {
|
|
44
|
+
method,
|
|
45
|
+
headers: { authorization: `Bearer ${await getPat()}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
|
|
46
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
47
|
+
});
|
|
48
|
+
if (res.status === 204)
|
|
49
|
+
return undefined;
|
|
50
|
+
const data = (await res.json().catch(() => ({})));
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
if (res.status === 401)
|
|
53
|
+
pat = undefined;
|
|
54
|
+
throw new UmaProtectionError(res.status, data.error ?? data.errors?.[0]?.code ?? 'error', data.message ?? data.errors?.[0]?.message ?? 'The protection API refused the request.');
|
|
55
|
+
}
|
|
56
|
+
return data;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
/** A Protection API Access Token (cached until shortly before it expires). */
|
|
60
|
+
getPat,
|
|
61
|
+
resources: {
|
|
62
|
+
create: (body) => call('POST', '/oauth2/uma/resource_set', body),
|
|
63
|
+
list: () => call('GET', '/oauth2/uma/resource_set'),
|
|
64
|
+
get: (id) => call('GET', `/oauth2/uma/resource_set/${encodeURIComponent(id)}`),
|
|
65
|
+
update: (id, body) => call('PUT', `/oauth2/uma/resource_set/${encodeURIComponent(id)}`, body),
|
|
66
|
+
remove: (id) => call('DELETE', `/oauth2/uma/resource_set/${encodeURIComponent(id)}`),
|
|
67
|
+
},
|
|
68
|
+
/** Ask for a permission ticket covering one or more resources and scopes. */
|
|
69
|
+
async requestTicket(permissions) {
|
|
70
|
+
return (await call('POST', '/oauth2/uma/permission', permissions)).ticket;
|
|
71
|
+
},
|
|
72
|
+
/** The `WWW-Authenticate` value for a 401 that hands the client a ticket. */
|
|
73
|
+
challenge(ticket, realm = 'uma') {
|
|
74
|
+
return `UMA realm="${realm}", as_uri="${issuer}", ticket="${ticket}"`;
|
|
75
|
+
},
|
|
76
|
+
/** Introspect a Requesting Party Token with the PAT. */
|
|
77
|
+
async introspect(rpt) {
|
|
78
|
+
const res = await doFetch(`${issuer}/oauth2/introspect`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { authorization: `Bearer ${await getPat()}`, 'content-type': 'application/x-www-form-urlencoded' },
|
|
81
|
+
body: new URLSearchParams({ token: rpt }).toString(),
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok)
|
|
84
|
+
throw new UmaProtectionError(res.status, 'introspection_failed', 'Introspection was refused.');
|
|
85
|
+
return (await res.json());
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Whether an introspected RPT grants `scope` on `resourceId` (and has not expired). */
|
|
90
|
+
function umaAllows(introspection, resourceId, scope, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
91
|
+
if (!introspection.active)
|
|
92
|
+
return false;
|
|
93
|
+
return (introspection.permissions ?? []).some((p) => p.resource_id === resourceId && p.resource_scopes.includes(scope) && (p.exp ?? introspection.exp ?? Infinity) > nowSeconds && (p.nbf ?? 0) <= nowSeconds);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=uma.js.map
|
package/dist/uma.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uma.js","sourceRoot":"","sources":["../src/uma.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface SessionClaims {
|
|
|
69
69
|
app_roles?: string[];
|
|
70
70
|
/** Permissions granted by the app roles; gate with `has({ appPermission })`. Absent when none. */
|
|
71
71
|
app_permissions?: string[];
|
|
72
|
+
/** USR-03: slugs of the instance groups the user belongs to (and their ancestors), when any. */
|
|
73
|
+
groups?: string[];
|
|
72
74
|
/** §billing entitlement — active plan slug; gate with `has({ plan })`. */
|
|
73
75
|
pla?: string;
|
|
74
76
|
/** §billing entitlement — granted feature keys; gate with `has({ feature })`. */
|