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.
@@ -1,8 +1,56 @@
1
+ # @title Securial Key Transformer Helpers
2
+ #
3
+ # Key transformation utilities for API response formatting in the Securial framework.
4
+ #
5
+ # This module provides utilities for transforming hash keys between different
6
+ # naming conventions (snake_case, camelCase, PascalCase) to ensure API responses
7
+ # follow consistent formatting standards regardless of Ruby's internal conventions.
8
+ #
9
+ # @example Converting keys to camelCase
10
+ # data = { user_name: "john", email_address: "john@example.com" }
11
+ # transformed = KeyTransformer.deep_transform_keys(data) { |key| KeyTransformer.camelize(key, :lowerCamelCase) }
12
+ # # => { userName: "john", emailAddress: "john@example.com" }
13
+ #
14
+ # @example Converting nested structures
15
+ # nested = { user: { first_name: "John", last_name: "Doe" } }
16
+ # camelized = KeyTransformer.deep_transform_keys(nested) { |key| KeyTransformer.camelize(key, :lowerCamelCase) }
17
+ # # => { user: { firstName: "John", lastName: "Doe" } }
18
+ #
1
19
  module Securial
2
20
  module Helpers
21
+ # Transforms hash keys between different naming conventions.
22
+ #
23
+ # This module provides methods to convert between snake_case (Ruby convention),
24
+ # camelCase (JavaScript convention), and PascalCase (C# convention) for API
25
+ # response formatting. It supports deep transformation of nested structures.
26
+ #
3
27
  module KeyTransformer
4
28
  extend self
5
29
 
30
+ # Converts a string to camelCase or PascalCase format.
31
+ #
32
+ # Transforms snake_case strings into camelCase variants based on the
33
+ # specified format. Useful for converting Ruby hash keys to JavaScript
34
+ # or other language conventions.
35
+ #
36
+ # @param [String, Symbol] str The string to transform
37
+ # @param [Symbol] format The target camelCase format
38
+ # @option format [Symbol] :lowerCamelCase Converts to lowerCamelCase (firstName)
39
+ # @option format [Symbol] :UpperCamelCase Converts to PascalCase (FirstName)
40
+ # @return [String] The transformed string, or original if format not recognized
41
+ #
42
+ # @example Converting to lowerCamelCase
43
+ # KeyTransformer.camelize("user_name", :lowerCamelCase)
44
+ # # => "userName"
45
+ #
46
+ # @example Converting to PascalCase
47
+ # KeyTransformer.camelize("email_address", :UpperCamelCase)
48
+ # # => "EmailAddress"
49
+ #
50
+ # @example Unrecognized format returns original
51
+ # KeyTransformer.camelize("user_name", :snake_case)
52
+ # # => "user_name"
53
+ #
6
54
  def camelize(str, format)
7
55
  case format
8
56
  when :lowerCamelCase
@@ -14,10 +62,68 @@ module Securial
14
62
  end
15
63
  end
16
64
 
65
+ # Converts a camelCase or PascalCase string to snake_case.
66
+ #
67
+ # Transforms camelCase or PascalCase strings back to Ruby's snake_case
68
+ # convention. Useful for converting API input keys to Ruby conventions.
69
+ #
70
+ # @param [String, Symbol] str The string to transform
71
+ # @return [String] The snake_case version of the string
72
+ #
73
+ # @example Converting from camelCase
74
+ # KeyTransformer.underscore("userName")
75
+ # # => "user_name"
76
+ #
77
+ # @example Converting from PascalCase
78
+ # KeyTransformer.underscore("EmailAddress")
79
+ # # => "email_address"
80
+ #
17
81
  def self.underscore(str)
18
82
  str.to_s.underscore
19
83
  end
20
84
 
