securial 1.0.2 → 1.0.3
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 +6 -4
- data/lib/securial/auth/auth_encoder.rb +78 -0
- data/lib/securial/auth/session_creator.rb +49 -0
- data/lib/securial/auth/token_generator.rb +74 -0
- data/lib/securial/config/signature.rb +116 -5
- data/lib/securial/config/validation.rb +91 -0
- data/lib/securial/helpers/key_transformer.rb +106 -0
- data/lib/securial/helpers/normalizing_helper.rb +69 -0
- data/lib/securial/helpers/regex_helper.rb +122 -0
- data/lib/securial/helpers/roles_helper.rb +71 -2
- data/lib/securial/middleware/request_tag_logger.rb +80 -0
- data/lib/securial/middleware/response_headers.rb +51 -3
- data/lib/securial/middleware/transform_request_keys.rb +143 -20
- data/lib/securial/middleware/transform_response_keys.rb +84 -4
- data/lib/securial/version.rb +1 -1
- metadata +20 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d961941c8221da3b247295215f1103d8321ae910dc7b0e3d5c1bff743e5bc3b
|
4
|
+
data.tar.gz: ffa192fdfc0060e769a635063a4f326ebcf1a720fb94d8304784328675dc6338
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84646cb2e5ed9a96f4f46baa4971086e62b7d8628e436c04e1a7ec30d6f772e74563d76163b933bd1c9d760620f763b9d96cf0bdfe1a1c43337155a8a85b1410
|
7
|
+
data.tar.gz: 4103a16e922ca508481402fd9492587ab71aed2604abea521b0f6a1ab8a6d42b576258540d02ec5a080e2f0becc60b8cf6229be19a203604e22984ec70bd30eb
|
data/README.md
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# Securial Gem
|
2
2
|
|
3
3
|
[](https://rubygems.org/gems/securial)
|
4
|
-
[](https://github.com/AlyBadawy/Securial?tab=MIT-1-ov-file#readme)
|
6
|
-
|
4
|
+
[](https://rubygems.org/gems/securial)
|
7
5
|
[](https://github.com/alybadawy/securial/actions)
|
8
6
|
[
|
9
7
|
](https://coveralls.io/github/AlyBadawy/Securial?branch=main)
|
10
8
|
|
11
|
-
|
9
|
+
|
10
|
+
[](https://github.com/AlyBadawy/Securial?tab=MIT-1-ov-file#readme)
|
11
|
+
[](https://alybadawy.github.io/Securial/_index.html)
|
12
|
+
[](https://github.com/alybadawy/securial/wiki)
|
13
|
+
|
12
14
|
|
13
15
|
---
|
14
16
|
|
@@ -1,10 +1,58 @@
|
|
1
|
+
# @title Securial Authentication Token Encoder
|
2
|
+
#
|
3
|
+
# JWT token encoding and decoding for session authentication.
|
4
|
+
#
|
5
|
+
# This module provides secure JWT token creation and validation for user sessions
|
6
|
+
# in the Securial authentication system. It handles the encoding of session data
|
7
|
+
# into JWT tokens and the secure decoding/validation of those tokens, including
|
8
|
+
# signature verification and claim validation.
|
9
|
+
#
|
10
|
+
# @example Encoding a session into a JWT token
|
11
|
+
# session = Securial::Session.find(session_id)
|
12
|
+
# token = Securial::Auth::AuthEncoder.encode(session)
|
13
|
+
# # => "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkwIiwic3ViIjoiM..."
|
14
|
+
#
|
15
|
+
# @example Decoding and validating a JWT token
|
16
|
+
# begin
|
17
|
+
# payload = Securial::Auth::AuthEncoder.decode(token)
|
18
|
+
# session_id = payload['jti']
|
19
|
+
# # Use session_id to retrieve session from database
|
20
|
+
# rescue Securial::Error::Auth::TokenDecodeError => e
|
21
|
+
# # Handle invalid token
|
22
|
+
# end
|
1
23
|
require "jwt"
|
2
24
|
|
3
25
|
module Securial
|
4
26
|
module Auth
|
27
|
+
# Handles JWT token encoding and decoding for session authentication.
|
28
|
+
#
|
29
|
+
# This module provides methods to securely encode session information into
|
30
|
+
# JWT tokens and decode/validate those tokens. It uses HMAC signing with
|
31
|
+
# a configurable secret and includes standard JWT claims for security.
|
32
|
+
#
|
33
|
+
# The tokens include:
|
34
|
+
# - Session ID (jti claim) for session lookup
|
35
|
+
# - Expiration time (exp claim) for automatic invalidation
|
36
|
+
# - Subject (sub claim) identifying the token type
|
37
|
+
# - Custom claims for IP address and user agent validation
|
38
|
+
#
|
39
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7519 JWT RFC
|
5
40
|
module AuthEncoder
|
6
41
|
extend self
|
7
42
|
|
43
|
+
# Encodes a session object into a signed JWT token.
|
44
|
+
#
|
45
|
+
# Creates a JWT token containing the session ID and metadata for later
|
46
|
+
# validation. The token is signed using the configured secret and algorithm.
|
47
|
+
#
|
48
|
+
# @param [Securial::Session] session The session object to encode
|
49
|
+
# @return [String, nil] The encoded JWT token, or nil if session is invalid
|
50
|
+
# @raise [Securial::Error::Auth::TokenEncodeError] If JWT encoding fails
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# session = Securial::Session.create!(user: current_user)
|
54
|
+
# token = AuthEncoder.encode(session)
|
55
|
+
# # => "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkw..."
|
8
56
|
def encode(session)
|
9
57
|
return nil unless session && session.class == Securial::Session
|
10
58
|
|
@@ -28,6 +76,21 @@ module Securial
|
|
28
76
|
end
|
29
77
|
end
|
30
78
|
|
79
|
+
# Decodes and validates a JWT token, returning the payload.
|
80
|
+
#
|
81
|
+
# Verifies the token signature, expiration, and other claims before
|
82
|
+
# returning the decoded payload. The token must have been signed with
|
83
|
+
# the current secret and must not be expired.
|
84
|
+
#
|
85
|
+
# @param [String] token The JWT token to decode and validate
|
86
|
+
# @return [Hash] The decoded token payload containing session information
|
87
|
+
# @raise [Securial::Error::Auth::TokenDecodeError] If token is invalid, expired, or malformed
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# payload = AuthEncoder.decode("eyJhbGciOiJIUzI1NiJ9...")
|
91
|
+
# session_id = payload['jti']
|
92
|
+
# ip_address = payload['ip']
|
93
|
+
# user_agent = payload['agent']
|
31
94
|
def decode(token)
|
32
95
|
begin
|
33
96
|
decoded = ::JWT.decode(token, secret, true, { algorithm: algorithm, verify_jti: true, iss: "securial" })
|
@@ -39,14 +102,29 @@ module Securial
|
|
39
102
|
|
40
103
|
private
|
41
104
|
|
105
|
+
# Returns the secret key used for JWT signing and verification.
|
106
|
+
#
|
107
|
+
# @return [String] The configured session secret
|
108
|
+
# @api private
|
109
|
+
#
|
42
110
|
def secret
|
43
111
|
Securial.configuration.session_secret
|
44
112
|
end
|
45
113
|
|
114
|
+
# Returns the algorithm used for JWT signing.
|
115
|
+
#
|
116
|
+
# @return [String] The configured session algorithm in uppercase
|
117
|
+
# @api private
|
118
|
+
#
|
46
119
|
def algorithm
|
47
120
|
Securial.configuration.session_algorithm.to_s.upcase
|
48
121
|
end
|
49
122
|
|
123
|
+
# Returns the token expiration duration.
|
124
|
+
#
|
125
|
+
# @return [ActiveSupport::Duration] The configured session expiration duration
|
126
|
+
# @api private
|
127
|
+
#
|
50
128
|
def expiry_duration
|
51
129
|
Securial.configuration.session_expiration_duration
|
52
130
|
end
|
@@ -1,8 +1,57 @@
|
|
1
|
+
# @title Securial Session Creator
|
2
|
+
#
|
3
|
+
# Session creation utilities for the Securial authentication system.
|
4
|
+
#
|
5
|
+
# This module provides functionality to create authenticated user sessions with
|
6
|
+
# proper token generation and metadata tracking. It handles the validation of
|
7
|
+
# user and request objects before creating database records and setting up
|
8
|
+
# the current session context.
|
9
|
+
#
|
10
|
+
# @example Creating a session for a user
|
11
|
+
# user = Securial::User.find_by(email: "user@example.com")
|
12
|
+
# session = Securial::Auth::SessionCreator.create_session!(user, request)
|
13
|
+
# # => #<Securial::Session id: "abc123", user_id: "user123", ...>
|
14
|
+
#
|
15
|
+
# @example Handling invalid inputs
|
16
|
+
# invalid_session = Securial::Auth::SessionCreator.create_session!(nil, request)
|
17
|
+
# # => nil
|
1
18
|
module Securial
|
2
19
|
module Auth
|
20
|
+
# Creates and manages user authentication sessions.
|
21
|
+
#
|
22
|
+
# This module provides methods to create new authenticated sessions for users,
|
23
|
+
# including proper validation of inputs, generation of refresh tokens, and
|
24
|
+
# setting up session metadata such as IP addresses and user agents.
|
25
|
+
#
|
26
|
+
# Created sessions are automatically set as the current session context and
|
27
|
+
# include all necessary tokens and expiration information for secure
|
28
|
+
# authentication management.
|
29
|
+
#
|
3
30
|
module SessionCreator
|
4
31
|
extend self
|
5
32
|
|
33
|
+
# Creates a new authenticated session for the given user and request.
|
34
|
+
#
|
35
|
+
# Validates the provided user and request objects, then creates a new session
|
36
|
+
# record with appropriate metadata and tokens. The newly created session is
|
37
|
+
# automatically set as the current session context.
|
38
|
+
#
|
39
|
+
# @param [Securial::User] user The user object to create a session for
|
40
|
+
# @param [ActionDispatch::Request] request The HTTP request object containing metadata
|
41
|
+
# @return [Securial::Session, nil] The created session object, or nil if validation fails
|
42
|
+
#
|
43
|
+
# @example Creating a session after successful authentication
|
44
|
+
# user = Securial::User.authenticate(email, password)
|
45
|
+
# if user
|
46
|
+
# session = SessionCreator.create_session!(user, request)
|
47
|
+
# # User is now authenticated with active session
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @example Handling validation failures
|
51
|
+
# # Invalid user (not persisted)
|
52
|
+
# new_user = Securial::User.new
|
53
|
+
# session = SessionCreator.create_session!(new_user, request)
|
54
|
+
# # => nil
|
6
55
|
def create_session!(user, request)
|
7
56
|
valid_user = user && user.is_a?(Securial::User) && user.persisted?
|
8
57
|
valid_request = request.is_a?(ActionDispatch::Request)
|
@@ -1,11 +1,48 @@
|
|
1
|
+
# @title Securial Token Generator
|
2
|
+
#
|
3
|
+
# Secure token generation utilities for the Securial authentication system.
|
4
|
+
#
|
5
|
+
# This module provides cryptographically secure token generation for various
|
6
|
+
# authentication purposes, including refresh tokens and password reset tokens.
|
7
|
+
# All tokens are generated using secure random sources and include appropriate
|
8
|
+
# entropy for their intended use cases.
|
9
|
+
#
|
10
|
+
# @example Generating a refresh token
|
11
|
+
# refresh_token = Securial::Auth::TokenGenerator.generate_refresh_token
|
12
|
+
# # => "a1b2c3d4e5f6...9876543210abcdef1234567890"
|
13
|
+
#
|
14
|
+
# @example Generating a password reset token
|
15
|
+
# reset_token = Securial::Auth::TokenGenerator.generate_password_reset_token
|
16
|
+
# # => "aBc123-DeF456"
|
17
|
+
#
|
1
18
|
require "openssl"
|
2
19
|
require "securerandom"
|
3
20
|
|
4
21
|
module Securial
|
5
22
|
module Auth
|
23
|
+
# Generates secure tokens for authentication operations.
|
24
|
+
#
|
25
|
+
# This module provides methods to generate cryptographically secure tokens
|
26
|
+
# for different authentication scenarios. All tokens use secure random
|
27
|
+
# generation and appropriate cryptographic techniques to ensure uniqueness
|
28
|
+
# and security.
|
6
29
|
module TokenGenerator
|
7
30
|
extend self
|
8
31
|
|
32
|
+
# Generates a secure refresh token using HMAC and random data.
|
33
|
+
#
|
34
|
+
# Creates a refresh token by combining an HMAC signature with random data,
|
35
|
+
# providing both integrity verification and sufficient entropy. The token
|
36
|
+
# is suitable for long-term storage and session refresh operations.
|
37
|
+
#
|
38
|
+
# @return [String] A secure refresh token (96 characters hexadecimal)
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# token = TokenGenerator.generate_refresh_token
|
42
|
+
# # => "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456789012345678901234567890abcdef"
|
43
|
+
#
|
44
|
+
# @see #generate_password_reset_token
|
45
|
+
#
|
9
46
|
def generate_refresh_token
|
10
47
|
secret = Securial.configuration.session_secret
|
11
48
|
algo = "SHA256"
|
@@ -17,10 +54,47 @@ module Securial
|
|
17
54
|
"#{hmac}#{random_data}"
|
18
55
|
end
|
19
56
|
|
57
|
+
# Generates a user-friendly password reset token.
|
58
|
+
#
|
59
|
+
# Creates a short, alphanumeric token formatted for easy user entry.
|
60
|
+
# The token is suitable for password reset flows where users need to
|
61
|
+
# manually enter the token from an email or SMS message.
|
62
|
+
#
|
63
|
+
# @return [String] A formatted password reset token (format: "ABC123-DEF456")
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# token = TokenGenerator.generate_password_reset_token
|
67
|
+
# # => "aBc123-DeF456"
|
68
|
+
#
|
69
|
+
# @note This token has lower entropy than refresh tokens and should have
|
70
|
+
# shorter expiration times and rate limiting protection.
|
71
|
+
#
|
72
|
+
# @see #generate_refresh_token
|
73
|
+
#
|
20
74
|
def generate_password_reset_token
|
21
75
|
token = SecureRandom.alphanumeric(12)
|
22
76
|
"#{token[0, 6]}-#{token[6, 6]}"
|
23
77
|
end
|
78
|
+
|
79
|
+
# Generates a URL-safe friendly token for general use.
|
80
|
+
#
|
81
|
+
# Creates a secure, URL-safe token suitable for various authentication
|
82
|
+
# operations that require a balance between security and usability.
|
83
|
+
#
|
84
|
+
# @param [Integer] length The desired length of the generated token (default: 20)
|
85
|
+
# @return [String] A URL-safe token containing letters, numbers, and safe symbols
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# token = TokenGenerator.friendly_token
|
89
|
+
# # => "aBcDeF123456GhIjKl78"
|
90
|
+
#
|
91
|
+
# @example With custom length
|
92
|
+
# token = TokenGenerator.friendly_token(32)
|
93
|
+
# # => "aBcDeF123456GhIjKl789012MnOpQr34"
|
94
|
+
#
|
95
|
+
def friendly_token(length = 20)
|
96
|
+
SecureRandom.urlsafe_base64(length).tr("lIO0", "sxyz")[0, length]
|
97
|
+
end
|
24
98
|
end
|
25
99
|
end
|
26
100
|
end
|
@@ -1,14 +1,74 @@
|
|
1
|
+
# @title Securial Configuration Signature
|
2
|
+
#
|
3
|
+
# Configuration schema definition and validation rules for the Securial framework.
|
4
|
+
#
|
5
|
+
# This module defines the complete configuration schema for Securial, including
|
6
|
+
# all available configuration options, their types, default values, and validation
|
7
|
+
# rules. It serves as the single source of truth for what configuration options
|
8
|
+
# are available and how they should be validated.
|
9
|
+
#
|
10
|
+
# @example Getting the complete configuration schema
|
11
|
+
# schema = Securial::Config::Signature.config_signature
|
12
|
+
# # => { app_name: { type: String, required: true, default: "Securial" }, ... }
|
13
|
+
#
|
14
|
+
# @example Getting default configuration values
|
15
|
+
# defaults = Securial::Config::Signature.default_config_attributes
|
16
|
+
# # => { app_name: "Securial", log_to_file: true, ... }
|
17
|
+
#
|
1
18
|
module Securial
|
2
19
|
module Config
|
20
|
+
# Configuration schema definition and validation rules.
|
21
|
+
#
|
22
|
+
# This module provides the complete schema for Securial's configuration system,
|
23
|
+
# defining all available options, their types, validation rules, and default values.
|
24
|
+
# It's used by the configuration system to validate user-provided settings.
|
25
|
+
#
|
3
26
|
module Signature
|
4
|
-
|
27
|
+
# Valid log levels for the logging system.
|
28
|
+
#
|
29
|
+
# @return [Array<Symbol>] Available log levels from least to most severe
|
30
|
+
#
|
31
|
+
LOG_LEVELS = %i[debug info warn error fatal unknown].freeze
|
32
|
+
|
33
|
+
# Supported JWT signing algorithms for session tokens.
|
34
|
+
#
|
35
|
+
# @return [Array<Symbol>] HMAC algorithms supported for JWT signing
|
36
|
+
#
|
5
37
|
SESSION_ALGORITHMS = %i[hs256 hs384 hs512].freeze
|
6
|
-
|
7
|
-
|
38
|
+
|
39
|
+
# Security header configuration options.
|
40
|
+
#
|
41
|
+
# @return [Array<Symbol>] Available security header policies
|
42
|
+
#
|
43
|
+
SECURITY_HEADERS = %i[strict default none].freeze
|
44
|
+
|
45
|
+
# Timestamp inclusion options for API responses.
|
46
|
+
#
|
47
|
+
# @return [Array<Symbol>] Who should see timestamps in responses
|
48
|
+
#
|
49
|
+
TIMESTAMP_OPTIONS = %i[all admins_only none].freeze
|
50
|
+
|
51
|
+
# Available key format transformations for API responses.
|
52
|
+
#
|
53
|
+
# @return [Array<Symbol>] Supported key case formats
|
54
|
+
#
|
8
55
|
RESPONSE_KEYS_FORMATS = %i[snake_case lowerCamelCase UpperCamelCase].freeze
|
9
56
|
|
10
57
|
extend self
|
11
58
|
|
59
|
+
# Returns the complete configuration schema for Securial.
|
60
|
+
#
|
61
|
+
# Combines all configuration sections into a single schema hash that defines
|
62
|
+
# every available configuration option, its type, validation rules, and
|
63
|
+
# default value.
|
64
|
+
#
|
65
|
+
# @return [Hash] Complete configuration schema with validation rules
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# schema = config_signature
|
69
|
+
# app_name_config = schema[:app_name]
|
70
|
+
# # => { type: String, required: true, default: "Securial" }
|
71
|
+
#
|
12
72
|
def config_signature
|
13
73
|
[
|
14
74
|
general_signature,
|
@@ -22,6 +82,18 @@ module Securial
|
|
22
82
|
].reduce({}, :merge)
|
23
83
|
end
|
24
84
|
|
85
|
+
# Extracts default values from the configuration schema.
|
86
|
+
#
|
87
|
+
# Transforms the complete configuration schema to return only the default
|
88
|
+
# values for each configuration option, suitable for initializing a new
|
89
|
+
# configuration instance.
|
90
|
+
#
|
91
|
+
# @return [Hash] Default values for all configuration options
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# defaults = default_config_attributes
|
95
|
+
# # => { app_name: "Securial", session_expiration_duration: 3.minutes, ... }
|
96
|
+
#
|
25
97
|
def default_config_attributes
|
26
98
|
config_signature.transform_values do |options|
|
27
99
|
options[:default]
|
@@ -30,12 +102,22 @@ module Securial
|
|
30
102
|
|
31
103
|
private
|
32
104
|
|
105
|
+
# General application configuration options.
|
106
|
+
#
|
107
|
+
# @return [Hash] Schema for general application settings
|
108
|
+
# @api private
|
109
|
+
#
|
33
110
|
def general_signature
|
34
111
|
{
|
35
112
|
app_name: { type: String, required: true, default: "Securial" },
|
36
113
|
}
|
37
114
|
end
|
38
115
|
|
116
|
+
# Logging system configuration options.
|
117
|
+
#
|
118
|
+
# @return [Hash] Schema for logging configuration
|
119
|
+
# @api private
|
120
|
+
#
|
39
121
|
def logger_signature
|
40
122
|
{
|
41
123
|
log_to_file: { type: [TrueClass, FalseClass], required: true, default: Rails.env.test? ? false : true },
|
@@ -45,12 +127,22 @@ module Securial
|
|
45
127
|
}
|
46
128
|
end
|
47
129
|
|
130
|
+
# User role and permission configuration options.
|
131
|
+
#
|
132
|
+
# @return [Hash] Schema for role management settings
|
133
|
+
# @api private
|
134
|
+
#
|
48
135
|
def roles_signature
|
49
136
|
{
|
50
137
|
admin_role: { type: Symbol, required: true, default: :admin },
|
51
138
|
}
|
52
139
|
end
|
53
140
|
|
141
|
+
# Session and JWT token configuration options.
|
142
|
+
#
|
143
|
+
# @return [Hash] Schema for session management settings
|
144
|
+
# @api private
|
145
|
+
#
|
54
146
|
def session_signature
|
55
147
|
{
|
56
148
|
session_expiration_duration: { type: ActiveSupport::Duration, required: true, default: 3.minutes },
|
@@ -60,6 +152,11 @@ module Securial
|
|
60
152
|
}
|
61
153
|
end
|
62
154
|
|
155
|
+
# Email and notification configuration options.
|
156
|
+
#
|
157
|
+
# @return [Hash] Schema for mailer settings
|
158
|
+
# @api private
|
159
|
+
#
|
63
160
|
def mailer_signature
|
64
161
|
{
|
65
162
|
mailer_sender: { type: String, required: true, default: "no-reply@example.com" },
|
@@ -73,6 +170,11 @@ module Securial
|
|
73
170
|
}
|
74
171
|
end
|
75
172
|
|
173
|
+
# Password policy and security configuration options.
|
174
|
+
#
|
175
|
+
# @return [Hash] Schema for password management settings
|
176
|
+
# @api private
|
177
|
+
#
|
76
178
|
def password_signature
|
77
179
|
{
|
78
180
|
password_min_length: { type: Numeric, required: true, default: 8 },
|
@@ -85,14 +187,23 @@ module Securial
|
|
85
187
|
}
|
86
188
|
end
|
87
189
|
|
190
|
+
# API response formatting configuration options.
|
191
|
+
#
|
192
|
+
# @return [Hash] Schema for response formatting settings
|
193
|
+
# @api private
|
194
|
+
#
|
88
195
|
def response_signature
|
89
196
|
{
|
90
|
-
response_keys_format: { type: Symbol, required: true, allowed_values:
|
91
|
-
RESPONSE_KEYS_FORMATS, default: :snake_case, },
|
197
|
+
response_keys_format: { type: Symbol, required: true, allowed_values: RESPONSE_KEYS_FORMATS, default: :snake_case },
|
92
198
|
timestamps_in_response: { type: Symbol, required: true, allowed_values: TIMESTAMP_OPTIONS, default: :all },
|
93
199
|
}
|
94
200
|
end
|
95
201
|
|
202
|
+
# Security and rate limiting configuration options.
|
203
|
+
#
|
204
|
+
# @return [Hash] Schema for security settings
|
205
|
+
# @api private
|
206
|
+
#
|
96
207
|
def security_signature
|
97
208
|
{
|
98
209
|
security_headers: { type: Symbol, required: true, allowed_values: SECURITY_HEADERS, default: :strict },
|
@@ -1,9 +1,51 @@
|
|
1
|
+
# @title Securial Configuration Validation
|
2
|
+
#
|
3
|
+
# Configuration validation utilities for the Securial framework.
|
4
|
+
#
|
5
|
+
# This module provides comprehensive validation for Securial configuration settings,
|
6
|
+
# ensuring that all required fields are present, types are correct, values are within
|
7
|
+
# acceptable ranges, and cross-field dependencies are satisfied. It validates against
|
8
|
+
# the schema defined in Securial::Config::Signature.
|
9
|
+
#
|
10
|
+
# @example Validating a configuration object
|
11
|
+
# config = Securial::Configuration.new
|
12
|
+
# begin
|
13
|
+
# Securial::Config::Validation.validate_all!(config)
|
14
|
+
# rescue Securial::Error::Config::InvalidConfigurationError => e
|
15
|
+
# Rails.logger.error("Configuration error: #{e.message}")
|
16
|
+
# end
|
17
|
+
#
|
1
18
|
require "securial/logger"
|
2
19
|
|
3
20
|
module Securial
|
4
21
|
module Config
|
22
|
+
# Configuration validation and verification utilities.
|
23
|
+
#
|
24
|
+
# This module provides methods to validate Securial configuration objects
|
25
|
+
# against the defined schema, ensuring type safety, required field presence,
|
26
|
+
# and business logic constraints are met before the application starts.
|
27
|
+
#
|
5
28
|
module Validation
|
6
29
|
extend self
|
30
|
+
|
31
|
+
# Validates all configuration settings against the schema.
|
32
|
+
#
|
33
|
+
# Performs comprehensive validation of the provided configuration object,
|
34
|
+
# including required field checks, type validation, value constraints,
|
35
|
+
# and cross-field dependency validation.
|
36
|
+
#
|
37
|
+
# @param [Securial::Configuration] securial_config The configuration object to validate
|
38
|
+
# @return [void]
|
39
|
+
# @raise [Securial::Error::Config::InvalidConfigurationError] If any validation fails
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# config = Securial::Configuration.new
|
43
|
+
# config.app_name = "MyApp"
|
44
|
+
# config.session_secret = "my-secret-key"
|
45
|
+
#
|
46
|
+
# Validation.validate_all!(config)
|
47
|
+
# # Configuration is valid and ready to use
|
48
|
+
#
|
7
49
|
def validate_all!(securial_config)
|
8
50
|
signature = Securial::Config::Signature.config_signature
|
9
51
|
|
@@ -14,13 +56,26 @@ module Securial
|
|
14
56
|
|
15
57
|
private
|
16
58
|
|
59
|
+
# Validates that all required configuration fields are present.
|
60
|
+
#
|
61
|
+
# Checks both unconditionally required fields and conditionally required
|
62
|
+
# fields based on other configuration values.
|
63
|
+
#
|
64
|
+
# @param [Hash] signature The configuration schema from Signature
|
65
|
+
# @param [Securial::Configuration] config The configuration object to validate
|
66
|
+
# @return [void]
|
67
|
+
# @raise [Securial::Error::Config::InvalidConfigurationError] If required fields are missing
|
68
|
+
# @api private
|
69
|
+
#
|
17
70
|
def validate_required_fields!(signature, config)
|
18
71
|
signature.each do |key, options|
|
19
72
|
value = config.send(key)
|
20
73
|
required = options[:required]
|
74
|
+
|
21
75
|
if required == true && value.nil?
|
22
76
|
raise_error("#{key} is required but not provided.")
|
23
77
|
elsif required.is_a?(String)
|
78
|
+
# Handle conditional requirements based on other config values
|
24
79
|
dynamic_required = config.send(required)
|
25
80
|
signature[key][:required] = dynamic_required
|
26
81
|
if dynamic_required && value.nil?
|
@@ -30,36 +85,72 @@ module Securial
|
|
30
85
|
end
|
31
86
|
end
|
32
87
|
|
88
|
+
# Validates types and value constraints for configuration fields.
|
89
|
+
#
|
90
|
+
# Ensures that configuration values match their expected types and fall
|
91
|
+
# within acceptable ranges or allowed value sets.
|
92
|
+
#
|
93
|
+
# @param [Hash] signature The configuration schema from Signature
|
94
|
+
# @param [Securial::Configuration] config The configuration object to validate
|
95
|
+
# @return [void]
|
96
|
+
# @raise [Securial::Error::Config::InvalidConfigurationError] If types or values are invalid
|
97
|
+
# @api private
|
98
|
+
#
|
33
99
|
def validate_types_and_values!(signature, config)
|
34
100
|
signature.each do |key, options|
|
35
101
|
next unless signature[key][:required]
|
102
|
+
|
36
103
|
value = config.send(key)
|
37
104
|
types = Array(options[:type])
|
38
105
|
|
106
|
+
# Type validation
|
39
107
|
unless types.any? { |type| value.is_a?(type) }
|
40
108
|
raise_error("#{key} must be of type(s) #{types.join(', ')}, but got #{value.class}.")
|
41
109
|
end
|
42
110
|
|
111
|
+
# Duration-specific validation
|
43
112
|
if options[:type] == ActiveSupport::Duration && value <= 0
|
44
113
|
raise_error("#{key} must be a positive duration, but got #{value}.")
|
45
114
|
end
|
46
115
|
|
116
|
+
# Numeric value validation
|
47
117
|
if options[:type] == Numeric && value < 0
|
48
118
|
raise_error("#{key} must be a non-negative numeric value, but got #{value}.")
|
49
119
|
end
|
50
120
|
|
121
|
+
# Allowed values validation
|
51
122
|
if options[:allowed_values] && options[:allowed_values].exclude?(value)
|
52
123
|
raise_error("#{key} must be one of #{options[:allowed_values].join(', ')}, but got #{value}.")
|
53
124
|
end
|
54
125
|
end
|
55
126
|
end
|
56
127
|
|
128
|
+
# Validates password length configuration constraints.
|
129
|
+
#
|
130
|
+
# Ensures that password minimum length does not exceed maximum length,
|
131
|
+
# which would create an impossible constraint for users.
|
132
|
+
#
|
133
|
+
# @param [Securial::Configuration] config The configuration object to validate
|
134
|
+
# @return [void]
|
135
|
+
# @raise [Securial::Error::Config::InvalidConfigurationError] If password lengths are invalid
|
136
|
+
# @api private
|
137
|
+
#
|
57
138
|
def validate_password_lengths!(config)
|
58
139
|
if config.password_min_length > config.password_max_length
|
59
140
|
raise_error("password_min_length cannot be greater than password_max_length.")
|
60
141
|
end
|
61
142
|
end
|
62
143
|
|
144
|
+
# Logs error message and raises configuration error.
|
145
|
+
#
|
146
|
+
# Provides a consistent way to handle validation failures by logging
|
147
|
+
# the error at fatal level and raising the appropriate exception.
|
148
|
+
#
|
149
|
+
# @param [String] msg The error message to log and include in the exception
|
150
|
+
# @return [void]
|
151
|
+
# @raise [Securial::Error::Config::InvalidConfigurationError] Always raises with the provided message
|
152
|
+
# @api private
|
153
|
+
#
|
63
154
|
def raise_error(msg)
|
64
155
|
Securial.logger.fatal msg
|
65
156
|
raise Securial::Error::Config::InvalidConfigurationError, msg
|