securial 0.4.2 → 0.5.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/README.md +56 -11
- data/app/controllers/concerns/securial/identity.rb +3 -3
- data/app/models/securial/session.rb +2 -2
- data/app/views/securial/roles/_securial_role.json.jbuilder +1 -2
- data/app/views/securial/sessions/_session.json.jbuilder +1 -2
- data/app/views/securial/shared/_timestamps.json.jbuilder +8 -0
- data/app/views/securial/users/_securial_user.json.jbuilder +1 -2
- data/lib/generators/securial/install/templates/securial_initializer.erb +11 -0
- data/lib/generators/securial/jbuilder/templates/_resource.json.erb +1 -2
- data/lib/securial/config/_index.rb +3 -0
- data/lib/securial/config/configuration.rb +42 -0
- data/lib/securial/config/errors.rb +19 -0
- data/lib/securial/config/validation.rb +179 -0
- data/lib/securial/engine.rb +10 -11
- data/lib/securial/helpers/_index.rb +2 -0
- data/lib/securial/helpers/regex_helper.rb +7 -7
- data/lib/securial/inspectors/_index.rb +1 -0
- data/lib/securial/inspectors/route_inspector.rb +52 -0
- data/lib/securial/logger.rb +2 -2
- data/lib/securial/sessions/_index.rb +2 -0
- data/lib/securial/sessions/errors.rb +15 -0
- data/lib/securial/sessions/session_encoder.rb +56 -0
- data/lib/securial/version.rb +1 -1
- data/lib/securial.rb +1 -71
- metadata +14 -11
- data/app/views/securial/passwords/_password.json.jbuilder +0 -2
- data/app/views/securial/passwords/index.json.jbuilder +0 -1
- data/app/views/securial/passwords/show.json.jbuilder +0 -1
- data/lib/securial/configuration.rb +0 -35
- data/lib/securial/errors/config_errors.rb +0 -12
- data/lib/securial/errors/session_errors.rb +0 -6
- data/lib/securial/helpers/auth_helper.rb +0 -46
- data/lib/securial/route_inspector.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a67cb5167555d544a8787d5731542215aa5d43a50955cbb4a0a929d9df39ed08
|
4
|
+
data.tar.gz: 6ee38fc5624167b7cf6d9307dc07224e0762a163145df864247350c523b09a5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a85b1cd94066087eab59f7c883e4b822a16e91e03a2df80e4100f7a81cc25712427d35719bb90dbf509c028081bbd215436d097e2056e054e13433fc1b97d9ef
|
7
|
+
data.tar.gz: a39f8dfd001c239fc2f5612e9777f587d688c93de8070656fe7e8fa38a3610463ce18502bef35b12d75c45eb9ee5f27608f8a25d2dc00aa9394f2f8dd5a7bc38
|
data/README.md
CHANGED
@@ -19,15 +19,55 @@
|
|
19
19
|
- ✅ Clean, JSON-based API responses
|
20
20
|
- ✅ Database-agnostic support
|
21
21
|
|
22
|
-
Next, mount the engine in `config/routes.rb`:
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
### 🚀 Why Choose Securial?
|
24
|
+
|
25
|
+
Securial isn't just another auth library — it's designed to give you control, flexibility, and peace of mind when building secure Rails APIs.
|
26
|
+
|
27
|
+
**🔧 Built for Developers**
|
28
|
+
- Easy to mount and extend using familiar Rails conventions.
|
29
|
+
- Fully customizable controllers, serializers, and logic — no more black-box auth.
|
30
|
+
|
31
|
+
**🧩 Modular by Design**
|
32
|
+
- Use only the components you need: JWT, sessions, API tokens — or all of them together.
|
33
|
+
- Clean separation of concerns makes testing and debugging simpler.
|
34
|
+
|
35
|
+
**⚡ API-First Approach**
|
36
|
+
- JSON-only responses make Securial ideal for frontend frameworks and mobile apps.
|
37
|
+
- No HTML views or form helpers — just clean endpoints that work out of the box.
|
38
|
+
|
39
|
+
**🛡️ Secure by Default**
|
40
|
+
- Uses industry best practices for token management and access control.
|
41
|
+
- No reliance on client-side sessions or cookies.
|
42
|
+
|
43
|
+
**📦 Lightweight, Database-Agnostic**
|
44
|
+
- No assumptions about your schema or ORM — works with any relational database.
|
45
|
+
- Minimal dependencies, fast to integrate.
|
46
|
+
|
47
|
+
**🌱 Ready to Grow With You**
|
48
|
+
- Start small with basic JWT auth, scale to multi-token API clients, admin scopes, or full RBAC.
|
49
|
+
- Perfect for startups, side projects, and production APIs alike.
|
29
50
|
|
30
|
-
|
51
|
+
## 🚀 Installation
|
52
|
+
|
53
|
+
Securial can be installed on an existing Rails application or use the `securial new app_name` command to create a new Securial-ready Rails app.
|
54
|
+
|
55
|
+
### Installation on an existing Rails app:
|
56
|
+
|
57
|
+
To add Securial to an existing Rails app:
|
58
|
+
|
59
|
+
- Add `gem "securial"` to your GemFile
|
60
|
+
- Run `bundle install`
|
61
|
+
- Run `rails generate securial:install`
|
62
|
+
- Mount the Securial engine in your Rails application `config/routes.rb` file:
|
63
|
+
```ruby
|
64
|
+
Rails.application.routes.draw do
|
65
|
+
mount Securial::Engine => "/securial"
|
66
|
+
end
|
67
|
+
```
|
68
|
+
- Run the migrations by running the command: `rails db:migrate`
|
69
|
+
|
70
|
+
💡 Full installation steps are available in the [Wiki › Installation](https://github.com/AlyBadawy/Securial/wiki/Installation).
|
31
71
|
|
32
72
|
## ⚙️ Configuration
|
33
73
|
|
@@ -49,24 +89,29 @@ After installation and mounting, **Securial** exposes endpoints like:
|
|
49
89
|
|
50
90
|
Full details, including authentication flows and protected routes, are available in the [Wiki › Authentication module docs](https://github.com/AlyBadawy/Securial/wiki/Authentication).
|
51
91
|
|
52
|
-
🧩 Modules
|
92
|
+
## 🧩 Modules
|
53
93
|
|
54
94
|
**Securial** is organized into modular components including:
|
55
95
|
|
56
96
|
- Authentication
|
57
97
|
- User Management
|
58
98
|
- Generators
|
59
|
-
-
|
99
|
+
- Identity concern
|
100
|
+
- Configuration
|
60
101
|
|
61
102
|
Explore all modules in the [Wiki](https://github.com/AlyBadawy/Securial/wiki).
|
62
103
|
|
63
104
|
## 🛠 Development & Testing
|
64
105
|
|
106
|
+
- Clone the repo on your computer
|
107
|
+
- Run `bundle install`
|
108
|
+
- Start coding right away 🏃♂️
|
109
|
+
|
110
|
+
|
65
111
|
To run the test suite:
|
66
112
|
|
67
113
|
```bash
|
68
|
-
$
|
69
|
-
$ bundle exec rspec
|
114
|
+
$ bin/test
|
70
115
|
```
|
71
116
|
|
72
117
|
View the coverage report:
|
@@ -32,9 +32,9 @@ module Securial
|
|
32
32
|
if auth_header.present? && auth_header.start_with?("Bearer ")
|
33
33
|
token = auth_header.split(" ").last
|
34
34
|
begin
|
35
|
-
decoded_token =
|
35
|
+
decoded_token = Securial::Sessions::SessionEncoder.decode(token)
|
36
36
|
Current.session = Session.find_by!(id: decoded_token["jti"], revoked: false)
|
37
|
-
rescue
|
37
|
+
rescue Securial::Sessions::Errors::SessionDecodeError, ActiveRecord::RecordNotFound => e
|
38
38
|
render status: :unauthorized, json: { error: "Invalid token: #{e.message}" } and return
|
39
39
|
end
|
40
40
|
else
|
@@ -55,7 +55,7 @@ module Securial
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def create_jwt_for_current_session
|
58
|
-
|
58
|
+
Securial::Sessions::SessionEncoder.encode(Current.session)
|
59
59
|
end
|
60
60
|
|
61
61
|
def internal_rails_request?
|
@@ -15,8 +15,8 @@ module Securial
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def refresh!
|
18
|
-
raise Securial::
|
19
|
-
raise Securial::
|
18
|
+
raise Securial::Sessions::Errors::SessionRevokedError, "Session is revoked" if revoked
|
19
|
+
raise Securial::Sessions::Errors::SessionExpiredError, "Session is expired" if refresh_token_expires_at < Time.current
|
20
20
|
|
21
21
|
update!(refresh_token: SecureRandom.hex(64),
|
22
22
|
refresh_count: self.refresh_count + 1,
|
@@ -3,7 +3,6 @@ json.id securial_role.id
|
|
3
3
|
json.role_name securial_role.role_name
|
4
4
|
json.hide_from_profile securial_role.hide_from_profile
|
5
5
|
|
6
|
-
json.
|
7
|
-
json.updated_at securial_role.updated_at
|
6
|
+
json.partial! "securial/shared/timestamps", record: securial_role
|
8
7
|
|
9
8
|
json.url securial.roles_url(securial_role, format: :json)
|
@@ -9,7 +9,6 @@ json.refresh_token_expires_at securial_session.refresh_token_expires_at
|
|
9
9
|
json.revoked securial_session.revoked
|
10
10
|
json.user_id securial_session.user_id
|
11
11
|
|
12
|
-
json.
|
13
|
-
json.updated_at securial_session.updated_at
|
12
|
+
json.partial! "securial/shared/timestamps", record: securial_session
|
14
13
|
|
15
14
|
json.url "No URL available for this action"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
if Securial.configuration.timestamps_in_response == :all ||
|
2
|
+
(
|
3
|
+
Securial.configuration.timestamps_in_response == :admins_only &&
|
4
|
+
current_user&.admin?
|
5
|
+
)
|
6
|
+
json.created_at record.created_at if record.respond_to?(:created_at)
|
7
|
+
json.updated_at record.updated_at if record.respond_to?(:updated_at)
|
8
|
+
end
|
@@ -8,7 +8,6 @@ json.bio securial_user.bio
|
|
8
8
|
|
9
9
|
json.roles securial_user.roles, partial: "securial/roles/securial_role", as: :securial_role
|
10
10
|
|
11
|
-
json.
|
12
|
-
json.updated_at securial_user.updated_at
|
11
|
+
json.partial! "securial/shared/timestamps", record: securial_user
|
13
12
|
|
14
13
|
json.url securial.user_url(securial_user, format: :json)
|
@@ -106,4 +106,15 @@ Securial.configure do |config|
|
|
106
106
|
# that they cannot be tampered with. It is important to keep this
|
107
107
|
# secret secure and not share it with anyone.
|
108
108
|
config.reset_password_token_secret = "reset_secret"
|
109
|
+
|
110
|
+
### Timestamp Configuration
|
111
|
+
## Set whether to use timestamps in the json responses.
|
112
|
+
# The options are:
|
113
|
+
# :none - no timestamps will be included in the json responses.
|
114
|
+
# :admins_only - the created_at and updated_at timestamps will be included
|
115
|
+
# for admin users. This is useful for keeping the json responses clean
|
116
|
+
# for regular users while still providing timestamps for admin users.
|
117
|
+
# :all - the created_at and updated_at timestamps will be included
|
118
|
+
# for all users. This is useful for debugging and development purposes.
|
119
|
+
config.timestamps_in_response = Rails.env.production? ? :admins_only : :all
|
109
120
|
end
|
@@ -4,7 +4,6 @@ json.id <%= singular_table_name %>.id
|
|
4
4
|
json.<%= attr %> <%= singular_table_name %>.<%= attr %>
|
5
5
|
<% end -%>
|
6
6
|
|
7
|
-
json.
|
8
|
-
json.updated_at <%= singular_table_name %>.updated_at
|
7
|
+
json.partial! "securial/shared/timestamps", record: <%= singular_table_name %>
|
9
8
|
|
10
9
|
json.url securial.<%= name.pluralize.downcase %>_url(<%= singular_table_name %>, format: :json)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Securial
|
2
|
+
module Config
|
3
|
+
VALID_SESSION_ENCRYPTION_ALGORITHMS = [:hs256, :hs384, :hs512].freeze
|
4
|
+
VALID_TIMESTAMP_OPTIONS = [:all, :admins_only, :none].freeze
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :log_to_file, :log_to_stdout
|
8
|
+
attr_accessor :log_file_level, :log_stdout_level
|
9
|
+
attr_accessor :admin_role
|
10
|
+
attr_accessor :session_expiration_duration
|
11
|
+
attr_accessor :session_secret, :session_algorithm
|
12
|
+
attr_accessor :mailer_sender
|
13
|
+
attr_accessor :password_reset_email_subject
|
14
|
+
attr_accessor :password_min_length, :password_max_length
|
15
|
+
attr_accessor :password_complexity
|
16
|
+
attr_accessor :password_expires_in
|
17
|
+
attr_accessor :reset_password_token_expires_in
|
18
|
+
attr_accessor :reset_password_token_secret
|
19
|
+
attr_accessor :timestamps_in_response
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@log_to_file = !Rails.env.test?
|
23
|
+
@log_to_stdout = !Rails.env.test?
|
24
|
+
@log_file_level = :info
|
25
|
+
@log_stdout_level = :info
|
26
|
+
@admin_role = :admin
|
27
|
+
@session_expiration_duration = 3.minutes
|
28
|
+
@session_secret = "secret"
|
29
|
+
@session_algorithm = :hs256
|
30
|
+
@mailer_sender = "no-reply@example.com"
|
31
|
+
@password_reset_email_subject = "SECURIAL: Password Reset Instructions"
|
32
|
+
@password_min_length = 8
|
33
|
+
@password_max_length = 128
|
34
|
+
@password_complexity = Securial::RegexHelper::PASSWORD_REGEX
|
35
|
+
@password_expires_in = 90.days
|
36
|
+
@reset_password_token_expires_in = 2.hours
|
37
|
+
@reset_password_token_secret = "reset_secret"
|
38
|
+
@timestamps_in_response = :all
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Securial
|
2
|
+
module Config
|
3
|
+
module Errors
|
4
|
+
class BaseConfigError < StandardError
|
5
|
+
def backtrace; []; end
|
6
|
+
end
|
7
|
+
|
8
|
+
class ConfigAdminRoleError < BaseConfigError; end
|
9
|
+
|
10
|
+
class ConfigSessionExpirationDurationError < BaseConfigError; end
|
11
|
+
class ConfigSessionAlgorithmError < BaseConfigError; end
|
12
|
+
class ConfigSessionSecretError < BaseConfigError; end
|
13
|
+
|
14
|
+
class ConfigMailerSenderError < BaseConfigError; end
|
15
|
+
class ConfigPasswordError < BaseConfigError; end
|
16
|
+
class ConfigTimestampsInResponseError < BaseConfigError; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "securial/logger"
|
2
|
+
|
3
|
+
module Securial
|
4
|
+
module Config
|
5
|
+
module Validation
|
6
|
+
class << self
|
7
|
+
def validate_all!(config)
|
8
|
+
validate_admin_role!(config)
|
9
|
+
validate_session_config!(config)
|
10
|
+
validate_mailer_sender!(config)
|
11
|
+
validate_password_config!(config)
|
12
|
+
validate_timestamps_in_response!(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_admin_role!(config)
|
18
|
+
if config.admin_role.nil? || config.admin_role.to_s.strip.empty?
|
19
|
+
error_message = "Admin role is not set."
|
20
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
21
|
+
raise Securial::Config::Errors::ConfigAdminRoleError, error_message
|
22
|
+
end
|
23
|
+
|
24
|
+
unless config.admin_role.is_a?(Symbol) || config.admin_role.is_a?(String)
|
25
|
+
error_message = "Admin role must be a Symbol or String."
|
26
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
27
|
+
raise Securial::Config::Errors::ConfigAdminRoleError, error_message
|
28
|
+
end
|
29
|
+
|
30
|
+
if config.admin_role.to_s.pluralize.downcase == "accounts"
|
31
|
+
error_message = "The admin role cannot be 'account' or 'accounts' as it conflicts with the default routes."
|
32
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
33
|
+
raise Securial::Config::Errors::ConfigAdminRoleError, error_message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_session_config!(config)
|
38
|
+
validate_session_expiry_duration!(config)
|
39
|
+
validate_session_algorithm!(config)
|
40
|
+
validate_session_secret!(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_session_expiry_duration!(config)
|
44
|
+
if config.session_expiration_duration.nil?
|
45
|
+
error_message = "Session expiration duration is not set."
|
46
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
47
|
+
raise Securial::Config::Errors::ConfigSessionExpirationDurationError, error_message
|
48
|
+
end
|
49
|
+
if config.session_expiration_duration.class != ActiveSupport::Duration
|
50
|
+
error_message = "Session expiration duration must be an ActiveSupport::Duration."
|
51
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
52
|
+
raise Securial::Config::Errors::ConfigSessionExpirationDurationError, error_message
|
53
|
+
end
|
54
|
+
if config.session_expiration_duration <= 0
|
55
|
+
Securial::ENGINE_LOGGER.error("Session expiration duration must be greater than 0.")
|
56
|
+
raise Securial::Config::Errors::ConfigSessionExpirationDurationError, "Session expiration duration must be greater than 0."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_session_algorithm!(config)
|
61
|
+
if config.session_algorithm.blank?
|
62
|
+
error_message = "Session algorithm is not set."
|
63
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
64
|
+
raise Securial::Config::Errors::ConfigSessionAlgorithmError, error_message
|
65
|
+
end
|
66
|
+
unless config.session_algorithm.is_a?(Symbol)
|
67
|
+
error_message = "Session algorithm must be a Symbol."
|
68
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
69
|
+
raise Securial::Config::Errors::ConfigSessionAlgorithmError, error_message
|
70
|
+
end
|
71
|
+
valid_algorithms = Securial::Config::VALID_SESSION_ENCRYPTION_ALGORITHMS
|
72
|
+
unless valid_algorithms.include?(config.session_algorithm)
|
73
|
+
error_message = "Invalid session algorithm. Valid options are: #{valid_algorithms.map(&:inspect).join(', ')}."
|
74
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
75
|
+
raise Securial::Config::Errors::ConfigSessionAlgorithmError, error_message
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate_session_secret!(config)
|
80
|
+
if config.session_secret.blank?
|
81
|
+
error_message = "Session secret is not set."
|
82
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
83
|
+
raise Securial::Config::Errors::ConfigSessionSecretError, error_message
|
84
|
+
end
|
85
|
+
unless config.session_secret.is_a?(String)
|
86
|
+
error_message = "Session secret must be a String."
|
87
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
88
|
+
raise Securial::Config::Errors::ConfigSessionSecretError, error_message
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_mailer_sender!(config)
|
93
|
+
if config.mailer_sender.blank?
|
94
|
+
error_message = "Mailer sender is not set."
|
95
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
96
|
+
raise Securial::Config::Errors::ConfigMailerSenderError, error_message
|
97
|
+
end
|
98
|
+
if config.mailer_sender !~ URI::MailTo::EMAIL_REGEXP
|
99
|
+
error_message = "Mailer sender is not a valid email address."
|
100
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
101
|
+
raise Securial::Config::Errors::ConfigMailerSenderError, error_message
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate_password_config!(config)
|
106
|
+
validate_password_reset_subject!(config)
|
107
|
+
validate_password_min_max_length!(config)
|
108
|
+
validate_password_complexity!(config)
|
109
|
+
validate_password_expiration!(config)
|
110
|
+
validate_password_reset_token!(config)
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate_password_reset_token!(config)
|
114
|
+
if config.reset_password_token_secret.blank?
|
115
|
+
error_message = "Reset password token secret is not set."
|
116
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
117
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
118
|
+
end
|
119
|
+
unless config.reset_password_token_secret.is_a?(String)
|
120
|
+
error_message = "Reset password token secret must be a String."
|
121
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
122
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate_password_reset_subject!(config)
|
127
|
+
if config.password_reset_email_subject.blank?
|
128
|
+
error_message = "Password reset email subject is not set."
|
129
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
130
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
131
|
+
end
|
132
|
+
unless config.password_reset_email_subject.is_a?(String)
|
133
|
+
error_message = "Password reset email subject must be a String."
|
134
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
135
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def validate_password_min_max_length!(config)
|
140
|
+
unless config.password_min_length.is_a?(Integer) && config.password_min_length > 0
|
141
|
+
error_message = "Password minimum length must be a positive integer."
|
142
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
143
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
144
|
+
end
|
145
|
+
unless config.password_max_length.is_a?(Integer) && config.password_max_length >= config.password_min_length
|
146
|
+
error_message = "Password maximum length must be an integer greater than or equal to the minimum length."
|
147
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
148
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate_password_complexity!(config)
|
153
|
+
if config.password_complexity.nil? || !config.password_complexity.is_a?(Regexp)
|
154
|
+
error_message = "Password complexity regex is not set or is not a valid Regexp."
|
155
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
156
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def validate_password_expiration!(config)
|
161
|
+
if config.password_expires_in.nil? || !config.password_expires_in.is_a?(ActiveSupport::Duration) || config.password_expires_in <= 0
|
162
|
+
error_message = "Password expiration duration is not set or is not a valid ActiveSupport::Duration."
|
163
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
164
|
+
raise Securial::Config::Errors::ConfigPasswordError, error_message
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def validate_timestamps_in_response!(config)
|
169
|
+
valid_options = Securial::Config::VALID_TIMESTAMP_OPTIONS
|
170
|
+
unless valid_options.include?(config.timestamps_in_response)
|
171
|
+
error_message = "Invalid timestamps_in_response option. Valid options are: #{valid_options.map(&:inspect).join(', ')}."
|
172
|
+
Securial::ENGINE_LOGGER.error(error_message)
|
173
|
+
raise Securial::Config::Errors::ConfigTimestampsInResponseError, error_message
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/securial/engine.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
require_relative "./logger"
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require_relative "./
|
6
|
-
|
7
|
-
require_relative "./helpers/auth_helper"
|
8
|
-
require_relative "./helpers/normalizing_helper"
|
9
|
-
require_relative "./helpers/regex_helper"
|
10
|
-
|
11
|
-
require_relative "./route_inspector"
|
2
|
+
require_relative "./config/_index"
|
3
|
+
require_relative "./helpers/_index"
|
4
|
+
require_relative "./sessions/_index"
|
5
|
+
require_relative "./inspectors/_index"
|
12
6
|
|
13
7
|
require_relative "./middleware/request_logger_tag"
|
14
8
|
require "jwt"
|
@@ -31,7 +25,12 @@ module Securial
|
|
31
25
|
end
|
32
26
|
|
33
27
|
initializer "securial.engine_initialized" do |app|
|
34
|
-
Securial::ENGINE_LOGGER.info("[Securial] Engine
|
28
|
+
Securial::ENGINE_LOGGER.info("[Securial] Initializing Engine... Host app: #{app.class.name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer "securial.config" do
|
32
|
+
Securial::ENGINE_LOGGER.info("[Securial] Validating configuration in `config/initializers/securial.rb`...")
|
33
|
+
Securial::Config::Validation.validate_all!(Securial.configuration)
|
35
34
|
end
|
36
35
|
|
37
36
|
initializer "securial.factories", after: "factory_bot.set_factory_paths" do
|
@@ -4,14 +4,14 @@ module Securial
|
|
4
4
|
USERNAME_REGEX = /\A(?![0-9])[a-zA-Z](?:[a-zA-Z0-9]|[._](?![._]))*[a-zA-Z0-9]\z/
|
5
5
|
PASSWORD_REGEX = %r{\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])[a-zA-Z].*\z}
|
6
6
|
|
7
|
-
|
7
|
+
class << self
|
8
|
+
def valid_email?(email)
|
9
|
+
email.match?(EMAIL_REGEX)
|
10
|
+
end
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def valid_username?(username)
|
14
|
-
username.match?(USERNAME_REGEX)
|
12
|
+
def valid_username?(username)
|
13
|
+
username.match?(USERNAME_REGEX)
|
14
|
+
end
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "route_inspector"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Securial
|
2
|
+
module Inspectors
|
3
|
+
module RouteInspector
|
4
|
+
def self.print_routes(controller: nil)
|
5
|
+
filtered = Securial::Engine.routes.routes.select do |r|
|
6
|
+
ctrl = r.defaults[:controller]
|
7
|
+
controller.nil? || ctrl == "securial/#{controller}"
|
8
|
+
end
|
9
|
+
|
10
|
+
print_headers(filtered, controller)
|
11
|
+
print_details(filtered, controller)
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
private
|
17
|
+
|
18
|
+
# rubocop:disable Rails/Output
|
19
|
+
def print_headers(filtered, controller)
|
20
|
+
Securial::ENGINE_LOGGER.debug "Securial engine routes:"
|
21
|
+
Securial::ENGINE_LOGGER.debug "Total routes: #{filtered.size}"
|
22
|
+
Securial::ENGINE_LOGGER.debug "Filtered by controller: #{controller}" if controller
|
23
|
+
Securial::ENGINE_LOGGER.debug "Filtered routes: #{filtered.size}" if controller
|
24
|
+
Securial::ENGINE_LOGGER.debug "-" * 120
|
25
|
+
Securial::ENGINE_LOGGER.debug "#{'Verb'.ljust(8)} #{'Path'.ljust(45)} #{'Controller#Action'.ljust(40)} Name"
|
26
|
+
Securial::ENGINE_LOGGER.debug "-" * 120
|
27
|
+
end
|
28
|
+
|
29
|
+
def print_details(filtered, controller) # rubocop:disable Rails/Output
|
30
|
+
if filtered.empty?
|
31
|
+
if controller
|
32
|
+
Securial::ENGINE_LOGGER.debug "No routes found for controller: #{controller}"
|
33
|
+
else
|
34
|
+
Securial::ENGINE_LOGGER.debug "No routes found for Securial engine"
|
35
|
+
end
|
36
|
+
Securial::ENGINE_LOGGER.debug "-" * 120
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
Securial::ENGINE_LOGGER.debug filtered.map { |r|
|
41
|
+
name = r.name || ""
|
42
|
+
verb = r.verb.to_s.ljust(8)
|
43
|
+
path = r.path.spec.to_s.sub(/\(\.:format\)/, "").ljust(45)
|
44
|
+
ctrl_action = "#{r.defaults[:controller]}##{r.defaults[:action]}"
|
45
|
+
"#{verb} #{path} #{ctrl_action.ljust(40)} #{name}"
|
46
|
+
}.join("\n")
|
47
|
+
end
|
48
|
+
# rubocop:enable Rails/Output
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/securial/logger.rb
CHANGED
@@ -7,13 +7,13 @@ module Securial
|
|
7
7
|
def self.build
|
8
8
|
outputs = []
|
9
9
|
|
10
|
-
|
10
|
+
unless Securial.configuration.log_to_file == false
|
11
11
|
log_file = Rails.root.join("log", "securial.log").open("a")
|
12
12
|
log_file.sync = true
|
13
13
|
outputs << log_file
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
unless Securial.configuration.log_to_stdout == false
|
17
17
|
outputs << STDOUT
|
18
18
|
end
|
19
19
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Securial
|
2
|
+
module Sessions
|
3
|
+
module Errors
|
4
|
+
class BaseSessionError < StandardError
|
5
|
+
def backtrace; []; end
|
6
|
+
end
|
7
|
+
|
8
|
+
class SessionEncodeError < BaseSessionError; end
|
9
|
+
class SessionDecodeError < BaseSessionError; end
|
10
|
+
|
11
|
+
class SessionRevokedError < BaseSessionError; end
|
12
|
+
class SessionExpiredError < BaseSessionError; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Securial
|
2
|
+
module Sessions
|
3
|
+
module SessionEncoder
|
4
|
+
class << self
|
5
|
+
def encode(session)
|
6
|
+
return nil unless session && session.class == Securial::Session
|
7
|
+
|
8
|
+
base_payload = {
|
9
|
+
jti: session.id,
|
10
|
+
exp: expiry_duration.from_now.to_i,
|
11
|
+
sub: "session-access-token",
|
12
|
+
refresh_count: session.refresh_count,
|
13
|
+
}
|
14
|
+
|
15
|
+
session_payload = {
|
16
|
+
ip: session.ip_address,
|
17
|
+
agent: session.user_agent,
|
18
|
+
}
|
19
|
+
|
20
|
+
payload = base_payload.merge(session_payload)
|
21
|
+
begin
|
22
|
+
JWT.encode(payload, secret, algorithm, { kid: "hmac" })
|
23
|
+
rescue JWT::EncodeError => e
|
24
|
+
raise Errors::SessionEncodeError, "Failed to encode session: #{e.message}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def decode(token)
|
29
|
+
begin
|
30
|
+
decoded = JWT.decode(token, secret, true, { algorithm: algorithm, verify_jti: true, iss: "securial" })
|
31
|
+
rescue JWT::DecodeError => e
|
32
|
+
raise Securial::Sessions::Errors::SessionDecodeError, "Failed to decode session token: #{e.message}"
|
33
|
+
end
|
34
|
+
decoded.first
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def secret
|
40
|
+
# Config::Validation.validate_session_secret!(Securial.configuration)
|
41
|
+
Securial.configuration.session_secret
|
42
|
+
end
|
43
|
+
|
44
|
+
def algorithm
|
45
|
+
# Config::Validation.validate_session_algorithm!(Securial.configuration)
|
46
|
+
Securial.configuration.session_algorithm.to_s.upcase
|
47
|
+
end
|
48
|
+
|
49
|
+
def expiry_duration
|
50
|
+
# Config::Validation.validate_session_expiry_duration!(Securial.configuration)
|
51
|
+
Securial.configuration.session_expiration_duration
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/securial/version.rb
CHANGED
data/lib/securial.rb
CHANGED
@@ -8,17 +8,11 @@ module Securial
|
|
8
8
|
attr_writer :configuration
|
9
9
|
|
10
10
|
def configuration
|
11
|
-
@configuration ||= Configuration.new
|
11
|
+
@configuration ||= Securial::Config::Configuration.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def configure
|
15
15
|
yield(configuration)
|
16
|
-
validate_admin_role!
|
17
|
-
validate_session_expiry_duration!
|
18
|
-
validate_session_algorithm!
|
19
|
-
validate_session_secret!
|
20
|
-
validate_mailer_sender!
|
21
|
-
validate_password_config!
|
22
16
|
end
|
23
17
|
|
24
18
|
# Returns the pluralized form of the admin role.
|
@@ -26,69 +20,5 @@ module Securial
|
|
26
20
|
def admin_namespace
|
27
21
|
configuration.admin_role.to_s.pluralize.downcase
|
28
22
|
end
|
29
|
-
|
30
|
-
def validate_admin_role!
|
31
|
-
error_message = "The admin role cannot be 'account' or 'accounts' as it conflicts with the default routes."
|
32
|
-
|
33
|
-
if configuration.admin_role.to_s.pluralize.downcase == "accounts"
|
34
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
35
|
-
raise Securial::ConfigErrors::ConfigAdminRoleError, error_message
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def validate_session_expiry_duration!
|
40
|
-
if configuration.session_expiration_duration.nil?
|
41
|
-
error_message = "Session expiration duration is not set."
|
42
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
43
|
-
raise Securial::ConfigErrors::ConfigSessionExpirationDurationError, error_message
|
44
|
-
end
|
45
|
-
if configuration.session_expiration_duration.class != ActiveSupport::Duration
|
46
|
-
error_message = "Session expiration duration must be an ActiveSupport::Duration."
|
47
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
48
|
-
raise Securial::ConfigErrors::ConfigSessionExpirationDurationError, error_message
|
49
|
-
end
|
50
|
-
if configuration.session_expiration_duration <= 0
|
51
|
-
Securial::ENGINE_LOGGER.error("Session expiration duration must be greater than 0.")
|
52
|
-
raise Securial::ConfigErrors::ConfigSessionExpirationDurationError, "Session expiration duration must be greater than 0."
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def validate_session_algorithm!
|
57
|
-
valid_algorithms = [:hs256, :hs384, :hs512]
|
58
|
-
unless valid_algorithms.include?(configuration.session_algorithm)
|
59
|
-
error_message = "Invalid session algorithm. Valid options are: #{valid_algorithms.join(', ')}."
|
60
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
61
|
-
raise Securial::ConfigErrors::ConfigSessionAlgorithmError, error_message
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def validate_session_secret!
|
66
|
-
if configuration.session_secret.blank?
|
67
|
-
error_message = "Session secret is not set."
|
68
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
69
|
-
raise Securial::ConfigErrors::ConfigSessionSecretError, error_message
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def validate_mailer_sender!
|
74
|
-
if configuration.mailer_sender.blank?
|
75
|
-
error_message = "Mailer sender is not set."
|
76
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
77
|
-
raise Securial::ConfigErrors::ConfigMailerSenderError, error_message
|
78
|
-
end
|
79
|
-
if configuration.mailer_sender !~ URI::MailTo::EMAIL_REGEXP
|
80
|
-
error_message = "Mailer sender is not a valid email address."
|
81
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
82
|
-
raise Securial::ConfigErrors::ConfigMailerSenderError, error_message
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def validate_password_config!
|
87
|
-
if configuration.password_reset_email_subject.blank?
|
88
|
-
error_message = "Password reset email subject is not set."
|
89
|
-
Securial::ENGINE_LOGGER.error(error_message)
|
90
|
-
raise Securial::ConfigErrors::ConfigMailerSenderError, error_message
|
91
|
-
end
|
92
|
-
end
|
93
23
|
end
|
94
24
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: securial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aly Badawy
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-28 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -353,9 +353,6 @@ files:
|
|
353
353
|
- app/views/layouts/securial/mailer.html.erb
|
354
354
|
- app/views/layouts/securial/mailer.text.erb
|
355
355
|
- app/views/securial/accounts/show.json.jbuilder
|
356
|
-
- app/views/securial/passwords/_password.json.jbuilder
|
357
|
-
- app/views/securial/passwords/index.json.jbuilder
|
358
|
-
- app/views/securial/passwords/show.json.jbuilder
|
359
356
|
- app/views/securial/role_assignments/show.json.jbuilder
|
360
357
|
- app/views/securial/roles/_securial_role.json.jbuilder
|
361
358
|
- app/views/securial/roles/index.json.jbuilder
|
@@ -365,6 +362,7 @@ files:
|
|
365
362
|
- app/views/securial/sessions/_session.json.jbuilder
|
366
363
|
- app/views/securial/sessions/index.json.jbuilder
|
367
364
|
- app/views/securial/sessions/show.json.jbuilder
|
365
|
+
- app/views/securial/shared/_timestamps.json.jbuilder
|
368
366
|
- app/views/securial/status/show.json.jbuilder
|
369
367
|
- app/views/securial/users/_securial_user.json.jbuilder
|
370
368
|
- app/views/securial/users/index.json.jbuilder
|
@@ -390,27 +388,32 @@ files:
|
|
390
388
|
- lib/generators/securial/scaffold/templates/routes.erb
|
391
389
|
- lib/generators/securial/scaffold/templates/routing_spec.erb
|
392
390
|
- lib/securial.rb
|
393
|
-
- lib/securial/
|
391
|
+
- lib/securial/config/_index.rb
|
392
|
+
- lib/securial/config/configuration.rb
|
393
|
+
- lib/securial/config/errors.rb
|
394
|
+
- lib/securial/config/validation.rb
|
394
395
|
- lib/securial/engine.rb
|
395
|
-
- lib/securial/errors/config_errors.rb
|
396
|
-
- lib/securial/errors/session_errors.rb
|
397
396
|
- lib/securial/factories/securial/role_assignments.rb
|
398
397
|
- lib/securial/factories/securial/roles.rb
|
399
398
|
- lib/securial/factories/securial/sessions.rb
|
400
399
|
- lib/securial/factories/securial/users.rb
|
401
|
-
- lib/securial/helpers/
|
400
|
+
- lib/securial/helpers/_index.rb
|
402
401
|
- lib/securial/helpers/normalizing_helper.rb
|
403
402
|
- lib/securial/helpers/regex_helper.rb
|
403
|
+
- lib/securial/inspectors/_index.rb
|
404
|
+
- lib/securial/inspectors/route_inspector.rb
|
404
405
|
- lib/securial/logger.rb
|
405
406
|
- lib/securial/middleware/request_logger_tag.rb
|
406
|
-
- lib/securial/
|
407
|
+
- lib/securial/sessions/_index.rb
|
408
|
+
- lib/securial/sessions/errors.rb
|
409
|
+
- lib/securial/sessions/session_encoder.rb
|
407
410
|
- lib/securial/version.rb
|
408
411
|
- lib/tasks/securial_tasks.rake
|
409
412
|
homepage: https://github.com/AlyBadawy/Securial/wiki
|
410
413
|
licenses:
|
411
414
|
- MIT
|
412
415
|
metadata:
|
413
|
-
release_date: '2025-05-
|
416
|
+
release_date: '2025-05-28'
|
414
417
|
allowed_push_host: https://rubygems.org
|
415
418
|
homepage_uri: https://github.com/AlyBadawy/Securial/wiki
|
416
419
|
source_code_uri: https://github.com/AlyBadawy/Securial
|
@@ -1 +0,0 @@
|
|
1
|
-
json.array! @passwords, partial: "securial/passwords/password", as: :password
|
@@ -1 +0,0 @@
|
|
1
|
-
json.partial! "securial/passwords/password", password: @password
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Securial
|
2
|
-
class Configuration
|
3
|
-
attr_accessor :log_to_file, :log_to_stdout
|
4
|
-
attr_accessor :log_file_level, :log_stdout_level
|
5
|
-
attr_accessor :admin_role
|
6
|
-
attr_accessor :session_expiration_duration
|
7
|
-
attr_accessor :session_secret, :session_algorithm
|
8
|
-
attr_accessor :mailer_sender
|
9
|
-
attr_accessor :password_reset_email_subject
|
10
|
-
attr_accessor :password_min_length, :password_max_length
|
11
|
-
attr_accessor :password_complexity
|
12
|
-
attr_accessor :password_expires_in
|
13
|
-
attr_accessor :reset_password_token_expires_in
|
14
|
-
attr_accessor :reset_password_token_secret
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@log_to_file = !Rails.env.test?
|
18
|
-
@log_to_stdout = !Rails.env.test?
|
19
|
-
@log_file_level = :info
|
20
|
-
@log_stdout_level = :info
|
21
|
-
@admin_role = :admin
|
22
|
-
@session_expiration_duration = 3.minutes
|
23
|
-
@session_secret = "secret"
|
24
|
-
@session_algorithm = :hs256
|
25
|
-
@mailer_sender = "no-reply@example.com"
|
26
|
-
@password_reset_email_subject = "SECURIAL: Password Reset Instructions"
|
27
|
-
@password_min_length = 8
|
28
|
-
@password_max_length = 128
|
29
|
-
@password_complexity = Securial::RegexHelper::PASSWORD_REGEX
|
30
|
-
@password_expires_in = 90.days
|
31
|
-
@reset_password_token_expires_in = 2.hours
|
32
|
-
@reset_password_token_secret = "reset_secret"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
module Securial
|
2
|
-
module ConfigErrors
|
3
|
-
class ConfigAdminRoleError < StandardError; end
|
4
|
-
|
5
|
-
class ConfigSessionExpirationDurationError < StandardError; end
|
6
|
-
class ConfigSessionAlgorithmError < StandardError; end
|
7
|
-
class ConfigSessionSecretError < StandardError; end
|
8
|
-
|
9
|
-
class ConfigMailerSenderError < StandardError; end
|
10
|
-
class ConfigPasswordError < StandardError; end
|
11
|
-
end
|
12
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Securial
|
2
|
-
module AuthHelper
|
3
|
-
class << self
|
4
|
-
def encode(session)
|
5
|
-
return nil unless session && session.class == Securial::Session
|
6
|
-
|
7
|
-
base_payload = {
|
8
|
-
jti: session.id,
|
9
|
-
exp: expiry_duration.from_now.to_i,
|
10
|
-
sub: "session-access-token",
|
11
|
-
refresh_count: session.refresh_count,
|
12
|
-
}
|
13
|
-
|
14
|
-
session_payload = {
|
15
|
-
ip: session.ip_address,
|
16
|
-
agent: session.user_agent,
|
17
|
-
}
|
18
|
-
|
19
|
-
payload = base_payload.merge(session_payload)
|
20
|
-
JWT.encode(payload, secret, algorithm, { kid: "hmac" })
|
21
|
-
end
|
22
|
-
|
23
|
-
def decode(token)
|
24
|
-
decoded = JWT.decode(token, secret, true, { algorithm: algorithm, verify_jti: true, iss: "securial" })
|
25
|
-
decoded.first
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def secret
|
31
|
-
Securial.validate_session_secret!
|
32
|
-
Securial.configuration.session_secret
|
33
|
-
end
|
34
|
-
|
35
|
-
def algorithm
|
36
|
-
Securial.validate_session_algorithm!
|
37
|
-
Securial.configuration.session_algorithm.to_s.upcase
|
38
|
-
end
|
39
|
-
|
40
|
-
def expiry_duration
|
41
|
-
Securial.validate_session_expiry_duration!
|
42
|
-
Securial.configuration.session_expiration_duration
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
module Securial
|
2
|
-
module RouteInspector
|
3
|
-
def self.print_routes(controller: nil)
|
4
|
-
filtered = Securial::Engine.routes.routes.select do |r|
|
5
|
-
ctrl = r.defaults[:controller]
|
6
|
-
controller.nil? || ctrl == "securial/#{controller}"
|
7
|
-
end
|
8
|
-
|
9
|
-
print_headers(filtered, controller)
|
10
|
-
print_details(filtered, controller)
|
11
|
-
true
|
12
|
-
end
|
13
|
-
|
14
|
-
class << self
|
15
|
-
private
|
16
|
-
|
17
|
-
# rubocop:disable Rails/Output
|
18
|
-
def print_headers(filtered, controller)
|
19
|
-
Securial::ENGINE_LOGGER.debug "Securial engine routes:"
|
20
|
-
Securial::ENGINE_LOGGER.debug "Total routes: #{filtered.size}"
|
21
|
-
Securial::ENGINE_LOGGER.debug "Filtered by controller: #{controller}" if controller
|
22
|
-
Securial::ENGINE_LOGGER.debug "Filtered routes: #{filtered.size}" if controller
|
23
|
-
Securial::ENGINE_LOGGER.debug "-" * 120
|
24
|
-
Securial::ENGINE_LOGGER.debug "#{'Verb'.ljust(8)} #{'Path'.ljust(45)} #{'Controller#Action'.ljust(40)} Name"
|
25
|
-
Securial::ENGINE_LOGGER.debug "-" * 120
|
26
|
-
end
|
27
|
-
|
28
|
-
def print_details(filtered, controller) # rubocop:disable Rails/Output
|
29
|
-
if filtered.empty?
|
30
|
-
if controller
|
31
|
-
Securial::ENGINE_LOGGER.debug "No routes found for controller: #{controller}"
|
32
|
-
else
|
33
|
-
Securial::ENGINE_LOGGER.debug "No routes found for Securial engine"
|
34
|
-
end
|
35
|
-
Securial::ENGINE_LOGGER.debug "-" * 120
|
36
|
-
return
|
37
|
-
end
|
38
|
-
|
39
|
-
Securial::ENGINE_LOGGER.debug filtered.map { |r|
|
40
|
-
name = r.name || ""
|
41
|
-
verb = r.verb.to_s.ljust(8)
|
42
|
-
path = r.path.spec.to_s.sub(/\(\.:format\)/, "").ljust(45)
|
43
|
-
ctrl_action = "#{r.defaults[:controller]}##{r.defaults[:action]}"
|
44
|
-
"#{verb} #{path} #{ctrl_action.ljust(40)} #{name}"
|
45
|
-
}.join("\n")
|
46
|
-
end
|
47
|
-
# rubocop:enable Rails/Output
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|