pages_core 3.13.0 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
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.