diff --git a/README.adoc b/README.adoc index 0c9ac6e..510fb49 100644 --- a/README.adoc +++ b/README.adoc @@ -274,6 +274,8 @@ which provides some useful base functionality such as splitting the `webhookMidd To facilitate debugging problems in production, se-edu-bot exposes its logs via the `/logs` endpoint. +You need to be a member of the `se-edu` organization in order to access them. + == Coding standard We follow the oss-generic coding standard. diff --git a/lib/Auth.ts b/lib/Auth.ts index 7c4ba50..9f62bed 100644 --- a/lib/Auth.ts +++ b/lib/Auth.ts @@ -3,6 +3,7 @@ import koaCompose = require('koa-compose'); import koaRoute = require('koa-route'); import simpleOauth2 = require('simple-oauth2'); import createError = require('http-errors'); +import * as github from '../lib/github'; /** * Options to pass to {@link Auth}. @@ -59,6 +60,44 @@ export class Auth { ]); } + createAccessControlByInstallationId(installationId: number): Koa.Middleware { + return async (ctx: Koa.Context, next?: () => Promise): Promise => { + const ghUserApi = this.getGhUserApi(ctx); + if (!ghUserApi) { + ctx.redirect(this.getLoginRedirect(ctx)); + return; + } + + // Check that the user has access to our installation + const installationIds: number[] = []; + await github.forEachPage(ghUserApi, { + url: 'user/installations', + }, body => { + const installations: any[] = body.installations; + installations.forEach(installation => installationIds.push(installation.id)); + }); + + if (!installationIds.includes(installationId)) { + throw createError(403, 'User is not authorized to access this page'); + } + + if (next) { + await next(); + } + }; + } + + private getGhUserApi(ctx: Koa.Context): github.RequestApi | undefined { + const accessToken = ctx.cookies.get(this.accessTokenCookieName); + if (!accessToken) { + return; + } + return github.createAccessTokenApi({ + accessToken, + userAgent: this.userAgent, + }); + } + private async loginMiddleware(ctx: Koa.Context): Promise { const authorizationUri = this.oauth2.authorizationCode.authorizeURL({ redirect_uri: this.getOauthRedirectUri(ctx), @@ -112,6 +151,11 @@ export class Auth { } return redirect; } + + private getLoginRedirect(ctx: Koa.Context): string { + const redirect = `${ctx.path}${ctx.search}`; + return `${this.loginRoute}?redirect=${encodeURIComponent(redirect)}`; + } } export default Auth; diff --git a/server.ts b/server.ts index e730b4a..522bc70 100644 --- a/server.ts +++ b/server.ts @@ -100,6 +100,7 @@ export function createApp(appConfig: AppConfig): Koa { fileName: logFileName, }); app.use(koaRoute.get('/logs', koaCompose([ + auth.createAccessControlByInstallationId(appConfig.githubInstallationId), async ctx => { ctx.type = 'text'; ctx.body = logger.createReadableStream();