FEATURE: optional global invite_code for account registration
On some sites when bootstrapping communities it is helpful to bootstrap with a "light weight" invite code. Use the site setting `invite_code` to set a global invite code. In this case the administrator can share the code with a community which is very easy to remember and then anyone who has that code can easily register accounts. People without the invite code are not allowed account registration. Global invite codes are less secure than indevidual codes, in that they tend to leak in the community however in some cases when starting a brand new community the security guarantees of invites are not needed.
This commit is contained in:
parent
a14313e9d0
commit
a1d660d951
|
@ -40,6 +40,7 @@ export default Controller.extend(
|
|||
hasAuthOptions: notEmpty("authOptions"),
|
||||
canCreateLocal: setting("enable_local_logins"),
|
||||
showCreateForm: or("hasAuthOptions", "canCreateLocal"),
|
||||
requireInviteCode: setting("require_invite_code"),
|
||||
|
||||
resetForm() {
|
||||
// We wrap the fields in a structure so we can assign a value
|
||||
|
@ -66,7 +67,8 @@ export default Controller.extend(
|
|||
"usernameValidation.failed",
|
||||
"passwordValidation.failed",
|
||||
"userFieldsValidation.failed",
|
||||
"formSubmitted"
|
||||
"formSubmitted",
|
||||
"inviteCode"
|
||||
)
|
||||
submitDisabled() {
|
||||
if (this.formSubmitted) return true;
|
||||
|
@ -78,6 +80,8 @@ export default Controller.extend(
|
|||
return true;
|
||||
if (this.get("userFieldsValidation.failed")) return true;
|
||||
|
||||
if (this.requireInviteCode && !this.inviteCode) return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
@ -225,7 +229,8 @@ export default Controller.extend(
|
|||
"accountEmail",
|
||||
"accountPassword",
|
||||
"accountUsername",
|
||||
"accountChallenge"
|
||||
"accountChallenge",
|
||||
"inviteCode"
|
||||
);
|
||||
|
||||
attrs["accountPasswordConfirm"] = this.accountHoneypot;
|
||||
|
|
|
@ -909,17 +909,23 @@ User.reopenClass(Singleton, {
|
|||
},
|
||||
|
||||
createAccount(attrs) {
|
||||
let data = {
|
||||
name: attrs.accountName,
|
||||
email: attrs.accountEmail,
|
||||
password: attrs.accountPassword,
|
||||
username: attrs.accountUsername,
|
||||
password_confirmation: attrs.accountPasswordConfirm,
|
||||
challenge: attrs.accountChallenge,
|
||||
user_fields: attrs.userFields,
|
||||
timezone: moment.tz.guess()
|
||||
};
|
||||
|
||||
if (attrs.inviteCode) {
|
||||
data.invite_code = attrs.inviteCode;
|
||||
}
|
||||
|
||||
return ajax(userPath(), {
|
||||
data: {
|
||||
name: attrs.accountName,
|
||||
email: attrs.accountEmail,
|
||||
password: attrs.accountPassword,
|
||||
username: attrs.accountUsername,
|
||||
password_confirmation: attrs.accountPasswordConfirm,
|
||||
challenge: attrs.accountChallenge,
|
||||
user_fields: attrs.userFields,
|
||||
timezone: moment.tz.guess()
|
||||
},
|
||||
data,
|
||||
type: "POST"
|
||||
});
|
||||
}
|
||||
|
|
|
@ -89,6 +89,17 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
{{#if requireInviteCode }}
|
||||
<tr class="invite-code">
|
||||
<td><label for='invite-code'>{{i18n 'user.invite_code.title'}}</label></td>
|
||||
<td>
|
||||
{{input value=inviteCode id="inviteCode"}}
|
||||
</td>
|
||||
|
||||
<td><label>{{i18n 'user.invite_code.instructions'}}</label></td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet name="create-account-after-password"
|
||||
noTags=true
|
||||
args=(hash accountName=accountName
|
||||
|
|
|
@ -410,6 +410,7 @@ class UsersController < ApplicationController
|
|||
def create
|
||||
params.require(:email)
|
||||
params.require(:username)
|
||||
params.require(:invite_code) if SiteSetting.require_invite_code
|
||||
params.permit(:user_fields)
|
||||
|
||||
unless SiteSetting.allow_new_registrations
|
||||
|
@ -424,6 +425,10 @@ class UsersController < ApplicationController
|
|||
return fail_with("login.email_too_long")
|
||||
end
|
||||
|
||||
if SiteSetting.require_invite_code && SiteSetting.invite_code != params[:invite_code]
|
||||
return fail_with("login.wrong_invite_code")
|
||||
end
|
||||
|
||||
if clashing_with_existing_route?(params[:username]) || User.reserved_username?(params[:username])
|
||||
return fail_with("login.reserved_username")
|
||||
end
|
||||
|
|
|
@ -173,6 +173,11 @@ class SiteSetting < ActiveRecord::Base
|
|||
SiteSetting::Upload
|
||||
end
|
||||
|
||||
def self.require_invite_code
|
||||
invite_code.present?
|
||||
end
|
||||
client_settings << :require_invite_code
|
||||
|
||||
%i{
|
||||
site_logo_url
|
||||
site_logo_small_url
|
||||
|
|
|
@ -1132,6 +1132,10 @@ en:
|
|||
password_confirmation:
|
||||
title: "Password Again"
|
||||
|
||||
invite_code:
|
||||
title: "Invite Code"
|
||||
instructions: "Account registration requires an invite code"
|
||||
|
||||
auth_tokens:
|
||||
title: "Recently Used Devices"
|
||||
ip: "IP"
|
||||
|
|
|
@ -1530,6 +1530,7 @@ en:
|
|||
markdown_typographer_quotation_marks: "List of double and single quotes replacement pairs"
|
||||
post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)."
|
||||
must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site."
|
||||
invite_code: "User must type this code to be allowed account registration, ignored when empty"
|
||||
approve_suspect_users: "Add suspicious users to the review queue. Suspicious users have entered a bio/website but have no reading activity."
|
||||
pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications."
|
||||
maximum_session_age: "User will remain logged in for n hours since last visit"
|
||||
|
@ -2382,6 +2383,7 @@ en:
|
|||
new_registrations_disabled: "New account registrations are not allowed at this time."
|
||||
password_too_long: "Passwords are limited to 200 characters."
|
||||
email_too_long: "The email you provided is too long. Mailbox names must be no more than 254 characters, and domain names must be no more than 253 characters."
|
||||
wrong_invite_code: "The invite code you entered was incorrect."
|
||||
reserved_username: "That username is not allowed."
|
||||
missing_user_field: "You have not completed all the user fields"
|
||||
auth_complete: "Authentication is complete."
|
||||
|
|
|
@ -336,6 +336,7 @@ login:
|
|||
must_approve_users:
|
||||
client: true
|
||||
default: false
|
||||
invite_code: ""
|
||||
enable_local_logins:
|
||||
client: true
|
||||
default: true
|
||||
|
|
|
@ -593,8 +593,8 @@ describe UsersController do
|
|||
email: @user.email }
|
||||
end
|
||||
|
||||
def post_user
|
||||
post "/u.json", params: post_user_params
|
||||
def post_user(extra_params = {})
|
||||
post "/u.json", params: post_user_params.merge(extra_params)
|
||||
end
|
||||
|
||||
context 'when email params is missing' do
|
||||
|
@ -616,17 +616,27 @@ describe UsersController do
|
|||
expect(User.find_by(username: @user.username).locale).to eq('fr')
|
||||
end
|
||||
|
||||
it 'requires invite code when specified' do
|
||||
expect(SiteSetting.require_invite_code).to eq(false)
|
||||
SiteSetting.invite_code = "abc"
|
||||
expect(SiteSetting.require_invite_code).to eq(true)
|
||||
|
||||
post_user(invite_code: "abcd")
|
||||
expect(response.status).to eq(200)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json["success"]).to eq(false)
|
||||
|
||||
post_user(invite_code: "abc")
|
||||
expect(response.status).to eq(200)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json["success"]).to eq(true)
|
||||
|
||||
end
|
||||
|
||||
context "when timezone is provided as a guess on signup" do
|
||||
let(:post_user_params) do
|
||||
{ name: @user.name,
|
||||
username: @user.username,
|
||||
password: "strongpassword",
|
||||
email: @user.email,
|
||||
timezone: "Australia/Brisbane" }
|
||||
end
|
||||
|
||||
it "sets the timezone" do
|
||||
post_user
|
||||
post_user(timezone: "Australia/Brisbane")
|
||||
expect(response.status).to eq(200)
|
||||
expect(User.find_by(username: @user.username).user_option.timezone).to eq("Australia/Brisbane")
|
||||
end
|
||||
|
@ -1440,7 +1450,7 @@ describe UsersController do
|
|||
inviter = Fabricate(:user, trust_level: 2)
|
||||
sign_in(inviter)
|
||||
invitee = Fabricate(:user)
|
||||
invite = Fabricate(:invite, invited_by: inviter, user: invitee)
|
||||
_invite = Fabricate(:invite, invited_by: inviter, user: invitee)
|
||||
get "/u/#{user.username}/invited_count.json"
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
|
|
Loading…
Reference in New Issue