Skip to content

Commit d907bbd

Browse files
authored
feat(setup): support registration using an existing GitHub app (probot#1345)
1 parent 31fdd61 commit d907bbd

5 files changed

Lines changed: 174 additions & 7 deletions

File tree

src/apps/setup.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import bodyParser from "body-parser";
12
import { exec } from "child_process";
23
import { Request, Response } from "express";
4+
import updateDotenv from "update-dotenv";
35
import { Application } from "../application";
46
import { ManifestCreation } from "../manifest-creation";
57

@@ -27,12 +29,7 @@ export const setupAppFactory = (
2729
printWelcomeMessage(app, host, port);
2830

2931
route.get("/probot", async (req, res) => {
30-
const protocols = req.headers["x-forwarded-proto"] || req.protocol;
31-
const protocol =
32-
typeof protocols === "string" ? protocols.split(",")[0] : protocols[0];
33-
const host = req.headers["x-forwarded-host"] || req.get("host");
34-
const baseUrl = `${protocol}://${host}`;
35-
32+
const baseUrl = getBaseUrl(req);
3633
const pkg = setup.pkg;
3734
const manifest = setup.getManifest(pkg, baseUrl);
3835
const createAppUrl = setup.createAppUrl;
@@ -56,6 +53,26 @@ export const setupAppFactory = (
5653
res.redirect(`${response}/installations/new`);
5754
});
5855

56+
route.get("/probot/import", async (_req, res) => {
57+
const { WEBHOOK_PROXY_URL, GHE_HOST } = process.env;
58+
const GH_HOST = `https://${GHE_HOST ?? "github.com"}`;
59+
res.render("import.hbs", { WEBHOOK_PROXY_URL, GH_HOST });
60+
});
61+
62+
route.post("/probot/import", bodyParser.json(), async (req, res) => {
63+
const { appId, pem, webhook_secret } = req.body;
64+
if (!appId || !pem || !webhook_secret) {
65+
res.status(400).send("appId and/or pem and/or webhook_secret missing");
66+
return;
67+
}
68+
updateDotenv({
69+
APP_ID: appId,
70+
PRIVATE_KEY: `"${pem}"`,
71+
WEBHOOK_SECRET: webhook_secret,
72+
});
73+
res.end();
74+
});
75+
5976
route.get("/probot/success", async (req, res) => {
6077
res.render("success.hbs");
6178
});
@@ -87,3 +104,12 @@ function printWelcomeMessage(
87104
app.log.info(line);
88105
});
89106
}
107+
108+
function getBaseUrl(req: Request): string {
109+
const protocols = req.headers["x-forwarded-proto"] || req.protocol;
110+
const protocol =
111+
typeof protocols === "string" ? protocols.split(",")[0] : protocols[0];
112+
const host = req.headers["x-forwarded-host"] || req.get("host");
113+
const baseUrl = `${protocol}://${host}`;
114+
return baseUrl;
115+
}

test/apps/__snapshots__/setup.test.ts.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,20 @@ Array [
1616
],
1717
]
1818
`;
19+
20+
exports[`Setup app POST /probot/import updates .env 1`] = `
21+
Array [
22+
Array [
23+
Object {
24+
"WEBHOOK_PROXY_URL": "mocked proxy URL",
25+
},
26+
],
27+
Array [
28+
Object {
29+
"APP_ID": "foo",
30+
"PRIVATE_KEY": "\\"bar\\"",
31+
"WEBHOOK_SECRET": "baz",
32+
},
33+
],
34+
]
35+
`;

test/apps/setup.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,45 @@ describe("Setup app", () => {
100100
});
101101
});
102102

103+
describe("GET /probot/import", () => {
104+
it("renders import.hbs", async () => {
105+
await request(probot.server).get("/probot/import").expect(200);
106+
});
107+
});
108+
109+
describe("POST /probot/import", () => {
110+
it("updates .env", async () => {
111+
const body = JSON.stringify({
112+
appId: "foo",
113+
pem: "bar",
114+
webhook_secret: "baz",
115+
});
116+
117+
await request(probot.server)
118+
.post("/probot/import")
119+
.set("content-type", "application/json")
120+
.send(body)
121+
.expect(200)
122+
.expect("");
123+
124+
expect(updateDotenv.mock.calls).toMatchSnapshot();
125+
});
126+
127+
it("400 when keys are missing", async () => {
128+
const body = JSON.stringify({
129+
appId: "foo",
130+
/* no pem */
131+
webhook_secret: "baz",
132+
});
133+
134+
await request(probot.server)
135+
.post("/probot/import")
136+
.set("content-type", "application/json")
137+
.send(body)
138+
.expect(400);
139+
});
140+
});
141+
103142
describe("GET /probot/success", () => {
104143
it("returns a 200 response", async () => {
105144
await request(probot.server).get("/probot/success").expect(200);

views/import.hbs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!DOCTYPE html>
2+
<html lang="en" class="height-full">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
8+
<title>Import {{#if pkg.name }}{{ pkg.name }}{{else}}Your App{{/if}} | built with Probot</title>
9+
<link rel="icon" href="/probot/static/probot-head.png">
10+
<link rel="stylesheet" href="/probot/static/primer.css">
11+
</head>
12+
13+
<body class="bg-gray-light">
14+
<div class="d-flex flex-column flex-justify-center flex-items-center text-center height-full py-6">
15+
<a href="/probot"><img src="/probot/static/robot.svg" alt="Probot Logo" width="100" class="mb-6"></a>
16+
<div class="box-shadow rounded-2 border p-6 bg-white">
17+
<h2>Use existing Github App</h2>
18+
<br>
19+
20+
<h3>Step 1:</h3>
21+
<p class="d-block mt-2">
22+
Replace your app's Webhook URL with <br>
23+
<b>{{ WEBHOOK_PROXY_URL }}</b>
24+
</p>
25+
<a class="d-block mt-2" href="{{ GH_HOST }}/settings/apps" target="__blank" rel="noreferrer">
26+
You can do it here
27+
</a>
28+
29+
<br>
30+
<h3>Step 2:</h3>
31+
<p class="mt-2">Fill out this form</p>
32+
<form onsubmit="return onSubmit(event) || false">
33+
<label class="d-block mt-2" for="appId">App Id</label>
34+
<input class="form-control width-full" type="text" required="true" id="appId" name="appId"><br>
35+
36+
<label class="d-block mt-3" for="whs">Webhook secret (required!)</label>
37+
<input class="form-control width-full" type="password" required="true" id="whs" name="whs"><br>
38+
39+
<label class="d-block mt-3" for="pem">Private Key</label>
40+
<input class="form-control width-full m-2" type="file" accept=".pem" required="true" id="pem"
41+
name="pem">
42+
<br>
43+
44+
<button class="btn btn-outline m-2" type="submit">Submit</button>
45+
</form>
46+
</div>
47+
48+
<div class="mt-4">
49+
<h4 class="alt-h4 text-gray-light">Need help?</h4>
50+
<div class="d-flex flex-justify-center mt-2">
51+
<a href="https://probot.github.io/docs/" class="btn btn-outline mr-2">Documentation</a>
52+
<a href="https://probot-slackin.herokuapp.com/" class="btn btn-outline">Chat on Slack</a>
53+
</div>
54+
</div>
55+
</div>
56+
<script>
57+
function onSubmit(e) {
58+
e.preventDefault();
59+
60+
const idEl = document.getElementById('appId');
61+
const appId = idEl.value;
62+
63+
64+
const secretEl = document.getElementById('whs');
65+
const webhook_secret = secretEl.value;
66+
67+
const fileEl = document.getElementById('pem');
68+
const file = fileEl.files[0];
69+
70+
file.text().then((text) => fetch('', {
71+
method: 'POST',
72+
headers: { 'content-type': 'application/json' },
73+
body: JSON.stringify({ appId, pem: text, webhook_secret })
74+
})).then((r) => {
75+
if (r.ok) {
76+
location.replace('/probot/success');
77+
}
78+
}).catch((e) => alert(e));
79+
return false;
80+
}
81+
</script>
82+
</body>
83+
84+
</html>

views/setup.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
<p>To start building a GitHub App, you'll need to register a new app on GitHub.</p>
3232
<br>
3333

34-
<form action="{{ createAppUrl }}" method="post" target="_blank">
34+
<form action="{{ createAppUrl }}" method="post" target="_blank" class="d-flex flex-items-center">
3535
<button class="btn btn-outline" name="manifest" id="manifest" value='{{ manifest }}' >Register GitHub App</button>
36+
<a href="/probot/import" class="ml-2">or use an existing Github App</a>
3637
</form>
3738
</div>
3839
</div>

0 commit comments

Comments
 (0)