diff --git a/app/assets/javascripts/discourse/app/components/modal/create-account.gjs b/app/assets/javascripts/discourse/app/components/modal/create-account.gjs
deleted file mode 100644
index 0200beba851..00000000000
--- a/app/assets/javascripts/discourse/app/components/modal/create-account.gjs
+++ /dev/null
@@ -1,898 +0,0 @@
-import { tracked } from "@glimmer/tracking";
-import { A } from "@ember/array";
-import Component, { Input } from "@ember/component";
-import { hash } from "@ember/helper";
-import { on } from "@ember/modifier";
-import EmberObject, { action } from "@ember/object";
-import { dependentKeyCompat } from "@ember/object/compat";
-import { service } from "@ember/service";
-import { htmlSafe } from "@ember/template";
-import { isEmpty } from "@ember/utils";
-import { observes } from "@ember-decorators/object";
-import { Promise } from "rsvp";
-import { and, not } from "truth-helpers";
-import DModal from "discourse/components/d-modal";
-import FullnameInput from "discourse/components/fullname-input";
-import HoneypotInput from "discourse/components/honeypot-input";
-import InputTip from "discourse/components/input-tip";
-import LoginButtons from "discourse/components/login-buttons";
-import PasswordField from "discourse/components/password-field";
-import PluginOutlet from "discourse/components/plugin-outlet";
-import SignupPageCta from "discourse/components/signup-page-cta";
-import SignupProgressBar from "discourse/components/signup-progress-bar";
-import TogglePasswordMask from "discourse/components/toggle-password-mask";
-import UserField from "discourse/components/user-field";
-import WelcomeHeader from "discourse/components/welcome-header";
-import concatClass from "discourse/helpers/concat-class";
-import icon from "discourse/helpers/d-icon";
-import loadingSpinner from "discourse/helpers/loading-spinner";
-import routeAction from "discourse/helpers/route-action";
-import valueEntered from "discourse/helpers/value-entered";
-import { ajax } from "discourse/lib/ajax";
-import { setting } from "discourse/lib/computed";
-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 UserFieldsValidationHelper from "discourse/lib/user-fields-validation-helper";
-import UsernameValidationHelper from "discourse/lib/username-validation-helper";
-import { emailValid } from "discourse/lib/utilities";
-import { findAll } from "discourse/models/login-method";
-import User from "discourse/models/user";
-import { i18n } from "discourse-i18n";
-
-export default class CreateAccount extends Component {
- @service site;
- @service siteSettings;
- @service login;
-
- @tracked isDeveloper = false;
- @tracked accountName = this.model.accountName;
- @tracked accountEmail = this.model.accountEmail;
- @tracked accountUsername = this.model.accountUsername;
- @tracked accountPassword = this.model.accountPassword;
- @tracked authOptions = this.model.authOptions;
- @tracked skipConfirmation = this.model.skipConfirmation;
- accountChallenge = 0;
- accountHoneypot = 0;
- formSubmitted = false;
- rejectedEmails = A();
- prefilledUsername = null;
- maskPassword = true;
- emailValidationVisible = false;
- nameValidationHelper = new NameValidationHelper(this);
- usernameValidationHelper = new UsernameValidationHelper({
- getAccountEmail: () => this.accountEmail,
- getAccountUsername: () => this.accountUsername,
- getPrefilledUsername: () => this.prefilledUsername,
- getAuthOptionsUsername: () => this.authOptions?.username,
- getForceValidationReason: () => this.forceValidationReason,
- siteSettings: this.siteSettings,
- isInvalid: () => this.isDestroying || this.isDestroyed,
- updateIsDeveloper: (isDeveloper) => (this.isDeveloper = isDeveloper),
- updateUsernames: (username) => {
- this.accountUsername = username;
- this.prefilledUsername = username;
- },
- });
- passwordValidationHelper = new PasswordValidationHelper(this);
- userFieldsValidationHelper = new UserFieldsValidationHelper({
- getUserFields: () => this.site.get("user_fields"),
- getAccountPassword: () => this.accountPassword,
- showValidationOnInit: false,
- });
-
- @setting("enable_local_logins") canCreateLocal;
- @setting("require_invite_code") requireInviteCode;
-
- init() {
- super.init(...arguments);
-
- if (cookie("email")) {
- this.accountEmail = cookie("email");
- }
-
- this.fetchConfirmationValue();
-
- if (this.model.skipConfirmation) {
- this.performAccountCreation().finally(
- () => (this.skipConfirmation = false)
- );
- }
- }
-
- @dependentKeyCompat
- get userFields() {
- return this.userFieldsValidationHelper.userFields;
- }
-
- @dependentKeyCompat
- get userFieldsValidation() {
- return this.userFieldsValidationHelper.userFieldsValidation;
- }
-
- @action
- setAccountUsername(event) {
- this.accountUsername = event.target.value;
- }
-
- @dependentKeyCompat
- get usernameValidation() {
- return this.usernameValidationHelper.usernameValidation;
- }
-
- get passwordValidation() {
- return this.passwordValidationHelper.passwordValidation;
- }
-
- get nameTitle() {
- return this.nameValidationHelper.nameTitle;
- }
-
- get nameValidation() {
- return this.nameValidationHelper.nameValidation;
- }
-
- @dependentKeyCompat
- get hasAuthOptions() {
- return !isEmpty(this.authOptions);
- }
-
- @dependentKeyCompat
- get forceValidationReason() {
- return this.nameValidationHelper.forceValidationReason;
- }
-
- @bind
- actionOnEnter(event) {
- if (!this.submitDisabled && event.key === "Enter") {
- event.preventDefault();
- event.stopPropagation();
- this.createAccount();
- return false;
- }
- }
-
- @bind
- selectKitFocus(event) {
- const target = document.getElementById(event.target.getAttribute("for"));
- if (target?.classList.contains("select-kit")) {
- event.preventDefault();
- target.querySelector(".select-kit-header").click();
- }
- }
-
- get showCreateForm() {
- return (
- (this.hasAuthOptions || this.canCreateLocal) && !this.skipConfirmation
- );
- }
-
- @discourseComputed("site.desktopView", "hasAuthOptions")
- showExternalLoginButtons(desktopView, hasAuthOptions) {
- return desktopView && !hasAuthOptions;
- }
-
- @discourseComputed("formSubmitted")
- submitDisabled() {
- return this.formSubmitted;
- }
-
- @discourseComputed("userFields", "hasAtLeastOneLoginButton", "hasAuthOptions")
- modalBodyClasses(userFields, hasAtLeastOneLoginButton, hasAuthOptions) {
- const classes = [];
- if (userFields) {
- classes.push("has-user-fields");
- }
- if (hasAtLeastOneLoginButton && !hasAuthOptions) {
- classes.push("has-alt-auth");
- }
- if (!this.canCreateLocal) {
- classes.push("no-local-logins");
- }
- return classes.join(" ");
- }
-
- get usernameDisabled() {
- return this.authOptions && !this.authOptions.can_edit_username;
- }
-
- get nameDisabled() {
- return this.authOptions && !this.authOptions.can_edit_name;
- }
-
- @discourseComputed
- showFullname() {
- return this.site.full_name_visible_in_signup;
- }
-
- @discourseComputed
- fullnameRequired() {
- return this.site.full_name_required_for_signup;
- }
-
- @discourseComputed(
- "emailValidation.ok",
- "emailValidation.reason",
- "emailValidationVisible"
- )
- showEmailValidation(
- emailValidationOk,
- emailValidationReason,
- emailValidationVisible
- ) {
- return (
- emailValidationOk || (emailValidationReason && emailValidationVisible)
- );
- }
-
- get showPasswordValidation() {
- return this.passwordValidation.ok || this.passwordValidation.reason;
- }
-
- get showUsernameInstructions() {
- return (
- this.siteSettings.show_signup_form_username_instructions &&
- !this.usernameValidation.reason
- );
- }
-
- get passwordRequired() {
- return isEmpty(this.authOptions?.auth_provider);
- }
-
- @discourseComputed
- disclaimerHtml() {
- if (this.site.tos_url && this.site.privacy_policy_url) {
- return i18n("create_account.disclaimer", {
- tos_link: this.site.tos_url,
- privacy_link: this.site.privacy_policy_url,
- });
- }
- }
-
- // Check the email address
- @discourseComputed(
- "serverAccountEmail",
- "serverEmailValidation",
- "accountEmail",
- "rejectedEmails.[]",
- "forceValidationReason"
- )
- emailValidation(
- serverAccountEmail,
- serverEmailValidation,
- email,
- rejectedEmails,
- forceValidationReason
- ) {
- const failedAttrs = {
- failed: true,
- ok: false,
- element: document.querySelector("#new-account-email"),
- };
-
- if (serverAccountEmail === email && serverEmailValidation) {
- return serverEmailValidation;
- }
-
- // If blank, fail without a reason
- if (isEmpty(email)) {
- return EmberObject.create(
- Object.assign(failedAttrs, {
- message: i18n("user.email.required"),
- reason: forceValidationReason ? i18n("user.email.required") : null,
- })
- );
- }
-
- if (rejectedEmails.includes(email) || !emailValid(email)) {
- return EmberObject.create(
- Object.assign(failedAttrs, {
- reason: i18n("user.email.invalid"),
- })
- );
- }
-
- if (this.authOptions?.email === email && this.authOptions?.email_valid) {
- return EmberObject.create({
- ok: true,
- reason: i18n("user.email.authenticated", {
- provider: this.authProviderDisplayName(
- this.authOptions?.auth_provider
- ),
- }),
- });
- }
-
- return EmberObject.create({
- ok: true,
- reason: i18n("user.email.ok"),
- });
- }
-
- @action
- checkEmailAvailability() {
- this.set("emailValidationVisible", Boolean(this.emailValidation.reason));
-
- if (
- !this.emailValidation.ok ||
- this.serverAccountEmail === this.accountEmail
- ) {
- return;
- }
-
- return User.checkEmail(this.accountEmail)
- .then((result) => {
- if (this.isDestroying || this.isDestroyed) {
- return;
- }
-
- if (result.failed) {
- this.setProperties({
- serverAccountEmail: this.accountEmail,
- serverEmailValidation: EmberObject.create({
- failed: true,
- element: document.querySelector("#new-account-email"),
- reason: result.errors[0],
- }),
- });
- } else {
- this.setProperties({
- serverAccountEmail: this.accountEmail,
- serverEmailValidation: EmberObject.create({
- ok: true,
- reason: i18n("user.email.ok"),
- }),
- });
- }
- })
- .catch(() => {
- this.setProperties({
- serverAccountEmail: null,
- serverEmailValidation: null,
- });
- });
- }
-
- get emailDisabled() {
- return (
- this.authOptions?.email === this.accountEmail &&
- this.authOptions?.email_valid
- );
- }
-
- authProviderDisplayName(providerName) {
- const matchingProvider = findAll().find((provider) => {
- return provider.name === providerName;
- });
- return matchingProvider ? matchingProvider.get("prettyName") : providerName;
- }
-
- @observes("emailValidation", "accountEmail")
- prefillUsername() {
- if (this.prefilledUsername) {
- // If username field has been filled automatically, and email field just changed,
- // then remove the username.
- if (this.accountUsername === this.prefilledUsername) {
- this.accountUsername = "";
- }
- this.set("prefilledUsername", null);
- }
- if (
- this.get("emailValidation.ok") &&
- (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,
- // then look for a registered username that matches the email.
- discourseDebounce(
- this,
- () => this.usernameValidationHelper.fetchExistingUsername(),
- 500
- );
- }
- }
-
- // Determines whether at least one login button is enabled
- @discourseComputed
- hasAtLeastOneLoginButton() {
- return findAll().length > 0;
- }
-
- fetchConfirmationValue() {
- if (this._challengeDate === undefined && this._hpPromise) {
- // Request already in progress
- return this._hpPromise;
- }
-
- this._hpPromise = ajax("/session/hp.json")
- .then((json) => {
- if (this.isDestroying || this.isDestroyed) {
- return;
- }
-
- this._challengeDate = new Date();
- // remove 30 seconds for jitter, make sure this works for at least
- // 30 seconds so we don't have hard loops
- this._challengeExpiry = parseInt(json.expires_in, 10) - 30;
- if (this._challengeExpiry < 30) {
- this._challengeExpiry = 30;
- }
-
- this.setProperties({
- accountHoneypot: json.value,
- accountChallenge: json.challenge.split("").reverse().join(""),
- });
- })
- .finally(() => (this._hpPromise = undefined));
-
- return this._hpPromise;
- }
-
- performAccountCreation() {
- if (
- !this._challengeDate ||
- new Date() - this._challengeDate > 1000 * this._challengeExpiry
- ) {
- return this.fetchConfirmationValue().then(() =>
- this.performAccountCreation()
- );
- }
-
- const attrs = {
- accountName: this.accountName,
- accountEmail: this.accountEmail,
- accountPassword: this.accountPassword,
- accountUsername: this.accountUsername,
- accountChallenge: this.accountChallenge,
- inviteCode: this.inviteCode,
- accountPasswordConfirm: this.accountHoneypot,
- };
-
- const destinationUrl = this.authOptions?.destination_url;
-
- if (!isEmpty(destinationUrl)) {
- cookie("destination_url", destinationUrl, { path: "/" });
- }
-
- // Add the userFields to the data
- if (!isEmpty(this.userFields)) {
- attrs.userFields = {};
- this.userFields.forEach((f) => (attrs.userFields[f.field.id] = f.value));
- }
-
- this.set("formSubmitted", true);
- return User.createAccount(attrs).then(
- (result) => {
- if (this.isDestroying || this.isDestroyed) {
- return;
- }
-
- this.isDeveloper = false;
- if (result.success) {
- // invalidate honeypot
- this._challengeExpiry = 1;
-
- // Trigger the browser's password manager using the hidden static login form:
- const hiddenLoginForm = document.querySelector("#hidden-login-form");
- if (hiddenLoginForm) {
- hiddenLoginForm.querySelector("input[name=username]").value =
- attrs.accountUsername;
- hiddenLoginForm.querySelector("input[name=password]").value =
- attrs.accountPassword;
- hiddenLoginForm.querySelector("input[name=redirect]").value =
- userPath("account-created");
- hiddenLoginForm.submit();
- }
- return new Promise(() => {}); // This will never resolve, the page will reload instead
- } else {
- this.set("flash", result.message || i18n("create_account.failed"));
- if (result.is_developer) {
- this.isDeveloper = true;
- }
- if (
- result.errors &&
- result.errors.email &&
- result.errors.email.length > 0 &&
- result.values
- ) {
- this.rejectedEmails.pushObject(result.values.email);
- }
- if (result.errors?.["user_password.password"]?.length > 0) {
- this.passwordValidationHelper.rejectedPasswords.push(
- attrs.accountPassword
- );
- }
- this.set("formSubmitted", false);
- removeCookie("destination_url");
- }
- },
- () => {
- this.set("formSubmitted", false);
- removeCookie("destination_url");
- return this.set("flash", i18n("create_account.failed"));
- }
- );
- }
-
- get associateHtml() {
- const url = this.authOptions?.associate_url;
- if (!url) {
- return;
- }
- return i18n("create_account.associate", {
- associate_link: url,
- provider: i18n(`login.${this.authOptions.auth_provider}.name`),
- });
- }
-
- @action
- scrollInputIntoView(event) {
- event.target.scrollIntoView({
- behavior: "smooth",
- block: "center",
- });
- }
-
- @action
- togglePasswordMask() {
- this.toggleProperty("maskPassword");
- }
-
- @action
- externalLogin(provider) {
- // we will automatically redirect to the external auth service
- this.login.externalLogin(provider, { signup: true });
- }
-
- @action
- createAccount() {
- this.set("flash", "");
- this.nameValidationHelper.forceValidationReason = true;
- this.userFieldsValidationHelper.validationVisible = true;
- this.set("emailValidationVisible", true);
-
- const validation = [
- this.emailValidation,
- this.usernameValidation,
- this.nameValidation,
- this.passwordValidation,
- this.userFieldsValidation,
- ].find((v) => v.failed);
-
- if (validation) {
- const element = validation.element;
- if (element) {
- if (element.tagName === "DIV") {
- if (element.scrollIntoView) {
- element.scrollIntoView();
- }
- element.click();
- } else {
- element.focus();
- }
- }
-
- return;
- }
-
- this.userFieldsValidationHelper.validationVisible = false;
- this.nameValidationHelper.forceValidationReason = false;
- this.performAccountCreation();
- }
-
-
- {{! template-lint-disable no-duplicate-id }}
-
- <:body>
-
-
-
-
-
-
-
- {{#if this.showCreateForm}}
-
-
- {{#if this.site.desktopView}}
-
-
-
- {{/if}}
- {{/if}}
-
- {{#if this.skipConfirmation}}
- {{loadingSpinner size="large"}}
- {{/if}}
-
-
- {{#if this.hasAtLeastOneLoginButton}}
- {{#if this.site.mobileView}}
-
- {{i18n "login.or"}}
{{/if}}
-
-
-
- {{/if}}
-
-
- <:footer>
- {{#if (and this.showCreateForm this.site.mobileView)}}
-
- {{/if}}
-
-
-
-}
diff --git a/app/assets/javascripts/discourse/app/components/modal/login.gjs b/app/assets/javascripts/discourse/app/components/modal/login.gjs
deleted file mode 100644
index 27766c6be0e..00000000000
--- a/app/assets/javascripts/discourse/app/components/modal/login.gjs
+++ /dev/null
@@ -1,526 +0,0 @@
-import Component from "@glimmer/component";
-import { tracked } from "@glimmer/tracking";
-import { hash } from "@ember/helper";
-import { on } from "@ember/modifier";
-import { action } from "@ember/object";
-import didInsert from "@ember/render-modifiers/modifiers/did-insert";
-import { schedule } from "@ember/runloop";
-import { service } from "@ember/service";
-import { htmlSafe } from "@ember/template";
-import { isEmpty } from "@ember/utils";
-import { and } from "truth-helpers";
-import DModal from "discourse/components/d-modal";
-import LocalLoginForm from "discourse/components/local-login-form";
-import LoginButtons from "discourse/components/login-buttons";
-import LoginPageCta from "discourse/components/login-page-cta";
-import PluginOutlet from "discourse/components/plugin-outlet";
-import WelcomeHeader from "discourse/components/welcome-header";
-import { ajax } from "discourse/lib/ajax";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import cookie, { removeCookie } from "discourse/lib/cookie";
-import escape from "discourse/lib/escape";
-import getURL from "discourse/lib/get-url";
-import { wantsNewWindow } from "discourse/lib/intercept-click";
-import { areCookiesEnabled } from "discourse/lib/utilities";
-import {
- getPasskeyCredential,
- isWebauthnSupported,
-} from "discourse/lib/webauthn";
-import { findAll } from "discourse/models/login-method";
-import { SECOND_FACTOR_METHODS } from "discourse/models/user";
-import { i18n } from "discourse-i18n";
-import ForgotPassword from "./forgot-password";
-
-export default class Login extends Component {
- @service capabilities;
- @service dialog;
- @service siteSettings;
- @service site;
- @service login;
- @service modal;
-
- @tracked loggingIn = false;
- @tracked loggedIn = false;
- @tracked showLoginButtons = true;
- @tracked showSecondFactor = false;
- @tracked loginPassword = "";
- @tracked loginName = "";
- @tracked flash = this.args.model.flash;
- @tracked flashType = this.args.model.flashType;
- @tracked canLoginLocal = this.siteSettings.enable_local_logins;
- @tracked
- canLoginLocalWithEmail = this.siteSettings.enable_local_logins_via_email;
- @tracked secondFactorMethod = SECOND_FACTOR_METHODS.TOTP;
- @tracked securityKeyCredential;
- @tracked otherMethodAllowed;
- @tracked secondFactorRequired;
- @tracked backupEnabled;
- @tracked totpEnabled;
- @tracked showSecurityKey;
- @tracked securityKeyChallenge;
- @tracked securityKeyAllowedCredentialIds;
- @tracked secondFactorToken;
-
- get awaitingApproval() {
- return (
- this.args.model.awaitingApproval &&
- !this.canLoginLocal &&
- !this.canLoginLocalWithEmail
- );
- }
-
- get loginDisabled() {
- return this.loggingIn || this.loggedIn;
- }
-
- get modalBodyClasses() {
- const classes = ["login-modal-body"];
- if (this.awaitingApproval) {
- classes.push("awaiting-approval");
- }
- if (
- this.hasAtLeastOneLoginButton &&
- !this.showSecondFactor &&
- !this.showSecurityKey
- ) {
- classes.push("has-alt-auth");
- }
- if (!this.canLoginLocal) {
- classes.push("no-local-login");
- }
- if (this.showSecondFactor || this.showSecurityKey) {
- classes.push("second-factor");
- }
- return classes.join(" ");
- }
-
- get canUsePasskeys() {
- return (
- this.siteSettings.enable_local_logins &&
- this.siteSettings.enable_passkeys &&
- isWebauthnSupported()
- );
- }
-
- get hasAtLeastOneLoginButton() {
- return findAll().length > 0 || this.canUsePasskeys;
- }
-
- get hasNoLoginOptions() {
- return !this.hasAtLeastOneLoginButton && !this.canLoginLocal;
- }
-
- get loginButtonLabel() {
- return this.loggingIn ? "login.logging_in" : "login.title";
- }
-
- get showSignupLink() {
- return this.args.model.canSignUp && !this.showSecondFactor;
- }
-
- get adminLoginPath() {
- return getURL("/u/admin-login");
- }
-
- @action
- async passkeyLogin(mediation = "optional") {
- try {
- const publicKeyCredential = await getPasskeyCredential(
- (e) => this.dialog.alert(e),
- mediation,
- this.capabilities.isFirefox
- );
-
- if (publicKeyCredential) {
- const authResult = await ajax("/session/passkey/auth.json", {
- type: "POST",
- data: { publicKeyCredential },
- });
-
- if (authResult && !authResult.error) {
- const destinationUrl = cookie("destination_url");
- const ssoDestinationUrl = cookie("sso_destination_url");
-
- if (ssoDestinationUrl) {
- removeCookie("sso_destination_url");
- window.location.assign(ssoDestinationUrl);
- } else if (destinationUrl) {
- removeCookie("destination_url");
- window.location.assign(destinationUrl);
- } else if (this.args.model.referrerTopicUrl) {
- window.location.assign(this.args.model.referrerTopicUrl);
- } else {
- window.location.reload();
- }
- } else {
- this.dialog.alert(authResult.error);
- }
- }
- } catch (e) {
- popupAjaxError(e);
- }
- }
-
- @action
- preloadLogin() {
- const prefillUsername = document.querySelector(
- "#hidden-login-form input[name=username]"
- )?.value;
- if (prefillUsername) {
- this.loginName = prefillUsername;
- this.loginPassword = document.querySelector(
- "#hidden-login-form input[name=password]"
- ).value;
- } else if (cookie("email")) {
- this.loginName = cookie("email");
- }
- }
-
- @action
- securityKeyCredentialChanged(value) {
- this.securityKeyCredential = value;
- }
-
- @action
- flashChanged(value) {
- this.flash = value;
- }
-
- @action
- flashTypeChanged(value) {
- this.flashType = value;
- }
-
- @action
- loginNameChanged(event) {
- this.loginName = event.target.value;
- }
-
- @action
- async triggerLogin() {
- if (this.loginDisabled) {
- return;
- }
-
- if (isEmpty(this.loginName) || isEmpty(this.loginPassword)) {
- this.flash = i18n("login.blank_username_or_password");
- this.flashType = "error";
- return;
- }
-
- try {
- this.loggingIn = true;
- const result = await ajax("/session", {
- type: "POST",
- data: {
- login: this.loginName,
- password: this.loginPassword,
- second_factor_token:
- this.securityKeyCredential || this.secondFactorToken,
- second_factor_method: this.secondFactorMethod,
- timezone: moment.tz.guess(),
- },
- });
- if (result && result.error) {
- this.loggingIn = false;
- this.flash = null;
-
- if (
- (result.security_key_enabled || result.totp_enabled) &&
- !this.secondFactorRequired
- ) {
- this.otherMethodAllowed = result.multiple_second_factor_methods;
- this.secondFactorRequired = true;
- this.showLoginButtons = false;
- this.backupEnabled = result.backup_enabled;
- this.totpEnabled = result.totp_enabled;
- this.showSecondFactor = result.totp_enabled;
- this.showSecurityKey = result.security_key_enabled;
- this.secondFactorMethod = result.security_key_enabled
- ? SECOND_FACTOR_METHODS.SECURITY_KEY
- : SECOND_FACTOR_METHODS.TOTP;
- this.securityKeyChallenge = result.challenge;
- this.securityKeyAllowedCredentialIds = result.allowed_credential_ids;
-
- // only need to focus the 2FA input for TOTP
- if (!this.showSecurityKey) {
- schedule("afterRender", () =>
- document
- .getElementById("second-factor")
- .querySelector("input")
- .focus()
- );
- }
-
- return;
- } else if (result.reason === "not_activated") {
- this.args.model.showNotActivated({
- username: this.loginName,
- sentTo: escape(result.sent_to_email),
- currentEmail: escape(result.current_email),
- });
- } else if (result.reason === "suspended") {
- this.args.closeModal();
- this.dialog.alert(result.error);
- } else if (result.reason === "expired") {
- this.flash = htmlSafe(
- i18n("login.password_expired", {
- reset_url: getURL("/password-reset"),
- })
- );
- this.flashType = "error";
- } else {
- this.flash = result.error;
- this.flashType = "error";
- }
- } else {
- this.loggedIn = true;
- // Trigger the browser's password manager using the hidden static login form:
- const hiddenLoginForm = document.getElementById("hidden-login-form");
- const applyHiddenFormInputValue = (value, key) => {
- if (!hiddenLoginForm) {
- return;
- }
-
- hiddenLoginForm.querySelector(`input[name=${key}]`).value = value;
- };
-
- const destinationUrl = cookie("destination_url");
- const ssoDestinationUrl = cookie("sso_destination_url");
-
- applyHiddenFormInputValue(this.loginName, "username");
- applyHiddenFormInputValue(this.loginPassword, "password");
-
- if (ssoDestinationUrl) {
- removeCookie("sso_destination_url");
- window.location.assign(ssoDestinationUrl);
- return;
- } else if (destinationUrl) {
- // redirect client to the original URL
- removeCookie("destination_url");
-
- applyHiddenFormInputValue(destinationUrl, "redirect");
- } else if (this.args.model.referrerTopicUrl) {
- applyHiddenFormInputValue(
- this.args.model.referrerTopicUrl,
- "redirect"
- );
- } else {
- applyHiddenFormInputValue(window.location.href, "redirect");
- }
-
- if (hiddenLoginForm) {
- if (
- navigator.userAgent.match(/(iPad|iPhone|iPod)/g) &&
- navigator.userAgent.match(/Safari/g)
- ) {
- // In case of Safari on iOS do not submit hidden login form
- window.location.href = hiddenLoginForm.querySelector(
- "input[name=redirect]"
- ).value;
- } else {
- hiddenLoginForm.submit();
- }
- }
- return;
- }
- } catch (e) {
- // Failed to login
- if (e.jqXHR && e.jqXHR.status === 429) {
- this.flash = i18n("login.rate_limit");
- this.flashType = "error";
- } else if (
- e.jqXHR &&
- e.jqXHR.status === 503 &&
- e.jqXHR.responseJSON.error_type === "read_only"
- ) {
- this.flash = i18n("read_only_mode.login_disabled");
- this.flashType = "error";
- } else if (!areCookiesEnabled()) {
- this.flash = i18n("login.cookies_error");
- this.flashType = "error";
- } else {
- this.flash = i18n("login.error");
- this.flashType = "error";
- }
- this.loggingIn = false;
- }
- }
-
- @action
- externalLoginAction(loginMethod) {
- if (this.loginDisabled) {
- return;
- }
- this.login.externalLogin(loginMethod, {
- signup: false,
- setLoggingIn: (value) => (this.loggingIn = value),
- });
- }
-
- @action
- createAccount() {
- let createAccountProps = {};
- if (this.loginName && this.loginName.indexOf("@") > 0) {
- createAccountProps.accountEmail = this.loginName;
- createAccountProps.accountUsername = null;
- } else {
- createAccountProps.accountUsername = this.loginName;
- createAccountProps.accountEmail = null;
- }
- this.args.model.showCreateAccount(createAccountProps);
- }
-
- @action
- interceptResetLink(event) {
- if (
- !wantsNewWindow(event) &&
- event.target.href &&
- new URL(event.target.href).pathname === getURL("/password-reset")
- ) {
- event.preventDefault();
- event.stopPropagation();
- this.modal.show(ForgotPassword, {
- model: {
- emailOrUsername: this.loginName,
- },
- });
- }
- }
-
-
-
- <:body>
-
-
- {{#if this.hasNoLoginOptions}}
-
-
-
- {{else}}
- {{#if this.site.mobileView}}
-
-
-
- {{#if this.showLoginButtons}}
-
- {{/if}}
- {{/if}}
-
- {{#if this.canLoginLocal}}
-
- {{#if this.site.desktopView}}
-
-
-
- {{/if}}
-
- {{#if this.site.desktopView}}
-
- {{/if}}
-
- {{/if}}
-
- {{#if (and this.showLoginButtons this.site.desktopView)}}
- {{#unless this.canLoginLocal}}
-
-
-
- {{/unless}}
- {{#if this.hasAtLeastOneLoginButton}}
-
-
-
- {{/if}}
- {{/if}}
- {{/if}}
-
-
- <:footer>
- {{#if this.site.mobileView}}
- {{#unless this.hasNoLoginOptions}}
-
- {{/unless}}
- {{/if}}
-
-
-
-}
diff --git a/app/assets/javascripts/discourse/app/controllers/login.js b/app/assets/javascripts/discourse/app/controllers/login.js
index cef5a44105d..71ce31bf638 100644
--- a/app/assets/javascripts/discourse/app/controllers/login.js
+++ b/app/assets/javascripts/discourse/app/controllers/login.js
@@ -118,9 +118,7 @@ export default class LoginPageController extends Controller {
get shouldTriggerRouteAction() {
return (
- !this.siteSettings.full_page_login ||
- this.siteSettings.enable_discourse_connect ||
- this.singleExternalLogin
+ this.siteSettings.enable_discourse_connect || this.singleExternalLogin
);
}
diff --git a/app/assets/javascripts/discourse/app/instance-initializers/auth-complete.js b/app/assets/javascripts/discourse/app/instance-initializers/auth-complete.js
index 1be0379df5f..e51dc843b58 100644
--- a/app/assets/javascripts/discourse/app/instance-initializers/auth-complete.js
+++ b/app/assets/javascripts/discourse/app/instance-initializers/auth-complete.js
@@ -1,8 +1,6 @@
import EmberObject from "@ember/object";
import { next } from "@ember/runloop";
import { htmlSafe } from "@ember/template";
-import CreateAccount from "discourse/components/modal/create-account";
-import LoginModal from "discourse/components/modal/login";
import cookie, { removeCookie } from "discourse/lib/cookie";
import DiscourseUrl from "discourse/lib/url";
import { i18n } from "discourse-i18n";
@@ -52,11 +50,9 @@ export default {
.lookup("controller:invites-show")
.authenticationComplete(options);
} else {
- const modal = owner.lookup("service:modal");
const siteSettings = owner.lookup("service:site-settings");
const loginError = (errorMsg, className, properties, callback) => {
- const applicationRoute = owner.lookup("route:application");
const applicationController = owner.lookup(
"controller:application"
);
@@ -69,23 +65,12 @@ export default {
...properties,
};
- if (siteSettings.full_page_login) {
- router.transitionTo("login").then((login) => {
- Object.keys(loginProps || {}).forEach((key) => {
- login.controller.set(key, loginProps[key]);
- });
+ router.transitionTo("login").then((login) => {
+ Object.keys(loginProps || {}).forEach((key) => {
+ login.controller.set(key, loginProps[key]);
});
- } else {
- modal.show(LoginModal, {
- model: {
- showNotActivated: (props) =>
- applicationRoute.send("showNotActivated", props),
- showCreateAccount: (props) =>
- applicationRoute.send("showCreateAccount", props),
- ...loginProps,
- },
- });
- }
+ });
+
next(() => callback?.());
};
@@ -139,16 +124,12 @@ export default {
skipConfirmation: siteSettings.auth_skip_create_confirm,
};
- if (siteSettings.full_page_login) {
- router.transitionTo("signup").then((signup) => {
- const signupController =
- signup.controller || owner.lookup("controller:signup");
- Object.assign(signupController, createAccountProps);
- signupController.handleSkipConfirmation();
- });
- } else {
- modal.show(CreateAccount, { model: createAccountProps });
- }
+ router.transitionTo("signup").then((signup) => {
+ const signupController =
+ signup.controller || owner.lookup("controller:signup");
+ Object.assign(signupController, createAccountProps);
+ signupController.handleSkipConfirmation();
+ });
});
}
});
diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js
index d9d78bdafef..81f4bcb7cd8 100644
--- a/app/assets/javascripts/discourse/app/routes/application.js
+++ b/app/assets/javascripts/discourse/app/routes/application.js
@@ -1,8 +1,6 @@
import { action } from "@ember/object";
import { service } from "@ember/service";
-import CreateAccount from "discourse/components/modal/create-account";
import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts-help";
-import LoginModal from "discourse/components/modal/login";
import NotActivatedModal from "discourse/components/modal/not-activated";
import { RouteException } from "discourse/controllers/exception";
import { setting } from "discourse/lib/computed";
@@ -270,24 +268,13 @@ export default class ApplicationRoute extends DiscourseRoute {
} else {
if (this.login.isOnlyOneExternalLoginMethod) {
this.login.singleExternalLogin();
- } else if (this.siteSettings.full_page_login) {
+ } else {
this.router.transitionTo("login").then((login) => {
login.controller.set("canSignUp", this.controller.canSignUp);
if (this.siteSettings.login_required) {
login.controller.set("showLogin", true);
}
});
- } else {
- this.modal.show(LoginModal, {
- model: {
- showNotActivated: (props) => this.send("showNotActivated", props),
- showCreateAccount: (props) => this.send("showCreateAccount", props),
- canSignUp: this.controller.canSignUp,
- referrerTopicUrl: DiscourseURL.isInternalTopic(document.referrer)
- ? document.referrer
- : null,
- },
- });
}
}
}
@@ -300,14 +287,12 @@ export default class ApplicationRoute extends DiscourseRoute {
if (this.login.isOnlyOneExternalLoginMethod) {
// we will automatically redirect to the external auth service
this.login.singleExternalLogin({ signup: true });
- } else if (this.siteSettings.full_page_login) {
+ } else {
this.router.transitionTo("signup").then((signup) => {
Object.keys(createAccountProps || {}).forEach((key) => {
signup.controller.set(key, createAccountProps[key]);
});
});
- } else {
- this.modal.show(CreateAccount, { model: createAccountProps });
}
}
}
diff --git a/app/assets/javascripts/discourse/app/routes/login.js b/app/assets/javascripts/discourse/app/routes/login.js
index 03be3d8a43d..ac6d35cc3ff 100644
--- a/app/assets/javascripts/discourse/app/routes/login.js
+++ b/app/assets/javascripts/discourse/app/routes/login.js
@@ -24,15 +24,9 @@ export default class LoginRoute extends DiscourseRoute {
) {
this.login.singleExternalLogin();
}
- } else if (
- this.login.isOnlyOneExternalLoginMethod &&
- this.siteSettings.full_page_login
- ) {
+ } else if (this.login.isOnlyOneExternalLoginMethod) {
this.login.singleExternalLogin();
- } else if (
- !this.siteSettings.full_page_login ||
- this.siteSettings.enable_discourse_connect
- ) {
+ } else if (this.siteSettings.enable_discourse_connect) {
this.router
.replaceWith(`/${defaultHomepage()}`)
.followRedirects()
diff --git a/app/assets/javascripts/discourse/app/routes/signup.js b/app/assets/javascripts/discourse/app/routes/signup.js
index f32ee482571..adb654dd796 100644
--- a/app/assets/javascripts/discourse/app/routes/signup.js
+++ b/app/assets/javascripts/discourse/app/routes/signup.js
@@ -33,16 +33,15 @@ export default class SignupRoute extends DiscourseRoute {
@action
async showCreateAccount() {
const { canSignUp } = this.controllerFor("application");
- if (canSignUp && this.siteSettings.full_page_login) {
- return;
- }
- const route = await this.router
- .replaceWith(
- this.siteSettings.login_required ? "login" : "discovery.latest"
- )
- .followRedirects();
- if (canSignUp) {
- next(() => route.send("showCreateAccount"));
+ if (!canSignUp) {
+ const route = await this.router
+ .replaceWith(
+ this.siteSettings.login_required ? "login" : "discovery.latest"
+ )
+ .followRedirects();
+ if (canSignUp) {
+ next(() => route.send("showCreateAccount"));
+ }
}
}
}
diff --git a/app/assets/javascripts/discourse/app/templates/login.gjs b/app/assets/javascripts/discourse/app/templates/login.gjs
index cc8a4da3a98..c180854188d 100644
--- a/app/assets/javascripts/discourse/app/templates/login.gjs
+++ b/app/assets/javascripts/discourse/app/templates/login.gjs
@@ -29,12 +29,7 @@ export default RouteTemplate(
{{loadingSpinner}}
{{else}}
{{#if
- (and
- @controller.siteSettings.full_page_login
- (or
- @controller.showLogin (not @controller.siteSettings.login_required)
- )
- )
+ (or @controller.showLogin (not @controller.siteSettings.login_required))
}}
{{! Show the full page login form }}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js
deleted file mode 100644
index 979e8d9cd6d..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/login-redirect-test.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { currentRouteName, visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import { acceptance } from "discourse/tests/helpers/qunit-helpers";
-
-acceptance("Login redirect - anonymous", function (needs) {
- needs.settings({ full_page_login: false });
-
- test("redirects login to default homepage", async function (assert) {
- await visit("/login");
- assert.strictEqual(
- currentRouteName(),
- "discovery.latest",
- "it works when latest is the homepage"
- );
- });
-});
-
-acceptance("Login redirect - categories default", function (needs) {
- needs.settings({
- top_menu: "categories|latest|top|hot",
- full_page_login: false,
- });
-
- test("when site setting is categories", async function (assert) {
- await visit("/login");
- assert.strictEqual(
- currentRouteName(),
- "discovery.categories",
- "it works when categories is the homepage"
- );
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js
index e0bb085f31b..85c600e0262 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/login-required-test.js
@@ -2,27 +2,8 @@ import { click, currentRouteName, visit } from "@ember/test-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
-acceptance("Login Required", function (needs) {
- needs.settings({ login_required: true, full_page_login: false });
-
- test("redirect", async function (assert) {
- await visit("/latest");
- assert.strictEqual(
- currentRouteName(),
- "login",
- "it redirects them to login"
- );
-
- await click(".login-button");
- assert.dom(".login-modal").exists("login modal is shown");
-
- await click(".d-modal__header .modal-close");
- assert.dom(".login-modal").doesNotExist("closes the login modal");
- });
-});
-
acceptance("Login Required - Full page login", function (needs) {
- needs.settings({ login_required: true, full_page_login: true });
+ needs.settings({ login_required: true });
test("page", async function (assert) {
await visit("/");
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js
deleted file mode 100644
index 69ffe2e1434..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-test.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { click, fillIn, tab, visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import sinon from "sinon";
-import { acceptance, chromeTest } from "discourse/tests/helpers/qunit-helpers";
-import { i18n } from "discourse-i18n";
-
-acceptance("Modal - Login", function (needs) {
- needs.settings({
- full_page_login: false,
- });
-
- chromeTest("You can tab to the login button", async function (assert) {
- await visit("/");
- await click("header .login-button");
- // you have to press the tab key thrice to get to the login button
- await tab({ unRestrainTabIndex: true });
- await tab({ unRestrainTabIndex: true });
- await tab({ unRestrainTabIndex: true });
- assert.dom(".d-modal__footer #login-button").isFocused();
- });
-});
-
-acceptance("Modal - Login - With 2FA", function (needs) {
- needs.settings({
- enable_local_logins_via_email: true,
- full_page_login: false,
- });
-
- needs.pretender((server, helper) => {
- server.post(`/session`, () =>
- helper.response({
- error: i18n("login.invalid_second_factor_code"),
- multiple_second_factor_methods: false,
- security_key_enabled: false,
- totp_enabled: true,
- })
- );
- });
-
- test("You can tab to 2FA login button", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- await fillIn("#login-account-name", "isaac@discourse.org");
- await fillIn("#login-account-password", "password");
- await click("#login-button");
-
- assert.dom("#login-second-factor").isFocused();
- await tab();
- assert.dom("#login-button").isFocused();
- });
-});
-
-acceptance("Login - With Passkeys enabled", function () {
- test("Includes passkeys button and conditional UI", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert.dom(".passkey-login-button").exists();
-
- assert
- .dom("#login-account-name")
- .hasAttribute("autocomplete", "username webauthn");
- });
-});
-
-acceptance("Modal - Login - With Passkeys disabled", function (needs) {
- needs.settings({
- enable_passkeys: false,
- });
-
- test("Excludes passkeys button and conditional UI", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert.dom(".passkey-login-button").doesNotExist();
- assert.dom("#login-account-name").hasAttribute("autocomplete", "username");
- });
-});
-
-acceptance("Login - Passkeys on mobile", function (needs) {
- needs.mobileView();
-
- test("Includes passkeys button and conditional UI", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- sinon.stub(navigator.credentials, "get").callsFake(function () {
- return Promise.reject(new Error("credentials.get got called"));
- });
-
- assert
- .dom("#login-account-name")
- .hasAttribute("autocomplete", "username webauthn");
-
- await click(".passkey-login-button");
-
- // clicking the button triggers credentials.get
- // but we can't really test that in frontend so an error is returned
- assert.dom(".dialog-body").exists();
- });
-});
-
-acceptance("Login - With no way to login", function (needs) {
- needs.settings({
- enable_local_logins: false,
- enable_facebook_logins: false,
- });
- needs.site({ auth_providers: [] });
-
- test("Displays a helpful message", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert.dom("#login-account-name").doesNotExist();
- assert.dom("#login-button").doesNotExist();
- assert.dom(".no-login-methods-configured").exists();
- });
-});
-
-acceptance("Login button", function () {
- test("with custom event on webview", async function (assert) {
- const capabilities = this.container.lookup("service:capabilities");
- sinon.stub(capabilities, "isAppWebview").value(true);
-
- window.ReactNativeWebView = {
- postMessage: () => {},
- };
-
- const webviewSpy = sinon.spy(window.ReactNativeWebView, "postMessage");
-
- await visit("/");
- await click("header .login-button");
-
- assert.true(
- webviewSpy.withArgs('{"showLogin":true}').calledOnce,
- "triggers postmessage event"
- );
-
- delete window.ReactNativeWebView;
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-2fa-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-2fa-test.js
deleted file mode 100644
index 59f3728f61c..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-2fa-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-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("user.second_factor.title"));
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-hide-email-address-taken-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-hide-email-address-taken-test.js
deleted file mode 100644
index 44d9b5dbf6d..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-hide-email-address-taken-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-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";
-
-acceptance("Login with email - hide email address taken", function (needs) {
- needs.settings({
- enable_local_logins_via_email: true,
- hide_email_address_taken: true,
- });
-
- needs.pretender((server, helper) => {
- server.post("/u/email-login", () => {
- return helper.response({ success: "OK" });
- });
- });
-
- test("with hide_email_address_taken enabled", async function (assert) {
- await visit("/");
- await click("header .login-button");
- await fillIn("#login-account-name", "someuser@example.com");
- await click("#email-login-link");
-
- assert.dom(".alert-success").hasHtml(
- i18n("email_login.complete_email_found", {
- email: "someuser@example.com",
- }),
- "displays the success message for any email address"
- );
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-no-social-logins-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-no-social-logins-test.js
deleted file mode 100644
index ff0b9850773..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-and-no-social-logins-test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { click, visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import { acceptance } from "discourse/tests/helpers/qunit-helpers";
-
-acceptance("Login with email - no social logins", function (needs) {
- needs.settings({ enable_local_logins_via_email: true });
- needs.pretender((server, helper) => {
- server.post("/u/email-login", () => helper.response({ success: "OK" }));
- });
-
- test("with login with email enabled", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert.dom("#email-login-link").exists();
- });
-
- test("with login with email disabled", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert.dom(".login-buttons").doesNotExist();
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-disabled-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-disabled-test.js
deleted file mode 100644
index c04576b53a9..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-disabled-test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { click, visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import { acceptance } from "discourse/tests/helpers/qunit-helpers";
-
-acceptance("Login with email disabled", function (needs) {
- needs.settings({
- enable_local_logins_via_email: false,
- enable_facebook_logins: true,
- });
-
- test("with email button", async function (assert) {
- await visit("/");
- await click("header .login-button");
-
- assert
- .dom(".btn-social.facebook")
- .exists("it displays the facebook login button");
-
- assert
- .dom("#email-login-link")
- .doesNotExist("it displays the login with email button");
- });
-});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-test.js b/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-test.js
deleted file mode 100644
index d237c67af62..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/modal/login/login-with-email-test.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { click, fillIn, visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import sinon from "sinon";
-import DiscourseURL from "discourse/lib/url";
-import { acceptance } from "discourse/tests/helpers/qunit-helpers";
-import { i18n } from "discourse-i18n";
-
-const TOKEN = "sometoken";
-
-acceptance("Login with email", function (needs) {
- needs.settings({
- enable_local_logins_via_email: true,
- enable_facebook_logins: true,
- });
-
- let userFound = false;
- needs.pretender((server, helper) => {
- 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) {
- await visit("/");
- await click("header .login-button");
-
- assert
- .dom(".btn-social.facebook")
- .exists("it displays the facebook login button");
-
- assert
- .dom("#email-login-link")
- .exists("it displays the login with email button");
-
- await fillIn("#login-account-name", "someuser");
- await click("#email-login-link");
-
- assert.dom(".alert-error").hasHtml(
- i18n("email_login.complete_username_not_found", {
- username: "someuser",
- }),
- "displays an error for an invalid username"
- );
-
- await fillIn("#login-account-name", "someuser@gmail.com");
- await click("#email-login-link");
-
- assert.dom(".alert-error").hasHtml(
- i18n("email_login.complete_email_not_found", {
- email: "someuser@gmail.com",
- }),
- "displays an error for an invalid email"
- );
-
- await fillIn("#login-account-name", "someuser");
-
- userFound = true;
-
- await click("#email-login-link");
-
- assert
- .dom(".alert-success")
- .hasHtml(
- i18n("email_login.complete_username_found", { username: "someuser" }),
- "displays a success message for a valid username"
- );
-
- await visit("/");
- await click("header .login-button");
- await fillIn("#login-account-name", "someuser@gmail.com");
- await click("#email-login-link");
-
- assert.dom(".alert-success").hasHtml(
- i18n("email_login.complete_email_found", {
- email: "someuser@gmail.com",
- }),
- "displays a success message for a valid email"
- );
-
- 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/assets/javascripts/discourse/tests/acceptance/static-test.js b/app/assets/javascripts/discourse/tests/acceptance/static-test.js
index 9eb3c7283e2..c89102f576a 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/static-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/static-test.js
@@ -41,17 +41,6 @@ acceptance("Static pages", function () {
assert.dom(".body-page").exists("The content is present");
});
- test("Login redirect", async function (assert) {
- this.siteSettings.full_page_login = false;
- await visit("/login");
-
- assert.strictEqual(
- currentRouteName(),
- "discovery.latest",
- "it redirects to /latest"
- );
- });
-
test("Login-required page", async function (assert) {
this.siteSettings.login_required = true;
await visit("/login");
@@ -61,24 +50,4 @@ acceptance("Static pages", function () {
assert.dom(".sign-up-button").exists();
assert.dom(".login-button").exists();
});
-
- test("Signup redirect", async function (assert) {
- this.siteSettings.full_page_login = false;
- await visit("/signup");
-
- assert.strictEqual(
- currentRouteName(),
- "discovery.latest",
- "it redirects to /latest"
- );
- });
-
- test("Signup redirect with login_required", async function (assert) {
- this.siteSettings.full_page_login = false;
- this.siteSettings.login_required = true;
- await visit("/signup");
-
- assert.strictEqual(currentRouteName(), "login");
- assert.dom(".body-page").exists("The content is present");
- });
});
diff --git a/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js b/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js
deleted file mode 100644
index bb80e023108..00000000000
--- a/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { getOwner } from "@ember/owner";
-import { settled } from "@ember/test-helpers";
-import { setupTest } from "ember-qunit";
-import { module, test } from "qunit";
-import { i18n } from "discourse-i18n";
-
-module("Unit | Component | create-account", function (hooks) {
- setupTest(hooks);
-
- test("basicUsernameValidation", function (assert) {
- const testInvalidUsername = (username, expectedReason) => {
- const component = this.owner
- .factoryFor("component:modal/create-account")
- .create({ model: { accountUsername: username } });
-
- const validation =
- component.usernameValidationHelper.basicUsernameValidation(username);
- assert.true(validation.failed, `username should be invalid: ${username}`);
- assert.strictEqual(
- validation.reason,
- expectedReason,
- `username validation reason: ${username}, ${expectedReason}`
- );
- };
-
- testInvalidUsername("", null);
- testInvalidUsername("x", i18n("user.username.too_short"));
- testInvalidUsername(
- "123456789012345678901",
- i18n("user.username.too_long")
- );
-
- const component = this.owner
- .factoryFor("component:modal/create-account")
- .create({ model: { accountUsername: "porkchops" } });
- component.set("prefilledUsername", "porkchops");
-
- const validation =
- component.usernameValidationHelper.basicUsernameValidation("porkchops");
- assert.true(validation.ok, "Prefilled username is valid");
- assert.strictEqual(
- validation.reason,
- i18n("user.username.prefilled"),
- "Prefilled username is valid"
- );
- });
-
- test("passwordValidation", async function (assert) {
- const component = this.owner
- .factoryFor("component:modal/create-account")
- .create({
- model: {
- accountEmail: "pork@chops.com",
- accountUsername: "porkchops123",
- },
- });
-
- component.set("prefilledUsername", "porkchops123");
- component.set("accountPassword", "b4fcdae11f9167");
- assert.true(component.passwordValidation.ok, "Password is ok");
- assert.strictEqual(
- component.passwordValidation.reason,
- i18n("user.password.ok"),
- "Password is valid"
- );
-
- const testInvalidPassword = (password, expectedReason) => {
- component.set("accountPassword", password);
-
- assert.true(
- component.passwordValidation.failed,
- `password should be invalid: ${password}`
- );
- assert.strictEqual(
- component.passwordValidation.reason,
- expectedReason,
- `password validation reason: ${password}, ${expectedReason}`
- );
- };
-
- const siteSettings = getOwner(this).lookup("service:site-settings");
- testInvalidPassword("", null);
- testInvalidPassword(
- "x",
- i18n("user.password.too_short", {
- count: siteSettings.min_password_length,
- })
- );
- testInvalidPassword("porkchops123", i18n("user.password.same_as_username"));
- testInvalidPassword("pork@chops.com", i18n("user.password.same_as_email"));
-
- // Wait for username check request to finish
- await settled();
- });
-
- test("authProviderDisplayName", function (assert) {
- const component = this.owner
- .factoryFor("component:modal/create-account")
- .create({ model: {} });
-
- assert.strictEqual(
- component.authProviderDisplayName("facebook"),
- i18n("login.facebook.name"),
- "provider name is translated correctly"
- );
-
- assert.strictEqual(
- component.authProviderDisplayName("does-not-exist"),
- "does-not-exist",
- "provider name falls back if not found"
- );
- });
-});
diff --git a/app/assets/stylesheets/common/modal/_index.scss b/app/assets/stylesheets/common/modal/_index.scss
index 0884805a4b1..fef1f68dc99 100644
--- a/app/assets/stylesheets/common/modal/_index.scss
+++ b/app/assets/stylesheets/common/modal/_index.scss
@@ -1,3 +1,2 @@
-@import "login-modal";
@import "modal-overrides";
@import "user-status";
diff --git a/app/assets/stylesheets/common/modal/login-modal.scss b/app/assets/stylesheets/common/modal/login-modal.scss
deleted file mode 100644
index 0155c8588f1..00000000000
--- a/app/assets/stylesheets/common/modal/login-modal.scss
+++ /dev/null
@@ -1,446 +0,0 @@
-.d-modal.login-modal,
-.d-modal.create-account {
- &:not(:has(.login-right-side)) .d-modal__container {
- max-width: 500px;
- }
-
- &.awaiting-approval {
- display: none;
- }
-
- .d-modal {
- &__container {
- position: relative;
- width: 100%;
- }
-
- &__header {
- border-bottom: none;
- padding: 0;
- position: absolute;
- top: 0.75em;
- right: 0.75em;
- z-index: z("max");
- }
-
- &__body {
- display: flex;
- gap: 2rem;
- padding: 0;
- }
-
- &__footer {
- flex-wrap: nowrap;
- padding: 0;
- border: 0;
-
- .inline-spinner {
- display: inline-flex;
- }
- }
- }
-
- .login-left-side {
- box-sizing: border-box;
- width: 100%;
- padding: 3rem;
- overflow: auto;
- }
-
- .login-right-side {
- background: var(--tertiary-or-tertiary-low);
- padding: 3.5rem 3rem;
- max-width: 16em;
- }
-
- .btn-social-title {
- @include ellipsis;
- }
-
- #login-buttons {
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 1rem;
- height: 100%;
- white-space: nowrap;
- }
-
- #login-form {
- margin: 2em 0 1em 0;
- display: flex;
- flex-direction: column;
-
- .create-account-associate-link {
- order: 1;
- }
- }
-
- .tip {
- font-size: var(--font-down-1);
- min-height: 1.4em;
- display: block;
-
- &.bad {
- color: var(--danger);
- }
- }
-
- .toggle-password-mask span {
- font-size: var(--font-down-1-rem);
- }
-
- .more-info,
- .instructions {
- font-size: var(--font-down-1);
- color: var(--primary-medium);
- overflow-wrap: anywhere;
- }
-
- .caps-lock-warning {
- color: var(--danger);
- font-size: var(--font-down-1);
- font-weight: bold;
- margin-top: 0.5em;
- }
-
- #modal-alert {
- box-sizing: border-box;
- display: inline-block;
-
- // if you want to use flexbox here make sure child elements like
don't cause line breaks
- padding: 1em 3.5em 1em 1em; // large right padding to make sure long text isn't under the close button
- width: 100%;
- max-width: 100%;
- margin-bottom: 0;
- min-height: 35px;
-
- &:empty {
- min-height: 0;
- padding: 0;
- overflow: hidden;
- display: inline;
- }
- }
-
- .login-page-cta,
- .signup-page-cta {
- width: 100%;
- display: flex;
- flex-direction: column;
-
- &__disclaimer {
- color: var(--primary-medium);
- margin-bottom: 1rem;
- }
-
- &__buttons {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
-
- button {
- font-size: var(--font-0) !important;
- width: 100%;
- }
- }
-
- &__existing-account,
- &__no-account-yet {
- color: var(--primary-medium);
- font-size: var(--font-down);
- margin-bottom: 0.5rem;
- text-align: center;
- width: 100%;
-
- &::before {
- content: " ";
- display: block;
- height: 1px;
- width: 100%;
- background-color: var(--primary-low);
- margin-block: 1rem;
- }
- }
- }
-
- .desktop-view & {
- @media screen and (width <= 767px) {
- // important to maintain narrow desktop widths
- // for auth modals in Discourse Hub on iPad
- .d-modal__header {
- right: 0;
- top: 0;
- }
-
- .has-alt-auth {
- flex-direction: column;
- overflow: auto;
- gap: 0;
-
- .d-modal__footer,
- .btn-social {
- font-size: var(--font-down-1);
- }
-
- .login-left-side {
- overflow: unset;
- padding: 1em;
- }
-
- .login-right-side {
- padding: 1em;
- max-width: unset;
- }
-
- #login-form {
- margin: 1.5em 0;
- }
-
- .signup-progress-bar {
- display: none;
- }
- }
- }
- }
-}
-
-.invites-show {
- #account-email-validation:not(:has(svg)) {
- display: none;
- }
-}
-
-.d-modal.create-account {
- .d-modal {
- &__container {
- width: 100%;
- }
-
- &__footer {
- flex-direction: column;
- align-items: flex-start;
- }
-
- &__footer-buttons {
- display: flex;
- flex-direction: column;
- width: 100%;
- align-items: center;
-
- .already-have-account {
- color: var(--primary-medium);
- margin-bottom: 0.5em;
- }
-
- button,
- hr {
- width: 100%;
- }
- }
- }
-
- .disclaimer {
- color: var(--primary-medium);
- margin-bottom: 0.5em;
- }
-
- .create-account__password-info {
- display: flex;
- justify-content: space-between;
-
- .create-account__password-tip-validation {
- display: flex;
- }
- }
-}
-
-// Login Form Styles
-.login-modal,
-.create-account,
-.invites-show {
- #login-form,
- .login-form,
- .invite-form {
- .input-group {
- position: relative;
- display: flex;
- flex-direction: column;
- margin-bottom: 1em;
-
- input,
- .select-kit-header {
- padding: 0.75em 0.77em;
- min-width: 250px;
- margin-bottom: 0.25em;
- width: 100%;
- }
-
- input:focus {
- outline: none;
- border: 1px solid var(--tertiary);
- box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
- }
-
- input:disabled {
- background-color: var(--primary-low);
- }
-
- span.more-info {
- color: var(--primary-medium);
- min-height: 1.4em; // prevents height increase due to tips
- overflow-wrap: anywhere;
- }
-
- label.alt-placeholder,
- .user-field.text label.control-label,
- .user-field.dropdown label.control-label,
- .user-field.multiselect label.control-label {
- color: var(--primary-medium);
- font-size: 16px;
- font-weight: normal;
- position: absolute;
- pointer-events: none;
- left: 1em;
- top: 13px;
- box-shadow: 0 0 0 0 rgba(var(--tertiary-rgb), 0);
- transition: 0.2s ease all;
- }
-
- .user-field.text label.control-label,
- .user-field.dropdown label.control-label,
- .user-field.multiselect label.control-label {
- z-index: 999;
- top: -8px;
- left: calc(1em - 0.25em);
- background-color: var(--secondary);
- padding: 0 0.25em 0 0.25em;
- font-size: $font-down-1;
- }
-
- .user-field.text label.control-label {
- top: 13px;
- }
-
- .user-field.text:focus-within,
- .user-field.dropdown:focus-within,
- .user-field.multiselect:focus-within {
- z-index: 1000; // ensures the active input is always on top of sibling input labels
- }
-
- input:focus + label.alt-placeholder,
- input.value-entered + label.alt-placeholder {
- top: -8px;
- left: calc(1em - 0.25em);
- background-color: var(--secondary);
- padding: 0 0.25em 0 0.25em;
- font-size: var(--font-down-1);
- }
-
- input.alt-placeholder:invalid {
- color: var(--primary);
- }
-
- .user-field.dropdown,
- .user-field.multiselect {
- .more-info,
- .instructions {
- opacity: 1;
- }
- }
-
- #email-login-link {
- transition: opacity 0.5s;
-
- &.no-login-filled {
- opacity: 0;
- visibility: hidden;
- }
- }
-
- #email-login-link,
- .login__password-links {
- font-size: var(--font-down-1);
- display: flex;
- justify-content: space-between;
- }
-
- .tip:not(:empty) + label.more-info {
- display: none;
- }
- }
-
- #second-factor {
- input {
- width: 100%;
- padding: 0.75em 0.5em;
- min-width: 250px;
- box-shadow: none;
- }
-
- input:focus {
- outline: none;
- border: 1px solid var(--tertiary);
- box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
- }
- }
-
- // user fields input groups will
- // be styled differently
- .user-fields .input-group {
- .user-field {
- &.text {
- &.value-entered label.alt-placeholder.control-label,
- input:focus + label.alt-placeholder.control-label {
- top: -8px;
- left: calc(1em - 0.25em);
- background-color: var(--secondary);
- padding: 0 0.25em 0 0.25em;
- font-size: 14px;
- color: var(--primary-medium);
- }
-
- label.alt-placeholder.control-label {
- color: var(--primary-medium);
- font-size: 16px;
- position: absolute;
- pointer-events: none;
- transition: 0.2s ease all;
- max-width: calc(100% - 2em);
- white-space: nowrap;
- display: inline-block;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
-
- details:not(.has-selection) span.name,
- details:not(.has-selection) span.formatted-selection {
- color: var(--primary-medium);
- }
-
- .select-kit-row span.name {
- color: var(--primary);
- }
-
- .select-kit.combo-box.is-expanded summary {
- outline: none;
- border: 1px solid var(--tertiary);
- box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
- }
-
- .controls .checkbox-label {
- display: flex;
- align-items: center;
-
- input[type="checkbox"].ember-checkbox {
- width: 1em !important;
- min-width: unset;
- margin-block: 0;
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/mobile/_index.scss b/app/assets/stylesheets/mobile/_index.scss
index 8c81746e8e0..cad24c49710 100644
--- a/app/assets/stylesheets/mobile/_index.scss
+++ b/app/assets/stylesheets/mobile/_index.scss
@@ -1,7 +1,6 @@
@import "discourse";
@import "invite-signup";
@import "list-controls";
-@import "login-modal";
@import "login-signup-page";
@import "menu-panel";
@import "modal";
diff --git a/app/assets/stylesheets/mobile/login-modal.scss b/app/assets/stylesheets/mobile/login-modal.scss
deleted file mode 100644
index 18059665175..00000000000
--- a/app/assets/stylesheets/mobile/login-modal.scss
+++ /dev/null
@@ -1,292 +0,0 @@
-.static-login {
- #main-outlet-wrapper,
- #main-outlet {
- padding: 0;
- }
-
- .contents.body-page {
- margin: 0;
- }
-}
-
-.login-welcome {
- gap: 2em;
- height: 100svh;
- width: 100svw;
- box-sizing: border-box;
-
- .body-page-button-container {
- display: flex;
- flex-direction: column;
- gap: 1em;
- width: 100%;
- }
-}
-
-.d-modal.login-modal .d-modal__body {
- margin-top: 3.75rem;
-}
-
-.d-modal.login-modal,
-.d-modal.create-account {
- padding-bottom: var(--safe-area-inset-bottom);
-
- .d-modal {
- &__container {
- height: 100svh;
- max-height: unset;
- width: 100%;
- }
-
- &__body {
- flex-direction: column;
- gap: unset;
- padding-inline: 0.5rem;
- padding-bottom: 1em;
- outline: none;
- }
-
- &__footer {
- padding: 1em 1.5rem;
- border-top: 1px solid var(--primary-low);
- }
-
- &__footer-buttons {
- gap: 0.5em;
- }
- }
-
- .login-title {
- font-size: var(--font-up-4);
- }
-
- .close {
- font-size: var(--font-up-3);
- }
-
- .login-welcome-header {
- padding: 1rem;
- }
-
- .login-right-side {
- padding: 1rem 0 0;
- background: unset;
- max-width: unset;
- }
-
- .login-or-separator {
- border-top: 1px solid var(--primary-low);
- position: relative;
- margin-block: 0.75rem;
-
- span {
- transform: translate(-50%, -50%);
- position: absolute;
- left: 50%;
- top: 50%;
- background: var(--secondary);
- padding-inline: 0.5rem;
- color: var(--primary-medium);
- font-size: var(--font-down-1-rem);
- text-transform: uppercase;
- }
- }
-
- #login-buttons {
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: space-between;
- padding: 0 1rem;
- gap: 0.25em;
- margin-bottom: 1rem;
- height: unset;
-
- .btn {
- margin: 0;
- padding-block: 0.65rem;
- border: 1px solid var(--primary-low);
- flex-grow: 1;
- font-size: var(--font-down-1);
- white-space: nowrap;
- min-width: calc(50% - 0.25em);
- }
- }
-
- .login-page-cta {
- &__buttons {
- .login-page-cta__login {
- width: 100%;
- margin-bottom: 0.5rem;
- }
- }
-
- &__signup {
- background: none !important;
- font-size: var(--font-down);
- padding: 0;
- }
- }
-
- .signup-page-cta {
- &__buttons {
- .signup-page-cta__signup {
- width: 100%;
- margin-bottom: 0.5rem;
- }
- }
-
- &__login {
- background: none !important;
- font-size: var(--font-down);
- padding: 0;
- }
- }
-
- .login-page-cta,
- .signup-page-cta {
- &__existing-account,
- &__no-account-yet {
- width: unset;
- margin-bottom: 0;
-
- &::before {
- display: none;
- }
- }
-
- &__buttons {
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-wrap: wrap;
- justify-content: center;
- gap: 0.5rem;
- }
-
- button {
- width: fit-content;
- }
- }
-
- #login-form,
- .login-form {
- margin: 0;
- padding: 1rem 1rem 0;
-
- .input-group {
- &:not(:last-child) {
- margin-bottom: 0.75em;
- }
-
- input:focus + label,
- input.value-entered + label.alt-placeholder {
- top: -10px;
- }
-
- input.alt-placeholder:invalid {
- color: var(--primary);
- }
-
- label.more-info {
- color: var(--primary-medium);
- }
-
- .more-info,
- .tip,
- .instructions {
- font-size: var(--font-down-1-rem);
- }
- }
-
- .user-fields {
- display: contents;
- }
- }
-
- .caps-lock-warning {
- display: none;
- }
-}
-
-.login-modal,
-.create-account,
-.invites-show {
- #login-form,
- .login-form,
- .invite-form {
- .input-group {
- label.alt-placeholder,
- .user-field.text label.control-label {
- top: 11px;
- }
-
- div.user-field:not(.dropdown)
- .controls
- input:focus
- + label.alt-placeholder.control-label,
- div.user-field.value-entered:not(.dropdown)
- .controls
- label.alt-placeholder.control-label {
- top: -10px;
- }
-
- .user-field.dropdown label.control-label,
- .user-field.multiselect label.control-label {
- top: -10px;
- }
- }
- }
-}
-
-.account-created,
-.activate-account {
- height: 100%;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- max-width: unset;
- margin: 0;
- padding: 0.75em 1rem;
- box-shadow: none;
-
- .activate-new-email {
- width: 100%;
- height: 2.5em;
- }
-
- .activation-controls {
- flex-direction: column;
- gap: 1em;
- margin-top: auto;
- margin-bottom: 0.75em;
-
- button {
- height: 2.5em;
- }
- }
-
- .activate-account-button {
- margin-top: auto;
- margin-bottom: 0.75em;
- width: 100%;
- height: 2.5em;
- }
-
- .account-activated {
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- align-items: center;
-
- .tada-image {
- margin: 0;
- }
-
- .continue-button {
- margin-top: auto;
- margin-bottom: 0.75em;
- width: 100%;
- height: 2.5em;
- }
- }
-}
diff --git a/app/models/problem_check.rb b/app/models/problem_check.rb
index 02f8cb78507..59aaef47419 100644
--- a/app/models/problem_check.rb
+++ b/app/models/problem_check.rb
@@ -66,7 +66,6 @@ class ProblemCheck
ProblemCheck::FacebookConfig,
ProblemCheck::FailingEmails,
ProblemCheck::ForceHttps,
- ProblemCheck::FullPageLoginCheck,
ProblemCheck::GithubConfig,
ProblemCheck::GoogleAnalyticsVersion,
ProblemCheck::GoogleOauth2Config,
diff --git a/app/services/problem_check/full_page_login_check.rb b/app/services/problem_check/full_page_login_check.rb
deleted file mode 100644
index 355d57f940f..00000000000
--- a/app/services/problem_check/full_page_login_check.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-class ProblemCheck::FullPageLoginCheck < ProblemCheck
- self.priority = "low"
-
- def call
- if full_page_login_disabled?
- return problem(override_key: "dashboard.problem.full_page_login_check")
- end
-
- no_problem
- end
-
- private
-
- def full_page_login_disabled?
- SiteSetting.full_page_login == false
- end
-end
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 46cefad0020..7cff3c2d488 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1736,7 +1736,6 @@ en:
category_style_deprecated: "Your Discourse is currently using a deprecated category style which will be removed before the final beta release of Discourse 3.2. Please refer to Moving to a Single Category Style Site Setting for instructions on how to keep your selected category style."
maxmind_db_configuration: 'The server has been configured to use MaxMind databases for reverse IP lookups but a valid MaxMind account ID has not been configured which may result in MaxMind databases failing to download in the future. See this guide to learn more.'
admin_sidebar_deprecation: "The old admin layout is deprecated in favour of the new sidebar layout and will be removed in the next release. You can configure the new sidebar layout now to opt in before that."
- full_page_login_check: "Your site has disabled the full page login setting. This setting is deprecated and will be removed on 29 April 2025. Your site will be updated to use full page signup and login screens on that date. Learn more."
back_from_logster_text: "Back to site"
site_settings:
@@ -1905,7 +1904,6 @@ en:
pending_users_reminder_delay_minutes: "Notify moderators if new users have been waiting for approval for longer than this many minutes. Set to -1 to disable notifications."
persistent_sessions: "Users will remain logged in when the web browser is closed"
maximum_session_age: "User will remain logged in for n hours since last visit"
- full_page_login: "Show the login and signup forms in a full page (when unchecked, users will see the forms in a modal). "
show_signup_form_email_instructions: Show email instructions on the signup form.
show_signup_form_username_instructions: Show username instructions on the signup form.
show_signup_form_full_name_instructions: Show full name instructions on the signup form.
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 71e1251387d..583db59fe3a 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -723,9 +723,6 @@ login:
default: 1440
min: 1
max: 175200
- full_page_login:
- default: true
- client: true
show_signup_form_email_instructions:
client: true
default: true
diff --git a/db/migrate/20250407040934_remove_full_page_login_problem_check_trackers.rb b/db/migrate/20250407040934_remove_full_page_login_problem_check_trackers.rb
new file mode 100644
index 00000000000..71fbd6e1bde
--- /dev/null
+++ b/db/migrate/20250407040934_remove_full_page_login_problem_check_trackers.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+class RemoveFullPageLoginProblemCheckTrackers < ActiveRecord::Migration[7.2]
+ def up
+ execute(<<~SQL)
+ DELETE FROM problem_check_trackers WHERE identifier = 'full_page_login_check';
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/migrate/20250407042814_delete_full_page_login_setting.rb b/db/migrate/20250407042814_delete_full_page_login_setting.rb
new file mode 100644
index 00000000000..e6f758d257a
--- /dev/null
+++ b/db/migrate/20250407042814_delete_full_page_login_setting.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+class DeleteFullPageLoginSetting < ActiveRecord::Migration[7.2]
+ def up
+ execute "DELETE FROM site_settings WHERE name = 'full_page_login'"
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/spec/services/problem_check/full_page_login_check_spec.rb b/spec/services/problem_check/full_page_login_check_spec.rb
deleted file mode 100644
index 63c21a5b0a8..00000000000
--- a/spec/services/problem_check/full_page_login_check_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe ProblemCheck::FullPageLoginCheck do
- let(:check) { described_class.new }
-
- describe "#call" do
- context "when full_page_login is enabled" do
- before { SiteSetting.full_page_login = true }
-
- it { expect(check).to be_chill_about_it }
- end
-
- context "when full_page_login is enabled" do
- before { SiteSetting.full_page_login = false }
-
- it { expect(check).to have_a_problem.with_priority("low") }
- end
- end
-end
diff --git a/spec/support/shared_examples/core_features.rb b/spec/support/shared_examples/core_features.rb
index ae28caef466..0b9ccf077e8 100644
--- a/spec/support/shared_examples/core_features.rb
+++ b/spec/support/shared_examples/core_features.rb
@@ -10,16 +10,16 @@ RSpec.shared_examples_for "having working core features" do |skip_examples: []|
if skip_examples.exclude?(:login)
describe "Login" do
- let(:login_form) { PageObjects::Modals::Login.new }
+ let(:login_form) { PageObjects::Pages::Login.new }
before { EmailToken.confirm(Fabricate(:email_token, user: active_user).token) }
it "logs in" do
visit("/")
- login_form
- .open
- .fill(username: active_user.username, password: "secure_password")
- .click_login
+ login_form.open
+ login_form.fill_username(active_user.username)
+ login_form.fill_password("secure_password")
+ login_form.click_login
expect(page).to have_css(".current-user", visible: true)
end
diff --git a/spec/system/email_change_spec.rb b/spec/system/email_change_spec.rb
index 3536d84b4dd..f37e713e14e 100644
--- a/spec/system/email_change_spec.rb
+++ b/spec/system/email_change_spec.rb
@@ -85,7 +85,6 @@ describe "Changing email", type: :system do
end
it "does not require login to confirm email change" do
- SiteSetting.full_page_login = false
second_factor = Fabricate(:user_second_factor_totp, user: user)
sign_in user
diff --git a/spec/system/login_spec.rb b/spec/system/login_spec.rb
index fcd38e8b50f..7ecbdf73629 100644
--- a/spec/system/login_spec.rb
+++ b/spec/system/login_spec.rb
@@ -360,12 +360,10 @@ end
describe "Login", type: :system do
context "when fullpage desktop" do
- before { SiteSetting.full_page_login = true }
include_examples "login scenarios", PageObjects::Pages::Login.new
end
context "when fullpage mobile", mobile: true do
- before { SiteSetting.full_page_login = true }
include_examples "login scenarios", PageObjects::Pages::Login.new
end
end
diff --git a/spec/system/page_objects/modals/login.rb b/spec/system/page_objects/modals/login.rb
deleted file mode 100644
index ae7947f95b0..00000000000
--- a/spec/system/page_objects/modals/login.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-module PageObjects
- module Modals
- class Login < PageObjects::Modals::Base
- def open?
- super && has_css?(".login-modal")
- end
-
- def closed?
- super && has_no_css?(".login-modal")
- end
-
- def open
- visit("/login")
- self
- end
-
- def open_from_header
- find(".login-button").click
- end
-
- def click(selector)
- if page.has_css?("html.mobile-view", wait: 0)
- expect(page).to have_css(".d-modal:not(.is-animating)")
- end
- find(selector).click
- end
-
- def open_signup
- click("#new-account-link")
- end
-
- def click_login
- click("#login-button")
- end
-
- def email_login_link
- click("#email-login-link")
- end
-
- def forgot_password
- click("#forgot-password-link")
- end
-
- def fill_input(selector, text)
- if page.has_css?("html.mobile-view", wait: 0)
- expect(page).to have_css(".d-modal:not(.is-animating)")
- end
- find(selector).fill_in(with: text)
- self
- end
-
- def fill_username(username)
- fill_input("#login-account-name", username)
- self
- end
-
- def fill_password(password)
- fill_input("#login-account-password", password)
- self
- end
-
- def fill(username: nil, password: nil)
- fill_username(username) if username
- fill_password(password) if password
- self
- end
-
- def click_social_button(provider)
- click(".btn-social.#{provider}")
- end
- end
- end
-end
diff --git a/spec/system/page_objects/modals/signup.rb b/spec/system/page_objects/modals/signup.rb
deleted file mode 100644
index 0e8e7e0ccd7..00000000000
--- a/spec/system/page_objects/modals/signup.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-module PageObjects
- module Modals
- class Signup < PageObjects::Modals::Base
- def open?
- super && has_css?(".modal.create-account")
- end
-
- def closed?
- super && has_no_css?(".modal.create-account")
- end
-
- def open
- visit("/signup")
- self
- end
-
- def open_from_header
- find(".sign-up-button").click
- end
-
- def click(selector)
- if page.has_css?("html.mobile-view", wait: 0)
- expect(page).to have_css(".d-modal:not(.is-animating)")
- end
- find(selector).click
- end
-
- def open_login
- click("#login-link")
- end
-
- def click_create_account
- click(".modal.create-account .btn-primary")
- end
-
- def has_password_input?
- has_css?("#new-account-password")
- end
-
- def has_no_password_input?
- has_no_css?("#new-account-password")
- end
-
- def has_name_input?
- has_css?("#new-account-name")
- end
-
- def has_no_name_input?
- has_no_css?("#new-account-name")
- end
-
- def fill_input(selector, text)
- if page.has_css?("html.mobile-view", wait: 0)
- expect(page).to have_css(".d-modal:not(.is-animating)")
- end
- find(selector).fill_in(with: text)
- end
-
- def fill_email(email)
- fill_input("#new-account-email", email)
- self
- end
-
- def fill_username(username)
- fill_input("#new-account-username", username)
- self
- end
-
- def fill_name(name)
- fill_input("#new-account-name", name)
- self
- end
-
- def fill_password(password)
- fill_input("#new-account-password", password)
- self
- end
-
- def fill_code(code)
- fill_input("#inviteCode", code)
- self
- end
-
- def fill_custom_field(name, value)
- find(".user-field-#{name.downcase} input").fill_in(with: value)
- self
- end
-
- def has_valid_email?
- find(".create-account-email").has_css?("#account-email-validation.good")
- end
-
- def has_valid_username?
- find(".create-account__username").has_css?("#username-validation.good")
- end
-
- def has_valid_password?
- find(".create-account__password").has_css?("#password-validation.good")
- end
-
- def has_valid_fields?
- has_valid_email?
- has_valid_username?
- has_valid_password?
- end
-
- def has_disabled_email?
- find(".create-account-email").has_css?("input[disabled]")
- end
-
- def has_disabled_name?
- find(".create-account__fullname").has_css?("input[disabled]")
- end
-
- def has_disabled_username?
- find(".create-account__username").has_css?("input[disabled]")
- end
-
- def click_social_button(provider)
- click(".btn-social.#{provider}")
- end
- end
- end
-end
diff --git a/spec/system/signup_spec.rb b/spec/system/signup_spec.rb
index f6b53ef2ba9..50411a9d550 100644
--- a/spec/system/signup_spec.rb
+++ b/spec/system/signup_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
shared_examples "signup scenarios" do |signup_page_object, login_page_object|
- let(:login_form) { login_page_object }
let(:signup_form) { signup_page_object }
+ let(:login_form) { login_page_object }
let(:invite_form) { PageObjects::Pages::InviteForm.new }
let(:activate_account) { PageObjects::Pages::ActivateAccount.new }
let(:invite) { Fabricate(:invite, email: "johndoe@example.com") }
@@ -285,14 +285,8 @@ shared_examples "signup scenarios" do |signup_page_object, login_page_object|
expect(page).to have_css(".invited-by .user-info[data-username='#{inviter.username}']")
find(".invitation-cta__sign-in").click
-
- if SiteSetting.full_page_login
- expect(page).to have_css("#login-form")
- page.go_back
- else
- find(".d-modal .modal-close").click
- end
-
+ expect(page).to have_css("#login-form")
+ page.go_back
expect(page).to have_css(".invited-by .user-info[data-username='#{inviter.username}']")
end
@@ -350,14 +344,12 @@ end
describe "Signup", type: :system do
context "when fullpage desktop" do
- before { SiteSetting.full_page_login = true }
include_examples "signup scenarios",
PageObjects::Pages::Signup.new,
PageObjects::Pages::Login.new
end
context "when fullpage mobile", mobile: true do
- before { SiteSetting.full_page_login = true }
include_examples "signup scenarios",
PageObjects::Pages::Signup.new,
PageObjects::Pages::Login.new
diff --git a/spec/system/social_authentication_spec.rb b/spec/system/social_authentication_spec.rb
index 68a9fc4c1cf..2625b3b5f14 100644
--- a/spec/system/social_authentication_spec.rb
+++ b/spec/system/social_authentication_spec.rb
@@ -454,14 +454,12 @@ describe "Social authentication", type: :system do
before { SiteSetting.full_name_requirement = "optional_at_signup" }
context "when fullpage desktop" do
- before { SiteSetting.full_page_login = true }
include_examples "social authentication scenarios",
PageObjects::Pages::Signup.new,
PageObjects::Pages::Login.new
end
context "when fullpage mobile", mobile: true do
- before { SiteSetting.full_page_login = true }
include_examples "social authentication scenarios",
PageObjects::Pages::Signup.new,
PageObjects::Pages::Login.new