Skip to content

Commit 1eb4f04

Browse files
committed
logs: only allow organization members to access them
The bot's logs are accessible to everyone at the /logs endpoint. This is dangerous because the logs may contain sensitive info. Fix this by only allowing se-edu organization members to view the logs. Note that our access control doesn't actually check if the current user is a member of se-edu, but rather whether the user has access to the se-edu-bot installation in the se-edu organization. This should be equivalent.
1 parent 805a9a7 commit 1eb4f04

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

README.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ which provides some useful base functionality such as splitting the `webhookMidd
274274
To facilitate debugging problems in production,
275275
se-edu-bot exposes its logs via the `/logs` endpoint.
276276

277+
You need to be a member of the `se-edu` organization in order to access them.
278+
277279
== Coding standard
278280

279281
We follow the oss-generic coding standard.

lib/Auth.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import koaCompose = require('koa-compose');
33
import koaRoute = require('koa-route');
44
import simpleOauth2 = require('simple-oauth2');
55
import createError = require('http-errors');
6+
import * as github from '../lib/github';
67

78
/**
89
* Options to pass to {@link Auth}.
@@ -59,6 +60,44 @@ export class Auth {
5960
]);
6061
}
6162

63+
createAccessControlByInstallationId(installationId: number): Koa.Middleware {
64+
return async (ctx: Koa.Context, next?: () => Promise<void>): Promise<void> => {
65+
const ghUserApi = this.getGhUserApi(ctx);
66+
if (!ghUserApi) {
67+
ctx.redirect(this.getLoginRedirect(ctx));
68+
return;
69+
}
70+
71+
// Check that the user has access to our installation
72+
const installationIds: number[] = [];
73+
await github.forEachPage(ghUserApi, {
74+
url: 'user/installations',
75+
}, body => {
76+
const installations: any[] = body.installations;
77+
installations.forEach(installation => installationIds.push(installation.id));
78+
});
79+
80+
if (!installationIds.includes(installationId)) {
81+
throw createError(403, 'User is not authorized to access this page');
82+
}
83+
84+
if (next) {
85+
await next();
86+
}
87+
};
88+
}
89+
90+
private getGhUserApi(ctx: Koa.Context): github.RequestApi | undefined {
91+
const accessToken = ctx.cookies.get(this.accessTokenCookieName);
92+
if (!accessToken) {
93+
return;
94+
}
95+
return github.createAccessTokenApi({
96+
accessToken,
97+
userAgent: this.userAgent,
98+
});
99+
}
100+
62101
private async loginMiddleware(ctx: Koa.Context): Promise<void> {
63102
const authorizationUri = this.oauth2.authorizationCode.authorizeURL({
64103
redirect_uri: this.getOauthRedirectUri(ctx),
@@ -112,6 +151,11 @@ export class Auth {
112151
}
113152
return redirect;
114153
}
154+
155+
private getLoginRedirect(ctx: Koa.Context): string {
156+
const redirect = `${ctx.path}${ctx.search}`;
157+
return `${this.loginRoute}?redirect=${encodeURIComponent(redirect)}`;
158+
}
115159
}
116160

117161
export default Auth;

server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function createApp(appConfig: AppConfig): Koa {
100100
fileName: logFileName,
101101
});
102102
app.use(koaRoute.get('/logs', koaCompose<Koa.Context>([
103+
auth.createAccessControlByInstallationId(appConfig.githubInstallationId),
103104
async ctx => {
104105
ctx.type = 'text';
105106
ctx.body = logger.createReadableStream();

0 commit comments

Comments
 (0)