@hardenlabs/hmac 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.
Files changed (45) hide show
  1. package/dist/canonical.d.ts +26 -0
  2. package/dist/canonical.d.ts.map +1 -0
  3. package/dist/canonical.js +87 -0
  4. package/dist/canonical.js.map +1 -0
  5. package/dist/client-factory.d.ts +23 -0
  6. package/dist/client-factory.d.ts.map +1 -0
  7. package/dist/client-factory.js +53 -0
  8. package/dist/client-factory.js.map +1 -0
  9. package/dist/config.d.ts +75 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +85 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/env-loader.d.ts +21 -0
  14. package/dist/env-loader.d.ts.map +1 -0
  15. package/dist/env-loader.js +145 -0
  16. package/dist/env-loader.js.map +1 -0
  17. package/dist/errors.d.ts +8 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +10 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/http-client.d.ts +61 -0
  22. package/dist/http-client.d.ts.map +1 -0
  23. package/dist/http-client.js +51 -0
  24. package/dist/http-client.js.map +1 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +11 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/middleware/express.d.ts +24 -0
  30. package/dist/middleware/express.d.ts.map +1 -0
  31. package/dist/middleware/express.js +145 -0
  32. package/dist/middleware/express.js.map +1 -0
  33. package/dist/middleware/fetch.d.ts +16 -0
  34. package/dist/middleware/fetch.d.ts.map +1 -0
  35. package/dist/middleware/fetch.js +105 -0
  36. package/dist/middleware/fetch.js.map +1 -0
  37. package/dist/signing.d.ts +18 -0
  38. package/dist/signing.d.ts.map +1 -0
  39. package/dist/signing.js +62 -0
  40. package/dist/signing.js.map +1 -0
  41. package/dist/validation.d.ts +21 -0
  42. package/dist/validation.d.ts.map +1 -0
  43. package/dist/validation.js +47 -0
  44. package/dist/validation.js.map +1 -0
  45. package/package.json +51 -0
