FIX: prevents error due to property override (#26741)

The `secondFactorMethod` property is defined as a @discourseComputed` which means it can't be overridden. Yet, we do override it in `app/assets/javascripts/discourse/app/components/security-key-form.js` and `app/assets/javascripts/discourse/app/components/second-factor-form.js` by doing `this.set("secondFactorMethod", ...)`.

This commit sets a default property `secondFactorMethod` on the `email-login` controller after the model has been loaded. Given this property is no longer computed, it can be set again at other places.

Followups:
- Ideally we would follow DDAU pattern but this is quite a significant refactor.
- The test I added is very limited, ideally we should start writing system specs for this, but it means having to deal with the email, it's a significant work.
This commit is contained in:
Joffrey JAFFEUX 2024-04-24 17:54:16 +02:00 committed by GitHub
parent 21ef033e86
commit 9f8091abf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 8 deletions

View File

@ -4,13 +4,16 @@ import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { getWebauthnCredential } from "discourse/lib/webauthn"; import { getWebauthnCredential } from "discourse/lib/webauthn";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({ export default Controller.extend({
router: service(), router: service(),
secondFactorMethod: null,
secondFactorToken: null,
lockImageUrl: getURL("/images/lock.svg"), lockImageUrl: getURL("/images/lock.svg"),
@discourseComputed("model") @discourseComputed("model")
@ -18,13 +21,6 @@ export default Controller.extend({
return model.security_key_required || model.second_factor_required; return model.security_key_required || model.second_factor_required;
}, },
@discourseComputed("model")
secondFactorMethod(model) {
return model.security_key_required
? SECOND_FACTOR_METHODS.SECURITY_KEY
: SECOND_FACTOR_METHODS.TOTP;
},
actions: { actions: {
finishLogin() { finishLogin() {
let data = { let data = {

View File

@ -1,4 +1,5 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
@ -10,4 +11,15 @@ export default DiscourseRoute.extend({
model(params) { model(params) {
return ajax(`/session/email-login/${params.token}.json`); return ajax(`/session/email-login/${params.token}.json`);
}, },
setupController(controller, model) {
this._super.apply(this, arguments);
controller.set(
"secondFactorMethod",
model.security_key_required
? SECOND_FACTOR_METHODS.SECURITY_KEY
: SECOND_FACTOR_METHODS.TOTP
);
},
}); });

View File

@ -0,0 +1,44 @@
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import I18n from "discourse-i18n";
const TOKEN = "sometoken";
acceptance("Login with email and 2FA", function (needs) {
needs.settings({
enable_local_logins_via_email: true,
});
needs.pretender((server, helper) => {
server.post("/u/email-login", () =>
helper.response({
success: "OK",
user_found: true,
})
);
server.get(`/session/email-login/${TOKEN}.json`, () =>
helper.response({
token: TOKEN,
can_login: true,
token_email: "blah@example.com",
security_key_required: true,
second_factor_required: true,
})
);
});
test("You can switch from security key to 2FA", async function (assert) {
await visit("/");
await click("header .login-button");
await fillIn("#login-account-name", "blah@example.com");
await click("#email-login-link");
await visit(`/session/email-login/${TOKEN}`);
await click(".toggle-second-factor-method");
assert
.dom("#second-factor")
.containsText(I18n.t("user.second_factor.title"));
});
});