Skip to content

Commit

Permalink
Merge pull request #203 from upstash/fix/receiver
Browse files Browse the repository at this point in the history
DX-1388 Verify Next Signing Key
  • Loading branch information
CahidArda authored Oct 28, 2024
2 parents a51c1ac + 6d14dad commit ddd44f5
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 8 deletions.
76 changes: 76 additions & 0 deletions src/receiver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
/**
* Tests the Receiver functionality.
*/

import { nanoid } from "ai";
import { describe, test } from "bun:test";
import { SignJWT } from "jose";
import { createHash } from "node:crypto";
import { Receiver } from ".";

async function createUpstashSingature({
url,
body,
key,
}: {
url: string;
body: string;
key: string;
}) {
const payload = {
iss: "Upstash",
sub: url,
exp: Math.floor(Date.now() / 1000) + 300, // expires in 5 minutes
nbf: Math.floor(Date.now() / 1000),
iat: Math.floor(Date.now() / 1000),
jti: `jwt_${Math.random().toString(36).slice(2, 15)}`,
body: createHash("sha256").update(body).digest("base64url"),
};

const jwt = await new SignJWT(payload)
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.sign(Buffer.from(key, "utf8"));

return jwt;
}

const currentSigningKey = nanoid();
const nextSigningKey = nanoid();

const randomBody = btoa(nanoid());
const url = "example.com";

describe("receiver", () => {
test("verify signed with currentSigningKey", async () => {
const receiver = new Receiver({ currentSigningKey, nextSigningKey });

const upstashSignature = await createUpstashSingature({
url: url,
body: randomBody,
key: currentSigningKey,
});

await receiver.verify({
signature: upstashSignature,
body: randomBody,
url: url,
});
});

test("verify signed with nextSigninKey", async () => {
const receiver = new Receiver({ currentSigningKey, nextSigningKey });

const upstashSignature = await createUpstashSingature({
url: url,
body: randomBody,
key: nextSigningKey,
});

await receiver.verify({
signature: upstashSignature,
body: randomBody,
url: url,
});
});
});
21 changes: 13 additions & 8 deletions src/receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,20 @@ export class Receiver {
* If that fails, the signature is invalid and a `SignatureError` is thrown.
*/
public async verify(request: VerifyRequest): Promise<boolean> {
const isValid = await this.verifyWithKey(this.currentSigningKey, request);
if (isValid) {
return true;
let payload: jose.JWTPayload;
try {
payload = await this.verifyWithKey(this.currentSigningKey, request);
} catch {
payload = await this.verifyWithKey(this.nextSigningKey, request);
}
return this.verifyWithKey(this.nextSigningKey, request);
this.verifyBodyAndUrl(payload, request);
return true;
}

/**
* Verify signature with a specific signing key
*/
private async verifyWithKey(key: string, request: VerifyRequest): Promise<boolean> {
private async verifyWithKey(key: string, request: VerifyRequest): Promise<jose.JWTPayload> {
const jwt = await jose
.jwtVerify(request.signature, new TextEncoder().encode(key), {
issuer: "Upstash",
Expand All @@ -89,7 +92,11 @@ export class Receiver {
throw new SignatureError((error as Error).message);
});

const p = jwt.payload as {
return jwt.payload;
}

private verifyBodyAndUrl(payload: jose.JWTPayload, request: VerifyRequest) {
const p = payload as {
iss: string;
sub: string;
exp: number;
Expand All @@ -110,7 +117,5 @@ export class Receiver {
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
}

return true;
}
}

0 comments on commit ddd44f5

Please sign in to comment.