katalyst-koi 4.19.0 → 4.20.1

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.
@@ -11,7 +11,7 @@ module Koi
11
11
  super()
12
12
 
13
13
  @resource = resource
14
- @title = title
14
+ @title = title
15
15
 
16
16
  @header = HeaderComponent.new(title: self.title)
17
17
  end
@@ -26,8 +26,8 @@ module Koi
26
26
  def initialize(cell:, url:, default_url:, **)
27
27
  super(**)
28
28
 
29
- @cell = cell
30
- @url = url
29
+ @cell = cell
30
+ @url = url
31
31
  @default_url = default_url
32
32
  end
33
33
 
@@ -71,7 +71,7 @@ module Admin
71
71
  private
72
72
 
73
73
  def set_admin
74
- @admin = Admin::User.with_archived.find(params[:id])
74
+ @admin = Admin::User.with_archived.find(params.expect(:id))
75
75
 
76
76
  request.variant << :self if @admin == current_admin_user
77
77
  end
@@ -52,7 +52,7 @@ module Admin
52
52
  end
53
53
 
54
54
  def destroy
55
- credential = @admin_user.credentials.find(params[:id])
55
+ credential = @admin_user.credentials.find(params.expect(:id))
56
56
  credential.destroy!
57
57
 
58
58
  respond_to do |format|
@@ -68,7 +68,7 @@ module Admin
68
68
  end
69
69
 
70
70
  def set_admin_user
71
- @admin_user = Admin::User.find(params[:admin_user_id])
71
+ @admin_user = Admin::User.find(params.expect(:admin_user_id))
72
72
 
73
73
  if current_admin == @admin_user
74
74
  request.variant = :self
@@ -40,7 +40,7 @@ module Admin
40
40
  end
41
41
 
42
42
  def set_admin_user
43
- @admin_user = Admin::User.find(params[:admin_user_id])
43
+ @admin_user = Admin::User.find(params.expect(:admin_user_id))
44
44
 
45
45
  if current_admin == @admin_user
46
46
  request.variant = :self
@@ -35,9 +35,7 @@ module Admin
35
35
  end
36
36
 
37
37
  def destroy
38
- record_sign_out!(current_admin_user)
39
-
40
- session[:admin_user_id] = nil
38
+ destroy_admin_session!(current_admin_user)
41
39
 
42
40
  redirect_to new_admin_session_path
43
41
  end
@@ -95,9 +93,7 @@ module Admin
95
93
  end
96
94
 
97
95
  def admin_sign_in(admin_user)
98
- record_sign_in!(admin_user)
99
-
100
- session[:admin_user_id] = admin_user.id
96
+ create_admin_session!(admin_user)
101
97
 
102
98
  redirect_to(url_from(params[:redirect].presence) || admin_dashboard_path, status: :see_other)
103
99
  end
@@ -22,9 +22,7 @@ module Admin
22
22
 
23
23
  def update
24
24
  if (@admin_user = Admin::User.find_by_token_for(:password_reset, params[:token]))
25
- record_sign_in!(admin_user)
26
-
27
- session[:admin_user_id] = admin_user.id
25
+ create_admin_session!(admin_user)
28
26
 
29
27
  redirect_to admin_admin_user_path(admin_user), status: :see_other, notice: t("koi.auth.token_consumed")
30
28
  else
@@ -35,7 +33,7 @@ module Admin
35
33
  private
36
34
 
37
35
  def set_admin_user
38
- @admin_user = Admin::User.find(params[:admin_user_id])
36
+ @admin_user = Admin::User.find(params.expect(:admin_user_id))
39
37
  end
40
38
  end
41
39
  end
@@ -56,7 +56,7 @@ module Admin
56
56
  end
57
57
 
58
58
  def set_url_rewrite
59
- @url_rewrite = UrlRewrite.find(params[:id])
59
+ @url_rewrite = UrlRewrite.find(params.expect(:id))
60
60
  end
61
61
 
62
62
  class Collection < Admin::Collection
@@ -55,7 +55,7 @@ module Admin
55
55
 
56
56
  # Use callbacks to share common setup or constraints between actions.
57
57
  def set_well_known
58
- @well_known = ::WellKnown.find(params[:id])
58
+ @well_known = ::WellKnown.find(params.expect(:id))
59
59
  end
60
60
 
61
61
  class Collection < Admin::Collection
@@ -16,7 +16,7 @@ module Koi
16
16
  include HasAttachments
17
17
  include Katalyst::Tables::Backend
18
18
 
19
- if (pagy = "Pagy::Method".safe_constantize)
19
+ if (pagy = "Pagy::Method".safe_constantize)
20
20
  include pagy
21
21
  elsif (pagy = "Pagy::Backend".safe_constantize)
22
22
  # @deprecated Pagy <43
@@ -49,8 +49,12 @@ module Koi
49
49
  def authenticate_local_admin
50
50
  return if admin_signed_in? || !Rails.env.development?
51
51
 
