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
@@ -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
|
-
#
|
4
|
-
#
|
5
|
-
#
|
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
|