diff --git a/Gemfile b/Gemfile index 120ac9d..cd1b2bf 100644 --- a/Gemfile +++ b/Gemfile @@ -4,13 +4,14 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.2" gem "bootsnap", require: false +gem "clearance" +gem "dotenv-rails" gem "importmap-rails" gem "pg", "~> 1.1" gem "propshaft" gem "puma", "~> 5.0" -gem "rails", "~> 7.0.3" -gem "dotenv-rails" gem "pundit" +gem "rails", "~> 7.0.3" gem "ransack" group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 8f490b1..daf3448 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,10 +66,22 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + argon2 (2.1.1) + ffi (~> 1.14) + ffi-compiler (~> 1.0) + bcrypt (3.1.18) bindex (0.8.1) bootsnap (1.12.0) msgpack (~> 1.2) 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) crass (1.0.6) debug (1.5.0) @@ -80,7 +92,13 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) + email_validator (2.2.3) + activemodel erubi (1.10.0) + ffi (1.15.5) + ffi-compiler (1.0.1) + ffi (>= 1.0.0) + rake globalid (1.0.0) activesupport (>= 5.0) i18n (1.10.0) @@ -186,6 +204,7 @@ PLATFORMS DEPENDENCIES bootsnap + clearance debug dotenv-rails importmap-rails diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..789d46b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,3 @@ class ApplicationController < ActionController::Base + include Clearance::Controller end diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb new file mode 100644 index 0000000..564557d --- /dev/null +++ b/app/controllers/boards_controller.rb @@ -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 diff --git a/app/controllers/contributions_controller.rb b/app/controllers/contributions_controller.rb index 15d812c..47c26b4 100644 --- a/app/controllers/contributions_controller.rb +++ b/app/controllers/contributions_controller.rb @@ -1,4 +1,5 @@ class ContributionsController < ApplicationController + before_action :require_login before_action :set_member, only: %i[ new create ] before_action :set_contribution, only: %i[ edit update delete destroy ] diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index a69ad47..00282d0 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -1,4 +1,5 @@ class MembersController < ApplicationController + before_action :require_login before_action :set_member, only: %i[ show edit update delete destroy ] helper_method :sort_params diff --git a/app/guards/active_guard.rb b/app/guards/active_guard.rb new file mode 100644 index 0000000..5e9fc1a --- /dev/null +++ b/app/guards/active_guard.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..2d96452 --- /dev/null +++ b/app/models/user.rb @@ -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 diff --git a/app/views/boards/edit.html.erb b/app/views/boards/edit.html.erb new file mode 100644 index 0000000..3c5c2c0 --- /dev/null +++ b/app/views/boards/edit.html.erb @@ -0,0 +1,19 @@ +

<%= t('boards.edit.title') %>

+ +

<%= t('boards.edit.warning') %>

+ +<%= form_with scope: :users, url: board_path, method: :put do |form| %> + + <% @users.each do |user| %> + + + + <% end %> +
+ <%= form.email_field nil, value: user.email %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/clearance_mailer/change_password.html.erb b/app/views/clearance_mailer/change_password.html.erb new file mode 100644 index 0000000..228c8c2 --- /dev/null +++ b/app/views/clearance_mailer/change_password.html.erb @@ -0,0 +1,8 @@ +

<%= t(".opening") %>

+ +

+ <%= link_to t(".link_text", default: "Change my password"), + url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %> +

+ +

<%= t(".closing") %>

diff --git a/app/views/clearance_mailer/change_password.text.erb b/app/views/clearance_mailer/change_password.text.erb new file mode 100644 index 0000000..5a5d2d5 --- /dev/null +++ b/app/views/clearance_mailer/change_password.text.erb @@ -0,0 +1,5 @@ +<%= t(".opening") %> + +<%= url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %> + +<%= t(".closing") %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ae94ca1..5bb562a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,9 +11,19 @@ - + <% if signed_in? %> + + <% end %> <%= yield %> diff --git a/app/views/passwords/create.html.erb b/app/views/passwords/create.html.erb new file mode 100644 index 0000000..1215156 --- /dev/null +++ b/app/views/passwords/create.html.erb @@ -0,0 +1,3 @@ +
+

<%= t(".description") %>

+
diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb new file mode 100644 index 0000000..584e809 --- /dev/null +++ b/app/views/passwords/edit.html.erb @@ -0,0 +1,18 @@ +
+

<%= t(".title") %>

+ +

<%= t(".description") %>

+ + <%= form_for :password_reset, + url: [@user, :password, token: @user.confirmation_token], + html: { method: :put } do |form| %> +
+ <%= form.label :password %> + <%= form.password_field :password %> +
+ +
+ <%= form.submit %> +
+ <% end %> +
diff --git a/app/views/passwords/new.html.erb b/app/views/passwords/new.html.erb new file mode 100644 index 0000000..4c2e12f --- /dev/null +++ b/app/views/passwords/new.html.erb @@ -0,0 +1,16 @@ +
+

<%= t(".title") %>

+ +

<%= t(".description") %>

+ + <%= form_for :password, url: passwords_path do |form| %> +
+ <%= form.label :email %> + <%= form.email_field :email %> +
+ +
+ <%= form.submit %> +
+ <% end %> +
diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb new file mode 100644 index 0000000..fd02376 --- /dev/null +++ b/app/views/sessions/_form.html.erb @@ -0,0 +1,28 @@ +<%= form_for :session, url: session_path do |form| %> + + + + + + + + + + +
+ <%= form.label :email %> + + <%= form.email_field :email %> +
<%= form.label :password %><%= form.password_field :password %>
+ +
+ <%= form.submit %> +
+ + +<% end %> diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..3ba090f --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,5 @@ +
+

<%= t(".title") %>

+ + <%= render partial: '/sessions/form' %> +
diff --git a/config/environments/development.rb b/config/environments/development.rb index 1317d9a..3ac9faf 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -53,6 +53,10 @@ Rails.application.configure do enable_starttls_auto: true, } + config.action_mailer.default_url_options = { + host: ENV['BASE_HOST'], + } + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/initializers/clearance.rb b/config/initializers/clearance.rb new file mode 100644 index 0000000..c4ea458 --- /dev/null +++ b/config/initializers/clearance.rb @@ -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 diff --git a/config/locales/clearance.en.yml b/config/locales/clearance.en.yml new file mode 100644 index 0000000..f302a64 --- /dev/null +++ b/config/locales/clearance.en.yml @@ -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 diff --git a/config/locales/pt.yml b/config/locales/pt.yml index b6fa5e6..04fd018 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -101,3 +101,14 @@ pt: cancelled: subject: "ANSOL - Inscrição cancelada" 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. diff --git a/config/routes.rb b/config/routes.rb index f21fa29..e0380c7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,8 @@ Rails.application.routes.draw do # Defines the root path route ("/") # root "articles#index" + # + root to: redirect('/members') resources :members do member do @@ -16,4 +18,6 @@ Rails.application.routes.draw do get :delete end end + + resource :board, only: [:edit, :update] end diff --git a/db/migrate/20220625203020_create_users.rb b/db/migrate/20220625203020_create_users.rb new file mode 100644 index 0000000..a93b986 --- /dev/null +++ b/db/migrate/20220625203020_create_users.rb @@ -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 diff --git a/db/migrate/20220625210821_add_active_to_user.rb b/db/migrate/20220625210821_add_active_to_user.rb new file mode 100644 index 0000000..2a12143 --- /dev/null +++ b/db/migrate/20220625210821_add_active_to_user.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index acaad17..e66b68e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 enable_extension "pgcrypto" 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" 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 "notifications", "members" end