MCPcopy Index your code
hub / github.com/zenstackhq/zenstack / createSignatureMiddleware

Function createSignatureMiddleware

packages/cli/src/actions/proxy.ts:315–384  ·  view source on GitHub ↗

* Creates an Express middleware that verifies the ed25519 signature on every request. * * The signature header format is: `t= ,v1= ` * * The signed message is constructed as: * - GET requests: ` [ ]` * - Oth

(publicKey: string, toleranceSeconds: number)

Source from the content-addressed store, hash-verified

313 * `authorizationToken` is the bearer token value from the `Authorization` header (if present).
314 */
315function createSignatureMiddleware(publicKey: string, toleranceSeconds: number) {
316 // Throttle invalid-signature warnings to at most once per 60 seconds.
317 let lastInvalidSigWarnAt = 0;
318 const WARN_THROTTLE_SECS = 60;
319
320 function warnInvalidSignature() {
321 const now = Math.floor(Date.now() / 1000);
322 if (now - lastInvalidSigWarnAt >= WARN_THROTTLE_SECS) {
323 lastInvalidSigWarnAt = now;
324 console.warn(
325 colors.yellow(
326 'Warning: Received a request with an invalid signature. ' +
327 'Please double-check whether you have the correct public API key configured.',
328 ),
329 );
330 }
331 }
332
333 return (req: express.Request, res: express.Response, next: express.NextFunction) => {
334 const signatureHeader = req.headers['x-zenstack-signature'];
335 if (!signatureHeader || typeof signatureHeader !== 'string') {
336 return rejectAuth(res, 'MISSING_SIGNATURE_HEADER');
337 }
338
339 const parts = signatureHeader.split(',');
340 const timestampPart = parts.find((p) => p.startsWith('t='));
341 const sigPart = parts.find((p) => p.startsWith('v1='));
342 if (!timestampPart || !sigPart) {
343 return rejectAuth(res, 'INVALID_SIGNATURE_FORMAT');
344 }
345 const timestamp = timestampPart.substring(2);
346 const sig = sigPart.substring(3);
347
348 // Replay-attack prevention: reject requests whose timestamp deviates
349 // from server time by more than the configured tolerance.
350 const requestTime = parseInt(timestamp, 10);
351 const now = Math.floor(Date.now() / 1000);
352 if (isNaN(requestTime) || Math.abs(now - requestTime) > toleranceSeconds) {
353 return rejectAuth(res, 'INVALID_TIMESTAMP');
354 }
355
356 // Payload: raw query string for GET/DELETE, raw body for other methods.
357 let payload: string;
358 if (req.method === 'GET' || req.method === 'DELETE') {
359 const qMark = req.originalUrl.indexOf('?');
360 payload = qMark >= 0 ? req.originalUrl.substring(qMark + 1) : '';
361 } else {
362 payload = (req as express.Request & { rawBody?: string }).rawBody ?? '';
363 }
364
365 // authorizationToken is the bearer token value (if present).
366 const authHeader = req.headers['authorization'];
367 const authorizationToken = authHeader && authHeader.startsWith('Bearer ') ? authHeader.substring(7) : undefined;
368
369 const message = authorizationToken ? `${payload}${timestamp}${authorizationToken}` : `${payload}${timestamp}`;
370
371 try {
372 const isValid = verify(null, Buffer.from(message, 'utf8'), publicKey, Buffer.from(sig, 'base64url'));

Callers 1

createProxyAppFunction · 0.85

Calls 3

rejectAuthFunction · 0.85
warnInvalidSignatureFunction · 0.85
findMethod · 0.80

Tested by

no test coverage detected