@buildersgarden/siwa 0.0.9 → 0.0.10
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 +14 -6
- package/dist/erc8128.js +12 -0
- package/dist/express.d.ts +61 -0
- package/dist/express.js +93 -0
- package/dist/next.d.ts +46 -0
- package/dist/next.js +83 -0
- package/package.json +18 -1
package/dist/erc8128.d.ts
CHANGED
|
@@ -18,18 +18,26 @@ export interface VerifyOptions {
|
|
|
18
18
|
verifyOnchain?: boolean;
|
|
19
19
|
publicClient?: PublicClient;
|
|
20
20
|
}
|
|
21
|
+
/** Verified agent identity returned from a successful auth check. */
|
|
22
|
+
export interface SiwaAgent {
|
|
23
|
+
address: string;
|
|
24
|
+
agentId: number;
|
|
25
|
+
agentRegistry: string;
|
|
26
|
+
chainId: number;
|
|
27
|
+
}
|
|
21
28
|
export type AuthResult = {
|
|
22
29
|
valid: true;
|
|
23
|
-
agent:
|
|
24
|
-
address: string;
|
|
25
|
-
agentId: number;
|
|
26
|
-
agentRegistry: string;
|
|
27
|
-
chainId: number;
|
|
28
|
-
};
|
|
30
|
+
agent: SiwaAgent;
|
|
29
31
|
} | {
|
|
30
32
|
valid: false;
|
|
31
33
|
error: string;
|
|
32
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the receipt secret from an explicit value or environment variables.
|
|
37
|
+
*
|
|
38
|
+
* Checks (in order): `explicit` → `RECEIPT_SECRET` env → `SIWA_SECRET` env → throws.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveReceiptSecret(explicit?: string): string;
|
|
33
41
|
/** Header name for the verification receipt */
|
|
34
42
|
export declare const RECEIPT_HEADER = "X-SIWA-Receipt";
|
|
35
43
|
/**
|
package/dist/erc8128.js
CHANGED
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
import { signRequest, verifyRequest, } from '@slicekit/erc8128';
|
|
13
13
|
import { signRawMessage, getAddress } from './keystore.js';
|
|
14
14
|
import { verifyReceipt } from './receipt.js';
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the receipt secret from an explicit value or environment variables.
|
|
17
|
+
*
|
|
18
|
+
* Checks (in order): `explicit` → `RECEIPT_SECRET` env → `SIWA_SECRET` env → throws.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveReceiptSecret(explicit) {
|
|
21
|
+
const secret = explicit || process.env.RECEIPT_SECRET || process.env.SIWA_SECRET;
|
|
22
|
+
if (!secret) {
|
|
23
|
+
throw new Error('Missing receipt secret: pass receiptSecret option, or set RECEIPT_SECRET / SIWA_SECRET env var');
|
|
24
|
+
}
|
|
25
|
+
return secret;
|
|
26
|
+
}
|
|
15
27
|
/** Header name for the verification receipt */
|
|
16
28
|
export const RECEIPT_HEADER = 'X-SIWA-Receipt';
|
|
17
29
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* express.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side wrappers for Express applications.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express";
|
|
9
|
+
*
|
|
10
|
+
* app.use(siwaJsonParser());
|
|
11
|
+
* app.use(siwaCors());
|
|
12
|
+
* app.get('/api/protected', siwaMiddleware(), (req, res) => {
|
|
13
|
+
* res.json({ agent: req.agent });
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { RequestHandler } from 'express';
|
|
18
|
+
import { type SiwaAgent, type VerifyOptions } from './erc8128.js';
|
|
19
|
+
export type { SiwaAgent };
|
|
20
|
+
declare global {
|
|
21
|
+
namespace Express {
|
|
22
|
+
interface Request {
|
|
23
|
+
agent?: SiwaAgent;
|
|
24
|
+
rawBody?: string;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export interface SiwaMiddlewareOptions {
|
|
29
|
+
/** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
|
|
30
|
+
receiptSecret?: string;
|
|
31
|
+
/** RPC URL for optional onchain verification. */
|
|
32
|
+
rpcUrl?: string;
|
|
33
|
+
/** Enable onchain ownerOf check. */
|
|
34
|
+
verifyOnchain?: boolean;
|
|
35
|
+
/** Public client for ERC-1271 or onchain checks. */
|
|
36
|
+
publicClient?: VerifyOptions['publicClient'];
|
|
37
|
+
}
|
|
38
|
+
export interface SiwaCorsOptions {
|
|
39
|
+
/** Allowed origin(s). Defaults to "*". */
|
|
40
|
+
origin?: string;
|
|
41
|
+
/** Allowed HTTP methods. */
|
|
42
|
+
methods?: string;
|
|
43
|
+
/** Allowed headers. */
|
|
44
|
+
headers?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* CORS middleware pre-configured with SIWA-specific headers.
|
|
48
|
+
* Handles OPTIONS preflight automatically.
|
|
49
|
+
*/
|
|
50
|
+
export declare function siwaCors(options?: SiwaCorsOptions): RequestHandler;
|
|
51
|
+
/**
|
|
52
|
+
* `express.json()` pre-configured with rawBody capture for Content-Digest verification.
|
|
53
|
+
*/
|
|
54
|
+
export declare function siwaJsonParser(): RequestHandler;
|
|
55
|
+
/**
|
|
56
|
+
* Express middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
|
|
57
|
+
*
|
|
58
|
+
* On success, sets `req.agent` with the verified agent identity.
|
|
59
|
+
* On failure, responds with 401.
|
|
60
|
+
*/
|
|
61
|
+
export declare function siwaMiddleware(options?: SiwaMiddlewareOptions): RequestHandler;
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* express.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side wrappers for Express applications.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express";
|
|
9
|
+
*
|
|
10
|
+
* app.use(siwaJsonParser());
|
|
11
|
+
* app.use(siwaCors());
|
|
12
|
+
* app.get('/api/protected', siwaMiddleware(), (req, res) => {
|
|
13
|
+
* res.json({ agent: req.agent });
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import express from 'express';
|
|
18
|
+
import { verifyAuthenticatedRequest, expressToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// CORS middleware
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const DEFAULT_SIWA_HEADERS = 'Content-Type, X-SIWA-Receipt, Signature, Signature-Input, Content-Digest';
|
|
23
|
+
/**
|
|
24
|
+
* CORS middleware pre-configured with SIWA-specific headers.
|
|
25
|
+
* Handles OPTIONS preflight automatically.
|
|
26
|
+
*/
|
|
27
|
+
export function siwaCors(options) {
|
|
28
|
+
const origin = options?.origin ?? '*';
|
|
29
|
+
const methods = options?.methods ?? 'GET, POST, OPTIONS';
|
|
30
|
+
const headers = options?.headers ?? DEFAULT_SIWA_HEADERS;
|
|
31
|
+
return (req, res, next) => {
|
|
32
|
+
res.header('Access-Control-Allow-Origin', origin);
|
|
33
|
+
res.header('Access-Control-Allow-Methods', methods);
|
|
34
|
+
res.header('Access-Control-Allow-Headers', headers);
|
|
35
|
+
if (req.method === 'OPTIONS') {
|
|
36
|
+
res.sendStatus(204);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
next();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// JSON parser with rawBody capture
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* `express.json()` pre-configured with rawBody capture for Content-Digest verification.
|
|
47
|
+
*/
|
|
48
|
+
export function siwaJsonParser() {
|
|
49
|
+
return express.json({
|
|
50
|
+
verify: (req, _res, buf) => {
|
|
51
|
+
req.rawBody = buf.toString();
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Auth middleware
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* Express middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
|
|
60
|
+
*
|
|
61
|
+
* On success, sets `req.agent` with the verified agent identity.
|
|
62
|
+
* On failure, responds with 401.
|
|
63
|
+
*/
|
|
64
|
+
export function siwaMiddleware(options) {
|
|
65
|
+
return async (req, res, next) => {
|
|
66
|
+
const hasSignature = req.headers['signature'] && req.headers['x-siwa-receipt'];
|
|
67
|
+
if (!hasSignature) {
|
|
68
|
+
res.status(401).json({
|
|
69
|
+
error: 'Unauthorized — provide ERC-8128 Signature + X-SIWA-Receipt headers',
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const secret = resolveReceiptSecret(options?.receiptSecret);
|
|
75
|
+
const fetchReq = expressToFetchRequest(req);
|
|
76
|
+
const result = await verifyAuthenticatedRequest(fetchReq, {
|
|
77
|
+
receiptSecret: secret,
|
|
78
|
+
rpcUrl: options?.rpcUrl,
|
|
79
|
+
verifyOnchain: options?.verifyOnchain,
|
|
80
|
+
publicClient: options?.publicClient,
|
|
81
|
+
});
|
|
82
|
+
if (!result.valid) {
|
|
83
|
+
res.status(401).json({ error: result.error });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
req.agent = result.agent;
|
|
87
|
+
next();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
res.status(401).json({ error: `ERC-8128 auth failed: ${err.message}` });
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* next.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side wrappers for Next.js App Router route handlers.
|
|
5
|
+
* Uses only web standard APIs (Request, Response, Headers) — no `next` dependency needed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next";
|
|
10
|
+
*
|
|
11
|
+
* export const POST = withSiwa(async (agent, req) => {
|
|
12
|
+
* const body = await req.json();
|
|
13
|
+
* return { received: body, agent: { address: agent.address, agentId: agent.agentId } };
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* export { siwaOptions as OPTIONS };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { type SiwaAgent } from './erc8128.js';
|
|
20
|
+
export type { SiwaAgent };
|
|
21
|
+
export interface WithSiwaOptions {
|
|
22
|
+
/** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
|
|
23
|
+
receiptSecret?: string;
|
|
24
|
+
/** RPC URL for optional onchain verification. */
|
|
25
|
+
rpcUrl?: string;
|
|
26
|
+
/** Enable onchain ownerOf check. */
|
|
27
|
+
verifyOnchain?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** CORS headers required by SIWA-authenticated requests. */
|
|
30
|
+
export declare function corsHeaders(): Record<string, string>;
|
|
31
|
+
/** Return a JSON Response with CORS headers. */
|
|
32
|
+
export declare function corsJson(data: unknown, init?: {
|
|
33
|
+
status?: number;
|
|
34
|
+
}): Response;
|
|
35
|
+
/** Return a 204 OPTIONS response with CORS headers. */
|
|
36
|
+
export declare function siwaOptions(): Response;
|
|
37
|
+
type SiwaHandler = (agent: SiwaAgent, req: Request) => Promise<Record<string, unknown> | Response> | Record<string, unknown> | Response;
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a Next.js route handler with SIWA ERC-8128 authentication.
|
|
40
|
+
*
|
|
41
|
+
* - Clones POST/PUT/PATCH requests so the body is available after verification
|
|
42
|
+
* - Normalizes the request URL for reverse-proxy environments
|
|
43
|
+
* - Returns 401 with CORS headers on auth failure
|
|
44
|
+
* - If the handler returns a plain object, it is auto-wrapped in a JSON Response with CORS headers
|
|
45
|
+
*/
|
|
46
|
+
export declare function withSiwa(handler: SiwaHandler, options?: WithSiwaOptions): (req: Request) => Promise<Response>;
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* next.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side wrappers for Next.js App Router route handlers.
|
|
5
|
+
* Uses only web standard APIs (Request, Response, Headers) — no `next` dependency needed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next";
|
|
10
|
+
*
|
|
11
|
+
* export const POST = withSiwa(async (agent, req) => {
|
|
12
|
+
* const body = await req.json();
|
|
13
|
+
* return { received: body, agent: { address: agent.address, agentId: agent.agentId } };
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* export { siwaOptions as OPTIONS };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { verifyAuthenticatedRequest, nextjsToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// CORS helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/** CORS headers required by SIWA-authenticated requests. */
|
|
24
|
+
export function corsHeaders() {
|
|
25
|
+
return {
|
|
26
|
+
'Access-Control-Allow-Origin': '*',
|
|
27
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
28
|
+
'Access-Control-Allow-Headers': 'Content-Type, X-SIWA-Receipt, Signature, Signature-Input, Content-Digest',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Return a JSON Response with CORS headers. */
|
|
32
|
+
export function corsJson(data, init) {
|
|
33
|
+
return new Response(JSON.stringify(data), {
|
|
34
|
+
status: init?.status ?? 200,
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
...corsHeaders(),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Return a 204 OPTIONS response with CORS headers. */
|
|
42
|
+
export function siwaOptions() {
|
|
43
|
+
return new Response(null, { status: 204, headers: corsHeaders() });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a Next.js route handler with SIWA ERC-8128 authentication.
|
|
47
|
+
*
|
|
48
|
+
* - Clones POST/PUT/PATCH requests so the body is available after verification
|
|
49
|
+
* - Normalizes the request URL for reverse-proxy environments
|
|
50
|
+
* - Returns 401 with CORS headers on auth failure
|
|
51
|
+
* - If the handler returns a plain object, it is auto-wrapped in a JSON Response with CORS headers
|
|
52
|
+
*/
|
|
53
|
+
export function withSiwa(handler, options) {
|
|
54
|
+
return async (req) => {
|
|
55
|
+
const secret = resolveReceiptSecret(options?.receiptSecret);
|
|
56
|
+
// Clone for body-consuming methods so handler can still read the body
|
|
57
|
+
const hasBody = !['GET', 'HEAD'].includes(req.method);
|
|
58
|
+
const verifyReq = hasBody ? req.clone() : req;
|
|
59
|
+
const verifyOptions = {
|
|
60
|
+
receiptSecret: secret,
|
|
61
|
+
rpcUrl: options?.rpcUrl,
|
|
62
|
+
verifyOnchain: options?.verifyOnchain,
|
|
63
|
+
};
|
|
64
|
+
const result = await verifyAuthenticatedRequest(nextjsToFetchRequest(verifyReq), verifyOptions);
|
|
65
|
+
if (!result.valid) {
|
|
66
|
+
return corsJson({ error: result.error }, { status: 401 });
|
|
67
|
+
}
|
|
68
|
+
const response = await handler(result.agent, req);
|
|
69
|
+
if (response instanceof Response) {
|
|
70
|
+
// Merge CORS headers into existing response
|
|
71
|
+
const headers = new Headers(response.headers);
|
|
72
|
+
for (const [k, v] of Object.entries(corsHeaders())) {
|
|
73
|
+
headers.set(k, v);
|
|
74
|
+
}
|
|
75
|
+
return new Response(response.body, {
|
|
76
|
+
status: response.status,
|
|
77
|
+
statusText: response.statusText,
|
|
78
|
+
headers,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return corsJson(response);
|
|
82
|
+
};
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildersgarden/siwa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
"./erc8128": {
|
|
39
39
|
"types": "./dist/erc8128.d.ts",
|
|
40
40
|
"default": "./dist/erc8128.js"
|
|
41
|
+
},
|
|
42
|
+
"./next": {
|
|
43
|
+
"types": "./dist/next.d.ts",
|
|
44
|
+
"default": "./dist/next.js"
|
|
45
|
+
},
|
|
46
|
+
"./express": {
|
|
47
|
+
"types": "./dist/express.d.ts",
|
|
48
|
+
"default": "./dist/express.js"
|
|
41
49
|
}
|
|
42
50
|
},
|
|
43
51
|
"main": "./dist/index.js",
|
|
@@ -61,7 +69,16 @@
|
|
|
61
69
|
"@slicekit/erc8128": "^0.1.0",
|
|
62
70
|
"viem": "^2.21.0"
|
|
63
71
|
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"express": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
64
80
|
"devDependencies": {
|
|
81
|
+
"@types/express": "^4.17.0",
|
|
65
82
|
"@types/node": "^25.2.1",
|
|
66
83
|
"typescript": "^5.5.0"
|
|
67
84
|
}
|