From 3ffc213fa988a6ed755a6c669389849e19a8d7ad Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 15 Aug 2022 15:30:07 +0100 Subject: [PATCH] FEATURE: Add safe-mode toggle to `/u/admin-login` (#17930) Previously, this would require manually adding `?safe_mode=...` multiple times during the email-based login flow. `/u/admin-login` is often used when debugging a site, so it makes sense for this to be easier. This commit introduces a new checkbox on the `/u/admin-login` screen. When checked, it'll set the safe_mode parameter on the `/email-login` link, and then pass it all the way through to the homepage redirect. --- .../discourse/app/controllers/email-login.js | 18 +++++++++- .../tests/acceptance/login-with-email-test.js | 35 +++++++++++++++++++ app/controllers/users_controller.rb | 6 +++- app/views/users/admin_login.html.erb | 5 +++ config/locales/server.en.yml | 1 + spec/requests/users_controller_spec.rb | 8 +++++ 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/email-login.js b/app/assets/javascripts/discourse/app/controllers/email-login.js index a488daf5e77..b17304a779b 100644 --- a/app/assets/javascripts/discourse/app/controllers/email-login.js +++ b/app/assets/javascripts/discourse/app/controllers/email-login.js @@ -6,8 +6,11 @@ import discourseComputed from "discourse-common/utils/decorators"; import getURL from "discourse-common/lib/get-url"; import { getWebauthnCredential } from "discourse/lib/webauthn"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { inject as service } from "@ember/service"; export default Controller.extend({ + router: service(), + lockImageUrl: getURL("/images/lock.svg"), @discourseComputed("model") @@ -41,7 +44,20 @@ export default Controller.extend({ }) .then((result) => { if (result.success) { - DiscourseURL.redirectTo("/"); + let destination = "/"; + + const safeMode = new URL( + this.router.currentURL, + window.location.origin + ).searchParams.get("safe_mode"); + + if (safeMode) { + const params = new URLSearchParams(); + params.set("safe_mode", safeMode); + destination += `?${params.toString()}`; + } + + DiscourseURL.redirectTo(destination); } else { this.set("model.error", result.error); } diff --git a/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js index ee9718f0fa4..15f83901d26 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js @@ -6,6 +6,10 @@ import { import { click, fillIn, visit } from "@ember/test-helpers"; import I18n from "I18n"; import { test } from "qunit"; +import DiscourseURL from "discourse/lib/url"; +import sinon from "sinon"; + +const TOKEN = "sometoken"; acceptance("Login with email", function (needs) { needs.settings({ @@ -18,6 +22,20 @@ acceptance("Login with email", function (needs) { server.post("/u/email-login", () => helper.response({ success: "OK", user_found: userFound }) ); + + server.get(`/session/email-login/${TOKEN}.json`, () => + helper.response({ + token: TOKEN, + can_login: true, + token_email: "blah@example.com", + }) + ); + + server.post(`/session/email-login/${TOKEN}`, () => + helper.response({ + success: true, + }) + ); }); test("with email button", async function (assert) { @@ -83,4 +101,21 @@ acceptance("Login with email", function (needs) { userFound = false; }); + + test("finish login UI", async function (assert) { + await visit(`/session/email-login/${TOKEN}`); + sinon.stub(DiscourseURL, "redirectTo"); + await click(".email-login .btn-primary"); + assert.true(DiscourseURL.redirectTo.calledWith("/"), "redirects to home"); + }); + + test("finish login UI - safe mode", async function (assert) { + await visit(`/session/email-login/${TOKEN}?safe_mode=no_themes,no_plugins`); + sinon.stub(DiscourseURL, "redirectTo"); + await click(".email-login .btn-primary"); + assert.true( + DiscourseURL.redirectTo.calledWith("/?safe_mode=no_themes%2Cno_plugins"), + "redirects to home with safe mode" + ); + }); }); diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8d83520d61e..662a2c3a6e9 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -961,7 +961,11 @@ class UsersController < ApplicationController if user = User.with_email(params[:email]).admins.human_users.first email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:email_login]) - Jobs.enqueue(:critical_user_email, type: "admin_login", user_id: user.id, email_token: email_token.token) + token_string = email_token.token + if params["use_safe_mode"] + token_string += "?safe_mode=no_plugins,no_themes" + end + Jobs.enqueue(:critical_user_email, type: "admin_login", user_id: user.id, email_token: token_string) @message = I18n.t("admin_login.success") else @message = I18n.t("admin_login.errors.unknown_email_address") diff --git a/app/views/users/admin_login.html.erb b/app/views/users/admin_login.html.erb index 95a74a123ff..82e061bdfd6 100644 --- a/app/views/users/admin_login.html.erb +++ b/app/views/users/admin_login.html.erb @@ -5,6 +5,11 @@ <%=form_tag(u_admin_login_path, method: :put) do %> <%= label_tag(:email, t('admin_login.email_input')) %> <%= text_field_tag(:email, nil, autofocus: true) %>

+ +
<%= submit_tag t('admin_login.submit_button'), class: "btn btn-primary" %> <% end %> <% end %> diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c966b7c8097..b2b180f86bf 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -4833,6 +4833,7 @@ en: invalid_token: "Invalid token." email_input: "Admin Email" submit_button: "Send Email" + safe_mode: "Safe Mode: disable all themes/plugins when logging in" performance_report: initial_post_raw: This topic includes daily performance reports for your site. diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 6a75373b0b0..b37bb8e447a 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -535,6 +535,14 @@ RSpec.describe UsersController do expect(args["user_id"]).to eq(admin.id) end + it 'passes through safe mode' do + put "/u/admin-login", params: { email: admin.email, use_safe_mode: true } + expect(response.status).to eq(200) + expect(Jobs::CriticalUserEmail.jobs.size).to eq(1) + args = Jobs::CriticalUserEmail.jobs.first["args"].first + expect(args["email_token"]).to end_with("?safe_mode=no_plugins,no_themes") + end + context 'when email is incorrect' do it 'should return the right response' do put "/u/admin-login", params: { email: 'random' }