85
+ # Recursively transforms all keys in a nested data structure.
86
+ #
87
+ # Applies a key transformation block to all hash keys in a deeply nested
88
+ # structure containing hashes, arrays, and other objects. The transformation
89
+ # preserves the structure while only modifying the keys.
90
+ #
91
+ # @param [Object] obj The object to transform (Hash, Array, or other)
92
+ # @yieldparam [String, Symbol] key Each hash key to be transformed
93
+ # @yieldreturn [String, Symbol] The transformed key
94
+ # @return [Object] The transformed object with modified keys
95
+ #
96
+ # @example Transforming a simple hash
97
+ # data = { user_name: "john", user_email: "john@example.com" }
98
+ # KeyTransformer.deep_transform_keys(data) { |key| key.to_s.upcase }
99
+ # # => { "USER_NAME" => "john", "USER_EMAIL" => "john@example.com" }
100
+ #
101
+ # @example Transforming nested structures
102
+ # nested = {
103
+ # user_info: {
104
+ # first_name: "John",
105
+ # addresses: [
106
+ # { street_name: "Main St", zip_code: "12345" }
107
+ # ]
108
+ # }
109
+ # }
110
+ # result = KeyTransformer.deep_transform_keys(nested) { |key|
111
+ # KeyTransformer.camelize(key, :lowerCamelCase)
112
+ # }
113
+ # # => {
114
+ # # userInfo: {
115
+ # # firstName: "John",
116
+ # # addresses: [
117
+ # # { streetName: "Main St", zipCode: "12345" }
118
+ # # ]
119
+ # # }
120
+ # # }
121
+ #
122
+ # @example Non-hash objects are preserved
123
+ # mixed = ["string", 123, { user_name: "john" }]
124
+ # KeyTransformer.deep_transform_keys(mixed) { |key| key.to_s.upcase }
125
+ # # => ["string", 123, { "USER_NAME" => "john" }]
126
+ #
21
127
  def self.deep_transform_keys(obj, &block)
22
128
  case obj
23
129
  when Hash
@@ -1,14 +1,83 @@
1
+ # @title Securial Normalizing Helpers
2
+ #
3
+ # Data normalization utilities for the Securial framework.
4
+ #
5
+ # This module provides utilities for normalizing user input data to ensure
6
+ # consistency across the application. It handles common normalization tasks
7
+ # like email address formatting and role name standardization to prevent
8
+ # data inconsistencies and improve user experience.
9
+ #
10
+ # @example Normalizing email addresses
11
+ # email = NormalizingHelper.normalize_email_address(" USER@EXAMPLE.COM ")
12
+ # # => "user@example.com"
13
+ #
14
+ # @example Normalizing role names
15
+ # role = NormalizingHelper.normalize_role_name(" admin user ")
16
+ # # => "Admin User"
17
+ #
1
18
  module Securial
2
19
  module Helpers
20
+ # Provides data normalization methods for common input sanitization.
21
+ #
22
+ # This module contains methods to normalize various types of user input
23
+ # to ensure data consistency and prevent common formatting issues that
24
+ # could lead to duplicate records or authentication problems.
25
+ #
3
26
  module NormalizingHelper
4
27
  extend self
5
28
 
29
+ # Normalizes an email address for consistent storage and comparison.
30
+ #
31
+ # Strips whitespace and converts to lowercase to ensure email addresses
32
+ # are stored consistently regardless of how users input them. This prevents
33
+ # duplicate accounts and authentication issues caused by case sensitivity.
34
+ #
35
+ # @param [String] email The email address to normalize
36
+ # @return [String] The normalized email address, or empty string if input is empty
37
+ #
38
+ # @example Standard normalization
39
+ # normalize_email_address(" USER@EXAMPLE.COM ")
40
+ # # => "user@example.com"
41
+ #
42
+ # @example Handling empty input
43
+ # normalize_email_address("")
44
+ # # => ""
45
+ #
46
+ # @example Preserving valid format
47
+ # normalize_email_address("user@example.com")
48
+ # # => "user@example.com"
49
+ #
6
50
  def normalize_email_address(email)
7
51
  return "" if email.empty?
8
52
 
9
53
  email.strip.downcase
10
54
  end
11
55
 
