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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/assets/builds/pages_core/admin-dist.js +1 -1
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +27 -4
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -6
- data/app/assets/stylesheets/pages_core/admin/components/totp.css +26 -0
- data/app/controllers/admin/account_recoveries_controller.rb +87 -0
- data/app/controllers/admin/invites_controller.rb +3 -2
- data/app/controllers/admin/otp_secrets_controller.rb +45 -0
- data/app/controllers/admin/recovery_codes_controller.rb +32 -0
- data/app/controllers/admin/sessions_controller.rb +65 -0
- data/app/controllers/admin/users_controller.rb +2 -8
- data/app/controllers/concerns/pages_core/authentication.rb +12 -10
- data/app/controllers/pages_core/admin_controller.rb +1 -1
- data/app/helpers/pages_core/admin/admin_helper.rb +11 -0
- data/app/javascript/index.ts +0 -2
- data/app/mailers/admin_mailer.rb +2 -2
- data/app/models/concerns/pages_core/has_otp.rb +27 -0
- data/app/models/otp_secret.rb +101 -0
- data/app/models/user.rb +15 -37
- data/app/policies/user_policy.rb +4 -0
- data/app/views/admin/account_recoveries/new.html.erb +22 -0
- data/app/views/admin/account_recoveries/show.html.erb +37 -0
- data/app/views/admin/invites/show.html.erb +1 -1
- data/app/views/admin/otp_secrets/create.html.erb +7 -0
- data/app/views/admin/otp_secrets/new.html.erb +60 -0
- data/app/views/admin/recovery_codes/_codes.html.erb +14 -0
- data/app/views/admin/recovery_codes/create.html.erb +7 -0
- data/app/views/admin/recovery_codes/new.html.erb +11 -0
- data/app/views/admin/sessions/_otp_form.html.erb +13 -0
- data/app/views/admin/sessions/new.html.erb +33 -0
- data/app/views/admin/sessions/verify_otp.html.erb +19 -0
- data/app/views/admin/users/edit.html.erb +31 -1
- data/app/views/admin/users/new.html.erb +1 -1
- data/app/views/admin_mailer/account_recovery.text.erb +10 -0
- data/app/views/layouts/admin/_header.html.erb +1 -1
- data/app/views/layouts/admin/_toast.html.erb +12 -0
- data/app/views/layouts/admin.html.erb +1 -1
- data/config/locales/en.yml +11 -3
- data/config/routes.rb +11 -6
- data/db/migrate/20240126160700_add_2fa_fields.rb +22 -0
- data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
- data/lib/pages_core.rb +6 -0
- metadata +51 -9
- data/app/controllers/admin/password_resets_controller.rb +0 -85
- data/app/controllers/sessions_controller.rb +0 -27
- data/app/javascript/controllers/LoginController.ts +0 -32
- data/app/models/password_reset_token.rb +0 -34
- data/app/views/admin/password_resets/show.html.erb +0 -21
- data/app/views/admin/users/login.html.erb +0 -65
- 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.
|