securial 1.0.1 → 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/.yardopts +4 -0
- data/README.md +19 -12
- data/app/controllers/concerns/securial/identity.rb +91 -2
- data/app/controllers/securial/accounts_controller.rb +68 -5
- data/app/controllers/securial/application_controller.rb +34 -2
- data/app/controllers/securial/passwords_controller.rb +44 -4
- data/app/controllers/securial/role_assignments_controller.rb +55 -4
- data/app/controllers/securial/roles_controller.rb +54 -0
- data/app/controllers/securial/sessions_controller.rb +77 -3
- data/app/controllers/securial/status_controller.rb +24 -0
- data/app/controllers/securial/users_controller.rb +54 -0
- data/app/jobs/securial/application_job.rb +9 -0
- data/app/mailers/securial/application_mailer.rb +12 -0
- data/app/mailers/securial/securial_mailer.rb +30 -0
- data/app/models/concerns/securial/password_resettable.rb +70 -0
- data/app/models/securial/application_record.rb +19 -0
- data/app/models/securial/current.rb +13 -0
- data/app/models/securial/role.rb +17 -0
- data/app/models/securial/role_assignment.rb +16 -0
- data/app/models/securial/session.rb +79 -1
- data/app/models/securial/user.rb +34 -0
- data/lib/generators/factory_bot/model/model_generator.rb +1 -0
- 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/auth.rb +44 -6
- data/lib/securial/cli.rb +124 -0
- data/lib/securial/config/signature.rb +116 -5
- data/lib/securial/config/validation.rb +91 -0
- data/lib/securial/config.rb +49 -2
- data/lib/securial/engine.rb +41 -0
- data/lib/securial/error/auth.rb +52 -0
- data/lib/securial/error/base_securial_error.rb +51 -0
- data/lib/securial/error/config.rb +33 -0
- data/lib/securial/error.rb +33 -3
- 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/helpers.rb +48 -4
- data/lib/securial/logger/broadcaster.rb +89 -1
- data/lib/securial/logger/builder.rb +54 -1
- data/lib/securial/logger/formatter.rb +73 -0
- data/lib/securial/logger.rb +42 -1
- 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/middleware.rb +40 -9
- data/lib/securial/security/request_rate_limiter.rb +47 -1
- data/lib/securial/security.rb +37 -6
- data/lib/securial/version.rb +8 -1
- data/lib/securial.rb +36 -0
- metadata +21 -15
data/lib/securial/error.rb
CHANGED
@@ -1,9 +1,39 @@
|
|
1
|
+
# Requires all error classes used in the Securial framework.
|
2
|
+
#
|
3
|
+
# This file serves as the central error management system for Securial,
|
4
|
+
# loading all specialized error types and establishing the Error namespace.
|
5
|
+
# Each error type extends from BaseSecurialError and provides specific
|
6
|
+
# error handling for different parts of the framework.
|
7
|
+
#
|
8
|
+
# @example Accessing a specific error type
|
9
|
+
# begin
|
10
|
+
# # Some operation that might fail
|
11
|
+
# rescue Securial::Error::Auth::TokenDecodeError => e
|
12
|
+
# # Handle token decoding errors
|
13
|
+
# rescue Securial::Error::Config::ValidationError => e
|
14
|
+
# # Handle configuration validation errors
|
15
|
+
# rescue Securial::Error::BaseError => e
|
16
|
+
# # Handle any other Securial errors
|
17
|
+
# end
|
18
|
+
#
|
1
19
|
require "securial/error/base_securial_error"
|
2
20
|
require "securial/error/config"
|
3
21
|
require "securial/error/auth"
|
4
22
|
|
5
23
|
module Securial
|
6
|
-
|
7
|
-
|
8
|
-
|
24
|
+
# Namespace for all Securial-specific errors and exceptions.
|
25
|
+
#
|
26
|
+
# The Error module contains specialized error classes for different
|
27
|
+
# components of the Securial framework, organized into submodules
|
28
|
+
# for better categorization:
|
29
|
+
#
|
30
|
+
# - {BaseError} - Base class for all Securial errors
|
31
|
+
# - {Config} - Configuration-related errors
|
32
|
+
# - {Auth} - Authentication and authorization errors
|
33
|
+
#
|
34
|
+
# @see Securial::Error::BaseError
|
35
|
+
# @see Securial::Error::Config
|
36
|
+
# @see Securial::Error::Auth
|
37
|
+
#
|
38
|
+
module Error; end
|
9
39
|
end
|
@@ -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
|
data/lib/securial/helpers.rb
CHANGED
@@ -1,11 +1,55 @@
|
|
1
|
+
# @title Securial Helper Utilities
|
2
|
+
#
|
3
|
+
# Centralized collection of helper modules for the Securial framework.
|
4
|
+
#
|
5
|
+
# This file serves as the entry point for all helper functionality in Securial,
|
6
|
+
# loading specialized utility modules that provide common functions used
|
7
|
+
# throughout the framework. These helpers handle tasks such as normalization,
|
8
|
+
# pattern matching, role management, and data transformation, making common
|
9
|
+
# operations consistent across the codebase.
|
10
|
+
#
|
11
|
+
# @example Normalizing user input
|
12
|
+
# # For normalizing email addresses (converts to lowercase)
|
13
|
+
# email = Securial::Helpers::NormalizingHelper.normalize_email("USER@EXAMPLE.COM")
|
14
|
+
# # => "user@example.com"
|
15
|
+
#
|
16
|
+
# @example Validating input format
|
17
|
+
# # For validating email format
|
18
|
+
# valid = Securial::Helpers::RegexHelper.valid_email?("user@example.com")
|
19
|
+
# # => true
|
20
|
+
#
|
21
|
+
# invalid = Securial::Helpers::RegexHelper.valid_email?("not-an-email")
|
22
|
+
# # => false
|
23
|
+
#
|
24
|
+
# @example Transforming data structure keys
|
25
|
+
# # For transforming keys in response data (snake_case to camelCase)
|
26
|
+
# data = { user_id: 1, first_name: "John", last_name: "Doe" }
|
27
|
+
# json = Securial::Helpers::KeyTransformer.transform(data, :lower_camel_case)
|
28
|
+
# # => { userId: 1, firstName: "John", lastName: "Doe" }
|
29
|
+
#
|
1
30
|
require "securial/helpers/normalizing_helper"
|
2
31
|
require "securial/helpers/regex_helper"
|
3
32
|
require "securial/helpers/roles_helper"
|
4
33
|
require "securial/helpers/key_transformer"
|
5
34
|
|
6
35
|
module Securial
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
36
|
+
# Namespace containing utility modules for common operations.
|
37
|
+
#
|
38
|
+
# The Helpers module serves as a container for specialized utility modules
|
39
|
+
# that provide reusable functionality across the Securial framework:
|
40
|
+
#
|
41
|
+
# - {NormalizingHelper} - Methods for normalizing user input (emails, usernames, etc.)
|
42
|
+
# - {RegexHelper} - Regular expressions and validation methods for input validation
|
43
|
+
# - {RolesHelper} - Functions for working with user roles and permissions management
|
44
|
+
# - {KeyTransformer} - Tools for transforming data structure keys between formats (snake_case, camelCase)
|
45
|
+
#
|
46
|
+
# These helpers ensure consistent behavior across the application and reduce
|
47
|
+
# code duplication by centralizing common operations.
|
48
|
+
#
|
49
|
+
# @see Securial::Helpers::NormalizingHelper
|
50
|
+
# @see Securial::Helpers::RegexHelper
|
51
|
+
# @see Securial::Helpers::RolesHelper
|
52
|
+
# @see Securial::Helpers::KeyTransformer
|
53
|
+
#
|
54
|
+
module Helpers; end
|
11
55
|
end
|
@@ -1,36 +1,109 @@
|
|
1
|
+
# @title Securial Logger Broadcaster
|
2
|
+
#
|
3
|
+
# Logger broadcasting utility for the Securial framework.
|
4
|
+
#
|
5
|
+
# This file defines a broadcaster class that sends logging messages to multiple
|
6
|
+
# destination loggers simultaneously. This enables unified logging across different
|
7
|
+
# outputs (like console and file) while maintaining appropriate formatting for each.
|
8
|
+
#
|
9
|
+
# @example Creating a broadcaster with multiple loggers
|
10
|
+
# # Create individual loggers
|
11
|
+
# file_logger = Logger.new('application.log')
|
12
|
+
# stdout_logger = Logger.new(STDOUT)
|
13
|
+
#
|
14
|
+
# # Combine them into a broadcaster
|
15
|
+
# broadcaster = Securial::Logger::Broadcaster.new([file_logger, stdout_logger])
|
16
|
+
#
|
17
|
+
# # Log messages go to both destinations
|
18
|
+
# broadcaster.info("Processing authentication request")
|
19
|
+
#
|
1
20
|
module Securial
|
2
21
|
module Logger
|
22
|
+
# Broadcasts log messages to multiple logger instances simultaneously.
|
23
|
+
#
|
24
|
+
# This class implements the Logger interface and forwards all logging calls
|
25
|
+
# to a collection of underlying loggers. This allows unified logging to
|
26
|
+
# multiple destinations (e.g., file and stdout) while maintaining specific
|
27
|
+
# formatting for each destination.
|
28
|
+
#
|
29
|
+
# @example Using a broadcaster with two loggers
|
30
|
+
# file_logger = Logger.new('app.log')
|
31
|
+
# console_logger = Logger.new(STDOUT)
|
32
|
+
# broadcaster = Broadcaster.new([file_logger, console_logger])
|
33
|
+
#
|
34
|
+
# # This logs to both destinations
|
35
|
+
# broadcaster.info("User logged in")
|
36
|
+
#
|
3
37
|
class Broadcaster
|
38
|
+
# Initializes a new broadcaster with the provided loggers.
|
39
|
+
#
|
40
|
+
# @param loggers [Array<Logger>] Collection of logger instances
|
41
|
+
# @return [Broadcaster] New broadcaster instance
|
4
42
|
def initialize(loggers)
|
5
43
|
@loggers = loggers
|
6
44
|
end
|
7
45
|
|
46
|
+
# Dynamically define logging methods for each severity level
|
47
|
+
# (debug, info, warn, error, fatal, unknown)
|
8
48
|
::Logger::Severity.constants.each do |severity|
|
9
49
|
define_method(severity.downcase) do |*args, &block|
|
10
50
|
@loggers.each { |logger| logger.public_send(severity.downcase, *args, &block) }
|
11
51
|
end
|
12
52
|
end
|
13
53
|
|
54
|
+
# Writes a message to each underlying logger.
|
55
|
+
#
|
56
|
+
# @param msg [String] Message to be written
|
57
|
+
# @return [Array] Results from each logger's << method
|
14
58
|
def <<(msg)
|
15
59
|
@loggers.each { |logger| logger << msg }
|
16
60
|
end
|
17
61
|
|
62
|
+
# Closes all underlying loggers.
|
63
|
+
#
|
64
|
+
# @return [void]
|
18
65
|
def close
|
19
66
|
@loggers.each(&:close)
|
20
67
|
end
|
21
68
|
|
69
|
+
# No-op method to satisfy the Logger interface.
|
70
|
+
#
|
71
|
+
# Since each underlying logger has its own formatter, setting a
|
72
|
+
# formatter on the broadcaster is not supported.
|
73
|
+
#
|
74
|
+
# @param _formatter [Object] Ignored formatter object
|
75
|
+
# @return [void]
|
22
76
|
def formatter=(_formatter)
|
23
|
-
# Do nothing
|
77
|
+
# Do nothing - each logger maintains its own formatter
|
24
78
|
end
|
25
79
|
|
80
|
+
# Returns nil to satisfy the Logger interface.
|
81
|
+
#
|
82
|
+
# Since each underlying logger has its own formatter, there is
|
83
|
+
# no single formatter for the broadcaster.
|
84
|
+
#
|
85
|
+
# @return [nil] Always returns nil
|
26
86
|
def formatter
|
27
87
|
nil
|
28
88
|
end
|
29
89
|
|
90
|
+
# Returns the collection of managed loggers.
|
91
|
+
#
|
92
|
+
# @return [Array<Logger>] All loggers managed by this broadcaster
|
93
|
+
#
|
30
94
|
def loggers
|
31
95
|
@loggers
|
32
96
|
end
|
33
97
|
|
98
|
+
# Executes a block with the specified tags added to the log.
|
99
|
+
#
|
100
|
+
# Supports ActiveSupport::TaggedLogging by forwarding tagged blocks
|
101
|
+
# to all underlying loggers that support tagging.
|
102
|
+
#
|
103
|
+
# @param tags [Array<String>] Tags to apply to log messages
|
104
|
+
# @yield Block to be executed with the tags applied
|
105
|
+
# @return [Object] Result of the block
|
106
|
+
#
|
34
107
|
def tagged(*tags, &block)
|
35
108
|
# If all loggers support tagged, nest the calls, otherwise just yield.
|
36
109
|
taggable_loggers = @loggers.select { |logger| logger.respond_to?(:tagged) }
|
@@ -44,10 +117,25 @@ module Securial
|
|
44
117
|
end
|
45
118
|
end
|
46
119
|
|
120
|
+
# Checks if the broadcaster responds to the given method.
|
121
|
+
#
|
122
|
+
# @param method [Symbol] Method name to check
|
123
|
+
# @param include_private [Boolean] Whether to include private methods
|
124
|
+
# @return [Boolean] True if any of the underlying loggers respond to the method
|
47
125
|
def respond_to_missing?(method, include_private = false)
|
48
126
|
@loggers.any? { |logger| logger.respond_to?(method, include_private) } || super
|
49
127
|
end
|
50
128
|
|
129
|
+
# Delegates missing methods to all underlying loggers.
|
130
|
+
#
|
131
|
+
# If all loggers respond to the method, it will be called on each logger.
|
132
|
+
# Otherwise, raises a NoMethodError.
|
133
|
+
#
|
134
|
+
# @param method [Symbol] Method name to call
|
135
|
+
# @param args [Array] Arguments to pass to the method
|
136
|
+
# @param block [Proc] Block to pass to the method
|
137
|
+
# @return [Array] Results from each logger's method call
|
138
|
+
# @raise [NoMethodError] If any logger doesn't respond to the method
|
51
139
|
def method_missing(method, *args, &block)
|
52
140
|
if @loggers.all? { |logger| logger.respond_to?(method) }
|
53
141
|
@loggers.map { |logger| logger.send(method, *args, &block) }
|