FEATURE: disposable invite tokens
This commit is contained in:
parent
2a40c04480
commit
575b5e3d13
|
@ -41,6 +41,38 @@ class InvitesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def create_disposable_invite
|
||||
guardian.ensure_can_create_disposable_invite!(current_user)
|
||||
params.permit(:username, :email, :quantity, :group_names)
|
||||
|
||||
username_or_email = params[:username] ? fetch_username : fetch_email
|
||||
user = User.find_by_username_or_email(username_or_email)
|
||||
|
||||
# generate invite tokens
|
||||
invite_tokens = Invite.generate_disposable_tokens(user, params[:quantity], params[:group_names])
|
||||
|
||||
render_json_dump(invite_tokens)
|
||||
end
|
||||
|
||||
def redeem_disposable_invite
|
||||
params.require(:email)
|
||||
params.permit(:username, :name)
|
||||
|
||||
invite = Invite.find_by(invite_key: params[:token])
|
||||
|
||||
if invite.present?
|
||||
user = Invite.redeem_from_token(params[:token], params[:email], params[:username], params[:name])
|
||||
if user.present?
|
||||
log_on_user(user)
|
||||
|
||||
# Send a welcome message if required
|
||||
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to "/"
|
||||
end
|
||||
|
||||
def destroy
|
||||
params.require(:email)
|
||||
|
||||
|
@ -95,4 +127,14 @@ class InvitesController < ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
def fetch_username
|
||||
params.require(:username)
|
||||
params[:username]
|
||||
end
|
||||
|
||||
def fetch_email
|
||||
params.require(:email)
|
||||
params[:email]
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -9,15 +9,14 @@ class Invite < ActiveRecord::Base
|
|||
has_many :groups, through: :invited_groups
|
||||
has_many :topic_invites
|
||||
has_many :topics, through: :topic_invites, source: :topic
|
||||
validates_presence_of :email
|
||||
validates_presence_of :invited_by_id
|
||||
|
||||
before_create do
|
||||
self.invite_key ||= SecureRandom.hex
|
||||
end
|
||||
|
||||
before_save do
|
||||
self.email = Email.downcase(email)
|
||||
before_validation do
|
||||
self.email = Email.downcase(email) unless email.nil?
|
||||
end
|
||||
|
||||
validate :user_doesnt_already_exist
|
||||
|
@ -96,6 +95,37 @@ class Invite < ActiveRecord::Base
|
|||
invite
|
||||
end
|
||||
|
||||
|
||||
# generate invite tokens without email
|
||||
def self.generate_disposable_tokens(invited_by, quantity=nil, group_names=nil)
|
||||
invite_tokens = []
|
||||
quantity ||= 1
|
||||
group_ids = get_group_ids(group_names)
|
||||
|
||||
quantity.to_i.times do
|
||||
invite = Invite.create!(invited_by: invited_by)
|
||||
group_ids = group_ids - invite.invited_groups.pluck(:group_id)
|
||||
group_ids.each do |group_id|
|
||||
invite.invited_groups.create!(group_id: group_id)
|
||||
end
|
||||
invite_tokens.push(invite.invite_key)
|
||||
end
|
||||
|
||||
invite_tokens
|
||||
end
|
||||
|
||||
def self.get_group_ids(group_names)
|
||||
group_ids = []
|
||||
if group_names
|
||||
group_names = group_names.split(',')
|
||||
group_names.each { |group_name|
|
||||
group_detail = Group.find_by_name(group_name)
|
||||
group_ids.push(group_detail.id) if group_detail
|
||||
}
|
||||
end
|
||||
group_ids
|
||||
end
|
||||
|
||||
def self.find_all_invites_from(inviter)
|
||||
Invite.where(invited_by_id: inviter.id)
|
||||
.includes(:user => :user_stat)
|
||||
|
@ -138,6 +168,15 @@ class Invite < ActiveRecord::Base
|
|||
invite
|
||||
end
|
||||
|
||||
def self.redeem_from_token(token, email, username=nil, name=nil)
|
||||
invite = Invite.find_by(invite_key: token)
|
||||
if invite
|
||||
invite.update_column(:email, email)
|
||||
user = InviteRedeemer.new(invite, username, name).redeem
|
||||
end
|
||||
user
|
||||
end
|
||||
|
||||
def self.base_directory
|
||||
File.join(Rails.root, "public", "csv", RailsMultisite::ConnectionManagement.current_db)
|
||||
end
|
||||
|
@ -153,7 +192,7 @@ end
|
|||
#
|
||||
# id :integer not null, primary key
|
||||
# invite_key :string(32) not null
|
||||
# email :string(255) not null
|
||||
# email :string(255)
|
||||
# invited_by_id :integer not null
|
||||
# user_id :integer
|
||||
# redeemed_at :datetime
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
InviteRedeemer = Struct.new(:invite) do
|
||||
InviteRedeemer = Struct.new(:invite, :username, :name) do
|
||||
|
||||
def redeem
|
||||
Invite.transaction do
|
||||
|
@ -18,19 +18,23 @@ InviteRedeemer = Struct.new(:invite) do
|
|||
end
|
||||
|
||||
# extracted from User cause it is very specific to invites
|
||||
def self.create_user_from_invite(invite)
|
||||
|
||||
def self.create_user_from_invite(invite, username, name)
|
||||
user_exists = User.find_by_email(invite.email)
|
||||
return user if user_exists
|
||||
|
||||
username = UserNameSuggester.suggest(invite.email)
|
||||
if username && User.username_available?(username)
|
||||
available_username = username
|
||||
else
|
||||
available_username = UserNameSuggester.suggest(invite.email)
|
||||
end
|
||||
available_name = name || available_username
|
||||
|
||||
DiscourseHub.username_operation do
|
||||
match, available, suggestion = DiscourseHub.username_match?(username, invite.email)
|
||||
username = suggestion unless match || available
|
||||
match, available, suggestion = DiscourseHub.username_match?(available_username, invite.email)
|
||||
available_username = suggestion unless match || available
|
||||
end
|
||||
|
||||
user = User.new(email: invite.email, username: username, name: username, active: true, trust_level: SiteSetting.default_invitee_trust_level)
|
||||
user = User.new(email: invite.email, username: available_username, name: available_name, active: true, trust_level: SiteSetting.default_invitee_trust_level)
|
||||
user.save!
|
||||
|
||||
DiscourseHub.username_operation { DiscourseHub.register_username(username, invite.email) }
|
||||
|
@ -65,7 +69,7 @@ InviteRedeemer = Struct.new(:invite) do
|
|||
|
||||
def get_invited_user
|
||||
result = get_existing_user
|
||||
result ||= InviteRedeemer.create_user_from_invite(invite)
|
||||
result ||= InviteRedeemer.create_user_from_invite(invite, username, name)
|
||||
result.send_welcome_message = false
|
||||
result
|
||||
end
|
||||
|
|
|
@ -375,6 +375,8 @@ Discourse::Application.routes.draw do
|
|||
post "upload" => "invites#upload_csv_chunk"
|
||||
end
|
||||
end
|
||||
post "invites/disposable" => "invites#create_disposable_invite"
|
||||
get "invites/redeem/:token" => "invites#redeem_disposable_invite"
|
||||
delete "invites" => "invites#destroy"
|
||||
|
||||
get "onebox" => "onebox#show"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class RemoveNotNullFromEmail < ActiveRecord::Migration
|
||||
def up
|
||||
execute "ALTER TABLE invites ALTER COLUMN email DROP NOT NULL"
|
||||
end
|
||||
|
||||
def down
|
||||
execute "ALTER TABLE invites ALTER COLUMN email SET NOT NULL"
|
||||
end
|
||||
end
|
|
@ -210,6 +210,10 @@ class Guardian
|
|||
user.admin?
|
||||
end
|
||||
|
||||
def can_create_disposable_invite?(user)
|
||||
user.admin?
|
||||
end
|
||||
|
||||
def can_see_private_messages?(user_id)
|
||||
is_admin? || (authenticated? && @user.id == user_id)
|
||||
end
|
||||
|
|
|
@ -150,6 +150,60 @@ describe InvitesController do
|
|||
|
||||
end
|
||||
|
||||
context '.create_disposable_invite' do
|
||||
it 'requires you to be logged in' do
|
||||
lambda {
|
||||
post :create, email: 'jake@adventuretime.ooo'
|
||||
}.should raise_error(Discourse::NotLoggedIn)
|
||||
end
|
||||
|
||||
context 'while logged in as normal user' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "does not create disposable invitation" do
|
||||
log_in
|
||||
post :create_disposable_invite, email: user.email
|
||||
response.should_not be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'while logged in as admin' do
|
||||
before do
|
||||
log_in(:admin)
|
||||
end
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "creates disposable invitation" do
|
||||
post :create_disposable_invite, email: user.email
|
||||
response.should be_success
|
||||
Invite.where(invited_by_id: user.id).count.should == 1
|
||||
end
|
||||
|
||||
it "creates multiple disposable invitations" do
|
||||
post :create_disposable_invite, email: user.email, quantity: 10
|
||||
response.should be_success
|
||||
Invite.where(invited_by_id: user.id).count.should == 10
|
||||
end
|
||||
|
||||
it "allows group invite" do
|
||||
group = Fabricate(:group)
|
||||
post :create_disposable_invite, email: user.email, group_names: group.name
|
||||
response.should be_success
|
||||
Invite.find_by(invited_by_id: user.id).invited_groups.count.should == 1
|
||||
end
|
||||
|
||||
it "allows multiple group invite" do
|
||||
group_1 = Fabricate(:group, name: "security")
|
||||
group_2 = Fabricate(:group, name: "support")
|
||||
post :create_disposable_invite, email: user.email, group_names: "security,support"
|
||||
response.should be_success
|
||||
Invite.find_by(invited_by_id: user.id).invited_groups.count.should == 2
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '.check_csv_chunk' do
|
||||
it 'requires you to be logged in' do
|
||||
lambda {
|
||||
|
|
|
@ -3,10 +3,10 @@ require 'spec_helper'
|
|||
describe InviteRedeemer do
|
||||
|
||||
describe '#create_for_email' do
|
||||
let(:user) { InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com')) }
|
||||
let(:user) { InviteRedeemer.create_user_from_invite(Fabricate(:invite, email: 'walter.white@email.com'), 'walter', 'Walter White') }
|
||||
it "should be created correctly" do
|
||||
user.username.should == 'walter_white'
|
||||
user.name.should == 'walter_white'
|
||||
user.username.should == 'walter'
|
||||
user.name.should == 'Walter White'
|
||||
user.should be_active
|
||||
user.email.should == 'walter.white@email.com'
|
||||
end
|
||||
|
|
|
@ -2,7 +2,6 @@ require 'spec_helper'
|
|||
|
||||
describe Invite do
|
||||
|
||||
it { should validate_presence_of :email }
|
||||
it { should validate_presence_of :invited_by_id }
|
||||
|
||||
let(:iceking) { 'iceking@adventuretime.ooo' }
|
||||
|
@ -369,4 +368,23 @@ describe Invite do
|
|||
|
||||
end
|
||||
|
||||
describe '.redeem_from_token' do
|
||||
let(:inviter) { Fabricate(:user) }
|
||||
let(:invite) { Fabricate(:invite, invited_by: inviter, email: 'test@example.com', user_id: nil) }
|
||||
let(:user) { Fabricate(:user, email: invite.email) }
|
||||
|
||||
it 'redeems the invite from token' do
|
||||
result = Invite.redeem_from_token(invite.invite_key, user.email)
|
||||
invite.reload
|
||||
invite.should be_redeemed
|
||||
end
|
||||
|
||||
it 'does not redeem the invite if token does not match' do
|
||||
result = Invite.redeem_from_token("bae0071f995bb4b6f756e80b383778b5", user.email)
|
||||
invite.reload
|
||||
invite.should_not be_redeemed
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue