Er zijn geen gebruikers om weer te geven
@@ -79,7 +79,7 @@ export default {
computed: {
total: function() {
return this.users.map(user => user.credit)
- .reduce((current, credit) => parseFloat(current) + parseFloat(credit));
+ .reduce((current, credit) => parseFloat(current) + parseFloat(credit), 0);
},
sortedUsers: function() {
diff --git a/app/javascript/packs/users.js b/app/javascript/packs/users.js
index 8d958d8b7..ee0d63af3 100644
--- a/app/javascript/packs/users.js
+++ b/app/javascript/packs/users.js
@@ -11,14 +11,18 @@ document.addEventListener('turbolinks:load', () => {
var element = document.getElementById('users-index');
if (element !== null) {
var manual_users = JSON.parse(element.dataset.manualUsers);
+ var identity_users = JSON.parse(element.dataset.identityUsers);
var amber_users = JSON.parse(element.dataset.amberUsers);
- var inactive_users = JSON.parse(element.dataset.inactiveUsers);
+ var not_activated_users = JSON.parse(element.dataset.notActivatedUsers);
+ var deactivated_users = JSON.parse(element.dataset.deactivatedUsers);
new Vue({
el: element,
data: () => ({
manual_users,
+ identity_users,
amber_users,
- inactive_users
+ not_activated_users,
+ deactivated_users
}),
components: {
UsersTable
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
new file mode 100644
index 000000000..cba16231a
--- /dev/null
+++ b/app/mailers/user_mailer.rb
@@ -0,0 +1,28 @@
+class UserMailer < ApplicationMailer
+ def account_creation_email(user)
+ @user = user
+ @activate_account_url = Identity.activate_account_url(@user.id, @user.activation_token)
+ @new_activation_link_url = Identity.new_activation_link_url(@user.id)
+ @header_text = "Welkom op #{Rails.application.config.x.site_name}!"
+ @call_to_action = { text: 'Klik hier om uw account te activeren!', url: @activate_account_url }
+ mail to: user.email, subject: "Accountactivatie voor het streepsysteem van uw vereniging"
+ end
+
+ def new_activation_link_email(user)
+ @user = user
+ @activate_account_url = Identity.activate_account_url(@user.id, @user.activation_token)
+ @new_activation_link_url = Identity.new_activation_link_url(@user.id)
+ @header_text = "Welkom op #{Rails.application.config.x.site_name}!"
+ @call_to_action = { text: 'Klik hier om uw account te activeren!', url: @activate_account_url }
+ mail to: user.email, subject: "Accountactivatie voor het streepsysteem van uw vereniging"
+ end
+
+ def forgot_password_email(user)
+ @user = user
+ @username = user.identity.username
+ @reset_password_url = user.identity.reset_password_url(@user.activation_token)
+ @forgot_password_url = Identity.forgot_password_url
+ @call_to_action = { text: 'Wachtwoord herstellen!', url: @reset_password_url }
+ mail to: user.email, subject: "Wachtwoordherstel voor het streepsysteem van uw vereniging"
+ end
+end
diff --git a/app/models/identity.rb b/app/models/identity.rb
new file mode 100644
index 000000000..94fab2a0e
--- /dev/null
+++ b/app/models/identity.rb
@@ -0,0 +1,36 @@
+class Identity < OmniAuth::Identity::Models::ActiveRecord
+ has_one_time_password
+
+ belongs_to :user
+
+ validates :user, presence: true, uniqueness: true
+ validates :username, presence: true, uniqueness: true
+ validates :password, length: { minimum: 12 }, allow_nil: true # the presence of :password is already checked by omniauth-identity itself
+
+ auth_key :username # optional: specifies the field within the model that will be used during the login process
+ # defaults to email, but may be username, uid, login, etc.
+
+ def self.activate_account_url(user_id, activation_token)
+ params = { user_id: user_id, activation_token: activation_token }
+ default_options = Rails.application.config.action_mailer.default_url_options
+ URI::Generic.build(default_options.merge(path: '/identities/activate_account', query: params.to_query)).to_s
+ end
+
+ def self.new_activation_link_url(user_id)
+ params = { user_id: user_id }
+ default_options = Rails.application.config.action_mailer.default_url_options
+ URI::Generic.build(default_options.merge(path: '/identities/new_activation_link', query: params.to_query)).to_s
+ end
+
+ def self.forgot_password_url
+ default_options = Rails.application.config.action_mailer.default_url_options
+ URI::Generic.build(default_options.merge(path: '/identities/forgot_password')).to_s
+ end
+
+ def reset_password_url(activation_token)
+ params = { activation_token: activation_token }
+ default_options = Rails.application.config.action_mailer.default_url_options
+ URI::Generic.build(default_options.merge(path: "/identities/#{id}/reset_password", query: params.to_query)).to_s
+ end
+
+end
diff --git a/app/models/role.rb b/app/models/role.rb
index 779a9ad66..650e43df9 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -1,7 +1,7 @@
class Role < ApplicationRecord
enum role_type: { treasurer: 0, main_bartender: 1 }
- validates :role_type, :group_uid, presence: true
+ validates :role_type, presence: true
has_many :roles_users, class_name: 'RolesUsers', dependent: :destroy, inverse_of: :role
has_many :users, through: :roles_users
diff --git a/app/models/user.rb b/app/models/user.rb
index 04a78dc63..864567a96 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,5 +1,5 @@
class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
- devise :omniauthable, omniauth_providers: [:amber_oauth2]
+ devise :omniauthable, omniauth_providers: [:amber_oauth2, :identity]
has_many :orders, dependent: :destroy
has_many :order_rows, through: :orders, dependent: :destroy
has_many :credit_mutations, dependent: :destroy
@@ -11,15 +11,44 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
validates :name, presence: true
validates :uid, uniqueness: true, allow_blank: true
validate :no_deactivation_when_nonzero_credit
+ validates :email, format: { with: Devise.email_regexp }, allow_blank: true
+ validates :email, presence: true, if: ->(user) { !user.deactivated && user.identity.present? }
scope :in_amber, (-> { where(provider: 'amber_oauth2') })
+ scope :identity, (-> { where(provider: 'identity') })
scope :manual, (-> { where(provider: nil) })
- scope :active, (-> { where(deactivated: false) })
- scope :inactive, (-> { where(deactivated: true) })
+ scope :active, (-> {
+ where(deactivated: false).where('(provider IS NULL OR provider != ?) OR (provider = ? AND id IN (?))', 'identity', 'identity', Identity.select('user_id'))
+ })
+ scope :not_activated, (-> { where(deactivated: false, provider: 'identity').where('id NOT IN (?)', Identity.select('user_id')) })
+ scope :deactivated, (-> { where(deactivated: true) })
scope :treasurer, (-> { joins(:roles).merge(Role.treasurer) })
+ has_one :identity, dependent: :delete
+ accepts_nested_attributes_for :identity
+
attr_accessor :current_activity
+ before_save do
+ if new_record? && self.provider == 'identity'
+ self.activation_token = SecureRandom.urlsafe_base64
+ self.activation_token_valid_till = 5.day.from_now
+ end
+ end
+
+ after_save do
+ a = age
+ if self.deactivated && (new_record? || self.deactivated_previously_changed?(from: false, to: true))
+ archive!
+ end
+ end
+
+ after_create do
+ if User.identity.exists?(id: self.id)
+ UserMailer.account_creation_email(self).deliver_later
+ end
+ end
+
def credit
credit_mutations.sum('amount') - order_rows.sum('product_count * price_per_product')
end
@@ -66,16 +95,18 @@ def main_bartender?
end
def update_role(groups)
- roles_to_have = Role.where(group_uid: groups)
- roles_users_to_have = roles_to_have.map { |role| RolesUsers.find_or_create_by(role: role, user: self) }
+ if (User.in_amber.exists?(self.id))
+ roles_to_have = Role.where(group_uid: groups)
+ roles_users_to_have = roles_to_have.map { |role| RolesUsers.find_or_create_by(role: role, user: self) }
- roles_users_not_to_have = roles_users - roles_users_to_have
- roles_users_not_to_have.map(&:destroy)
+ roles_users_not_to_have = roles_users - roles_users_to_have
+ roles_users_not_to_have.map(&:destroy)
+ end
end
def archive!
attributes.each_key do |attribute|
- self[attribute] = nil unless %w[deleted_at updated_at created_at provider id uid].include? attribute
+ self[attribute] = nil unless %w[deleted_at updated_at created_at provider identity id uid].include? attribute
end
self.name = "Gearchiveerde gebruiker #{id}"
self.deactivated = true
@@ -98,6 +129,11 @@ def self.from_omniauth(auth) # rubocop:disable Metrics/AbcSize, Metrics/MethodLe
user
end
+ def self.from_omniauth_inspect(auth)
+ identity = Identity.find(auth.uid)
+ identity.user
+ end
+
# :nocov:
def self.full_name_from_attributes(first_name, last_name_prefix, last_name, nickname)
diff --git a/app/policies/identity_policy.rb b/app/policies/identity_policy.rb
new file mode 100644
index 000000000..fc5c211c2
--- /dev/null
+++ b/app/policies/identity_policy.rb
@@ -0,0 +1,21 @@
+class IdentityPolicy < ApplicationPolicy
+ def update?
+ record.user == user && User.exists?(id: record.user)
+ end
+
+ def update_with_identity?
+ update?
+ end
+
+ def update_password?
+ update?
+ end
+
+ def enable_otp?
+ update?
+ end
+
+ def disable_otp?
+ update?
+ end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 80b8604e3..f7ed39b22 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -22,4 +22,8 @@ def json?
def activities?
user&.treasurer? || record == user
end
+
+ def update_with_identity?
+ record == user && User.active.identity.exists?(id: record)
+ end
end
diff --git a/app/views/identities/activate_account.html.erb b/app/views/identities/activate_account.html.erb
new file mode 100644
index 000000000..025d24477
--- /dev/null
+++ b/app/views/identities/activate_account.html.erb
@@ -0,0 +1,20 @@
+<% content_for :title, "Account activatie - #{Rails.application.config.x.site_name}" %>
+
+
+
+
+ <%= simple_form_for @identity, url: identities_path(user_id: @user_id, activation_token: @activation_token), method: :post, wrapper: :vertical_form do |f| %>
+ <%= f.input :username, as: :string, required: true, input_html: {class: 'identity-input'} %>
+ <% if @request_email %>
+ <%= f.input :email, as: :email, required: true, input_html: {class: 'identity-input', name: 'user[email]', value: ''} %>
+ <% end %>
+ <%= f.input :password, as: :password, required: true, input_html: {class: 'identity-input'} %>
+ <%= f.input :password_confirmation, as: :password, required: true, input_html: {class: 'identity-input'} %>
+
+ <%= f.button :submit, 'Activeren', class: 'btn btn-primary' %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/identities/forgot_password_view.html.erb b/app/views/identities/forgot_password_view.html.erb
new file mode 100644
index 000000000..ab4c5ff46
--- /dev/null
+++ b/app/views/identities/forgot_password_view.html.erb
@@ -0,0 +1,16 @@
+<% content_for :title, "Wachtwoord resetten - #{Rails.application.config.x.site_name}" %>
+
+
+
+
+ <%= simple_form_for :identity, url: forgot_password_identities_path, method: :patch, wrapper: :vertical_form do |f| %>
+
Door dit formulier in te vullen ontvangt u een email met een link om een nieuw wachwoord in te stellen.
+ <%= f.input :username, as: :string, input_html: {name: 'username', class: 'identity-input'} %>
+
+ <%= f.button :submit, 'Link aanvragen', method: :patch, class: 'btn btn-primary mt-1' %>
+
+ <% end %>
+
+
diff --git a/app/views/identities/login.html.erb b/app/views/identities/login.html.erb
new file mode 100644
index 000000000..442c72af0
--- /dev/null
+++ b/app/views/identities/login.html.erb
@@ -0,0 +1,107 @@
+<% content_for :title, "Login - #{Rails.application.config.x.site_name}" %>
+
+
+
+<%= simple_form_for :identity, id: "authentication_form", authenticity_token: false, wrapper: :vertical_form do |f| %>
+
+
+
+ <%= f.input :auth_key, as: :string, label: "Gebruikersnaam", input_html: {name: 'auth_key', class: 'identity-input'} %>
+ <%= f.input :password, as: :password, label: "Wachtwoord", hint: 'Wachtwoord vergeten?', wrapper: :with_hint_link, input_html: {name: 'password', class: 'identity-input'}, hint_html: {href: forgot_password_view_identities_path} %>
+
+ Inloggen
+
+
+
+
+
+
+
+ <%= f.input :verification_code, as: :string, label: "Authenticatiecode", input_html: {name: 'verification_code', value: '', class: 'identity-input'} %>
+
+ Open de authenticatie app op je telefoon en geef hier de zescijferige authenticatiecode op. Authenticatie codes kwijtgeraakt? Neem dan contact op met de ICT-commissie voor identificatie.
+
+
+ Authenticeren
+
+
+
+<% end %>
+
+
\ No newline at end of file
diff --git a/app/views/identities/new_activation_link.html.erb b/app/views/identities/new_activation_link.html.erb
new file mode 100644
index 000000000..1e6572e9d
--- /dev/null
+++ b/app/views/identities/new_activation_link.html.erb
@@ -0,0 +1,10 @@
+<% content_for :title, "Nieuwe activatielink - #{Rails.application.config.x.site_name}" %>
+
+
\ No newline at end of file
diff --git a/app/views/identities/reset_password_view.html.erb b/app/views/identities/reset_password_view.html.erb
new file mode 100644
index 000000000..f726554dd
--- /dev/null
+++ b/app/views/identities/reset_password_view.html.erb
@@ -0,0 +1,16 @@
+<% content_for :title, "Wachtwoord resetten - #{Rails.application.config.x.site_name}" %>
+
+
+
+
+ <%= simple_form_for :identity, url: reset_password_identity_path(activation_token: @activation_token), method: :patch, wrapper: :vertical_form do |f| %>
+ <%= f.input :password, as: :password, required: true, input_html: {class: 'identity-input'} %>
+ <%= f.input :password_confirmation, as: :password, required: true, input_html: {class: 'identity-input'} %>
+
+ <%= f.button :submit, 'Opslaan', method: :patch, class: 'btn btn-primary' %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/partials/_flash.html.erb b/app/views/partials/_flash.html.erb
index 1de88aca9..9f2c49dd3 100644
--- a/app/views/partials/_flash.html.erb
+++ b/app/views/partials/_flash.html.erb
@@ -1,12 +1,12 @@
<% if flash.any? %>
-
+
<% flash.each do |key, value| %>
- <%= value %>
+ <%= value.html_safe %>
diff --git a/app/views/partials/_login_prompt.html.erb b/app/views/partials/_login_prompt.html.erb
index 0ba91def2..efe4ef40f 100644
--- a/app/views/partials/_login_prompt.html.erb
+++ b/app/views/partials/_login_prompt.html.erb
@@ -2,16 +2,31 @@
-
-
- Log in with a <%= Rails.application.config.x.site_association %> account.
-
-
- <%= link_to("Sign in with #{Rails.application.config.x.site_association}", user_amber_oauth2_omniauth_authorize_path, class: 'btn btn-primary', method: :post) %>
-
-
+
+ <% if Rails.application.config.x.site_association == 'C.S.V. Alpha' %>
+
+
+ Log in met een <%= Rails.application.config.x.site_association %> account.
+
+
+ <%= link_to("Log in met #{Rails.application.config.x.site_association}", user_amber_oauth2_omniauth_authorize_path, class: 'btn btn-primary', method: :post) %>
+
+
+
+ OF
+
+ <% end %>
+
+
+ Log in met een streepsysteem account.
+
+
+ <%= link_to "Log in", login_identities_path, class: 'btn btn-primary'%>
+
+
+
diff --git a/app/views/user_mailer/account_creation_email.html.erb b/app/views/user_mailer/account_creation_email.html.erb
new file mode 100644
index 000000000..d9001d2b2
--- /dev/null
+++ b/app/views/user_mailer/account_creation_email.html.erb
@@ -0,0 +1,14 @@
+Er is een account voor u aangemaakt op het streepsysteem van uw vereniging. Deze is op dit moment nog niet geactiveerd.
+
+
+Volg deze link om uw account te activeren:
+
+ <%= @activate_account_url %>
+
+
+
+De link is 5 dagen geldig. Mocht de link verlopen zijn, dan kunt u een nieuwe link aanvragen:
+
+ <%= @new_activation_link_url %>
+
+
diff --git a/app/views/user_mailer/forgot_password_email.html.erb b/app/views/user_mailer/forgot_password_email.html.erb
new file mode 100644
index 000000000..d5f33ca0e
--- /dev/null
+++ b/app/views/user_mailer/forgot_password_email.html.erb
@@ -0,0 +1,18 @@
+Er is een aanvraag gedaan tot het resetten van het wachtwoord voor het account verbonden met dit e-mailadres op het streepsysteem van uw vereniging.
+Als u niets hebt aangevraagd kunt u deze e-mail negeren.
+
+
+Volg deze link om uw nieuwe wachtwoord in te stellen:
+
+ <%= @reset_password_url %>
+
+
+
+Uw gebruikersnaam is
<%= @username %> .
+
+
+De link is 1 dag geldig. Mocht de link verlopen zijn, dan kunt u een nieuwe link aanvragen:
+
+ <%= @forgot_password_url %>
+
+
diff --git a/app/views/user_mailer/new_activation_link_email.html.erb b/app/views/user_mailer/new_activation_link_email.html.erb
new file mode 100644
index 000000000..3d9001beb
--- /dev/null
+++ b/app/views/user_mailer/new_activation_link_email.html.erb
@@ -0,0 +1,14 @@
+Er is een aanvraag gedaan voor een nieuwe activatielink voor het account verbonden met dit e-mailadres op het streepsysteem van uw vereniging.
+
+
+Volg deze link om uw account te activeren:
+
+ <%= @activate_account_url %>
+
+
+
+De link is 1 dag geldig. Mocht de link verlopen zijn, dan kunt u een nieuwe link aanvragen:
+
+ <%= @new_activation_link_url %>
+
+
diff --git a/app/views/users/_edit_identity_modal.html.erb b/app/views/users/_edit_identity_modal.html.erb
new file mode 100644
index 000000000..d853e12f6
--- /dev/null
+++ b/app/views/users/_edit_identity_modal.html.erb
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+ <% if @identity && policy(@identity).update? %>
+
+
+
+ <%= simple_form_for @user, wrapper: :horizontal_form, url: update_with_identity_user_path, method: :patch do |f| %>
+ <%= f.simple_fields_for :identity do |i| %>
+ <%= i.input :username, required: true %>
+ <% end %>
+ <% if current_user.treasurer? %>
+ <%= f.input :name, label: 'Naam', placeholder: 'Naam', required: true %>
+ <% end %>
+ <%= f.input :email, label: 'E-mailadres', placeholder: 'E-mailadres', required: true %>
+ <% if current_user.treasurer? %>
+
+
+ Gedeactiveerd
+
+
+ <%= f.check_box :deactivated %>
+
+
+ <% end %>
+ <%= f.button :submit, 'Opslaan', :data => {:disable_with => 'Bezig...'}, class: 'btn btn-primary float-end', method: :patch %>
+ <% end %>
+
+
+
+
+
+
+ <%= simple_form_for @identity, wrapper: :horizontal_form, url: update_password_identity_path(@identity.id), method: :patch do |f| %>
+ <%= f.input :old_password %>
+ <%= f.input :password %>
+ <%= f.input :password_confirmation, required: false %>
+ <%= f.button :submit, 'Opslaan', :data => {:disable_with => 'Bezig...'}, class: 'btn btn-primary float-end', method: :patch %>
+ <% end %>
+
+
+
+
+
+
+
+ Two-factor-authenticatie geeft een extra beveiligingslaag door bij het
+ inloggen naast je wachtwoord ook om een zescijferige code te vragen. Deze code
+ wordt gegenereerd door een app op je telefoon. Zo heb je naast iets dat je
+ weet (wachtwoord) ook iets nodig dat je hebt (telefoon) om in te kunnen
+ loggen.
+
+
+ <% if @identity.otp_enabled %>
+
+
+
+
+ Je hebt two-factor-authenticatie geactiveerd!
+
+
+ <%= link_to 'Deactiveren', disable_otp_identity_path, class: 'btn btn-danger float-end' %>
+
+
+
+
+ <% else %>
+ <%= simple_form_for @identity, wrapper: :horizontal_form, url: enable_otp_identity_path(@identity.id), method: :patch do |f| %>
+
+ Scan de QR-code hieronder met een authenticatorapp. Er zijn
+ authenticatorapps beschikbaar voor alle mobiele platforms. Hier volgen
+ enkele links naar apps:
+
+
+
+
+ Nadat je de code gescand hebt, bevestig je de set-up door een keer een
+ gegenereerde code in te voeren.
+
+
+ <%= render inline: @svg_qr_code %>
+
+
+ <%= f.input :verification_code, as: :string, input_html: { name: 'verification_code', value: "" } %>
+
+
+ <%= f.button :submit, '2FA Activeren', :data => {:disable_with => 'Bezig...'}, class: 'btn btn-primary float-end', method: :patch %>
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+
+
+
+
+
diff --git a/app/views/users/_new_identity_user_modal.html.erb b/app/views/users/_new_identity_user_modal.html.erb
new file mode 100644
index 000000000..b09da5863
--- /dev/null
+++ b/app/views/users/_new_identity_user_modal.html.erb
@@ -0,0 +1,27 @@
+
+
+
+
+
+ <%= simple_form_for @new_user, wrapper: :horizontal_form do |f| %>
+ <%= f.hidden_field :provider, value: "identity" %>
+
+ <%= f.input :name, label: 'Naam', placeholder: 'Naam', required: true %>
+ <%= f.input :email, label: 'E-mailadres', placeholder: 'E-mailadres', required: true %>
+
+
+
+ <% end %>
+
+
+
diff --git a/app/views/users/activate_account.html.erb b/app/views/users/activate_account.html.erb
new file mode 100644
index 000000000..d5820ffac
--- /dev/null
+++ b/app/views/users/activate_account.html.erb
@@ -0,0 +1,17 @@
+<% content_for :title, "Account activatie - #{Rails.application.config.x.site_name}" %>
+
+
+
+
+ <%= simple_form_for @identity, url: "/users/auth/identity/register", wrapper: :vertical_form do |f| %>
+ <%= f.input :user_id, as: :hidden, input_html: {name: 'user_id', value: @identity.user_id} %>
+ <%= f.input :password, as: :password, required: true, input_html: {name: 'password'} %>
+ <%= f.input :password_confirmation, as: :password, required: true, input_html: {name: 'password_confirmation'} %>
+
+ <%= f.button :submit, 'Activeren', class: 'btn btn-primary' %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index d43f11001..14e35c2f5 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -1,12 +1,13 @@
<% content_for :title, "Gebruikers - #{Rails.application.config.x.site_name}" %>
<% content_for :modal do %>
<%= render 'modal' %>
+ <%= render 'new_identity_user_modal' %>
<% end %>