securial 0.6.1 → 0.8.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 +2 -5
- data/app/controllers/concerns/securial/identity.rb +17 -16
- data/app/controllers/securial/accounts_controller.rb +2 -2
- data/app/controllers/securial/passwords_controller.rb +2 -2
- data/app/controllers/securial/role_assignments_controller.rb +5 -7
- data/app/controllers/securial/roles_controller.rb +1 -0
- data/app/controllers/securial/sessions_controller.rb +6 -8
- data/app/controllers/securial/status_controller.rb +1 -1
- data/app/controllers/securial/users_controller.rb +8 -7
- data/app/mailers/securial/securial_mailer.rb +17 -6
- data/app/models/concerns/securial/password_resettable.rb +1 -1
- data/app/models/securial/application_record.rb +1 -1
- data/app/models/securial/role.rb +1 -1
- data/app/models/securial/session.rb +16 -5
- data/app/models/securial/user.rb +4 -6
- data/app/views/securial/securial_mailer/forgot_password.html.erb +20 -0
- data/app/views/securial/securial_mailer/forgot_password.text.erb +14 -0
- data/app/views/securial/securial_mailer/sign_in.html.erb +31 -0
- data/app/views/securial/securial_mailer/sign_in.text.erb +17 -0
- data/app/views/securial/securial_mailer/update_account.html.erb +15 -0
- data/app/views/securial/securial_mailer/update_account.text.erb +11 -0
- data/app/views/securial/securial_mailer/welcome.html.erb +11 -0
- data/app/views/securial/securial_mailer/welcome.text.erb +8 -0
- data/app/views/securial/users/_securial_user.json.jbuilder +9 -3
- data/config/routes.rb +5 -1
- data/db/migrate/{20250515104930_create_securial_roles.rb → 20250603130214_create_securial_roles.rb} +0 -2
- data/db/migrate/{20250517155521_create_securial_users.rb → 20250604110520_create_securial_users.rb} +8 -3
- data/db/migrate/{20250519075407_create_securial_sessions.rb → 20250604184841_create_securial_sessions.rb} +4 -0
- data/lib/generators/securial/install/templates/securial_initializer.erb +2 -2
- data/lib/generators/securial/scaffold/templates/controller.erb +1 -0
- data/lib/generators/securial/scaffold/templates/routes.erb +1 -1
- data/lib/securial/auth/auth_encoder.rb +42 -43
- data/lib/securial/auth/session_creator.rb +15 -12
- data/lib/securial/auth/token_generator.rb +26 -0
- data/lib/securial/auth.rb +12 -0
- data/lib/securial/config/configuration.rb +33 -15
- data/lib/securial/config/validation/logger_validation.rb +29 -0
- data/lib/securial/config/validation/mailer_validation.rb +24 -0
- data/lib/securial/config/validation/password_validation.rb +91 -0
- data/lib/securial/config/validation/response_validation.rb +37 -0
- data/lib/securial/config/validation/roles_validation.rb +32 -0
- data/lib/securial/config/validation/security_validation.rb +56 -0
- data/lib/securial/config/validation/session_validation.rb +87 -0
- data/lib/securial/config/validation.rb +16 -239
- data/lib/securial/config.rb +26 -0
- data/lib/securial/engine.rb +7 -70
- data/lib/securial/engine_initializers.rb +33 -0
- data/lib/securial/error/auth.rb +21 -0
- data/lib/securial/error/base_securial_error.rb +16 -0
- data/lib/securial/error/config.rb +37 -0
- data/lib/securial/error.rb +9 -0
- data/lib/securial/helpers/normalizing_helper.rb +11 -9
- data/lib/securial/helpers/regex_helper.rb +12 -10
- data/lib/securial/helpers/roles_helper.rb +17 -0
- data/lib/securial/helpers.rb +10 -0
- data/lib/securial/logger/broadcaster.rb +60 -0
- data/lib/securial/logger/builder.rb +45 -0
- data/lib/securial/logger/formatter.rb +35 -0
- data/lib/securial/logger.rb +7 -63
- data/lib/securial/middleware/request_tag_logger.rb +39 -0
- data/lib/securial/middleware.rb +13 -0
- data/lib/securial/version.rb +1 -1
- data/lib/securial.rb +2 -20
- metadata +45 -150
- data/app/views/securial/securial_mailer/reset_password.html.erb +0 -5
- data/app/views/securial/securial_mailer/reset_password.text.erb +0 -4
- data/lib/securial/auth/_index.rb +0 -3
- data/lib/securial/auth/errors.rb +0 -15
- data/lib/securial/config/_index.rb +0 -3
- data/lib/securial/config/errors.rb +0 -20
- data/lib/securial/helpers/_index.rb +0 -2
- data/lib/securial/inspectors/_index.rb +0 -1
- data/lib/securial/inspectors/route_inspector.rb +0 -52
- data/lib/securial/key_transformer.rb +0 -32
- data/lib/securial/middleware/_index.rb +0 -3
- data/lib/securial/middleware/request_logger_tag.rb +0 -18
- data/lib/securial/middleware/transform_request_keys.rb +0 -33
- data/lib/securial/middleware/transform_response_keys.rb +0 -45
- data/lib/securial/rack_attack.rb +0 -48
- /data/db/migrate/{20250518122749_create_securial_role_assignments.rb → 20250604123805_create_securial_role_assignments.rb} +0 -0
- /data/lib/generators/securial/install/{views_generastor.rb → views_generator.rb} +0 -0
@@ -11,7 +11,7 @@ Securial.configure do |config|
|
|
11
11
|
config.log_to_stdout = true # Enable or disable logging to STDOUT
|
12
12
|
|
13
13
|
# Set log level for file logger: :debug, :info, :warn, :error, :fatal, or :unknown
|
14
|
-
config.log_file_level = :
|
14
|
+
config.log_file_level = :debug
|
15
15
|
|
16
16
|
# Set log level for stdout logger
|
17
17
|
config.log_stdout_level = :debug
|
@@ -60,7 +60,7 @@ Securial.configure do |config|
|
|
60
60
|
## Set the password reset email subject
|
61
61
|
# This is the subject line that will be used for the password reset
|
62
62
|
# email. The default is "Password Reset Instructions".
|
63
|
-
config.
|
63
|
+
config.mailer_forgot_password_subject = "Password Reset Instructions"
|
64
64
|
|
65
65
|
## Set the minimum password length
|
66
66
|
# This is the minimum length that a password must have
|
@@ -2,7 +2,7 @@ Securial::Engine.routes.draw do
|
|
2
2
|
defaults format: :json do
|
3
3
|
get "/status", to: "status#show", as: :status
|
4
4
|
|
5
|
-
namespace Securial.
|
5
|
+
namespace Securial.protected_namespace do
|
6
6
|
# Placeholder for admin-specific routes. Add routes here as needed.
|
7
7
|
# For example:
|
8
8
|
# resources :users, only: [:index, :show]
|
@@ -1,56 +1,55 @@
|
|
1
|
+
require "jwt"
|
2
|
+
|
1
3
|
module Securial
|
2
4
|
module Auth
|
3
5
|
module AuthEncoder
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def encode(session)
|
9
|
+
return nil unless session && session.class == Securial::Session
|
10
|
+
|
11
|
+
base_payload = {
|
12
|
+
jti: session.id,
|
13
|
+
exp: expiry_duration.from_now.to_i,
|
14
|
+
sub: "session-access-token",
|
15
|
+
refresh_count: session.refresh_count,
|
16
|
+
}
|
17
|
+
|
18
|
+
session_payload = {
|
19
|
+
ip: session.ip_address,
|
20
|
+
agent: session.user_agent,
|
21
|
+
}
|
22
|
+
|
23
|
+
payload = base_payload.merge(session_payload)
|
24
|
+
begin
|
25
|
+
::JWT.encode(payload, secret, algorithm, { kid: "hmac" })
|
26
|
+
rescue JWT::EncodeError
|
27
|
+
raise Securial::Error::Auth::TokenEncodeError
|
26
28
|
end
|
29
|
+
end
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
decoded = JWT.decode(token, secret, true, { algorithm: algorithm, verify_jti: true, iss: "securial" })
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
decoded.first
|
31
|
+
def decode(token)
|
32
|
+
begin
|
33
|
+
decoded = ::JWT.decode(token, secret, true, { algorithm: algorithm, verify_jti: true, iss: "securial" })
|
34
|
+
rescue JWT::DecodeError
|
35
|
+
raise Securial::Error::Auth::TokenDecodeError
|
35
36
|
end
|
37
|
+
decoded.first
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# Config::Validation.validate_session_secret!(Securial.configuration)
|
41
|
-
Securial.configuration.session_secret
|
42
|
-
end
|
40
|
+
def secret
|
41
|
+
Securial.configuration.session_secret
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
44
|
+
def algorithm
|
45
|
+
Securial.configuration.session_algorithm.to_s.upcase
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
Securial.configuration.session_expiration_duration
|
52
|
-
end
|
48
|
+
def expiry_duration
|
49
|
+
Securial.configuration.session_expiration_duration
|
53
50
|
end
|
51
|
+
|
52
|
+
private_class_method :secret, :algorithm, :expiry_duration
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
module Securial
|
2
2
|
module Auth
|
3
3
|
module SessionCreator
|
4
|
-
|
5
|
-
def create_session(user, request)
|
6
|
-
return nil unless user && user.persisted? && request.is_a?(ActionDispatch::Request)
|
4
|
+
module_function
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
def create_session!(user, request)
|
7
|
+
valid_user = user && user.is_a?(Securial::User) && user.persisted?
|
8
|
+
valid_request = request.is_a?(ActionDispatch::Request)
|
9
|
+
|
10
|
+
return nil unless valid_user && valid_request
|
11
|
+
|
12
|
+
user.sessions.create!(
|
13
|
+
user_agent: request.user_agent,
|
14
|
+
ip_address: request.remote_ip,
|
15
|
+
refresh_token: Securial::Auth::TokenGenerator.generate_refresh_token,
|
16
|
+
last_refreshed_at: Time.current,
|
17
|
+
refresh_token_expires_at: 1.week.from_now,
|
18
|
+
).tap do |session|
|
19
|
+
Current.session = session
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module Securial
|
5
|
+
module Auth
|
6
|
+
module TokenGenerator
|
7
|
+
class << self
|
8
|
+
def generate_refresh_token
|
9
|
+
secret = Securial.configuration.session_secret
|
10
|
+
algo = "SHA256"
|
11
|
+
|
12
|
+
random_data = SecureRandom.hex(32)
|
13
|
+
digest = OpenSSL::Digest.new(algo)
|
14
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, secret, random_data)
|
15
|
+
|
16
|
+
"#{hmac}#{random_data}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_password_reset_token
|
20
|
+
token = SecureRandom.alphanumeric(12)
|
21
|
+
"#{token[0, 6]}-#{token[6, 6]}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "securial/auth/auth_encoder"
|
2
|
+
require "securial/auth/session_creator"
|
3
|
+
require "securial/auth/token_generator"
|
4
|
+
|
5
|
+
module Securial
|
6
|
+
module Auth
|
7
|
+
# This is a placeholder for the Auth module.
|
8
|
+
# It can be used to include authentication-related methods or constants
|
9
|
+
# that are not specific to encoding or session creation.
|
10
|
+
# Currently, it serves as a namespace for the AuthEncoder and SessionCreator modules.
|
11
|
+
end
|
12
|
+
end
|
@@ -1,33 +1,52 @@
|
|
1
|
-
|
1
|
+
require "securial/config/validation"
|
2
|
+
require "securial/helpers/regex_helper"
|
3
|
+
|
2
4
|
module Securial
|
3
5
|
module Config
|
4
|
-
VALID_SESSION_ENCRYPTION_ALGORITHMS = [:hs256, :hs384, :hs512].freeze
|
5
|
-
VALID_TIMESTAMP_OPTIONS = [:all, :admins_only, :none].freeze
|
6
|
-
VALID_RESPONSE_KEYS_FORMATS = [:snake_case, :lowerCamelCase, :UpperCamelCase].freeze
|
7
|
-
VALID_SECURITY_HEADERS = [:strict, :default, :none].freeze
|
8
|
-
|
9
6
|
class Configuration
|
10
|
-
def self.
|
7
|
+
def self.default_config_attributes # rubocop:disable Metrics/MethodLength
|
11
8
|
{
|
9
|
+
# General configuration
|
10
|
+
app_name: "Securial",
|
11
|
+
|
12
|
+
# Logger configuration
|
12
13
|
log_to_file: !Rails.env.test?,
|
13
14
|
log_to_stdout: !Rails.env.test?,
|
14
|
-
log_file_level: :
|
15
|
+
log_file_level: :debug,
|
15
16
|
log_stdout_level: :debug,
|
17
|
+
|
18
|
+
# Roles configuration
|
16
19
|
admin_role: :admin,
|
20
|
+
|
17
21
|
session_expiration_duration: 3.minutes,
|
18
22
|
session_secret: "secret",
|
19
23
|
session_algorithm: :hs256,
|
24
|
+
session_refresh_token_expires_in: 1.week,
|
25
|
+
|
26
|
+
# Mailer configuration
|
20
27
|
mailer_sender: "no-reply@example.com",
|
21
|
-
|
28
|
+
mailer_sign_up_enabled: true,
|
29
|
+
mailer_sign_up_subject: "SECURIAL: Welcome to Our Service",
|
30
|
+
mailer_sign_in_enabled: true,
|
31
|
+
mailer_sign_in_subject: "SECURIAL: Sign In Notification",
|
32
|
+
mailer_update_account_enabled: true,
|
33
|
+
mailer_update_account_subject: "SECURIAL: Account Update Notification",
|
34
|
+
mailer_forgot_password_subject: "SECURIAL: Password Reset Instructions",
|
35
|
+
|
36
|
+
# Password configuration
|
22
37
|
password_min_length: 8,
|
23
38
|
password_max_length: 128,
|
24
|
-
password_complexity: Securial::RegexHelper::PASSWORD_REGEX,
|
39
|
+
password_complexity: Securial::Helpers::RegexHelper::PASSWORD_REGEX,
|
25
40
|
password_expires: true,
|
26
41
|
password_expires_in: 90.days,
|
27
42
|
reset_password_token_expires_in: 2.hours,
|
28
43
|
reset_password_token_secret: "reset_secret",
|
29
|
-
|
44
|
+
|
45
|
+
# Response configuration
|
30
46
|
response_keys_format: :snake_case,
|
47
|
+
timestamps_in_response: :all,
|
48
|
+
|
49
|
+
# Security configuration
|
31
50
|
security_headers: :strict,
|
32
51
|
rate_limiting_enabled: true,
|
33
52
|
rate_limit_requests_per_minute: 60,
|
@@ -37,13 +56,12 @@ module Securial
|
|
37
56
|
end
|
38
57
|
|
39
58
|
def initialize
|
40
|
-
self.class.
|
41
|
-
instance_variable_set("@#{attr}",
|
59
|
+
self.class.default_config_attributes.each do |attr, default_value|
|
60
|
+
instance_variable_set("@#{attr}", default_value)
|
42
61
|
end
|
43
|
-
validate!
|
44
62
|
end
|
45
63
|
|
46
|
-
|
64
|
+
default_config_attributes.each_key do |attr|
|
47
65
|
define_method(attr) { instance_variable_get("@#{attr}") }
|
48
66
|
|
49
67
|
define_method("#{attr}=") do |value|
|
@@ -0,0 +1,29 @@
|
|
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
|
@@ -0,0 +1,24 @@
|
|
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
|
@@ -0,0 +1,91 @@
|
|
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
|
@@ -0,0 +1,37 @@
|
|
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
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,56 @@
|
|
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
|
@@ -0,0 +1,87 @@
|
|
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
|