FIX: Automatic auth flow with full page login/signup (#30928)

Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
This commit is contained in:
Jan Cernik
2025-01-23 08:43:07 -03:00
committed by GitHub
parent f23004c626
commit 13f86c99ea
8 changed files with 520 additions and 444 deletions

View File

@ -140,7 +140,11 @@ export default {
}; };
if (siteSettings.full_page_login) { if (siteSettings.full_page_login) {
router.transitionTo("signup").then((signup) => { router
.transitionTo("signup", {
queryParams: { authComplete: true },
})
.then((signup) => {
const signupController = const signupController =
signup.controller || owner.lookup("controller:signup"); signup.controller || owner.lookup("controller:signup");
Object.keys(createAccountProps || {}).forEach((key) => { Object.keys(createAccountProps || {}).forEach((key) => {

View File

@ -17,7 +17,6 @@ import DiscourseURL from "discourse/lib/url";
import { postRNWebviewMessage } from "discourse/lib/utilities"; import { postRNWebviewMessage } from "discourse/lib/utilities";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import Composer from "discourse/models/composer"; import Composer from "discourse/models/composer";
import { findAll } from "discourse/models/login-method";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
@ -44,17 +43,6 @@ export default class ApplicationRoute extends DiscourseRoute {
@setting("title") siteTitle; @setting("title") siteTitle;
@setting("short_site_description") shortSiteDescription; @setting("short_site_description") shortSiteDescription;
get isOnlyOneExternalLoginMethod() {
return (
!this.siteSettings.enable_local_logins &&
this.externalLoginMethods.length === 1
);
}
get externalLoginMethods() {
return findAll();
}
@action @action
loading(transition) { loading(transition) {
this.loadingSlider.transitionStarted(); this.loadingSlider.transitionStarted();
@ -295,8 +283,8 @@ export default class ApplicationRoute extends DiscourseRoute {
: encodeURIComponent(window.location.pathname); : encodeURIComponent(window.location.pathname);
window.location = getURL("/session/sso?return_path=" + returnPath); window.location = getURL("/session/sso?return_path=" + returnPath);
} else { } else {
if (this.isOnlyOneExternalLoginMethod) { if (this.login.isOnlyOneExternalLoginMethod) {
this.login.externalLogin(this.externalLoginMethods[0]); this.login.singleExternalLogin();
} else if (this.siteSettings.full_page_login) { } else if (this.siteSettings.full_page_login) {
this.router.transitionTo("login").then((login) => { this.router.transitionTo("login").then((login) => {
login.controller.set("canSignUp", this.controller.canSignUp); login.controller.set("canSignUp", this.controller.canSignUp);
@ -321,11 +309,9 @@ export default class ApplicationRoute extends DiscourseRoute {
const returnPath = encodeURIComponent(window.location.pathname); const returnPath = encodeURIComponent(window.location.pathname);
window.location = getURL("/session/sso?return_path=" + returnPath); window.location = getURL("/session/sso?return_path=" + returnPath);
} else { } else {
if (this.isOnlyOneExternalLoginMethod) { if (this.login.isOnlyOneExternalLoginMethod) {
// we will automatically redirect to the external auth service // we will automatically redirect to the external auth service
this.login.externalLogin(this.externalLoginMethods[0], { this.login.singleExternalLogin({ signup: true });
signup: true,
});
} else if (this.siteSettings.full_page_login) { } else if (this.siteSettings.full_page_login) {
this.router.transitionTo("signup").then((signup) => { this.router.transitionTo("signup").then((signup) => {
Object.keys(createAccountProps || {}).forEach((key) => { Object.keys(createAccountProps || {}).forEach((key) => {

View File

@ -7,9 +7,12 @@ import DiscourseRoute from "discourse/routes/discourse";
export default class LoginRoute extends DiscourseRoute { export default class LoginRoute extends DiscourseRoute {
@service siteSettings; @service siteSettings;
@service router; @service router;
@service login;
beforeModel() { beforeModel() {
if ( if (this.login.isOnlyOneExternalLoginMethod) {
this.login.singleExternalLogin();
} else if (
!this.siteSettings.login_required && !this.siteSettings.login_required &&
(!this.siteSettings.full_page_login || (!this.siteSettings.full_page_login ||
this.siteSettings.enable_discourse_connect) this.siteSettings.enable_discourse_connect)
@ -35,6 +38,10 @@ export default class LoginRoute extends DiscourseRoute {
controller.set("flashType", ""); controller.set("flashType", "");
controller.set("flash", ""); controller.set("flash", "");
if (this.login.isOnlyOneExternalLoginMethod) {
controller.set("isRedirecting", true);
}
if (this.siteSettings.login_required) { if (this.siteSettings.login_required) {
controller.set("showLogin", false); controller.set("showLogin", false);
} }

View File

@ -4,12 +4,29 @@ import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class SignupRoute extends DiscourseRoute { export default class SignupRoute extends DiscourseRoute {
@service router;
@service siteSettings; @service siteSettings;
@service router;
@service login;
beforeModel() { authComplete = false;
beforeModel(transition) {
this.authComplete = transition.to.queryParams.authComplete || false;
if (this.login.isOnlyOneExternalLoginMethod && !this.authComplete) {
this.login.singleExternalLogin({ signup: true });
} else {
this.showCreateAccount(); this.showCreateAccount();
} }
}
setupController(controller) {
super.setupController(...arguments);
if (this.login.isOnlyOneExternalLoginMethod && !this.authComplete) {
controller.set("isRedirecting", true);
}
}
@action @action
async showCreateAccount() { async showCreateAccount() {

View File

@ -1,9 +1,12 @@
import { action } from "@ember/object"; import { action } from "@ember/object";
import Service from "@ember/service"; import Service, { service } from "@ember/service";
import { disableImplicitInjections } from "discourse/lib/implicit-injections"; import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { findAll } from "discourse/models/login-method";
@disableImplicitInjections @disableImplicitInjections
export default class LoginService extends Service { export default class LoginService extends Service {
@service siteSettings;
@action @action
async externalLogin( async externalLogin(
loginMethod, loginMethod,
@ -16,4 +19,20 @@ export default class LoginService extends Service {
setLoggingIn?.(false); setLoggingIn?.(false);
} }
} }
@action
async singleExternalLogin(opts) {
await this.externalLogin(this.externalLoginMethods[0], opts);
}
get isOnlyOneExternalLoginMethod() {
return (
!this.siteSettings.enable_local_logins &&
this.externalLoginMethods.length === 1
);
}
get externalLoginMethods() {
return findAll();
}
} }

View File

@ -1,3 +1,9 @@
{{hide-application-sidebar}}
{{body-class "login-page"}}
{{#if this.isRedirecting}}
{{loading-spinner}}
{{else}}
{{#if {{#if
(and (and
this.siteSettings.full_page_login this.siteSettings.full_page_login
@ -5,8 +11,6 @@
) )
}} }}
{{hide-application-header-buttons "search" "login" "signup" "menu"}} {{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}}
{{body-class "login-page"}}
<div class="login-fullpage"> <div class="login-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} /> <FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
@ -16,7 +20,9 @@
{{#if this.hasNoLoginOptions}} {{#if this.hasNoLoginOptions}}
<div class={{if this.site.desktopView "login-left-side"}}> <div class={{if this.site.desktopView "login-left-side"}}>
<div class="login-welcome-header no-login-methods-configured"> <div class="login-welcome-header no-login-methods-configured">
<h1 class="login-title">{{i18n "login.no_login_methods.title"}}</h1> <h1 class="login-title">{{i18n
"login.no_login_methods.title"
}}</h1>
<img /> <img />
<p class="login-subheader"> <p class="login-subheader">
{{html-safe {{html-safe
@ -174,3 +180,4 @@
</div> </div>
</section> </section>
{{/if}} {{/if}}
{{/if}}

View File

@ -2,6 +2,10 @@
{{hide-application-header-buttons "search" "login" "signup" "menu"}} {{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}} {{hide-application-sidebar}}
{{body-class "signup-page"}} {{body-class "signup-page"}}
{{#if this.isRedirecting}}
{{loading-spinner}}
{{else}}
<div class="signup-fullpage"> <div class="signup-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} /> <FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
@ -148,7 +152,10 @@
{{else if {{else if
this.siteSettings.show_signup_form_password_instructions this.siteSettings.show_signup_form_password_instructions
}} }}
<span class="more-info" id="password-validation-more-info"> <span
class="more-info"
id="password-validation-more-info"
>
{{this.passwordInstructions}} {{this.passwordInstructions}}
</span> </span>
{{/if}} {{/if}}
@ -285,3 +292,4 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{/if}}

View File

@ -232,6 +232,34 @@ shared_examples "social authentication scenarios" do |signup_page_object, login_
expect(page).to have_css(".header-dropdown-toggle.current-user") expect(page).to have_css(".header-dropdown-toggle.current-user")
end end
end end
context "when there is only one external login method enabled" do
before do
SiteSetting.enable_google_oauth2_logins = true
SiteSetting.enable_local_logins = false
end
after { reset_omniauth_config(:google_oauth2) }
it "automatically redirects to the external auth provider" 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
signup_form.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "automatically redirects to the external auth provider when skipping the signup form" do
SiteSetting.auth_skip_create_confirm = true
mock_google_auth
visit("/login")
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
end end
context "when user exists" do context "when user exists" do