From 0d5467968f85f8919e493432aae6562550f38720 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 28 Feb 2024 18:05:30 -0800 Subject: [PATCH 01/12] session: add a concept of sudo mode This introduces "sudo mode", whereby an admin can enter a privileged mode for a certain period of time. The session tracks this in the browser's local storage. --- app/services/session.js | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/services/session.js b/app/services/session.js index 55a456ceb5e..f8e0af0de93 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -1,4 +1,5 @@ import Service, { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; import { dropTask, race, rawTimeout, task, waitForEvent } from 'ember-concurrency'; import window from 'ember-window-mock'; @@ -15,6 +16,15 @@ export default class SessionService extends Service { savedTransition = null; + /** + * The timestamp (in milliseconds since the UNIX epoch, as returned by + * {@link Date.now()}) that the user has sudo enabled until. + * + * @type {number | null} + */ + @tracked sudoEnabledUntil = null; + + /** @type {import("../models/user").default | null} */ @alias('loadUserTask.last.value.currentUser') currentUser; @alias('loadUserTask.last.value.ownedCrates') ownedCrates; @@ -30,6 +40,36 @@ export default class SessionService extends Service { } } + get isAdmin() { + return this.currentUser?.is_admin === true; + } + + get isSudoEnabled() { + return this.currentUser?.is_admin === true && this.sudoEnabledUntil !== null && this.sudoEnabledUntil >= Date.now(); + } + + /** + * Enables or disables sudo mode based on the `duration_ms` parameter. + * + * If the user is not an admin, nothing happens, successfully. + * + * @param {number} duration_ms If non-zero, enables sudo mode for this + * length of time. If zero, disables sudo mode + * immediately. + */ + setSudo(duration_ms) { + if (this.currentUser?.is_admin) { + if (duration_ms) { + const expiry = Date.now() + duration_ms; + localStorage.setItem('sudo', expiry); + this.sudoEnabledUntil = expiry; + } else { + localStorage.removeItem('sudo'); + this.sudoEnabledUntil = null; + } + } + } + /** * This task will open a popup window, query the `/api/private/session/begin` API * endpoint and then navigate the popup window to the received URL. @@ -158,6 +198,20 @@ export default class SessionService extends Service { let { id } = currentUser; this.sentry.setUser({ id }); + // If the user is an admin, we need to look up whether they have enabled + // sudo mode. + if (currentUser?.is_admin) { + const expiry = localStorage.getItem('sudo'); + if (expiry !== null) { + try { + this.sudoEnabledUntil = +expiry; + } catch { + // It doesn't really matter if this fails; any invalid value will just + // be treated as the user not being in sudo mode. + } + } + } + return { currentUser, ownedCrates }; }); } From 4455ac82e32523ecd32c3e3c09fa7c94d7fe376d Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 28 Feb 2024 18:08:17 -0800 Subject: [PATCH 02/12] header: add a menu item for admins to enter and leave sudo mode If the user is in sudo mode, the expiry time is also displayed in the menu. --- app/components/header.hbs | 24 +++++++++++++++++++----- app/components/header.js | 13 +++++++++++++ app/components/header.module.css | 13 ++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app/components/header.hbs b/app/components/header.hbs index 9c1368d7761..e781b5b2c38 100644 --- a/app/components/header.hbs +++ b/app/components/header.hbs @@ -25,11 +25,25 @@ {{ this.session.currentUser.name }} - - Dashboard - Account Settings - Owner Invites - + + Dashboard + Account Settings + Owner Invites + {{#if this.session.isAdmin}} + + {{#if this.session.isSudoEnabled}} + + {{else}} + + {{/if}} + + {{/if}} + + + +