Skip to content

Commit 7a56d1b

Browse files
authored
feat: initial version (#1)
1 parent f3d848c commit 7a56d1b

9 files changed

+2011
-2
lines changed

.github/workflows/release.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Release
2+
"on":
3+
push:
4+
branches:
5+
- main
6+
7+
# These are recommended by the semantic-release docs: https://github.com/semantic-release/npm#npm-provenance
8+
permissions:
9+
contents: write # to be able to publish a GitHub release
10+
issues: write # to be able to comment on released issues
11+
pull-requests: write # to be able to comment on released pull requests
12+
id-token: write # to enable use of OIDC for npm provenance
13+
14+
jobs:
15+
release:
16+
name: release
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: lts/*
23+
- run: npm ci
24+
- run: npm run build
25+
- run: npx semantic-release
26+
env:
27+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/test.yml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Test
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
types: [opened, synchronize]
8+
9+
jobs:
10+
test_matrix:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node_version:
15+
- 20
16+
- 22
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Use Node.js ${{ matrix.node_version }}
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node_version }}
24+
cache: npm
25+
- run: npm ci
26+
- run: npm run test:code
27+
28+
test:
29+
runs-on: ubuntu-latest
30+
needs: test_matrix
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: actions/setup-node@v4
34+
with:
35+
node-version: ${{ matrix.node_version }}
36+
cache: npm
37+
- run: npm ci
38+
- run: npm run test:tsc
39+
- run: npm run test:types

README.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
# 🚧 Work in progress - see [#1](https://github.com/copilot-extensions/preview-sdk.js/pull/1)
2-
31
# `@copilot-extensions/preview-sdk`
42

3+
> SDK for building GitHub Copilot Extensions
4+
5+
⚠️ **This SDK is a preview and subjetct to change**. We will however adhere to [semantic versioning](https://semver.org/), so it's save to use for early experimentation. Just beware there will be breaking changes. Best to watch this repository's releases for updates.
6+
7+
## Usage
8+
9+
### `verify(rawBody, signature, keyId, options)`
10+
11+
```js
12+
import { verify } from "@copilot-extensions/preview-sdk";
13+
14+
const payloadIsVerified = await verify(request.body, signature, keyId, {
15+
token,
16+
});
17+
// true or false
18+
```
19+
520
## Contributing
621

722
Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)

index.d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { request } from "@octokit/request";
2+
3+
type RequestInterface = typeof request;
4+
type RequestOptions = {
5+
request?: RequestInterface;
6+
token?: string;
7+
};
8+
9+
interface VerifyInterface {
10+
(
11+
rawBody: string,
12+
signature: string,
13+
keyId: string,
14+
requestOptions?: RequestOptions,
15+
): Promise<boolean>;
16+
}
17+
18+
export declare const verify: VerifyInterface;

index.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @ts-check
2+
3+
import { createVerify } from "node:crypto";
4+
5+
import { request as defaultRequest } from "@octokit/request";
6+
import { RequestError } from "@octokit/request-error";
7+
8+
/** @type {import('.').VerifyInterface} */
9+
export async function verify(
10+
rawBody,
11+
signature,
12+
keyId,
13+
{ token = "", request = defaultRequest } = { request: defaultRequest },
14+
) {
15+
// verify arguments
16+
assertValidString(rawBody, "Invalid payload");
17+
assertValidString(signature, "Invalid signature");
18+
assertValidString(keyId, "Invalid keyId");
19+
20+
// receive valid public keys from GitHub
21+
const requestOptions = request.endpoint("GET /meta/public_keys/copilot_api", {
22+
headers: token
23+
? {
24+
Authorization: `token ${token}`,
25+
}
26+
: {},
27+
});
28+
const response = await request(requestOptions);
29+
const { data: keys } = response;
30+
31+
// verify provided key Id
32+
const publicKey = keys.public_keys.find(
33+
(key) => key.key_identifier === keyId,
34+
);
35+
if (!publicKey) {
36+
throw new RequestError(
37+
"[@copilot-extensions/preview-sdk] No public key found matching key identifier",
38+
404,
39+
{
40+
request: requestOptions,
41+
response,
42+
},
43+
);
44+
}
45+
46+
const verify = createVerify("SHA256").update(rawBody);
47+
48+
// verify signature
49+
return verify.verify(publicKey.key, signature, "base64");
50+
}
51+
52+
function assertValidString(value, message) {
53+
if (typeof value !== "string" || value.length === 0) {
54+
throw new Error(`[@copilot-extensions/preview-sdk] ${message}`);
55+
}
56+
}

index.test-d.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { expectType } from "tsd";
2+
import { request } from "@octokit/request";
3+
4+
import { verify } from "./index.js";
5+
6+
const rawBody = "";
7+
const signature = "";
8+
const keyId = "";
9+
const token = "";
10+
11+
export async function verifyTest() {
12+
const result = await verify(rawBody, signature, keyId);
13+
expectType<boolean>(result);
14+
15+
// @ts-expect-error - first 3 arguments are required
16+
verify(rawBody, signature);
17+
18+
// @ts-expect-error - rawBody must be a string
19+
await verify(1, signature, keyId);
20+
21+
// @ts-expect-error - signature must be a string
22+
await verify(rawBody, 1, keyId);
23+
24+
// @ts-expect-error - keyId must be a string
25+
await verify(rawBody, signature, 1);
26+
27+
// accepts a token argument
28+
await verify(rawBody, signature, keyId, { token });
29+
30+
// accepts a request argument
31+
await verify(rawBody, signature, keyId, { request });
32+
}

0 commit comments

Comments
 (0)