@bevel-software/platform-core-backend 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +32 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +13 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +51 -5
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-requests.contract.d.ts +75 -0
- package/dist/modules/access/access-requests.contract.d.ts.map +1 -0
- package/dist/modules/access/access-requests.contract.js +20 -0
- package/dist/modules/access/access-requests.contract.js.map +1 -0
- package/dist/modules/access/access-requests.routes.d.ts +35 -0
- package/dist/modules/access/access-requests.routes.d.ts.map +1 -0
- package/dist/modules/access/access-requests.routes.js +237 -0
- package/dist/modules/access/access-requests.routes.js.map +1 -0
- package/dist/modules/access/access-requests.service.d.ts +123 -0
- package/dist/modules/access/access-requests.service.d.ts.map +1 -0
- package/dist/modules/access/access-requests.service.js +337 -0
- package/dist/modules/access/access-requests.service.js.map +1 -0
- package/dist/modules/access-model/access-grammar.d.ts +12 -0
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +24 -0
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +26 -1
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +11 -2
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/github-app/github-app.client.d.ts +101 -0
- package/dist/modules/github-app/github-app.client.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.client.js +222 -0
- package/dist/modules/github-app/github-app.client.js.map +1 -0
- package/dist/modules/github-app/github-app.connection.d.ts +102 -0
- package/dist/modules/github-app/github-app.connection.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.connection.js +185 -0
- package/dist/modules/github-app/github-app.connection.js.map +1 -0
- package/dist/modules/github-app/github-app.routes.d.ts +59 -0
- package/dist/modules/github-app/github-app.routes.d.ts.map +1 -0
- package/dist/modules/github-app/github-app.routes.js +323 -0
- package/dist/modules/github-app/github-app.routes.js.map +1 -0
- package/dist/modules/github-app/index.d.ts +4 -0
- package/dist/modules/github-app/index.d.ts.map +1 -0
- package/dist/modules/github-app/index.js +4 -0
- package/dist/modules/github-app/index.js.map +1 -0
- package/dist/modules/kb-fs/remote-url.d.ts +22 -0
- package/dist/modules/kb-fs/remote-url.d.ts.map +1 -0
- package/dist/modules/kb-fs/remote-url.js +35 -0
- package/dist/modules/kb-fs/remote-url.js.map +1 -0
- package/dist/modules/plugins/join-proposals.d.ts +17 -5
- package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
- package/dist/modules/plugins/join-proposals.js +76 -22
- package/dist/modules/plugins/join-proposals.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts +111 -18
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +173 -29
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +3 -2
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +22 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +93 -4
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/managed-repository.d.ts +43 -0
- package/dist/modules/settings/managed-repository.d.ts.map +1 -0
- package/dist/modules/settings/managed-repository.js +60 -0
- package/dist/modules/settings/managed-repository.js.map +1 -0
- package/dist/modules/settings/repository-source.d.ts +128 -0
- package/dist/modules/settings/repository-source.d.ts.map +1 -0
- package/dist/modules/settings/repository-source.js +150 -0
- package/dist/modules/settings/repository-source.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +27 -4
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +187 -13
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +6 -8
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +24 -63
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +15 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +38 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +3 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +17 -4
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +1 -1
- package/dist/modules/workflow/git/node-git-runner.js +5 -0
- package/dist/modules/workflow/git/node-git-runner.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +45 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +99 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +3 -1
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +34 -7
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +148 -48
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +66 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +148 -0
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +17 -1
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +28 -2
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +39 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +55 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +10 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js.map +1 -1
- package/package.json +3 -3
- package/src/core/create-core-server.ts +37 -1
- package/src/core/create-core-services.ts +65 -8
- package/src/modules/access/__tests__/access-requests.recut.test.ts +134 -0
- package/src/modules/access/__tests__/access-requests.routes.test.ts +610 -0
- package/src/modules/access/access-requests.contract.ts +93 -0
- package/src/modules/access/access-requests.routes.ts +286 -0
- package/src/modules/access/access-requests.service.ts +420 -0
- package/src/modules/access-model/access-grammar.ts +22 -0
- package/src/modules/auth/__tests__/auth.service.test.ts +38 -0
- package/src/modules/auth/auth.service.ts +28 -1
- package/src/modules/github-app/__tests__/github-app.test.ts +848 -0
- package/src/modules/github-app/github-app.client.ts +268 -0
- package/src/modules/github-app/github-app.connection.ts +204 -0
- package/src/modules/github-app/github-app.routes.ts +359 -0
- package/src/modules/github-app/index.ts +19 -0
- package/src/modules/kb-fs/__tests__/remote-url.test.ts +38 -0
- package/src/modules/kb-fs/remote-url.ts +35 -0
- package/src/modules/plugins/__tests__/join-proposals.test.ts +100 -19
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +27 -11
- package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +371 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
- package/src/modules/plugins/join-proposals.ts +87 -19
- package/src/modules/plugins/join-requests.service.ts +199 -39
- package/src/modules/plugins/plugins.routes.ts +3 -2
- package/src/modules/settings/__tests__/repository-source.test.ts +191 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +353 -0
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +282 -0
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +233 -0
- package/src/modules/settings/deployment-settings.service.ts +109 -3
- package/src/modules/settings/managed-repository.ts +68 -0
- package/src/modules/settings/repository-source.ts +197 -0
- package/src/modules/settings/setup.routes.ts +214 -10
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +5 -1
- package/src/modules/skills/skill-access-requests.routes.ts +25 -75
- package/src/modules/tool-helpers/tool-context.ts +15 -0
- package/src/modules/workflow/__tests__/apply-failure.test.ts +9 -1
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +159 -8
- package/src/modules/workflow/__tests__/workflow.service.update-from-target.test.ts +146 -40
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +141 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +49 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +121 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +145 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-delete.test.ts +166 -0
- package/src/modules/workflow/git/git.service.ts +25 -4
- package/src/modules/workflow/git/node-git-runner.ts +6 -0
- package/src/modules/workflow/git/pull-request.service.ts +119 -0
- package/src/modules/workflow/workflow.routes.ts +3 -1
- package/src/modules/workflow/workflow.service.ts +170 -52
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +43 -0
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +233 -7
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +35 -3
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +211 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +159 -0
- package/src/modules/workspace/workspace.service.ts +17 -1
- package/src/modules/workspace/workspace.tools.ts +35 -0
- package/src/shared/domain-errors.ts +58 -0
- package/src/shared/git.contract.ts +10 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { logger } from '../../shared/logging.js';
|
|
2
|
+
import { GitHubAppClient, } from './github-app.client.js';
|
|
3
|
+
/** Renewed this long before it would expire, so no git call starts on a token with minutes to live. */
|
|
4
|
+
const RENEW_AHEAD_MS = 10 * 60_000;
|
|
5
|
+
/** How long a failed renewal is remembered the first time, and the most it grows to. */
|
|
6
|
+
const FIRST_WAIT_MS = 5_000;
|
|
7
|
+
const LONGEST_WAIT_MS = 5 * 60_000;
|
|
8
|
+
/** The repositories a person may connect, as a setting holds them: one `owner/name` a line. */
|
|
9
|
+
export function repositoriesAsSetting(names) {
|
|
10
|
+
return [...new Set(names.map((name) => name.trim()).filter(Boolean))].join('\n');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The deployment's connection to GitHub through a GitHub App: the app it
|
|
14
|
+
* acts as, the installation it was given, the repositories it may be
|
|
15
|
+
* pointed at, and the token that stands for the first two when git runs.
|
|
16
|
+
*
|
|
17
|
+
* THE TOKEN EXPIRES. An installation token is good for an hour, which is
|
|
18
|
+
* the point of it: nothing long-lived is stored that reaches the
|
|
19
|
+
* repository. It is kept in memory, renewed before it runs out, and never
|
|
20
|
+
* written anywhere. A deployment that restarts asks for a new one.
|
|
21
|
+
*
|
|
22
|
+
* What is stored is what cannot be asked for again: the app's private key,
|
|
23
|
+
* encrypted like every secret, the number of the installation, and the
|
|
24
|
+
* repositories the person who connected it could push to.
|
|
25
|
+
*
|
|
26
|
+
* WHAT THE INSTALLATION REACHES IS NOT WHAT THE DEPLOYMENT MAY USE. An
|
|
27
|
+
* installation's token reads and writes every repository the installation
|
|
28
|
+
* covers. The person who connected it may hold far less: being able to
|
|
29
|
+
* read ONE of those repositories is enough for GitHub to count the
|
|
30
|
+
* installation among theirs. So the deployment is limited to what that
|
|
31
|
+
* person could push to with their own account, recorded when they
|
|
32
|
+
* connected (see {@link permits}); the installation's reach is what git
|
|
33
|
+
* works through, never what is offered.
|
|
34
|
+
*/
|
|
35
|
+
export class GitHubAppConnection {
|
|
36
|
+
opts;
|
|
37
|
+
client;
|
|
38
|
+
now;
|
|
39
|
+
log = () => logger('github-app');
|
|
40
|
+
/** The token in hand and what it is a token FOR: another app or installation is another token. */
|
|
41
|
+
held = null;
|
|
42
|
+
/** One renewal at a time, shared by every git call that arrives during it. */
|
|
43
|
+
renewing = null;
|
|
44
|
+
/** The last renewal failed: how many in a row have, and until when GitHub is not asked again. */
|
|
45
|
+
failed = null;
|
|
46
|
+
constructor(opts) {
|
|
47
|
+
this.opts = opts;
|
|
48
|
+
this.client = opts.client ?? new GitHubAppClient();
|
|
49
|
+
this.now = opts.now ?? Date.now;
|
|
50
|
+
}
|
|
51
|
+
/** The app the deployment acts as, or null while any part of it is missing. */
|
|
52
|
+
credentials(read = this.opts.read) {
|
|
53
|
+
const credentials = {
|
|
54
|
+
appId: read('githubAppId'),
|
|
55
|
+
slug: read('githubAppSlug'),
|
|
56
|
+
privateKey: read('githubAppPrivateKey'),
|
|
57
|
+
clientId: read('githubAppClientId'),
|
|
58
|
+
clientSecret: read('githubAppClientSecret'),
|
|
59
|
+
};
|
|
60
|
+
return Object.values(credentials).every(Boolean) ? credentials : null;
|
|
61
|
+
}
|
|
62
|
+
/** Who supplied the app: whoever operates the deployment, or the setup screen. Null: there is none. */
|
|
63
|
+
registeredBy() {
|
|
64
|
+
if (!this.credentials())
|
|
65
|
+
return null;
|
|
66
|
+
return this.opts.sourceOf('githubAppId') === 'env' ? 'environment' : 'setup';
|
|
67
|
+
}
|
|
68
|
+
installationId(read = this.opts.read) {
|
|
69
|
+
return read('githubInstallationId');
|
|
70
|
+
}
|
|
71
|
+
url(read = this.opts.read) {
|
|
72
|
+
const repository = read('githubRepository');
|
|
73
|
+
return repository ? `https://github.com/${repository}.git` : '';
|
|
74
|
+
}
|
|
75
|
+
answered(read = this.opts.read) {
|
|
76
|
+
return Boolean(this.credentials(read) && this.installationId(read) && read('githubRepository'));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether the deployment may be pointed at `repository`: it is one the
|
|
80
|
+
* person who connected the installation could push to.
|
|
81
|
+
*
|
|
82
|
+
* An installation the OPERATOR supplied, through the environment, has no
|
|
83
|
+
* such person. It is the operator's statement about their own
|
|
84
|
+
* deployment, and everything it reaches is permitted, as everything an
|
|
85
|
+
* access token reaches is.
|
|
86
|
+
*/
|
|
87
|
+
permits(repository, read = this.opts.read) {
|
|
88
|
+
if (this.opts.sourceOf('githubInstallationId') === 'env')
|
|
89
|
+
return true;
|
|
90
|
+
const wanted = repository.trim().toLowerCase();
|
|
91
|
+
if (!wanted)
|
|
92
|
+
return false;
|
|
93
|
+
// GitHub's names do not tell case apart.
|
|
94
|
+
return read('githubRepositoriesPermitted')
|
|
95
|
+
.split('\n')
|
|
96
|
+
.some((name) => name.trim().toLowerCase() === wanted);
|
|
97
|
+
}
|
|
98
|
+
token() {
|
|
99
|
+
const held = this.held;
|
|
100
|
+
if (!held || held.for !== this.holder() || held.expiresAt <= this.now())
|
|
101
|
+
return null;
|
|
102
|
+
return held.token;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Have a token for the call about to be made, WITHOUT MAKING THE CALL
|
|
106
|
+
* WAIT ON GITHUB UNLESS IT HAS TO. This runs before every git call the
|
|
107
|
+
* deployment makes, the ones that never leave the disk included.
|
|
108
|
+
*
|
|
109
|
+
* - A token with time to spare: nothing is asked.
|
|
110
|
+
* - A token that is good but close to running out: a renewal is started
|
|
111
|
+
* and the call goes ahead with the token in hand.
|
|
112
|
+
* - No token that is good: the call waits for one, and gets what GitHub
|
|
113
|
+
* says, unless GitHub has just failed to give one. A failure is
|
|
114
|
+
* remembered for a short while that grows with each one in a row, and
|
|
115
|
+
* inside it nothing is asked: git runs with no token, and a call that
|
|
116
|
+
* needed one is told so by the host, at once.
|
|
117
|
+
*
|
|
118
|
+
* `asked` is for an admin waiting on the answer itself (the setup screen
|
|
119
|
+
* listing repositories, proving a connection): GitHub is asked whatever
|
|
120
|
+
* is remembered, and the failure is thrown.
|
|
121
|
+
*/
|
|
122
|
+
async prepare(opts = {}) {
|
|
123
|
+
const credentials = this.credentials();
|
|
124
|
+
const installationId = this.installationId();
|
|
125
|
+
if (!credentials || !installationId)
|
|
126
|
+
return;
|
|
127
|
+
const held = this.held?.for === this.holder() ? this.held : null;
|
|
128
|
+
const good = held !== null && held.expiresAt > this.now();
|
|
129
|
+
if (good && held.expiresAt - this.now() > RENEW_AHEAD_MS)
|
|
130
|
+
return;
|
|
131
|
+
if (!opts.asked && this.failed && this.now() < this.failed.until)
|
|
132
|
+
return;
|
|
133
|
+
this.renewing ??= this.renew(credentials, installationId).finally(() => {
|
|
134
|
+
this.renewing = null;
|
|
135
|
+
});
|
|
136
|
+
if (good && !opts.asked) {
|
|
137
|
+
// Not waited on, so not thrown to anyone: the renewal has logged it.
|
|
138
|
+
this.renewing.catch(() => undefined);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await this.renewing;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The repositories the deployment may be pointed at: those the
|
|
145
|
+
* installation reaches AND the person who connected it could push to.
|
|
146
|
+
* Asked of GitHub each time, so a repository taken out of the
|
|
147
|
+
* installation since is not offered.
|
|
148
|
+
*/
|
|
149
|
+
async repositories() {
|
|
150
|
+
await this.prepare({ asked: true });
|
|
151
|
+
const token = this.token();
|
|
152
|
+
if (!token)
|
|
153
|
+
return { repositories: [], more: false };
|
|
154
|
+
const reached = await this.client.repositories(token);
|
|
155
|
+
return { repositories: reached.repositories.filter((r) => this.permits(r.fullName)), more: reached.more };
|
|
156
|
+
}
|
|
157
|
+
/** Drop the token in hand: the installation it was for is no longer the deployment's. */
|
|
158
|
+
forget() {
|
|
159
|
+
this.held = null;
|
|
160
|
+
this.failed = null;
|
|
161
|
+
}
|
|
162
|
+
async renew(credentials, installationId) {
|
|
163
|
+
const holder = this.holder();
|
|
164
|
+
try {
|
|
165
|
+
const issued = await this.client.installationToken(credentials, installationId);
|
|
166
|
+
this.held = { for: holder, token: issued.token, expiresAt: issued.expiresAt };
|
|
167
|
+
this.failed = null;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const times = (this.failed?.times ?? 0) + 1;
|
|
171
|
+
const wait = Math.min(FIRST_WAIT_MS * 2 ** (times - 1), LONGEST_WAIT_MS);
|
|
172
|
+
this.failed = { times, until: this.now() + wait };
|
|
173
|
+
// Once per failure, and a failure is once per wait.
|
|
174
|
+
this.log().error(`GitHub gave no token for the installation; not asked again for ${Math.round(wait / 1000)} s:`, {
|
|
175
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
176
|
+
failuresInARow: times,
|
|
177
|
+
});
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
holder() {
|
|
182
|
+
return `${this.opts.read('githubAppId')}:${this.installationId()}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=github-app.connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app.connection.js","sourceRoot":"","sources":["../../../src/modules/github-app/github-app.connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
3
|
+
import type { DeploymentSettingsService } from '../settings/deployment-settings.service.js';
|
|
4
|
+
import { GitHubAppClient } from './github-app.client.js';
|
|
5
|
+
import { type GitHubAppConnection } from './github-app.connection.js';
|
|
6
|
+
import '../auth/auth.middleware.js';
|
|
7
|
+
export interface GitHubAppRoutesDeps {
|
|
8
|
+
settings: Pick<DeploymentSettingsService, 'resolve' | 'record'>;
|
|
9
|
+
connection: GitHubAppConnection;
|
|
10
|
+
adminAccess: IAdminAccessService;
|
|
11
|
+
/** Where GitHub sends the browser back to: this deployment's own address. */
|
|
12
|
+
publicBackendUrl: string;
|
|
13
|
+
publicFrontendUrl: string;
|
|
14
|
+
/** Whether the deployment is set up: decides which page a round trip ends on. */
|
|
15
|
+
isComplete(): boolean;
|
|
16
|
+
client?: GitHubAppClient;
|
|
17
|
+
}
|
|
18
|
+
/** The address GitHub is told to send the browser back to once the app is installed. */
|
|
19
|
+
export declare function githubAppCallbackUrl(publicBackendUrl: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* The manifest a deployment's own GitHub App is created from. It asks for
|
|
22
|
+
* what the deployment does and nothing else: read and write the contents of
|
|
23
|
+
* the repositories it is installed on. No webhook, no events, not public:
|
|
24
|
+
* the app is this deployment's, installed by its owner on the one
|
|
25
|
+
* repository the knowledge base lives in.
|
|
26
|
+
*/
|
|
27
|
+
export declare function githubAppManifest(publicBackendUrl: string, publicFrontendUrl: string): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Connecting a repository on GitHub through a GitHub App, from the setup
|
|
30
|
+
* screen. Mounted behind the deployment's sign-in; every route is an
|
|
31
|
+
* admin's.
|
|
32
|
+
*
|
|
33
|
+
* GET /setup/github-app where the connection stands
|
|
34
|
+
* POST /setup/github-app/manifest start registering this deployment's own app
|
|
35
|
+
* GET /setup/github-app/registered GitHub sends the browser back: the app exists
|
|
36
|
+
* POST /setup/github-app/install where on GitHub the app is installed
|
|
37
|
+
* POST /setup/github-app/refresh where to sign in, to read again what may be connected
|
|
38
|
+
* GET /setup/github-app/callback GitHub sends the browser back: the app is installed
|
|
39
|
+
* GET /setup/github-app/repositories what the deployment may be pointed at
|
|
40
|
+
*
|
|
41
|
+
* A ROUND TRIP IS STARTED BY A POST AND ENDED BY A GET. The two routes
|
|
42
|
+
* GitHub sends the browser to are ordinary navigations, authenticated by
|
|
43
|
+
* the session cookie; each checks a state this browser was given when the
|
|
44
|
+
* round trip started, and spends it. Nothing that starts one is a link, so
|
|
45
|
+
* a link somebody was sent starts nothing.
|
|
46
|
+
*
|
|
47
|
+
* WHAT THE DEPLOYMENT MAY REACH is never taken from the address the browser
|
|
48
|
+
* came back on. GitHub puts an installation's number there and anyone can
|
|
49
|
+
* write another: with one app serving many deployments, that number is the
|
|
50
|
+
* only thing between a deployment and another organisation's repositories.
|
|
51
|
+
* GitHub sends the person back signed in, and the deployment asks GitHub
|
|
52
|
+
* two things about THAT PERSON: which installations they reach, and which
|
|
53
|
+
* repositories of the one they named they can push to with their own
|
|
54
|
+
* account. The first alone is not enough, since read access to one
|
|
55
|
+
* repository puts an installation on that list. Only the second is what
|
|
56
|
+
* the deployment may be pointed at.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createGitHubAppRoutes(deps: GitHubAppRoutesDeps): express.Router;
|
|
59
|
+
//# sourceMappingURL=github-app.routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/github-app/github-app.routes.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAkB,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,4BAA4B,CAAC;AAWpC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,IAAI,CAAC,yBAAyB,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAC;IAChE,UAAU,EAAE,mBAAmB,CAAC;IAChC,WAAW,EAAE,mBAAmB,CAAC;IACjC,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,UAAU,IAAI,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAe9G;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CA0P/E"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { logger } from '../../shared/logging.js';
|
|
4
|
+
import { GitHubAppClient, GitHubAppError } from './github-app.client.js';
|
|
5
|
+
import { repositoriesAsSetting } from './github-app.connection.js';
|
|
6
|
+
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
7
|
+
const log = logger('github-app');
|
|
8
|
+
const STATE_COOKIE = 'hexis_github_state';
|
|
9
|
+
const STATE_MAX_AGE_S = 15 * 60;
|
|
10
|
+
/** A GitHub organisation's name, as it appears in an address. */
|
|
11
|
+
const ORGANIZATION_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
|
|
12
|
+
/** GitHub refuses an app name longer than this. */
|
|
13
|
+
const MAX_APP_NAME = 34;
|
|
14
|
+
/** The address GitHub is told to send the browser back to once the app is installed. */
|
|
15
|
+
export function githubAppCallbackUrl(publicBackendUrl) {
|
|
16
|
+
return `${publicBackendUrl}/api/setup/github-app/callback`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The manifest a deployment's own GitHub App is created from. It asks for
|
|
20
|
+
* what the deployment does and nothing else: read and write the contents of
|
|
21
|
+
* the repositories it is installed on. No webhook, no events, not public:
|
|
22
|
+
* the app is this deployment's, installed by its owner on the one
|
|
23
|
+
* repository the knowledge base lives in.
|
|
24
|
+
*/
|
|
25
|
+
export function githubAppManifest(publicBackendUrl, publicFrontendUrl) {
|
|
26
|
+
const host = new URL(publicFrontendUrl).host;
|
|
27
|
+
return {
|
|
28
|
+
name: `Hexis ${host}`.slice(0, MAX_APP_NAME),
|
|
29
|
+
url: publicFrontendUrl,
|
|
30
|
+
description: `Lets the Hexis deployment at ${host} read and write the repository its knowledge base lives in.`,
|
|
31
|
+
redirect_url: `${publicBackendUrl}/api/setup/github-app/registered`,
|
|
32
|
+
callback_urls: [githubAppCallbackUrl(publicBackendUrl)],
|
|
33
|
+
// GitHub then sends the person back signed in, which is what lets the
|
|
34
|
+
// deployment check that the installation they name is one they hold.
|
|
35
|
+
request_oauth_on_install: true,
|
|
36
|
+
public: false,
|
|
37
|
+
default_permissions: { contents: 'write', metadata: 'read' },
|
|
38
|
+
default_events: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function readCookie(req, name) {
|
|
42
|
+
for (const segment of (req.headers.cookie ?? '').split(';')) {
|
|
43
|
+
const trimmed = segment.trim();
|
|
44
|
+
if (!trimmed.startsWith(`${name}=`))
|
|
45
|
+
continue;
|
|
46
|
+
try {
|
|
47
|
+
return decodeURIComponent(trimmed.slice(name.length + 1)) || null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function same(a, b) {
|
|
56
|
+
const left = Buffer.from(a, 'utf8');
|
|
57
|
+
const right = Buffer.from(b, 'utf8');
|
|
58
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Connecting a repository on GitHub through a GitHub App, from the setup
|
|
62
|
+
* screen. Mounted behind the deployment's sign-in; every route is an
|
|
63
|
+
* admin's.
|
|
64
|
+
*
|
|
65
|
+
* GET /setup/github-app where the connection stands
|
|
66
|
+
* POST /setup/github-app/manifest start registering this deployment's own app
|
|
67
|
+
* GET /setup/github-app/registered GitHub sends the browser back: the app exists
|
|
68
|
+
* POST /setup/github-app/install where on GitHub the app is installed
|
|
69
|
+
* POST /setup/github-app/refresh where to sign in, to read again what may be connected
|
|
70
|
+
* GET /setup/github-app/callback GitHub sends the browser back: the app is installed
|
|
71
|
+
* GET /setup/github-app/repositories what the deployment may be pointed at
|
|
72
|
+
*
|
|
73
|
+
* A ROUND TRIP IS STARTED BY A POST AND ENDED BY A GET. The two routes
|
|
74
|
+
* GitHub sends the browser to are ordinary navigations, authenticated by
|
|
75
|
+
* the session cookie; each checks a state this browser was given when the
|
|
76
|
+
* round trip started, and spends it. Nothing that starts one is a link, so
|
|
77
|
+
* a link somebody was sent starts nothing.
|
|
78
|
+
*
|
|
79
|
+
* WHAT THE DEPLOYMENT MAY REACH is never taken from the address the browser
|
|
80
|
+
* came back on. GitHub puts an installation's number there and anyone can
|
|
81
|
+
* write another: with one app serving many deployments, that number is the
|
|
82
|
+
* only thing between a deployment and another organisation's repositories.
|
|
83
|
+
* GitHub sends the person back signed in, and the deployment asks GitHub
|
|
84
|
+
* two things about THAT PERSON: which installations they reach, and which
|
|
85
|
+
* repositories of the one they named they can push to with their own
|
|
86
|
+
* account. The first alone is not enough, since read access to one
|
|
87
|
+
* repository puts an installation on that list. Only the second is what
|
|
88
|
+
* the deployment may be pointed at.
|
|
89
|
+
*/
|
|
90
|
+
export function createGitHubAppRoutes(deps) {
|
|
91
|
+
const router = express.Router();
|
|
92
|
+
const client = deps.client ?? new GitHubAppClient();
|
|
93
|
+
const { settings, connection } = deps;
|
|
94
|
+
const secure = deps.publicBackendUrl.startsWith('https');
|
|
95
|
+
const requireAdmin = async (req, res, next) => {
|
|
96
|
+
if (!(await deps.adminAccess.isAdmin(req.userEmail))) {
|
|
97
|
+
res.status(403).json({ error: 'Admins only' });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
next();
|
|
101
|
+
};
|
|
102
|
+
/** A state for one round trip, kept in this browser until it comes back. */
|
|
103
|
+
const beginRoundTrip = (res) => {
|
|
104
|
+
const tag = settings.resolve('githubAppStateTag');
|
|
105
|
+
const state = `${tag ? `${tag}.` : ''}${randomBytes(24).toString('base64url')}`;
|
|
106
|
+
res.cookie(STATE_COOKIE, state, { httpOnly: true, sameSite: 'lax', secure, maxAge: STATE_MAX_AGE_S * 1000, path: '/' });
|
|
107
|
+
return state;
|
|
108
|
+
};
|
|
109
|
+
/** Whether the browser that came back is the one that was sent. */
|
|
110
|
+
const cameBack = (req) => {
|
|
111
|
+
const kept = readCookie(req, STATE_COOKIE);
|
|
112
|
+
const given = typeof req.query.state === 'string' ? req.query.state : '';
|
|
113
|
+
return Boolean(kept && given && same(kept, given));
|
|
114
|
+
};
|
|
115
|
+
/** Back to the setup screen, saying how the round trip ended. */
|
|
116
|
+
const finish = (res, outcome) => {
|
|
117
|
+
const page = deps.isComplete() ? '/deployment' : '/';
|
|
118
|
+
res.redirect(`${deps.publicFrontendUrl}${page}?github=${encodeURIComponent(outcome)}`);
|
|
119
|
+
};
|
|
120
|
+
const installUrl = (slug, state) => `https://github.com/apps/${encodeURIComponent(slug)}/installations/new?state=${encodeURIComponent(state)}`;
|
|
121
|
+
const outcomeOf = (err) => err instanceof GitHubAppError && err.kind === 'unreachable' ? 'unreachable' : 'refused';
|
|
122
|
+
router.get('/setup/github-app', requireAdmin, (_req, res) => {
|
|
123
|
+
const credentials = connection.credentials();
|
|
124
|
+
res.json({
|
|
125
|
+
// Who supplied the app. Null: this deployment has none yet, and can register its own.
|
|
126
|
+
registeredBy: connection.registeredBy(),
|
|
127
|
+
app: credentials ? { slug: credentials.slug, url: `https://github.com/apps/${credentials.slug}` } : null,
|
|
128
|
+
installation: connection.installationId()
|
|
129
|
+
? { id: connection.installationId(), account: settings.resolve('githubInstallationAccount') }
|
|
130
|
+
: null,
|
|
131
|
+
repository: settings.resolve('githubRepository') || null,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
router.post('/setup/github-app/manifest', requireAdmin, (req, res) => {
|
|
135
|
+
if (connection.credentials()) {
|
|
136
|
+
res.status(409).json({ error: 'This deployment already has a GitHub App.' });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const organization = req.body?.organization;
|
|
140
|
+
const org = typeof organization === 'string' ? organization.trim() : '';
|
|
141
|
+
if (org && !ORGANIZATION_PATTERN.test(org)) {
|
|
142
|
+
res.status(400).json({ error: 'Enter the organisation’s name as it appears in its GitHub address.' });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const state = beginRoundTrip(res);
|
|
146
|
+
const base = org ? `https://github.com/organizations/${org}/settings/apps/new` : 'https://github.com/settings/apps/new';
|
|
147
|
+
// The browser posts the manifest to GitHub itself: a form, not a request of ours.
|
|
148
|
+
res.json({
|
|
149
|
+
action: `${base}?state=${encodeURIComponent(state)}`,
|
|
150
|
+
manifest: githubAppManifest(deps.publicBackendUrl, deps.publicFrontendUrl),
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
router.get('/setup/github-app/registered', requireAdmin, async (req, res) => {
|
|
154
|
+
// Spent on return, however the return ends.
|
|
155
|
+
const returned = cameBack(req);
|
|
156
|
+
res.clearCookie(STATE_COOKIE, { path: '/' });
|
|
157
|
+
if (!returned) {
|
|
158
|
+
finish(res, 'state');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const code = typeof req.query.code === 'string' ? req.query.code : '';
|
|
162
|
+
if (!code || connection.credentials()) {
|
|
163
|
+
finish(res, code ? 'already-registered' : 'refused');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const app = await client.convertManifest(code);
|
|
168
|
+
await settings.record({
|
|
169
|
+
githubAppId: app.appId,
|
|
170
|
+
githubAppSlug: app.slug,
|
|
171
|
+
githubAppPrivateKey: app.privateKey,
|
|
172
|
+
githubAppClientId: app.clientId,
|
|
173
|
+
githubAppClientSecret: app.clientSecret,
|
|
174
|
+
}, req.userId ?? null);
|
|
175
|
+
log.info(`GitHub App "${app.slug}" registered for this deployment`, { slug: app.slug, owner: app.owner });
|
|
176
|
+
// Straight on to installing it: the same round trip, the same state.
|
|
177
|
+
res.redirect(installUrl(app.slug, beginRoundTrip(res)));
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
log.error('the GitHub App could not be registered:', { detail: err instanceof Error ? err.message : String(err) });
|
|
181
|
+
finish(res, outcomeOf(err));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* A POST that answers with the address, and the page sends the browser
|
|
186
|
+
* there. As a link it was a round trip anyone could start in an admin's
|
|
187
|
+
* browser by getting them to follow it: the state would be set, and
|
|
188
|
+
* whatever they then installed would come back with a state that matched.
|
|
189
|
+
*/
|
|
190
|
+
router.post('/setup/github-app/install', requireAdmin, (_req, res) => {
|
|
191
|
+
const credentials = connection.credentials();
|
|
192
|
+
if (!credentials) {
|
|
193
|
+
res.status(409).json({ error: 'This deployment has no GitHub App yet. Create it first.' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
res.json({ url: installUrl(credentials.slug, beginRoundTrip(res)) });
|
|
197
|
+
});
|
|
198
|
+
/**
|
|
199
|
+
* Ask GitHub again what the person may connect, WITHOUT installing
|
|
200
|
+
* anything: a sign-in with the app and nothing more, which comes back on
|
|
201
|
+
* the same callback with a code and no installation named.
|
|
202
|
+
*
|
|
203
|
+
* It exists because installing comes back only the first time. Once the
|
|
204
|
+
* app is installed, the address that installs it opens the installation's
|
|
205
|
+
* settings on GitHub, and GitHub sends nobody back from there: a
|
|
206
|
+
* repository added on that page was reached by the app and never offered
|
|
207
|
+
* here, since what is offered is what was recorded when the person
|
|
208
|
+
* connected. The same goes for an installation an organisation's owner
|
|
209
|
+
* approved later, in a browser of their own.
|
|
210
|
+
*/
|
|
211
|
+
router.post('/setup/github-app/refresh', requireAdmin, (_req, res) => {
|
|
212
|
+
const credentials = connection.credentials();
|
|
213
|
+
if (!credentials) {
|
|
214
|
+
res.status(409).json({ error: 'This deployment has no GitHub App yet. Create it first.' });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const state = beginRoundTrip(res);
|
|
218
|
+
res.json({
|
|
219
|
+
url: `https://github.com/login/oauth/authorize?client_id=${encodeURIComponent(credentials.clientId)}&state=${encodeURIComponent(state)}`,
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
router.get('/setup/github-app/callback', requireAdmin, async (req, res) => {
|
|
223
|
+
const returned = cameBack(req);
|
|
224
|
+
res.clearCookie(STATE_COOKIE, { path: '/' });
|
|
225
|
+
if (!returned) {
|
|
226
|
+
finish(res, 'state');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const credentials = connection.credentials();
|
|
230
|
+
if (!credentials) {
|
|
231
|
+
finish(res, 'not-registered');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Someone without the right to install asked their organisation's owner
|
|
235
|
+
// to: nothing is installed yet, and there is nothing to record.
|
|
236
|
+
if (req.query.setup_action === 'request') {
|
|
237
|
+
finish(res, 'requested');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const code = typeof req.query.code === 'string' ? req.query.code : '';
|
|
241
|
+
const given = typeof req.query.installation_id === 'string' ? req.query.installation_id : '';
|
|
242
|
+
if (!code || (given !== '' && !/^\d+$/.test(given))) {
|
|
243
|
+
finish(res, 'refused');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Back from installing, GitHub names the installation. Back from a
|
|
247
|
+
// sign-in alone (see `refresh`) it names none, and the one in question
|
|
248
|
+
// is the one this deployment has. Either way it is only a name: what
|
|
249
|
+
// decides is what GitHub says the person reaches, below.
|
|
250
|
+
const refreshing = given === '';
|
|
251
|
+
const named = given || connection.installationId();
|
|
252
|
+
try {
|
|
253
|
+
const userToken = await client.exchangeUserCode(credentials, code);
|
|
254
|
+
const reached = await client.installationsOf(userToken);
|
|
255
|
+
let held = named ? reached.find((installation) => installation.id === named) : undefined;
|
|
256
|
+
if (!named) {
|
|
257
|
+
// No installation on record and none named: the app was installed
|
|
258
|
+
// where this browser never came back from (an owner approved it
|
|
259
|
+
// later). One the person reaches is the one; several are a choice
|
|
260
|
+
// only installing from here can make.
|
|
261
|
+
if (reached.length === 0) {
|
|
262
|
+
finish(res, 'not-installed');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (reached.length > 1) {
|
|
266
|
+
finish(res, 'several');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
held = reached[0];
|
|
270
|
+
}
|
|
271
|
+
if (!held) {
|
|
272
|
+
log.warn('an installation was named that the person who came back cannot reach', { installation: named });
|
|
273
|
+
finish(res, 'not-yours');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Reaching the installation is not holding it: read access to one
|
|
277
|
+
// repository it covers is enough for GitHub to list it. What the
|
|
278
|
+
// deployment may be pointed at is what THIS PERSON can push to.
|
|
279
|
+
const theirs = await client.repositoriesOf(userToken, held.id);
|
|
280
|
+
const writable = theirs.repositories.filter((repository) => repository.writable).map((r) => r.fullName);
|
|
281
|
+
if (writable.length === 0) {
|
|
282
|
+
log.warn('the person who came back can push to nothing the installation covers', { installation: held.id });
|
|
283
|
+
finish(res, 'nothing-to-write');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await settings.record({
|
|
287
|
+
githubInstallationId: held.id,
|
|
288
|
+
githubRepositoriesPermitted: repositoriesAsSetting(writable),
|
|
289
|
+
...(held.account ? { githubInstallationAccount: held.account } : {}),
|
|
290
|
+
}, req.userId ?? null);
|
|
291
|
+
// A token in hand was another installation's.
|
|
292
|
+
connection.forget();
|
|
293
|
+
log.info(refreshing
|
|
294
|
+
? `what may be connected through the GitHub App on "${held.account}" was read again`
|
|
295
|
+
: `GitHub App installed on "${held.account}"`, { installation: held.id, account: held.account, repositories: writable.length });
|
|
296
|
+
finish(res, refreshing ? 'refreshed' : 'connected');
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
log.error('the installation could not be confirmed:', { detail: err instanceof Error ? err.message : String(err) });
|
|
300
|
+
finish(res, outcomeOf(err));
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
router.get('/setup/github-app/repositories', requireAdmin, async (_req, res) => {
|
|
304
|
+
if (!connection.credentials() || !connection.installationId()) {
|
|
305
|
+
res.status(409).json({ error: 'Connect GitHub first.' });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
res.json(await connection.repositories());
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
log.error('the repositories could not be listed:', { detail: err instanceof Error ? err.message : String(err) });
|
|
313
|
+
const unreachable = err instanceof GitHubAppError && err.kind === 'unreachable';
|
|
314
|
+
res.status(502).json({
|
|
315
|
+
error: unreachable
|
|
316
|
+
? 'GitHub could not be reached. Try again shortly.'
|
|
317
|
+
: 'GitHub would not list the repositories. The app may have been uninstalled: connect it again.',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return router;
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=github-app.routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-app.routes.js","sourceRoot":"","sources":["../../../src/modules/github-app/github-app.routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { GitHubAppClient, GitHubAppError, appJwt, normalizePrivateKey, type GitHubAppCredentials, type GitHubFailureKind, type Installation, type InstallationRepository, type InstallationToken, type RegisteredApp, } from './github-app.client.js';
|
|
2
|
+
export { GitHubAppConnection, repositoriesAsSetting, type GitHubAppConnectionOptions } from './github-app.connection.js';
|
|
3
|
+
export { createGitHubAppRoutes, githubAppCallbackUrl, githubAppManifest, type GitHubAppRoutesDeps, } from './github-app.routes.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/github-app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EACd,MAAM,EACN,mBAAmB,EACnB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACzH,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { GitHubAppClient, GitHubAppError, appJwt, normalizePrivateKey, } from './github-app.client.js';
|
|
2
|
+
export { GitHubAppConnection, repositoriesAsSetting } from './github-app.connection.js';
|
|
3
|
+
export { createGitHubAppRoutes, githubAppCallbackUrl, githubAppManifest, } from './github-app.routes.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/github-app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,cAAc,EACd,MAAM,EACN,mBAAmB,GAOpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAmC,MAAM,4BAA4B,CAAC;AACzH,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,GAElB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether two repository addresses name the same repository.
|
|
3
|
+
*
|
|
4
|
+
* The answer decides whether a working copy on disk is KEPT or DELETED, so it
|
|
5
|
+
* errs towards "same": only differences that cannot change which repository
|
|
6
|
+
* is meant are ignored, and they are ignored on both sides.
|
|
7
|
+
*
|
|
8
|
+
* - a trailing slash, and a `.git` suffix — hosts serve both spellings;
|
|
9
|
+
* - the letter case of the scheme and the host — DNS does not distinguish;
|
|
10
|
+
* - userinfo (`user:token@`) — who asks does not change what is asked for,
|
|
11
|
+
* and a credential must never decide, or appear in, a comparison.
|
|
12
|
+
*
|
|
13
|
+
* The PATH keeps its case: hosts that fold it and hosts that do not both
|
|
14
|
+
* exist, and treating `Org/Repo` and `org/repo` as one would keep a clone of
|
|
15
|
+
* the wrong repository on a host that tells them apart — but deleting a clone
|
|
16
|
+
* of the right one is the worse error, and an operator who only changed the
|
|
17
|
+
* case gets a re-clone, not a loss: the work on that clone was pushed to the
|
|
18
|
+
* same place.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeRepositoryAddress(address: string): string;
|
|
21
|
+
export declare function sameRepository(a: string, b: string): boolean;
|
|
22
|
+
//# sourceMappingURL=remote-url.d.ts.map
|