Add account management

This commit is contained in:
Hugo Peixoto 2022-06-25 23:45:47 +01:00
parent 882d0a51ed
commit 4f190f67f0
25 changed files with 323 additions and 6 deletions

View File

@ -4,13 +4,14 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.2" ruby "3.1.2"
gem "bootsnap", require: false gem "bootsnap", require: false
gem "clearance"
gem "dotenv-rails"
gem "importmap-rails" gem "importmap-rails"
gem "pg", "~> 1.1" gem "pg", "~> 1.1"
gem "propshaft" gem "propshaft"
gem "puma", "~> 5.0" gem "puma", "~> 5.0"
gem "rails", "~> 7.0.3"
gem "dotenv-rails"
gem "pundit" gem "pundit"
gem "rails", "~> 7.0.3"
gem "ransack" gem "ransack"
group :development, :test do group :development, :test do

View File

@ -66,10 +66,22 @@ GEM
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
argon2 (2.1.1)
ffi (~> 1.14)
ffi-compiler (~> 1.0)
bcrypt (3.1.18)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.12.0) bootsnap (1.12.0)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.2.4) builder (3.2.4)
clearance (2.6.0)
actionmailer (>= 5.0)
activemodel (>= 5.0)
activerecord (>= 5.0)
argon2 (~> 2.0, >= 2.0.2)
bcrypt (>= 3.1.1)
email_validator (~> 2.0)
railties (>= 5.0)
concurrent-ruby (1.1.10) concurrent-ruby (1.1.10)
crass (1.0.6) crass (1.0.6)
debug (1.5.0) debug (1.5.0)
@ -80,7 +92,13 @@ GEM
dotenv-rails (2.7.6) dotenv-rails (2.7.6)
dotenv (= 2.7.6) dotenv (= 2.7.6)
railties (>= 3.2) railties (>= 3.2)
email_validator (2.2.3)
activemodel
erubi (1.10.0) erubi (1.10.0)
ffi (1.15.5)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
i18n (1.10.0) i18n (1.10.0)
@ -186,6 +204,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
bootsnap bootsnap
clearance
debug debug
dotenv-rails dotenv-rails
importmap-rails importmap-rails

View File

@ -1,2 +1,3 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Clearance::Controller
end end

View File

@ -0,0 +1,20 @@
class BoardsController < ApplicationController
before_action :require_login
def edit
@users = User.where(active: true)
@users += Array.new(5 - @users.size) { User.new }
end
def update
User.update_board_members(board_params)
redirect_to edit_board_path
end
private
def board_params
params.require('users')
end
end

View File

@ -1,4 +1,5 @@
class ContributionsController < ApplicationController class ContributionsController < ApplicationController
before_action :require_login
before_action :set_member, only: %i[ new create ] before_action :set_member, only: %i[ new create ]
before_action :set_contribution, only: %i[ edit update delete destroy ] before_action :set_contribution, only: %i[ edit update delete destroy ]

View File

@ -1,4 +1,5 @@
class MembersController < ApplicationController class MembersController < ApplicationController
before_action :require_login
before_action :set_member, only: %i[ show edit update delete destroy ] before_action :set_member, only: %i[ show edit update delete destroy ]
helper_method :sort_params helper_method :sort_params

View File

@ -0,0 +1,9 @@
class ActiveGuard < Clearance::SignInGuard
def call
if signed_in? && !current_user.active
failure(t('sessions.new.deactivated_error'))
else
next_guard
end
end
end

27
app/models/user.rb Normal file
View File

@ -0,0 +1,27 @@
class User < ApplicationRecord
include Clearance::User
def self.update_board_members(new_emails)
where(active: true).each do |user|
if !new_emails.include?(user.email)
user.update(active: false)
# TODO: notify board member of deactivation
end
end
new_emails.each do |email|
user = User.find_by(email: email)
if user
if !user.active?
# TODO: notify board member of activation
user.update(active: true, password: SecureRandom.hex(32))
end
else
# TODO: notify board member of activation
User.create(email: email, password: SecureRandom.hex(32))
end
end
end
end

View File

@ -0,0 +1,19 @@
<h1><%= t('boards.edit.title') %></h1>
<p><%= t('boards.edit.warning') %></p>
<%= form_with scope: :users, url: board_path, method: :put do |form| %>
<table class='lined'>
<% @users.each do |user| %>
<tr>
<td>
<%= form.email_field nil, value: user.email %>
</td>
</tr>
<% end %>
</table>
<div>
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,8 @@
<p><%= t(".opening") %></p>
<p>
<%= link_to t(".link_text", default: "Change my password"),
url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
</p>
<p><%= t(".closing") %></p>

View File

@ -0,0 +1,5 @@
<%= t(".opening") %>
<%= url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
<%= t(".closing") %>

View File

@ -11,9 +11,19 @@
</head> </head>
<body> <body>
<% if signed_in? %>
<nav> <nav>
<%= link_to t('navigation.members'), members_path %> <ul>
<li><%= link_to t('navigation.members'), members_path %></li>
<li><%= link_to t('navigation.board'), edit_board_path %></li>
<li>
<%= button_to sign_out_path, method: :delete do %>
Sign out of <%= current_user.email %>
<% end %>
</li>
</ul>
</nav> </nav>
<% end %>
<%= yield %> <%= yield %>
</body> </body>

View File

@ -0,0 +1,3 @@
<div id="clearance" class="password-reset">
<p><%= t(".description") %></p>
</div>

View File

@ -0,0 +1,18 @@
<div id="clearance" class="password-reset">
<h2><%= t(".title") %></h2>
<p><%= t(".description") %></p>
<%= form_for :password_reset,
url: [@user, :password, token: @user.confirmation_token],
html: { method: :put } do |form| %>
<div class="password-field">
<%= form.label :password %>
<%= form.password_field :password %>
</div>
<div class="submit-field">
<%= form.submit %>
</div>
<% end %>
</div>

