securial 0.8.1 → 1.0.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -16
  3. data/app/controllers/concerns/securial/identity.rb +18 -9
  4. data/app/controllers/securial/status_controller.rb +2 -0
  5. data/app/controllers/securial/users_controller.rb +1 -1
  6. data/app/views/securial/status/show.json.jbuilder +1 -1
  7. data/bin/securial +20 -52
  8. data/db/migrate/20250606182648_seed_roles_and_users.rb +69 -0
  9. data/lib/generators/securial/install/templates/securial_initializer.erb +115 -18
  10. data/lib/securial/cli/run.rb +11 -0
  11. data/lib/securial/cli/securial_new.rb +53 -0
  12. data/lib/securial/cli/show_help.rb +26 -0
  13. data/lib/securial/cli/show_version.rb +9 -0
  14. data/lib/securial/config/configuration.rb +3 -53
  15. data/lib/securial/config/signature.rb +107 -0
  16. data/lib/securial/config/validation.rb +58 -14
  17. data/lib/securial/config.rb +2 -0
  18. data/lib/securial/engine.rb +2 -0
  19. data/lib/securial/engine_initializers.rb +21 -2
  20. data/lib/securial/error/config.rb +0 -28
  21. data/lib/securial/helpers/key_transformer.rb +33 -0
  22. data/lib/securial/helpers.rb +1 -0
  23. data/lib/securial/middleware/response_headers.rb +19 -0
  24. data/lib/securial/middleware/transform_request_keys.rb +35 -0
  25. data/lib/securial/middleware/transform_response_keys.rb +47 -0
  26. data/lib/securial/middleware.rb +3 -0
  27. data/lib/securial/security/request_rate_limiter.rb +45 -0
  28. data/lib/securial/security.rb +8 -0
  29. data/lib/securial/version.rb +1 -1
  30. data/lib/tasks/securial_routes.rake +26 -0
  31. metadata +44 -19
  32. data/lib/securial/config/validation/logger_validation.rb +0 -29
  33. data/lib/securial/config/validation/mailer_validation.rb +0 -24
  34. data/lib/securial/config/validation/password_validation.rb +0 -91
  35. data/lib/securial/config/validation/response_validation.rb +0 -37
  36. data/lib/securial/config/validation/roles_validation.rb +0 -32
  37. data/lib/securial/config/validation/security_validation.rb +0 -56
  38. data/lib/securial/config/validation/session_validation.rb +0 -87
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.8.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aly Badawy
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-06 00:00:00.000000000 Z
10
+ date: 2025-06-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -43,28 +43,42 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '2.11'
46
+ version: '2.13'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.11'
53
+ version: '2.13'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: jwt
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.10'
60
+ version: 3.0.0
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '2.10'
67
+ version: 3.0.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: rack-attack
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '6.7'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '6.7'
68
82
  description: Securial is a mountable Rails engine that provides robust, extensible
69
83
  authentication and access control for Rails applications. It supports JWT, API tokens,
70
84
  session-based auth, and is designed for easy integration with modern web and mobile
@@ -127,6 +141,7 @@ files:
127
141
  - db/migrate/20250604110520_create_securial_users.rb
128
142
  - db/migrate/20250604123805_create_securial_role_assignments.rb
129
143
  - db/migrate/20250604184841_create_securial_sessions.rb
144
+ - db/migrate/20250606182648_seed_roles_and_users.rb
130
145
  - lib/generators/factory_bot/model/model_generator.rb
131
146
  - lib/generators/factory_bot/templates/factory.erb
132
147
  - lib/generators/securial/install/install_generator.rb
@@ -146,16 +161,14 @@ files:
146
161
  - lib/securial/auth/auth_encoder.rb
147
162
  - lib/securial/auth/session_creator.rb
148
163
  - lib/securial/auth/token_generator.rb
164
+ - lib/securial/cli/run.rb
165
+ - lib/securial/cli/securial_new.rb
166
+ - lib/securial/cli/show_help.rb
167
+ - lib/securial/cli/show_version.rb
149
168
  - lib/securial/config.rb
150
169
  - lib/securial/config/configuration.rb
170
+ - lib/securial/config/signature.rb
151
171
  - lib/securial/config/validation.rb
