Compare commits
25 Commits
b8e75c7c4a
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8903ae2624 | |||
|
26e9073674
|
|||
| 73a89c2601 | |||
|
7d4dee17b7
|
|||
| 602ca6ee94 | |||
|
69fc1ca57e
|
|||
|
ee72a32c7e
|
|||
|
8a0d89ef60
|
|||
|
54af949c7d
|
|||
|
6dac732a7f
|
|||
|
e8c1a6066a
|
|||
| 44fadb12d6 | |||
|
533452469b
|
|||
| efe168b205 | |||
|
5b6d6bbd00
|
|||
|
458b585cdb
|
|||
|
f651289410
|
|||
|
7ca91cf882
|
|||
|
022094ce51
|
|||
| 2a2b0a90dc | |||
| e44535daee | |||
| c8ccb418b2 | |||
| a792d66c90 | |||
| f7e48ad3a6 | |||
|
8a7d809b92
|
@@ -12,6 +12,9 @@ steps:
|
|||||||
restore: true
|
restore: true
|
||||||
mount:
|
mount:
|
||||||
- vendor
|
- vendor
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
- name: rspec
|
- name: rspec
|
||||||
image: guildeducation/rails:2.7.1-12.19.0
|
image: guildeducation/rails:2.7.1-12.19.0
|
||||||
commands:
|
commands:
|
||||||
@@ -30,6 +33,9 @@ steps:
|
|||||||
rebuild: true
|
rebuild: true
|
||||||
mount:
|
mount:
|
||||||
- vendor
|
- vendor
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: cache
|
- name: cache
|
||||||
|
|||||||
@@ -1,8 +1 @@
|
|||||||
LDAP_HOST=192.168.33.10
|
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||||
LDAP_PORT=389
|
|
||||||
#
|
|
||||||
# Production LDAP server:
|
|
||||||
#
|
|
||||||
# LDAP_HOST=ldap.kosmos.org
|
|
||||||
# LDAP_PORT=636
|
|
||||||
# LDAP_USE_TLS=true
|
|
||||||
|
|||||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
|||||||
|
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
||||||
5
Gemfile
5
Gemfile
@@ -21,13 +21,15 @@ gem 'jbuilder', '~> 2.7'
|
|||||||
# Reduces boot times through caching; required in config/boot.rb
|
# Reduces boot times through caching; required in config/boot.rb
|
||||||
gem 'bootsnap', '>= 1.4.2', require: false
|
gem 'bootsnap', '>= 1.4.2', require: false
|
||||||
|
|
||||||
gem 'dotenv-rails', groups: [:development, :test]
|
gem 'dotenv-rails'
|
||||||
|
|
||||||
gem 'warden'
|
gem 'warden'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'devise_ldap_authenticatable'
|
gem 'devise_ldap_authenticatable'
|
||||||
gem 'net-ldap'
|
gem 'net-ldap'
|
||||||
|
|
||||||
|
gem 'faraday'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# Use sqlite3 as the database for Active Record
|
# Use sqlite3 as the database for Active Record
|
||||||
gem 'sqlite3', '~> 1.4'
|
gem 'sqlite3', '~> 1.4'
|
||||||
@@ -51,6 +53,7 @@ group :test do
|
|||||||
gem 'factory_bot_rails'
|
gem 'factory_bot_rails'
|
||||||
gem 'capybara'
|
gem 'capybara'
|
||||||
gem 'database_cleaner'
|
gem 'database_cleaner'
|
||||||
|
gem 'webmock'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
|
|||||||
13
Gemfile.lock
13
Gemfile.lock
@@ -73,6 +73,8 @@ GEM
|
|||||||
regexp_parser (~> 1.5)
|
regexp_parser (~> 1.5)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
concurrent-ruby (1.1.7)
|
concurrent-ruby (1.1.7)
|
||||||
|
crack (0.4.3)
|
||||||
|
safe_yaml (~> 1.0.0)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
database_cleaner (1.8.5)
|
database_cleaner (1.8.5)
|
||||||
devise (4.7.3)
|
devise (4.7.3)
|
||||||
@@ -95,9 +97,12 @@ GEM
|
|||||||
factory_bot_rails (6.1.0)
|
factory_bot_rails (6.1.0)
|
||||||
factory_bot (~> 6.1.0)
|
factory_bot (~> 6.1.0)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
|
faraday (0.17.0)
|
||||||
|
multipart-post (>= 1.2, < 3)
|
||||||
ffi (1.13.1)
|
ffi (1.13.1)
|
||||||
globalid (0.4.2)
|
globalid (0.4.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
|
hashdiff (0.4.0)
|
||||||
i18n (1.8.5)
|
i18n (1.8.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jbuilder (2.10.1)
|
jbuilder (2.10.1)
|
||||||
@@ -126,6 +131,7 @@ GEM
|
|||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.14.2)
|
minitest (5.14.2)
|
||||||
msgpack (1.3.3)
|
msgpack (1.3.3)
|
||||||
|
multipart-post (2.1.1)
|
||||||
net-ldap (0.16.3)
|
net-ldap (0.16.3)
|
||||||
nio4r (2.5.4)
|
nio4r (2.5.4)
|
||||||
nokogiri (1.10.10)
|
nokogiri (1.10.10)
|
||||||
@@ -191,6 +197,7 @@ GEM
|
|||||||
rspec-mocks (~> 3.9)
|
rspec-mocks (~> 3.9)
|
||||||
rspec-support (~> 3.9)
|
rspec-support (~> 3.9)
|
||||||
rspec-support (3.10.0)
|
rspec-support (3.10.0)
|
||||||
|
safe_yaml (1.0.5)
|
||||||
sass-rails (6.0.0)
|
sass-rails (6.0.0)
|
||||||
sassc-rails (~> 2.1, >= 2.1.1)
|
sassc-rails (~> 2.1, >= 2.1.1)
|
||||||
sassc (2.4.0)
|
sassc (2.4.0)
|
||||||
@@ -228,6 +235,10 @@ GEM
|
|||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
webmock (3.6.0)
|
||||||
|
addressable (>= 2.3.6)
|
||||||
|
crack (>= 0.3.2)
|
||||||
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webpacker (4.3.0)
|
webpacker (4.3.0)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
@@ -251,6 +262,7 @@ DEPENDENCIES
|
|||||||
devise_ldap_authenticatable
|
devise_ldap_authenticatable
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
|
faraday
|
||||||
jbuilder (~> 2.7)
|
jbuilder (~> 2.7)
|
||||||
letter_opener
|
letter_opener
|
||||||
letter_opener_web
|
letter_opener_web
|
||||||
@@ -268,6 +280,7 @@ DEPENDENCIES
|
|||||||
tzinfo-data
|
tzinfo-data
|
||||||
warden
|
warden
|
||||||
web-console (>= 3.3.0)
|
web-console (>= 3.3.0)
|
||||||
|
webmock
|
||||||
webpacker (~> 4.0)
|
webpacker (~> 4.0)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ credentials, invites, donations, etc..
|
|||||||
* [x] Reset account password when logged in, via reset email
|
* [x] Reset account password when logged in, via reset email
|
||||||
* [x] Log in with admin permissions
|
* [x] Log in with admin permissions
|
||||||
* [x] View LDAP users as admin
|
* [x] View LDAP users as admin
|
||||||
|
* [x] Sign up for a new account via invitation
|
||||||
* [ ] List my donations
|
* [ ] List my donations
|
||||||
* [ ] Invite new users from your account
|
* [ ] Invite new users from your account
|
||||||
* [ ] Sign up for a new account via invitation
|
|
||||||
* [ ] Sign up for a new account by donating upfront
|
* [ ] Sign up for a new account by donating upfront
|
||||||
* [ ] Sign up for a new account via proving contributions (via cryptographic signature)
|
* [ ] Sign up for a new account via proving contributions (via cryptographic signature)
|
||||||
* [ ] ...
|
* [ ] ...
|
||||||
|
|||||||
@@ -25,4 +25,11 @@ form {
|
|||||||
.actions {
|
.actions {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accept-terms {
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.5em;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ class InvitationsController < ApplicationController
|
|||||||
|
|
||||||
# GET /invitations
|
# GET /invitations
|
||||||
def index
|
def index
|
||||||
@invitations = current_user.invitations
|
@invitations_unused = current_user.invitations.unused
|
||||||
|
@invitations_used = current_user.invitations.used
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /invitations/a-random-invitation-token
|
# GET /invitations/a-random-invitation-token
|
||||||
|
|||||||
@@ -94,17 +94,15 @@ class SignupController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def complete_signup
|
def complete_signup
|
||||||
@user.save!
|
|
||||||
session[:new_user] = nil
|
session[:new_user] = nil
|
||||||
session[:validation_error] = nil
|
session[:validation_error] = nil
|
||||||
|
|
||||||
CreateAccount.call(
|
CreateAccount.call(
|
||||||
username: @user.cn,
|
username: @user.cn,
|
||||||
|
domain: "kosmos.org",
|
||||||
email: @user.email,
|
email: @user.email,
|
||||||
password: @user.password
|
password: @user.password,
|
||||||
|
invitation: @invitation
|
||||||
)
|
)
|
||||||
|
|
||||||
@invitation.update_attributes invited_user_id: @user.id,
|
|
||||||
used_at: DateTime.now
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
15
app/models/concerns/email_validatable.rb
Normal file
15
app/models/concerns/email_validatable.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require 'mail'
|
||||||
|
|
||||||
|
module EmailValidatable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class EmailValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
begin
|
||||||
|
a = Mail::Address.new(value)
|
||||||
|
rescue Mail::Field::ParseError
|
||||||
|
record.errors[attribute] << (options[:message] || "is not a valid address")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,10 +2,16 @@ class Invitation < ApplicationRecord
|
|||||||
# Relations
|
# Relations
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
# Validations
|
||||||
validates_presence_of :user
|
validates_presence_of :user
|
||||||
|
|
||||||
|
# Hooks
|
||||||
before_create :generate_token
|
before_create :generate_token
|
||||||
|
|
||||||
|
# Scopes
|
||||||
|
scope :unused, -> { where(used_at: nil) }
|
||||||
|
scope :used, -> { where.not(used_at: nil) }
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def generate_token
|
def generate_token
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
|
include EmailValidatable
|
||||||
|
|
||||||
# Relations
|
# Relations
|
||||||
has_many :invitations, dependent: :destroy
|
has_many :invitations, dependent: :destroy
|
||||||
|
|
||||||
validates_uniqueness_of :cn
|
validates_uniqueness_of :cn
|
||||||
validates_uniqueness_of :email
|
|
||||||
validates_length_of :cn, :minimum => 3
|
validates_length_of :cn, :minimum => 3
|
||||||
|
validates_uniqueness_of :email
|
||||||
|
validates :email, email: true
|
||||||
|
|
||||||
# Include default devise modules. Others available are:
|
# Include default devise modules. Others available are:
|
||||||
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
class ApplicationService
|
class ApplicationService
|
||||||
|
# This enables executing a service's `#call` method directly via
|
||||||
|
# `MyService.call(args)`, without creating a class instance it first.
|
||||||
def self.call(*args, &block)
|
def self.call(*args, &block)
|
||||||
new(*args, &block).call
|
new(*args, &block).call
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,38 @@
|
|||||||
class CreateAccount < ApplicationService
|
class CreateAccount < ApplicationService
|
||||||
def initialize(args)
|
def initialize(args)
|
||||||
@username = args[:username]
|
@username = args[:username]
|
||||||
@email = args[:email]
|
@domain = args[:ou] || "kosmos.org"
|
||||||
@password = args[:password]
|
@email = args[:email]
|
||||||
|
@password = args[:password]
|
||||||
|
@invitation = args[:invitation]
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
|
user = create_user_in_database
|
||||||
add_ldap_document
|
add_ldap_document
|
||||||
|
|
||||||
|
if @invitation.present?
|
||||||
|
update_invitation(user.id)
|
||||||
|
exchange_xmpp_contacts
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_user_in_database
|
||||||
|
User.create!(
|
||||||
|
cn: @username,
|
||||||
|
ou: @domain,
|
||||||
|
email: @email,
|
||||||
|
password: @password,
|
||||||
|
password_confirmation: @password
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_invitation(user_id)
|
||||||
|
@invitation.update! invited_user_id: user_id, used_at: DateTime.now
|
||||||
|
end
|
||||||
|
|
||||||
def add_ldap_document
|
def add_ldap_document
|
||||||
dn = "cn=#{@username},ou=kosmos.org,cn=users,dc=kosmos,dc=org"
|
dn = "cn=#{@username},ou=kosmos.org,cn=users,dc=kosmos,dc=org"
|
||||||
attr = {
|
attr = {
|
||||||
@@ -39,4 +61,23 @@ class CreateAccount < ApplicationService
|
|||||||
def ldap_config
|
def ldap_config
|
||||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exchange_xmpp_contacts
|
||||||
|
#TODO enable in development when we have easy setup of ejabberd etc.
|
||||||
|
return if Rails.env.development?
|
||||||
|
|
||||||
|
ejabberd = EjabberdApiClient.new
|
||||||
|
inviter = @invitation.user
|
||||||
|
|
||||||
|
ejabberd.add_rosteritem({
|
||||||
|
"localuser": @username, "localhost": @domain,
|
||||||
|
"user": inviter.cn, "host": inviter.ou,
|
||||||
|
"nick": inviter.cn, "group": "Friends", "subs": "both"
|
||||||
|
})
|
||||||
|
ejabberd.add_rosteritem({
|
||||||
|
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||||
|
"user": @username, "host": @domain,
|
||||||
|
"nick": @username, "group": "Friends", "subs": "both"
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
20
app/services/ejabberd_api_client.rb
Normal file
20
app/services/ejabberd_api_client.rb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
class EjabberdApiClient
|
||||||
|
def initialize
|
||||||
|
@base_url = ENV["EJABBERD_API_URL"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(endpoint, payload)
|
||||||
|
res = Faraday.post("#{@base_url}/#{endpoint}", payload.to_json,
|
||||||
|
"Content-Type" => "application/json")
|
||||||
|
|
||||||
|
if res.status != 200
|
||||||
|
Rails.logger.error "[ejabberd] API request failed:"
|
||||||
|
Rails.logger.error res.body
|
||||||
|
#TODO add some kind of exception tracking/notifications
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_rosteritem(payload)
|
||||||
|
post "add_rosteritem", payload
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="grid services">
|
<div class="grid services">
|
||||||
<div class="grid-item chat">
|
<div class="grid-item chat">
|
||||||
<h3><%= link_to "Chat", "https://wiki.kosmos.org/Services:XMPP" %></h3>
|
<h3><%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat" %></h3>
|
||||||
<p>
|
<p>
|
||||||
Chat rooms and instant messaging (XMPP/Jabber)
|
Chat rooms and instant messaging (XMPP/Jabber)
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,23 +1,46 @@
|
|||||||
<section>
|
<section>
|
||||||
<h2>Invitations</h2>
|
<h2>Invitations</h2>
|
||||||
|
<% if @invitations_unused.any? %>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @invitations_unused.each do |invitation| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= invitation_url(invitation.token) %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% else %>
|
||||||
|
<p>
|
||||||
|
You do not have any invitations to give away yet. All good
|
||||||
|
things come in time.
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<% if @invitations_used.any? %>
|
||||||
|
<h3>Accepted Invitations</h3>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Token</th>
|
<th>URL</th>
|
||||||
<th>Created at</th>
|
<th>Used at</th>
|
||||||
<th colspan="3"></th>
|
<th>Invited user</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @invitations.each do |invitation| %>
|
<% @invitations_used.each do |invitation| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= invitation.token %></td>
|
<td><%= invitation_url(invitation.token) %></td>
|
||||||
<td><%= invitation.created_at %></td>
|
<td><%= invitation.used_at %></td>
|
||||||
<td><%= link_to 'Delete', invitation, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
<% end %>
|
||||||
|
|||||||
@@ -48,8 +48,14 @@
|
|||||||
<p class="error-msg">Password <%= @validation_error %></p>
|
<p class="error-msg">Password <%= @validation_error %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="accept-terms">
|
||||||
|
<small>
|
||||||
|
By clicking the button below, you accept our future Terms of Service
|
||||||
|
and Privacy Policy. Don't worry, they will be excellent!
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<p><%= f.submit "Continue" %></p>
|
<p><%= f.submit "Create account" %></p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -43,4 +43,10 @@ Rails.application.configure do
|
|||||||
|
|
||||||
# Raises error for missing translations.
|
# Raises error for missing translations.
|
||||||
# config.action_view.raise_on_missing_translations = true
|
# config.action_view.raise_on_missing_translations = true
|
||||||
|
|
||||||
|
config.action_mailer.default_url_options = {
|
||||||
|
host: "accounts.kosmos.org",
|
||||||
|
protocol: "https",
|
||||||
|
from: "accounts@kosmos.org"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ class CreateInvitations < ActiveRecord::Migration[6.0]
|
|||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
add_index :invitations, :user_id
|
||||||
|
add_index :invitations, :invited_user_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :invitation do
|
factory :invitation do
|
||||||
id { 1 }
|
|
||||||
token { "abcdef123456" }
|
token { "abcdef123456" }
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,37 +3,105 @@ require "rails_helper"
|
|||||||
RSpec.describe "Signup", type: :feature do
|
RSpec.describe "Signup", type: :feature do
|
||||||
let(:user) { create :user }
|
let(:user) { create :user }
|
||||||
|
|
||||||
before do
|
describe "Invitation handling" do
|
||||||
@unused_invitation = Invitation.create(user: user)
|
before do
|
||||||
@used_invitation = Invitation.create(user: user)
|
@unused_invitation = Invitation.create(user: user)
|
||||||
@used_invitation.update_attribute :used_at, DateTime.now - 1.day
|
@used_invitation = Invitation.create(user: user)
|
||||||
end
|
@used_invitation.update_attribute :used_at, DateTime.now - 1.day
|
||||||
|
end
|
||||||
|
|
||||||
scenario "Follow link for non-existing invitation" do
|
scenario "Follow link for non-existing invitation" do
|
||||||
visit invitation_url(id: "123")
|
visit invitation_url(id: "123")
|
||||||
|
|
||||||
within ".flash-msg.alert" do
|
within ".flash-msg.alert" do
|
||||||
expect(page).to have_content("doesn't exist")
|
expect(page).to have_content("doesn't exist")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Follow link for used invitation" do
|
||||||
|
visit invitation_url(id: @used_invitation.token)
|
||||||
|
|
||||||
|
within ".flash-msg.alert" do
|
||||||
|
expect(page).to have_content("has already been used")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Follow link for unused invitation" do
|
||||||
|
visit invitation_url(id: @unused_invitation.token)
|
||||||
|
|
||||||
|
expect(current_url).to eq(signup_url)
|
||||||
|
expect(page).to have_content("Welcome")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "Follow link for used invitation" do
|
describe "Signup steps" do
|
||||||
visit invitation_url(id: @used_invitation.token)
|
before do
|
||||||
|
@invitation = Invitation.create(user: user)
|
||||||
within ".flash-msg.alert" do
|
visit invitation_url(id: @invitation.token)
|
||||||
expect(page).to have_content("has already been used")
|
click_link "Get started"
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
scenario "Follow link for unused invitation" do
|
scenario "Successful signup (happy path galore)" do
|
||||||
visit invitation_url(id: @unused_invitation.token)
|
expect(page).to have_content("Choose a username")
|
||||||
|
|
||||||
expect(current_url).to eq(signup_url)
|
fill_in "user_cn", with: "tony"
|
||||||
expect(page).to have_content("Welcome")
|
click_button "Continue"
|
||||||
end
|
expect(page).to have_content("What's your email?")
|
||||||
|
|
||||||
scenario "Successful signup" do
|
fill_in "user_email", with: "tony@example.com"
|
||||||
visit invitation_url(id: @unused_invitation.token)
|
click_button "Continue"
|
||||||
click_link "Get started"
|
expect(page).to have_content("Choose a password")
|
||||||
|
|
||||||
|
expect(CreateAccount).to receive(:call)
|
||||||
|
.with(
|
||||||
|
username: "tony", domain: "kosmos.org",
|
||||||
|
email: "tony@example.com", password: "a-valid-password",
|
||||||
|
invitation: Invitation.last
|
||||||
|
).and_return(true)
|
||||||
|
|
||||||
|
fill_in "user_password", with: "a-valid-password"
|
||||||
|
click_button "Create account"
|
||||||
|
within ".flash-msg.notice" do
|
||||||
|
expect(page).to have_content("confirm your address")
|
||||||
|
end
|
||||||
|
expect(page).to have_content("close this window or tab now")
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Validation errors" do
|
||||||
|
fill_in "user_cn", with: "t"
|
||||||
|
click_button "Continue"
|
||||||
|
expect(page).to have_content("Username is too short")
|
||||||
|
fill_in "user_cn", with: "jimmy"
|
||||||
|
click_button "Continue"
|
||||||
|
expect(page).to have_content("Username has already been taken")
|
||||||
|
fill_in "user_cn", with: "tony"
|
||||||
|
click_button "Continue"
|
||||||
|
|
||||||
|
fill_in "user_email", with: "tony@"
|
||||||
|
click_button "Continue"
|
||||||
|
expect(page).to have_content("Email is not a valid address")
|
||||||
|
fill_in "user_email", with: ""
|
||||||
|
click_button "Continue"
|
||||||
|
expect(page).to have_content("Email can't be blank")
|
||||||
|
fill_in "user_email", with: "tony@example.com"
|
||||||
|
click_button "Continue"
|
||||||
|
|
||||||
|
fill_in "user_password", with: "123456"
|
||||||
|
click_button "Create account"
|
||||||
|
expect(page).to have_content("Password is too short")
|
||||||
|
|
||||||
|
expect(CreateAccount).to receive(:call)
|
||||||
|
.with(
|
||||||
|
username: "tony", domain: "kosmos.org",
|
||||||
|
email: "tony@example.com", password: "a-valid-password",
|
||||||
|
invitation: Invitation.last
|
||||||
|
).and_return(true)
|
||||||
|
|
||||||
|
fill_in "user_password", with: "a-valid-password"
|
||||||
|
click_button "Create account"
|
||||||
|
within ".flash-msg.notice" do
|
||||||
|
expect(page).to have_content("confirm your address")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,4 +19,26 @@ RSpec.describe Invitation, type: :model do
|
|||||||
expect(token).not_to eq(invitation_2.token)
|
expect(token).not_to eq(invitation_2.token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "scopes" do
|
||||||
|
before do
|
||||||
|
@unused_invitation = create :invitation, user: user
|
||||||
|
@used_invitation = create :invitation, user: user, used_at: DateTime.now
|
||||||
|
@used_invitation_2 = create :invitation, user: user, used_at: DateTime.now
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#unused" do
|
||||||
|
it "returns unused invitations" do
|
||||||
|
expect(Invitation.unused.count).to eq(1)
|
||||||
|
expect(Invitation.unused.first).to eq(@unused_invitation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#used" do
|
||||||
|
it "returns used invitations" do
|
||||||
|
expect(Invitation.used.count).to eq(2)
|
||||||
|
expect(Invitation.used.first).to eq(@used_invitation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
require 'webmock/rspec'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
RSpec.describe CreateAccount, type: :model do
|
RSpec.describe CreateAccount, type: :model do
|
||||||
let(:ldap_client_mock) { instance_double(Net::LDAP) }
|
let(:ldap_client_mock) { instance_double(Net::LDAP) }
|
||||||
@@ -7,6 +9,44 @@ RSpec.describe CreateAccount, type: :model do
|
|||||||
allow(service).to receive(:ldap_client).and_return(ldap_client_mock)
|
allow(service).to receive(:ldap_client).and_return(ldap_client_mock)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#create_user_in_database" do
|
||||||
|
let(:service) { CreateAccount.new(
|
||||||
|
username: 'isaacnewton',
|
||||||
|
email: 'isaacnewton@example.com',
|
||||||
|
password: 'bright-ideas-in-autumn'
|
||||||
|
)}
|
||||||
|
|
||||||
|
it "creates a new user record in the akkounts database" do
|
||||||
|
expect(User.count).to eq(0)
|
||||||
|
service.send(:create_user_in_database)
|
||||||
|
expect(User.count).to eq(1)
|
||||||
|
expect(User.last.cn).to eq("isaacnewton")
|
||||||
|
expect(User.last.email).to eq("isaacnewton@example.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#update_invitation" do
|
||||||
|
let(:invitation) { create :invitation }
|
||||||
|
let(:service) { CreateAccount.new(
|
||||||
|
username: 'isaacnewton',
|
||||||
|
email: 'isaacnewton@example.com',
|
||||||
|
password: 'bright-ideas-in-autumn',
|
||||||
|
invitation: invitation
|
||||||
|
)}
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
service.send(:update_invitation, 23)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "marks the invitation as used" do
|
||||||
|
expect(invitation.used_at).not_to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "saves the invited user's ID" do
|
||||||
|
expect(invitation.invited_user_id).to eq(23)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#add_ldap_document" do
|
describe "#add_ldap_document" do
|
||||||
let(:service) { CreateAccount.new(
|
let(:service) { CreateAccount.new(
|
||||||
username: 'halfinney',
|
username: 'halfinney',
|
||||||
@@ -30,4 +70,29 @@ RSpec.describe CreateAccount, type: :model do
|
|||||||
service.send(:add_ldap_document)
|
service.send(:add_ldap_document)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#exchange_xmpp_contacts" do
|
||||||
|
let(:inviter) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
||||||
|
let(:invitation) { create :invitation, user: inviter }
|
||||||
|
let(:service) { CreateAccount.new(
|
||||||
|
username: 'isaacnewton',
|
||||||
|
email: 'isaacnewton@example.com',
|
||||||
|
password: 'bright-ideas-in-autumn',
|
||||||
|
invitation: invitation
|
||||||
|
)}
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||||
|
.to_return(status: 200, body: "", headers: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "posts add_rosteritem commands to the ejabberd API" do
|
||||||
|
service.send(:exchange_xmpp_contacts)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||||
|
.with { |req| req.body == '{"localuser":"isaacnewton","localhost":"kosmos.org","user":"willherschel","host":"kosmos.org","nick":"willherschel","group":"Friends","subs":"both"}' }
|
||||||
|
expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||||
|
.with { |req| req.body == '{"localuser":"willherschel","localhost":"kosmos.org","user":"isaacnewton","host":"kosmos.org","nick":"isaacnewton","group":"Friends","subs":"both"}' }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user