@shelbyb/daywalker-sdk 0.1.0 → 0.1.2

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/dist/next.d.ts CHANGED
@@ -1,10 +1,63 @@
1
- import { type Daywalker } from "./client.js";
1
+ import { Daywalker } from "./client.js";
2
+ import type { TokenPayload } from "./types.js";
2
3
  /**
3
- * Next.js App Router route handler for /.well-known/daywalker-keys.json.
4
+ * Real Server Action IDs are hex hashes. Scanners probing for CVE-2025-55182 (React2Shell)
5
+ * send junk like `Next-Action: x`, which Next.js rejects with an internal error.
6
+ * Dropping them up front protects application logs and server resources.
7
+ */
8
+ export declare const SERVER_ACTION_ID_PATTERN: RegExp;
9
+ export declare const DEFAULT_BYPASS_PATHS: string[];
10
+ export interface NextMiddlewareOptions {
11
+ client?: Daywalker;
12
+ serviceSlug?: string;
13
+ bypassPaths?: string[];
14
+ errorPath?: string;
15
+ homePath?: string;
16
+ protectApiOnly?: boolean;
17
+ validateServerActions?: boolean;
18
+ allowedHosts?: string[];
19
+ }
20
+ /**
21
+ * Sanitizes open redirect path parameter (`from`).
22
+ * Prevents protocol-relative URLs (`//evil.com`), backslashes (`/\evil.com`), and control characters.
23
+ */
24
+ export declare function sanitizeRedirectPath(path: string): string | null;
25
+ /**
26
+ * Hardened Next.js Middleware for root `middleware.ts`.
27
+ *
28
+ * Implements:
29
+ * 1. Server Action probe defense (CVE-2025-55182 scanner mitigation)
30
+ * 2. Static asset and health check bypass
31
+ * 3. Self-healing service error page routing with `from` back-redirect
32
+ * 4. Distinct handling for API routes (JSON + CORS) vs UI pages (redirect)
33
+ *
34
+ * Example:
35
+ * // middleware.ts
36
+ * import { createDaywalkerMiddleware } from "@shelbyb/daywalker-sdk/next";
37
+ *
38
+ * export const middleware = createDaywalkerMiddleware();
39
+ *
40
+ * export const config = {
41
+ * matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
42
+ * };
43
+ */
44
+ export declare function createDaywalkerMiddleware(options?: NextMiddlewareOptions): (request: Request) => Promise<Response>;
45
+ /**
46
+ * Next.js App Router route handler wrapper with Daywalker token validation.
47
+ * Extracts `Authorization: Bearer <token>` or `x-api-token`, validates via client,
48
+ * returns 401/429 JSON response on error, or passes validated `auth` payload in context.
49
+ *
50
+ * Example:
51
+ * // app/api/protected/route.ts
52
+ * import { withDaywalkerAuth } from "@shelbyb/daywalker-sdk/next";
4
53
  *
5
- * // app/.well-known/daywalker-keys.json/route.ts
6
- * export { GET, dynamic } from "@shelbyb/daywalker-sdk/next";
54
+ * export const POST = withDaywalkerAuth(async (req, { auth }) => {
55
+ * return Response.json({ message: "Hello " + auth.serviceSlug, auth });
56
+ * });
7
57
  */