152
- - lib/securial/config/validation/logger_validation.rb
153
- - lib/securial/config/validation/mailer_validation.rb
154
- - lib/securial/config/validation/password_validation.rb
155
- - lib/securial/config/validation/response_validation.rb
156
- - lib/securial/config/validation/roles_validation.rb
157
- - lib/securial/config/validation/security_validation.rb
158
- - lib/securial/config/validation/session_validation.rb
159
172
  - lib/securial/engine.rb
160
173
  - lib/securial/engine_initializers.rb
161
174
  - lib/securial/error.rb
@@ -167,6 +180,7 @@ files:
167
180
  - lib/securial/factories/securial/sessions.rb
168
181
  - lib/securial/factories/securial/users.rb
169
182
  - lib/securial/helpers.rb
183
+ - lib/securial/helpers/key_transformer.rb
170
184
  - lib/securial/helpers/normalizing_helper.rb
171
185
  - lib/securial/helpers/regex_helper.rb
172
186
  - lib/securial/helpers/roles_helper.rb
@@ -176,23 +190,34 @@ files:
176
190
  - lib/securial/logger/formatter.rb
177
191
  - lib/securial/middleware.rb
178
192
  - lib/securial/middleware/request_tag_logger.rb
193
+ - lib/securial/middleware/response_headers.rb
194
+ - lib/securial/middleware/transform_request_keys.rb
195
+ - lib/securial/middleware/transform_response_keys.rb
196
+ - lib/securial/security.rb
197
+ - lib/securial/security/request_rate_limiter.rb
179
198
  - lib/securial/version.rb
199
+ - lib/tasks/securial_routes.rake
180
200
  - lib/tasks/securial_tasks.rake
181
201
  homepage: https://github.com/AlyBadawy/Securial/wiki
182
202
  licenses:
183
203
  - MIT
184
204
  metadata:
185
- release_date: '2025-06-06'
205
+ release_date: '2025-06-18'
186
206
  allowed_push_host: https://rubygems.org
187
207
  homepage_uri: https://github.com/AlyBadawy/Securial/wiki
188
208
  source_code_uri: https://github.com/AlyBadawy/Securial
189
209
  changelog_uri: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md
190
210
  post_install_message: "\n ---\n [SECURIAL] Thank you for installing Securial!\n\n
191
- \ Securial is a mountable Rails engine that provides robust,\n extensible authentication
192
- and access control for Rails applications.\n Please review the [changelog]
193
- and [WIKI] for more info on the latest\n changes and how to use this gem/engine:\n
194
- \ [changelog]: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md\n
195
- \ [WIKI]: https://github.com/AlyBadawy/Securial/wiki\n ---\n "
211
+ \ Securial is a mountable Rails engine that provides robust, extensible\n authentication
212
+ and access control for Rails applications. It supports JWT,\n API tokens, session-based
213
+ auth, and is designed for easy integration with\n modern web and mobile apps.\n\n
214
+ \ Usage:\n securial new APP_NAME [rails_options...] # Create a new Rails
215
+ app with Securial pre-installed\n securial -v, --version #
216
+ Show the Securial gem version\n securial -h, --help #
217
+ Show this help message\n\n Example:\n securial new myapp --api --database=postgresql
218
+ -T\n\n More Info:\n review the [changelog] and [WIKI] for more info on the
219
+ latest\n changes and how to use this gem/engine:\n [Changelog]: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md\n
220
+ \ [WIKI]: https://github.com/AlyBadawy/Securial/wiki\n ---\n "
196
221
  rdoc_options: []
197
222
  require_paths:
198
223
  - lib
