@buildersgarden/siwa 0.0.13 → 0.0.15

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/erc8128.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { Address, PublicClient } from 'viem';
13
13
  import { type EthHttpSigner, type NonceStore } from '@slicekit/erc8128';
14
- import type { Signer, SignerType } from './signer.js';
14
+ import type { Signer, SignerType } from './signer/index.js';
15
15
  export interface VerifyOptions {
16
16
  receiptSecret: string;
17
17
  rpcUrl?: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './signer.js';
1
+ export * from './signer/index.js';
2
2
  export * from './keystore.js';
3
3
  export * from './siwa.js';
4
4
  export * from './identity.js';
@@ -7,4 +7,5 @@ export * from './registry.js';
7
7
  export * from './addresses.js';
8
8
  export * from './receipt.js';
9
9
  export * from './erc8128.js';
10
+ export * from './nonce-store.js';
10
11
  export * from './tba.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from './signer.js';
1
+ export * from './signer/index.js';
2
2
  export * from './keystore.js';
3
3
  export * from './siwa.js';
4
4
  export * from './identity.js';
@@ -7,4 +7,5 @@ export * from './registry.js';
7
7
  export * from './addresses.js';
8
8
  export * from './receipt.js';
9
9
  export * from './erc8128.js';
10
+ export * from './nonce-store.js';
10
11
  export * from './tba.js';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * nonce-store.ts
3
+ *
4
+ * Pluggable nonce store for SIWA sign-in replay protection.
5
+ *
6
+ * The default HMAC-based stateless nonces are convenient but can be replayed
7
+ * within their TTL window. A SIWANonceStore tracks issued nonces server-side
8
+ * so each nonce can only be consumed once.
9
+ *
10
+ * Built-in factories:
11
+ * - createMemorySIWANonceStore() — single-process (Map + TTL)
12
+ * - createRedisSIWANonceStore(redis) — ioredis / node-redis
13
+ * - createKVSIWANonceStore(kv) — Cloudflare Workers KV
14
+ *
15
+ * For databases (SQL, Prisma, Drizzle), implement the SIWANonceStore interface
16
+ * directly — it's just two methods.
17
+ *
18
+ * No new runtime dependencies — each factory accepts a minimal interface so
19
+ * users bring their own client.
20
+ */
21
+ export interface SIWANonceStore {
22
+ /** Store an issued nonce. Returns true on success, false if already exists. */
23
+ issue(nonce: string, ttlMs: number): Promise<boolean>;
24
+ /** Atomically check-and-delete a nonce. Returns true if it existed (valid), false otherwise. */
25
+ consume(nonce: string): Promise<boolean>;
26
+ }
27
+ /**
28
+ * In-memory nonce store with TTL-based expiry.
29
+ *
30
+ * Suitable for single-process servers. For multi-instance deployments,
31
+ * implement SIWANonceStore with a shared store (Redis, database, etc.).
32
+ */
33
+ export declare function createMemorySIWANonceStore(): SIWANonceStore;
34
+ /**
35
+ * Minimal subset of the ioredis / node-redis API used by the nonce store.
36
+ * Both ioredis and node-redis v4 (with legacyMode or the `.set()` overload)
37
+ * satisfy this interface out of the box.
38
+ */
39
+ export interface RedisLikeClient {
40
+ set(...args: unknown[]): Promise<unknown>;
41
+ del(...args: unknown[]): Promise<number>;
42
+ }
43
+ /**
44
+ * Redis-backed nonce store.
45
+ *
46
+ * Uses `SET key 1 PX ttl NX` for atomic issue (fails if key exists) and
47
+ * `DEL key` for atomic consume (returns 1 only on first delete).
48
+ *
49
+ * @param redis An ioredis instance or any client matching `RedisLikeClient`.
50
+ * @param prefix Optional key prefix (default `"siwa:nonce:"`).
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // ioredis
55
+ * import Redis from "ioredis";
56
+ * const redis = new Redis();
57
+ * const nonceStore = createRedisSIWANonceStore(redis);
58
+ *
59
+ * // node-redis v4 — wrap with a tiny adapter:
60
+ * import { createClient } from "redis";
61
+ * const client = createClient(); await client.connect();
62
+ * const nonceStore = createRedisSIWANonceStore({
63
+ * set: (...a: unknown[]) => client.set(a[0] as string, a[1] as string,
64
+ * { PX: a[3] as number, NX: true }).then(r => r ?? null),
65
+ * del: (k: unknown) => client.del(k as string),
66
+ * });
67
+ * ```
68
+ */
69
+ export declare function createRedisSIWANonceStore(redis: RedisLikeClient, prefix?: string): SIWANonceStore;
70
+ /**
71
+ * Minimal subset of the Cloudflare KV namespace binding API.
72
+ */
73
+ export interface KVNamespaceLike {
74
+ put(key: string, value: string, options?: {
75
+ expirationTtl?: number;
76
+ }): Promise<void>;
77
+ get(key: string): Promise<string | null>;
78
+ delete(key: string): Promise<void>;
79
+ }
80
+ /**
81
+ * Cloudflare Workers KV-backed nonce store.
82
+ *
83
+ * `issue` uses `put` with an `expirationTtl` so nonces auto-expire.
84
+ * `consume` does a `get` + `delete` pair — not fully atomic, but acceptable
85
+ * for random 128-bit nonces where collisions are astronomically unlikely.
86
+ *
87
+ * @param kv A KV namespace binding (e.g. `env.SIWA_NONCES`).
88
+ * @param prefix Optional key prefix (default `"siwa:nonce:"`).
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // In a Cloudflare Worker
93
+ * export default {
94
+ * async fetch(request, env) {
95
+ * const nonceStore = createKVSIWANonceStore(env.SIWA_NONCES);
96
+ * // use with createSIWANonce / verifySIWA
97
+ * },
98
+ * };
99
+ * ```
100
+ */
101
+ export declare function createKVSIWANonceStore(kv: KVNamespaceLike, prefix?: string): SIWANonceStore;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * nonce-store.ts
3
+ *
4
+ * Pluggable nonce store for SIWA sign-in replay protection.
5
+ *
6
+ * The default HMAC-based stateless nonces are convenient but can be replayed
7
+ * within their TTL window. A SIWANonceStore tracks issued nonces server-side
8
+ * so each nonce can only be consumed once.
9
+ *
10
+ * Built-in factories:
11
+ * - createMemorySIWANonceStore() — single-process (Map + TTL)
12
+ * - createRedisSIWANonceStore(redis) — ioredis / node-redis
13
+ * - createKVSIWANonceStore(kv) — Cloudflare Workers KV
14
+ *
15
+ * For databases (SQL, Prisma, Drizzle), implement the SIWANonceStore interface
16
+ * directly — it's just two methods.
17
+ *
18
+ * No new runtime dependencies — each factory accepts a minimal interface so
19
+ * users bring their own client.
20
+ */
21
+ /**
22
+ * In-memory nonce store with TTL-based expiry.
23
+ *
24
+ * Suitable for single-process servers. For multi-instance deployments,
25
+ * implement SIWANonceStore with a shared store (Redis, database, etc.).
26
+ */
27
+ export function createMemorySIWANonceStore() {
28
+ const nonces = new Map(); // nonce → expiry timestamp (ms)
29
+ function cleanup() {
30
+ const now = Date.now();
31
+ for (const [k, expiry] of nonces) {
32
+ if (expiry < now)
33
+ nonces.delete(k);
34
+ }
35
+ }
36
+ return {
37
+ async issue(nonce, ttlMs) {
38
+ cleanup();
39
+ if (nonces.has(nonce))
40
+ return false;
41
+ nonces.set(nonce, Date.now() + ttlMs);
42
+ return true;
43
+ },
44
+ async consume(nonce) {
45
+ cleanup();
46
+ if (!nonces.has(nonce))
47
+ return false;
48
+ const expiry = nonces.get(nonce);
49
+ nonces.delete(nonce);
50
+ return expiry >= Date.now();
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * Redis-backed nonce store.
56
+ *
57
+ * Uses `SET key 1 PX ttl NX` for atomic issue (fails if key exists) and
58
+ * `DEL key` for atomic consume (returns 1 only on first delete).
59
+ *
60
+ * @param redis An ioredis instance or any client matching `RedisLikeClient`.
61
+ * @param prefix Optional key prefix (default `"siwa:nonce:"`).
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // ioredis
66
+ * import Redis from "ioredis";
67
+ * const redis = new Redis();
68
+ * const nonceStore = createRedisSIWANonceStore(redis);
69
+ *
70
+ * // node-redis v4 — wrap with a tiny adapter:
71
+ * import { createClient } from "redis";
72
+ * const client = createClient(); await client.connect();
73
+ * const nonceStore = createRedisSIWANonceStore({
74
+ * set: (...a: unknown[]) => client.set(a[0] as string, a[1] as string,
75
+ * { PX: a[3] as number, NX: true }).then(r => r ?? null),
76
+ * del: (k: unknown) => client.del(k as string),
77
+ * });
78
+ * ```
79
+ */
80
+ export function createRedisSIWANonceStore(redis, prefix = 'siwa:nonce:') {
81
+ return {
82
+ async issue(nonce, ttlMs) {
83
+ // SET key "1" PX ttlMs NX → "OK" if the key was set, null otherwise
84
+ const result = await redis.set(prefix + nonce, '1', 'PX', ttlMs, 'NX');
85
+ return result === 'OK';
86
+ },
87
+ async consume(nonce) {
88
+ // DEL returns the number of keys removed (1 = existed, 0 = didn't)
89
+ const deleted = await redis.del(prefix + nonce);
90
+ return deleted === 1;
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * Cloudflare Workers KV-backed nonce store.
96
+ *
97
+ * `issue` uses `put` with an `expirationTtl` so nonces auto-expire.
98
+ * `consume` does a `get` + `delete` pair — not fully atomic, but acceptable
99
+ * for random 128-bit nonces where collisions are astronomically unlikely.
100
+ *
101
+ * @param kv A KV namespace binding (e.g. `env.SIWA_NONCES`).
102
+ * @param prefix Optional key prefix (default `"siwa:nonce:"`).
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // In a Cloudflare Worker
107
+ * export default {
108
+ * async fetch(request, env) {
109
+ * const nonceStore = createKVSIWANonceStore(env.SIWA_NONCES);
110
+ * // use with createSIWANonce / verifySIWA
111
+ * },
112
+ * };
113
+ * ```
114
+ */
115
+ export function createKVSIWANonceStore(kv, prefix = 'siwa:nonce:') {
116
+ return {
117
+ async issue(nonce, ttlMs) {
118
+ const key = prefix + nonce;
119
+ // Check-before-write: KV put is unconditional, so read first
120
+ const existing = await kv.get(key);
121
+ if (existing !== null)
122
+ return false;
123
+ await kv.put(key, '1', { expirationTtl: Math.ceil(ttlMs / 1000) });
124
+ return true;
125
+ },
126
+ async consume(nonce) {
127
+ const key = prefix + nonce;
128
+ const value = await kv.get(key);
129
+ if (value === null)
130
+ return false;
131
+ await kv.delete(key);
132
+ return true;
133
+ },
134
+ };
135
+ }
package/dist/receipt.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * Format: base64url(json).base64url(hmac-sha256)
15
15
  * Same token format as nonce tokens in siwa.ts.
16
16
  */
17
- import type { SignerType } from './signer.js';
17
+ import type { SignerType } from './signer/index.js';
18
18
  export interface ReceiptPayload {
19
19
  address: string;
20
20
  agentId: number;
@@ -8,7 +8,7 @@
8
8
  * npm install viem
9
9
  */
10
10
  import { type PublicClient } from 'viem';
11
- import type { TransactionSigner } from './signer.js';
11
+ import type { TransactionSigner } from './signer/index.js';
12
12
  /** Service endpoint types defined in ERC-8004 */
13
13
  export type ServiceType = 'web' | 'A2A' | 'MCP' | 'OASF' | 'ENS' | 'DID' | 'email';
14
14
  /** Trust models defined in ERC-8004 */
@@ -15,8 +15,8 @@
15
15
  * ```
16
16
  */
17
17
  import type { RequestHandler } from 'express';
18
- import { type SiwaAgent, type VerifyOptions } from './erc8128.js';
19
- import type { SignerType } from './signer.js';
18
+ import { type SiwaAgent, type VerifyOptions } from '../erc8128.js';
19
+ import type { SignerType } from '../signer/index.js';
20
20
  export type { SiwaAgent };
21
21
  declare global {
22
22
  namespace Express {
@@ -15,7 +15,7 @@
15
15
  * ```
16
16
  */
17
17
  import express from 'express';
18
- import { verifyAuthenticatedRequest, expressToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
18
+ import { verifyAuthenticatedRequest, expressToFetchRequest, resolveReceiptSecret, } from '../erc8128.js';
19
19
  // ---------------------------------------------------------------------------
20
20
  // CORS middleware
21
21
  // ---------------------------------------------------------------------------
@@ -0,0 +1,58 @@
1
+ /**
2
+ * fastify.ts
3
+ *
4
+ * Server-side wrappers for Fastify applications.
5
+ * Uses preHandler hooks for authentication.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import Fastify from "fastify";
10
+ * import { siwaPlugin, siwaAuth } from "@buildersgarden/siwa/fastify";
11
+ *
12
+ * const fastify = Fastify();
13
+ * await fastify.register(siwaPlugin);
14
+ *
15
+ * fastify.post("/api/protected", { preHandler: siwaAuth() }, async (req) => {
16
+ * return { agent: req.agent };
17
+ * });
18
+ * ```
19
+ */
20
+ import type { FastifyPluginAsync, preHandlerHookHandler } from 'fastify';
21
+ import { type SiwaAgent, type VerifyOptions } from '../erc8128.js';
22
+ import type { SignerType } from '../signer/index.js';
23
+ export type { SiwaAgent };
24
+ declare module 'fastify' {
25
+ interface FastifyRequest {
26
+ agent?: SiwaAgent;
27
+ }
28
+ }
29
+ export interface SiwaAuthOptions {
30
+ /** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
31
+ receiptSecret?: string;
32
+ /** RPC URL for optional onchain verification. */
33
+ rpcUrl?: string;
34
+ /** Enable onchain ownerOf check. */
35
+ verifyOnchain?: boolean;
36
+ /** Public client for ERC-1271 or onchain checks. */
37
+ publicClient?: VerifyOptions['publicClient'];
38
+ /** Allowed signer types. Omit to accept all. */
39
+ allowedSignerTypes?: SignerType[];
40
+ }
41
+ export interface SiwaPluginOptions {
42
+ /** CORS allowed origin(s). Defaults to true (reflect origin). */
43
+ origin?: boolean | string | string[];
44
+ /** Allowed headers including SIWA-specific ones. */
45
+ allowedHeaders?: string[];
46
+ }
47
+ /**
48
+ * Fastify plugin that sets up CORS with SIWA-specific headers.
49
+ * Requires @fastify/cors to be installed.
50
+ */
51
+ export declare const siwaPlugin: FastifyPluginAsync<SiwaPluginOptions>;
52
+ /**
53
+ * Fastify preHandler that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
54
+ *
55
+ * On success, sets `req.agent` with the verified agent identity.
56
+ * On failure, responds with 401.
57
+ */
58
+ export declare function siwaAuth(options?: SiwaAuthOptions): preHandlerHookHandler;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * fastify.ts
3
+ *
4
+ * Server-side wrappers for Fastify applications.
5
+ * Uses preHandler hooks for authentication.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import Fastify from "fastify";
10
+ * import { siwaPlugin, siwaAuth } from "@buildersgarden/siwa/fastify";
11
+ *
12
+ * const fastify = Fastify();
13
+ * await fastify.register(siwaPlugin);
14
+ *
15
+ * fastify.post("/api/protected", { preHandler: siwaAuth() }, async (req) => {
16
+ * return { agent: req.agent };
17
+ * });
18
+ * ```
19
+ */
20
+ import { verifyAuthenticatedRequest, resolveReceiptSecret, } from '../erc8128.js';
21
+ // ---------------------------------------------------------------------------
22
+ // CORS headers
23
+ // ---------------------------------------------------------------------------
24
+ const DEFAULT_SIWA_HEADERS = [
25
+ 'Content-Type',
26
+ 'X-SIWA-Receipt',
27
+ 'Signature',
28
+ 'Signature-Input',
29
+ 'Content-Digest',
30
+ ];
31
+ // ---------------------------------------------------------------------------
32
+ // Request conversion helper
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Convert a Fastify request to a Fetch Request for verification.
36
+ */
37
+ function toFetchRequest(req) {
38
+ const url = `${req.protocol}://${req.hostname}${req.url}`;
39
+ return new Request(url, {
40
+ method: req.method,
41
+ headers: req.headers,
42
+ body: req.method !== 'GET' && req.method !== 'HEAD'
43
+ ? JSON.stringify(req.body)
44
+ : undefined,
45
+ });
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Fastify plugin
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * Fastify plugin that sets up CORS with SIWA-specific headers.
52
+ * Requires @fastify/cors to be installed.
53
+ */
54
+ export const siwaPlugin = async (fastify, options) => {
55
+ // Try to register @fastify/cors if available
56
+ try {
57
+ const cors = await import('@fastify/cors');
58
+ await fastify.register(cors.default ?? cors, {
59
+ origin: options?.origin ?? true,
60
+ allowedHeaders: options?.allowedHeaders ?? DEFAULT_SIWA_HEADERS,
61
+ });
62
+ }
63
+ catch {
64
+ // @fastify/cors not installed, set headers manually
65
+ fastify.addHook('onSend', async (req, reply) => {
66
+ reply.header('Access-Control-Allow-Origin', '*');
67
+ reply.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
68
+ reply.header('Access-Control-Allow-Headers', (options?.allowedHeaders ?? DEFAULT_SIWA_HEADERS).join(', '));
69
+ });
70
+ // Handle OPTIONS preflight
71
+ fastify.options('*', async (req, reply) => {
72
+ reply.status(204).send();
73
+ });
74
+ }
75
+ };
76
+ // ---------------------------------------------------------------------------
77
+ // Auth preHandler
78
+ // ---------------------------------------------------------------------------
79
+ /**
80
+ * Fastify preHandler that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
81
+ *
82
+ * On success, sets `req.agent` with the verified agent identity.
83
+ * On failure, responds with 401.
84
+ */
85
+ export function siwaAuth(options) {
86
+ return async (req, reply) => {
87
+ const hasSignature = req.headers['signature'] && req.headers['x-siwa-receipt'];
88
+ if (!hasSignature) {
89
+ return reply.status(401).send({
90
+ error: 'Unauthorized — provide ERC-8128 Signature + X-SIWA-Receipt headers',
91
+ });
92
+ }
93
+ try {
94
+ const secret = resolveReceiptSecret(options?.receiptSecret);
95
+ const result = await verifyAuthenticatedRequest(toFetchRequest(req), {
96
+ receiptSecret: secret,
97
+ rpcUrl: options?.rpcUrl,
98
+ verifyOnchain: options?.verifyOnchain,
99
+ publicClient: options?.publicClient,
100
+ allowedSignerTypes: options?.allowedSignerTypes,
101
+ });
102
+ if (!result.valid) {
103
+ return reply.status(401).send({ error: result.error });
104
+ }
105
+ req.agent = result.agent;
106
+ }
107
+ catch (err) {
108
+ return reply.status(401).send({ error: `ERC-8128 auth failed: ${err.message}` });
109
+ }
110
+ };
111
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * hono.ts
3
+ *
4
+ * Server-side wrappers for Hono applications.
5
+ * Uses web standard Request/Response APIs.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { Hono } from "hono";
10
+ * import { siwaMiddleware, siwaCors } from "@buildersgarden/siwa/hono";
11
+ *
12
+ * const app = new Hono();
13
+ * app.use("*", siwaCors());
14
+ * app.post("/api/protected", siwaMiddleware(), (c) => {
15
+ * return c.json({ agent: c.get("agent") });
16
+ * });
17
+ * ```
18
+ */
19
+ import type { MiddlewareHandler } from 'hono';
20
+ import { type SiwaAgent, type VerifyOptions } from '../erc8128.js';
21
+ import type { SignerType } from '../signer/index.js';
22
+ export type { SiwaAgent };
23
+ export interface SiwaMiddlewareOptions {
24
+ /** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
25
+ receiptSecret?: string;
26
+ /** RPC URL for optional onchain verification. */
27
+ rpcUrl?: string;
28
+ /** Enable onchain ownerOf check. */
29
+ verifyOnchain?: boolean;
30
+ /** Public client for ERC-1271 or onchain checks. */
31
+ publicClient?: VerifyOptions['publicClient'];
32
+ /** Allowed signer types. Omit to accept all. */
33
+ allowedSignerTypes?: SignerType[];
34
+ }
35
+ export interface SiwaCorsOptions {
36
+ /** Allowed origin(s). Defaults to "*". */
37
+ origin?: string;
38
+ /** Allowed HTTP methods. */
39
+ methods?: string[];
40
+ /** Allowed headers. */
41
+ headers?: string[];
42
+ }
43
+ /**
44
+ * CORS middleware pre-configured with SIWA-specific headers.
45
+ * Handles OPTIONS preflight automatically.
46
+ */
47
+ export declare function siwaCors(options?: SiwaCorsOptions): MiddlewareHandler;
48
+ /**
49
+ * Hono middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
50
+ *
51
+ * On success, sets `c.set("agent", agent)` with the verified agent identity.
52
+ * On failure, responds with 401.
53
+ */
54
+ export declare function siwaMiddleware(options?: SiwaMiddlewareOptions): MiddlewareHandler;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * hono.ts
3
+ *
4
+ * Server-side wrappers for Hono applications.
5
+ * Uses web standard Request/Response APIs.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { Hono } from "hono";
10
+ * import { siwaMiddleware, siwaCors } from "@buildersgarden/siwa/hono";
11
+ *
12
+ * const app = new Hono();
13
+ * app.use("*", siwaCors());
14
+ * app.post("/api/protected", siwaMiddleware(), (c) => {
15
+ * return c.json({ agent: c.get("agent") });
16
+ * });
17
+ * ```
18
+ */
19
+ import { verifyAuthenticatedRequest, resolveReceiptSecret, } from '../erc8128.js';
20
+ // ---------------------------------------------------------------------------
21
+ // CORS middleware
22
+ // ---------------------------------------------------------------------------
23
+ const DEFAULT_SIWA_HEADERS = [
24
+ 'Content-Type',
25
+ 'X-SIWA-Receipt',
26
+ 'Signature',
27
+ 'Signature-Input',
28
+ 'Content-Digest',
29
+ ];
30
+ /**
31
+ * CORS middleware pre-configured with SIWA-specific headers.
32
+ * Handles OPTIONS preflight automatically.
33
+ */
34
+ export function siwaCors(options) {
35
+ const origin = options?.origin ?? '*';
36
+ const methods = options?.methods ?? ['GET', 'POST', 'OPTIONS'];
37
+ const headers = options?.headers ?? DEFAULT_SIWA_HEADERS;
38
+ return async (c, next) => {
39
+ c.header('Access-Control-Allow-Origin', origin);
40
+ c.header('Access-Control-Allow-Methods', methods.join(', '));
41
+ c.header('Access-Control-Allow-Headers', headers.join(', '));
42
+ if (c.req.method === 'OPTIONS') {
43
+ return c.body(null, 204);
44
+ }
45
+ await next();
46
+ };
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Auth middleware
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Hono middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
53
+ *
54
+ * On success, sets `c.set("agent", agent)` with the verified agent identity.
55
+ * On failure, responds with 401.
56
+ */
57
+ export function siwaMiddleware(options) {
58
+ return async (c, next) => {
59
+ const hasSignature = c.req.header('signature') && c.req.header('x-siwa-receipt');
60
+ if (!hasSignature) {
61
+ return c.json({ error: 'Unauthorized — provide ERC-8128 Signature + X-SIWA-Receipt headers' }, 401);
62
+ }
63
+ try {
64
+ const secret = resolveReceiptSecret(options?.receiptSecret);
65
+ const result = await verifyAuthenticatedRequest(c.req.raw, {
66
+ receiptSecret: secret,
67
+ rpcUrl: options?.rpcUrl,
68
+ verifyOnchain: options?.verifyOnchain,
69
+ publicClient: options?.publicClient,
70
+ allowedSignerTypes: options?.allowedSignerTypes,
71
+ });
72
+ if (!result.valid) {
73
+ return c.json({ error: result.error }, 401);
74
+ }
75
+ c.set('agent', result.agent);
76
+ await next();
77
+ }
78
+ catch (err) {
79
+ return c.json({ error: `ERC-8128 auth failed: ${err.message}` }, 401);
80
+ }
81
+ };
82
+ }
@@ -16,8 +16,8 @@
16
16
  * export { siwaOptions as OPTIONS };
17
17
  * ```
18
18
  */
19
- import { type SiwaAgent } from './erc8128.js';
20
- import type { SignerType } from './signer.js';
19
+ import { type SiwaAgent } from '../erc8128.js';
20
+ import type { SignerType } from '../signer/index.js';
21
21
  export type { SiwaAgent };
22
22
  export interface WithSiwaOptions {
23
23
  /** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
@@ -16,7 +16,7 @@
16
16
  * export { siwaOptions as OPTIONS };
17
17
  * ```
18
18
  */
19
- import { verifyAuthenticatedRequest, nextjsToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
19
+ import { verifyAuthenticatedRequest, nextjsToFetchRequest, resolveReceiptSecret, } from '../erc8128.js';
20
20
  // ---------------------------------------------------------------------------
21
21
  // CORS helpers
22
22
  // ---------------------------------------------------------------------------