Skip to content

Commit 19eb9e5

Browse files
authored
fix: correct behaviour of functions in the web module to correspond to the one from the node module (#46)
1 parent 10e070d commit 19eb9e5

File tree

8 files changed

+88
-40
lines changed

8 files changed

+88
-40
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
"lint": "prettier --check '{src,test}/**/*' README.md package.json",
1111
"lint:fix": "prettier --write '{src,test}/**/*' README.md package.json",
1212
"pretest": "npm run -s lint",
13-
"test": "npm run -s test:node && npm run -s test:deno && npm run -s test:browser",
13+
"test": "npm run -s test:node && npm run -s test:web",
1414
"test:node": "jest --coverage",
15+
"test:web": "npm run test:deno && npm run test:browser",
16+
"pretest:web": "npm run -s build",
1517
"test:deno": "cd test/deno && deno test",
16-
"pretest:browser": "npm run -s build",
1718
"test:browser": "node test/browser-test.js"
1819
},
1920
"repository": "github:octokit/webhooks-methods.js",

src/node/sign.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
import { createHmac } from "crypto";
2+
import { Algorithm, SignOptions } from "../types";
23
import { VERSION } from "../version";
34

4-
export enum Algorithm {
5-
SHA1 = "sha1",
6-
SHA256 = "sha256",
7-
}
8-
9-
type SignOptions = {
10-
secret: string;
11-
algorithm?: Algorithm | "sha1" | "sha256";
12-
};
13-
145
export async function sign(
156
options: SignOptions | string,
167
payload: string

src/node/verify.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { Buffer } from "buffer";
33

44
import { sign } from "./sign";
55
import { VERSION } from "../version";
6-
7-
const getAlgorithm = (signature: string) => {
8-
return signature.startsWith("sha256=") ? "sha256" : "sha1";
9-
};
6+
import { getAlgorithm } from "../utils";
107

118
export async function verify(
129
secret: string,

src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export enum Algorithm {
2+
SHA1 = "sha1",
3+
SHA256 = "sha256",
4+
}
5+
6+
export type AlgorithmLike = Algorithm | "sha1" | "sha256";
7+
8+
export type SignOptions = {
9+
secret: string;
10+
algorithm?: AlgorithmLike;
11+
};

src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const getAlgorithm = (signature: string) => {
2+
return signature.startsWith("sha256=") ? "sha256" : "sha1";
3+
};

src/web.ts

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,7 @@
1-
const enc = new TextEncoder();
2-
3-
export async function sign(secret: string, data: string) {
4-
const signature = await crypto.subtle.sign(
5-
"HMAC",
6-
await importKey(secret),
7-
enc.encode(data)
8-
);
9-
return UInt8ArrayToHex(signature);
10-
}
1+
import { Algorithm, AlgorithmLike, SignOptions } from "./types";
2+
import { getAlgorithm } from "./utils";
113

12-
export async function verify(secret: string, data: string, signature: string) {
13-
return await crypto.subtle.verify(
14-
"HMAC",
15-
await importKey(secret),
16-
hexToUInt8Array(signature),
17-
enc.encode(data)
18-
);
19-
}
4+
const enc = new TextEncoder();
205

216
function hexToUInt8Array(string: string) {
227
// convert string to pairs of 2 characters
@@ -36,16 +21,76 @@ function UInt8ArrayToHex(signature: ArrayBuffer) {
3621
.join("");
3722
}
3823

39-
async function importKey(secret: string) {
24+
function getHMACHashName(algorithm: AlgorithmLike) {
25+
return (
26+
{
27+
[Algorithm.SHA1]: "SHA-1",
28+
[Algorithm.SHA256]: "SHA-256",
29+
} as { [key in Algorithm]: string }
30+
)[algorithm];
31+
}
32+
33+
async function importKey(secret: string, algorithm: AlgorithmLike) {
34+
// ref: https://developer.mozilla.org/en-US/docs/Web/API/HmacImportParams
4035
return crypto.subtle.importKey(
4136
"raw", // raw format of the key - should be Uint8Array
4237
enc.encode(secret),
4338
{
4439
// algorithm details
4540
name: "HMAC",
46-
hash: { name: "SHA-256" },
41+
hash: { name: getHMACHashName(algorithm) },
4742
},
4843
false, // export = false
4944
["sign", "verify"] // what this key can do
5045
);
5146
}
47+
48+
export async function sign(options: SignOptions | string, payload: string) {
49+
const { secret, algorithm } =
50+
typeof options === "object"
51+
? {
52+
secret: options.secret,
53+
algorithm: options.algorithm || Algorithm.SHA256,
54+
}
55+
: { secret: options, algorithm: Algorithm.SHA256 };
56+
57+
if (!secret || !payload) {
58+
throw new TypeError(
59+
"[@octokit/webhooks-methods] secret & payload required for sign()"
60+
);
61+
}
62+
63+
if (!Object.values(Algorithm).includes(algorithm as Algorithm)) {
64+
throw new TypeError(
65+
`[@octokit/webhooks] Algorithm ${algorithm} is not supported. Must be 'sha1' or 'sha256'`
66+
);
67+
}
68+
69+
const signature = await crypto.subtle.sign(
70+
"HMAC",
71+
await importKey(secret, algorithm),
72+
enc.encode(payload)
73+
);
74+
75+
return `${algorithm}=${UInt8ArrayToHex(signature)}`;
76+
}
77+
78+
export async function verify(
79+
secret: string,
80+
eventPayload: string,
81+
signature: string
82+
) {
83+
if (!secret || !eventPayload || !signature) {
84+
throw new TypeError(
85+
"[@octokit/webhooks-methods] secret, eventPayload & signature required"
86+
);
87+
}
88+
89+
const algorithm = getAlgorithm(signature);
90+
return await crypto.subtle.verify(
91+
"HMAC",
92+
await importKey(secret, algorithm),
93+
hexToUInt8Array(signature.replace(`${algorithm}=`, "")),
94+
enc.encode(eventPayload)
95+
);
96+
}

test/browser-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async function runTests() {
2727

2828
strictEqual(
2929
signature,
30-
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"
30+
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db"
3131
);
3232
strictEqual(verified, true);
3333

test/deno/web.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { sign, verify } from "../../src/web.ts";
1+
import { sign, verify } from "../../pkg/dist-web/index.js";
22

33
import { assertEquals } from "std/testing/asserts.ts";
44

55
Deno.test("sign", async () => {
66
const actual = await sign("secret", "data");
77
const expected =
8-
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
8+
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
99
assertEquals(actual, expected);
1010
});
1111

1212
Deno.test("verify", async () => {
1313
const signature =
14-
"1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
14+
"sha256=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
1515
const actual = await verify("secret", "data", signature);
1616
const expected = true;
1717
assertEquals(actual, expected);

0 commit comments

Comments
 (0)