mirror of
https://github.com/discourse/discourse.git
synced 2025-08-17 18:04:11 +08:00
FIX: 'destination_url' cookie handling (#33072)
Since the introduction of dedicated login and signup pages (as opposed to modals), we've been seeing reports of issues where visitors aren't redirected back to the "page" they were at when they initiated the _authentication_ process. Since we have a bazillion of ways a user might authenticate (credentials, social logins, SSO, passkeys, discourse connect, etc...), it's really hard to know what a change will impact. The goal of this PR is to "simplify" the way we handle this "redirection back to origin" by leveraging the use of a single `destination_url` cookie set on the client-side. The changes remove scattered cookie-setting code and consolidate the redirection logic to ensure users are properly redirected back to their original page after authentication. - Centralized destination URL cookie management in routes and authentication flows - Removed manual cookie setting from various components in favor of automatic handling - Updated test scenarios to properly test the new redirection behavior
This commit is contained in:
parent
fea4d73787
commit
ced043be3c
31 changed files with 759 additions and 889 deletions
|
@ -5,7 +5,6 @@ import { service } from "@ember/service";
|
|||
import { classNames } from "@ember-decorators/component";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import RequestGroupMembershipForm from "./modal/request-group-membership-form";
|
||||
|
@ -37,11 +36,6 @@ export default class GroupMembershipButton extends Component {
|
|||
return !!isGroupUser;
|
||||
}
|
||||
|
||||
_showLoginModal() {
|
||||
this.showLogin();
|
||||
cookie("destination_url", window.location.href);
|
||||
}
|
||||
|
||||
removeFromGroup() {
|
||||
const model = this.model;
|
||||
model
|
||||
|
@ -56,23 +50,23 @@ export default class GroupMembershipButton extends Component {
|
|||
|
||||
@action
|
||||
joinGroup() {
|
||||
if (this.currentUser) {
|
||||
this.set("updatingMembership", true);
|
||||
const group = this.model;
|
||||
|
||||
group
|
||||
.join()
|
||||
.then(() => {
|
||||
group.set("is_group_user", true);
|
||||
this.appEvents.trigger("group:join", group);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.set("updatingMembership", false);
|
||||
});
|
||||
} else {
|
||||
this._showLoginModal();
|
||||
if (!this.currentUser) {
|
||||
return this.showLogin();
|
||||
}
|
||||
|
||||
this.set("updatingMembership", true);
|
||||
const group = this.model;
|
||||
|
||||
group
|
||||
.join()
|
||||
.then(() => {
|
||||
group.set("is_group_user", true);
|
||||
this.appEvents.trigger("group:join", group);
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.set("updatingMembership", false);
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -92,15 +86,15 @@ export default class GroupMembershipButton extends Component {
|
|||
|
||||
@action
|
||||
showRequestMembershipForm() {
|
||||
if (this.currentUser) {
|
||||
this.modal.show(RequestGroupMembershipForm, {
|
||||
model: {
|
||||
group: this.model,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._showLoginModal();
|
||||
if (!this.currentUser) {
|
||||
return this.showLogin();
|
||||
}
|
||||
|
||||
this.modal.show(RequestGroupMembershipForm, {
|
||||
model: {
|
||||
group: this.model,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
|
|
|
@ -4,14 +4,13 @@ import { action } from "@ember/object";
|
|||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import ForgotPassword from "discourse/components/modal/forgot-password";
|
||||
import NotActivatedModal from "discourse/components/modal/not-activated";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
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,
|
||||
|
@ -39,9 +38,6 @@ export default class LoginPageController extends Controller {
|
|||
@tracked showSecondFactor = false;
|
||||
@tracked loginPassword = "";
|
||||
@tracked loginName = "";
|
||||
@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;
|
||||
|
@ -55,6 +51,9 @@ export default class LoginPageController extends Controller {
|
|||
@tracked flash;
|
||||
@tracked flashType;
|
||||
|
||||
@setting("enable_local_logins") canLoginLocal;
|
||||
@setting("enable_local_logins_via_email") canLoginLocalWithEmail;
|
||||
|
||||
get isAwaitingApproval() {
|
||||
return (
|
||||
this.awaitingApproval &&
|
||||
|
@ -109,24 +108,13 @@ export default class LoginPageController extends Controller {
|
|||
}
|
||||
|
||||
get showSignupLink() {
|
||||
return this.canSignUp && !this.showSecondFactor;
|
||||
return this.application.canSignUp && !this.showSecondFactor;
|
||||
}
|
||||
|
||||
get adminLoginPath() {
|
||||
return getURL("/u/admin-login");
|
||||
}
|
||||
|
||||
get shouldTriggerRouteAction() {
|
||||
return (
|
||||
this.siteSettings.enable_discourse_connect || this.singleExternalLogin
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
showFullPageLogin() {
|
||||
this.showLogin = true;
|
||||
}
|
||||
|
||||
@action
|
||||
async passkeyLogin(mediation = "optional") {
|
||||
try {
|
||||
|
@ -153,8 +141,6 @@ export default class LoginPageController extends Controller {
|
|||
if (destinationUrl) {
|
||||
removeCookie("destination_url");
|
||||
window.location.assign(destinationUrl);
|
||||
} else if (this.referrerTopicUrl) {
|
||||
window.location.assign(this.referrerTopicUrl);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -167,21 +153,6 @@ export default class LoginPageController extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
@ -207,45 +178,13 @@ export default class LoginPageController extends Controller {
|
|||
this.loginPassword = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
showCreateAccount(createAccountProps = {}) {
|
||||
if (this.site.isReadOnly) {
|
||||
this.dialog.alert(i18n("read_only_mode.login_disabled"));
|
||||
} else {
|
||||
this.handleShowCreateAccount(createAccountProps);
|
||||
}
|
||||
}
|
||||
|
||||
handleShowCreateAccount(createAccountProps) {
|
||||
if (this.siteSettings.enable_discourse_connect) {
|
||||
const returnPath = encodeURIComponent(window.location.pathname);
|
||||
window.location = getURL("/session/sso?return_path=" + returnPath);
|
||||
} else {
|
||||
if (
|
||||
this.isOnlyOneExternalLoginMethod &&
|
||||
this.siteSettings.auth_immediately
|
||||
) {
|
||||
// we will automatically redirect to the external auth service
|
||||
this.login.externalLogin(this.externalLoginMethods[0], {
|
||||
signup: true,
|
||||
});
|
||||
} else {
|
||||
this.router.transitionTo("signup").then((signup) => {
|
||||
Object.keys(createAccountProps || {}).forEach((key) => {
|
||||
signup.controller.set(key, createAccountProps[key]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
showNotActivated(props) {
|
||||
this.modal.show(NotActivatedModal, { model: props });
|
||||
}
|
||||
|
||||
@action
|
||||
async triggerLogin() {
|
||||
async localLogin() {
|
||||
if (this.loginDisabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -268,9 +207,10 @@ export default class LoginPageController extends Controller {
|
|||
timezone: moment.tz.guess(),
|
||||
},
|
||||
});
|
||||
if (result && result.error) {
|
||||
if (result?.error) {
|
||||
this.loggingIn = false;
|
||||
this.flash = null;
|
||||
this.flashType = "error";
|
||||
|
||||
if (
|
||||
(result.security_key_enabled || result.totp_enabled) &&
|
||||
|
@ -304,111 +244,79 @@ export default class LoginPageController extends Controller {
|
|||
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;
|
||||
const _form = document.getElementById("hidden-login-form");
|
||||
if (_form) {
|
||||
const set = (key, value) => {
|
||||
_form.querySelector(`input[name=${key}]`).value = value;
|
||||
};
|
||||
|
||||
set("username", this.loginName);
|
||||
set("password", this.loginPassword);
|
||||
|
||||
const destinationUrl = cookie("destination_url");
|
||||
if (destinationUrl) {
|
||||
removeCookie("destination_url");
|
||||
set("redirect", destinationUrl);
|
||||
} else {
|
||||
set("redirect", window.location.href);
|
||||
}
|
||||
|
||||
hiddenLoginForm.querySelector(`input[name=${key}]`).value = value;
|
||||
};
|
||||
|
||||
applyHiddenFormInputValue(this.loginName, "username");
|
||||
applyHiddenFormInputValue(this.loginPassword, "password");
|
||||
|
||||
const destinationUrl = cookie("destination_url");
|
||||
if (destinationUrl) {
|
||||
removeCookie("destination_url");
|
||||
applyHiddenFormInputValue(destinationUrl, "redirect");
|
||||
} else if (this.referrerTopicUrl) {
|
||||
applyHiddenFormInputValue(this.referrerTopicUrl, "redirect");
|
||||
} else {
|
||||
applyHiddenFormInputValue(window.location.href, "redirect");
|
||||
}
|
||||
|
||||
if (hiddenLoginForm) {
|
||||
if (
|
||||
navigator.userAgent.match(/(iPad|iPhone|iPod)/g) &&
|
||||
navigator.userAgent.match(/Safari/g)
|
||||
) {
|
||||
if (this.capabilities.isIOS && this.capabilities.isSafari) {
|
||||
// In case of Safari on iOS do not submit hidden login form
|
||||
window.location.href = hiddenLoginForm.querySelector(
|
||||
window.location.href = _form.querySelector(
|
||||
"input[name=redirect]"
|
||||
).value;
|
||||
} else {
|
||||
hiddenLoginForm.submit();
|
||||
_form.submit();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// Failed to login
|
||||
if (e.jqXHR && e.jqXHR.status === 429) {
|
||||
this.loggingIn = false;
|
||||
this.flashType = "error";
|
||||
if (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"
|
||||
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;
|
||||
externalLogin(loginMethod) {
|
||||
if (!this.loginDisabled) {
|
||||
this.login.externalLogin(loginMethod, {
|
||||
setLoggingIn: (value) => (this.loggingIn = value),
|
||||
});
|
||||
}
|
||||
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;
|
||||
// This makes the UX a little bit nicer by auto-filling the email/username when switching from /login to /signup
|
||||
if (this.loginName?.indexOf("@") > 0) {
|
||||
this.send("showCreateAccount", {
|
||||
accountEmail: this.loginName,
|
||||
accountUsername: "",
|
||||
});
|
||||
} else {
|
||||
createAccountProps.accountUsername = this.loginName;
|
||||
createAccountProps.accountEmail = null;
|
||||
}
|
||||
this.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,
|
||||
},
|
||||
this.send("showCreateAccount", {
|
||||
accountEmail: "",
|
||||
accountUsername: this.loginName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class SignupPageController extends Controller {
|
|||
@tracked isDeveloper = false;
|
||||
@tracked authOptions;
|
||||
@tracked skipConfirmation;
|
||||
|
||||
accountChallenge = 0;
|
||||
accountHoneypot = 0;
|
||||
formSubmitted = false;
|
||||
|
@ -450,12 +451,6 @@ export default class SignupPageController extends Controller {
|
|||
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 = {};
|
||||
|
@ -475,30 +470,32 @@ export default class SignupPageController extends Controller {
|
|||
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();
|
||||
const _form = document.getElementById("hidden-login-form");
|
||||
if (_form) {
|
||||
const set = (key, value) => {
|
||||
_form.querySelector(`input[name=${key}]`).value = value;
|
||||
};
|
||||
|
||||
set("username", this.accountUsername);
|
||||
set("password", this.accountPassword);
|
||||
|
||||
const destinationUrl = cookie("destination_url");
|
||||
if (destinationUrl) {
|
||||
removeCookie("destination_url");
|
||||
set("redirect", destinationUrl);
|
||||
} else {
|
||||
set("redirect", userPath("account-created"));
|
||||
}
|
||||
|
||||
_form.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
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import DiscourseUrl from "discourse/lib/url";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
// This is happening outside of the app via popup
|
||||
const AuthErrors = [
|
||||
"requires_invite",
|
||||
"awaiting_approval",
|
||||
"awaiting_activation",
|
||||
"admin_not_allowed_from_ip_address",
|
||||
"awaiting_activation",
|
||||
"awaiting_approval",
|
||||
"not_allowed_from_ip_address",
|
||||
"requires_invite",
|
||||
];
|
||||
|
||||
const beforeAuthCompleteCallbacks = [];
|
||||
|
@ -27,13 +25,8 @@ export function resetBeforeAuthCompleteCallbacks() {
|
|||
export default {
|
||||
after: "inject-objects",
|
||||
initialize(owner) {
|
||||
let lastAuthResult;
|
||||
|
||||
if (document.getElementById("data-authentication")) {
|
||||
// Happens for full screen logins
|
||||
lastAuthResult = document.getElementById("data-authentication").dataset
|
||||
.authenticationData;
|
||||
}
|
||||
const lastAuthResult = document.getElementById("data-authentication")
|
||||
?.dataset?.authenticationData;
|
||||
|
||||
if (lastAuthResult) {
|
||||
const router = owner.lookup("service:router");
|
||||
|
@ -52,32 +45,31 @@ export default {
|
|||
} else {
|
||||
const siteSettings = owner.lookup("service:site-settings");
|
||||
|
||||
const loginError = (errorMsg, className, properties, callback) => {
|
||||
const applicationController = owner.lookup(
|
||||
"controller:application"
|
||||
);
|
||||
|
||||
const loginProps = {
|
||||
canSignUp: applicationController.canSignUp,
|
||||
flash: errorMsg,
|
||||
flashType: className || "success",
|
||||
const loginError = (flash, properties, callback) => {
|
||||
const props = {
|
||||
flash,
|
||||
flashType: "error",
|
||||
awaitingApproval: options.awaiting_approval,
|
||||
...properties,
|
||||
};
|
||||
|
||||
router.transitionTo("login").then((login) => {
|
||||
Object.keys(loginProps || {}).forEach((key) => {
|
||||
login.controller.set(key, loginProps[key]);
|
||||
});
|
||||
});
|
||||
router.trigger("showLogin", props);
|
||||
|
||||
next(() => callback?.());
|
||||
};
|
||||
|
||||
const error = AuthErrors.find((name) => options[name]);
|
||||
if (error) {
|
||||
return loginError(i18n(`login.${error}`));
|
||||
}
|
||||
|
||||
if (options.suspended) {
|
||||
return loginError(options.suspended_message);
|
||||
}
|
||||
|
||||
if (options.omniauth_disallow_totp) {
|
||||
return loginError(
|
||||
i18n("login.omniauth_disallow_totp"),
|
||||
"error",
|
||||
{
|
||||
loginName: options.email,
|
||||
showLoginButtons: false,
|
||||
|
@ -86,29 +78,14 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < AuthErrors.length; i++) {
|
||||
const cond = AuthErrors[i];
|
||||
if (options[cond]) {
|
||||
return loginError(htmlSafe(i18n(`login.${cond}`)));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.suspended) {
|
||||
return loginError(options.suspended_message, "error");
|
||||
}
|
||||
|
||||
// Reload the page if we're authenticated
|
||||
if (options.authenticated) {
|
||||
const destinationUrl =
|
||||
cookie("destination_url") || options.destination_url;
|
||||
if (destinationUrl) {
|
||||
// redirect client to the original URL
|
||||
removeCookie("destination_url");
|
||||
window.location.href = destinationUrl;
|
||||
} else if (
|
||||
window.location.pathname === DiscourseUrl.getURL("/login")
|
||||
) {
|
||||
window.location = DiscourseUrl.getURL("/");
|
||||
} else if (window.location.pathname === getURL("/login")) {
|
||||
window.location = getURL("/");
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -116,7 +93,7 @@ export default {
|
|||
}
|
||||
|
||||
next(() => {
|
||||
const createAccountProps = {
|
||||
const props = {
|
||||
accountEmail: options.email,
|
||||
accountUsername: options.username,
|
||||
accountName: options.name,
|
||||
|
@ -124,11 +101,10 @@ export default {
|
|||
skipConfirmation: siteSettings.auth_skip_create_confirm,
|
||||
};
|
||||
|
||||
router.transitionTo("signup").then((signup) => {
|
||||
const signupController =
|
||||
signup.controller || owner.lookup("controller:signup");
|
||||
Object.assign(signupController, createAccountProps);
|
||||
signupController.handleSkipConfirmation();
|
||||
router.transitionTo("signup").then(({ controller }) => {
|
||||
controller ??= owner.lookup("controller:signup");
|
||||
controller.setProperties(props);
|
||||
controller.handleSkipConfirmation();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,14 +8,11 @@ export default function logout({ redirect } = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
const ctx = helperContext();
|
||||
let keyValueStore = ctx.keyValueStore;
|
||||
const { keyValueStore, siteSettings } = helperContext();
|
||||
|
||||
keyValueStore.abandonLocal();
|
||||
|
||||
if (!isEmpty(redirect)) {
|
||||
window.location.href = redirect;
|
||||
return;
|
||||
}
|
||||
const url = ctx.siteSettings.login_required ? "/login" : "/";
|
||||
window.location.href = getURL(url);
|
||||
window.location = isEmpty(redirect)
|
||||
? getURL(siteSettings.login_required ? "/login" : "/")
|
||||
: redirect;
|
||||
}
|
||||
|
|
|
@ -784,3 +784,20 @@ export function isPrimaryTab() {
|
|||
export function optionalRequire(path, name = "default") {
|
||||
return require.has(path) && require(path)[name];
|
||||
}
|
||||
|
||||
// Keep in sync with `NO_DESTINATION_COOKIE` in `app/controllers/application_controller.rb`
|
||||
const NO_DESTINATION_COOKIE = [
|
||||
"/login",
|
||||
"/signup",
|
||||
"/session/",
|
||||
"/auth/",
|
||||
"/uploads/",
|
||||
];
|
||||
|
||||
export function isValidDestinationUrl(url) {
|
||||
return (
|
||||
url &&
|
||||
url !== getURL("/") &&
|
||||
!NO_DESTINATION_COOKIE.some((p) => url.startsWith(getURL(p)))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { Promise } from "rsvp";
|
||||
import { updateCsrfToken } from "discourse/lib/ajax";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import Session from "discourse/models/session";
|
||||
|
@ -77,6 +78,11 @@ export default class LoginMethod extends EmberObject {
|
|||
}
|
||||
}
|
||||
|
||||
const destinationUrl = cookie("destination_url");
|
||||
if (destinationUrl) {
|
||||
params.origin = destinationUrl;
|
||||
}
|
||||
|
||||
return LoginMethod.buildPostForm(getURL(`/auth/${this.name}`), params).then(
|
||||
(form) => form.submit()
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@ import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts
|
|||
import NotActivatedModal from "discourse/components/modal/not-activated";
|
||||
import { RouteException } from "discourse/controllers/exception";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { getOwnerWithFallback } from "discourse/lib/get-owner";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
|
@ -12,16 +11,11 @@ import logout from "discourse/lib/logout";
|
|||
import mobile from "discourse/lib/mobile";
|
||||
import identifySource, { consolePrefix } from "discourse/lib/source-identifier";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { postRNWebviewMessage } from "discourse/lib/utilities";
|
||||
import Category from "discourse/models/category";
|
||||
import Composer from "discourse/models/composer";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
function isStrictlyReadonly(site) {
|
||||
return site.isReadOnly && !site.isStaffWritesOnly;
|
||||
}
|
||||
|
||||
export default class ApplicationRoute extends DiscourseRoute {
|
||||
@service capabilities;
|
||||
@service clientErrorHandler;
|
||||
|
@ -86,11 +80,15 @@ export default class ApplicationRoute extends DiscourseRoute {
|
|||
|
||||
@action
|
||||
logout() {
|
||||
if (isStrictlyReadonly(this.site)) {
|
||||
const { isReadOnly, isStaffWritesOnly } = this.site;
|
||||
|
||||
if (isReadOnly && !isStaffWritesOnly) {
|
||||
this.dialog.alert(i18n("read_only_mode.logout_disabled"));
|
||||
return;
|
||||
} else if (this.currentUser) {
|
||||
this.currentUser
|
||||
.destroySession()
|
||||
.then((response) => logout({ redirect: response["redirect_url"] }));
|
||||
}
|
||||
this._handleLogout();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -175,21 +173,17 @@ export default class ApplicationRoute extends DiscourseRoute {
|
|||
}
|
||||
|
||||
@action
|
||||
showLogin() {
|
||||
if (isStrictlyReadonly(this.site)) {
|
||||
this.dialog.alert(i18n("read_only_mode.login_disabled"));
|
||||
return;
|
||||
}
|
||||
this.handleShowLogin();
|
||||
showLogin(props = {}) {
|
||||
const t = this.router.transitionTo("login");
|
||||
t.wantsTo = true;
|
||||
return t.then(({ controller }) => controller.setProperties({ ...props }));
|
||||
}
|
||||
|
||||
@action
|
||||
showCreateAccount(createAccountProps = {}) {
|
||||
if (this.site.isReadOnly) {
|
||||
this.dialog.alert(i18n("read_only_mode.login_disabled"));
|
||||
} else {
|
||||
this.handleShowCreateAccount(createAccountProps);
|
||||
}
|
||||
showCreateAccount(props = {}) {
|
||||
const t = this.router.transitionTo("signup");
|
||||
t.wantsTo = true;
|
||||
return t.then(({ controller }) => controller.setProperties({ ...props }));
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -255,50 +249,4 @@ export default class ApplicationRoute extends DiscourseRoute {
|
|||
hasGroups,
|
||||
});
|
||||
}
|
||||
|
||||
handleShowLogin() {
|
||||
if (this.capabilities.isAppWebview) {
|
||||
postRNWebviewMessage("showLogin", true);
|
||||
}
|
||||
if (this.siteSettings.enable_discourse_connect) {
|
||||
const returnPath = cookie("destination_url")
|
||||
? getURL("/")
|
||||
: encodeURIComponent(window.location.pathname);
|
||||
window.location = getURL("/session/sso?return_path=" + returnPath);
|
||||
} else {
|
||||
if (this.login.isOnlyOneExternalLoginMethod) {
|
||||
this.login.singleExternalLogin();
|
||||
} else {
|
||||
this.router.transitionTo("login").then((login) => {
|
||||
login.controller.set("canSignUp", this.controller.canSignUp);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleShowCreateAccount(createAccountProps) {
|
||||
if (this.siteSettings.enable_discourse_connect) {
|
||||
const returnPath = encodeURIComponent(window.location.pathname);
|
||||
window.location = getURL("/session/sso?return_path=" + returnPath);
|
||||
} else {
|
||||
if (this.login.isOnlyOneExternalLoginMethod) {
|
||||
// we will automatically redirect to the external auth service
|
||||
this.login.singleExternalLogin({ signup: true });
|
||||
} else {
|
||||
this.router.transitionTo("signup").then((signup) => {
|
||||
Object.keys(createAccountProps || {}).forEach((key) => {
|
||||
signup.controller.set(key, createAccountProps[key]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleLogout() {
|
||||
if (this.currentUser) {
|
||||
this.currentUser
|
||||
.destroySession()
|
||||
.then((response) => logout({ redirect: response["redirect_url"] }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,33 @@
|
|||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import AssociateAccountConfirm from "discourse/components/modal/associate-account-confirm";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class AssociateAccount extends DiscourseRoute {
|
||||
@service router;
|
||||
export default class extends DiscourseRoute {
|
||||
@service currentUser;
|
||||
@service modal;
|
||||
@service router;
|
||||
|
||||
beforeModel(transition) {
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", transition.intent.url);
|
||||
return this.router.replaceWith("login");
|
||||
}
|
||||
const params = this.paramsFor("associate-account");
|
||||
this.redirectToAccount(params);
|
||||
}
|
||||
transition.send("showLogin");
|
||||
} else {
|
||||
const { token } = this.paramsFor("associate-account");
|
||||
|
||||
@action
|
||||
async redirectToAccount(params) {
|
||||
await this.router
|
||||
.replaceWith(`preferences.account`, this.currentUser)
|
||||
.followRedirects();
|
||||
next(() => this.showAssociateAccount(params));
|
||||
}
|
||||
|
||||
@action
|
||||
async showAssociateAccount(params) {
|
||||
try {
|
||||
const model = await ajax(
|
||||
`/associate/${encodeURIComponent(params.token)}.json`
|
||||
);
|
||||
this.modal.show(AssociateAccountConfirm, { model });
|
||||
} catch (e) {
|
||||
popupAjaxError(e);
|
||||
this.router
|
||||
.replaceWith("preferences.account", this.currentUser)
|
||||
.followRedirects()
|
||||
.then(async () => {
|
||||
try {
|
||||
const model = await ajax(
|
||||
`/associate/${encodeURIComponent(token)}.json`
|
||||
);
|
||||
this.modal.show(AssociateAccountConfirm, { model });
|
||||
} catch (e) {
|
||||
popupAjaxError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,90 @@
|
|||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
import StaticPage from "discourse/models/static-page";
|
||||
import {
|
||||
isValidDestinationUrl,
|
||||
postRNWebviewMessage,
|
||||
} from "discourse/lib/utilities";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class LoginRoute extends DiscourseRoute {
|
||||
@service siteSettings;
|
||||
@service router;
|
||||
export default class extends DiscourseRoute {
|
||||
@service capabilities;
|
||||
@service dialog;
|
||||
@service login;
|
||||
@service router;
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
|
||||
isRedirecting = false;
|
||||
|
||||
beforeModel(transition) {
|
||||
if (transition.from) {
|
||||
this.internalReferrer = this.router.urlFor(transition.from.name);
|
||||
const { from, wantsTo } = transition;
|
||||
const { currentUser, dialog, router } = this;
|
||||
const { isReadOnly, isStaffWritesOnly } = this.site;
|
||||
const { isAppWebview } = this.capabilities;
|
||||
const { auth_immediately, enable_discourse_connect, login_required } =
|
||||
this.siteSettings;
|
||||
const { pathname: url } = window.location;
|
||||
const { referrer } = document;
|
||||
const { isOnlyOneExternalLoginMethod, singleExternalLogin } = this.login;
|
||||
|
||||
// Regular users can't log in but staff can when the site is read-only
|
||||
if (isReadOnly && !isStaffWritesOnly) {
|
||||
transition.abort();
|
||||
dialog.alert(i18n("read_only_mode.login_disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.siteSettings.login_required) {
|
||||
if (
|
||||
this.login.isOnlyOneExternalLoginMethod &&
|
||||
this.siteSettings.auth_immediately &&
|
||||
!document.getElementById("data-authentication")?.dataset
|
||||
.authenticationData
|
||||
) {
|
||||
this.login.singleExternalLogin();
|
||||
// We're in the middle of an authentication flow
|
||||
if (document.getElementById("data-authentication")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When inside a webview, it handles the login flow itself
|
||||
if (isAppWebview) {
|
||||
postRNWebviewMessage("showLogin", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// When Discourse Connect is enabled, redirect to the SSO endpoint
|
||||
if (auth_immediately && enable_discourse_connect) {
|
||||
const returnPath = cookie("destination_url")
|
||||
? getURL("/")
|
||||
: encodeURIComponent(url);
|
||||
window.location = getURL(`/session/sso?return_path=${returnPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatically store the current URL (aka. the one **before** the transition)
|
||||
if (!currentUser) {
|
||||
if (isValidDestinationUrl(url)) {
|
||||
cookie("destination_url", url);
|
||||
} else if (DiscourseURL.isInternalTopic(referrer)) {
|
||||
cookie("destination_url", referrer);
|
||||
}
|
||||
} else if (this.login.isOnlyOneExternalLoginMethod) {
|
||||
this.login.singleExternalLogin();
|
||||
} else if (this.siteSettings.enable_discourse_connect) {
|
||||
this.router
|
||||
.replaceWith(`/${defaultHomepage()}`)
|
||||
.followRedirects()
|
||||
.then((e) => next(() => e.send("showLogin")));
|
||||
}
|
||||
}
|
||||
|
||||
model() {
|
||||
if (this.siteSettings.login_required) {
|
||||
return StaticPage.find("login");
|
||||
// Automatically kick off the external login if it's the only one available
|
||||
if (isOnlyOneExternalLoginMethod) {
|
||||
if (auth_immediately || login_required || !from || wantsTo) {
|
||||
this.isRedirecting = true;
|
||||
singleExternalLogin();
|
||||
} else {
|
||||
router.replaceWith("discovery.login-required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
|
||||
const { canSignUp } = this.controllerFor("application");
|
||||
controller.set("canSignUp", canSignUp);
|
||||
controller.set("flashType", "");
|
||||
controller.set("flash", "");
|
||||
|
||||
if (
|
||||
this.internalReferrer ||
|
||||
DiscourseURL.isInternalTopic(document.referrer)
|
||||
) {
|
||||
controller.set(
|
||||
"referrerTopicUrl",
|
||||
this.internalReferrer || document.referrer
|
||||
);
|
||||
// We're in the middle of an authentication flow
|
||||
if (document.getElementById("data-authentication")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.login.isOnlyOneExternalLoginMethod) {
|
||||
if (this.siteSettings.auth_immediately) {
|
||||
controller.set("isRedirectingToExternalAuth", true);
|
||||
} else {
|
||||
controller.set("singleExternalLogin", this.login.singleExternalLogin);
|
||||
}
|
||||
}
|
||||
controller.isRedirectingToExternalAuth = this.isRedirecting;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import CreateInvite from "discourse/components/modal/create-invite";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class extends DiscourseRoute {
|
||||
@service router;
|
||||
@service modal;
|
||||
@service dialog;
|
||||
@service currentUser;
|
||||
@service dialog;
|
||||
@service modal;
|
||||
@service router;
|
||||
|
||||
async beforeModel(transition) {
|
||||
if (this.currentUser) {
|
||||
if (transition.from) {
|
||||
// when navigating from another ember route
|
||||
transition.abort();
|
||||
this.#openInviteModalIfAllowed();
|
||||
} else {
|
||||
// when landing on this route from a full page load
|
||||
this.router
|
||||
.replaceWith("discovery.latest")
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
this.#openInviteModalIfAllowed();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cookie("destination_url", window.location.href);
|
||||
this.router.replaceWith("login");
|
||||
if (!this.currentUser) {
|
||||
transition.send("showLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
// when navigating from another ember route
|
||||
if (transition.from) {
|
||||
transition.abort();
|
||||
this.#openInviteModalIfAllowed();
|
||||
return;
|
||||
}
|
||||
|
||||
// when landing on the route from a full page load
|
||||
this.router
|
||||
.replaceWith(`discovery.${defaultHomepage()}`)
|
||||
.followRedirects()
|
||||
.then(() => this.#openInviteModalIfAllowed());
|
||||
}
|
||||
|
||||
#openInviteModalIfAllowed() {
|
||||
|
|
|
@ -1,89 +1,65 @@
|
|||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
import Group from "discourse/models/group";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class NewMessage extends DiscourseRoute {
|
||||
@service dialog;
|
||||
export default class extends DiscourseRoute {
|
||||
@service composer;
|
||||
@service dialog;
|
||||
@service router;
|
||||
|
||||
beforeModel(transition) {
|
||||
const params = transition.to.queryParams;
|
||||
const userName = params.username;
|
||||
const groupName = params.groupname || params.group_name;
|
||||
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href);
|
||||
this.router.replaceWith("login");
|
||||
transition.send("showLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
const { queryParams: params } = transition.to;
|
||||
|
||||
// when navigating from another ember route
|
||||
if (transition.from) {
|
||||
transition.abort();
|
||||
|
||||
if (userName) {
|
||||
return this.openComposer(transition, userName);
|
||||
}
|
||||
|
||||
if (groupName) {
|
||||
// send a message to a group
|
||||
return Group.messageable(groupName)
|
||||
.then((result) => {
|
||||
if (result.messageable) {
|
||||
this.openComposer(transition, groupName);
|
||||
} else {
|
||||
this.dialog.alert(
|
||||
i18n("composer.cant_send_pm", { username: groupName })
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() =>
|
||||
this.dialog.alert(i18n("composer.create_message_error"))
|
||||
);
|
||||
}
|
||||
|
||||
return this.openComposer(transition);
|
||||
} else {
|
||||
this.router
|
||||
.replaceWith("discovery.latest")
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
if (userName) {
|
||||
return this.openComposer(transition, userName);
|
||||
}
|
||||
|
||||
if (groupName) {
|
||||
// send a message to a group
|
||||
return Group.messageable(groupName)
|
||||
.then((result) => {
|
||||
if (result.messageable) {
|
||||
this.openComposer(transition, groupName);
|
||||
} else {
|
||||
this.dialog.alert(
|
||||
i18n("composer.cant_send_pm", { username: groupName })
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() =>
|
||||
this.dialog.alert(i18n("composer.create_message_error"))
|
||||
);
|
||||
}
|
||||
|
||||
return this.openComposer(transition);
|
||||
});
|
||||
this.#openComposerWithPrefilledValues(params);
|
||||
return;
|
||||
}
|
||||
|
||||
// when landing on the route from a full page load
|
||||
this.router
|
||||
.replaceWith(`discovery.${defaultHomepage()}`)
|
||||
.followRedirects()
|
||||
.then(() => this.#openComposerWithPrefilledValues(params));
|
||||
}
|
||||
|
||||
openComposer(transition, recipients) {
|
||||
next(() => {
|
||||
this.composer.openNewMessage({
|
||||
recipients,
|
||||
title: transition.to.queryParams.title,
|
||||
body: transition.to.queryParams.body,
|
||||
});
|
||||
});
|
||||
#openComposer({ title, body }, recipients = "") {
|
||||
next(() => this.composer.openNewMessage({ recipients, title, body }));
|
||||
}
|
||||
|
||||
#openComposerWithPrefilledValues(params) {
|
||||
const username = params.username;
|
||||
const groupname = params.groupname || params.group_name;
|
||||
|
||||
if (username) {
|
||||
this.#openComposer(params, username);
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupname) {
|
||||
Group.messageable(groupname)
|
||||
.then(({ messageable }) => {
|
||||
if (messageable) {
|
||||
this.#openComposer(params, groupname);
|
||||
} else {
|
||||
this.dialog.alert(
|
||||
i18n("composer.cant_send_pm", { username: groupname })
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() => this.dialog.alert(i18n("composer.create_message_error")));
|
||||
return;
|
||||
}
|
||||
|
||||
this.#openComposer(params);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,117 +1,103 @@
|
|||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
import Category from "discourse/models/category";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class extends DiscourseRoute {
|
||||
@service composer;
|
||||
@service router;
|
||||
@service currentUser;
|
||||
@service router;
|
||||
@service site;
|
||||
|
||||
async beforeModel(transition) {
|
||||
if (this.currentUser) {
|
||||
let category;
|
||||
if (this.site.lazy_load_categories) {
|
||||
if (transition.to.queryParams.category_id) {
|
||||
const categories = await Category.asyncFindByIds([
|
||||
transition.to.queryParams.category_id,
|
||||
]);
|
||||
category = categories[0];
|
||||
} else if (transition.to.queryParams.category) {
|
||||
category = await Category.asyncFindBySlugPath(
|
||||
transition.to.queryParams.category
|
||||
);
|
||||
}
|
||||
} else {
|
||||
category = this.parseCategoryFromTransition(transition);
|
||||
}
|
||||
|
||||
if (category) {
|
||||
// Using URL-based transition to avoid bug with dynamic segments and refreshModel query params
|
||||
// https://github.com/emberjs/ember.js/issues/16992
|
||||
this.router
|
||||
.replaceWith(`/c/${category.id}`)
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
if (this.currentUser.can_create_topic) {
|
||||
this.openComposer({ transition, category });
|
||||
}
|
||||
});
|
||||
} else if (transition.from) {
|
||||
// Navigation from another ember route
|
||||
transition.abort();
|
||||
this.openComposer({ transition });
|
||||
} else {
|
||||
this.router
|
||||
.replaceWith("discovery.latest")
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
if (this.currentUser.can_create_topic) {
|
||||
this.openComposer({ transition });
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// User is not logged in
|
||||
cookie("destination_url", window.location.href);
|
||||
this.router.replaceWith("login");
|
||||
if (!this.currentUser) {
|
||||
transition.send("showLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
const { queryParams: params } = transition.to;
|
||||
const category = await this.#loadCategoryFromTransition(params);
|
||||
|
||||
if (category) {
|
||||
// Using URL-based transition to avoid bug with dynamic segments and refreshModel query params
|
||||
// https://github.com/emberjs/ember.js/issues/16992
|
||||
this.router
|
||||
.replaceWith(`/c/${category.id}`)
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
if (this.currentUser.can_create_topic) {
|
||||
this.#openComposer(params, category);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// When navigating from another ember route
|
||||
if (transition.from) {
|
||||
transition.abort();
|
||||
this.#openComposer(params);
|
||||
return;
|
||||
}
|
||||
|
||||
// When landing on the route from a full page load
|
||||
this.router
|
||||
.replaceWith(`discovery.${defaultHomepage()}`)
|
||||
.followRedirects()
|
||||
.then(() => {
|
||||
if (this.currentUser.can_create_topic) {
|
||||
this.#openComposer(params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openComposer({ transition, category }) {
|
||||
next(() => {
|
||||
this.composer.openNewTopic({
|
||||
title: transition.to.queryParams.title,
|
||||
body: transition.to.queryParams.body,
|
||||
category,
|
||||
tags: transition.to.queryParams.tags,
|
||||
});
|
||||
#openComposer(params, category) {
|
||||
const { title, body, tags } = params;
|
||||
|
||||
this.composer.set("formTemplateInitialValues", transition.to.queryParams);
|
||||
next(() => {
|
||||
this.composer.openNewTopic({ title, body, category, tags });
|
||||
this.composer.set("formTemplateInitialValues", params);
|
||||
});
|
||||
}
|
||||
|
||||
parseCategoryFromTransition(transition) {
|
||||
let category;
|
||||
async #loadCategoryFromTransition(params) {
|
||||
let category = null;
|
||||
|
||||
if (transition.to.queryParams.category_id) {
|
||||
const categoryId = transition.to.queryParams.category_id;
|
||||
category = Category.findById(categoryId);
|
||||
} else if (transition.to.queryParams.category) {
|
||||
const splitCategory = transition.to.queryParams.category.split("/");
|
||||
|
||||
category = this._getCategory(
|
||||
splitCategory[0],
|
||||
splitCategory[1],
|
||||
"nameLower"
|
||||
);
|
||||
|
||||
if (!category) {
|
||||
category = this._getCategory(
|
||||
splitCategory[0],
|
||||
splitCategory[1],
|
||||
"slug"
|
||||
);
|
||||
if (this.site.lazy_load_categories) {
|
||||
if (params.category_id) {
|
||||
category = await Category.asyncFindById(params.category_id);
|
||||
} else if (params.category) {
|
||||
category = await Category.asyncFindBySlugPath(params.category);
|
||||
}
|
||||
} else {
|
||||
if (params.category_id) {
|
||||
category = Category.findById(params.category_id);
|
||||
} else if (params.category) {
|
||||
// TODO: does this work with more than 2 levels of categories?
|
||||
const [main, sub] = params.category.split("/");
|
||||
category = this.#getCategory(main, sub, "nameLower");
|
||||
category ||= this.#getCategory(main, sub, "slug");
|
||||
}
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
_getCategory(mainCategory, subCategory, type) {
|
||||
let category;
|
||||
if (!subCategory) {
|
||||
category = this.site.categories.findBy(type, mainCategory.toLowerCase());
|
||||
#getCategory(main, sub, type) {
|
||||
let category = null;
|
||||
|
||||
if (!sub) {
|
||||
category = this.site.categories.findBy(type, main.toLowerCase());
|
||||
} else {
|
||||
const categories = this.site.categories;
|
||||
const main = categories.findBy(type, mainCategory.toLowerCase());
|
||||
if (main) {
|
||||
const { categories } = this.site;
|
||||
const parent = categories.findBy(type, main.toLowerCase());
|
||||
|
||||
if (parent) {
|
||||
category = categories.find((item) => {
|
||||
return (
|
||||
item &&
|
||||
item[type] === subCategory.toLowerCase() &&
|
||||
item.parent_category_id === main.id
|
||||
item[type] === sub.toLowerCase() &&
|
||||
item.parent_category_id === parent.id
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,47 +1,103 @@
|
|||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import {
|
||||
defaultHomepage,
|
||||
isValidDestinationUrl,
|
||||
postRNWebviewMessage,
|
||||
} from "discourse/lib/utilities";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class SignupRoute extends DiscourseRoute {
|
||||
@service siteSettings;
|
||||
@service router;
|
||||
export default class extends DiscourseRoute {
|
||||
@service capabilities;
|
||||
@service dialog;
|
||||
@service login;
|
||||
@service router;
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
|
||||
authComplete = false;
|
||||
isRedirecting = false;
|
||||
|
||||
beforeModel() {
|
||||
this.authComplete = document.getElementById(
|
||||
"data-authentication"
|
||||
)?.dataset.authenticationData;
|
||||
beforeModel(transition) {
|
||||
const { from, wantsTo } = transition;
|
||||
const { currentUser, dialog, router } = this;
|
||||
const { isReadOnly } = this.site;
|
||||
const { isAppWebview } = this.capabilities;
|
||||
const {
|
||||
auth_immediately,
|
||||
enable_discourse_connect,
|
||||
invite_only,
|
||||
login_required,
|
||||
} = this.siteSettings;
|
||||
const { pathname: url } = window.location;
|
||||
const { referrer } = document;
|
||||
const { canSignUp } = this.controllerFor("application");
|
||||
const { isOnlyOneExternalLoginMethod, singleExternalLogin } = this.login;
|
||||
|
||||
if (this.login.isOnlyOneExternalLoginMethod && !this.authComplete) {
|
||||
this.login.singleExternalLogin({ signup: true });
|
||||
} else {
|
||||
this.showCreateAccount();
|
||||
// Can't sign up when the site is read-only
|
||||
if (isReadOnly) {
|
||||
transition.abort();
|
||||
dialog.alert(i18n("read_only_mode.login_disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
// In some cases, the user is only allowed to log in, not sign up
|
||||
if (!canSignUp && (invite_only || !auth_immediately)) {
|
||||
const route = `discovery.${login_required ? "login-required" : defaultHomepage()}`;
|
||||
router.replaceWith(route).followRedirects();
|
||||
return;
|
||||
}
|
||||
|
||||
// We're in the middle of an authentication flow
|
||||
if (document.getElementById("data-authentication")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When inside a webview, it handles the login flow itself
|
||||
if (isAppWebview) {
|
||||
postRNWebviewMessage("showLogin", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// When Discourse Connect is enabled, redirect to the SSO endpoint
|
||||
if (auth_immediately && enable_discourse_connect) {
|
||||
const returnPath = cookie("destination_url")
|
||||
? getURL("/")
|
||||
: encodeURIComponent(url);
|
||||
window.location = getURL(`/session/sso?return_path=${returnPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatically store the current URL (aka. the one **before** the transition)
|
||||
if (!currentUser) {
|
||||
if (isValidDestinationUrl(url)) {
|
||||
cookie("destination_url", url);
|
||||
} else if (DiscourseURL.isInternalTopic(referrer)) {
|
||||
cookie("destination_url", referrer);
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically kick off the external login if it's the only one available
|
||||
if (isOnlyOneExternalLoginMethod) {
|
||||
if (auth_immediately || login_required || !from || wantsTo) {
|
||||
this.isRedirecting = true;
|
||||
singleExternalLogin({ signup: true });
|
||||
} else {
|
||||
router.replaceWith("discovery.login-required");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
|
||||
if (this.login.isOnlyOneExternalLoginMethod && !this.authComplete) {
|
||||
controller.set("isRedirectingToExternalAuth", true);
|
||||
// We're in the middle of an authentication flow
|
||||
if (document.getElementById("data-authentication")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async showCreateAccount() {
|
||||
const { canSignUp } = this.controllerFor("application");
|
||||
if (!canSignUp) {
|
||||
const route = await this.router
|
||||
.replaceWith(
|
||||
this.siteSettings.login_required ? "login" : "discovery.latest"
|
||||
)
|
||||
.followRedirects();
|
||||
if (canSignUp) {
|
||||
next(() => route.send("showCreateAccount"));
|
||||
}
|
||||
}
|
||||
controller.isRedirectingToExternalAuth = this.isRedirecting;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,10 @@ export default RouteTemplate(
|
|||
{{hideApplicationSidebar}}
|
||||
{{bodyClass "login-page"}}
|
||||
|
||||
{{#if @controller.isRedirectingToExternalAuth}}
|
||||
{{! Hide the login form if the site has only one external }}
|
||||
{{! authentication method and is being automatically redirected to it }}
|
||||
{{loadingSpinner}}
|
||||
{{else}}
|
||||
<div class="login-fullpage">
|
||||
<div class="login-fullpage">
|
||||
{{#if @controller.isRedirectingToExternalAuth}}
|
||||
{{loadingSpinner size="large"}}
|
||||
{{else}}
|
||||
<FlashMessage
|
||||
@flash={{@controller.flash}}
|
||||
@type={{@controller.flashType}}
|
||||
|
@ -73,7 +71,7 @@ export default RouteTemplate(
|
|||
{{#if @controller.showLoginButtons}}
|
||||
|
||||
<LoginButtons
|
||||
@externalLogin={{@controller.externalLoginAction}}
|
||||
@externalLogin={{@controller.externalLogin}}
|
||||
@passkeyLogin={{@controller.passkeyLogin}}
|
||||
@context="login"
|
||||
/>
|
||||
|
@ -97,7 +95,7 @@ export default RouteTemplate(
|
|||
<PluginOutlet
|
||||
@name="login-wrapper"
|
||||
@outletArgs={{lazyHash
|
||||
externalLoginAction=@controller.externalLoginAction
|
||||
externalLogin=@controller.externalLogin
|
||||
}}
|
||||
>
|
||||
<LocalLoginForm
|
||||
|
@ -118,19 +116,18 @@ export default RouteTemplate(
|
|||
@otherMethodAllowed={{@controller.otherMethodAllowed}}
|
||||
@showSecondFactor={{@controller.showSecondFactor}}
|
||||
@handleForgotPassword={{@controller.handleForgotPassword}}
|
||||
@login={{@controller.triggerLogin}}
|
||||
@login={{@controller.localLogin}}
|
||||
@flashChanged={{@controller.flashChanged}}
|
||||
@flashTypeChanged={{@controller.flashTypeChanged}}
|
||||
@securityKeyCredentialChanged={{@controller.securityKeyCredentialChanged}}
|
||||
/>
|
||||
|
||||
</PluginOutlet>
|
||||
|
||||
{{#if @controller.site.desktopView}}
|
||||
<LoginPageCta
|
||||
@canLoginLocal={{@controller.canLoginLocal}}
|
||||
@showSecurityKey={{@controller.showSecurityKey}}
|
||||
@login={{@controller.triggerLogin}}
|
||||
@login={{@controller.localLogin}}
|
||||
@loginButtonLabel={{@controller.loginButtonLabel}}
|
||||
@loginDisabled={{@controller.loginDisabled}}
|
||||
@showSignupLink={{@controller.showSignupLink}}
|
||||
|
@ -154,7 +151,7 @@ export default RouteTemplate(
|
|||
{{#if @controller.hasAtLeastOneLoginButton}}
|
||||
<div class="login-right-side">
|
||||
<LoginButtons
|
||||
@externalLogin={{@controller.externalLoginAction}}
|
||||
@externalLogin={{@controller.externalLogin}}
|
||||
@passkeyLogin={{@controller.passkeyLogin}}
|
||||
@context="login"
|
||||
/>
|
||||
|
@ -169,7 +166,7 @@ export default RouteTemplate(
|
|||
<LoginPageCta
|
||||
@canLoginLocal={{@controller.canLoginLocal}}
|
||||
@showSecurityKey={{@controller.showSecurityKey}}
|
||||
@login={{@controller.triggerLogin}}
|
||||
@login={{@controller.localLogin}}
|
||||
@loginButtonLabel={{@controller.loginButtonLabel}}
|
||||
@loginDisabled={{@controller.loginDisabled}}
|
||||
@showSignupLink={{@controller.showSignupLink}}
|
||||
|
@ -182,7 +179,7 @@ export default RouteTemplate(
|
|||
|
||||
</div>
|
||||
<PluginOutlet @name="below-login-page" />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
);
|
||||
|
|
|
@ -33,12 +33,10 @@ export default RouteTemplate(
|
|||
{{hideApplicationSidebar}}
|
||||
{{bodyClass "signup-page"}}
|
||||
|
||||
{{#if @controller.isRedirectingToExternalAuth}}
|
||||
{{! Hide the signup form if the site has only one external }}
|
||||
{{! authentication method and is being automatically redirected to it }}
|
||||
{{loadingSpinner}}
|
||||
{{else}}
|
||||
<div class="signup-fullpage">
|
||||
<div class="signup-fullpage">
|
||||
{{#if @controller.isRedirectingToExternalAuth}}
|
||||
{{loadingSpinner size="large"}}
|
||||
{{else}}
|
||||
<FlashMessage
|
||||
@flash={{@controller.flash}}
|
||||
@type={{@controller.flashType}}
|
||||
|
@ -342,7 +340,7 @@ export default RouteTemplate(
|
|||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
);
|
||||
|
|
|
@ -113,7 +113,7 @@ acceptance("Create Account", function () {
|
|||
sinon.stub(LoginMethod, "buildPostForm").callsFake((url, params) => {
|
||||
assert.step("buildPostForm");
|
||||
assert.strictEqual(url, "/auth/facebook");
|
||||
assert.deepEqual(params, { signup: true });
|
||||
assert.true(params.signup);
|
||||
});
|
||||
|
||||
await visit("/signup");
|
||||
|
|
|
@ -732,8 +732,15 @@ class ApplicationController < ActionController::Base
|
|||
raise Discourse::InvalidAccess.new unless SiteSetting.wizard_enabled?
|
||||
end
|
||||
|
||||
# Keep in sync with `NO_DESTINATION_COOKIE` in `app/assets/javascripts/discourse/app/lib/utilities.js`
|
||||
NO_DESTINATION_COOKIE = %w[/login /signup /session/ /auth/ /uploads/].freeze
|
||||
|
||||
def is_valid_destination_url?(url)
|
||||
url.present? && url != path("/") && NO_DESTINATION_COOKIE.none? { url.start_with? path(_1) }
|
||||
end
|
||||
|
||||
def destination_url
|
||||
request.original_url unless request.original_url =~ /uploads/
|
||||
request.original_url if is_valid_destination_url?(request.original_url)
|
||||
end
|
||||
|
||||
def redirect_to_login
|
||||
|
@ -744,7 +751,7 @@ class ApplicationController < ActionController::Base
|
|||
session[:destination_url] = destination_url
|
||||
redirect_to path("/session/sso")
|
||||
elsif SiteSetting.auth_immediately && !SiteSetting.enable_local_logins &&
|
||||
Discourse.enabled_authenticators.length == 1 && !cookies[:authentication_data]
|
||||
Discourse.enabled_authenticators.one? && !cookies[:authentication_data]
|
||||
# Only one authentication provider, direct straight to it.
|
||||
# If authentication_data is present, then we are halfway though registration. Don't redirect offsite
|
||||
cookies[:destination_url] = destination_url
|
||||
|
|
|
@ -219,9 +219,8 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||
|
||||
def persist_auth_token(auth)
|
||||
secret = SecureRandom.hex
|
||||
secure_session.set "#{Users::AssociateAccountsController.key(secret)}",
|
||||
auth.to_json,
|
||||
expires: 10.minutes
|
||||
key = Users::AssociateAccountsController.key(secret)
|
||||
secure_session.set key, auth.to_json, expires: 10.minutes
|
||||
"#{Discourse.base_path}/associate/#{secret}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ OmniAuth.config.before_request_phase do |env|
|
|||
request.session[:auth_reconnect] = !!request.params["reconnect"]
|
||||
|
||||
# If the client provided an origin, store in session to redirect back
|
||||
request.session[:destination_url] = request.params["origin"]
|
||||
request.session[:destination_url] = request.params["origin"] if request.params["origin"].present?
|
||||
end
|
||||
|
||||
OmniAuth.config.on_failure do |env|
|
||||
|
|
|
@ -96,22 +96,13 @@ class Auth::ManagedAuthenticator < Auth::Authenticator
|
|||
retrieve_profile(association.user, association.info)
|
||||
|
||||
# Build the Auth::Result object
|
||||
result = Auth::Result.new
|
||||
info = auth_token[:info]
|
||||
result = Auth::Result.new
|
||||
result.email = info[:email]
|
||||
result.name =
|
||||
(
|
||||
if (info[:first_name] && info[:last_name])
|
||||
"#{info[:first_name]} #{info[:last_name]}"
|
||||
else
|
||||
info[:name]
|
||||
end
|
||||
)
|
||||
if result.name.present? && result.name == result.email
|
||||
# Some IDPs send the email address in the name parameter (e.g. Auth0 with default configuration)
|
||||
# We add some generic protection here, so that users don't accidently make their email addresses public
|
||||
result.name = nil
|
||||
end
|
||||
result.name = "#{info[:first_name]} #{info[:last_name]}".presence || info[:name]
|
||||
# Some IDPs send the email address in the name parameter (e.g. Auth0 with default configuration)
|
||||
# We add some generic protection here, so that users don't accidently make their email addresses public
|
||||
result.name = nil if result.name.present? && result.name == result.email
|
||||
result.username = info[:nickname]
|
||||
result.email_valid = primary_email_verified?(auth_token) if result.email.present?
|
||||
result.overrides_email = always_update_user_email?
|
||||
|
|
|
@ -35,8 +35,7 @@ class Middleware::OmniauthBypassMiddleware
|
|||
return @app.call(env) unless env["PATH_INFO"].start_with?("/auth")
|
||||
|
||||
# When only one provider is enabled, assume it can be completely trusted, and allow GET requests
|
||||
only_one_provider =
|
||||
!SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
|
||||
only_one_provider = !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.one?
|
||||
|
||||
allow_get = only_one_provider || !SiteSetting.auth_require_interaction
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Slug
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { service } from "@ember/service";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import UserTopicListRoute from "discourse/routes/user-topic-list";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
|
@ -14,8 +13,7 @@ export default class UserActivityAssigned extends UserTopicListRoute {
|
|||
|
||||
beforeModel() {
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href);
|
||||
this.router.transitionTo("login");
|
||||
this.send("showLogin");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class Transcript extends DiscourseRoute {
|
||||
|
@ -10,18 +11,17 @@ export default class Transcript extends DiscourseRoute {
|
|||
|
||||
async model(params) {
|
||||
if (!this.currentUser) {
|
||||
this.session.set("shouldRedirectToUrl", window.location.href);
|
||||
this.router.replaceWith("login");
|
||||
this.send("showLogin");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.router.replaceWith("discovery.latest").followRedirects();
|
||||
await this.router
|
||||
.replaceWith(`discovery.${defaultHomepage()}`)
|
||||
.followRedirects();
|
||||
|
||||
try {
|
||||
const result = await ajax(`/chat-transcript/${params.secret}`);
|
||||
this.composer.openNewTopic({
|
||||
body: result.content,
|
||||
});
|
||||
const { content: body } = await ajax(`/chat-transcript/${params.secret}`);
|
||||
this.composer.openNewTopic({ body });
|
||||
} catch (e) {
|
||||
popupAjaxError(e);
|
||||
}
|
||||
|
|
|
@ -271,8 +271,7 @@ export default class DiscourseReactionsActions extends Component {
|
|||
toggle(params) {
|
||||
if (!this.currentUser) {
|
||||
if (this.args.showLogin) {
|
||||
this.args.showLogin();
|
||||
return;
|
||||
return this.args.showLogin();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,8 +452,7 @@ export default class DiscourseReactionsActions extends Component {
|
|||
toggleFromButton(attrs) {
|
||||
if (!this.currentUser) {
|
||||
if (this.args.showLogin) {
|
||||
this.args.showLogin();
|
||||
return;
|
||||
return this.args.showLogin();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import Component from "@glimmer/component";
|
|||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { applyBehaviorTransformer } from "discourse/lib/transformer";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
|
@ -63,9 +62,7 @@ export default class VoteBox extends Component {
|
|||
click() {
|
||||
applyBehaviorTransformer("topic-vote-button-click", () => {
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
this.args.showLogin();
|
||||
return;
|
||||
return this.args.showLogin();
|
||||
}
|
||||
|
||||
const { topic } = this.args;
|
||||
|
|
|
@ -9,7 +9,6 @@ import AsyncContent from "discourse/components/async-content";
|
|||
import SmallUserList from "discourse/components/small-user-list";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
|
||||
|
@ -45,9 +44,7 @@ export default class VoteBox extends Component {
|
|||
event.stopPropagation();
|
||||
|
||||
if (!this.currentUser) {
|
||||
cookie("destination_url", window.location.href, { path: "/" });
|
||||
this.args.showLogin();
|
||||
return;
|
||||
return this.args.showLogin();
|
||||
}
|
||||
|
||||
if (this.showWhoVoted) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
shared_examples "signup scenarios" do
|
||||
let(:signup_form) { PageObjects::Pages::Signup.new }
|
||||
let(:login_form) { PageObjects::Pages::Login.new }
|
||||
let(:signup_page) { PageObjects::Pages::Signup.new }
|
||||
let(:login_page) { PageObjects::Pages::Login.new }
|
||||
let(:invite_form) { PageObjects::Pages::InviteForm.new }
|
||||
let(:activate_account) { PageObjects::Pages::ActivateAccount.new }
|
||||
let(:invite) { Fabricate(:invite) }
|
||||
|
@ -12,27 +12,27 @@ shared_examples "signup scenarios" do
|
|||
before { Jobs.run_immediately! }
|
||||
|
||||
it "can signup" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
end
|
||||
|
||||
it "can signup and activate account" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
|
@ -49,14 +49,14 @@ shared_examples "signup scenarios" do
|
|||
end
|
||||
|
||||
it "can access 2FA preferences screen after signing up and activating account" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
|
@ -102,11 +102,11 @@ shared_examples "signup scenarios" do
|
|||
end
|
||||
|
||||
it "cannot signup with a common password" do
|
||||
signup_form.open.fill_email(invite.email).fill_username("john").fill_password("0123456789")
|
||||
expect(signup_form).to have_valid_fields
|
||||
signup_page.open.fill_email(invite.email).fill_username("john").fill_password("0123456789")
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
expect(signup_form).to have_content(
|
||||
signup_page.click_create_account
|
||||
expect(signup_page).to have_content(
|
||||
I18n.t("activerecord.errors.models.user_password.attributes.password.common"),
|
||||
)
|
||||
end
|
||||
|
@ -115,30 +115,30 @@ shared_examples "signup scenarios" do
|
|||
before { SiteSetting.invite_code = "cupcake" }
|
||||
|
||||
it "can signup with valid code" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
.fill_code("cupcake")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
end
|
||||
|
||||
it "cannot signup with invalid code" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
.fill_code("pudding")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
expect(signup_form).to have_content(I18n.t("login.wrong_invite_code"))
|
||||
expect(signup_form).to have_no_css(".account-created")
|
||||
signup_page.click_create_account
|
||||
expect(signup_page).to have_content(I18n.t("login.wrong_invite_code"))
|
||||
expect(signup_page).to have_no_css(".account-created")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -153,31 +153,31 @@ shared_examples "signup scenarios" do
|
|||
end
|
||||
|
||||
it "can signup when filling the custom field" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
.fill_custom_field("Occupation", "Jedi")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
end
|
||||
|
||||
it "cannot signup without filling the custom field" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
|
||||
expect(signup_form).to have_content("What you do for work")
|
||||
expect(signup_page).to have_content("What you do for work")
|
||||
|
||||
signup_form.click_create_account
|
||||
expect(signup_form).to have_content(I18n.t("js.user_fields.required", name: "Occupation"))
|
||||
expect(signup_form).to have_no_css(".account-created")
|
||||
expect(signup_form).to have_css(".tip.bad", text: "Please enter a value for \"Occupation\"")
|
||||
signup_page.click_create_account
|
||||
expect(signup_page).to have_content(I18n.t("js.user_fields.required", name: "Occupation"))
|
||||
expect(signup_page).to have_no_css(".account-created")
|
||||
expect(signup_page).to have_css(".tip.bad", text: "Please enter a value for \"Occupation\"")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -188,51 +188,51 @@ shared_examples "signup scenarios" do
|
|||
end
|
||||
|
||||
it "can signup but cannot login until approval" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_fields
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to have_valid_fields
|
||||
signup_page.click_create_account
|
||||
|
||||
wait_for(timeout: 5) { User.find_by(username: "john") != nil }
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
|
||||
visit "/"
|
||||
login_form.open
|
||||
login_form.fill_username("john")
|
||||
login_form.fill_password("supersecurepassword")
|
||||
login_form.click_login
|
||||
expect(login_form).to have_content(I18n.t("login.not_approved"))
|
||||
login_page.open
|
||||
login_page.fill_username("john")
|
||||
login_page.fill_password("supersecurepassword")
|
||||
login_page.click_login
|
||||
expect(login_page).to have_content(I18n.t("login.not_approved"))
|
||||
|
||||
user = User.find_by(username: "john")
|
||||
user.update!(approved: true)
|
||||
EmailToken.confirm(Fabricate(:email_token, user: user).token)
|
||||
|
||||
login_form.click_login
|
||||
login_page.click_login
|
||||
|
||||
expect(page).to have_current_path("/")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
it "can login directly when using an auto approved email" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email("johndoe@awesomeemail.com")
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
|
||||
expect(page).to have_current_path("/u/account-created")
|
||||
|
||||
user = User.find_by(username: "john")
|
||||
EmailToken.confirm(Fabricate(:email_token, user:).token)
|
||||
visit "/"
|
||||
login_form.open.fill_username("john").fill_password("supersecurepassword").click_login
|
||||
login_page.open.fill_username("john").fill_password("supersecurepassword").click_login
|
||||
|
||||
expect(page).to have_current_path("/")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
|
@ -244,13 +244,13 @@ shared_examples "signup scenarios" do
|
|||
|
||||
it "can signup and activate account" do
|
||||
visit("/discuss/signup")
|
||||
signup_form
|
||||
signup_page
|
||||
.fill_email(invite.email)
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_fields
|
||||
expect(signup_page).to have_valid_fields
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_current_path("/discuss/u/account-created")
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
|
@ -275,27 +275,27 @@ shared_examples "signup scenarios" do
|
|||
end
|
||||
|
||||
it "cannot signup" do
|
||||
signup_form
|
||||
signup_page
|
||||
.open
|
||||
.fill_email("blocked@example.com")
|
||||
.fill_username("john")
|
||||
.fill_password("supersecurepassword")
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_password
|
||||
expect(signup_form).to have_content(I18n.t("user.email.not_allowed"))
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_password
|
||||
expect(signup_page).to have_content(I18n.t("user.email.not_allowed"))
|
||||
end
|
||||
end
|
||||
|
||||
context "when site is invite only" do
|
||||
before { SiteSetting.invite_only = true }
|
||||
|
||||
it "cannot open the signup modal" do
|
||||
signup_form.open
|
||||
expect(signup_form).to be_closed
|
||||
it "cannot open the signup page" do
|
||||
signup_page.open
|
||||
expect(signup_page).to be_closed
|
||||
expect(page).to have_no_css(".sign-up-button")
|
||||
|
||||
login_form.open_from_header
|
||||
expect(login_form).to have_no_css("#new-account-link")
|
||||
login_page.open_from_header
|
||||
expect(login_page).to have_no_css("#new-account-link")
|
||||
end
|
||||
|
||||
it "can signup with invite link" do
|
||||
|
@ -333,8 +333,8 @@ shared_examples "signup scenarios" do
|
|||
before { SiteSetting.login_required = true }
|
||||
|
||||
it "displays the name field" do
|
||||
signup_form.open
|
||||
expect(signup_form).to have_name_input
|
||||
signup_page.open
|
||||
expect(signup_page).to have_name_input
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -342,8 +342,8 @@ shared_examples "signup scenarios" do
|
|||
before { SiteSetting.enable_names = false }
|
||||
|
||||
it "hides the name field" do
|
||||
signup_form.open
|
||||
expect(signup_form).to have_no_name_input
|
||||
signup_page.open
|
||||
expect(signup_page).to have_no_name_input
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -352,8 +352,8 @@ shared_examples "signup scenarios" do
|
|||
before { SiteSetting.full_name_requirement = "hidden_at_signup" }
|
||||
|
||||
it "hides the name field" do
|
||||
signup_form.open
|
||||
expect(signup_form).to have_no_name_input
|
||||
signup_page.open
|
||||
expect(signup_page).to have_no_name_input
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -361,16 +361,16 @@ shared_examples "signup scenarios" do
|
|||
before { SiteSetting.full_name_requirement = "required_at_signup" }
|
||||
|
||||
it "displays the name field" do
|
||||
signup_form.open
|
||||
expect(signup_form).to have_name_input
|
||||
signup_page.open
|
||||
expect(signup_page).to have_name_input
|
||||
end
|
||||
|
||||
context "when enable_names is false" do
|
||||
before { SiteSetting.enable_names = false }
|
||||
|
||||
it "hides the name field" do
|
||||
signup_form.open
|
||||
expect(signup_form).to have_no_name_input
|
||||
signup_page.open
|
||||
expect(signup_page).to have_no_name_input
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -381,13 +381,13 @@ shared_examples "signup scenarios" do
|
|||
user_field_text = Fabricate(:user_field)
|
||||
user_field_dropdown = Fabricate(:user_field_dropdown)
|
||||
|
||||
signup_form.open
|
||||
signup_page.open
|
||||
find(".signup-page-cta__signup").click
|
||||
|
||||
expect(signup_form).to have_content(
|
||||
expect(signup_page).to have_content(
|
||||
I18n.t("js.user_fields.required", name: user_field_text.name),
|
||||
)
|
||||
expect(signup_form).to have_content(
|
||||
expect(signup_page).to have_content(
|
||||
I18n.t("js.user_fields.required_select", name: user_field_dropdown.name),
|
||||
)
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
shared_context "with omniauth setup" do
|
||||
include OmniauthHelpers
|
||||
|
||||
let(:login_form) { PageObjects::Pages::Login.new }
|
||||
let(:signup_form) { PageObjects::Pages::Signup.new }
|
||||
let(:login_page) { PageObjects::Pages::Login.new }
|
||||
let(:signup_page) { PageObjects::Pages::Signup.new }
|
||||
|
||||
before { OmniAuth.config.test_mode = true }
|
||||
end
|
||||
|
@ -21,14 +21,14 @@ shared_examples "social authentication scenarios" do
|
|||
mock_facebook_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("facebook")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_page.open.click_social_button("facebook")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -41,14 +41,14 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -57,14 +57,14 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth(verified: false)
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".account-created")
|
||||
end
|
||||
end
|
||||
|
@ -78,13 +78,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_github_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("github")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("github")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -93,13 +93,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_github_auth(verified: false)
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("github")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("github")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".account-created")
|
||||
end
|
||||
end
|
||||
|
@ -114,17 +114,17 @@ shared_examples "social authentication scenarios" do
|
|||
mock_github_auth(name: "")
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("github")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_editable_name_input
|
||||
signup_page.open.click_social_button("github")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_editable_name_input
|
||||
|
||||
signup_form.fill_input("#new-account-name", "Test User")
|
||||
signup_page.fill_input("#new-account-name", "Test User")
|
||||
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -132,14 +132,14 @@ shared_examples "social authentication scenarios" do
|
|||
mock_github_auth(name: "Some Name")
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("github")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_disabled_name_input
|
||||
signup_page.open.click_social_button("github")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_disabled_name_input
|
||||
|
||||
signup_form.click_create_account
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -153,14 +153,14 @@ shared_examples "social authentication scenarios" do
|
|||
mock_twitter_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("twitter")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
signup_form.fill_email(OmniauthHelpers::EMAIL)
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("twitter")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
signup_page.fill_email(OmniauthHelpers::EMAIL)
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".account-created")
|
||||
end
|
||||
|
||||
|
@ -169,13 +169,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_twitter_auth(verified: false)
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("twitter")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
signup_form.fill_email(OmniauthHelpers::EMAIL)
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("twitter")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
signup_page.fill_email(OmniauthHelpers::EMAIL)
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".account-created")
|
||||
end
|
||||
end
|
||||
|
@ -189,13 +189,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_discord_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("discord")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("discord")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -212,13 +212,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_linkedin_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("linkedin_oidc")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("linkedin_oidc")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -232,13 +232,13 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_no_right_side_column
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_no_right_side_column
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -255,15 +255,15 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_form).to have_disabled_username
|
||||
expect(signup_form).to have_disabled_email
|
||||
expect(signup_form).to have_disabled_name
|
||||
signup_form.click_create_account
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
expect(signup_page).to have_disabled_username
|
||||
expect(signup_page).to have_disabled_email
|
||||
expect(signup_page).to have_disabled_name
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -279,7 +279,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -299,23 +299,24 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
|
||||
visit("/login")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
visit("/signup")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
visit("/")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -324,22 +325,22 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
|
||||
visit("/login")
|
||||
expect(page).to have_css(".btn-social")
|
||||
expect(signup_page).to be_open
|
||||
|
||||
visit("/")
|
||||
expect(page).to have_css(".login-welcome")
|
||||
expect(page).to have_css(".site-logo")
|
||||
|
||||
find(".login-welcome .login-button").click
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_page).to be_open
|
||||
|
||||
visit("/")
|
||||
find(".login-welcome .sign-up-button").click
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -348,11 +349,11 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
|
||||
visit("/signup")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
|
@ -366,37 +367,51 @@ shared_examples "social authentication scenarios" do
|
|||
end
|
||||
end
|
||||
|
||||
it "automatically redirects when using the login button" do
|
||||
it "automatically redirects when using the login button or the routes" do
|
||||
SiteSetting.auth_immediately = false
|
||||
mock_google_auth
|
||||
|
||||
visit("/")
|
||||
find(".header-buttons .login-button").click
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
visit("/login")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
visit("/signup")
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
signup_page.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
it "automatically redirects when using the routes" do
|
||||
SiteSetting.auth_immediately = false
|
||||
it "redirects the user back to the last page they visited" do
|
||||
mock_google_auth
|
||||
|
||||
visit("/login")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
category = Fabricate(:category)
|
||||
|
||||
visit(category.url)
|
||||
|
||||
find(".header-buttons .login-button").click
|
||||
|
||||
expect(signup_page).to be_open
|
||||
expect(signup_page).to have_no_password_input
|
||||
expect(signup_page).to have_valid_username
|
||||
expect(signup_page).to have_valid_email
|
||||
|
||||
signup_page.click_create_account
|
||||
|
||||
visit("/signup")
|
||||
expect(signup_form).to be_open
|
||||
expect(signup_form).to have_no_password_input
|
||||
expect(signup_form).to have_valid_username
|
||||
expect(signup_form).to have_valid_email
|
||||
signup_form.click_create_account
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
expect(page).to have_current_path(category.url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -419,7 +434,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_facebook_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("facebook")
|
||||
signup_page.open.click_social_button("facebook")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -432,7 +447,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_google_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("google_oauth2")
|
||||
signup_page.open.click_social_button("google_oauth2")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -445,7 +460,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_github_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("github")
|
||||
signup_page.open.click_social_button("github")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -464,7 +479,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_twitter_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("twitter")
|
||||
signup_page.open.click_social_button("twitter")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -477,7 +492,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_discord_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("discord")
|
||||
signup_page.open.click_social_button("discord")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -494,7 +509,7 @@ shared_examples "social authentication scenarios" do
|
|||
mock_linkedin_auth
|
||||
visit("/")
|
||||
|
||||
signup_form.open.click_social_button("linkedin_oidc")
|
||||
signup_page.open.click_social_button("linkedin_oidc")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
|
@ -504,11 +519,11 @@ end
|
|||
describe "Social authentication", type: :system do
|
||||
before { SiteSetting.full_name_requirement = "optional_at_signup" }
|
||||
|
||||
context "when fullpage desktop" do
|
||||
context "when desktop" do
|
||||
include_examples "social authentication scenarios"
|
||||
end
|
||||
|
||||
context "when fullpage mobile", mobile: true do
|
||||
context "when mobile", mobile: true do
|
||||
include_examples "social authentication scenarios"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,8 +70,6 @@ describe "User preferences | Security", type: :system do
|
|||
hasResidentKey: true,
|
||||
isUserVerified: true,
|
||||
) do
|
||||
add_cookie(name: "destination_url", value: "/new")
|
||||
|
||||
user_preferences_security_page.visit(user)
|
||||
|
||||
find(".pref-passkeys__add .btn").click
|
||||
|
@ -104,6 +102,12 @@ describe "User preferences | Security", type: :system do
|
|||
|
||||
user_menu.sign_out
|
||||
|
||||
# ensures /hot isn't the homepage (otherwise the test below is pointless)
|
||||
expect(SiteSetting.top_menu_items.first).not_to eq("hot")
|
||||
|
||||
# visit /hot to ensure we have a destination_url cookie set
|
||||
visit("/hot")
|
||||
|
||||
# login with the key we just created
|
||||
# this triggers the conditional UI for passkeys
|
||||
# which uses the virtual authenticator
|
||||
|
@ -112,7 +116,7 @@ describe "User preferences | Security", type: :system do
|
|||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
|
||||
# ensures that we are redirected to the destination_url cookie
|
||||
expect(page.driver.current_url).to include("/new")
|
||||
expect(page.driver.current_url).to include("/hot")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue