@agiledigital/pingone-aic-script-tester 0.1.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/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +274 -0
- package/dist/bin/fetch-jar.d.ts +2 -0
- package/dist/bin/fetch-jar.js +13 -0
- package/dist/bin/pull-profile.d.ts +2 -0
- package/dist/bin/pull-profile.js +40 -0
- package/dist/bin/show-log.d.ts +2 -0
- package/dist/bin/show-log.js +31 -0
- package/dist/classes/.complete +12 -0
- package/dist/classes/AmClassShutter.class +0 -0
- package/dist/classes/AmContextFactory$ObservedContext.class +0 -0
- package/dist/classes/AmContextFactory.class +0 -0
- package/dist/classes/AmScopes.class +0 -0
- package/dist/classes/HostOps.class +0 -0
- package/dist/classes/JsValues.class +0 -0
- package/dist/classes/Json$Parser.class +0 -0
- package/dist/classes/Json.class +0 -0
- package/dist/classes/LanguageVersions.class +0 -0
- package/dist/classes/Runner.class +0 -0
- package/dist/classes/ScriptContextScope.class +0 -0
- package/dist/src/aic/callbacks.d.ts +35 -0
- package/dist/src/aic/callbacks.js +130 -0
- package/dist/src/aic/conform.d.ts +90 -0
- package/dist/src/aic/conform.js +193 -0
- package/dist/src/aic/constants.d.ts +13 -0
- package/dist/src/aic/constants.js +13 -0
- package/dist/src/aic/diff.d.ts +25 -0
- package/dist/src/aic/diff.js +201 -0
- package/dist/src/aic/emit-journey.d.ts +64 -0
- package/dist/src/aic/emit-journey.js +254 -0
- package/dist/src/aic/emit-result.d.ts +10 -0
- package/dist/src/aic/emit-result.js +39 -0
- package/dist/src/aic/emit-session.d.ts +26 -0
- package/dist/src/aic/emit-session.js +132 -0
- package/dist/src/aic/emit-subject.d.ts +40 -0
- package/dist/src/aic/emit-subject.js +210 -0
- package/dist/src/aic/file-lease.d.ts +35 -0
- package/dist/src/aic/file-lease.js +543 -0
- package/dist/src/aic/http.d.ts +21 -0
- package/dist/src/aic/http.js +127 -0
- package/dist/src/aic/idm.d.ts +10 -0
- package/dist/src/aic/idm.js +112 -0
- package/dist/src/aic/index.d.ts +35 -0
- package/dist/src/aic/index.js +21 -0
- package/dist/src/aic/js.d.ts +8 -0
- package/dist/src/aic/js.js +11 -0
- package/dist/src/aic/lease-identity.d.ts +38 -0
- package/dist/src/aic/lease-identity.js +72 -0
- package/dist/src/aic/lease-lock.d.ts +45 -0
- package/dist/src/aic/lease-lock.js +173 -0
- package/dist/src/aic/managed.d.ts +28 -0
- package/dist/src/aic/managed.js +199 -0
- package/dist/src/aic/provider.d.ts +156 -0
- package/dist/src/aic/provider.js +416 -0
- package/dist/src/aic/record.d.ts +28 -0
- package/dist/src/aic/record.js +183 -0
- package/dist/src/aic/resource-snapshot.d.ts +12 -0
- package/dist/src/aic/resource-snapshot.js +116 -0
- package/dist/src/aic/run.d.ts +102 -0
- package/dist/src/aic/run.js +450 -0
- package/dist/src/aic/tenant.d.ts +107 -0
- package/dist/src/aic/tenant.js +307 -0
- package/dist/src/aic/trace.d.ts +25 -0
- package/dist/src/aic/trace.js +56 -0
- package/dist/src/aic/txid.d.ts +11 -0
- package/dist/src/aic/txid.js +25 -0
- package/dist/src/aic/unsupported.d.ts +15 -0
- package/dist/src/aic/unsupported.js +60 -0
- package/dist/src/bindings/allowlist.d.ts +42 -0
- package/dist/src/bindings/allowlist.js +62 -0
- package/dist/src/bindings/harvest.d.ts +7 -0
- package/dist/src/bindings/harvest.js +137 -0
- package/dist/src/bindings/index.d.ts +4 -0
- package/dist/src/bindings/index.js +3 -0
- package/dist/src/bindings/preamble.d.ts +23 -0
- package/dist/src/bindings/preamble.js +36 -0
- package/dist/src/bindings/run.d.ts +33 -0
- package/dist/src/bindings/run.js +55 -0
- package/dist/src/case/equal.d.ts +2 -0
- package/dist/src/case/equal.js +40 -0
- package/dist/src/case/index.d.ts +5 -0
- package/dist/src/case/index.js +4 -0
- package/dist/src/case/portable.d.ts +10 -0
- package/dist/src/case/portable.js +28 -0
- package/dist/src/case/session.d.ts +34 -0
- package/dist/src/case/session.js +66 -0
- package/dist/src/case/state.d.ts +4 -0
- package/dist/src/case/state.js +47 -0
- package/dist/src/case/types.d.ts +266 -0
- package/dist/src/case/types.js +106 -0
- package/dist/src/case/util.d.ts +7 -0
- package/dist/src/case/util.js +115 -0
- package/dist/src/case/validate.d.ts +8 -0
- package/dist/src/case/validate.js +463 -0
- package/dist/src/case/verdict.d.ts +6 -0
- package/dist/src/case/verdict.js +810 -0
- package/dist/src/diagnostics.d.ts +7 -0
- package/dist/src/diagnostics.js +5 -0
- package/dist/src/emit-dts.d.ts +2 -0
- package/dist/src/emit-dts.js +82 -0
- package/dist/src/emit-js.d.ts +3 -0
- package/dist/src/emit-js.js +149 -0
- package/dist/src/harness/failures.d.ts +26 -0
- package/dist/src/harness/failures.js +113 -0
- package/dist/src/harness/idm.d.ts +12 -0
- package/dist/src/harness/idm.js +44 -0
- package/dist/src/harness/index.d.ts +11 -0
- package/dist/src/harness/index.js +6 -0
- package/dist/src/harness/lease.d.ts +136 -0
- package/dist/src/harness/lease.js +286 -0
- package/dist/src/harness/residue.d.ts +23 -0
- package/dist/src/harness/residue.js +73 -0
- package/dist/src/harness/show-log.d.ts +43 -0
- package/dist/src/harness/show-log.js +253 -0
- package/dist/src/harness/spec.d.ts +67 -0
- package/dist/src/harness/spec.js +189 -0
- package/dist/src/harness/step.d.ts +80 -0
- package/dist/src/harness/step.js +97 -0
- package/dist/src/harness/types.d.ts +100 -0
- package/dist/src/harness/types.js +1 -0
- package/dist/src/harness/vitest.d.ts +52 -0
- package/dist/src/harness/vitest.js +155 -0
- package/dist/src/idents.d.ts +4 -0
- package/dist/src/idents.js +42 -0
- package/dist/src/jvm.d.ts +124 -0
- package/dist/src/jvm.js +432 -0
- package/dist/src/load.d.ts +2 -0
- package/dist/src/load.js +104 -0
- package/dist/src/paths.d.ts +36 -0
- package/dist/src/paths.js +50 -0
- package/dist/src/profile/index.d.ts +5 -0
- package/dist/src/profile/index.js +3 -0
- package/dist/src/profile/normalise.d.ts +16 -0
- package/dist/src/profile/normalise.js +106 -0
- package/dist/src/profile/pull.d.ts +22 -0
- package/dist/src/profile/pull.js +36 -0
- package/dist/src/profile/seed.d.ts +11 -0
- package/dist/src/profile/seed.js +28 -0
- package/dist/src/profile/store.d.ts +13 -0
- package/dist/src/profile/store.js +60 -0
- package/dist/src/profile/types.d.ts +45 -0
- package/dist/src/profile/types.js +1 -0
- package/dist/src/profile/validate.d.ts +38 -0
- package/dist/src/profile/validate.js +129 -0
- package/dist/src/project.d.ts +37 -0
- package/dist/src/project.js +93 -0
- package/dist/src/protocol.d.ts +62 -0
- package/dist/src/protocol.js +68 -0
- package/dist/src/provider-module.d.ts +11 -0
- package/dist/src/provider-module.js +46 -0
- package/dist/src/runner.d.ts +39 -0
- package/dist/src/runner.js +337 -0
- package/dist/src/schema.d.ts +48 -0
- package/dist/src/schema.js +93 -0
- package/generated/scripted-decision-mocks.cjs +722 -0
- package/generated/scripted-decision-mocks.d.ts +626 -0
- package/generated/scripted-decision-next.json +3640 -0
- package/java/AmClassShutter.java +98 -0
- package/java/AmContextFactory.java +69 -0
- package/java/AmScopes.java +49 -0
- package/java/HostOps.java +180 -0
- package/java/JsValues.java +39 -0
- package/java/Json.java +441 -0
- package/java/LanguageVersions.java +55 -0
- package/java/Runner.java +289 -0
- package/java/ScriptContextScope.java +147 -0
- package/package.json +88 -0
- package/src/bindings/rhino/runtime.cjs +3021 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { deepEqual } from "../case/equal.js";
|
|
6
|
+
import { isPlainObject } from "../case/util.js";
|
|
7
|
+
import { AicLaneError, amRequest } from "./tenant.js";
|
|
8
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
|
+
/**
|
|
10
|
+
* Fields the maintainer measured as accepted on create but omitted by GET.
|
|
11
|
+
* Additions need their own live evidence; skipping every absent field would
|
|
12
|
+
* let a silently dropped readable value pass the seed check.
|
|
13
|
+
*/
|
|
14
|
+
const READBACK_EXEMPT_FIELDS = {
|
|
15
|
+
"managed/alpha_user": ["password"],
|
|
16
|
+
};
|
|
17
|
+
/** Whether the first pass's managed seed came exactly from this fixture ledger. */
|
|
18
|
+
export function managedSeedMatches(managed, fixtures) {
|
|
19
|
+
const seeded = {};
|
|
20
|
+
for (const fixture of fixtures) {
|
|
21
|
+
const rows = seeded[fixture.type] ?? [];
|
|
22
|
+
rows.push(fixture.record);
|
|
23
|
+
seeded[fixture.type] = rows;
|
|
24
|
+
}
|
|
25
|
+
return deepEqual(managed ?? {}, seeded);
|
|
26
|
+
}
|
|
27
|
+
/** Create with CREST's create-only request, then prove every declared field landed. */
|
|
28
|
+
export async function seedManagedFixtures(io, session, fixtures, seeded) {
|
|
29
|
+
for (const fixture of fixtures) {
|
|
30
|
+
const identity = fixtureIdentity(fixture);
|
|
31
|
+
const response = await amRequest(io, session, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
path: `${identity.collection}?_action=create`,
|
|
34
|
+
body: JSON.stringify(fixture.record),
|
|
35
|
+
});
|
|
36
|
+
if (response.status === 412) {
|
|
37
|
+
throw new AicLaneError(`managed fixture collision at ${identity.resource}: _id ${JSON.stringify(identity.id)} already exists; it may be a leaked fixture or tenant-owned record`);
|
|
38
|
+
}
|
|
39
|
+
if (response.status !== 201) {
|
|
40
|
+
throw new AicLaneError(`create managed fixture ${identity.resource} returned HTTP ${response.status}: ${response.bodyText}`);
|
|
41
|
+
}
|
|
42
|
+
seeded.push({ type: fixture.type, id: identity.id });
|
|
43
|
+
await checkSeededManaged(io, session, fixture, identity.resource);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Delete one fixture the harness successfully created. A prior script delete is clean. */
|
|
47
|
+
export async function deleteManagedFixture(io, session, fixture) {
|
|
48
|
+
const resource = managedResource(fixture.type, fixture.id);
|
|
49
|
+
const response = await amRequest(io, session, {
|
|
50
|
+
method: "DELETE",
|
|
51
|
+
path: resource,
|
|
52
|
+
});
|
|
53
|
+
if (response.status === 404 || (response.status >= 200 && response.status < 300)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
throw new AicLaneError(`delete managed fixture ${resource} returned HTTP ${response.status}: ${response.bodyText}`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Serialize managed-fixture runs across worker processes for one tenant.
|
|
60
|
+
* A collision after this lock is acquired is therefore pre-existing state,
|
|
61
|
+
* not two files in this harness racing each other.
|
|
62
|
+
*/
|
|
63
|
+
export async function acquireManagedFixtureLock(session, label) {
|
|
64
|
+
const tenantKey = createHash("sha256")
|
|
65
|
+
.update(session.baseUrl)
|
|
66
|
+
.digest("hex")
|
|
67
|
+
.slice(0, 20);
|
|
68
|
+
const lockPath = join(tmpdir(), `rhino-local-managed-${tenantKey}.lock`);
|
|
69
|
+
const ownerPath = join(lockPath, "owner.json");
|
|
70
|
+
const deadline = Date.now() + 120_000;
|
|
71
|
+
for (;;) {
|
|
72
|
+
let acquired = false;
|
|
73
|
+
try {
|
|
74
|
+
await mkdir(lockPath, { mode: 0o700 });
|
|
75
|
+
acquired = true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!hasCode(error, "EEXIST")) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (acquired) {
|
|
83
|
+
try {
|
|
84
|
+
await writeFile(ownerPath, JSON.stringify({ pid: process.pid, label }), { mode: 0o600 });
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
await removeLock(lockPath, ownerPath);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
return async () => removeLock(lockPath, ownerPath);
|
|
91
|
+
}
|
|
92
|
+
const owner = await readOwner(ownerPath);
|
|
93
|
+
if (owner !== undefined && !processExists(owner.pid)) {
|
|
94
|
+
await removeLock(lockPath, ownerPath);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (owner === undefined && (await lockIsStale(lockPath))) {
|
|
98
|
+
await removeLock(lockPath, ownerPath);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (Date.now() >= deadline) {
|
|
102
|
+
throw new AicLaneError(`timed out waiting for managed-fixture tenant lock${owner === undefined ? "" : ` held by ${JSON.stringify(owner.label)}`}`);
|
|
103
|
+
}
|
|
104
|
+
await delay(100);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function checkSeededManaged(io, session, fixture, resource) {
|
|
108
|
+
const response = await amRequest(io, session, { method: "GET", path: resource });
|
|
109
|
+
if (response.status < 200 || response.status >= 300 || !isPlainObject(response.body)) {
|
|
110
|
+
throw new AicLaneError(`managed fixture ${resource} was not readable after create (HTTP ${response.status})`);
|
|
111
|
+
}
|
|
112
|
+
for (const [key, expected] of Object.entries(fixture.record)) {
|
|
113
|
+
const present = Object.prototype.hasOwnProperty.call(response.body, key);
|
|
114
|
+
if (!present &&
|
|
115
|
+
READBACK_EXEMPT_FIELDS[fixture.type]?.includes(key) === true) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!present || !deepEqual(response.body[key], expected)) {
|
|
119
|
+
throw new AicLaneError(`managed fixture ${resource} did not contain the declared field ${JSON.stringify(key)} after create`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function fixtureIdentity(fixture) {
|
|
124
|
+
const match = /^managed\/([^/]+)$/.exec(fixture.type);
|
|
125
|
+
if (match === null || match[1] === undefined) {
|
|
126
|
+
throw new AicLaneError(`managed fixture type ${JSON.stringify(fixture.type)} must be managed/<type>`);
|
|
127
|
+
}
|
|
128
|
+
const id = fixture.record._id;
|
|
129
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
130
|
+
throw new AicLaneError(`managed fixture ${fixture.type} needs a non-empty string _id`);
|
|
131
|
+
}
|
|
132
|
+
if (fixture.type === "managed/alpha_user" && !UUID_PATTERN.test(id)) {
|
|
133
|
+
throw new AicLaneError(`managed/alpha_user fixture _id ${JSON.stringify(id)} must be a 36-character UUID because user _id is the fr-idm-uuid RDN; managed/alpha_role accepts readable ids instead`);
|
|
134
|
+
}
|
|
135
|
+
const collection = `/openidm/managed/${encodeURIComponent(match[1])}`;
|
|
136
|
+
return { collection, id, resource: `${collection}/${encodeURIComponent(id)}` };
|
|
137
|
+
}
|
|
138
|
+
export function managedResource(type, id) {
|
|
139
|
+
const match = /^managed\/([^/]+)$/.exec(type);
|
|
140
|
+
if (match === null || match[1] === undefined) {
|
|
141
|
+
throw new AicLaneError(`managed fixture type ${JSON.stringify(type)} must be managed/<type>`);
|
|
142
|
+
}
|
|
143
|
+
return `/openidm/managed/${encodeURIComponent(match[1])}/${encodeURIComponent(id)}`;
|
|
144
|
+
}
|
|
145
|
+
async function readOwner(path) {
|
|
146
|
+
try {
|
|
147
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
148
|
+
return typeof value.pid === "number" && typeof value.label === "string"
|
|
149
|
+
? { pid: value.pid, label: value.label }
|
|
150
|
+
: undefined;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function processExists(pid) {
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return hasCode(error, "EPERM");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function lockIsStale(path) {
|
|
166
|
+
try {
|
|
167
|
+
return Date.now() - (await stat(path)).mtimeMs > 5_000;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function removeLock(lockPath, ownerPath) {
|
|
174
|
+
try {
|
|
175
|
+
await unlink(ownerPath);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (!hasCode(error, "ENOENT")) {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
await rmdir(lockPath);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!hasCode(error, "ENOENT")) {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function hasCode(error, code) {
|
|
192
|
+
return (typeof error === "object" &&
|
|
193
|
+
error !== null &&
|
|
194
|
+
"code" in error &&
|
|
195
|
+
error.code === code);
|
|
196
|
+
}
|
|
197
|
+
function delay(ms) {
|
|
198
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
199
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createPrivateKey, type JsonWebKey } from "node:crypto";
|
|
2
|
+
import { type HttpRequest, type HttpResponse } from "./http.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Where the tenant lane gets its tenant and its bearer.
|
|
5
|
+
*
|
|
6
|
+
* This is the one seam between the harness and whatever holds the
|
|
7
|
+
* credentials. In this repo that is the `aic` agent ({@link aicCliProvider});
|
|
8
|
+
* outside it, a service account ({@link serviceAccountProvider}) or the
|
|
9
|
+
* caller's own token source ({@link tokenCallbackProvider}). Everything past
|
|
10
|
+
* this seam — journey upload, `/authenticate`, IDM seeding, teardown — is the
|
|
11
|
+
* same code whichever provider supplied the bearer.
|
|
12
|
+
*/
|
|
13
|
+
export interface TenantProvider {
|
|
14
|
+
/** Where the tenant is. Called once per connection, before any token. */
|
|
15
|
+
describe(): Promise<TenantDescription>;
|
|
16
|
+
/**
|
|
17
|
+
* A bearer for AM and IDM calls. Called with `reason: "initial"` when a
|
|
18
|
+
* connection opens, and again with `reason: "rejected"` after a
|
|
19
|
+
* non-anonymous call came back 401 — so a provider that caches must not
|
|
20
|
+
* hand back the token that was just refused.
|
|
21
|
+
*/
|
|
22
|
+
getToken(request: TokenRequest): Promise<string>;
|
|
23
|
+
/** Present when this provider can read the tenant's logs (`show-log`). */
|
|
24
|
+
readonly logs?: LogReader;
|
|
25
|
+
}
|
|
26
|
+
export interface TenantDescription {
|
|
27
|
+
/** Recorded in failure dumps and traces; `show-log` matches on it. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** `https://<tenant>` — no trailing slash, no path. */
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TokenRequest {
|
|
33
|
+
reason: "initial" | "rejected";
|
|
34
|
+
/** The bearer the tenant refused, when `reason` is `"rejected"`. */
|
|
35
|
+
rejected?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface LogReader {
|
|
38
|
+
/**
|
|
39
|
+
* Every event for one transaction id, polling until AM's trailing
|
|
40
|
+
* `AM-ACCESS-OUTCOME` event arrives or `waitMs` elapses — logs lag the
|
|
41
|
+
* request by tens of seconds. Whatever has arrived is returned on timeout.
|
|
42
|
+
*/
|
|
43
|
+
transaction(id: string, options?: {
|
|
44
|
+
waitMs?: number;
|
|
45
|
+
}): Promise<unknown[]>;
|
|
46
|
+
}
|
|
47
|
+
/** Console-issued log API key pair (`docs/api/08-logs.md`). */
|
|
48
|
+
export interface LogKeys {
|
|
49
|
+
id: string;
|
|
50
|
+
secret: string;
|
|
51
|
+
}
|
|
52
|
+
export type HttpSend = (req: HttpRequest) => Promise<HttpResponse>;
|
|
53
|
+
export declare class TenantProviderError extends Error {
|
|
54
|
+
constructor(message: string, options?: {
|
|
55
|
+
cause?: unknown;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export interface TokenCallbackOptions {
|
|
59
|
+
baseUrl: string;
|
|
60
|
+
/** Default: the base URL's hostname. */
|
|
61
|
+
name?: string;
|
|
62
|
+
getToken: (request: TokenRequest) => Promise<string> | string;
|
|
63
|
+
logKeys?: LogKeys;
|
|
64
|
+
http?: HttpSend;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The caller supplies the bearer. The harness calls `getToken` when a
|
|
68
|
+
* connection opens and again after a 401, and does no caching of its own.
|
|
69
|
+
*/
|
|
70
|
+
export declare function tokenCallbackProvider(options: TokenCallbackOptions): TenantProvider;
|
|
71
|
+
/** `docs/api/00-auth.md`; a returned scope may be a re-ordered subset. */
|
|
72
|
+
export declare const DEFAULT_SERVICE_ACCOUNT_SCOPE = "fr:am:* fr:idm:* fr:idc:esv:*";
|
|
73
|
+
export interface ServiceAccountOptions {
|
|
74
|
+
baseUrl: string;
|
|
75
|
+
/** The service account's UUID — `iss` and `sub` of the assertion. */
|
|
76
|
+
serviceAccountId: string;
|
|
77
|
+
/** The RSA private key the console issued, as a JWK object or JSON text. */
|
|
78
|
+
jwk: JsonWebKey | string;
|
|
79
|
+
scope?: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
logKeys?: LogKeys;
|
|
82
|
+
http?: HttpSend;
|
|
83
|
+
/** Seconds since the epoch; injected by tests. */
|
|
84
|
+
now?: () => number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Mint bearers with the JWT-bearer grant, as the `aic` agent does: an RS256
|
|
88
|
+
* assertion (`iss` = `sub` = the account id, `aud` = the token endpoint, a
|
|
89
|
+
* short `exp`, a fresh `jti`) posted to the root realm's
|
|
90
|
+
* `/am/oauth2/access_token` as `client_id=service-account`.
|
|
91
|
+
*
|
|
92
|
+
* The token is cached until {@link REFRESH_MARGIN_S} before its
|
|
93
|
+
* `expires_in`; a `"rejected"` request always mints a new one.
|
|
94
|
+
*/
|
|
95
|
+
export declare function serviceAccountProvider(options: ServiceAccountOptions): TenantProvider;
|
|
96
|
+
/** Compact JWS, RS256. Exported for tests that verify it with the public key. */
|
|
97
|
+
export declare function signAssertion(key: ReturnType<typeof createPrivateKey>, claims: Record<string, unknown>): string;
|
|
98
|
+
/** What `aic logs tx` reads by default (`src/logs/cli.rs`). */
|
|
99
|
+
export declare const DEFAULT_LOG_SOURCES: readonly ["am-everything", "idm-everything"];
|
|
100
|
+
export interface LogReaderOptions {
|
|
101
|
+
/** Injected by tests; default `setTimeout`. */
|
|
102
|
+
sleep?: (ms: number) => Promise<void>;
|
|
103
|
+
/** Milliseconds since the epoch; injected by tests. */
|
|
104
|
+
now?: () => number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* `GET /monitoring/logs?source=…&transactionId=…` with the `x-api-key` /
|
|
108
|
+
* `x-api-secret` pair, following `pagedResultsCookie` — the query
|
|
109
|
+
* `aic logs tx` makes, with its request spacing and its `Retry-After`-honouring
|
|
110
|
+
* 429 backoff. The log API rejects bearers (`docs/api/08-logs.md`), which is
|
|
111
|
+
* why this needs its own keys.
|
|
112
|
+
*
|
|
113
|
+
* `transactionId` is a **prefix** match (`docs/api/08-logs.md`), as it is for
|
|
114
|
+
* `aic logs tx`: asking for a chain's stem returns every pass under it, which
|
|
115
|
+
* is what `show-log`'s "whole chain" choice relies on.
|
|
116
|
+
*/
|
|
117
|
+
export declare function logKeysReader(baseUrl: string, keys: LogKeys, http: HttpSend, options?: LogReaderOptions): LogReader;
|
|
118
|
+
/** The variables {@link providerFromEnv} reads. */
|
|
119
|
+
export declare const TENANT_ENV: {
|
|
120
|
+
readonly url: "AIC_SCRIPT_TESTER_TENANT_URL";
|
|
121
|
+
readonly name: "AIC_SCRIPT_TESTER_TENANT_NAME";
|
|
122
|
+
readonly serviceAccountId: "AIC_SCRIPT_TESTER_SA_ID";
|
|
123
|
+
readonly jwk: "AIC_SCRIPT_TESTER_SA_JWK";
|
|
124
|
+
readonly jwkFile: "AIC_SCRIPT_TESTER_SA_JWK_FILE";
|
|
125
|
+
readonly scope: "AIC_SCRIPT_TESTER_SA_SCOPE";
|
|
126
|
+
readonly logKeyId: "AIC_SCRIPT_TESTER_LOG_KEY_ID";
|
|
127
|
+
readonly logKeySecret: "AIC_SCRIPT_TESTER_LOG_KEY_SECRET";
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* A service-account provider from the environment, or `undefined` when
|
|
131
|
+
* `AIC_SCRIPT_TESTER_TENANT_URL` is unset. A partial configuration throws rather
|
|
132
|
+
* than falling back, so a typo cannot quietly send the run to another tenant.
|
|
133
|
+
*/
|
|
134
|
+
export declare function providerFromEnv(env?: NodeJS.ProcessEnv): TenantProvider | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Set the provider every tenant-lane connection in this process uses, e.g.
|
|
137
|
+
* from a Vitest `setupFiles` module. Pass `undefined` to clear it.
|
|
138
|
+
*/
|
|
139
|
+
export declare function setTenantProvider(provider: TenantProvider | undefined): void;
|
|
140
|
+
/** The registered provider, else one from the environment, else `undefined`. */
|
|
141
|
+
export declare function configuredTenantProvider(env?: NodeJS.ProcessEnv): TenantProvider | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Re-check what a provider says about its tenant. Every built-in provider
|
|
144
|
+
* validates its URL when it is made, but a caller's own `TenantProvider` is
|
|
145
|
+
* only a promise, and `connectTenant` sends a bearer to whatever this returns.
|
|
146
|
+
*/
|
|
147
|
+
export declare function checkTenantDescription(description: TenantDescription): TenantDescription;
|
|
148
|
+
/**
|
|
149
|
+
* The OAuth `error` / `error_description` of a refused token request, with the
|
|
150
|
+
* tenant's origin and hostname replaced. The description is what names the
|
|
151
|
+
* problem (a bad `aud` quotes the URL it expected), but the message ends up in
|
|
152
|
+
* test output and CI logs, where the tenant's hostname does not belong.
|
|
153
|
+
*/
|
|
154
|
+
export declare function tokenErrorDetail(body: string, baseUrl: string): string;
|
|
155
|
+
/** `text` with the tenant's origin and hostname replaced by `<tenant>`. */
|
|
156
|
+
export declare function scrubTenant(text: string, baseUrl: string): string;
|