52
- session[:admin_user_id] =
53
- Admin::User.where(email: %W[#{ENV.fetch('USER', nil)}@katalyst.com.au admin@katalyst.com.au]).first&.id
52
+ admin_user = Admin::User.where(email: %W[#{ENV.fetch('USER', nil)}@katalyst.com.au admin@katalyst.com.au]).first
53
+
54
+ return unless admin_user
55
+
56
+ session[:admin_user_id] = admin_user.id
57
+ session[:admin_user_signed_in_at] = Time.current.iso8601(6)
54
58
 
55
59
  flash.delete(:redirect) if (redirect = flash[:redirect])
56
60
 
@@ -3,33 +3,46 @@
3
3
  module Koi
4
4
  module Controller
5
5
  module RecordsAuthentication
6
- def update_last_sign_in(admin_user)
7
- return if admin_user.current_sign_in_at.blank?
6
+ def create_admin_session!(admin_user)
7
+ sign_in_at = Time.current
8
8
 
9
- admin_user.last_sign_in_at = admin_user.current_sign_in_at
10
- admin_user.last_sign_in_ip = admin_user.current_sign_in_ip
11
- end
12
-
13
- def record_sign_in!(admin_user)
14
9
  update_last_sign_in(admin_user)
15
10
 
16
- admin_user.current_sign_in_at = Time.current
11
+ admin_user.current_sign_in_at = sign_in_at
17
12
  admin_user.current_sign_in_ip = request.remote_ip
18
- admin_user.sign_in_count += 1
13
+ admin_user.sign_in_count += 1
19
14
 
20
15
  admin_user.save!
16
+
17
+ session[:admin_user_id] = admin_user.id
18
+ session[:admin_user_signed_in_at] = sign_in_at.iso8601(6)
21
19
  end
22
20
 
23
- def record_sign_out!(admin_user)
21
+ def destroy_admin_session!(admin_user)
22
+ session[:admin_user_id] = nil
23
+ session[:admin_user_signed_in_at] = nil
24
+
24
25
  return unless admin_user
25
26
 
27
+ sign_out_at = Time.current
28
+
26
29
  update_last_sign_in(admin_user)
27
30
 
31
+ admin_user.last_sign_out_at = sign_out_at
28
32
  admin_user.current_sign_in_at = nil
29
33
  admin_user.current_sign_in_ip = nil
30
34
 
31
35
  admin_user.save!
32
36
  end
37
+
38
+ private
39
+
40
+ def update_last_sign_in(admin_user)
41
+ return if admin_user.current_sign_in_at.blank?
42
+
43
+ admin_user.last_sign_in_at = admin_user.current_sign_in_at
44
+ admin_user.last_sign_in_ip = admin_user.current_sign_in_ip
45
+ end
33
46
  end
34
47
  end
35
48
  end
@@ -10,6 +10,6 @@ class WellKnownsController < ApplicationController
10
10
  private
11
11
 
12
12
  def set_well_known
13
- @well_known = WellKnown.find_by!(name: params[:name])
13
+ @well_known = WellKnown.find_by!(name: params.expect(:name))
14
14
  end
15
15
  end
@@ -5,8 +5,8 @@ module Koi
5
5
  # Returns a string representing the number of days ago or from now.
6
6
  # If the date is not 'recent' returns nil.
7
7
  def days_ago_in_words(value)
8
- from_time = value.to_time
9
- to_time = Date.current.to_time
8
+ from_time = value.to_time
9
+ to_time = Date.current.to_time
10
10
  distance_in_days = ((to_time - from_time) / (24.0 * 60.0 * 60.0)).round
11
11
 
12
12
  case distance_in_days
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddLastSignOutAtToAdminUsers < ActiveRecord::Migration[8.0]
4
+ def change
5
+ add_column :admins, :last_sign_out_at, :datetime
6
+ end
7
+ end
@@ -15,7 +15,7 @@ module Koi
15
15
 
16
16
  hook_for :admin_controller, in: :koi, as: :admin, type: :boolean, default: true do |instance, controller|
17
17
  args, opts, config = @_initializer
18
- opts ||= {}
18
+ opts ||= {}
19
19
 
20
20
  # setting model_name so that generators will use the controller_class_path
21
21
  instance.invoke controller, args, { model_name: instance.name, **opts }, config
@@ -42,7 +42,7 @@ module Koi
42
42
  def permitted_params
43
43
  attachments, others = attributes_names.partition { |name| attachments?(name) }
44
44
  params = others.map { |name| ":#{name}" }
45
- params += attachments.map { |name| "#{name}: []" }
45
+ params += attachments.map { |name| "#{name}: []" }
46
46
  params.join(", ")
47
47
  end
48
48
 
@@ -67,9 +67,10 @@ module Koi
67
67
  in_root do
68
68
  if (namespace_match = match_file(route_file, namespace_pattern))
69
69
  base_indent, *, existing_block_indent = namespace_match.captures.compact.map(&:length)
70
- existing_line_pattern = /^ {,#{existing_block_indent}}\S.+\n?/
71
- routing_code = rebase_indentation(routing_code, base_indent + 2).gsub(existing_line_pattern, "")
72
- namespace_pattern = /#{Regexp.escape namespace_match.to_s}/
70
+
71
+ existing_line_pattern = /^ {,#{existing_block_indent}}\S.+\n?/
72
+ routing_code = rebase_indentation(routing_code, base_indent + 2).gsub(existing_line_pattern, "")
73
+ namespace_pattern = /#{Regexp.escape namespace_match.to_s}/
73
74
  end
74
75
 
75
76
  inject_into_file route_file, routing_code, after: namespace_pattern, verbose: true, force: false
@@ -41,7 +41,7 @@ module Koi
41
41
  # @see GOVUKDesignSystemFormBuilder::Builder#govuk_document_field
42
42
  def govuk_document_field(attribute_name, hint: {}, **, &)
43
43
  if hint.is_a?(Hash)
44
- max_size = hint.fetch(:max_size, Koi.config.document_size_limit)
44
+ max_size = hint.fetch(:max_size, Koi.config.document_size_limit)
45
45
  hint[:text] ||= t("helpers.hint.default.document", max_size: @template.number_to_human_size(max_size))
46
46
  end
47
47
 
@@ -52,7 +52,7 @@ module Koi
52
52
  # @see GOVUKDesignSystemFormBuilder::Builder#govuk_image_field
53
53
  def govuk_image_field(attribute_name, hint: {}, **, &)
54
54
  if hint.is_a?(Hash)
55
- max_size = hint.fetch(:max_size, Koi.config.image_size_limit)
55
+ max_size = hint.fetch(:max_size, Koi.config.image_size_limit)
56
56
  hint[:text] ||= t("helpers.hint.default.document", max_size: @template.number_to_human_size(max_size))
57
57
  end
58
58
 
@@ -97,7 +97,7 @@ module Koi
97
97
  {
98
98
  id: field_id(link_errors: true),
99
99
  class: classes,
100
- aria: { describedby: combine_references(hint_id, error_id, supplemental_id) },
100
+ aria: { describedby: combine_references(hint_id, error_id) },
101
101
  }
102
102
  end
103
103
 
@@ -16,10 +16,11 @@ module Koi
16
16
  end
17
17
 
18
18
  def admin_call(env)
19
- request = ActionDispatch::Request.new(env)
20
- session = ActionDispatch::Request::Session.find(request)
19
+ request = ActionDispatch::Request.new(env)
20
+ session = ActionDispatch::Request::Session.find(request)
21
+ authenticated = authenticated?(session)
21
22
 
22
- if requires_authentication?(request) && !authenticated?(session)
23
+ if requires_authentication?(request) && !authenticated
23
24
  # Set the redirection path for returning the user to their requested path after login
24
25
  if request.get?
25
26
  request.flash[:redirect] = request.fullpath
@@ -39,7 +40,34 @@ module Koi
39
40
  end
40
41
 
41
42
  def authenticated?(session)
42
- session[:admin_user_id].present?
43
+ admin_user = Admin::User.find_by(id: session[:admin_user_id])
44
+ unless admin_user
45
+ clear_admin_session(session)
46
+ return false
47
+ end
48
+
49
+ signed_in_at = session_signed_in_at(session)
50
+ if signed_in_at.blank? || session_expired?(admin_user, signed_in_at)
51
+ clear_admin_session(session)
52
+ return false
53
+ end
54
+
55
+ true
56
+ end
57
+
58
+ def session_signed_in_at(session)
59
+ Time.zone.parse(session[:admin_user_signed_in_at].to_s)
60
+ rescue ArgumentError
61
+ nil
62
+ end
63
+
64
+ def session_expired?(admin_user, signed_in_at)
65
+ admin_user.last_sign_out_at.present? && signed_in_at < admin_user.last_sign_out_at
66
+ end
67
+
68
+ def clear_admin_session(session)
69
+ session.delete(:admin_user_id)
70
+ session.delete(:admin_user_signed_in_at)
43
71
  end
44
72
  end
45
73
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-koi
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.19.0
4
+ version: 4.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
@@ -478,6 +478,7 @@ files:
478
478
  - db/migrate/20231211005214_add_status_code_to_url_rewrites.rb
479
479
  - db/migrate/20241214060913_add_otp_secret_to_admin_users.rb
480
480
  - db/migrate/20250204060748_create_well_knowns.rb
481
+ - db/migrate/20260501000000_add_last_sign_out_at_to_admin_users.rb
481
482
  - db/seeds.rb
482
483
  - lib/generators/koi/active_record/active_record_generator.rb
483
484
  - lib/generators/koi/admin/USAGE
@@ -537,7 +538,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
537
538
  - !ruby/object:Gem::Version
538
539
  version: '0'
539
540
  requirements: []
540
- rubygems_version: 4.0.3
541
+ rubygems_version: 4.0.10
541
542
  specification_version: 4
542
543
  summary: Koi CMS admin framework
543
544
  test_files: []