56
+ # Normalizes a role name for consistent display and storage.
57
+ #
58
+ # Strips whitespace, converts to lowercase, then applies title case formatting
59
+ # to ensure role names are displayed consistently across the application.
60
+ # This helps maintain a professional appearance and prevents formatting issues.
61
+ #
62
+ # @param [String] role_name The role name to normalize
63
+ # @return [String] The normalized role name in title case, or empty string if input is empty
64
+ #
65
+ # @example Standard normalization
66
+ # normalize_role_name(" admin user ")
67
+ # # => "Admin User"
68
+ #
69
+ # @example Handling mixed case input
70
+ # normalize_role_name("SUPER_ADMIN")
71
+ # # => "Super Admin"
72
+ #
73
+ # @example Handling empty input
74
+ # normalize_role_name("")
75
+ # # => ""
76
+ #
77
+ # @example Single word roles
78
+ # normalize_role_name("admin")
79
+ # # => "Admin"
80
+ #
12
81
  def normalize_role_name(role_name)
13
82
  return "" if role_name.empty?
14
83
 
@@ -1,18 +1,140 @@
1
+ # @title Securial Regex Helpers
2
+ #
3
+ # Regular expression utilities for data validation in the Securial framework.
4
+ #
5
+ # This module provides commonly used regular expressions and validation methods
6
+ # for user input such as email addresses, usernames, and passwords. It ensures
7
+ # consistent validation rules across the application and helps maintain data
8
+ # integrity and security standards.
9
+ #
10
+ # @example Validating an email address
11
+ # RegexHelper.valid_email?("user@example.com")
12
+ # # => true
13
+ #
14
+ # @example Validating a username
15
+ # RegexHelper.valid_username?("john_doe123")
16
+ # # => true
17
+ #
18
+ # @example Checking password complexity
19
+ # password = "MyPass123!"
20
+ # password.match?(RegexHelper::PASSWORD_REGEX)
21
+ # # => true
22
+ #
23
+ require "uri"
24
+
1
25
  module Securial
2
26
  module Helpers
27
+ # Regular expression patterns and validation methods for common data types.
28
+ #
29
+ # This module centralizes regex patterns used throughout Securial for
30
+ # validating user input. It provides both the raw regex patterns as
31
+ # constants and convenience methods for validation.
32
+ #
3
33
  module RegexHelper
34
+ # RFC-compliant email address validation pattern.
35
+ #
36
+ # Uses Ruby's built-in URI::MailTo::EMAIL_REGEXP which follows RFC 3696
37
+ # specifications for email address validation.
38
+ #
39
+ # @return [Regexp] Email validation regular expression
40
+ #
4
41
  EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP
42
+
43
+ # Username validation pattern with specific formatting rules.
44
+ #
45
+ # Enforces the following username requirements:
46
+ # - Must start with a letter (not a number)
47
+ # - Can contain letters, numbers, periods, and underscores
48
+ # - Cannot have consecutive periods or underscores
49
+ # - Must end with a letter or number
50
+ # - Minimum length of 2 characters
51
+ #
52
+ # @return [Regexp] Username validation regular expression
53
+ #
5
54
  USERNAME_REGEX = /\A(?![0-9])[a-zA-Z](?:[a-zA-Z0-9]|[._](?![._]))*[a-zA-Z0-9]\z/
55
+
56
+ # Password complexity validation pattern.
57
+ #
58
+ # Enforces strong password requirements including:
59
+ # - At least one digit (0-9)
60
+ # - At least one lowercase letter (a-z)
61
+ # - At least one uppercase letter (A-Z)
62
+ # - At least one special character (non-alphanumeric)
63
+ # - Must start with a letter
64
+ #
65
+ # @return [Regexp] Password complexity validation regular expression
66
+ #
6
67
  PASSWORD_REGEX = %r{\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])[a-zA-Z].*\z}
7
68
 
8
69
  extend self
70
+
71
+ # Validates if an email address matches the RFC-compliant pattern.
72
+ #
73
+ # Uses the EMAIL_REGEX pattern to check if the provided email address
74
+ # follows proper email formatting standards.
75
+ #
76
+ # @param [String] email The email address to validate
77
+ # @return [Boolean] true if the email is valid, false otherwise
78
+ #
79
+ # @example Valid email addresses
80
+ # valid_email?("user@example.com") # => true
81
+ # valid_email?("test.email@domain.co.uk") # => true
82
+ # valid_email?("user+tag@example.org") # => true
83
+ #
84
+ # @example Invalid email addresses
85
+ # valid_email?("invalid.email") # => false
86
+ # valid_email?("@example.com") # => false
87
+ # valid_email?("user@") # => false
88
+ #
9
89
  def valid_email?(email)
