@commonpub/layer 0.21.13 → 0.21.14
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/package.json +8 -8
- package/server/utils/inbox.ts +35 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"vue": "^3.4.0",
|
|
51
51
|
"vue-router": "^4.3.0",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
+
"@commonpub/explainer": "0.7.15",
|
|
54
|
+
"@commonpub/auth": "0.6.0",
|
|
53
55
|
"@commonpub/config": "0.13.0",
|
|
56
|
+
"@commonpub/editor": "0.7.10",
|
|
57
|
+
"@commonpub/server": "2.54.3",
|
|
58
|
+
"@commonpub/schema": "0.16.0",
|
|
59
|
+
"@commonpub/protocol": "0.11.0",
|
|
54
60
|
"@commonpub/docs": "0.6.3",
|
|
55
|
-
"@commonpub/auth": "0.6.0",
|
|
56
61
|
"@commonpub/learning": "0.5.2",
|
|
57
|
-
"@commonpub/
|
|
58
|
-
"@commonpub/explainer": "0.7.15",
|
|
59
|
-
"@commonpub/ui": "0.8.5",
|
|
60
|
-
"@commonpub/editor": "0.7.10",
|
|
61
|
-
"@commonpub/server": "2.54.2",
|
|
62
|
-
"@commonpub/protocol": "0.10.1"
|
|
62
|
+
"@commonpub/ui": "0.8.5"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|
package/server/utils/inbox.ts
CHANGED
|
@@ -78,28 +78,45 @@ export async function verifyInboxRequest(event: H3Event, label: string): Promise
|
|
|
78
78
|
throw createError({ statusCode: 401, statusMessage: 'Invalid actor URI' });
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// 6. Date header
|
|
81
|
+
// 6. Date header is mandatory + must be fresh (replay-window protection).
|
|
82
82
|
const dateHeader = getHeader(event, 'date');
|
|
83
|
-
if (dateHeader) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
if (!dateHeader) {
|
|
84
|
+
throw createError({ statusCode: 401, statusMessage: 'Missing Date header' });
|
|
85
|
+
}
|
|
86
|
+
const requestDate = new Date(dateHeader).getTime();
|
|
87
|
+
if (isNaN(requestDate)) {
|
|
88
|
+
throw createError({ statusCode: 401, statusMessage: 'Invalid Date header' });
|
|
89
|
+
}
|
|
90
|
+
const skew = Math.abs(Date.now() - requestDate);
|
|
91
|
+
if (skew > MAX_DATE_SKEW_MS) {
|
|
92
|
+
console.warn(`[${label}] Date header too old/new: skew=${Math.round(skew / 1000)}s from ${actorUri}`);
|
|
93
|
+
throw createError({ statusCode: 401, statusMessage: 'Request date too far from server time' });
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
// 7. Read
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
// 7. Read the RAW body once. Two uses:
|
|
97
|
+
// - Hashing for digest verification (must match the sender's digest,
|
|
98
|
+
// which was computed over the exact bytes on the wire).
|
|
99
|
+
// - JSON.parse for handler consumption.
|
|
100
|
+
// `JSON.stringify(JSON.parse(x)) !== x` in general, so we cannot
|
|
101
|
+
// rebuild the verify-Request from a re-serialized copy without
|
|
102
|
+
// breaking digest comparison. Item 6 of federation-hardening Stage 3.
|
|
103
|
+
const rawBody = await readRawBody(event, false);
|
|
104
|
+
if (!rawBody) {
|
|
105
|
+
throw createError({ statusCode: 400, statusMessage: 'Empty body' });
|
|
106
|
+
}
|
|
107
|
+
const bodyStr = typeof rawBody === 'string' ? rawBody : Buffer.from(rawBody).toString('utf-8');
|
|
97
108
|
|
|
98
|
-
// Body size check on actual content (in case Content-Length was missing/wrong)
|
|
99
109
|
if (bodyStr.length > MAX_BODY_SIZE) {
|
|
100
110
|
throw createError({ statusCode: 413, statusMessage: 'Payload too large' });
|
|
101
111
|
}
|
|
102
112
|
|
|
113
|
+
let body: Record<string, unknown>;
|
|
114
|
+
try {
|
|
115
|
+
body = JSON.parse(bodyStr);
|
|
116
|
+
} catch {
|
|
117
|
+
throw createError({ statusCode: 400, statusMessage: 'Invalid JSON body' });
|
|
118
|
+
}
|
|
119
|
+
|
|
103
120
|
const url = getRequestURL(event);
|
|
104
121
|
const headers = new Headers();
|
|
105
122
|
for (const [key, value] of Object.entries(getHeaders(event))) {
|
|
@@ -111,7 +128,10 @@ export async function verifyInboxRequest(event: H3Event, label: string): Promise
|
|
|
111
128
|
body: bodyStr,
|
|
112
129
|
});
|
|
113
130
|
|
|
114
|
-
// 8. Verify HTTP Signature cryptographically
|
|
131
|
+
// 8. Verify HTTP Signature cryptographically (also enforces the coverage
|
|
132
|
+
// policy: (request-target), host, date, and — when body is non-empty —
|
|
133
|
+
// digest MUST all be in the signed headers set; digest must match raw
|
|
134
|
+
// body SHA-256. Item 7.)
|
|
115
135
|
const signatureValid = await verifyHttpSignature(verifyRequest, actor.publicKey.publicKeyPem);
|
|
116
136
|
if (!signatureValid) {
|
|
117
137
|
console.warn(`[${label}] HTTP Signature verification failed for ${actorUri}`);
|