mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 13:06:56 +08:00
DEV: use PasswordValidationHelper instead of mixin for signup controller (#31380)
This introduces a helper class for password validation logic, and replaces the mixin in the signup controller class. All properties that impact password validation in that class are also converted to autotracked ones.
This commit is contained in:
@ -14,25 +14,29 @@ import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import discourseDebounce from "discourse/lib/debounce";
|
||||
import discourseComputed, { bind } from "discourse/lib/decorators";
|
||||
import NameValidationHelper from "discourse/lib/name-validation-helper";
|
||||
import PasswordValidationHelper from "discourse/lib/password-validation-helper";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import UsernameValidationHelper from "discourse/lib/username-validation-helper";
|
||||
import { emailValid } from "discourse/lib/utilities";
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
import UserFieldsValidation from "discourse/mixins/user-fields-validation";
|
||||
import { findAll } from "discourse/models/login-method";
|
||||
import User from "discourse/models/user";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class SignupPageController extends Controller.extend(
|
||||
PasswordValidation,
|
||||
UserFieldsValidation
|
||||
) {
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
@service login;
|
||||
|
||||
@tracked accountName;
|
||||
@tracked accountPassword;
|
||||
@tracked accountEmail;
|
||||
@tracked accountUsername;
|
||||
@tracked isDeveloper = false;
|
||||
@tracked authOptions;
|
||||
@tracked skipConfirmation;
|
||||
accountChallenge = 0;
|
||||
accountHoneypot = 0;
|
||||
formSubmitted = false;
|
||||
@ -40,10 +44,10 @@ export default class SignupPageController extends Controller.extend(
|
||||
prefilledUsername = null;
|
||||
userFields = null;
|
||||
maskPassword = true;
|
||||
passwordValidationVisible = false;
|
||||
emailValidationVisible = false;
|
||||
nameValidationHelper = new NameValidationHelper(this);
|
||||
usernameValidationHelper = new UsernameValidationHelper(this);
|
||||
passwordValidationHelper = new PasswordValidationHelper(this);
|
||||
|
||||
@notEmpty("authOptions") hasAuthOptions;
|
||||
@setting("enable_local_logins") canCreateLocal;
|
||||
@ -53,7 +57,7 @@ export default class SignupPageController extends Controller.extend(
|
||||
super.init(...arguments);
|
||||
|
||||
if (cookie("email")) {
|
||||
this.set("accountEmail", cookie("email"));
|
||||
this.accountEmail = cookie("email");
|
||||
}
|
||||
|
||||
this.fetchConfirmationValue();
|
||||
@ -64,6 +68,11 @@ export default class SignupPageController extends Controller.extend(
|
||||
return this.usernameValidationHelper.usernameValidation;
|
||||
}
|
||||
|
||||
@dependentKeyCompat
|
||||
get passwordValidation() {
|
||||
return this.passwordValidationHelper.passwordValidation;
|
||||
}
|
||||
|
||||
get nameTitle() {
|
||||
return this.nameValidationHelper.nameTitle;
|
||||
}
|
||||
@ -161,20 +170,8 @@ export default class SignupPageController extends Controller.extend(
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"passwordValidation.ok",
|
||||
"passwordValidation.reason",
|
||||
"passwordValidationVisible"
|
||||
)
|
||||
showPasswordValidation(
|
||||
passwordValidationOk,
|
||||
passwordValidationReason,
|
||||
passwordValidationVisible
|
||||
) {
|
||||
return (
|
||||
passwordValidationOk ||
|
||||
(passwordValidationReason && passwordValidationVisible)
|
||||
);
|
||||
get showPasswordValidation() {
|
||||
return this.passwordValidation.ok || this.passwordValidation.reason;
|
||||
}
|
||||
|
||||
@discourseComputed("usernameValidation.reason")
|
||||
@ -185,9 +182,8 @@ export default class SignupPageController extends Controller.extend(
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed("authOptions.auth_provider")
|
||||
passwordRequired(authProvider) {
|
||||
return isEmpty(authProvider);
|
||||
get passwordRequired() {
|
||||
return isEmpty(this.authOptions?.auth_provider);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
@ -243,15 +239,12 @@ export default class SignupPageController extends Controller.extend(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.get("authOptions.email") === email &&
|
||||
this.get("authOptions.email_valid")
|
||||
) {
|
||||
if (this.authOptions?.email === email && this.authOptions?.email_valid) {
|
||||
return EmberObject.create({
|
||||
ok: true,
|
||||
reason: i18n("user.email.authenticated", {
|
||||
provider: this.authProviderDisplayName(
|
||||
this.get("authOptions.auth_provider")
|
||||
this.authOptions?.auth_provider
|
||||
),
|
||||
}),
|
||||
});
|
||||
@ -268,15 +261,6 @@ export default class SignupPageController extends Controller.extend(
|
||||
this.accountUsername = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
togglePasswordValidation() {
|
||||
if (this.passwordValidation.reason) {
|
||||
this.set("passwordValidationVisible", true);
|
||||
} else {
|
||||
this.set("passwordValidationVisible", false);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
checkEmailAvailability() {
|
||||
if (this.emailValidation.reason) {
|
||||
@ -325,15 +309,10 @@ export default class SignupPageController extends Controller.extend(
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"accountEmail",
|
||||
"authOptions.email",
|
||||
"authOptions.email_valid"
|
||||
)
|
||||
emailDisabled() {
|
||||
get emailDisabled() {
|
||||
return (
|
||||
this.get("authOptions.email") === this.accountEmail &&
|
||||
this.get("authOptions.email_valid")
|
||||
this.authOptions?.email === this.accountEmail &&
|
||||
this.authOptions?.email_valid
|
||||
);
|
||||
}
|
||||
|
||||
@ -356,7 +335,7 @@ export default class SignupPageController extends Controller.extend(
|
||||
}
|
||||
if (
|
||||
this.get("emailValidation.ok") &&
|
||||
(isEmpty(this.accountUsername) || this.get("authOptions.email"))
|
||||
(isEmpty(this.accountUsername) || this.authOptions?.email)
|
||||
) {
|
||||
// If email is valid and username has not been entered yet,
|
||||
// or email and username were filled automatically by 3rd party auth,
|
||||
@ -407,8 +386,8 @@ export default class SignupPageController extends Controller.extend(
|
||||
|
||||
handleSkipConfirmation() {
|
||||
if (this.skipConfirmation) {
|
||||
this.performAccountCreation().finally(() =>
|
||||
this.set("skipConfirmation", false)
|
||||
this.performAccountCreation().finally(
|
||||
() => (this.skipConfirmation = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -433,7 +412,7 @@ export default class SignupPageController extends Controller.extend(
|
||||
accountPasswordConfirm: this.accountHoneypot,
|
||||
};
|
||||
|
||||
const destinationUrl = this.get("authOptions.destination_url");
|
||||
const destinationUrl = this.authOptions?.destination_url;
|
||||
|
||||
if (!isEmpty(destinationUrl)) {
|
||||
cookie("destination_url", destinationUrl, { path: "/" });
|
||||
@ -485,7 +464,9 @@ export default class SignupPageController extends Controller.extend(
|
||||
this.rejectedEmails.pushObject(result.values.email);
|
||||
}
|
||||
if (result.errors?.["user_password.password"]?.length > 0) {
|
||||
this.rejectedPasswords.pushObject(attrs.accountPassword);
|
||||
this.passwordValidationHelper.rejectedPasswords.push(
|
||||
attrs.accountPassword
|
||||
);
|
||||
}
|
||||
this.set("formSubmitted", false);
|
||||
removeCookie("destination_url");
|
||||
@ -534,7 +515,6 @@ export default class SignupPageController extends Controller.extend(
|
||||
this.set("flash", "");
|
||||
this.nameValidationHelper.forceValidationReason = true;
|
||||
this.set("emailValidationVisible", true);
|
||||
this.set("passwordValidationVisible", true);
|
||||
|
||||
const validation = [
|
||||
this.emailValidation,
|
||||
|
@ -143,9 +143,7 @@ export default {
|
||||
router.transitionTo("signup").then((signup) => {
|
||||
const signupController =
|
||||
signup.controller || owner.lookup("controller:signup");
|
||||
Object.keys(createAccountProps || {}).forEach((key) => {
|
||||
signupController.set(key, createAccountProps[key]);
|
||||
});
|
||||
Object.assign(signupController, createAccountProps);
|
||||
signupController.handleSkipConfirmation();
|
||||
});
|
||||
} else {
|
||||
|
@ -0,0 +1,104 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { dependentKeyCompat } from "@ember/object/compat";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { TrackedArray, TrackedMap } from "@ember-compat/tracked-built-ins";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
function failedResult(attrs) {
|
||||
return {
|
||||
failed: true,
|
||||
ok: false,
|
||||
element: document.querySelector("#new-account-password"),
|
||||
...attrs,
|
||||
};
|
||||
}
|
||||
|
||||
function validResult(attrs) {
|
||||
return { ok: true, ...attrs };
|
||||
}
|
||||
|
||||
export default class PasswordValidationHelper {
|
||||
@tracked rejectedPasswords = new TrackedArray();
|
||||
@tracked rejectedPasswordsMessages = new TrackedMap();
|
||||
|
||||
constructor(owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
get passwordInstructions() {
|
||||
return i18n("user.password.instructions", {
|
||||
count: this.passwordMinLength,
|
||||
});
|
||||
}
|
||||
|
||||
get passwordMinLength() {
|
||||
return this.owner.admin || this.owner.isDeveloper
|
||||
? this.owner.siteSettings.min_admin_password_length
|
||||
: this.owner.siteSettings.min_password_length;
|
||||
}
|
||||
|
||||
@dependentKeyCompat
|
||||
get passwordValidation() {
|
||||
if (!this.owner.passwordRequired) {
|
||||
return validResult();
|
||||
}
|
||||
|
||||
if (this.rejectedPasswords.includes(this.owner.accountPassword)) {
|
||||
return failedResult({
|
||||
reason:
|
||||
this.rejectedPasswordsMessages.get(this.owner.accountPassword) ||
|
||||
i18n("user.password.common"),
|
||||
});
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (isEmpty(this.owner.accountPassword)) {
|
||||
return failedResult({
|
||||
message: i18n("user.password.required"),
|
||||
reason: this.owner.forceValidationReason
|
||||
? i18n("user.password.required")
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
// If too short
|
||||
if (this.owner.accountPassword.length < this.passwordMinLength) {
|
||||
return failedResult({
|
||||
reason: i18n("user.password.too_short", {
|
||||
count: this.passwordMinLength,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isEmpty(this.owner.accountUsername) &&
|
||||
this.owner.accountPassword === this.owner.accountUsername
|
||||
) {
|
||||
return failedResult({
|
||||
reason: i18n("user.password.same_as_username"),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isEmpty(this.owner.accountName) &&
|
||||
this.owner.accountPassword === this.owner.accountName
|
||||
) {
|
||||
return failedResult({
|
||||
reason: i18n("user.password.same_as_name"),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isEmpty(this.owner.accountEmail) &&
|
||||
this.owner.accountPassword === this.owner.accountEmail
|
||||
) {
|
||||
return failedResult({
|
||||
reason: i18n("user.password.same_as_email"),
|
||||
});
|
||||
}
|
||||
|
||||
return validResult({
|
||||
reason: i18n("user.password.ok"),
|
||||
});
|
||||
}
|
||||
}
|
@ -128,7 +128,6 @@
|
||||
<div class="input-group create-account__password">
|
||||
{{#if this.passwordRequired}}
|
||||
<PasswordField
|
||||
{{on "focusout" this.togglePasswordValidation}}
|
||||
{{on "focusin" this.scrollInputIntoView}}
|
||||
@value={{this.accountPassword}}
|
||||
@capsLockOn={{this.capsLockOn}}
|
||||
@ -160,7 +159,7 @@
|
||||
class="more-info"
|
||||
id="password-validation-more-info"
|
||||
>
|
||||
{{this.passwordInstructions}}
|
||||
{{this.passwordValidationHelper.passwordInstructions}}
|
||||
</span>
|
||||
{{/if}}
|
||||
<div
|
||||
|
Reference in New Issue
Block a user