@shelbyb/daywalker-sdk 0.3.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 +31 -23
- package/dist/analytics/index.d.ts +16 -2
- package/dist/analytics/index.js +79 -14
- package/dist/cli.js +133 -31
- package/dist/client.d.ts +29 -2
- package/dist/client.js +314 -62
- package/dist/config.d.ts +8 -2
- package/dist/config.js +14 -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 +42 -12
- package/dist/crypto/signatures.js +236 -79
- package/dist/express.d.ts +1 -0
- package/dist/express.js +8 -6
- package/dist/flags/client.d.ts +32 -4
- package/dist/flags/client.js +74 -12
- package/dist/flags/common/events.d.ts +9 -0
- package/dist/flags/common/events.js +51 -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/index.d.ts +1 -1
- package/dist/flags/engine/index.js +1 -1
- package/dist/flags/engine/operators.d.ts +20 -3
- package/dist/flags/engine/operators.js +38 -32
- package/dist/flags/node.d.ts +26 -14
- package/dist/flags/node.js +51 -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 +52 -8
- package/dist/next.js +151 -37
- 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 +56 -4
- 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 +32 -14
- package/assets/logo-512.png +0 -0
|
@@ -8,6 +8,11 @@ export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
|
8
8
|
export declare function base64ToBytes(b64: string): Uint8Array;
|
|
9
9
|
export declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
10
10
|
export declare function base64UrlToBytes(b64url: string): Uint8Array;
|
|
11
|
+
/**
|
|
12
|
+
* Decodes unpadded base64url (RFC 7515 §2) strictly: only `[A-Za-z0-9_-]` and only the canonical encoding
|
|
13
|
+
* of the bytes, so a token or signature has exactly one accepted spelling. Throws otherwise.
|
|
14
|
+
*/
|
|
15
|
+
export declare function base64UrlToBytesStrict(b64url: string): Uint8Array;
|
|
11
16
|
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
12
17
|
export declare function hexToBytes(hex: string): Uint8Array;
|
|
13
18
|
/**
|
package/dist/crypto/encoding.js
CHANGED
|
@@ -47,6 +47,20 @@ export function base64UrlToBytes(b64url) {
|
|
|
47
47
|
}
|
|
48
48
|
return base64ToBytes(b64);
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Decodes unpadded base64url (RFC 7515 §2) strictly: only `[A-Za-z0-9_-]` and only the canonical encoding
|
|
52
|
+
* of the bytes, so a token or signature has exactly one accepted spelling. Throws otherwise.
|
|
53
|
+
*/
|
|
54
|
+
export function base64UrlToBytesStrict(b64url) {
|
|
55
|
+
if (!/^[A-Za-z0-9_-]*$/.test(b64url) || b64url.length % 4 === 1) {
|
|
56
|
+
throw new Error("Invalid base64url encoding");
|
|
57
|
+
}
|
|
58
|
+
const bytes = base64UrlToBytes(b64url);
|
|
59
|
+
if (bytesToBase64Url(bytes) !== b64url) {
|
|
60
|
+
throw new Error("Non-canonical base64url encoding");
|
|
61
|
+
}
|
|
62
|
+
return bytes;
|
|
63
|
+
}
|
|
50
64
|
export function bytesToHex(bytes) {
|
|
51
65
|
let hex = "";
|
|
52
66
|
for (let i = 0; i < bytes.length; i++) {
|
package/dist/crypto/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, bytesToHex, hexToBytes, timingSafeEqual, timingSafeEqualString, maskSecret, redactSecrets, } from "./encoding.js";
|
|
1
|
+
export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, base64UrlToBytesStrict, bytesToHex, hexToBytes, timingSafeEqual, timingSafeEqualString, maskSecret, redactSecrets, } from "./encoding.js";
|
|
2
2
|
export { DEFAULT_MAX_BODY_SIZE_BYTES, computeContentDigest, sha256Base64, sha256Base64Url, } from "./digest.js";
|
|
3
3
|
export { calculateJwkThumbprint, generateSigningKey, loadSigningKey, loadPublicKey, } from "./keys.js";
|
|
4
|
-
export { SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./signatures.js";
|
|
5
|
-
export type { SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult, } from "./signatures.js";
|
|
4
|
+
export { SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, REPLAY_CACHE_MAX_ENTRIES, SignatureReplayCache, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./signatures.js";
|
|
5
|
+
export type { SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult, ReplayStore, } from "./signatures.js";
|
package/dist/crypto/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, bytesToHex, hexToBytes, timingSafeEqual, timingSafeEqualString, maskSecret, redactSecrets, } from "./encoding.js";
|
|
1
|
+
export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, base64UrlToBytesStrict, bytesToHex, hexToBytes, timingSafeEqual, timingSafeEqualString, maskSecret, redactSecrets, } from "./encoding.js";
|
|
2
2
|
export { DEFAULT_MAX_BODY_SIZE_BYTES, computeContentDigest, sha256Base64, sha256Base64Url, } from "./digest.js";
|
|
3
3
|
export { calculateJwkThumbprint, generateSigningKey, loadSigningKey, loadPublicKey, } from "./keys.js";
|
|
4
|
-
export { SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./signatures.js";
|
|
4
|
+
export { SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, REPLAY_CACHE_MAX_ENTRIES, SignatureReplayCache, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./signatures.js";
|
package/dist/crypto/keys.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PublicJwk, SigningKey } from "../types.js";
|
|
1
|
+
import type { PublicJwk, PublicJwks, SigningKey } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Calculate the RFC 7638 JWK thumbprint for an Ed25519 public key.
|
|
4
4
|
*/
|
|
@@ -17,9 +17,9 @@ export declare function generateSigningKey(): Promise<SigningKey>;
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function loadSigningKey(raw: string | SigningKey): Promise<SigningKey>;
|
|
19
19
|
/**
|
|
20
|
-
* Load an Ed25519 public key from JWK, PEM, Base64 SPKI, or Base64url raw public key.
|
|
20
|
+
* Load an Ed25519 public key from JWK, JWKS (selected by `kid`), PEM, Base64 SPKI, or Base64url raw public key.
|
|
21
21
|
*/
|
|
22
|
-
export declare function loadPublicKey(raw: string | PublicJwk | CryptoKey): Promise<{
|
|
22
|
+
export declare function loadPublicKey(raw: string | PublicJwk | PublicJwks | CryptoKey, kid?: string): Promise<{
|
|
23
23
|
publicKey: CryptoKey;
|
|
24
24
|
publicJwk: PublicJwk;
|
|
25
25
|
}>;
|
package/dist/crypto/keys.js
CHANGED
|
@@ -131,9 +131,27 @@ export async function loadSigningKey(raw) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
134
|
+
* Picks the Ed25519 key with this `kid` from a JWKS, or its only Ed25519 key (the signature check still
|
|
135
|
+
* has to pass against it). Several keys and no matching `kid` is an error rather than a guess.
|
|
135
136
|
*/
|
|
136
|
-
|
|
137
|
+
function selectJwk(jwks, kid) {
|
|
138
|
+
const keys = (Array.isArray(jwks.keys) ? jwks.keys : []).filter((k) => !!k && typeof k === "object" && k.kty === "OKP" && k.crv === "Ed25519");
|
|
139
|
+
const byKid = kid ? keys.find((k) => k.kid === kid) : undefined;
|
|
140
|
+
if (byKid)
|
|
141
|
+
return byKid;
|
|
142
|
+
if (keys.length === 1)
|
|
143
|
+
return keys[0];
|
|
144
|
+
if (keys.length === 0)
|
|
145
|
+
throw new Error("JWKS contains no Ed25519 keys");
|
|
146
|
+
throw new Error(kid ? `No key in JWKS matches kid "${kid}"` : "JWKS has several keys and the signature names no kid");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Load an Ed25519 public key from JWK, JWKS (selected by `kid`), PEM, Base64 SPKI, or Base64url raw public key.
|
|
150
|
+
*/
|
|
151
|
+
export async function loadPublicKey(raw, kid) {
|
|
152
|
+
if (typeof raw === "object" && raw && "keys" in raw) {
|
|
153
|
+
return loadPublicKey(selectJwk(raw, kid));
|
|
154
|
+
}
|
|
137
155
|
if (typeof raw === "object" && "type" in raw && raw.type === "public") {
|
|
138
156
|
const exported = (await globalThis.crypto.subtle.exportKey("jwk", raw));
|
|
139
157
|
const kid = exported.kid || (await calculateJwkThumbprint({ crv: "Ed25519", kty: "OKP", x: exported.x }));
|
|
@@ -170,16 +188,19 @@ export async function loadPublicKey(raw) {
|
|
|
170
188
|
const trimmed = raw.trim();
|
|
171
189
|
// Try parsing as JSON JWK or JWKS
|
|
172
190
|
if (trimmed.startsWith("{")) {
|
|
191
|
+
let parsed;
|
|
173
192
|
try {
|
|
174
|
-
|
|
175
|
-
const jwkCandidate = parsed.keys ? parsed.keys[0] : parsed;
|
|
176
|
-
if (jwkCandidate && jwkCandidate.kty === "OKP" && jwkCandidate.crv === "Ed25519") {
|
|
177
|
-
return loadPublicKey(jwkCandidate);
|
|
178
|
-
}
|
|
193
|
+
parsed = JSON.parse(trimmed);
|
|
179
194
|
}
|
|
180
195
|
catch {
|
|
181
196
|
// Continue
|
|
182
197
|
}
|
|
198
|
+
if (parsed && typeof parsed === "object") {
|
|
199
|
+
const jwkCandidate = parsed.keys ? selectJwk(parsed, kid) : parsed;
|
|
200
|
+
if (jwkCandidate.kty === "OKP" && jwkCandidate.crv === "Ed25519") {
|
|
201
|
+
return loadPublicKey(jwkCandidate);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
183
204
|
}
|
|
184
205
|
// Handle PEM / Base64 / Base64url
|
|
185
206
|
let cleaned = trimmed
|
|
@@ -1,38 +1,57 @@
|
|
|
1
|
-
import type { PublicJwk, SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult } from "../types.js";
|
|
1
|
+
import type { PublicJwk, PublicJwks, ReplayStore, SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult } from "../types.js";
|
|
2
2
|
export declare const SIGNATURE_VERSION = "rfc9421";
|
|
3
3
|
export declare const SIGNATURE_LABEL = "sig1";
|
|
4
4
|
export declare const SIGNATURE_ALG = "ed25519";
|
|
5
5
|
export declare const MAX_CLOCK_SKEW_SECONDS = 300;
|
|
6
6
|
export declare const SAFE_IDENTIFIER_PATTERN: RegExp;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Splits a request target into its RFC 9421 "@path" value (§2.2.6; dot segments resolved and repeated
|
|
9
|
+
* slashes collapsed per RFC 3986, to prevent path traversal & signature confusion) and its "@query" value
|
|
10
|
+
* (§2.2.7; the query with its leading "?", or "?" alone when there is none). Fragments are dropped.
|
|
11
|
+
*/
|
|
12
|
+
export declare function splitRequestTarget(rawTarget: string): {
|
|
13
|
+
path: string;
|
|
14
|
+
query: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes an HTTP request path per RFC 3986 into the "@path" component. Path only: the query string is
|
|
18
|
+
* a separate "@query" component (see `splitRequestTarget`).
|
|
9
19
|
*/
|
|
10
20
|
export declare function normalizePath(rawPath: string): string;
|
|
11
21
|
/**
|
|
12
22
|
* Validates that an identifier (keyId, serviceSlug, etc.) contains only safe alphanumeric/hyphen characters.
|
|
13
23
|
*/
|
|
14
24
|
export declare function assertSafeIdentifier(name: string, value: string): void;
|
|
15
|
-
/**
|
|
16
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Upper bound on remembered signatures. Unexpired entries are never evicted: once the cache is full of them,
|
|
27
|
+
* new signatures are rejected (fail closed) until entries expire. Entries live 2 x maxClockSkewSeconds
|
|
28
|
+
* (600s by default), so the default sustains ~160 verified requests/s per process.
|
|
29
|
+
*/
|
|
30
|
+
export declare const REPLAY_CACHE_MAX_ENTRIES = 100000;
|
|
17
31
|
/**
|
|
18
32
|
* In-memory replay cache, keyed on keyid + decoded signature bytes (so re-encodings of the
|
|
19
|
-
* same signature collide).
|
|
33
|
+
* same signature collide). Per process only: multi-instance deployments need a shared `ReplayStore`.
|
|
20
34
|
*/
|
|
21
|
-
export declare class SignatureReplayCache {
|
|
35
|
+
export declare class SignatureReplayCache implements ReplayStore {
|
|
22
36
|
private readonly maxEntries;
|
|
23
37
|
private seen;
|
|
24
38
|
constructor(maxEntries?: number);
|
|
25
39
|
get size(): number;
|
|
26
40
|
/** True when the key was recorded and has not expired yet (a replay). */
|
|
27
41
|
has(key: string, now?: number): boolean;
|
|
28
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Records `key` until `now + ttlMs`. Returns false, recording nothing, when the key is already present
|
|
44
|
+
* or the cache is full of unexpired entries (a still-valid signature is never forgotten early).
|
|
45
|
+
*/
|
|
46
|
+
record(key: string, ttlMs: number, now?: number): boolean;
|
|
29
47
|
clear(): void;
|
|
30
48
|
}
|
|
31
49
|
export declare const globalReplayCache: SignatureReplayCache;
|
|
32
|
-
export type { SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult, };
|
|
50
|
+
export type { ReplayStore, SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult, };
|
|
33
51
|
/**
|
|
34
52
|
* Format the RFC 9421 Signature-Input parameters string.
|
|
35
53
|
* Example: `("@method" "@path" "@authority" "content-digest");created=1791561600;keyid="...";alg="ed25519";nonce="..."`
|
|
54
|
+
* Every value is validated, so none can close its quoted string and inject parameters.
|
|
36
55
|
*/
|
|
37
56
|
export declare function formatSignatureParams(params: {
|
|
38
57
|
components?: string[];
|
|
@@ -42,7 +61,10 @@ export declare function formatSignatureParams(params: {
|
|
|
42
61
|
nonce?: string;
|
|
43
62
|
}): string;
|
|
44
63
|
/**
|
|
45
|
-
* Construct canonical RFC 9421 signature base string.
|
|
64
|
+
* Construct canonical RFC 9421 signature base string from the covered components, in order. They are
|
|
65
|
+
* `components` when given, else the list declared in `signatureParams`. "@path" and "@query" come from
|
|
66
|
+
* `path` (which may carry a query string); "content-digest" from `contentDigest`; any other header field
|
|
67
|
+
* from `additionalComponents`. Throws when a covered component has no value.
|
|
46
68
|
*/
|
|
47
69
|
export declare function createSignatureBase(params: {
|
|
48
70
|
method: string;
|
|
@@ -51,13 +73,19 @@ export declare function createSignatureBase(params: {
|
|
|
51
73
|
contentDigest?: string | null;
|
|
52
74
|
signatureParams: string;
|
|
53
75
|
additionalComponents?: Record<string, string>;
|
|
76
|
+
components?: string[];
|
|
54
77
|
}): string;
|
|
55
78
|
/**
|
|
56
79
|
* Sign an outgoing HTTP request according to RFC 9421 + RFC 9530.
|
|
80
|
+
* Covers "@method", "@path", "@query" (only when `path` has a query string), "@authority" and
|
|
81
|
+
* "content-digest" (whenever there is a body, and always for POST / PUT / PATCH).
|
|
57
82
|
*/
|
|
58
83
|
export declare function signRequest(params: SignRequestParams): Promise<SignedHeaders>;
|
|
59
84
|
/**
|
|
60
|
-
* Verify an incoming RFC 9421 HTTP Message Signature.
|
|
85
|
+
* Verify an incoming RFC 9421 HTTP Message Signature. The signature base is built from the components the
|
|
86
|
+
* signature declares, which must include "@method", "@path" and "@authority", plus "@query" when the request
|
|
87
|
+
* has a query string and "content-digest" when it has a body or is a POST / PUT / PATCH.
|
|
88
|
+
* Replay protection uses `replayStore` (default: per-process memory; use a shared store across instances).
|
|
61
89
|
*/
|
|
62
90
|
export declare function verifyRequestSignature(params: VerifySignatureParams): Promise<VerifySignatureResult>;
|
|
63
91
|
/** Clock skew tolerated on JWT lease `exp` / `nbf`. */
|
|
@@ -65,11 +93,13 @@ export declare const JWT_CLOCK_SKEW_SECONDS = 60;
|
|
|
65
93
|
/**
|
|
66
94
|
* Zero-egress offline verification for asymmetric JWT token leases.
|
|
67
95
|
* Requires a numeric `exp` and an audience (`aud` or `serviceSlug`) equal to `expectedServiceSlug`;
|
|
68
|
-
* a verifier without an expected slug rejects every lease. `iss` is checked when `expectedIssuer` is set
|
|
96
|
+
* a verifier without an expected slug rejects every lease. `iss` is checked when `expectedIssuer` is set
|
|
97
|
+
* (Daywalker documents no fixed issuer, so there is no default). Segments must be canonical unpadded
|
|
98
|
+
* base64url, a `crit` header is rejected, and a JWKS `publicKey` is searched by the header's `kid`.
|
|
69
99
|
*/
|
|
70
100
|
export declare function verifyJwtLease(params: {
|
|
71
101
|
token: string;
|
|
72
|
-
publicKey: string | PublicJwk | CryptoKey;
|
|
102
|
+
publicKey: string | PublicJwk | PublicJwks | CryptoKey;
|
|
73
103
|
expectedServiceSlug?: string;
|
|
74
104
|
expectedIssuer?: string;
|
|
75
105
|
currentTime?: number;
|