Skip to content

Commit

Permalink
Use ExposableError for public errors in GitHub integration (#624)
Browse files Browse the repository at this point in the history
* Use ExposableError for public errors in GitHub integration

* Add changeset
  • Loading branch information
jpreynat authored Nov 20, 2024
1 parent 9bce519 commit d6ec68a
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-singers-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gitbook/integration-github': minor
---

Use ExposableError for public errors in GitHub integration
9 changes: 4 additions & 5 deletions integrations/github/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import LinkHeader from 'http-link-header';
import { StatusError } from 'itty-router';

import { Logger } from '@gitbook/runtime';
import { Logger, ExposableError } from '@gitbook/runtime';

import type { GithubRuntimeContext, GitHubSpaceConfiguration } from './types';
import { assertIsDefined, getSpaceConfigOrThrow } from './utils';
Expand Down Expand Up @@ -343,7 +342,7 @@ async function requestGitHubAPI(
logger.error(`[${options.method}] (${response.status}) GitHub API error: ${text}`);

// Otherwise, we throw an error
throw new StatusError(response.status, `GitHub API error: ${response.statusText}`);
throw new ExposableError(`GitHub API error: ${response.statusText}`, response.status);
}

return response;
Expand All @@ -370,7 +369,7 @@ async function refreshCredentials(

if (!resp.ok) {
// If refresh fails for whatever reason, we ask the user to re-authenticate
throw new StatusError(401, `Unauthorized: kindly re-authenticate!`);
throw new ExposableError(`Unauthorized: kindly re-authenticate!`, 401);
}

const data = await resp.formData();
Expand Down Expand Up @@ -398,7 +397,7 @@ export function extractTokenCredentialsOrThrow(

const oAuthCredentials = config?.oauth_credentials;
if (!oAuthCredentials?.access_token) {
throw new StatusError(401, 'Unauthorized: kindly re-authenticate!');
throw new ExposableError('Unauthorized: kindly re-authenticate!', 401);
}

return oAuthCredentials;
Expand Down
7 changes: 4 additions & 3 deletions integrations/github/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Router, error, StatusError } from 'itty-router';
import { Router, error } from 'itty-router';

import { ContentKitIcon, ContentKitSelectOption, GitSyncOperationState } from '@gitbook/api';
import {
Expand All @@ -7,6 +7,7 @@ import {
createOAuthHandler,
Logger,
EventCallback,
ExposableError,
} from '@gitbook/runtime';

import {
Expand Down Expand Up @@ -76,7 +77,7 @@ const handleFetchEvent: FetchEventCallback<GithubRuntimeContext> = async (reques
if (!verified) {
const message = `Invalid signature for integration task`;
logger.error(message);
throw new StatusError(400, message);
throw new ExposableError(message);
}

const { task } = JSON.parse(payloadString) as { task: IntegrationTask };
Expand Down Expand Up @@ -113,7 +114,7 @@ const handleFetchEvent: FetchEventCallback<GithubRuntimeContext> = async (reques
);
} catch (error: any) {
logger.error(`Error verifying signature ${error}`);
throw new StatusError(400, error.message);
throw new ExposableError(error.message);
}

logger.debug('received webhook event', { id, event });
Expand Down
7 changes: 2 additions & 5 deletions integrations/github/src/installation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { StatusError } from 'itty-router';

import { IntegrationSpaceInstallation } from '@gitbook/api';
import { Logger } from '@gitbook/runtime';
import { Logger, ExposableError } from '@gitbook/runtime';

import { fetchRepository } from './api';
import { triggerExport, triggerImport } from './sync';
Expand All @@ -28,8 +26,7 @@ export async function saveSpaceConfiguration(
assertIsDefined(spaceInstallation, { label: 'spaceInstallation' });

if (!state.installation || !state.repository || !state.branch) {
throw new StatusError(
400,
throw new ExposableError(
'Incomplete configuration: missing installation, repository or branch',
);
}
Expand Down
9 changes: 4 additions & 5 deletions integrations/github/src/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import type {
PullRequestOpenedEvent,
PullRequestSynchronizeEvent,
} from '@octokit/webhooks-types';
import { StatusError } from 'itty-router';

import { Logger } from '@gitbook/runtime';
import { Logger, ExposableError } from '@gitbook/runtime';

import { handleImportDispatchForSpaces } from './tasks';
import { GithubRuntimeContext } from './types';
Expand All @@ -24,9 +23,9 @@ export async function verifyGitHubWebhookSignature(
secret: string,
) {
if (!signature) {
throw new StatusError(400, 'No signature found on request');
throw new ExposableError('No signature found on request');
} else if (!signature.startsWith('sha256=')) {
throw new StatusError(400, 'Invalid format: signature is not using sha256');
throw new ExposableError('Invalid format: signature is not using sha256');
}

const algorithm = { name: 'HMAC', hash: 'SHA-256' };
Expand All @@ -39,7 +38,7 @@ export async function verifyGitHubWebhookSignature(
const signed = await crypto.subtle.sign(algorithm.name, key, enc.encode(payload));
const expectedSignature = `sha256=${arrayToHex(signed)}`;
if (!safeCompare(expectedSignature, signature)) {
throw new StatusError(400, 'Signature does not match event payload and secret');
throw new ExposableError('Signature does not match event payload and secret');
}

// All good!
Expand Down

0 comments on commit d6ec68a

Please sign in to comment.