FEATURE: Optionally skip the create account popup for external auth

This commit is contained in:
David Taylor 2020-06-18 18:53:17 +01:00
parent 041d28295f
commit 5284d41a8e
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
6 changed files with 107 additions and 25 deletions

View File

@ -20,6 +20,7 @@ import { userPath } from "discourse/lib/url";
import { findAll } from "discourse/models/login-method"; import { findAll } from "discourse/models/login-method";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import User from "discourse/models/user"; import User from "discourse/models/user";
import { Promise } from "rsvp";
export default Controller.extend( export default Controller.extend(
ModalFunctionality, ModalFunctionality,
@ -41,9 +42,13 @@ export default Controller.extend(
hasAuthOptions: notEmpty("authOptions"), hasAuthOptions: notEmpty("authOptions"),
canCreateLocal: setting("enable_local_logins"), canCreateLocal: setting("enable_local_logins"),
showCreateForm: or("hasAuthOptions", "canCreateLocal"),
requireInviteCode: setting("require_invite_code"), requireInviteCode: setting("require_invite_code"),
@discourseComputed("hasAuthOptions", "canCreateLocal", "skipConfirmation")
showCreateForm(hasAuthOptions, canCreateLocal, skipConfirmation) {
return (hasAuthOptions || canCreateLocal) && !skipConfirmation;
},
resetForm() { resetForm() {
// We wrap the fields in a structure so we can assign a value // We wrap the fields in a structure so we can assign a value
this.setProperties({ this.setProperties({
@ -197,26 +202,44 @@ export default Controller.extend(
@on("init") @on("init")
fetchConfirmationValue() { fetchConfirmationValue() {
return ajax(userPath("hp.json")).then(json => { if (this._challengeDate === undefined && this._hpPromise) {
this._challengeDate = new Date(); // Request already in progress
// remove 30 seconds for jitter, make sure this works for at least return this._hpPromise;
// 30 seconds so we don't have hard loops }
this._challengeExpiry = parseInt(json.expires_in, 10) - 30;
if (this._challengeExpiry < 30) {
this._challengeExpiry = 30;
}
this.setProperties({ this._hpPromise = ajax(userPath("hp.json"))
accountHoneypot: json.value, .then(json => {
accountChallenge: json.challenge this._challengeDate = new Date();
.split("") // remove 30 seconds for jitter, make sure this works for at least
.reverse() // 30 seconds so we don't have hard loops
.join("") this._challengeExpiry = parseInt(json.expires_in, 10) - 30;
}); if (this._challengeExpiry < 30) {
}); this._challengeExpiry = 30;
}
this.setProperties({
accountHoneypot: json.value,
accountChallenge: json.challenge
.split("")
.reverse()
.join("")
});
})
.finally(() => (this._hpPromise = undefined));
return this._hpPromise;
}, },
performAccountCreation() { performAccountCreation() {
if (
!this._challengeDate ||
new Date() - this._challengeDate > 1000 * this._challengeExpiry
) {
return this.fetchConfirmationValue().then(() =>
this.performAccountCreation()
);
}
const attrs = this.getProperties( const attrs = this.getProperties(
"accountName", "accountName",
"accountEmail", "accountEmail",
@ -263,6 +286,7 @@ export default Controller.extend(
.find("input[name=redirect]") .find("input[name=redirect]")
.val(userPath("account-created")); .val(userPath("account-created"));
$hidden_login_form.submit(); $hidden_login_form.submit();
return new Promise(() => {}); // This will never resolve, the page will reload instead
} else { } else {
this.flash( this.flash(
result.message || I18n.t("create_account.failed"), result.message || I18n.t("create_account.failed"),
@ -298,6 +322,14 @@ export default Controller.extend(
); );
}, },
onShow() {
if (this.skipConfirmation) {
this.performAccountCreation().finally(() =>
this.set("skipConfirmation", false)
);
}
},
actions: { actions: {
externalLogin(provider) { externalLogin(provider) {
this.login.send("externalLogin", provider); this.login.send("externalLogin", provider);
@ -332,13 +364,7 @@ export default Controller.extend(
return; return;
} }
if (new Date() - this._challengeDate > 1000 * this._challengeExpiry) { this.performAccountCreation();
this.fetchConfirmationValue().then(() =>
this.performAccountCreation()
);
} else {
this.performAccountCreation();
}
} }
} }
} }

View File

@ -381,12 +381,16 @@ export default Controller.extend(ModalFunctionality, {
return; return;
} }
const skipConfirmation =
options && this.siteSettings.external_auth_skip_create_confirm;
const createAccountController = this.createAccount; const createAccountController = this.createAccount;
createAccountController.setProperties({ createAccountController.setProperties({
accountEmail: options.email, accountEmail: options.email,
accountUsername: options.username, accountUsername: options.username,
accountName: options.name, accountName: options.name,
authOptions: EmberObject.create(options) authOptions: EmberObject.create(options),
skipConfirmation
}); });
showModal("createAccount", { modalClass: "create-account" }); showModal("createAccount", { modalClass: "create-account" });

View File

@ -7,6 +7,10 @@
{{login-buttons externalLogin=(action "externalLogin")}} {{login-buttons externalLogin=(action "externalLogin")}}
{{/unless}} {{/unless}}
{{#if skipConfirmation}}
{{loading-spinner size="large"}}
{{/if}}
{{#if showCreateForm}} {{#if showCreateForm}}
<div class="login-form"> <div class="login-form">
<form> <form>

View File

@ -1624,6 +1624,8 @@ en:
password_unique_characters: "Minimum number of unique characters that a password must have." password_unique_characters: "Minimum number of unique characters that a password must have."
block_common_passwords: "Don't allow passwords that are in the 10,000 most common passwords." block_common_passwords: "Don't allow passwords that are in the 10,000 most common passwords."
external_auth_skip_create_confirm: When signing up via external auth, skip the create account popup. Best used alongside sso_overrides_email, sso_overrides_username and sso_overrides_name.
enable_sso: "Enable single sign on via an external site (WARNING: USERS' EMAIL ADDRESSES *MUST* BE VALIDATED BY THE EXTERNAL SITE!)" enable_sso: "Enable single sign on via an external site (WARNING: USERS' EMAIL ADDRESSES *MUST* BE VALIDATED BY THE EXTERNAL SITE!)"
verbose_sso_logging: "Log verbose SSO related diagnostics to <a href='%{base_path}/logs' target='_blank'>/logs</a>" verbose_sso_logging: "Log verbose SSO related diagnostics to <a href='%{base_path}/logs' target='_blank'>/logs</a>"
enable_sso_provider: "Implement Discourse SSO provider protocol at the /session/sso_provider endpoint, requires sso_provider_secrets to be set" enable_sso_provider: "Implement Discourse SSO provider protocol at the /session/sso_provider endpoint, requires sso_provider_secrets to be set"

View File

@ -379,6 +379,9 @@ login:
discord_trusted_guilds: discord_trusted_guilds:
default: "" default: ""
type: list type: list
external_auth_skip_create_confirm:
default: false
client: true
enable_sso: enable_sso:
client: true client: true
default: false default: false

View File

@ -0,0 +1,43 @@
import { acceptance } from "helpers/qunit-helpers";
acceptance("Create Account - external auth", {
beforeEach() {
const node = document.createElement("meta");
node.dataset.authenticationData = JSON.stringify({
auth_provider: "test",
email: "blah@example.com",
can_edit_username: true,
can_edit_name: true
});
node.id = "data-authentication";
document.querySelector("head").appendChild(node);
},
afterEach() {
document
.querySelector("head")
.removeChild(document.getElementById("data-authentication"));
}
});
QUnit.test("when skip is disabled (default)", async assert => {
await visit("/");
assert.ok(
exists("#discourse-modal div.create-account"),
"it shows the registration modal"
);
assert.ok(exists("#new-account-username"), "it shows the fields");
});
QUnit.test("when skip is enabled", async assert => {
Discourse.SiteSettings.external_auth_skip_create_confirm = true;
await visit("/");
assert.ok(
exists("#discourse-modal div.create-account"),
"it shows the registration modal"
);
assert.not(exists("#new-account-username"), "it does not show the fields");
});