8
- export declare const dynamic = "force-dynamic";
9
- export declare function keysRouteHandler(client?: Daywalker): () => Response;
10
- export declare const GET: () => Response;
58
+ export declare function withDaywalkerAuth<Context extends Record<string, unknown> = Record<string, unknown>>(handler: (req: Request, context: Context & {
59
+ auth: TokenPayload;
60
+ }) => Promise<Response>, clientOrOptions?: Daywalker | {
61
+ client?: Daywalker;
62
+ serviceSlug?: string;
63
+ }): (req: Request, context?: Context) => Promise<Response>;
package/dist/next.js CHANGED
@@ -1,14 +1,189 @@
1
- import { daywalker } from "./client.js";
1
+ import { daywalker, Daywalker } from "./client.js";
2
2
  /**
3
- * Next.js App Router route handler for /.well-known/daywalker-keys.json.
3
+ * Real Server Action IDs are hex hashes. Scanners probing for CVE-2025-55182 (React2Shell)
4
+ * send junk like `Next-Action: x`, which Next.js rejects with an internal error.
5
+ * Dropping them up front protects application logs and server resources.
6
+ */
7
+ export const SERVER_ACTION_ID_PATTERN = /^[0-9a-f]{40,}$/i;
8
+ export const DEFAULT_BYPASS_PATHS = [
9
+ "/api/health",
10
+ "/healthz",
11
+ "/favicon.ico",
12
+ "/robots.txt",
13
+ "/sitemap.xml",
14
+ ];
15
+ /**
16
+ * Sanitizes open redirect path parameter (`from`).
17
+ * Prevents protocol-relative URLs (`//evil.com`), backslashes (`/\evil.com`), and control characters.
18
+ */
19
+ export function sanitizeRedirectPath(path) {
20
+ if (!path || typeof path !== "string")
21
+ return null;
22
+ const clean = path.trim().replace(/[\r\n\0]/g, "");
23
+ if (!clean.startsWith("/") || clean.startsWith("//") || clean.startsWith("/\\")) {
24
+ return null;
25
+ }
26
+ return clean;
27
+ }
28
+ function extractToken(req) {
29
+ const authHeader = req.headers.get("authorization");
30
+ if (authHeader && authHeader.toLowerCase().startsWith("bearer ")) {
31
+ return authHeader.slice(7).trim();
32
+ }
33
+ const apiTokenHeader = req.headers.get("x-api-token");
34
+ if (apiTokenHeader) {
35
+ return apiTokenHeader.trim();
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Hardened Next.js Middleware for root `middleware.ts`.
41
+ *
42
+ * Implements:
43
+ * 1. Server Action probe defense (CVE-2025-55182 scanner mitigation)
44
+ * 2. Static asset and health check bypass
45
+ * 3. Self-healing service error page routing with `from` back-redirect
46
+ * 4. Distinct handling for API routes (JSON + CORS) vs UI pages (redirect)
47
+ *
48
+ * Example:
49
+ * // middleware.ts
50
+ * import { createDaywalkerMiddleware } from "@shelbyb/daywalker-sdk/next";
51
+ *
52
+ * export const middleware = createDaywalkerMiddleware();
53
+ *
54
+ * export const config = {
55
+ * matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
56
+ * };
57
+ */
58
+ export function createDaywalkerMiddleware(options = {}) {
59
+ const client = options.client ?? daywalker;
60
+ const bypassPaths = options.bypassPaths ?? DEFAULT_BYPASS_PATHS;
61
+ const errorPath = options.errorPath ?? "/service-error";
62
+ const homePath = options.homePath ?? "/";
63
+ const validateServerActions = options.validateServerActions ?? true;
64
+ return async function middleware(request) {
65
+ const url = new URL(request.url);
66
+ const pathname = url.pathname;
67
+ const search = url.search;
68
+ // Host header validation if allowedHosts configured
69
+ if (options.allowedHosts && options.allowedHosts.length > 0) {
70
+ const host = request.headers.get("host")?.toLowerCase().replace(/:\d+$/, "");
71
+ if (host && !options.allowedHosts.some((h) => h.toLowerCase() === host)) {
72
+ return Response.json({ error: "Forbidden", message: "Invalid host header." }, { status: 403 });
73
+ }
74
+ }
75
+ // 0. Reject malformed Server Action requests before routing or auth checks
76
+ if (validateServerActions) {
77
+ const actionId = request.headers.get("next-action");
78
+ if (actionId !== null && (actionId.length > 256 || !SERVER_ACTION_ID_PATTERN.test(actionId.trim()))) {
79
+ return Response.json({ error: "Bad Request", message: "Invalid Server Action request." }, { status: 400 });
80
+ }
81
+ }
82
+ // 1. Bypass static files and health check probes
83
+ if (bypassPaths.some((path) => pathname === path || pathname.startsWith(`${path}/`)) ||
84
+ pathname.startsWith("/_next") ||
85
+ (pathname.includes(".") && !pathname.endsWith(".json"))) {
86
+ return globalThis.NextResponse?.next
87
+ ? globalThis.NextResponse.next()
88
+ : new Response(null, { headers: { "x-middleware-next": "1" } });
89
+ }
90
+ // 2. Validate token (from request header, or service's configured DAYWALKER_API_KEY)
91
+ const incomingToken = extractToken(request);
92
+ const authResult = await client.validateToken(incomingToken ? { token: incomingToken, serviceSlug: options.serviceSlug } : { serviceSlug: options.serviceSlug });
93
+ // 3. Handle service error page route
94
+ if (pathname === errorPath) {
95
+ if (authResult.ok) {
96
+ // If the service token is now valid, heal/redirect back to home or referer
97
+ const redirectUrl = new URL(homePath, request.url);
98
+ return globalThis.NextResponse?.redirect
99
+ ? globalThis.NextResponse.redirect(redirectUrl)
100
+ : Response.redirect(redirectUrl.toString(), 307);
101
+ }
102
+ // Allow rendering the error page
103
+ return globalThis.NextResponse?.next
104
+ ? globalThis.NextResponse.next()
105
+ : new Response(null, { headers: { "x-middleware-next": "1" } });
106
+ }
107
+ // 4. Handle invalid token for all other routes
108
+ if (!authResult.ok) {
109
+ // API routes: return JSON error response
110
+ if (pathname.startsWith("/api/")) {
111
+ return Response.json({
112
+ error: authResult.error || "Unauthorized",
113
+ message: authResult.message || "Service authorization check failed with Daywalker Auth.",
114
+ retryAfter: authResult.retryAfter,
115
+ }, {
116
+ status: authResult.status || 401,
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "Access-Control-Allow-Origin": "*",
120
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
121
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Api-Token",
122
+ ...(authResult.retryAfter ? { "Retry-After": String(authResult.retryAfter) } : {}),
123
+ },
124
+ });
125
+ }
126
+ // If user only wanted API protection, allow web routes through
127
+ if (options.protectApiOnly) {
128
+ return globalThis.NextResponse?.next
129
+ ? globalThis.NextResponse.next()
130
+ : new Response(null, { headers: { "x-middleware-next": "1" } });
131
+ }
132
+ // Web UI routes: redirect to diagnostic service error screen
133
+ const redirectUrl = new URL(errorPath, request.url);
134
+ redirectUrl.searchParams.set("error", authResult.error || "Unauthorized");
135
+ redirectUrl.searchParams.set("message", authResult.message || "Service authorization check failed.");
136
+ redirectUrl.searchParams.set("status", String(authResult.status || 401));
137
+ if (pathname !== "/" || search) {
138
+ const rawTarget = `${pathname}${search}`;
139
+ const sanitized = sanitizeRedirectPath(rawTarget);
140
+ if (sanitized) {
141
+ redirectUrl.searchParams.set("from", sanitized);
142
+ }
143
+ }
144
+ return globalThis.NextResponse?.redirect
145
+ ? globalThis.NextResponse.redirect(redirectUrl)
146
+ : Response.redirect(redirectUrl.toString(), 307);
147
+ }
148
+ return globalThis.NextResponse?.next
149
+ ? globalThis.NextResponse.next()
150
+ : new Response(null, { headers: { "x-middleware-next": "1" } });
151
+ };
152
+ }
153
+ /**
154
+ * Next.js App Router route handler wrapper with Daywalker token validation.
155
+ * Extracts `Authorization: Bearer <token>` or `x-api-token`, validates via client,
156
+ * returns 401/429 JSON response on error, or passes validated `auth` payload in context.
157
+ *
158
+ * Example:
159
+ * // app/api/protected/route.ts
160
+ * import { withDaywalkerAuth } from "@shelbyb/daywalker-sdk/next";
4
161
  *
5
- * // app/.well-known/daywalker-keys.json/route.ts
6
- * export { GET, dynamic } from "@shelbyb/daywalker-sdk/next";
162
+ * export const POST = withDaywalkerAuth(async (req, { auth }) => {
163
+ * return Response.json({ message: "Hello " + auth.serviceSlug, auth });
164
+ * });
7
165
  */
8
- export const dynamic = "force-dynamic";
9
- export function keysRouteHandler(client = daywalker()) {
10
- return function GET() {
11
- return Response.json(client.keys(), { headers: { "Cache-Control": "public, max-age=300" } });
166
+ export function withDaywalkerAuth(handler, clientOrOptions) {
167
+ const client = clientOrOptions instanceof Daywalker
168
+ ? clientOrOptions
169
+ : clientOrOptions?.client ?? daywalker;
170
+ const serviceSlug = !(clientOrOptions instanceof Daywalker) ? clientOrOptions?.serviceSlug : undefined;
171
+ return async function daywalkerAuthHandler(req, context) {
172
+ const token = extractToken(req);
173
+ if (!token) {
174
+ return Response.json({ error: "INVALID_TOKEN", message: "Missing Authorization header or x-api-token" }, { status: 401 });
175
+ }
176
+ const result = await client.validateToken(token, { serviceSlug });
177
+ if (!result.ok) {
178
+ return Response.json({ error: result.error, message: result.message, retryAfter: result.retryAfter }, {
179
+ status: result.status === 429 ? 429 : 401,
180
+ headers: result.retryAfter ? { "Retry-After": String(result.retryAfter) } : undefined,
181
+ });
182
+ }
183
+ const mergedContext = {
184
+ ...(context ?? {}),
185
+ auth: result.data,
186
+ };
187
+ return handler(req, mergedContext);
12
188
  };
13
189
  }
14
- export const GET = () => keysRouteHandler()();
package/dist/signing.d.ts CHANGED
@@ -1,53 +1,3 @@
1
- import { type KeyObject } from "node:crypto";
2
- /**
3
- * Domain-signed requests to Daywalker Auth.
4
- *
5
- * Each request is signed with an Ed25519 key whose public half the calling service serves at
6
- * https://<domain>/.well-known/daywalker-keys.json. Daywalker fetches that key over HTTPS, so
7
- * a valid signature proves the request comes from whoever controls the domain, wherever the
8
- * service is hosted. This must match Daywalker's verifier exactly (src/lib/request-signature.ts
9
- * in daywalker-service-auth).
10
- */
11
- export declare const SIGNATURE_VERSION = "daywalker-v1";
1
+ export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, bytesToHex, hexToBytes, computeContentDigest, sha256Base64, sha256Base64Url, calculateJwkThumbprint, generateSigningKey, loadSigningKey, loadPublicKey, SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./crypto/index.js";
12
2
  export declare const KEYS_PATH = "/.well-known/daywalker-keys.json";
13
- export interface PublicJwk {
14
- kty: "OKP";
15
- crv: "Ed25519";
16
- x: string;
17
- kid: string;
18
- use: "sig";
19
- alg: "EdDSA";
20
- }
21
- export interface SigningKey {
22
- privateKey: KeyObject;
23
- publicJwk: PublicJwk;
24
- }
25
- /** Generate a new key; `privateKey` is base64 PKCS#8 DER, the format DAYWALKER_SIGNING_KEY expects. */
26
- export declare function generateSigningKey(): {
27
- privateKey: string;
28
- publicJwk: PublicJwk;
29
- };
30
- /**
31
- * Load a private key from env: PKCS#8 PEM (literal "\n" escapes are fine) or base64 PKCS#8 DER.
32
- * The key id is the RFC 7638 JWK thumbprint, so it's stable without extra configuration.
33
- */
34
- export declare function loadSigningKey(raw: string): SigningKey;
35
- export declare function canonicalPayload(p: {
36
- method: string;
37
- path: string;
38
- domain: string;
39
- timestamp: string;
40
- nonce: string;
41
- body: string;
42
- }): string;
43
- /** The X-Daywalker-* headers for one request. Send exactly `body` as the request body. */
44
- export declare function signRequest(params: {
45
- method: string;
46
- /** URL path only, e.g. /api/v1/tokens/validate */
47
- path: string;
48
- domain: string;
49
- body: string;
50
- key: SigningKey;
51
- timestamp?: number;
52
- nonce?: string;
53
- }): Record<string, string>;
3
+ export type { PublicJwk, SigningKey, SignedHeaders, SignRequestParams, VerifySignatureParams, VerifySignatureResult, } from "./types.js";
package/dist/signing.js CHANGED
@@ -1,51 +1,2 @@
1
- import crypto from "node:crypto";
2
- /**
3
- * Domain-signed requests to Daywalker Auth.
4
- *
5
- * Each request is signed with an Ed25519 key whose public half the calling service serves at
6
- * https://<domain>/.well-known/daywalker-keys.json. Daywalker fetches that key over HTTPS, so
7
- * a valid signature proves the request comes from whoever controls the domain, wherever the
8
- * service is hosted. This must match Daywalker's verifier exactly (src/lib/request-signature.ts
9
- * in daywalker-service-auth).
10
- */
11
- export const SIGNATURE_VERSION = "daywalker-v1";
1
+ export { stringToUtf8, utf8ToString, bytesToBase64, base64ToBytes, bytesToBase64Url, base64UrlToBytes, bytesToHex, hexToBytes, computeContentDigest, sha256Base64, sha256Base64Url, calculateJwkThumbprint, generateSigningKey, loadSigningKey, loadPublicKey, SIGNATURE_VERSION, SIGNATURE_LABEL, SIGNATURE_ALG, MAX_CLOCK_SKEW_SECONDS, formatSignatureParams, createSignatureBase, signRequest, verifyRequestSignature, verifyJwtLease, registerPublicKey, } from "./crypto/index.js";
12
2
  export const KEYS_PATH = "/.well-known/daywalker-keys.json";
13
- /** Generate a new key; `privateKey` is base64 PKCS#8 DER, the format DAYWALKER_SIGNING_KEY expects. */
14
- export function generateSigningKey() {
15
- const { privateKey } = crypto.generateKeyPairSync("ed25519");
16
- const der = privateKey.export({ format: "der", type: "pkcs8" }).toString("base64");
17
- return { privateKey: der, publicJwk: loadSigningKey(der).publicJwk };
18
- }
19
- /**
20
- * Load a private key from env: PKCS#8 PEM (literal "\n" escapes are fine) or base64 PKCS#8 DER.
21
- * The key id is the RFC 7638 JWK thumbprint, so it's stable without extra configuration.
22
- */
23
- export function loadSigningKey(raw) {
24
- const value = raw.trim();
25
- const privateKey = value.includes("-----BEGIN")
26
- ? crypto.createPrivateKey(value.replace(/\\n/g, "\n"))
27
- : crypto.createPrivateKey({ key: Buffer.from(value, "base64"), format: "der", type: "pkcs8" });
28
- if (privateKey.asymmetricKeyType !== "ed25519") {
29
- throw new Error(`DAYWALKER_SIGNING_KEY must be an Ed25519 key (got ${privateKey.asymmetricKeyType})`);
30
- }
31
- const { x } = crypto.createPublicKey(privateKey).export({ format: "jwk" });
32
- const kid = crypto.createHash("sha256").update(JSON.stringify({ crv: "Ed25519", kty: "OKP", x })).digest("base64url");
33
- return { privateKey, publicJwk: { kty: "OKP", crv: "Ed25519", x, kid, use: "sig", alg: "EdDSA" } };
34
- }
35
- export function canonicalPayload(p) {
36
- const bodySha256 = crypto.createHash("sha256").update(p.body, "utf8").digest("hex");
37
- return [SIGNATURE_VERSION, p.method.toUpperCase(), p.path, p.domain.toLowerCase(), p.timestamp, p.nonce, bodySha256].join("\n");
38
- }
39
- /** The X-Daywalker-* headers for one request. Send exactly `body` as the request body. */
40
- export function signRequest(params) {
41
- const timestamp = String(params.timestamp ?? Math.floor(Date.now() / 1000));
42
- const nonce = params.nonce ?? crypto.randomBytes(24).toString("base64url");
43
- const payload = canonicalPayload({ ...params, timestamp, nonce });
44
- return {
45
- "X-Daywalker-Domain": params.domain.toLowerCase(),
46
- "X-Daywalker-Key-Id": params.key.publicJwk.kid,
47
- "X-Daywalker-Timestamp": timestamp,
48
- "X-Daywalker-Nonce": nonce,
49
- "X-Daywalker-Signature": crypto.sign(null, Buffer.from(payload), params.key.privateKey).toString("base64url"),
50
- };
51
- }
@@ -0,0 +1,122 @@
1
+ export interface RateLimitWindow {
2
+ limit: number;
3
+ remaining: number;
4
+ reset: number;
5
+ }
6
+ export interface TokenPayload {
7
+ serviceSlug: string;
8
+ tenantId: string;
9
+ rateLimit: RateLimitWindow;
10
+ rateLimitHourly?: RateLimitWindow;
11
+ tokenId?: string;
12
+ tokenName?: string;
13
+ userId?: string;
14
+ stage?: string;
15
+ offlineVerified?: boolean;
16
+ [key: string]: unknown;
17
+ }
18
+ export type ValidateErrorCode = "EXPIRED" | "REVOKED" | "INVALID_TOKEN" | "RATE_LIMITED" | "NETWORK_ERROR" | "CONFIGURATION_ERROR" | string;
19
+ export type ValidateResult = {
20
+ ok: true;
21
+ data: TokenPayload;
22
+ } | {
23
+ ok: false;
24
+ error: ValidateErrorCode;
25
+ status: number;
26
+ message: string;
27
+ retryAfter?: number;
28
+ };
29
+ export interface PublicJwk {
30
+ kty: "OKP";
31
+ crv: "Ed25519";
32
+ x: string;
33
+ kid: string;
34
+ use?: "sig";
35
+ alg?: "EdDSA" | "ed25519";
36
+ }
37
+ export interface SigningKey {
38
+ privateKey: CryptoKey;
39
+ publicKey: CryptoKey;
40
+ publicJwk: PublicJwk;
41
+ privateKeyPkcs8Base64?: string;
42
+ }
43
+ export interface RetryOptions {
44
+ maxRetries?: number;
45
+ initialDelayMs?: number;
46
+ maxDelayMs?: number;
47
+ backoffFactor?: number;
48
+ jitter?: boolean;
49
+ }
50
+ export interface DaywalkerConfig {
51
+ apiKey?: string;
52
+ signingKey?: string | SigningKey;
53
+ previousSigningKey?: string | SigningKey;
54
+ keyId?: string;
55
+ serviceSlug?: string;
56
+ domain?: string;
57
+ daywalkerPublicKey?: string | PublicJwk;
58
+ authUrl?: string;
59
+ cacheTtlMs?: number;
60
+ maxRetries?: number;
61
+ retry?: RetryOptions;
62
+ fetch?: typeof fetch;
63
+ env?: Record<string, string | undefined>;
64
+ }
65
+ export interface SelfCheckResult {
66
+ ok: boolean;
67
+ serviceSlug: string | null;
68
+ domain: string | null;
69
+ signing: boolean;
70
+ keyId: string | null;
71
+ offlineVerificationReady: boolean;
72
+ problems: string[];
73
+ }
74
+ export interface SignedHeaders {
75
+ "Content-Digest"?: string;
76
+ "Signature-Input": string;
77
+ Signature: string;
78
+ Authorization?: string;
79
+ [key: string]: string | undefined;
80
+ }
81
+ export interface SignRequestParams {
82
+ method: string;
83
+ path: string;
84
+ authority: string;
85
+ body?: string | Uint8Array | null;
86
+ key: SigningKey | string;
87
+ created?: number;
88
+ keyid?: string;
89
+ alg?: string;
90
+ apiKey?: string;
91
+ nonce?: string;
92
+ }
93
+ export interface VerifySignatureParams {
94
+ method: string;
95
+ path: string;
96
+ authority: string;
97
+ headers: Record<string, string | undefined> | Headers;
98
+ body?: string | Uint8Array | null;
99
+ publicKey: PublicJwk | CryptoKey | string;
100
+ currentTime?: number;
101
+ maxClockSkewSeconds?: number;
102
+ replayCheck?: boolean;
103
+ }
104
+ export interface VerifySignatureResult {
105
+ valid: boolean;
106
+ keyid?: string;
107
+ error?: string;
108
+ }
109
+ export declare class DaywalkerError extends Error {
110
+ readonly status?: number;
111
+ readonly code?: string;
112
+ constructor(message: string, status?: number, code?: string);
113
+ toJSON(): Record<string, unknown>;
114
+ }
115
+ export declare class DaywalkerAuthError extends DaywalkerError {
116
+ constructor(message: string, status?: number, code?: ValidateErrorCode);
117
+ }
118
+ export declare class DaywalkerRateLimitError extends DaywalkerError {
119
+ readonly retryAfter?: number;
120
+ constructor(message: string, retryAfter?: number);
121
+ toJSON(): Record<string, unknown>;
122
+ }
package/dist/types.js ADDED
@@ -0,0 +1,46 @@
1
+ import { redactSecrets } from "./crypto/encoding.js";
2
+ export class DaywalkerError extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status, code) {
6
+ const cleanMessage = redactSecrets(message);
7
+ super(cleanMessage);
8
+ this.name = "DaywalkerError";
9
+ this.status = status;
10
+ this.code = code;
11
+ Object.setPrototypeOf(this, new.target.prototype);
12
+ }
13
+ toJSON() {
14
+ return {
15
+ name: this.name,
16
+ message: redactSecrets(this.message),
17
+ status: this.status,
18
+ code: this.code,
19
+ };
20
+ }
21
+ [Symbol.for("nodejs.util.inspect.custom")]() {
22
+ return this.toJSON();
23
+ }
24
+ }
25
+ export class DaywalkerAuthError extends DaywalkerError {
26
+ constructor(message, status = 401, code = "INVALID_TOKEN") {
27
+ super(message, status, code);
28
+ this.name = "DaywalkerAuthError";
29
+ Object.setPrototypeOf(this, new.target.prototype);
30
+ }
31
+ }
32
+ export class DaywalkerRateLimitError extends DaywalkerError {
33
+ retryAfter;
34
+ constructor(message, retryAfter) {
35
+ super(message, 429, "RATE_LIMITED");
36
+ this.name = "DaywalkerRateLimitError";
37
+ this.retryAfter = retryAfter;
38
+ Object.setPrototypeOf(this, new.target.prototype);
39
+ }
40
+ toJSON() {
41
+ return {
42
+ ...super.toJSON(),
43
+ retryAfter: this.retryAfter,
44
+ };
45
+ }
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shelbyb/daywalker-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Validate Daywalker API tokens and make domain-signed calls to Daywalker Auth from any service.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,16 +14,21 @@
14
14
  "types": "./dist/next.d.ts",
15
15
  "import": "./dist/next.js"
16
16
  },
17
+ "./middleware": {
18
+ "types": "./dist/middleware.d.ts",
19
+ "import": "./dist/middleware.js"
20
+ },
17
21
  "./express": {
18
22
  "types": "./dist/express.d.ts",
19
23
  "import": "./dist/express.js"
20
24
  }
21
25
  },
