@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.21.13",
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/schema": "0.16.0",
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",
@@ -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 freshness check
81
+ // 6. Date header is mandatory + must be fresh (replay-window protection).
82
82
  const dateHeader = getHeader(event, 'date');
83
- if (dateHeader) {
84
- const requestDate = new Date(dateHeader).getTime();
85
- if (!isNaN(requestDate)) {
86
- const skew = Math.abs(Date.now() - requestDate);
87
- if (skew > MAX_DATE_SKEW_MS) {
88
- console.warn(`[${label}] Date header too old/new: skew=${Math.round(skew / 1000)}s from ${actorUri}`);
89
- throw createError({ statusCode: 401, statusMessage: 'Request date too far from server time' });
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 body and reconstruct Request for signature verification
95
- const body = await readBody(event);
96
- const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
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}`);