From 485d5af268d476541a3ea35238e5fb082fbd8b63 Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Thu, 2 May 2024 10:52:27 -0400 Subject: [PATCH] make api calls from the boot page be owned by the support user --- app/common/BaseAPI.ts | 6 ++++++ app/server/lib/Authorizer.ts | 13 +++++++++++++ app/server/lib/ICreate.ts | 2 +- app/server/lib/InstallAdmin.ts | 9 ++++++++- static/boot.html | 3 +++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/common/BaseAPI.ts b/app/common/BaseAPI.ts index 23e80112..d1f46232 100644 --- a/app/common/BaseAPI.ts +++ b/app/common/BaseAPI.ts @@ -61,6 +61,12 @@ export class BaseAPI { 'X-Requested-With': 'XMLHttpRequest', ...options.headers }; + if ((window as any)?.isGristBootPage) { + const parts = (new URL(window.location.href).pathname).split('/') + if (parts[0] === '' && parts[1] === 'boot' && parts[2] !== undefined) { + this._headers['X-Boot-Key'] = parts[2]; + } + } this._extraParameters = options.extraParameters; } diff --git a/app/server/lib/Authorizer.ts b/app/server/lib/Authorizer.ts index 761e76f2..296ed0a7 100644 --- a/app/server/lib/Authorizer.ts +++ b/app/server/lib/Authorizer.ts @@ -193,6 +193,19 @@ export async function addRequestUser( } } + if (!authDone && mreq.headers && mreq.headers['x-boot-key']) { + const reqBootKey = String(mreq.headers['x-boot-key']); + const bootKey = process.env.GRIST_BOOT_KEY; + if (!(bootKey && bootKey === reqBootKey)) { + return res.status(401).send('Bad request: invalid Boot key'); + } + const userId = dbManager.getSupportUserId(); + const user = await dbManager.getUser(userId); + mreq.user = user; + mreq.userId = userId; + mreq.userIsAuthorized = true; + } + // Special permission header for internal housekeeping tasks if (!authDone && mreq.headers && mreq.headers.permit) { const permitKey = String(mreq.headers.permit); diff --git a/app/server/lib/ICreate.ts b/app/server/lib/ICreate.ts index 8d76d030..3ca48d0c 100644 --- a/app/server/lib/ICreate.ts +++ b/app/server/lib/ICreate.ts @@ -158,6 +158,6 @@ export function makeSimpleCreator(opts: { }, getSqliteVariant: opts.getSqliteVariant, getSandboxVariants: opts.getSandboxVariants, - createInstallAdmin: opts.createInstallAdmin || (async () => new SimpleInstallAdmin()), + createInstallAdmin: opts.createInstallAdmin || (async (dbManager) => new SimpleInstallAdmin(dbManager)), }; } diff --git a/app/server/lib/InstallAdmin.ts b/app/server/lib/InstallAdmin.ts index 803656aa..03c1439e 100644 --- a/app/server/lib/InstallAdmin.ts +++ b/app/server/lib/InstallAdmin.ts @@ -1,4 +1,5 @@ import {ApiError} from 'app/common/ApiError'; +import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager'; import {appSettings} from 'app/server/lib/AppSettings'; import {getUser, RequestWithLogin} from 'app/server/lib/Authorizer'; import {User} from 'app/gen-server/entity/User'; @@ -40,13 +41,19 @@ export abstract class InstallAdmin { } // Considers the user whose email matches GRIST_DEFAULT_EMAIL env var, if given, to be the -// installation admin. If not given, then there is no admin. +// installation admin. The support user is also accepted. +// Otherwise, there is no admin. export class SimpleInstallAdmin extends InstallAdmin { private _installAdminEmail = appSettings.section('access').flag('installAdminEmail').readString({ envVar: 'GRIST_DEFAULT_EMAIL', }); + public constructor(private _dbManager: HomeDBManager) { + super(); + } + public override async isAdminUser(user: User): Promise { + if (user.id === this._dbManager.getSupportUserId()) { return true;} return this._installAdminEmail ? (user.loginEmail === this._installAdminEmail) : false; } } diff --git a/static/boot.html b/static/boot.html index 8f67607f..9c3388fe 100644 --- a/static/boot.html +++ b/static/boot.html @@ -10,6 +10,9 @@ Loading...<!-- INSERT TITLE SUFFIX --> +