pages_core 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +1 -1
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +27 -4
  6. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -6
  7. data/app/assets/stylesheets/pages_core/admin/components/totp.css +26 -0
  8. data/app/controllers/admin/account_recoveries_controller.rb +87 -0
  9. data/app/controllers/admin/invites_controller.rb +3 -2
  10. data/app/controllers/admin/otp_secrets_controller.rb +45 -0
  11. data/app/controllers/admin/recovery_codes_controller.rb +32 -0
  12. data/app/controllers/admin/sessions_controller.rb +65 -0
  13. data/app/controllers/admin/users_controller.rb +2 -8
  14. data/app/controllers/concerns/pages_core/authentication.rb +12 -10
  15. data/app/controllers/pages_core/admin_controller.rb +1 -1
  16. data/app/helpers/pages_core/admin/admin_helper.rb +11 -0
  17. data/app/javascript/index.ts +0 -2
  18. data/app/mailers/admin_mailer.rb +2 -2
  19. data/app/models/concerns/pages_core/has_otp.rb +27 -0
  20. data/app/models/otp_secret.rb +101 -0
  21. data/app/models/user.rb +15 -37
  22. data/app/policies/user_policy.rb +4 -0
  23. data/app/views/admin/account_recoveries/new.html.erb +22 -0
  24. data/app/views/admin/account_recoveries/show.html.erb +37 -0
  25. data/app/views/admin/invites/show.html.erb +1 -1
  26. data/app/views/admin/otp_secrets/create.html.erb +7 -0
  27. data/app/views/admin/otp_secrets/new.html.erb +60 -0
  28. data/app/views/admin/recovery_codes/_codes.html.erb +14 -0
  29. data/app/views/admin/recovery_codes/create.html.erb +7 -0
  30. data/app/views/admin/recovery_codes/new.html.erb +11 -0
  31. data/app/views/admin/sessions/_otp_form.html.erb +13 -0
  32. data/app/views/admin/sessions/new.html.erb +33 -0
  33. data/app/views/admin/sessions/verify_otp.html.erb +19 -0
  34. data/app/views/admin/users/edit.html.erb +31 -1
  35. data/app/views/admin/users/new.html.erb +1 -1
  36. data/app/views/admin_mailer/account_recovery.text.erb +10 -0
  37. data/app/views/layouts/admin/_header.html.erb +1 -1
  38. data/app/views/layouts/admin/_toast.html.erb +12 -0
  39. data/app/views/layouts/admin.html.erb +1 -1
  40. data/config/locales/en.yml +11 -3
  41. data/config/routes.rb +11 -6
  42. data/db/migrate/20240126160700_add_2fa_fields.rb +22 -0
  43. data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
  44. data/lib/pages_core.rb +6 -0
  45. metadata +51 -9
  46. data/app/controllers/admin/password_resets_controller.rb +0 -85
  47. data/app/controllers/sessions_controller.rb +0 -27
  48. data/app/javascript/controllers/LoginController.ts +0 -32
  49. data/app/models/password_reset_token.rb +0 -34
  50. data/app/views/admin/password_resets/show.html.erb +0 -21
  51. data/app/views/admin/users/login.html.erb +0 -65
  52. data/app/views/admin_mailer/password_reset.text.erb +0 -11
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Admin
4
- class PasswordResetsController < Admin::AdminController
5
- before_action :find_password_reset_token, only: %i[show update]
6
- before_action :check_for_expired_token, only: %i[show update]
7
- before_action :require_authentication, except: %i[create show update]
8
-
9
- layout "admin"
10
-
11
- def show
12
- @user = @password_reset_token.user
13
- end
14
-
15
- def create
16
- @user = find_user_by_email(params[:email])
17
- if @user
18
- @password_reset_token = @user.password_reset_tokens.create
19
- deliver_password_reset(@user, @password_reset_token)
20
- flash[:notice] = t("pages_core.password_reset.sent")
21
- else
22
- flash[:notice] = t("pages_core.password_reset.not_found")
23
- end
24
- redirect_to login_admin_users_url
25
- end
26
-
27
- def update
28
- @user = @password_reset_token.user
29
- if user_params[:password].present? && @user.update(user_params)
30
- @password_reset_token.destroy
31
- authenticate!(@user)
32
- flash[:notice] = t("pages_core.password_reset.changed")
33
- redirect_to login_admin_users_url
34
- else
35
- render action: :show
36
- end
37
- end
38
-
39
- private
40
-
41
- def deliver_password_reset(user, password_reset)
42
- AdminMailer.password_reset(
43
- user,
44
- admin_password_reset_with_token_url(
45
- password_reset, password_reset.token
46
- )
47
- ).deliver_later
48
- end
49
-
50
- def find_user_by_email(email)
51
- return unless email
52
-
53
- User.find_by_email(params[:email])
54
- end
55
-
56
- def user_params
57
- params.require(:user).permit(:password, :confirm_password)
58
- end
59
-
60
- def valid_token?(reset)
61
- reset && secure_compare(reset.token, params[:token])
62
- end
63
-
64
- def find_password_reset_token
65
- @password_reset_token = begin
66
- PasswordResetToken.find(params[:id])
67
- rescue ActiveRecord::RecordNotFound
68
- nil
69
- end
70
-
71
- return if valid_token?(@password_reset_token)
72
-
73
- flash[:notice] = t("pages_core.password_reset.invalid_request")
74
- redirect_to(login_admin_users_url) && return
75
- end
76
-
77
- def check_for_expired_token
78
- return unless @password_reset_token.expired?
79
-
80
- @password_reset_token.destroy
81
- flash[:notice] = t("pages_core.password_reset.expired")
82
- redirect_to(login_admin_users_url)
83
- end
84
- end
85
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class SessionsController < ApplicationController
4
- def create
5
- user = find_user(params[:email], params[:password])
6
- authenticate!(user) if user
7
-
8
- if logged_in?
9
- redirect_to admin_default_url
10
- else
11
- flash[:notice] = t("pages_core.invalid_login")
12
- redirect_to login_admin_users_url
13
- end
14
- end
15
-
16
- def destroy
17
- flash[:notice] = t("pages_core.logged_out")
18
- deauthenticate!
19
- redirect_to login_admin_users_url
20
- end
21
-
22
- protected
23
-
24
- def find_user(email, password)
25
- User.authenticate(email, password:) if email && password
26
- end
27
- end
@@ -1,32 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- export default class LoginController extends Controller {
4
- declare readonly tabTargets: HTMLDivElement[];
5
-
6
- static get targets() {
7
- return ["tab"];
8
- }
9
-
10
- connect() {
11
- if (this.tabTargets.length > 0) {
12
- this.showTab(this.tabTargets[0].dataset.tab);
13
- }
14
- }
15
-
16
- changeTab(evt: Event) {
17
- evt.preventDefault();
18
- if ("dataset" in evt.target && "tab" in evt.target.dataset) {
19
- this.showTab(evt.target.dataset.tab);
20
- }
21
- }
22
-
23
- showTab(tab: string) {
24
- this.tabTargets.forEach((t) => {
25
- if (t.dataset.tab == tab) {
26
- t.classList.remove("hidden");
27
- } else {
28
- t.classList.add("hidden");
29
- }
30
- });
31
- }
32
- }
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class PasswordResetToken < ApplicationRecord
4
- belongs_to :user
5
- before_create :ensure_token
6
- before_create :ensure_expiration
7
-
8
- scope :active, -> { where("expires_at >= ?", Time.now.utc) }
9
- scope :expired, -> { where("expires_at < ?", Time.now.utc) }
10
-
11
- class << self
12
- def default_expiration
13
- 24.hours
14
- end
15
-
16
- def expire!
17
- expired.delete_all
18
- end
19
- end
20
-
21
- def expired?
22
- expires_at < Time.now.utc
23
- end
24
-
25
- private
26
-
27
- def ensure_expiration
28
- self.expires_at ||= Time.now.utc + self.class.default_expiration
29
- end
30
-
31
- def ensure_token
32
- self.token ||= SecureRandom.hex(32)
33
- end
34
- end
@@ -1,21 +0,0 @@
1
- <% content_for :page_title, "Reset password" %>
2
- <% content_for :page_description, "Please choose a new password to proceed" %>
3
- <% content_for :body_class, "login" %>
4
-
5
- <div class="login-form">
6
- <%= form_for(@user,
7
- url: admin_password_reset_path(@password_reset_token, token: @password_reset_token.token),
8
- builder: PagesCore::Admin::FormBuilder,
9
- class: 'form') do |f| %>
10
- <%= f.labelled_password_field(:password,
11
- autocomplete: "new-password") %>
12
- <%= f.labelled_password_field(:confirm_password,
13
- autocomplete: "new-password") %>
14
- <p>
15
- <button type="submit">
16
- Change Password
17
- </button>
18
- or <%= link_to "Return to login screen", login_admin_users_path %>
19
- </p>
20
- <% end %>
21
- </div>
@@ -1,65 +0,0 @@
1
- <% content_for :page_title, "Sign in" %>
2
- <% content_for(:page_description,
3
- "Please enter your email address and password to sign in") %>
4
- <% content_for :body_class, "login" %>
5
-
6
- <% content_for :sidebar do %>
7
- <h2>Please note</h2>
8
- <p>
9
- Please contact support if you experience problems logging in or using Pages.
10
- </p>
11
- <% end %>
12
-
13
- <div class="login-form"
14
- data-controller="login">
15
- <div class="login-tab password"
16
- data-login-target="tab"
17
- data-tab="password">
18
- <%= form_tag session_path do %>
19
- <p>
20
- <label>Email address</label>
21
- <%= text_field_tag(:email, "", autocomplete: "email") %>
22
- </p>
23
- <p>
24
- <label>Password</label>
25
- <%= password_field_tag(:password, "", autocomplete: "current-password") %>
26
- </p>
27
- <p>
28
- <button type="submit">Sign in</button>
29
- </p>
30
- <ul>
31
- <li>
32
- <%= link_to("<b>Help!</b> I forgot my password!".html_safe,
33
- login_admin_users_path,
34
- data: {
35
- action: "click->login#changeTab",
36
- tab: "password-reset"
37
- }) %>
38
- </li>
39
- </ul>
40
- <% end %>
41
- </div>
42
-
43
- <div class="login-tab password-reset"
44
- data-login-target="tab"
45
- data-tab="password-reset">
46
- <%= form_tag admin_password_resets_path do %>
47
- <h2>
48
- Forgot your password?
49
- </h2>
50
- <p>
51
- Don't worry, it happens.
52
- Enter your email address below,
53
- and we'll send you a link where you can reset your password.
54
- </p>
55
- <p>
56
- <%= text_field_tag(:email, "", autocomplete: "email") %>
57
- </p>
58
- <p>
59
- <button type="submit">
60
- Send
61
- </button>
62
- </p>
63
- <% end %>
64
- </div>
65
- </div>
@@ -1,11 +0,0 @@
1
- Hi, <%= @user.name %>!
2
-
3
- We've received a request to reset the password for your account on <%= PagesCore.config(:site_name) %>.
4
-
5
- If you want to reset your password, please click the following link:
6
-
7
- <%= @url %>
8
-
9
- This will take you to a web page where you can set a new password of your choosing. The link will expire in 24 hours.
10
-
11
- If you do not want to change your password, please ignore this email.