22
26
  "bin": {
23
- "daywalker": "./dist/cli.js"
27
+ "daywalker": "dist/cli.js"
24
28
  },
25
29
  "files": [
26
30
  "dist",
31
+ "assets",
27
32
  "README.md",
28
33
  "LICENSE"
29
34
  ],
@@ -33,7 +38,8 @@
33
38
  "scripts": {
34
39
  "build": "tsc -p tsconfig.json",
35
40
  "typecheck": "tsc -p tsconfig.json --noEmit",
36
- "prepublishOnly": "npm run build"
41
+ "test": "npm run build && node --import tsx --test test/*.test.ts",
42
+ "prepublishOnly": "npm test"
37
43
  },
38
44
  "publishConfig": {
39
45
  "access": "public",
@@ -42,13 +48,13 @@
42
48
  },
43
49
  "repository": {
44
50
  "type": "git",
45
- "url": "git+https://github.com/Shelbyb/daywalker-service-auth.git",
46
- "directory": "packages/sdk"
51
+ "url": "git+https://github.com/Shelbyb/daywalker-sdk.git"
47
52
  },
48
53
  "license": "MIT",
49
54
  "devDependencies": {
50
55
  "@types/node": "^22.13.5",
51
- "typescript": "^5.9.3"
56
+ "typescript": "^5.9.3",
57
+ "tsx": "^4.23.15"
52
58
  },
53
59
  "keywords": [
54
60
  "daywalker",
@@ -57,5 +63,8 @@
57
63
  "ed25519",
58
64
  "request-signing"
59
65
  ],
60
- "homepage": "https://auth.daywalker.dev/docs"
66
+ "homepage": "https://auth.daywalker.dev/docs",
67
+ "bugs": {
68
+ "url": "https://github.com/Shelbyb/daywalker-sdk/issues"
69
+ }
61
70
  }