10
90
  email.match?(EMAIL_REGEX)
11
91
  end
12
92
 
93
+ # Validates if a username follows the defined formatting rules.
94
+ #
95
+ # Uses the USERNAME_REGEX pattern to check if the provided username
96
+ # meets the application's username requirements.
97
+ #
98
+ # @param [String] username The username to validate
99
+ # @return [Boolean] true if the username is valid, false otherwise
100
+ #
101
+ # @example Valid usernames
102
+ # valid_username?("john_doe") # => true
103
+ # valid_username?("user123") # => true
104
+ # valid_username?("test.user") # => true
105
+ # valid_username?("a1") # => true
106
+ #
107
+ # @example Invalid usernames
108
+ # valid_username?("123user") # => false (starts with number)
109
+ # valid_username?("user__name") # => false (consecutive underscores)
110
+ # valid_username?("user.") # => false (ends with period)
111
+ # valid_username?("a") # => false (too short)
112
+ #
13
113
  def valid_username?(username)
14
114
  username.match?(USERNAME_REGEX)
15
115
  end
116
+
117
+ # Validates if a password meets complexity requirements.
118
+ #
119
+ # Uses the PASSWORD_REGEX pattern to check if the provided password
120
+ # meets the application's security standards.
121
+ #
122
+ # @param [String] password The password to validate
123
+ # @return [Boolean] true if the password meets complexity requirements, false otherwise
124
+ #
125
+ # @example Valid passwords
126
+ # valid_password?("MyPass123!") # => true
127
+ # valid_password?("Secure@2024") # => true
128
+ # valid_password?("aB3#defgh") # => true
129
+ #
130
+ # @example Invalid passwords
131
+ # valid_password?("password") # => false (no uppercase, number, or special char)
132
+ # valid_password?("PASSWORD123") # => false (no lowercase or special char)
133
+ # valid_password?("123Password!") # => false (starts with number)
134
+ #
135
+ def valid_password?(password)
136
+ password.match?(PASSWORD_REGEX)
137
+ end
16
138
  end
17
139
  end
18
140
  end
@@ -1,13 +1,82 @@
1
+ # @title Securial Roles Helper
2
+ #
3
+ # Role and permission utilities for the Securial framework.
4
+ #
5
+ # This module provides helper methods for working with user roles and permissions
6
+ # throughout the application. It handles role name formatting, namespace generation,
7
+ # and other role-related operations to ensure consistency across the system.
8
+ #
9
+ # @example Getting the admin namespace for routing
10
+ # RolesHelper.protected_namespace
11
+ # # => "admins" (if admin_role is :admin)
12
+ #
13
+ # @example Getting a formatted admin role name for display
14
+ # RolesHelper.titleized_admin_role
15
+ # # => "Super Admin" (if admin_role is :super_admin)
16
+ #
1
17
  module Securial
2
18
  module Helpers
19
+ # Helper methods for role management and formatting.
20
+ #
21
+ # This module provides utility methods for working with user roles,
22
+ # including namespace generation for protected routes and role name
23
+ # formatting for user interfaces. It centralizes role-related logic
24
+ # to ensure consistent behavior across the application.
25
+ #
3
26
  module RolesHelper
4
- # This module provides helper methods related to roles.
5
- # It can be extended with additional role-related functionality as needed.
6
27
  extend self
28
+
29
+ # Generates a namespace string for protected admin routes.
30
+ #
31
+ # Converts the configured admin role into a pluralized, underscored
32
+ # namespace suitable for use in Rails routing and controller organization.
33
+ # This ensures admin routes are consistently organized under the
34
+ # appropriate namespace.
35
+ #
36
+ # @return [String] The pluralized, underscored admin role namespace
37
+ #
38
+ # @example With default admin role
39
+ # # When Securial.configuration.admin_role = :admin
40
+ # protected_namespace
41
+ # # => "admins"
42
+ #
43
+ # @example With custom admin role
44
+ # # When Securial.configuration.admin_role = :super_admin
45
+ # protected_namespace
46
+ # # => "super_admins"
47
+ #
48
+ # @example With spaced role name
49
+ # # When Securial.configuration.admin_role = "Site Administrator"
50
+ # protected_namespace
51
+ # # => "site_administrators"
52
+ #
7
53
  def protected_namespace
