Skip to content

Commit

Permalink
refactor: decouple github auth from api calls
Browse files Browse the repository at this point in the history
  • Loading branch information
kormide committed Jan 10, 2024
1 parent e3015e7 commit 8e592a5
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 185 deletions.
40 changes: 23 additions & 17 deletions e2e/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo, "unversioned-1.0.0");
Expand All @@ -118,6 +118,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand All @@ -144,7 +145,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo, "versioned-1.0.0");
Expand All @@ -156,6 +157,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -183,7 +185,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo, "tarball-1.0.0");
Expand All @@ -195,6 +197,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -222,7 +225,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseZip(repo, "zip-1.0.0");
Expand All @@ -234,6 +237,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -261,7 +265,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo);
Expand All @@ -273,6 +277,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -300,7 +305,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo);
Expand All @@ -312,6 +317,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand All @@ -327,12 +333,6 @@ describe("e2e tests", () => {
});

test("happy path", async () => {
// This test checks for authentication token requests which may be cached.
// Restart the cloud functions server to ensure a consistently uncached
// state for this test.
await cloudFunctions.shutdown();
await cloudFunctions.start();

const repo = Fixture.Versioned;
const tag = "v1.0.0";
await setupLocalRemoteRulesetRepo(repo, tag, releaser);
Expand All @@ -357,6 +357,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
rulesetInstallationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -422,7 +423,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive1 = await makeReleaseTarball(repo, "module-1.0.0");
Expand All @@ -441,6 +442,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -510,7 +512,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(
Expand All @@ -525,6 +527,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -556,7 +559,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(releaser.login!, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo, "versioned-1.0.0");
Expand All @@ -568,6 +571,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -605,7 +609,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
// App not installed to fork
// fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

Expand All @@ -618,6 +622,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down Expand Up @@ -652,7 +657,7 @@ describe("e2e tests", () => {
"bazelbuild",
"bazel-central-registry"
);
fakeGitHub.mockAppInstallation(testOrg, repo);
const installationId = fakeGitHub.mockAppInstallation(testOrg, repo);
fakeGitHub.mockAppInstallation(testOrg, "bazel-central-registry");

const releaseArchive = await makeReleaseTarball(repo, "versioned-1.0.0");
Expand All @@ -664,6 +669,7 @@ describe("e2e tests", () => {
const response = await publishReleaseEvent(
cloudFunctions.getBaseUrl(),
secrets.webhookSecret,
installationId,
{
owner: testOrg,
repo,
Expand Down
4 changes: 4 additions & 0 deletions e2e/helpers/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { DeepPartial } from "./types";
export async function publishReleaseEvent(
webhookUrl: string,
webhookSecret: string,
installationId: number,
release: { owner: string; repo: string; tag: string; releaser: Partial<User> }
): Promise<AxiosResponse> {
const body: DeepPartial<ReleasePublishedEvent> = {
Expand All @@ -22,6 +23,9 @@ export async function publishReleaseEvent(
html_url: `https://github.com/${release.owner}/${release.repo}/releases/tag/${release.tag}`,
tag_name: release.tag,
},
installation: {
id: installationId,
},
};

const signature = await sign(webhookSecret, JSON.stringify(body));
Expand Down
7 changes: 4 additions & 3 deletions e2e/stubs/fake-github.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { User } from "@octokit/webhooks-types";
import { randomInt, randomUUID } from "crypto";
import { randomUUID } from "crypto";
import * as mockttp from "mockttp";
import url from "node:url";
import { StubbedServer } from "./stubbed-server";
Expand Down Expand Up @@ -74,14 +74,15 @@ export class FakeGitHub implements StubbedServer {
this.repositories.clear();
}

private nextInstallationId = 1;
public mockAppInstallation(owner: string, repo: string): number {
const installationId = randomInt(50000);
const installationId = this.nextInstallationId++;
this.appInstallations.set(`${owner}/${repo}`, installationId);
return installationId;
}

public mockBotAppInstallation(owner: string, repo: string): number {
const installationId = randomInt(50000);
const installationId = this.nextInstallationId++;
this.botAppInstallations.set(`${owner}/${repo}`, installationId);
return installationId;
}
Expand Down
37 changes: 5 additions & 32 deletions src/application/cloudfunction/github-webhook-entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,19 @@
import { HttpFunction } from "@google-cloud/functions-framework";
import { Webhooks } from "@octokit/webhooks";
import { CreateEntryService } from "../../domain/create-entry.js";
import { FindRegistryForkService } from "../../domain/find-registry-fork.js";
import { PublishEntryService } from "../../domain/publish-entry.js";
import { Repository } from "../../domain/repository.js";
import { EmailClient } from "../../infrastructure/email.js";
import { GitClient } from "../../infrastructure/git.js";
import { GitHubClient } from "../../infrastructure/github.js";
import { SecretsClient } from "../../infrastructure/secrets.js";
import { NotificationsService } from "../notifications.js";
import { ReleaseEventHandler } from "../release-event-handler.js";

// Setup application dependencies using constructor dependency injection.
const secretsClient = new SecretsClient();
const gitClient = new GitClient();
const githubClient = new GitHubClient();
const emailClient = new EmailClient();
const findRegistryForkService = new FindRegistryForkService(githubClient);
const createEntryService = new CreateEntryService(gitClient, githubClient);
const publishEntryService = new PublishEntryService(githubClient);
const notificationsService = new NotificationsService(
emailClient,
secretsClient,
githubClient
);

const releaseEventHandler = new ReleaseEventHandler(
githubClient,
secretsClient,
findRegistryForkService,
createEntryService,
publishEntryService,
notificationsService
);
Repository.gitClient = gitClient;

// Handle incoming GitHub webhook messages. This is the entrypoint for
// the webhook cloud function.
export const handleGithubWebhookEvent: HttpFunction = async (
request,
response
) => {
// Setup application dependencies using constructor dependency injection.
const secretsClient = new SecretsClient();

const releaseEventHandler = new ReleaseEventHandler(secretsClient);

const githubWebhookSecret = await secretsClient.accessSecret(
"github-app-webhook-secret"
);
Expand Down
41 changes: 41 additions & 0 deletions src/application/octokit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Octokit } from "@octokit/rest";
import { getAppAuthorizedOctokit } from "../infrastructure/github.js";
import { SecretsClient } from "../infrastructure/secrets.js";

export async function createAppAuthorizedOctokit(
secretsClient: SecretsClient
): Promise<Octokit> {
const [githubAppPrivateKey, githubAppClientId, githubAppClientSecret] =
await Promise.all([
secretsClient.accessSecret("github-app-private-key"),
secretsClient.accessSecret("github-app-client-id"),
secretsClient.accessSecret("github-app-client-secret"),
]);

return getAppAuthorizedOctokit(
Number(process.env.GITHUB_APP_ID),
githubAppPrivateKey,
githubAppClientId,
githubAppClientSecret
);
}

export async function createBotAppAuthorizedOctokit(
secretsClient: SecretsClient
): Promise<Octokit> {
const [
githubBotAppPrivateKey,
githubBotAppClientId,
githubBotAppClientSecret,
] = await Promise.all([
secretsClient.accessSecret("github-bot-app-private-key"),
secretsClient.accessSecret("github-bot-app-client-id"),
secretsClient.accessSecret("github-bot-app-client-secret"),
]);
return getAppAuthorizedOctokit(
Number(process.env.GITHUB_BOT_APP_ID),
githubBotAppPrivateKey,
githubBotAppClientId,
githubBotAppClientSecret
);
}
Loading

0 comments on commit 8e592a5

Please sign in to comment.