View File

@ -0,0 +1,16 @@
<div id="clearance" class="password-reset">
<h2><%= t(".title") %></h2>
<p><%= t(".description") %></p>
<%= form_for :password, url: passwords_path do |form| %>
<div class="text-field">
<%= form.label :email %>
<%= form.email_field :email %>
</div>
<div class="submit-field">
<%= form.submit %>
</div>
<% end %>
</div>

View File

@ -0,0 +1,28 @@
<%= form_for :session, url: session_path do |form| %>
<table>
<tr>
<td>
<%= form.label :email %>
</td>
<td>
<%= form.email_field :email %>
</td>
</tr>
<tr>
<td><%= form.label :password %></td>
<td><%= form.password_field :password %></td>
</tr>
</table>
<div class="submit-field">
<%= form.submit %>
</div>
<div class="other-links">
<% if Clearance.configuration.allow_sign_up? %>
<%= link_to t(".sign_up"), sign_up_path %>
<% end %>
<%= link_to t(".forgot_password"), new_password_path %>
</div>
<% end %>

View File

@ -0,0 +1,5 @@
<div id="clearance" class="sign-in">
<h2><%= t(".title") %></h2>
<%= render partial: '/sessions/form' %>
</div>

View File

@ -53,6 +53,10 @@ Rails.application.configure do
enable_starttls_auto: true, enable_starttls_auto: true,
} }
config.action_mailer.default_url_options = {
host: ENV['BASE_HOST'],
}
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log

View File

@ -0,0 +1,8 @@
Clearance.configure do |config|
config.allow_sign_up = false
config.mailer_sender = ENV['SMTP_FROM_ADDRESS']
config.rotate_csrf_on_sign_in = true
config.secure_cookie = true
config.signed_cookie = true
config.sign_in_guards = ["ActiveGuard"]
end

View File

@ -0,0 +1,64 @@
---
en:
clearance:
models:
clearance_mailer:
change_password: Change your password
clearance_mailer:
change_password:
closing: If you didn't request this, ignore this email. Your password has
not been changed.
link_text: Change my password
opening: "Someone, hopefully you, requested we send you a link to change
your password:"
flashes:
failure_after_create: Bad email or password.
failure_after_update: Password can't be blank.
failure_when_forbidden: Please double check the URL or try submitting
the form again.
failure_when_not_signed_in: Please sign in to continue.
failure_when_missing_email: Email can't be blank.
helpers:
label:
password:
email: Email address
password_reset:
password: Choose password
session:
password: Password
user:
password: Password
submit:
password:
submit: Reset password
password_reset:
submit: Save this password
session:
submit: Sign in
user:
create: Sign up
layouts:
application:
sign_in: Sign in
sign_out: Sign out
passwords:
create:
description: You will receive an email within the next few minutes. It
contains instructions for changing your password.
edit:
description: Your password has been reset. Choose a new password below.
title: Change your password
new:
description: To be emailed a link to reset your password, please enter
your email address.
title: Reset your password
sessions:
form:
forgot_password: Forgot password?
sign_up: Sign up
new:
title: Sign in
users:
new:
sign_in: Sign in
title: Sign up

View File

@ -101,3 +101,14 @@ pt:
cancelled: cancelled:
subject: "ANSOL - Inscrição cancelada" subject: "ANSOL - Inscrição cancelada"
greetings: "Caro(a) %{display_name}" greetings: "Caro(a) %{display_name}"
sessions:
new:
title: "Entrar"
attributes:
email: "oi"
boards:
edit:
title: "Actualizar membros da direcção"
warning: |
Atenção: qualquer endereço que ficar omisso verá a sua conta suspensa e
deixará de poder aceder a este sistema.

View File

@ -3,6 +3,8 @@ Rails.application.routes.draw do
# Defines the root path route ("/") # Defines the root path route ("/")
# root "articles#index" # root "articles#index"
#
root to: redirect('/members')
resources :members do resources :members do
member do member do
@ -16,4 +18,6 @@ Rails.application.routes.draw do
get :delete get :delete
end end
end end
resource :board, only: [:edit, :update]
end end

View File

@ -0,0 +1,15 @@
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users, id: :uuid do |t|
t.timestamps null: false
t.string :email, null: false
t.string :encrypted_password, limit: 128, null: false
t.string :confirmation_token, limit: 128
t.string :remember_token, limit: 128, null: false
end
add_index :users, :email
add_index :users, :confirmation_token, unique: true
add_index :users, :remember_token, unique: true
end
end

View File

@ -0,0 +1,7 @@
class AddActiveToUser < ActiveRecord::Migration[7.0]
def change
change_table :users do |t|
t.boolean :active, null: false, default: true
end
end
end

15
db/schema.rb generated
View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_06_25_181824) do ActiveRecord::Schema[7.0].define(version: 2022_06_25_210821) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto" enable_extension "pgcrypto"
enable_extension "plpgsql" enable_extension "plpgsql"
@ -56,6 +56,19 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_25_181824) do
t.index ["member_id"], name: "index_notifications_on_member_id" t.index ["member_id"], name: "index_notifications_on_member_id"
end end
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", null: false
t.string "encrypted_password", limit: 128, null: false
t.string "confirmation_token", limit: 128
t.string "remember_token", limit: 128, null: false
t.boolean "active", default: true, null: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email"
t.index ["remember_token"], name: "index_users_on_remember_token", unique: true
end
add_foreign_key "contributions", "members" add_foreign_key "contributions", "members"
add_foreign_key "notifications", "members" add_foreign_key "notifications", "members"
end end