Skip to content

Commit

Permalink
feat: initial version (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
gr2m authored Aug 26, 2024
1 parent f3d848c commit 7a56d1b
Show file tree
Hide file tree
Showing 9 changed files with 2,011 additions and 2 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release
"on":
push:
branches:
- main

# These are recommended by the semantic-release docs: https://github.com/semantic-release/npm#npm-provenance
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance

jobs:
release:
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: npm ci
- run: npm run build
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Test
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize]

jobs:
test_matrix:
runs-on: ubuntu-latest
strategy:
matrix:
node_version:
- 20
- 22

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: npm
- run: npm ci
- run: npm run test:code

test:
runs-on: ubuntu-latest
needs: test_matrix
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: npm
- run: npm ci
- run: npm run test:tsc
- run: npm run test:types
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# 🚧 Work in progress - see [#1](https://github.com/copilot-extensions/preview-sdk.js/pull/1)

# `@copilot-extensions/preview-sdk`

> SDK for building GitHub Copilot Extensions
⚠️ **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.

## Usage

### `verify(rawBody, signature, keyId, options)`

```js
import { verify } from "@copilot-extensions/preview-sdk";

const payloadIsVerified = await verify(request.body, signature, keyId, {
token,
});
// true or false
```

## Contributing

Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
Expand Down
18 changes: 18 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { request } from "@octokit/request";

type RequestInterface = typeof request;
type RequestOptions = {
request?: RequestInterface;
token?: string;
};

interface VerifyInterface {
(
rawBody: string,
signature: string,
keyId: string,
requestOptions?: RequestOptions,
): Promise<boolean>;
}

export declare const verify: VerifyInterface;
56 changes: 56 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// @ts-check

import { createVerify } from "node:crypto";

import { request as defaultRequest } from "@octokit/request";
import { RequestError } from "@octokit/request-error";

/** @type {import('.').VerifyInterface} */
export async function verify(
rawBody,
signature,
keyId,
{ token = "", request = defaultRequest } = { request: defaultRequest },
) {
// verify arguments
assertValidString(rawBody, "Invalid payload");
assertValidString(signature, "Invalid signature");
assertValidString(keyId, "Invalid keyId");

// receive valid public keys from GitHub
const requestOptions = request.endpoint("GET /meta/public_keys/copilot_api", {
headers: token
? {
Authorization: `token ${token}`,
}
: {},
});
const response = await request(requestOptions);
const { data: keys } = response;

// verify provided key Id
const publicKey = keys.public_keys.find(
(key) => key.key_identifier === keyId,
);
if (!publicKey) {
throw new RequestError(
"[@copilot-extensions/preview-sdk] No public key found matching key identifier",
404,
{
request: requestOptions,
response,
},
);
}

const verify = createVerify("SHA256").update(rawBody);

// verify signature
return verify.verify(publicKey.key, signature, "base64");
}

function assertValidString(value, message) {
if (typeof value !== "string" || value.length === 0) {
throw new Error(`[@copilot-extensions/preview-sdk] ${message}`);
}
}
32 changes: 32 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { expectType } from "tsd";
import { request } from "@octokit/request";

import { verify } from "./index.js";

const rawBody = "";
const signature = "";
const keyId = "";
const token = "";

export async function verifyTest() {
const result = await verify(rawBody, signature, keyId);
expectType<boolean>(result);

// @ts-expect-error - first 3 arguments are required
verify(rawBody, signature);

// @ts-expect-error - rawBody must be a string
await verify(1, signature, keyId);

// @ts-expect-error - signature must be a string
await verify(rawBody, 1, keyId);

// @ts-expect-error - keyId must be a string
await verify(rawBody, signature, 1);

// accepts a token argument
await verify(rawBody, signature, keyId, { token });

// accepts a request argument
await verify(rawBody, signature, keyId, { request });
}
Loading

0 comments on commit 7a56d1b

Please sign in to comment.