@@ -0,0 +1,26 @@
1
+ import { type SignedHeadersConfig } from "./config.js";
2
+ /** Parameters for building a canonical string. */
3
+ export interface CanonicalStringParams {
4
+ method: string;
5
+ path: string;
6
+ body: string;
7
+ timestamp: number;
8
+ signedHeadersConfig?: SignedHeadersConfig;
9
+ requestHeaders?: Record<string, string>;
10
+ }
11
+ /**
12
+ * Build a canonical string from request components per the v1.0 specification.
13
+ *
14
+ * Format: METHOD\nPATH\nSIGNED_HEADERS\nBODY\nTIMESTAMP
15
+ */
16
+ export declare function buildCanonicalString(params: CanonicalStringParams): string;
17
+ /** Result of building signed headers. */
18
+ export interface SignedHeadersResult {
19
+ headerString: string;
20
+ headerNames: string[];
21
+ }
22
+ /**
23
+ * Build the signed headers string and return header names.
24
+ */
25
+ export declare function buildSignedHeaders(config: SignedHeadersConfig, requestHeaders: Record<string, string>): SignedHeadersResult;
26
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAErB,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAkC1E;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,mBAAmB,CAOrB"}
@@ -0,0 +1,87 @@
1
+ import { CLIENT_ID_HEADER_LOWER, HARDEN_HEADER_PREFIX, X_HEADER_PREFIX, } from "./config.js";
2
+ /**
3
+ * Build a canonical string from request components per the v1.0 specification.
4
+ *
5
+ * Format: METHOD\nPATH\nSIGNED_HEADERS\nBODY\nTIMESTAMP
6
+ */
7
+ export function buildCanonicalString(params) {
8
+ const { method, path, body, timestamp, signedHeadersConfig, requestHeaders, } = params;
9
+ if (method.includes("\n")) {
10
+ throw new Error("method must not contain newline characters");
11
+ }
12
+ if (path.includes("\n")) {
13
+ throw new Error("path must not contain newline characters");
14
+ }
15
+ const config = signedHeadersConfig ?? {
16
+ includeAuthorization: false,
17
+ includeXHeaders: false,
18
+ additionalHeaders: [],
19
+ excludeHeaders: [],
20
+ };
21
+ const headers = requestHeaders ?? {};
22
+ const signedHeadersString = buildSignedHeadersString(config, headers);
23
+ return [
24
+ method.toUpperCase(),
25
+ path,
26
+ signedHeadersString,
27
+ body ?? "",
28
+ String(timestamp),
29
+ ].join("\n");
30
+ }
31
+ /**
32
+ * Build the signed headers string and return header names.
33
+ */
34
+ export function buildSignedHeaders(config, requestHeaders) {
35
+ const selected = selectHeaders(config, requestHeaders);
36
+ const headerNames = selected.map((h) => h.name);
37
+ const headerString = selected
38
+ .map((h) => `${h.name}:${h.value}`)
39
+ .join("\n");
40
+ return { headerString, headerNames };
41
+ }
42
+ function buildSignedHeadersString(config, requestHeaders) {
43
+ const selected = selectHeaders(config, requestHeaders);
44
+ return selected.map((h) => `${h.name}:${h.value}`).join("\n");
45
+ }
46
+ function selectHeaders(config, requestHeaders) {
47
+ const excludeSet = new Set(config.excludeHeaders.map((h) => h.toLowerCase()));
48
+ const additionalSet = new Set(config.additionalHeaders.map((h) => h.toLowerCase()));
49
+ const selected = [];
50
+ for (const [name, value] of Object.entries(requestHeaders)) {
51
+ const lowerName = name.toLowerCase();
52
+ const trimmedValue = value.trim();
53
+ // Always exclude X-Harden-* headers, EXCEPT X-Harden-Client-Id
54
+ // (client identity is an identity claim, not signing metadata)
55
+ if (lowerName.startsWith(HARDEN_HEADER_PREFIX) && lowerName !== CLIENT_ID_HEADER_LOWER) {
56
+ continue;
57
+ }
58
+ let include = false;
59
+ if (config.includeAuthorization && lowerName === "authorization") {
60
+ include = true;
61
+ }
62
+ if (config.includeXHeaders &&
63
+ lowerName.startsWith(X_HEADER_PREFIX) &&
64
+ (!lowerName.startsWith(HARDEN_HEADER_PREFIX) || lowerName === CLIENT_ID_HEADER_LOWER)) {
65
+ include = true;
66
+ }
67
+ // X-Harden-Client-Id is always signed when present (identity claim must not be spoofable)
68
+ if (lowerName === CLIENT_ID_HEADER_LOWER) {
69
+ include = true;
70
+ }
71
+ if (additionalSet.has(lowerName)) {
72
+ include = true;
73
+ }
74
+ // Apply exclude override
75
+ if (excludeSet.has(lowerName)) {
76
+ include = false;
77
+ }
78
+ if (include) {
79
+ selected.push({ name: lowerName, value: trimmedValue });
80
+ }
81
+ }
82
+ // Sort by Unicode code point order (ordinal), matching C# StringComparison.Ordinal
83
+ // and Python default string comparison. Do NOT use localeCompare — it varies by runtime locale.
84
+ selected.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
85
+ return selected;
86
+ }
87
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { HmacConfig } from "./config.js";
2
+ import type { HmacClient, HmacClientFactoryOptions } from "./http-client.js";
3
+ /** Factory for creating pre-configured HMAC-signing HTTP clients per named target. */
4
+ export interface HmacClientFactory {
5
+ /**
6
+ * Create an HTTP client for the named target.
7
+ * The client prepends the target's base URL and signs all requests.
8
+ *
9
+ * @param targetName - The target name as defined in config.targets.
10
+ * @returns An HmacClient that auto-signs requests.
11
+ * @throws {Error} If the target name is not found.
12
+ */
13
+ createClient(targetName: string): HmacClient;
14
+ }
15
+ /**
16
+ * Create an HmacClientFactory from the given config.
17
+ *
18
+ * @param config - HMAC configuration with targets.
19
+ * @param options - Optional adapter configuration (fetch function or axios instance).
20
+ * @returns Factory that creates per-target signed HTTP clients.
21
+ */
22
+ export declare function createHmacClientFactory(config: HmacConfig, options?: HmacClientFactoryOptions): HmacClientFactory;
23
+ //# sourceMappingURL=client-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-factory.d.ts","sourceRoot":"","sources":["../src/client-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EACV,UAAU,EAIV,wBAAwB,EACzB,MAAM,kBAAkB,CAAC;AAI1B,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,wBAAwB,GACjC,iBAAiB,CAgEnB"}
@@ -0,0 +1,53 @@
1
+ import { CLIENT_ID_HEADER, configForTarget } from "./config.js";
2
+ import { FetchAdapter, AxiosAdapter } from "./http-client.js";
3
+ import { signRequestHeaders } from "./middleware/fetch.js";
4
+ /**
5
+ * Create an HmacClientFactory from the given config.
6
+ *
7
+ * @param config - HMAC configuration with targets.
8
+ * @param options - Optional adapter configuration (fetch function or axios instance).
9
+ * @returns Factory that creates per-target signed HTTP clients.
10
+ */
11
+ export function createHmacClientFactory(config, options) {
12
+ const adapter = options?.axios
13
+ ? new AxiosAdapter(options.axios)
14
+ : new FetchAdapter(options?.fetchFn);
15
+ return {
16
+ createClient(targetName) {
17
+ const target = config.targets?.[targetName];
18
+ if (!target) {
19
+ const available = config.targets
20
+ ? Object.keys(config.targets).join(", ")
21
+ : "";
22
+ throw new Error(`Target '${targetName}' is not configured. Available targets: [${available}].`);
23
+ }
24
+ const targetConfig = configForTarget(config, targetName);
25
+ const baseUrl = target.baseUrl.replace(/\/+$/, "");
26
+ function signedRequest(method, path, body, opts) {
27
+ const existingHeaders = {
28
+ [CLIENT_ID_HEADER.toLowerCase()]: targetName,
29
+ ...Object.fromEntries(Object.entries(opts?.headers ?? {}).map(([k, v]) => [
30
+ k.toLowerCase(),
31
+ v,
32
+ ])),
33
+ };
34
+ const sigHeaders = signRequestHeaders(targetConfig, method, path, body ?? "", existingHeaders);
35
+ const mergedHeaders = {
36
+ ...existingHeaders,
37
+ ...sigHeaders,
38
+ };
39
+ const fullUrl = `${baseUrl}${path}`;
40
+ return adapter.request(fullUrl, method, body, mergedHeaders);
41
+ }
42
+ return {
43
+ get: (path, opts) => signedRequest("GET", path, undefined, opts),
44
+ post: (path, body, opts) => signedRequest("POST", path, body, opts),
45
+ put: (path, body, opts) => signedRequest("PUT", path, body, opts),
46
+ patch: (path, body, opts) => signedRequest("PATCH", path, body, opts),
47
+ delete: (path, opts) => signedRequest("DELETE", path, undefined, opts),
48
+ request: (method, path, body, opts) => signedRequest(method.toUpperCase(), path, body, opts),
49
+ };
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=client-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-factory.js","sourceRoot":"","sources":["../src/client-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /** Configuration for which request headers are included in the HMAC signature. */
2
+ export interface SignedHeadersConfig {
3
+ /** Include the Authorization header in the signature. Default: true. */
4
+ includeAuthorization: boolean;
5
+ /** Include all X-* headers (except X-Harden-*) in the signature. Default: true. */
6
+ includeXHeaders: boolean;
7
+ /** Additional header names to include (case-insensitive). */
8
+ additionalHeaders: string[];
9
+ /** Header names to exclude (case-insensitive, overrides inclusion). */
10
+ excludeHeaders: string[];
11
+ }
12
+ /** Identity and credentials for a named client that connects to this server. */
13
+ export interface HmacClientIdentity {
14
+ /** The shared secret as a Base64-encoded string for this client. */
15
+ sharedSecret: string;
16
+ }
17
+ /** Per-target configuration for a named service target (client-side). */
18
+ export interface HmacTargetConfig {
19
+ /** Base URL for the target service. */
20
+ baseUrl: string;
21
+ /** The shared secret as a Base64-encoded string for this target. */
22
+ sharedSecret: string;
23
+ /** Per-target signed headers override. If undefined, uses global config. */
24
+ signedHeaders?: SignedHeadersConfig;
25
+ /** Per-target timestamp tolerance override. If undefined, uses global config. */
26
+ timestampToleranceSeconds?: number;
27
+ }
28
+ /** Configuration for HMAC signing and validation. */
29
+ export interface HmacConfig {
30
+ /** The shared secret as a Base64-encoded string. */
31
+ sharedSecretBase64: string;
32
+ /** Named service targets with their own base URLs and secrets. */
33
+ targets?: Record<string, HmacTargetConfig>;
34
+ /** Named client identities for server-side multi-client secret resolution. */
35
+ clients?: Record<string, HmacClientIdentity>;
36
+ /** Configuration for which headers to include in the signature. */
37
+ signedHeaders: SignedHeadersConfig;
38
+ /** Timestamp tolerance in seconds for server-side validation. Default: 30. */
39
+ timestampToleranceSeconds: number;
40
+ }
41
+ /** Header name constants. */
42
+ export declare const SIGNATURE_HEADER = "X-Harden-Signature";
43
+ export declare const TIMESTAMP_HEADER = "X-Harden-Timestamp";
44
+ export declare const SIGNED_HEADERS_HEADER = "X-Harden-Signed-Headers";
45
+ export declare const CLIENT_ID_HEADER = "X-Harden-Client-Id";
46
+ export declare const HARDEN_HEADER_PREFIX = "x-harden-";
47
+ export declare const CLIENT_ID_HEADER_LOWER = "x-harden-client-id";
48
+ export declare const X_HEADER_PREFIX = "x-";
49
+ export declare const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 30;
50
+ /** Default signed headers configuration: include Authorization and X-* headers. */
51
+ export declare function defaultSignedHeadersConfig(): SignedHeadersConfig;
52
+ /** Signed headers configuration that signs no headers. */
53
+ export declare function noneSignedHeadersConfig(): SignedHeadersConfig;
54
+ /** Create an HmacConfig with sensible defaults. */
55
+ export declare function createHmacConfig(sharedSecretBase64: string, overrides?: Partial<Omit<HmacConfig, "sharedSecretBase64">>): HmacConfig;
56
+ /**
57
+ * Resolve the effective shared secret for a named target.
58
+ * Returns the target's secret if set, otherwise the global secret.
59
+ *
60
+ * @throws {Error} If the target is not found.
61
+ */
62
+ export declare function getEffectiveSecret(config: HmacConfig, targetName: string): string;
63
+ /**
64
+ * Resolve the effective signed headers config for a named target.
65
+ */
66
+ export declare function getEffectiveSignedHeaders(config: HmacConfig, targetName: string): SignedHeadersConfig;
67
+ /**
68
+ * Resolve the effective timestamp tolerance for a named target.
69
+ */
70
+ export declare function getEffectiveTimestampTolerance(config: HmacConfig, targetName: string): number;
71
+ /**
72
+ * Build an effective HmacConfig for a specific target with all overrides resolved.
73
+ */
74
+ export declare function configForTarget(config: HmacConfig, targetName: string): HmacConfig;
75
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,oBAAoB,EAAE,OAAO,CAAC;IAC9B,mFAAmF;IACnF,eAAe,EAAE,OAAO,CAAC;IACzB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,uEAAuE;IACvE,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,iFAAiF;IACjF,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,mEAAmE;IACnE,aAAa,EAAE,mBAAmB,CAAC;IACnC,8EAA8E;IAC9E,yBAAyB,EAAE,MAAM,CAAC;CACnC;AAED,6BAA6B;AAC7B,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AACrD,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AACrD,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAC/D,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AACrD,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAChD,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAC3D,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,mCAAmC,KAAK,CAAC;AAEtD,mFAAmF;AACnF,wBAAgB,0BAA0B,IAAI,mBAAmB,CAOhE;AAED,0DAA0D;AAC1D,wBAAgB,uBAAuB,IAAI,mBAAmB,CAO7D;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAC9B,kBAAkB,EAAE,MAAM,EAC1B,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC,GAC1D,UAAU,CAUZ;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CAWR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,GACjB,mBAAmB,CAMrB;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,GACjB,UAAU,CASZ"}
package/dist/config.js ADDED
@@ -0,0 +1,85 @@
1
+ /** Header name constants. */
2
+ export const SIGNATURE_HEADER = "X-Harden-Signature";
3
+ export const TIMESTAMP_HEADER = "X-Harden-Timestamp";
4
+ export const SIGNED_HEADERS_HEADER = "X-Harden-Signed-Headers";
5
+ export const CLIENT_ID_HEADER = "X-Harden-Client-Id";
6
+ export const HARDEN_HEADER_PREFIX = "x-harden-";
7
+ export const CLIENT_ID_HEADER_LOWER = "x-harden-client-id";
8
+ export const X_HEADER_PREFIX = "x-";
9
+ export const DEFAULT_TIMESTAMP_TOLERANCE_SECONDS = 30;
10
+ /** Default signed headers configuration: include Authorization and X-* headers. */
11
+ export function defaultSignedHeadersConfig() {
12
+ return {
13
+ includeAuthorization: true,
14
+ includeXHeaders: true,
15
+ additionalHeaders: [],
16
+ excludeHeaders: [],
17
+ };
18
+ }
19
+ /** Signed headers configuration that signs no headers. */
20
+ export function noneSignedHeadersConfig() {
21
+ return {
22
+ includeAuthorization: false,
23
+ includeXHeaders: false,
24
+ additionalHeaders: [],
25
+ excludeHeaders: [],
26
+ };
27
+ }
28
+ /** Create an HmacConfig with sensible defaults. */
29
+ export function createHmacConfig(sharedSecretBase64, overrides) {
30
+ return {
31
+ sharedSecretBase64,
32
+ targets: overrides?.targets,
33
+ clients: overrides?.clients,
34
+ signedHeaders: overrides?.signedHeaders ?? defaultSignedHeadersConfig(),
35
+ timestampToleranceSeconds: overrides?.timestampToleranceSeconds ??
36
+ DEFAULT_TIMESTAMP_TOLERANCE_SECONDS,
37
+ };
38
+ }
39
+ /**
40
+ * Resolve the effective shared secret for a named target.
41
+ * Returns the target's secret if set, otherwise the global secret.
42
+ *
43
+ * @throws {Error} If the target is not found.
44
+ */
45
+ export function getEffectiveSecret(config, targetName) {
46
+ const target = config.targets?.[targetName];
47
+ if (!target) {
48
+ const available = config.targets
49
+ ? Object.keys(config.targets).join(", ")
50
+ : "";
51
+ throw new Error(`Target '${targetName}' is not configured. Available targets: [${available}].`);
52
+ }
53
+ return target.sharedSecret || config.sharedSecretBase64;
54
+ }
55
+ /**
56
+ * Resolve the effective signed headers config for a named target.
57
+ */
58
+ export function getEffectiveSignedHeaders(config, targetName) {
59
+ const target = config.targets?.[targetName];
60
+ if (target?.signedHeaders) {
61
+ return target.signedHeaders;
62
+ }
63
+ return config.signedHeaders;
64
+ }
65
+ /**
66
+ * Resolve the effective timestamp tolerance for a named target.
67
+ */
68
+ export function getEffectiveTimestampTolerance(config, targetName) {
69
+ const target = config.targets?.[targetName];
70
+ if (target?.timestampToleranceSeconds !== undefined) {
71
+ return target.timestampToleranceSeconds;
72
+ }
73
+ return config.timestampToleranceSeconds;
74
+ }
75
+ /**
76
+ * Build an effective HmacConfig for a specific target with all overrides resolved.
77
+ */
78
+ export function configForTarget(config, targetName) {
79
+ return {
80
+ sharedSecretBase64: getEffectiveSecret(config, targetName),
81
+ signedHeaders: getEffectiveSignedHeaders(config, targetName),
82
+ timestampToleranceSeconds: getEffectiveTimestampTolerance(config, targetName),
83
+ };
84
+ }
85
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { HmacConfig } from "./config.js";
2
+ /**
3
+ * Load HmacConfig from environment variables.
4
+ *
5
+ * Supports the following variables (prefix default: "HARDEN_HMAC_"):
6
+ * {prefix}SHARED_SECRET_BASE64 — global shared secret
7
+ * {prefix}TIMESTAMP_TOLERANCE_SECONDS — global tolerance
8
+ * {prefix}SIGNED_HEADERS__INCLUDE_AUTHORIZATION — true/false
9
+ * {prefix}SIGNED_HEADERS__INCLUDE_X_HEADERS — true/false
10
+ * {prefix}TARGETS__{NAME}__BASE_URL — per-target base URL
11
+ * {prefix}TARGETS__{NAME}__SHARED_SECRET — per-target secret
12
+ * {prefix}TARGETS__{NAME}__TIMESTAMP_TOLERANCE_SECONDS — per-target tolerance
13
+ *
14
+ * If dotenv is installed as a peer dependency, .env file variables are loaded first.
15
+ *
16
+ * @param prefix - Environment variable prefix. Default: "HARDEN_HMAC_".
17
+ * @param env - Optional env object for testing. Defaults to process.env.
18
+ * @returns Parsed HmacConfig.
19
+ */
20
+ export declare function fromEnv(prefix?: string, env?: Record<string, string | undefined>): Promise<HmacConfig>;
21
+ //# sourceMappingURL=env-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-loader.d.ts","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,UAAU,EAGX,MAAM,aAAa,CAAC;AAoCrB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,OAAO,CAC3B,MAAM,GAAE,MAAuB,EAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACvC,OAAO,CAAC,UAAU,CAAC,CAqHrB"}
@@ -0,0 +1,145 @@
1
+ import { DEFAULT_TIMESTAMP_TOLERANCE_SECONDS, defaultSignedHeadersConfig, } from "./config.js";
2
+ /**
3
+ * Try to load dotenv if it's available as a peer dependency.
4
+ * This is a best-effort load — if dotenv is not installed, we skip silently.
5
+ * Uses dynamic import() for ESM compatibility.
6
+ */
7
+ async function tryLoadDotenv() {
8
+ try {
9
+ const dotenv = await import("dotenv");
10
+ dotenv.config();
11
+ }
12
+ catch {
13
+ // dotenv is an optional peer dependency — skip if not available
14
+ }
15
+ }
16
+ function parseBool(value, defaultValue) {
17
+ if (value === undefined)
18
+ return defaultValue;
19
+ return ["true", "1", "yes"].includes(value.toLowerCase());
20
+ }
21
+ /**
22
+ * Parse an integer from a string, returning the default if the value is
23
+ * undefined, empty, or not a valid integer.
24
+ */
25
+ function safeParseInt(value, defaultValue) {
26
+ if (value === undefined || value === "")
27
+ return defaultValue;
28
+ const parsed = parseInt(value, 10);
29
+ if (isNaN(parsed))
30
+ return defaultValue;
31
+ return parsed;
32
+ }
33
+ /**
34
+ * Load HmacConfig from environment variables.
35
+ *
36
+ * Supports the following variables (prefix default: "HARDEN_HMAC_"):
37
+ * {prefix}SHARED_SECRET_BASE64 — global shared secret
38
+ * {prefix}TIMESTAMP_TOLERANCE_SECONDS — global tolerance
39
+ * {prefix}SIGNED_HEADERS__INCLUDE_AUTHORIZATION — true/false
40
+ * {prefix}SIGNED_HEADERS__INCLUDE_X_HEADERS — true/false
41
+ * {prefix}TARGETS__{NAME}__BASE_URL — per-target base URL
42
+ * {prefix}TARGETS__{NAME}__SHARED_SECRET — per-target secret
43
+ * {prefix}TARGETS__{NAME}__TIMESTAMP_TOLERANCE_SECONDS — per-target tolerance
44
+ *
45
+ * If dotenv is installed as a peer dependency, .env file variables are loaded first.
46
+ *
47
+ * @param prefix - Environment variable prefix. Default: "HARDEN_HMAC_".
48
+ * @param env - Optional env object for testing. Defaults to process.env.
49
+ * @returns Parsed HmacConfig.
50
+ */
51
+ export async function fromEnv(prefix = "HARDEN_HMAC_", env) {
52
+ if (!env) {
53
+ await tryLoadDotenv();
54
+ env = process.env;
55
+ }
56
+ const upperPrefix = prefix.toUpperCase();
57
+ // Extract all prefixed vars
58
+ const prefixed = {};
59
+ for (const [key, value] of Object.entries(env)) {
60
+ if (key && value !== undefined && key.toUpperCase().startsWith(upperPrefix)) {
61
+ const suffix = key.substring(upperPrefix.length).toUpperCase();
62
+ prefixed[suffix] = value;
63
+ }
64
+ }
65
+ // Parse global settings
66
+ const sharedSecretBase64 = prefixed["SHARED_SECRET_BASE64"] ?? "";
67
+ const timestampToleranceSeconds = safeParseInt(prefixed["TIMESTAMP_TOLERANCE_SECONDS"], DEFAULT_TIMESTAMP_TOLERANCE_SECONDS);
68
+ // Parse signed headers
69
+ const signedHeaders = {
70
+ ...defaultSignedHeadersConfig(),
71
+ includeAuthorization: parseBool(prefixed["SIGNED_HEADERS__INCLUDE_AUTHORIZATION"], true),
72
+ includeXHeaders: parseBool(prefixed["SIGNED_HEADERS__INCLUDE_X_HEADERS"], true),
73
+ };
74
+ // Parse targets: keys matching TARGETS__{NAME}__{FIELD}
75
+ const targetFields = {};
76
+ const targetPrefix = "TARGETS__";
77
+ for (const [key, value] of Object.entries(prefixed)) {
78
+ if (!key.startsWith(targetPrefix))
79
+ continue;
80
+ const rest = key.substring(targetPrefix.length);
81
+ const separatorIndex = rest.indexOf("__");
82
+ if (separatorIndex === -1)
83
+ continue;
84
+ const targetName = rest.substring(0, separatorIndex).toLowerCase().replace(/_/g, "-");
85
+ const fieldName = rest.substring(separatorIndex + 2).toUpperCase();
86
+ if (!targetFields[targetName]) {
87
+ targetFields[targetName] = {};
88
+ }
89
+ targetFields[targetName][fieldName] = value;
90
+ }
91
+ const targets = {};
92
+ for (const [name, fields] of Object.entries(targetFields)) {
93
+ let targetSignedHeaders;
94
+ if (fields["SIGNED_HEADERS__INCLUDE_AUTHORIZATION"] !== undefined ||
95
+ fields["SIGNED_HEADERS__INCLUDE_X_HEADERS"] !== undefined) {
96
+ targetSignedHeaders = {
97
+ ...defaultSignedHeadersConfig(),
98
+ includeAuthorization: parseBool(fields["SIGNED_HEADERS__INCLUDE_AUTHORIZATION"], true),
99
+ includeXHeaders: parseBool(fields["SIGNED_HEADERS__INCLUDE_X_HEADERS"], true),
100
+ };
101
+ }
102
+ let targetTimestampTolerance;
103
+ if (fields["TIMESTAMP_TOLERANCE_SECONDS"] !== undefined) {
104
+ const parsed = parseInt(fields["TIMESTAMP_TOLERANCE_SECONDS"], 10);
105
+ targetTimestampTolerance = isNaN(parsed) ? undefined : parsed;
106
+ }
107
+ targets[name] = {
108
+ baseUrl: fields["BASE_URL"] ?? "",
109
+ sharedSecret: fields["SHARED_SECRET"] ?? "",
110
+ signedHeaders: targetSignedHeaders,
111
+ timestampToleranceSeconds: targetTimestampTolerance,
112
+ };
113
+ }
114
+ // Parse clients: keys matching CLIENTS__{NAME}__{FIELD}
115
+ const clientFields = {};
116
+ const clientPrefix = "CLIENTS__";
117
+ for (const [key, value] of Object.entries(prefixed)) {
118
+ if (!key.startsWith(clientPrefix))
119
+ continue;
120
+ const rest = key.substring(clientPrefix.length);
121
+ const separatorIndex = rest.indexOf("__");
122
+ if (separatorIndex === -1)
123
+ continue;
124
+ const clientName = rest.substring(0, separatorIndex).toLowerCase().replace(/_/g, "-");
125
+ const fieldName = rest.substring(separatorIndex + 2).toUpperCase();
126
+ if (!clientFields[clientName]) {
127
+ clientFields[clientName] = {};
128
+ }
129
+ clientFields[clientName][fieldName] = value;
130
+ }
131
+ const clients = {};
132
+ for (const [name, fields] of Object.entries(clientFields)) {
133
+ clients[name] = {
134
+ sharedSecret: fields["SHARED_SECRET"] ?? "",
135
+ };
136
+ }
137
+ return {
138
+ sharedSecretBase64,
139
+ targets: Object.keys(targets).length > 0 ? targets : undefined,
140
+ clients: Object.keys(clients).length > 0 ? clients : undefined,
141
+ signedHeaders,
142
+ timestampToleranceSeconds,
143
+ };
144
+ }
145
+ //# sourceMappingURL=env-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-loader.js","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /** Error types for HMAC validation failures. */
2
+ export type HmacErrorType = "missing_signature" | "missing_timestamp" | "invalid_timestamp" | "timestamp_expired" | "timestamp_out_of_range" | "signature_invalid";
3
+ /** Error thrown when HMAC validation fails. */
4
+ export declare class HmacValidationError extends Error {
5
+ readonly errorType: HmacErrorType;
6
+ constructor(errorType: HmacErrorType, message: string);
7
+ }
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,MAAM,MAAM,aAAa,GACrB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACnB,wBAAwB,GACxB,mBAAmB,CAAC;AAExB,+CAA+C;AAC/C,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;gBAEtB,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM;CAKtD"}
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ /** Error thrown when HMAC validation fails. */
2
+ export class HmacValidationError extends Error {
3
+ errorType;
4
+ constructor(errorType, message) {
5
+ super(message);
6
+ this.name = "HmacValidationError";
7
+ this.errorType = errorType;
8
+ }
9
+ }
10
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AASA,+CAA+C;AAC/C,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,SAAS,CAAgB;IAElC,YAAY,SAAwB,EAAE,OAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}