@@ -1,29 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module LoggerValidation
7
- class << self
8
- LEVELS = %i[debug info warn error fatal unknown].freeze
9
-
10
- def validate!(securial_config)
11
- allowed_options = {
12
- log_to_file: [securial_config.log_to_file, [true, false], "boolean"],
13
- log_to_stdout: [securial_config.log_to_stdout, [true, false], "boolean"],
14
- log_file_level: [securial_config.log_file_level, LEVELS, "symbol"],
15
- log_stdout_level: [securial_config.log_stdout_level, LEVELS, "symbol"],
16
- }
17
-
18
- allowed_options.each do |attr, (value, allowed, type)|
19
- unless allowed.include?(value)
20
- allowed_values = type == "symbol" ? allowed.map { |v| ":#{v}" }.join(", ") : allowed.join(", ")
21
- raise Securial::Error::Config::LoggerValidationError, "#{attr} must be a #{type}. Allowed values: #{allowed_values}. Received: #{value.inspect}"
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,24 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module MailerValidation
7
- class << self
8
- def validate!(securial_config)
9
- if securial_config.mailer_sender.blank?
10
- error_message = "Mailer sender is not set."
11
- Securial.logger.fatal(error_message)
12
- raise Securial::Error::Config::MailerValidationError, error_message
13
- end
14
- if securial_config.mailer_sender !~ URI::MailTo::EMAIL_REGEXP
15
- error_message = "Mailer sender is not a valid email address."
16
- Securial.logger.fatal(error_message)
17
- raise Securial::Error::Config::MailerValidationError, error_message
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,91 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module PasswordValidation
7
- class << self
8
- def validate!(securial_config)
9
- validate_password_reset_subject!(securial_config)
10
- validate_password_min_max_length!(securial_config)
11
- validate_password_complexity!(securial_config)
12
- validate_password_expiration!(securial_config)
13
- validate_password_reset_token!(securial_config)
14
- end
15
-
16
- private
17
-
18
- def validate_password_reset_token!(securial_config)
19
- if securial_config.reset_password_token_secret.blank?
20
- error_message = "Reset password token secret is not set."
21
- Securial.logger.fatal(error_message)
22
- raise Securial::Error::Config::PasswordValidationError, error_message
23
- end
24
- unless securial_config.reset_password_token_secret.is_a?(String)
25
- error_message = "Reset password token secret must be a String."
26
- Securial.logger.fatal(error_message)
27
- raise Securial::Error::Config::PasswordValidationError, error_message
28
- end
29
- unless securial_config.reset_password_token_expires_in.is_a?(ActiveSupport::Duration) && securial_config.reset_password_token_expires_in > 0
30
- error_message = "Reset password token expiration must be a valid ActiveSupport::Duration greater than 0."
31
- Securial.logger.fatal(error_message)
32
- raise Securial::Error::Config::PasswordValidationError, error_message
33
- end
34
- end
35
-
36
- def validate_password_reset_subject!(securial_config)
37
- if securial_config.mailer_forgot_password_subject.blank?
38
- error_message = "Password reset email subject is not set."
39
- Securial.logger.fatal(error_message)
40
- raise Securial::Error::Config::PasswordValidationError, error_message
41
- end
42
- unless securial_config.mailer_forgot_password_subject.is_a?(String)
43
- error_message = "Password reset email subject must be a String."
44
- Securial.logger.fatal(error_message)
45
- raise Securial::Error::Config::PasswordValidationError, error_message
46
- end
47
- end
48
-
49
- def validate_password_min_max_length!(securial_config)
50
- unless securial_config.password_min_length.is_a?(Integer) && securial_config.password_min_length > 0
51
- error_message = "Password minimum length must be a positive integer."
52
- Securial.logger.fatal(error_message)
53
- raise Securial::Error::Config::PasswordValidationError, error_message
54
- end
55
- unless securial_config.password_max_length.is_a?(Integer) && securial_config.password_max_length >= securial_config.password_min_length
56
- error_message = "Password maximum length must be an integer greater than or equal to the minimum length."
57
- Securial.logger.fatal(error_message)
58
- raise Securial::Error::Config::PasswordValidationError, error_message
59
- end
60
- end
61
-
62
- def validate_password_complexity!(securial_config)
63
- if securial_config.password_complexity.nil? || !securial_config.password_complexity.is_a?(Regexp)
64
- error_message = "Password complexity regex is not set or is not a valid Regexp."
65
- Securial.logger.fatal(error_message)
66
- raise Securial::Error::Config::PasswordValidationError, error_message
67
- end
68
- end
69
-
70
- def validate_password_expiration!(securial_config)
71
- unless securial_config.password_expires.is_a?(TrueClass) || securial_config.password_expires.is_a?(FalseClass)
72
- error_message = "Password expiration must be a boolean value."
73
- Securial.logger.fatal(error_message)
74
- raise Securial::Error::Config::PasswordValidationError, error_message
75
- end
76
-
77
- if securial_config.password_expires == true && (
78
- securial_config.password_expires_in.nil? ||
79
- !securial_config.password_expires_in.is_a?(ActiveSupport::Duration) ||
80
- securial_config.password_expires_in <= 0
81
- )
82
- error_message = "Password expiration duration is not set or is not a valid ActiveSupport::Duration."
83
- Securial.logger.fatal(error_message)
84
- raise Securial::Error::Config::PasswordValidationError, error_message
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,37 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module ResponseValidation
7
- class << self
8
- VALID_TIMESTAMP_OPTIONS = %i[all admins_only none].freeze
9
- VALID_RESPONSE_KEYS_FORMATS = %i[snake_case lowerCamelCase UpperCamelCase].freeze
10
-
11
- def validate!(securial_config)
12
- validate_response_keys_format!(securial_config)
13
- validate_timestamps_in_response!(securial_config)
14
- end
15
-
16
- private
17
-
18
- def validate_response_keys_format!(securial_config)
19
- unless VALID_RESPONSE_KEYS_FORMATS.include?(securial_config.response_keys_format)
20
- error_message = "Invalid response_keys_format option. Valid options are: #{VALID_RESPONSE_KEYS_FORMATS.map(&:inspect).join(', ')}."
21
- Securial.logger.fatal(error_message)
22
- raise Securial::Error::Config::ResponseValidationError, error_message
23
- end
24
- end
25
-
26
- def validate_timestamps_in_response!(securial_config)
27
- unless VALID_TIMESTAMP_OPTIONS.include?(securial_config.timestamps_in_response)
28
- error_message = "Invalid timestamps_in_response option. Valid options are: #{VALID_TIMESTAMP_OPTIONS.map(&:inspect).join(', ')}."
29
- Securial.logger.fatal(error_message)
30
- raise Securial::Error::Config::ResponseValidationError, error_message
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,32 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module RolesValidation
7
- class << self
8
- def validate!(securial_config)
9
- if securial_config.admin_role.nil? || securial_config.admin_role.to_s.strip.empty?
10
- error_message = "Admin role is not set."
11
- Securial.logger.fatal(error_message)
12
- raise Securial::Error::Config::RolesValidationError, error_message
13
- end
14
-
15
- unless securial_config.admin_role.is_a?(Symbol) || securial_config.admin_role.is_a?(String)
16
- error_message = "Admin role must be a Symbol or String."
17
- Securial.logger.fatal(error_message)
18
- raise Securial::Error::Config::RolesValidationError, error_message
19
- end
20
-
21
- admin_role_downcase = securial_config.admin_role.to_s.downcase
22
- if admin_role_downcase == "account" || admin_role_downcase == "accounts"
23
- error_message = "The admin role cannot be 'account' or 'accounts' as it conflicts with the default routes."
24
- Securial.logger.fatal(error_message)
25
- raise Securial::Error::Config::RolesValidationError, error_message
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,56 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module SecurityValidation
7
- class << self
8
- VALID_SECURITY_HEADERS = %i[strict default none].freeze
9
-
10
- def validate!(securial_config)
11
- validate_security_headers!(securial_config)
12
- validate_rate_limiting!(securial_config)
13
- end
14
-
15
- private
16
-
17
- def validate_security_headers!(securial_config)
18
- unless VALID_SECURITY_HEADERS.include?(securial_config.security_headers)
19
- error_message = "Invalid security_headers option. Valid options are: #{VALID_SECURITY_HEADERS.map(&:inspect).join(', ')}."
20
- Securial.logger.fatal(error_message)
21
- raise Securial::Error::Config::SecurityValidationError, error_message
22
- end
23
- end
24
-
25
- def validate_rate_limiting!(securial_config) # rubocop:disable Metrics/MethodLength
26
- unless securial_config.rate_limiting_enabled.is_a?(TrueClass) || securial_config.rate_limiting_enabled.is_a?(FalseClass)
27
- error_message = "rate_limiting_enabled must be a boolean value."
28
- Securial.logger.fatal(error_message)
29
- raise Securial::Error::Config::SecurityValidationError, error_message
30
- end
31
-
32
- return unless securial_config.rate_limiting_enabled
33
-
34
- unless securial_config.rate_limit_requests_per_minute.is_a?(Integer) && securial_config.rate_limit_requests_per_minute > 0
35
- error_message = "rate_limit_requests_per_minute must be a positive integer when rate limiting is enabled."
36
- Securial.logger.fatal(error_message)
37
- raise Securial::Error::Config::SecurityValidationError, error_message
38
- end
39
-
40
- unless securial_config.rate_limit_response_status.is_a?(Integer) && securial_config.rate_limit_response_status.between?(400, 599)
41
- error_message = "rate_limit_response_status must be an HTTP status code between 4xx and 5xx."
42
- Securial.logger.fatal(error_message)
43
- raise Securial::Error::Config::SecurityValidationError, error_message
44
- end
45
-
46
- unless securial_config.rate_limit_response_message.is_a?(String) && !securial_config.rate_limit_response_message.strip.empty?
47
- error_message = "rate_limit_response_message must be a non-empty String."
48
- Securial.logger.fatal(error_message)
49
- raise Securial::Error::Config::SecurityValidationError, error_message
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,87 +0,0 @@
1
- require "securial/error"
2
-
3
- module Securial
4
- module Config
5
- module Validation
6
- module SessionValidation
7
- class << self
8
- VALID_SESSION_ENCRYPTION_ALGORITHMS = %i[hs256 hs384 hs512].freeze
9
-
10
- def validate!(securial_config)
11
- validate_session_expiry_duration!(securial_config)
12
- validate_session_algorithm!(securial_config)
13
- validate_session_secret!(securial_config)
14
- validate_session_refresh_token!(securial_config)
15
- end
16
-
17
- private
18
-
19
- def validate_session_expiry_duration!(securial_config)
20
- if securial_config.session_expiration_duration.nil?
21
- error_message = "Session expiration duration is not set."
22
- Securial.logger.fatal(error_message)
23
- raise Securial::Error::Config::SessionValidationError, error_message
24
- end
25
- if securial_config.session_expiration_duration.class != ActiveSupport::Duration
26
- error_message = "Session expiration duration must be an ActiveSupport::Duration."
27
- Securial.logger.fatal(error_message)
28
- raise Securial::Error::Config::SessionValidationError, error_message
29
- end
30
- if securial_config.session_expiration_duration <= 0
31
- Securial.logger.fatal("Session expiration duration must be greater than 0.")
32
- raise Securial::Error::Config::SessionValidationError, "Session expiration duration must be greater than 0."
33
- end
34
- end
35
-
36
- def validate_session_algorithm!(securial_config)
37
- if securial_config.session_algorithm.blank?
38
- error_message = "Session algorithm is not set."
39
- Securial.logger.fatal(error_message)
40
- raise Securial::Error::Config::SessionValidationError, error_message
41
- end
42
- unless securial_config.session_algorithm.is_a?(Symbol)
43
- error_message = "Session algorithm must be a Symbol."
44
- Securial.logger.fatal(error_message)
45
- raise Securial::Error::Config::SessionValidationError, error_message
46
- end
47
- unless VALID_SESSION_ENCRYPTION_ALGORITHMS.include?(securial_config.session_algorithm)
48
- error_message = "Invalid session algorithm. Valid options are: #{VALID_SESSION_ENCRYPTION_ALGORITHMS.map(&:inspect).join(', ')}."
49
- Securial.logger.fatal(error_message)
50
- raise Securial::Error::Config::SessionValidationError, error_message
51
- end
52
- end
53
-
54
- def validate_session_secret!(securial_config)
55
- if securial_config.session_secret.blank?
56
- error_message = "Session secret is not set."
57
- Securial.logger.fatal(error_message)
58
- raise Securial::Error::Config::SessionValidationError, error_message
59
- end
60
- unless securial_config.session_secret.is_a?(String)
61
- error_message = "Session secret must be a String."
62
- Securial.logger.fatal(error_message)
63
- raise Securial::Error::Config::SessionValidationError, error_message
64
- end
65
- end
66
-
67
- def validate_session_refresh_token!(securial_config)
68
- if securial_config.session_refresh_token_expires_in.nil?
69
- error_message = "Session refresh token expiration duration is not set."
70
- Securial.logger.fatal(error_message)
71
- raise Securial::Error::Config::SessionValidationError, error_message
72
- end
73
- if securial_config.session_refresh_token_expires_in.class != ActiveSupport::Duration
74
- error_message = "Session refresh token expiration duration must be an ActiveSupport::Duration."
75
- Securial.logger.fatal(error_message)
76
- raise Securial::Error::Config::SessionValidationError, error_message
77
- end
78
- if securial_config.session_refresh_token_expires_in <= 0
79
- Securial.logger.fatal("Session refresh token expiration duration must be greater than 0.")
80
- raise Securial::Error::Config::SessionValidationError, "Session refresh token expiration duration must be greater than 0."
81
- end
82
- end
83
- end
84
- end
85
- end
86
- end
87
- end