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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f93334f493089fe07b825a71ccbe4c8eb2b3ae9b924e2e7cab16f4818d0bb4a
4
- data.tar.gz: 47e73ee83002a71737e3d7e16c31fe428652ddb9a6d4104eb2d622b9bc9dc666
3
+ metadata.gz: 9d961941c8221da3b247295215f1103d8321ae910dc7b0e3d5c1bff743e5bc3b
4
+ data.tar.gz: ffa192fdfc0060e769a635063a4f326ebcf1a720fb94d8304784328675dc6338
5
5
  SHA512:
6
- metadata.gz: 3108d25afcf5df41d3f7578b12fdb496402deb3ba7ce9e8485ed90e96e3d71dd7d4297c67dd49911d73ffd2a31100845e792e5e1670d3a35cbe877bad8066ba6
7
- data.tar.gz: 1c403ba8a424348b2ac80c0a46c7cc71e9e720bff838a735caac6d263884c3eab06480917d3bff8c8f7a7ed295bc4ecac1e5cf53caf609c361aa2a5159533e0e
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
  [![Gem Version](https://img.shields.io/gem/v/securial?logo=rubygems&logoColor=ffffff&logoSize=auto&label=version&color=violet&cacheSeconds=120)](https://rubygems.org/gems/securial)
4
- [![Gem Downloads](https://img.shields.io/gem/dt/securial.svg)](https://rubygems.org/gems/securial)
5
- [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/AlyBadawy/Securial?tab=MIT-1-ov-file#readme)
6
-
4
+ [![Downloads](https://img.shields.io/gem/dt/securial.svg)](https://rubygems.org/gems/securial)
7
5
  [![Tests](https://github.com/alybadawy/securial/actions/workflows/ci.yml/badge.svg)](https://github.com/alybadawy/securial/actions)
8
6
  [![Coveralls](https://img.shields.io/coverallsCoverage/github/AlyBadawy/Securial?branch=main&logo=coveralls&logoColor=%233F5767&labelColor=ddeedd)
9
7
  ](https://coveralls.io/github/AlyBadawy/Securial?branch=main)
10
8
 
11
- [![Documentation](https://img.shields.io/badge/yard-Documentation-blue?style=flat&logo=readthedocs&logoColor=%238CA1AF&label=yard&link=https%3A%2F%2Fwww.rubydoc.info%2Fgems%2Fsecurial%2Findex)](https://alybadawy.github.io/Securial/_index.html)
9
+
10
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/AlyBadawy/Securial?tab=MIT-1-ov-file#readme)
11
+ [![Documentation](https://img.shields.io/badge/yard-Documentation-cyan?style=flat&logo=readthedocs&logoColor=%238CA1AF&label=yard)](https://alybadawy.github.io/Securial/_index.html)
12
+ [![Wiki](https://img.shields.io/badge/Wiki-Github_Wiki-orange?style=flat&logo=wikibooks&logoColor=%23F5CD0E&label=Wiki)](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
- LOG_LEVELS = %i[debug info warn error fatal unknown].freeze
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
- SECURITY_HEADERS = %i[strict default none].freeze
7
- TIMESTAMP_OPTIONS = %i[all admins_only none].freeze
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