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) {
router.transitionTo("signup").then((signup) => {
router
.transitionTo("signup", {
queryParams: { authComplete: true },
})
.then((signup) => {
const signupController =
signup.controller || owner.lookup("controller:signup");
Object.keys(createAccountProps || {}).forEach((key) => {

View File

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

View File

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

View File

@ -4,12 +4,29 @@ import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
export default class SignupRoute extends DiscourseRoute {
@service router;
@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();
}
}
setupController(controller) {
super.setupController(...arguments);
if (this.login.isOnlyOneExternalLoginMethod && !this.authComplete) {
controller.set("isRedirecting", true);
}
}
@action
async showCreateAccount() {

View File

@ -1,9 +1,12 @@
import { action } from "@ember/object";
import Service from "@ember/service";
import Service, { service } from "@ember/service";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { findAll } from "discourse/models/login-method";
@disableImplicitInjections
export default class LoginService extends Service {
@service siteSettings;
@action
async externalLogin(
loginMethod,
@ -16,4 +19,20 @@ export default class LoginService extends Service {
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
(and
this.siteSettings.full_page_login
@ -5,8 +11,6 @@
)
}}
{{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}}
{{body-class "login-page"}}
<div class="login-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
@ -16,7 +20,9 @@
{{#if this.hasNoLoginOptions}}
<div class={{if this.site.desktopView "login-left-side"}}>
<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 />
<p class="login-subheader">
{{html-safe
@ -174,3 +180,4 @@
</div>
</section>
{{/if}}
{{/if}}

View File

@ -2,6 +2,10 @@
{{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}}
{{body-class "signup-page"}}
{{#if this.isRedirecting}}
{{loading-spinner}}
{{else}}
<div class="signup-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
@ -148,7 +152,10 @@
{{else if
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}}
</span>
{{/if}}
@ -285,3 +292,4 @@
{{/if}}
</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")
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
context "when user exists" do