8
54
  Securial.configuration.admin_role.to_s.strip.underscore.pluralize
9
55
  end
10
56
 
57
+ # Formats the admin role name for user interface display.
58
+ #
59
+ # Converts the configured admin role into a human-readable, title-cased
60
+ # string suitable for display in user interfaces, form labels, and
61
+ # user-facing messages.
62
+ #
63
+ # @return [String] The title-cased admin role name
64
+ #
65
+ # @example With default admin role
66
+ # # When Securial.configuration.admin_role = :admin
67
+ # titleized_admin_role
68
+ # # => "Admin"
69
+ #
70
+ # @example With underscored role name
71
+ # # When Securial.configuration.admin_role = :super_admin
72
+ # titleized_admin_role
73
+ # # => "Super Admin"
74
+ #
75
+ # @example With already formatted role
76
+ # # When Securial.configuration.admin_role = "Site Administrator"
77
+ # titleized_admin_role
78
+ # # => "Site Administrator"
79
+ #
11
80
  def titleized_admin_role
12
81
  Securial.configuration.admin_role.to_s.strip.titleize
13
82
  end
@@ -1,11 +1,62 @@
1
+ # @title Securial Request Tag Logger Middleware
2
+ #
3
+ # Rack middleware for adding request context to log messages.
4
+ #
5
+ # This middleware automatically tags all log messages within a request with
6
+ # contextual information like request ID, IP address, and user agent. This
7
+ # enables better tracking and debugging of requests across the application
8
+ # by providing consistent context in all log entries.
9
+ #
10
+ # @example Adding middleware to Rails application
11
+ # # config/application.rb
12
+ # config.middleware.use Securial::Middleware::RequestTagLogger
13
+ #
14
+ # @example Log output with request tags
15
+ # # Without middleware:
16
+ # [2024-06-27 10:30:15] INFO -- User authentication successful
17
+ #
18
+ # # With middleware:
19
+ # [2024-06-27 10:30:15] INFO -- [abc123-def456] [IP:192.168.1.100] [UA:Mozilla/5.0...] User authentication successful
20
+ #
1
21
  module Securial
2
22
  module Middleware
23
+ # Rack middleware that adds request context tags to log messages.
24
+ #
25
+ # This middleware intercepts requests and wraps the application call
26
+ # with tagged logging, ensuring all log messages generated during
27
+ # request processing include relevant request metadata for better
28
+ # traceability and debugging.
29
+ #
3
30
  class RequestTagLogger
31
+ # Initializes the middleware with the Rack application and logger.
32
+ #
33
+ # @param [#call] app The Rack application to wrap
34
+ # @param [Logger] logger The logger instance to use for tagging (defaults to Securial.logger)
35
+ #
36
+ # @example
37
+ # middleware = RequestTagLogger.new(app, Rails.logger)
38
+ #
4
39
  def initialize(app, logger = Securial.logger)
5
40
  @app = app
6
41
  @logger = logger
7
42
  end
8
43
 
44
+ # Processes the request with tagged logging context.
45
+ #
46
+ # Extracts request metadata from the Rack environment and applies
47
+ # them as tags to all log messages generated during the request
48
+ # processing. Tags are automatically removed after the request completes.
49
+ #
50
+ # @param [Hash] env The Rack environment hash
51
+ # @return [Array] The Rack response array [status, headers, body]
52
+ #
53
+ # @example Request processing with tags
54
+ # # All log messages during this request will include:
55
+ # # - Request ID (if available)
56
+ # # - IP address (if available)
57
+ # # - User agent (if available)
58
+ # response = middleware.call(env)
59
+ #
9
60
  def call(env)
10
61
  request_id = request_id_from_env(env)
11
62
  ip_address = ip_from_env(env)
@@ -23,14 +74,43 @@ module Securial
23
74
 
24
75
  private
25
76
 
77
+ # Extracts the request ID from the Rack environment.
78
+ #
79
+ # Looks for request ID in ActionDispatch's request_id or the
80
+ # X-Request-ID header, providing request traceability across
81
+ # multiple services and log aggregation systems.
82
+ #
83
+ # @param [Hash] env The Rack environment hash
84
+ # @return [String, nil] The request ID if found, nil otherwise
85
+ # @api private
86
+ #
26
87
  def request_id_from_env(env)
