@shelbyb/daywalker-sdk 0.2.0 → 0.4.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/README.md +38 -24
- package/dist/analytics/index.d.ts +26 -3
- package/dist/analytics/index.js +107 -23
- package/dist/cli.js +136 -29
- package/dist/client.d.ts +44 -3
- package/dist/client.js +372 -73
- package/dist/config.d.ts +14 -2
- package/dist/config.js +36 -0
- package/dist/crypto/encoding.d.ts +5 -0
- package/dist/crypto/encoding.js +14 -0
- package/dist/crypto/index.d.ts +3 -3
- package/dist/crypto/index.js +2 -2
- package/dist/crypto/keys.d.ts +3 -3
- package/dist/crypto/keys.js +28 -7
- package/dist/crypto/signatures.d.ts +54 -11
- package/dist/crypto/signatures.js +310 -94
- package/dist/express.d.ts +1 -0
- package/dist/express.js +8 -6
- package/dist/flags/client.d.ts +34 -5
- package/dist/flags/client.js +93 -22
- package/dist/flags/common/events.d.ts +9 -0
- package/dist/flags/common/events.js +77 -6
- package/dist/flags/common/index.d.ts +3 -3
- package/dist/flags/common/index.js +3 -3
- package/dist/flags/common/sse.d.ts +24 -3
- package/dist/flags/common/sse.js +85 -15
- package/dist/flags/common/version.d.ts +2 -0
- package/dist/flags/common/version.js +2 -0
- package/dist/flags/engine/context.js +4 -3
- package/dist/flags/engine/index.d.ts +1 -1
- package/dist/flags/engine/index.js +1 -1
- package/dist/flags/engine/operators.d.ts +25 -0
- package/dist/flags/engine/operators.js +64 -8
- package/dist/flags/node.d.ts +27 -15
- package/dist/flags/node.js +54 -7
- package/dist/flags/react.d.ts +6 -4
- package/dist/flags/react.js +47 -17
- package/dist/index.d.ts +7 -6
- package/dist/index.js +4 -4
- package/dist/middleware.d.ts +18 -0
- package/dist/middleware.js +28 -7
- package/dist/next.d.ts +63 -6
- package/dist/next.js +182 -36
- package/dist/openfeature/server.d.ts +3 -0
- package/dist/openfeature/server.js +14 -2
- package/dist/openfeature/shared.d.ts +1 -1
- package/dist/openfeature/shared.js +22 -2
- package/dist/openfeature/web.js +9 -3
- package/dist/types.d.ts +58 -3
- package/dist/types.js +6 -0
- package/docs/analytics.md +1 -1
- package/docs/flags-node.md +1 -1
- package/docs/flags.md +2 -1
- package/package.json +34 -16
- package/assets/logo-512.png +0 -0
package/dist/cli.js
CHANGED
|
@@ -7,20 +7,27 @@ import { generateSigningKey, loadSigningKey, registerPublicKey, redactSecrets }
|
|
|
7
7
|
const HELP = `daywalker: connect a service to Daywalker Auth (RFC 9421 / Push Architecture)
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
|
-
daywalker init --service <slug> [--api-key
|
|
11
|
-
daywalker rotate [--service <slug>] [--api-key <
|
|
10
|
+
daywalker init --service <slug> [--api-key-stdin] [--domain <host>] [--auth-url <url>] [--force] [--skip-register] [--json|--export] [--print-key]
|
|
11
|
+
daywalker rotate [--service <slug>] [--api-key-stdin] [--domain <host>] [--auth-url <url>] [--skip-register] [--json|--export] [--print-key]
|
|
12
12
|
daywalker check
|
|
13
13
|
|
|
14
14
|
init Generates an Ed25519 keypair, registers the public key directly with Daywalker Auth
|
|
15
15
|
(POST /api/v1/keys/register), and writes DAYWALKER_* settings to .env.local (or outputs JSON with --json).
|
|
16
|
-
|
|
16
|
+
Nothing is written if registration fails. --force replaces an existing key (kept as
|
|
17
|
+
DAYWALKER_SIGNING_KEY_PREVIOUS for rollback). No public ingress or /.well-known route handlers are required.
|
|
17
18
|
rotate Generates a new Ed25519 key, registers it with Daywalker Auth, and keeps the old one
|
|
18
19
|
in .env.local as DAYWALKER_SIGNING_KEY_PREVIOUS during deployment transition.
|
|
20
|
+
Nothing is written, and the exit code is non-zero, unless registration succeeds.
|
|
19
21
|
check Validates configuration and confirms Daywalker Auth connection and signature readiness.
|
|
20
22
|
|
|
21
23
|
Options:
|
|
24
|
+
--api-key-stdin Read the Daywalker API key from stdin (e.g. \`op read ... | daywalker init --api-key-stdin\`).
|
|
25
|
+
The key is otherwise taken from DAYWALKER_API_KEY in the environment or .env.local.
|
|
26
|
+
--api-key <key> also works but exposes the key in shell history and process listings.
|
|
27
|
+
--skip-register Do not register the public key (alias: --no-register); register it yourself before deploying.
|
|
22
28
|
--json, --export Output configuration as JSON for piping into Vercel, DigitalOcean, Fly.io, AWS SSM, etc.
|
|
23
|
-
|
|
29
|
+
The JSON includes the private key (DAYWALKER_SIGNING_KEY); nothing is written to .env.local.
|
|
30
|
+
--print-key Print the private key to stdout after writing .env.local (disabled by default for security).
|
|
24
31
|
`;
|
|
25
32
|
function args(argv) {
|
|
26
33
|
const out = {};
|
|
@@ -42,6 +49,35 @@ function fail(message) {
|
|
|
42
49
|
console.error(redactSecrets(`✖ ${message}`));
|
|
43
50
|
process.exit(1);
|
|
44
51
|
}
|
|
52
|
+
/** Read all of stdin (for --api-key-stdin), so the key never appears in argv. */
|
|
53
|
+
async function readStdin() {
|
|
54
|
+
if (process.stdin.isTTY)
|
|
55
|
+
fail("--api-key-stdin expects the key on stdin (pipe it in)");
|
|
56
|
+
const chunks = [];
|
|
57
|
+
for await (const chunk of process.stdin)
|
|
58
|
+
chunks.push(Buffer.from(chunk));
|
|
59
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
60
|
+
}
|
|
61
|
+
async function resolveApiKey(opts, existing) {
|
|
62
|
+
if (typeof opts["api-key"] === "string")
|
|
63
|
+
return opts["api-key"].trim() || null;
|
|
64
|
+
if (opts["api-key-stdin"]) {
|
|
65
|
+
const fromStdin = await readStdin();
|
|
66
|
+
if (!fromStdin)
|
|
67
|
+
fail("--api-key-stdin: no API key received on stdin");
|
|
68
|
+
return fromStdin;
|
|
69
|
+
}
|
|
70
|
+
return String(existing.get("DAYWALKER_API_KEY") ?? process.env.DAYWALKER_API_KEY ?? "").trim() || null;
|
|
71
|
+
}
|
|
72
|
+
function skipRegister(opts) {
|
|
73
|
+
return Boolean(opts["skip-register"] || opts["no-register"]);
|
|
74
|
+
}
|
|
75
|
+
/** --json prints the private key by design (for secret stores); say so on stderr so it is never a surprise. */
|
|
76
|
+
function warnJsonIncludesKey(values) {
|
|
77
|
+
if (values.DAYWALKER_SIGNING_KEY) {
|
|
78
|
+
console.warn("! --json output includes the private signing key (DAYWALKER_SIGNING_KEY); pipe it straight into your secret store.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
45
81
|
function readEnvFile(file) {
|
|
46
82
|
const map = new Map();
|
|
47
83
|
if (!fs.existsSync(file))
|
|
@@ -53,8 +89,35 @@ function readEnvFile(file) {
|
|
|
53
89
|
}
|
|
54
90
|
return map;
|
|
55
91
|
}
|
|
56
|
-
|
|
57
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Values are written as KEY="value". Anything a dotenv parser could interpret (quotes, `$` expansion
|
|
94
|
+
* as done by Next.js / dotenv-expand, backslash escapes, newlines or other control characters) is
|
|
95
|
+
* rejected instead of escaped, since parsers disagree on escaping rules.
|
|
96
|
+
*/
|
|
97
|
+
const UNSAFE_ENV_VALUE = /["'`$\\\u0000-\u001f\u007f]/;
|
|
98
|
+
function assertSafeEnvValues(values) {
|
|
99
|
+
for (const [key, value] of Object.entries(values)) {
|
|
100
|
+
if (value !== null && UNSAFE_ENV_VALUE.test(value)) {
|
|
101
|
+
fail(`${key} contains quotes, "$", backslashes, newlines or control characters; refusing to write it to .env.local`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function writeEnvFile(file, values, comments = {}) {
|
|
106
|
+
assertSafeEnvValues(values);
|
|
107
|
+
let stat = null;
|
|
108
|
+
try {
|
|
109
|
+
stat = fs.lstatSync(file);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err.code !== "ENOENT")
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
// Never follow a symlink (it could point anywhere, e.g. a world-readable file); refuse non-files.
|
|
116
|
+
if (stat && stat.isSymbolicLink())
|
|
117
|
+
fail(`${path.basename(file)} is a symlink; refusing to write secrets through it`);
|
|
118
|
+
if (stat && !stat.isFile())
|
|
119
|
+
fail(`${path.basename(file)} is not a regular file`);
|
|
120
|
+
const lines = stat ? fs.readFileSync(file, "utf8").split("\n") : [];
|
|
58
121
|
const seen = new Set();
|
|
59
122
|
const out = [];
|
|
60
123
|
for (const line of lines) {
|
|
@@ -73,10 +136,38 @@ function writeEnvFile(file, values) {
|
|
|
73
136
|
if (added.length) {
|
|
74
137
|
if (out.length)
|
|
75
138
|
out.push("");
|
|
76
|
-
out.push("# Daywalker Auth (managed by `daywalker init`)"
|
|
139
|
+
out.push("# Daywalker Auth (managed by `daywalker init`)");
|
|
140
|
+
for (const [k, v] of added) {
|
|
141
|
+
if (comments[k])
|
|
142
|
+
out.push(`# ${comments[k]}`);
|
|
143
|
+
out.push(`${k}="${v}"`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Atomic replace: write a fresh 0600 temp file in the same directory, then rename it over the
|
|
147
|
+
// target, so the secrets never sit in a file with looser permissions and readers never see a
|
|
148
|
+
// half-written file.
|
|
149
|
+
const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
|
|
150
|
+
const fd = fs.openSync(tmp, "wx", 0o600);
|
|
151
|
+
try {
|
|
152
|
+
fs.fchmodSync(fd, 0o600);
|
|
153
|
+
fs.writeFileSync(fd, out.join("\n") + "\n");
|
|
154
|
+
fs.fsyncSync(fd);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
fs.closeSync(fd);
|
|
158
|
+
fs.rmSync(tmp, { force: true });
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
fs.closeSync(fd);
|
|
162
|
+
try {
|
|
163
|
+
fs.renameSync(tmp, file);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
fs.rmSync(tmp, { force: true });
|
|
167
|
+
throw err;
|
|
77
168
|
}
|
|
78
|
-
fs.writeFileSync(file, out.join("\n") + "\n", { mode: 0o600 });
|
|
79
169
|
}
|
|
170
|
+
const PREVIOUS_KEY_COMMENT = "Previous signing key, kept for rollback while the new key rolls out; remove it once deployed.";
|
|
80
171
|
function ensureGitignored(dir, entry) {
|
|
81
172
|
const file = path.join(dir, ".gitignore");
|
|
82
173
|
const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
@@ -102,15 +193,22 @@ async function init(opts) {
|
|
|
102
193
|
if (!domain)
|
|
103
194
|
fail(`--domain (${rawDomain}) is not a valid public hostname`);
|
|
104
195
|
}
|
|
105
|
-
const apiKey =
|
|
196
|
+
const apiKey = await resolveApiKey(opts, existing);
|
|
106
197
|
const authUrl = String(opts["auth-url"] ?? existing.get("DAYWALKER_AUTH_URL") ?? process.env.DAYWALKER_AUTH_URL ?? "https://auth.daywalker.dev").replace(/\/+$/, "");
|
|
107
|
-
const
|
|
198
|
+
const existingKey = existing.get("DAYWALKER_SIGNING_KEY") || null;
|
|
199
|
+
const keyExists = Boolean(existingKey) && !opts.force;
|
|
108
200
|
const key = keyExists ? null : await generateSigningKey();
|
|
109
|
-
|
|
201
|
+
// --force over an existing key: keep the old one so a deployment can roll back to it.
|
|
202
|
+
const previousKey = key && existingKey ? existingKey : null;
|
|
203
|
+
const activeKey = key ?? (existingKey ? await loadSigningKey(existingKey) : null);
|
|
110
204
|
const kid = activeKey?.publicJwk.kid ?? String(opts["key-id"] ?? "");
|
|
111
|
-
// Register public key directly with Daywalker Auth if API key is provided
|
|
205
|
+
// Register public key directly with Daywalker Auth if API key is provided. Replacing a key
|
|
206
|
+
// requires registration: writing an unregistered key would break request signing.
|
|
112
207
|
let registered = false;
|
|
113
|
-
if (
|
|
208
|
+
if (previousKey && !apiKey && !skipRegister(opts)) {
|
|
209
|
+
fail("Replacing DAYWALKER_SIGNING_KEY requires registering the new key: set DAYWALKER_API_KEY (or use --api-key-stdin), or pass --skip-register to write it anyway");
|
|
210
|
+
}
|
|
211
|
+
if (apiKey && activeKey && !skipRegister(opts)) {
|
|
114
212
|
if (!isJson)
|
|
115
213
|
console.log(`• Registering public key (${activeKey.publicJwk.kid}) with Daywalker Auth...`);
|
|
116
214
|
const regResult = await registerPublicKey({
|
|
@@ -126,13 +224,11 @@ async function init(opts) {
|
|
|
126
224
|
console.log(`✔ Public key registered with Daywalker Auth (Push Architecture)`);
|
|
127
225
|
}
|
|
128
226
|
else {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.warn(` You can register the key later by re-running init with your DAYWALKER_API_KEY.`);
|
|
132
|
-
}
|
|
227
|
+
fail(`Key registration failed (${regResult.status}): ${regResult.error}\n` +
|
|
228
|
+
` Nothing was written. Fix the problem and re-run, or pass --skip-register to write the key without registering it.`);
|
|
133
229
|
}
|
|
134
230
|
}
|
|
135
|
-
else if (!apiKey && !opts
|
|
231
|
+
else if (!apiKey && !skipRegister(opts) && !isJson) {
|
|
136
232
|
console.log(`• Note: Set DAYWALKER_API_KEY in .env.local to automatically register public keys.`);
|
|
137
233
|
}
|
|
138
234
|
const signingKeyStr = key?.privateKeyPkcs8Base64 ?? existing.get("DAYWALKER_SIGNING_KEY") ?? "";
|
|
@@ -143,12 +239,14 @@ async function init(opts) {
|
|
|
143
239
|
...(domain ? { DAYWALKER_DOMAIN: domain } : {}),
|
|
144
240
|
...(apiKey ? { DAYWALKER_API_KEY: apiKey } : {}),
|
|
145
241
|
...(signingKeyStr ? { DAYWALKER_SIGNING_KEY: signingKeyStr } : {}),
|
|
242
|
+
...(previousKey ? { DAYWALKER_SIGNING_KEY_PREVIOUS: previousKey } : {}),
|
|
146
243
|
};
|
|
147
244
|
if (isJson) {
|
|
245
|
+
warnJsonIncludesKey(envValues);
|
|
148
246
|
console.log(JSON.stringify(envValues, null, 2));
|
|
149
247
|
return;
|
|
150
248
|
}
|
|
151
|
-
writeEnvFile(envFile, envValues);
|
|
249
|
+
writeEnvFile(envFile, envValues, { DAYWALKER_SIGNING_KEY_PREVIOUS: PREVIOUS_KEY_COMMENT });
|
|
152
250
|
ensureGitignored(dir, ".env.local");
|
|
153
251
|
console.log(`✔ Daywalker configured for service "${slug}"${domain ? ` (${domain})` : ""}`);
|
|
154
252
|
console.log(` • RFC 9421 / RFC 9530 HTTP Message Signing active`);
|
|
@@ -157,6 +255,8 @@ async function init(opts) {
|
|
|
157
255
|
console.log(` • Key ID (RFC 7638 thumbprint): ${kid}`);
|
|
158
256
|
if (registered)
|
|
159
257
|
console.log(` • Zero-ingress push architecture: public key registered directly`);
|
|
258
|
+
if (previousKey)
|
|
259
|
+
console.log(` • Previous key kept as DAYWALKER_SIGNING_KEY_PREVIOUS for rollback; remove it once the new key is deployed.`);
|
|
160
260
|
if (opts["print-key"] && signingKeyStr)
|
|
161
261
|
console.log(`\nDAYWALKER_SIGNING_KEY=${signingKeyStr}`);
|
|
162
262
|
}
|
|
@@ -169,11 +269,16 @@ async function rotate(opts) {
|
|
|
169
269
|
if (!current)
|
|
170
270
|
fail("No DAYWALKER_SIGNING_KEY found; run `daywalker init` first");
|
|
171
271
|
const slug = String(opts.service ?? existing.get("DAYWALKER_SERVICE_SLUG") ?? process.env.DAYWALKER_SERVICE_SLUG ?? "").trim().toLowerCase();
|
|
172
|
-
const apiKey =
|
|
272
|
+
const apiKey = await resolveApiKey(opts, existing);
|
|
173
273
|
const authUrl = String(opts["auth-url"] ?? existing.get("DAYWALKER_AUTH_URL") ?? process.env.DAYWALKER_AUTH_URL ?? "https://auth.daywalker.dev").replace(/\/+$/, "");
|
|
174
274
|
const rotateDomain = publicHostname(String(opts.domain ?? existing.get("DAYWALKER_DOMAIN") ?? process.env.DAYWALKER_DOMAIN ?? ""));
|
|
175
275
|
const key = await generateSigningKey();
|
|
176
|
-
|
|
276
|
+
// The new key only becomes active once Daywalker Auth knows it, so registration must succeed
|
|
277
|
+
// before anything is written (unless the caller explicitly registers it some other way).
|
|
278
|
+
if (!skipRegister(opts)) {
|
|
279
|
+
if (!apiKey || !slug) {
|
|
280
|
+
fail("rotate needs DAYWALKER_API_KEY and DAYWALKER_SERVICE_SLUG to register the new key (or pass --skip-register to write it unregistered)");
|
|
281
|
+
}
|
|
177
282
|
if (!isJson)
|
|
178
283
|
console.log(`• Registering new public key (${key.publicJwk.kid}) with Daywalker Auth...`);
|
|
179
284
|
const regResult = await registerPublicKey({
|
|
@@ -183,25 +288,24 @@ async function rotate(opts) {
|
|
|
183
288
|
domain: rotateDomain,
|
|
184
289
|
publicJwk: key.publicJwk,
|
|
185
290
|
});
|
|
186
|
-
if (regResult.ok) {
|
|
187
|
-
|
|
188
|
-
console.log(`✔ New public key registered with Daywalker Auth`);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
if (!isJson)
|
|
192
|
-
console.warn(redactSecrets(`! Key registration warning: ${regResult.error}`));
|
|
291
|
+
if (!regResult.ok) {
|
|
292
|
+
fail(`Key registration failed (${regResult.status}): ${regResult.error}\n Nothing was written; the current key is still active.`);
|
|
193
293
|
}
|
|
294
|
+
if (!isJson)
|
|
295
|
+
console.log(`✔ New public key registered with Daywalker Auth`);
|
|
194
296
|
}
|
|
195
297
|
const envValues = {
|
|
196
298
|
DAYWALKER_KEY_ID: key.publicJwk.kid,
|
|
197
299
|
DAYWALKER_SIGNING_KEY: key.privateKeyPkcs8Base64 ?? "",
|
|
300
|
+
// Kept for rollback: services still signing with the old key keep working during rollout.
|
|
198
301
|
DAYWALKER_SIGNING_KEY_PREVIOUS: current,
|
|
199
302
|
};
|
|
200
303
|
if (isJson) {
|
|
304
|
+
warnJsonIncludesKey(envValues);
|
|
201
305
|
console.log(JSON.stringify(envValues, null, 2));
|
|
202
306
|
return;
|
|
203
307
|
}
|
|
204
|
-
writeEnvFile(envFile, envValues);
|
|
308
|
+
writeEnvFile(envFile, envValues, { DAYWALKER_SIGNING_KEY_PREVIOUS: PREVIOUS_KEY_COMMENT });
|
|
205
309
|
console.log(`✔ New key ${key.publicJwk.kid} written; previous key preserved as DAYWALKER_SIGNING_KEY_PREVIOUS.`);
|
|
206
310
|
console.log(` After deployment rollout is complete, you may remove DAYWALKER_SIGNING_KEY_PREVIOUS.`);
|
|
207
311
|
if (opts["print-key"])
|
|
@@ -233,6 +337,9 @@ async function check() {
|
|
|
233
337
|
async function main() {
|
|
234
338
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
235
339
|
const parsedArgs = args(rest);
|
|
340
|
+
if (typeof parsedArgs["api-key"] === "string") {
|
|
341
|
+
console.warn("! --api-key exposes the key in shell history and process listings; prefer DAYWALKER_API_KEY in the environment or .env.local.");
|
|
342
|
+
}
|
|
236
343
|
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
237
344
|
console.log(HELP);
|
|
238
345
|
return;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,26 +1,57 @@
|
|
|
1
1
|
import { type ResolvedConfig } from "./config.js";
|
|
2
2
|
import { type DaywalkerConfig, type PublicJwk, type RetryOptions, type SelfCheckResult, type TokenPayload, type ValidateResult } from "./types.js";
|
|
3
|
+
/** Validation results kept in memory; the least recently used are evicted past this. */
|
|
4
|
+
export declare const MAX_VALIDATION_CACHE_ENTRIES = 1000;
|
|
5
|
+
/** Longest 429 `Retry-After` waited out by default (`retry.maxRetryAfterMs`); longer ones return the 429. */
|
|
6
|
+
export declare const MAX_RETRY_AFTER_MS = 60000;
|
|
3
7
|
export type DaywalkerFetchInit = Omit<RequestInit, "body"> & {
|
|
8
|
+
/**
|
|
9
|
+
* Plain objects and arrays are sent as JSON. Strings, Uint8Array / ArrayBuffer(View) and URLSearchParams
|
|
10
|
+
* are sent (and digested) byte for byte, and Blobs are read into memory first when signing. FormData and
|
|
11
|
+
* ReadableStream bodies can't be digested, so they throw when a signing key is configured.
|
|
12
|
+
*/
|
|
4
13
|
body?: BodyInit | object | null;
|
|
5
14
|
retry?: RetryOptions;
|
|
15
|
+
/** Per-attempt timeout overriding `config.timeoutMs`; 0 disables. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
/** Sent as `Idempotency-Key` (unless already set) and lets non-idempotent requests retry network errors. */
|
|
18
|
+
idempotencyKey?: string;
|
|
6
19
|
};
|
|
7
20
|
export declare class Daywalker {
|
|
8
21
|
private readonly _config;
|
|
9
22
|
private readonly _rawConfig;
|
|
10
23
|
private _signingKeyPromise?;
|
|
24
|
+
private _previousSigningKeyPromise?;
|
|
11
25
|
private readonly _cache;
|
|
12
26
|
private readonly _inflight;
|
|
13
27
|
constructor(config?: DaywalkerConfig);
|
|
14
28
|
get config(): ResolvedConfig;
|
|
15
29
|
toJSON(): Record<string, unknown>;
|
|
16
30
|
private getLoadedSigningKey;
|
|
31
|
+
private getLoadedPreviousSigningKey;
|
|
17
32
|
/**
|
|
18
|
-
* Signs and executes
|
|
33
|
+
* Signs and executes a request to Daywalker Auth (same origin as `authUrl` only).
|
|
34
|
+
* - Redirects are never followed: a 3xx throws a DaywalkerError (code UNEXPECTED_REDIRECT), so the API key,
|
|
35
|
+
* signature and body never reach another URL and no redirect target's answer is trusted.
|
|
36
|
+
* - Each attempt times out after `timeoutMs` (default 5000ms) of waiting for response headers; the caller's
|
|
37
|
+
* `signal` aborts immediately, including during backoff, and is never retried.
|
|
38
|
+
* - 429s are retried with exponential backoff, honoring `Retry-After` up to `retry.maxRetryAfterMs`. Network
|
|
39
|
+
* errors and timeouts are retried only for GET / HEAD / OPTIONS, with an `idempotencyKey`, or when
|
|
40
|
+
* `retry.retryNetworkErrors` is true. Options merge as defaults < `config.retry` < `init.retry`.
|
|
41
|
+
* - An explicit `Authorization` header is sent as given; otherwise `Bearer <apiKey>` is added. The signature
|
|
42
|
+
* covers method, path, query, authority and Content-Digest, never Authorization.
|
|
19
43
|
*/
|
|
20
44
|
fetch(path: string, init?: DaywalkerFetchInit): Promise<Response>;
|
|
45
|
+
private _cacheGet;
|
|
46
|
+
private _cacheSet;
|
|
21
47
|
/**
|
|
22
|
-
* Validates incoming bearer
|
|
48
|
+
* Validates an incoming bearer token with memory caching and discriminated union return.
|
|
23
49
|
* Supports zero-egress offline verification for JWT token leases when Daywalker's public key is configured.
|
|
50
|
+
* A missing token is always rejected (401); it never falls back to this service's own API key —
|
|
51
|
+
* use `validateServiceKey()` for that.
|
|
52
|
+
* Online validation fails closed: only a 2xx JSON answer with `valid: true` and a sane shape is accepted.
|
|
53
|
+
* Outages (network errors, timeouts, 5xx, malformed answers) return status 503 with error NETWORK_ERROR or
|
|
54
|
+
* SERVICE_UNAVAILABLE and are never cached. Each call gets its own copy of the (cached) result.
|
|
24
55
|
*/
|
|
25
56
|
validateToken(tokenOrOptions?: string | {
|
|
26
57
|
token?: string;
|
|
@@ -33,6 +64,14 @@ export declare class Daywalker {
|
|
|
33
64
|
offlineOnly?: boolean;
|
|
34
65
|
}): Promise<ValidateResult>;
|
|
35
66
|
private _validateOnline;
|
|
67
|
+
/**
|
|
68
|
+
* Service health check: validates this service's own configured API key (DAYWALKER_API_KEY).
|
|
69
|
+
* Never use this to authenticate an incoming request.
|
|
70
|
+
*/
|
|
71
|
+
validateServiceKey(options?: {
|
|
72
|
+
serviceSlug?: string;
|
|
73
|
+
forceRefresh?: boolean;
|
|
74
|
+
}): Promise<ValidateResult>;
|
|
36
75
|
/**
|
|
37
76
|
* Throws DaywalkerAuthError or DaywalkerRateLimitError if token is invalid or rate limited.
|
|
38
77
|
*/
|
|
@@ -55,12 +94,14 @@ export declare class Daywalker {
|
|
|
55
94
|
}>;
|
|
56
95
|
/**
|
|
57
96
|
* Fetches Daywalker's public JWKS (GET /.well-known/jwks.json) for offline lease verification.
|
|
97
|
+
* Uses the configured `fetch`, sends no credentials, never follows redirects and times out after `timeoutMs`.
|
|
58
98
|
*/
|
|
59
99
|
fetchJwks(): Promise<{
|
|
60
100
|
keys: PublicJwk[];
|
|
61
101
|
}>;
|
|
62
102
|
/**
|
|
63
|
-
* The public JWK(s) configured for this service
|
|
103
|
+
* The public JWK(s) configured for this service: the current signing key, then the previous one
|
|
104
|
+
* (`previousSigningKey`) during a rotation, so requests from instances still on the old key keep verifying.
|
|
64
105
|
*/
|
|
65
106
|
keys(): Promise<{
|
|
66
107
|
keys: PublicJwk[];
|