27
88
  env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
28
89
  end
29
90
 
91
+ # Extracts the client IP address from the Rack environment.
92
+ #
93
+ # Prioritizes ActionDispatch's processed remote IP (which handles
94
+ # proxy headers) over the raw REMOTE_ADDR to ensure accurate
95
+ # client identification behind load balancers and proxies.
96
+ #
97
+ # @param [Hash] env The Rack environment hash
98
+ # @return [String, nil] The client IP address if found, nil otherwise
99
+ # @api private
100
+ #
30
101
  def ip_from_env(env)
31
102
  env["action_dispatch.remote_ip"]&.to_s || env["REMOTE_ADDR"]
32
103
  end
33
104
 
105
+ # Extracts the user agent string from the Rack environment.
106
+ #
107
+ # Retrieves the HTTP User-Agent header to provide context about
108
+ # the client application or browser making the request.
109
+ #
110
+ # @param [Hash] env The Rack environment hash
111
+ # @return [String, nil] The user agent string if found, nil otherwise
112
+ # @api private
113
+ #
34
114
  def user_agent_from_env(env)
35
115
  env["HTTP_USER_AGENT"]
36
116
  end
@@ -1,17 +1,65 @@
1
+ # @title Securial Response Headers Middleware
2
+ #
3
+ # Rack middleware for adding Securial-specific headers to HTTP responses.
4
+ #
5
+ # This middleware automatically adds identification headers to all HTTP responses
6
+ # to indicate that the application is using Securial for authentication and to
7
+ # provide developer attribution. These headers can be useful for debugging,
8
+ # monitoring, and identifying Securial-powered applications.
9
+ #
10
+ # @example Adding middleware to Rails application
11
+ # # config/application.rb
12
+ # config.middleware.use Securial::Middleware::ResponseHeaders
13
+ #
14
+ # @example Response headers added by middleware
15
+ # # HTTP Response Headers:
16
+ # X-Securial-Mounted: true
17
+ # X-Securial-Developer: Aly Badawy - https://alybadawy.com | @alybadawy
18
+ #
1
19
  module Securial
2
20
  module Middleware
3
- # This middleware removes sensitive headers from the request environment.
4
- # It is designed to enhance security by ensuring that sensitive information
5
- # is not inadvertently logged or processed.
21
+ # Rack middleware that adds Securial identification headers to responses.
22
+ #
23
+ # This middleware enhances security transparency by clearly identifying
24
+ # when Securial is mounted in an application and provides developer
25
+ # attribution information in response headers.
26
+ #
6
27
  class ResponseHeaders
28
+ # Initializes the middleware with the Rack application.
29
+ #
30
+ # @param [#call] app The Rack application to wrap
31
+ #
32
+ # @example
33
+ # middleware = ResponseHeaders.new(app)
34
+ #
7
35
  def initialize(app)
8
36
  @app = app
9
37
  end
10
38
 
39
+ # Processes the request and adds Securial headers to the response.
40
+ #
41
+ # Calls the wrapped application and then adds identification headers
42
+ # to the response before returning it to the client. The headers
43
+ # provide clear indication of Securial usage and developer attribution.
44
+ #
45
+ # @param [Hash] env The Rack environment hash
46
+ # @return [Array] The Rack response array [status, headers, body] with added headers
47
+ #
48
+ # @example Headers added to response
49
+ # # Original response headers remain unchanged
50
+ # # Additional headers added:
51
+ # # X-Securial-Mounted: "true"
52
+ # # X-Securial-Developer: "Aly Badawy - https://alybadawy.com | @alybadawy"
53
+ #
11
54
  def call(env)
12
55
  status, headers, response = @app.call(env)
56
+
57
+ # Indicate that Securial is mounted and active
13
58
  headers["X-Securial-Mounted"] = "true"
59
+
60
+ # Provide developer attribution
14
61
  headers["X-Securial-Developer"] = "Aly Badawy - https://alybadawy.com | @alybadawy"
62
+
15
63
  [status, headers, response]
16
64
  end
17
65
  end