securial 0.8.1 → 1.0.1
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 +14 -16
- data/app/controllers/concerns/securial/identity.rb +18 -9
- data/app/controllers/securial/status_controller.rb +2 -0
- data/app/controllers/securial/users_controller.rb +1 -1
- data/app/views/securial/status/show.json.jbuilder +1 -1
- data/bin/securial +5 -54
- data/db/migrate/20250606182648_seed_roles_and_users.rb +69 -0
- data/lib/generators/securial/install/install_generator.rb +2 -2
- data/lib/generators/securial/install/templates/securial_initializer.erb +115 -18
- data/lib/generators/securial/install/views_generator.rb +2 -1
- data/lib/generators/securial/jbuilder/jbuilder_generator.rb +2 -0
- data/lib/generators/securial/scaffold/scaffold_generator.rb +2 -0
- data/lib/securial/auth/auth_encoder.rb +3 -3
- data/lib/securial/auth/session_creator.rb +1 -1
- data/lib/securial/auth/token_generator.rb +13 -13
- data/lib/securial/cli.rb +158 -0
- data/lib/securial/config/configuration.rb +3 -53
- data/lib/securial/config/signature.rb +107 -0
- data/lib/securial/config/validation.rb +59 -16
- data/lib/securial/config.rb +17 -16
- data/lib/securial/engine.rb +2 -0
- data/lib/securial/engine_initializers.rb +21 -2
- data/lib/securial/error/base_securial_error.rb +5 -3
- data/lib/securial/error/config.rb +0 -28
- data/lib/securial/helpers/key_transformer.rb +33 -0
- data/lib/securial/helpers/normalizing_helper.rb +1 -1
- data/lib/securial/helpers/regex_helper.rb +6 -7
- data/lib/securial/helpers/roles_helper.rb +6 -7
- data/lib/securial/helpers.rb +1 -0
- data/lib/securial/logger.rb +7 -8
- data/lib/securial/middleware/response_headers.rb +19 -0
- data/lib/securial/middleware/transform_request_keys.rb +35 -0
- data/lib/securial/middleware/transform_response_keys.rb +47 -0
- data/lib/securial/middleware.rb +3 -0
- data/lib/securial/security/request_rate_limiter.rb +45 -0
- data/lib/securial/security.rb +8 -0
- data/lib/securial/version.rb +1 -1
- data/lib/securial.rb +4 -4
- data/lib/tasks/securial_routes.rake +26 -0
- metadata +47 -19
- data/lib/securial/config/validation/logger_validation.rb +0 -29
- data/lib/securial/config/validation/mailer_validation.rb +0 -24
- data/lib/securial/config/validation/password_validation.rb +0 -91
- data/lib/securial/config/validation/response_validation.rb +0 -37
- data/lib/securial/config/validation/roles_validation.rb +0 -32
- data/lib/securial/config/validation/security_validation.rb +0 -56
- data/lib/securial/config/validation/session_validation.rb +0 -87
data/lib/securial/cli.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
# rubocop:disable Rails/Exit, Rails/Output
|
4
|
+
|
5
|
+
module Securial
|
6
|
+
class CLI
|
7
|
+
def self.start(argv)
|
8
|
+
new.start(argv)
|
9
|
+
end
|
10
|
+
|
11
|
+
def start(argv)
|
12
|
+
# Process options and exit if a flag was handled
|
13
|
+
result = handle_flags(argv)
|
14
|
+
exit(result) if result
|
15
|
+
|
16
|
+
# Otherwise handle commands
|
17
|
+
exit(handle_commands(argv))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def handle_flags(argv)
|
23
|
+
parser = create_option_parser
|
24
|
+
|
25
|
+
begin
|
26
|
+
parser.order!(argv)
|
27
|
+
nil # Continue to command handling
|
28
|
+
rescue OptionParser::InvalidOption => e
|
29
|
+
warn "ERROR: Illegal option(s): #{e.args.join(' ')}"
|
30
|
+
puts parser
|
31
|
+
1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_commands(argv)
|
36
|
+
cmd = argv.shift
|
37
|
+
|
38
|
+
case cmd
|
39
|
+
when "new"
|
40
|
+
handle_new_command(argv)
|
41
|
+
else
|
42
|
+
puts create_option_parser
|
43
|
+
1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_new_command(argv)
|
48
|
+
app_name = argv.shift
|
49
|
+
|
50
|
+
if app_name.nil?
|
51
|
+
puts "ERROR: Please provide an app name."
|
52
|
+
puts create_option_parser
|
53
|
+
return 1
|
54
|
+
end
|
55
|
+
|
56
|
+
securial_new(app_name, argv)
|
57
|
+
0
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_option_parser
|
61
|
+
OptionParser.new do |opts|
|
62
|
+
opts.banner = "Usage: securial [options] <command> [command options]\n\n"
|
63
|
+
|
64
|
+
opts.separator ""
|
65
|
+
opts.separator "Commands:"
|
66
|
+
opts.separator " new APP_NAME [rails_options...] # Create a new Rails app with Securial pre-installed"
|
67
|
+
opts.separator ""
|
68
|
+
opts.separator "Options:"
|
69
|
+
|
70
|
+
opts.on("-v", "--version", "Show Securial version") do
|
71
|
+
show_version
|
72
|
+
exit(0)
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-h", "--help", "Show this help message") do
|
76
|
+
puts opts
|
77
|
+
exit(0)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def show_version
|
83
|
+
require "securial/version"
|
84
|
+
puts "Securial v#{Securial::VERSION}"
|
85
|
+
rescue LoadError
|
86
|
+
puts "Securial version information not available."
|
87
|
+
end
|
88
|
+
|
89
|
+
def securial_new(app_name, rails_options)
|
90
|
+
puts "🏗️ Creating new Rails app: #{app_name}"
|
91
|
+
|
92
|
+
create_rails_app(app_name, rails_options)
|
93
|
+
add_securial_gem(app_name)
|
94
|
+
install_gems(app_name)
|
95
|
+
install_securial(app_name)
|
96
|
+
mount_securial_engine(app_name)
|
97
|
+
print_final_instructions(app_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_rails_app(app_name, rails_options)
|
101
|
+
rails_command = ["rails", "new", app_name, *rails_options]
|
102
|
+
run(rails_command)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_securial_gem(app_name)
|
106
|
+
puts "📦 Adding Securial gem to Gemfile"
|
107
|
+
gemfile_path = File.join(app_name, "Gemfile")
|
108
|
+
File.open(gemfile_path, "a") { |f| f.puts "\ngem 'securial'" }
|
109
|
+
end
|
110
|
+
|
111
|
+
def install_gems(app_name)
|
112
|
+
run("bundle install", chdir: app_name)
|
113
|
+
end
|
114
|
+
|
115
|
+
def install_securial(app_name)
|
116
|
+
puts "🔧 Installing Securial"
|
117
|
+
run("bin/rails generate securial:install", chdir: app_name)
|
118
|
+
run("bin/rails db:migrate", chdir: app_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def mount_securial_engine(app_name)
|
122
|
+
puts "🔗 Mounting Securial engine in routes"
|
123
|
+
routes_path = File.join(app_name, "config/routes.rb")
|
124
|
+
routes = File.read(routes_path)
|
125
|
+
updated = routes.sub("Rails.application.routes.draw do") do |match|
|
126
|
+
"#{match}\n mount Securial::Engine => '/securial'"
|
127
|
+
end
|
128
|
+
File.write(routes_path, updated)
|
129
|
+
end
|
130
|
+
|
131
|
+
def print_final_instructions(app_name)
|
132
|
+
puts <<~INSTRUCTIONS
|
133
|
+
🎉 Securial has been successfully installed in your Rails app!
|
134
|
+
✅ Your app is ready at: ./#{app_name}
|
135
|
+
|
136
|
+
➡️ Next steps:
|
137
|
+
cd #{app_name}
|
138
|
+
⚙️ Optional: Configure Securial in config/initializers/securial.rb
|
139
|
+
rails server
|
140
|
+
INSTRUCTIONS
|
141
|
+
end
|
142
|
+
|
143
|
+
def run(command, chdir: nil)
|
144
|
+
puts "→ #{command.inspect}"
|
145
|
+
result =
|
146
|
+
if chdir
|
147
|
+
Dir.chdir(chdir) { system(*command) }
|
148
|
+
else
|
149
|
+
system(*command)
|
150
|
+
end
|
151
|
+
|
152
|
+
unless result
|
153
|
+
abort("❌ Command failed: #{command}")
|
154
|
+
end
|
155
|
+
0
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -1,67 +1,17 @@
|
|
1
1
|
require "securial/config/validation"
|
2
|
+
require "securial/config/signature"
|
2
3
|
require "securial/helpers/regex_helper"
|
3
4
|
|
4
5
|
module Securial
|
5
6
|
module Config
|
6
7
|
class Configuration
|
7
|
-
def self.default_config_attributes # rubocop:disable Metrics/MethodLength
|
8
|
-
{
|
9
|
-
# General configuration
|
10
|
-
app_name: "Securial",
|
11
|
-
|
12
|
-
# Logger configuration
|
13
|
-
log_to_file: !Rails.env.test?,
|
14
|
-
log_to_stdout: !Rails.env.test?,
|
15
|
-
log_file_level: :debug,
|
16
|
-
log_stdout_level: :debug,
|
17
|
-
|
18
|
-
# Roles configuration
|
19
|
-
admin_role: :admin,
|
20
|
-
|
21
|
-
session_expiration_duration: 3.minutes,
|
22
|
-
session_secret: "secret",
|
23
|
-
session_algorithm: :hs256,
|
24
|
-
session_refresh_token_expires_in: 1.week,
|
25
|
-
|
26
|
-
# Mailer configuration
|
27
|
-
mailer_sender: "no-reply@example.com",
|
28
|
-
mailer_sign_up_enabled: true,
|
29
|
-
mailer_sign_up_subject: "SECURIAL: Welcome to Our Service",
|
30
|
-
mailer_sign_in_enabled: true,
|
31
|
-
mailer_sign_in_subject: "SECURIAL: Sign In Notification",
|
32
|
-
mailer_update_account_enabled: true,
|
33
|
-
mailer_update_account_subject: "SECURIAL: Account Update Notification",
|
34
|
-
mailer_forgot_password_subject: "SECURIAL: Password Reset Instructions",
|
35
|
-
|
36
|
-
# Password configuration
|
37
|
-
password_min_length: 8,
|
38
|
-
password_max_length: 128,
|
39
|
-
password_complexity: Securial::Helpers::RegexHelper::PASSWORD_REGEX,
|
40
|
-
password_expires: true,
|
41
|
-
password_expires_in: 90.days,
|
42
|
-
reset_password_token_expires_in: 2.hours,
|
43
|
-
reset_password_token_secret: "reset_secret",
|
44
|
-
|
45
|
-
# Response configuration
|
46
|
-
response_keys_format: :snake_case,
|
47
|
-
timestamps_in_response: :all,
|
48
|
-
|
49
|
-
# Security configuration
|
50
|
-
security_headers: :strict,
|
51
|
-
rate_limiting_enabled: true,
|
52
|
-
rate_limit_requests_per_minute: 60,
|
53
|
-
rate_limit_response_status: 429,
|
54
|
-
rate_limit_response_message: "Too many requests, please try again later.",
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
8
|
def initialize
|
59
|
-
|
9
|
+
Securial::Config::Signature.default_config_attributes.each do |attr, default_value|
|
60
10
|
instance_variable_set("@#{attr}", default_value)
|
61
11
|
end
|
62
12
|
end
|
63
13
|
|
64
|
-
default_config_attributes.each_key do |attr|
|
14
|
+
Securial::Config::Signature.default_config_attributes.each_key do |attr|
|
65
15
|
define_method(attr) { instance_variable_get("@#{attr}") }
|
66
16
|
|
67
17
|
define_method("#{attr}=") do |value|
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Securial
|
2
|
+
module Config
|
3
|
+
module Signature
|
4
|
+
LOG_LEVELS = %i[debug info warn error fatal unknown].freeze
|
5
|
+
SESSION_ALGORITHMS = %i[hs256 hs384 hs512].freeze
|
6
|
+
SECURITY_HEADERS = %i[strict default none].freeze
|
7
|
+
TIMESTAMP_OPTIONS = %i[all admins_only none].freeze
|
8
|
+
RESPONSE_KEYS_FORMATS = %i[snake_case lowerCamelCase UpperCamelCase].freeze
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def config_signature
|
13
|
+
[
|
14
|
+
general_signature,
|
15
|
+
logger_signature,
|
16
|
+
roles_signature,
|
17
|
+
session_signature,
|
18
|
+
mailer_signature,
|
19
|
+
password_signature,
|
20
|
+
response_signature,
|
21
|
+
security_signature,
|
22
|
+
].reduce({}, :merge)
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_config_attributes
|
26
|
+
config_signature.transform_values do |options|
|
27
|
+
options[:default]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def general_signature
|
34
|
+
{
|
35
|
+
app_name: { type: String, required: true, default: "Securial" },
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger_signature
|
40
|
+
{
|
41
|
+
log_to_file: { type: [TrueClass, FalseClass], required: true, default: Rails.env.test? ? false : true },
|
42
|
+
log_file_level: { type: Symbol, required: "log_to_file", allowed_values: LOG_LEVELS, default: :debug },
|
43
|
+
log_to_stdout: { type: [TrueClass, FalseClass], required: true, default: Rails.env.test? ? false : true },
|
44
|
+
log_stdout_level: { type: Symbol, required: "log_to_stdout", allowed_values: LOG_LEVELS, default: :debug },
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def roles_signature
|
49
|
+
{
|
50
|
+
admin_role: { type: Symbol, required: true, default: :admin },
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def session_signature
|
55
|
+
{
|
56
|
+
session_expiration_duration: { type: ActiveSupport::Duration, required: true, default: 3.minutes },
|
57
|
+
session_secret: { type: String, required: true, default: "secret" },
|
58
|
+
session_algorithm: { type: Symbol, required: true, allowed_values: SESSION_ALGORITHMS, default: :hs256 },
|
59
|
+
session_refresh_token_expires_in: { type: ActiveSupport::Duration, required: true, default: 1.week },
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def mailer_signature
|
64
|
+
{
|
65
|
+
mailer_sender: { type: String, required: true, default: "no-reply@example.com" },
|
66
|
+
mailer_sign_up_enabled: { type: [TrueClass, FalseClass], required: true, default: true },
|
67
|
+
mailer_sign_up_subject: { type: String, required: "mailer_sign_up_enabled", default: "SECURIAL: Welcome to Our Service" },
|
68
|
+
mailer_sign_in_enabled: { type: [TrueClass, FalseClass], required: true, default: true },
|
69
|
+
mailer_sign_in_subject: { type: String, required: "mailer_sign_in_enabled", default: "SECURIAL: Sign In Notification" },
|
70
|
+
mailer_update_account_enabled: { type: [TrueClass, FalseClass], required: true, default: true },
|
71
|
+
mailer_update_account_subject: { type: String, required: "mailer_update_account_enabled", default: "SECURIAL: Account Update Notification" },
|
72
|
+
mailer_forgot_password_subject: { type: String, required: true, default: "SECURIAL: Password Reset Instructions" },
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def password_signature
|
77
|
+
{
|
78
|
+
password_min_length: { type: Numeric, required: true, default: 8 },
|
79
|
+
password_max_length: { type: Numeric, required: true, default: 128 },
|
80
|
+
password_complexity: { type: Regexp, required: true, default: Securial::Helpers::RegexHelper::PASSWORD_REGEX },
|
81
|
+
password_expires: { type: [TrueClass, FalseClass], required: true, default: true },
|
82
|
+
password_expires_in: { type: ActiveSupport::Duration, required: "password_expires", default: 90.days },
|
83
|
+
reset_password_token_expires_in: { type: ActiveSupport::Duration, required: true, default: 2.hours },
|
84
|
+
reset_password_token_secret: { type: String, required: true, default: "reset_secret" },
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def response_signature
|
89
|
+
{
|
90
|
+
response_keys_format: { type: Symbol, required: true, allowed_values:
|
91
|
+
RESPONSE_KEYS_FORMATS, default: :snake_case, },
|
92
|
+
timestamps_in_response: { type: Symbol, required: true, allowed_values: TIMESTAMP_OPTIONS, default: :all },
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def security_signature
|
97
|
+
{
|
98
|
+
security_headers: { type: Symbol, required: true, allowed_values: SECURITY_HEADERS, default: :strict },
|
99
|
+
rate_limiting_enabled: { type: [TrueClass, FalseClass], required: true, default: true },
|
100
|
+
rate_limit_requests_per_minute: { type: Numeric, required: "rate_limiting_enabled", default: 60 },
|
101
|
+
rate_limit_response_status: { type: Numeric, required: "rate_limiting_enabled", default: 429 },
|
102
|
+
rate_limit_response_message: { type: String, required: "rate_limiting_enabled", default: "Too many requests, please try again later." },
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -1,26 +1,69 @@
|
|
1
1
|
require "securial/logger"
|
2
|
-
require "securial/config/validation/logger_validation"
|
3
|
-
require "securial/config/validation/roles_validation"
|
4
|
-
require "securial/config/validation/session_validation"
|
5
|
-
require "securial/config/validation/mailer_validation"
|
6
|
-
require "securial/config/validation/password_validation"
|
7
|
-
require "securial/config/validation/response_validation"
|
8
|
-
require "securial/config/validation/security_validation"
|
9
2
|
|
10
3
|
module Securial
|
11
4
|
module Config
|
12
5
|
module Validation
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
extend self
|
7
|
+
def validate_all!(securial_config)
|
8
|
+
signature = Securial::Config::Signature.config_signature
|
9
|
+
|
10
|
+
validate_required_fields!(signature, securial_config)
|
11
|
+
validate_types_and_values!(signature, securial_config)
|
12
|
+
validate_password_lengths!(securial_config)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_required_fields!(signature, config)
|
18
|
+
signature.each do |key, options|
|
19
|
+
value = config.send(key)
|
20
|
+
required = options[:required]
|
21
|
+
if required == true && value.nil?
|
22
|
+
raise_error("#{key} is required but not provided.")
|
23
|
+
elsif required.is_a?(String)
|
24
|
+
dynamic_required = config.send(required)
|
25
|
+
signature[key][:required] = dynamic_required
|
26
|
+
if dynamic_required && value.nil?
|
27
|
+
raise_error("#{key} is required but not provided when #{required} is true.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_types_and_values!(signature, config)
|
34
|
+
signature.each do |key, options|
|
35
|
+
next unless signature[key][:required]
|
36
|
+
value = config.send(key)
|
37
|
+
types = Array(options[:type])
|
38
|
+
|
39
|
+
unless types.any? { |type| value.is_a?(type) }
|
40
|
+
raise_error("#{key} must be of type(s) #{types.join(', ')}, but got #{value.class}.")
|
41
|
+
end
|
42
|
+
|
43
|
+
if options[:type] == ActiveSupport::Duration && value <= 0
|
44
|
+
raise_error("#{key} must be a positive duration, but got #{value}.")
|
45
|
+
end
|
46
|
+
|
47
|
+
if options[:type] == Numeric && value < 0
|
48
|
+
raise_error("#{key} must be a non-negative numeric value, but got #{value}.")
|
49
|
+
end
|
50
|
+
|
51
|
+
if options[:allowed_values] && options[:allowed_values].exclude?(value)
|
52
|
+
raise_error("#{key} must be one of #{options[:allowed_values].join(', ')}, but got #{value}.")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_password_lengths!(config)
|
58
|
+
if config.password_min_length > config.password_max_length
|
59
|
+
raise_error("password_min_length cannot be greater than password_max_length.")
|
22
60
|
end
|
23
61
|
end
|
62
|
+
|
63
|
+
def raise_error(msg)
|
64
|
+
Securial.logger.fatal msg
|
65
|
+
raise Securial::Error::Config::InvalidConfigurationError, msg
|
66
|
+
end
|
24
67
|
end
|
25
68
|
end
|
26
69
|
end
|
data/lib/securial/config.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
require "securial/config/configuration"
|
2
|
+
require "securial/config/signature"
|
2
3
|
require "securial/config/validation"
|
3
4
|
|
4
|
-
module Securial
|
5
|
-
class << self
|
6
|
-
attr_accessor :configuration
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
module Securial
|
7
|
+
extend self
|
8
|
+
attr_accessor :configuration
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
Securial::Config::Validation.validate_all!(configuration)
|
16
|
-
else
|
17
|
-
raise ArgumentError, "Expected an instance of Securial::Config::Configuration"
|
18
|
-
end
|
19
|
-
end
|
10
|
+
def configuration
|
11
|
+
@configuration ||= Config::Configuration.new
|
12
|
+
end
|
20
13
|
|
21
|
-
|
22
|
-
|
14
|
+
def configuration=(config)
|
15
|
+
if config.is_a?(Config::Configuration)
|
16
|
+
@configuration = config
|
23
17
|
Securial::Config::Validation.validate_all!(configuration)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Expected an instance of Securial::Config::Configuration"
|
24
20
|
end
|
25
21
|
end
|
22
|
+
|
23
|
+
def configure
|
24
|
+
yield(configuration) if block_given?
|
25
|
+
Securial::Config::Validation.validate_all!(configuration)
|
26
|
+
end
|
26
27
|
end
|
data/lib/securial/engine.rb
CHANGED
@@ -5,7 +5,7 @@ module Securial
|
|
5
5
|
:password,
|
6
6
|
:password_confirmation,
|
7
7
|
:password_reset_token,
|
8
|
-
:reset_password_token
|
8
|
+
:reset_password_token,
|
9
9
|
]
|
10
10
|
end
|
11
11
|
|
@@ -16,10 +16,29 @@ module Securial
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
initializer "securial.
|
19
|
+
initializer "securial.security.request_rate_limiter" do |app|
|
20
|
+
if Securial.configuration.rate_limiting_enabled
|
21
|
+
Securial::Security::RequestRateLimiter.apply!
|
22
|
+
app.middleware.use Rack::Attack
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
initializer "securial.middleware.transform_request_keys" do |app|
|
27
|
+
app.middleware.use Securial::Middleware::TransformRequestKeys
|
28
|
+
end
|
29
|
+
|
30
|
+
initializer "securial.middleware.transform_response_keys" do |app|
|
31
|
+
app.middleware.use Securial::Middleware::TransformResponseKeys
|
32
|
+
end
|
33
|
+
|
34
|
+
initializer "securial.middleware.logger" do |app|
|
20
35
|
app.middleware.use Securial::Middleware::RequestTagLogger
|
21
36
|
end
|
22
37
|
|
38
|
+
initializer "securial.middleware.response_headers" do |app|
|
39
|
+
app.middleware.use Securial::Middleware::ResponseHeaders
|
40
|
+
end
|
41
|
+
|
23
42
|
initializer "securial.extend_application_controller" do
|
24
43
|
ActiveSupport.on_load(:action_controller_base) { include Securial::Identity }
|
25
44
|
ActiveSupport.on_load(:action_controller_api) { include Securial::Identity }
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Securial
|
2
2
|
module Error
|
3
3
|
class BaseError < StandardError
|
4
|
+
class_attribute :_default_message, instance_writer: false
|
5
|
+
|
4
6
|
def self.default_message(message = nil)
|
5
|
-
|
6
|
-
|
7
|
+
self._default_message = message if message
|
8
|
+
self._default_message
|
7
9
|
end
|
8
10
|
|
9
11
|
def initialize(message = nil)
|
10
|
-
super(message || self.class.
|
12
|
+
super(message || self.class._default_message || "An error occurred in Securial")
|
11
13
|
end
|
12
14
|
|
13
15
|
def backtrace; []; end
|
@@ -4,34 +4,6 @@ module Securial
|
|
4
4
|
class InvalidConfigurationError < Securial::Error::BaseError
|
5
5
|
default_message "Invalid configuration for Securial"
|
6
6
|
end
|
7
|
-
|
8
|
-
class LoggerValidationError < InvalidConfigurationError
|
9
|
-
default_message "Logger configuration validation failed"
|
10
|
-
end
|
11
|
-
|
12
|
-
class RolesValidationError < InvalidConfigurationError
|
13
|
-
default_message "Roles configuration validation failed"
|
14
|
-
end
|
15
|
-
|
16
|
-
class SessionValidationError < InvalidConfigurationError
|
17
|
-
default_message "Session configuration validation failed"
|
18
|
-
end
|
19
|
-
|
20
|
-
class MailerValidationError < InvalidConfigurationError
|
21
|
-
default_message "Mailer configuration validation failed"
|
22
|
-
end
|
23
|
-
|
24
|
-
class PasswordValidationError < InvalidConfigurationError
|
25
|
-
default_message "Password configuration validation failed"
|
26
|
-
end
|
27
|
-
|
28
|
-
class ResponseValidationError < InvalidConfigurationError
|
29
|
-
default_message "Response configuration validation failed"
|
30
|
-
end
|
31
|
-
|
32
|
-
class SecurityValidationError < InvalidConfigurationError
|
33
|
-
default_message "Security configuration validation failed"
|
34
|
-
end
|
35
7
|
end
|
36
8
|
end
|
37
9
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Securial
|
2
|
+
module Helpers
|
3
|
+
module KeyTransformer
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def camelize(str, format)
|
7
|
+
case format
|
8
|
+
when :lowerCamelCase
|
9
|
+
str.to_s.camelize(:lower)
|
10
|
+
when :UpperCamelCase
|
11
|
+
str.to_s.camelize
|
12
|
+
else
|
13
|
+
str.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.underscore(str)
|
18
|
+
str.to_s.underscore
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.deep_transform_keys(obj, &block)
|
22
|
+
case obj
|
23
|
+
when Hash
|
24
|
+
obj.transform_keys(&block).transform_values { |v| deep_transform_keys(v, &block) }
|
25
|
+
when Array
|
26
|
+
obj.map { |e| deep_transform_keys(e, &block) }
|
27
|
+
else
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -5,14 +5,13 @@ module Securial
|
|
5
5
|
USERNAME_REGEX = /\A(?![0-9])[a-zA-Z](?:[a-zA-Z0-9]|[._](?![._]))*[a-zA-Z0-9]\z/
|
6
6
|
PASSWORD_REGEX = %r{\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])[a-zA-Z].*\z}
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
extend self
|
9
|
+
def valid_email?(email)
|
10
|
+
email.match?(EMAIL_REGEX)
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
13
|
+
def valid_username?(username)
|
14
|
+
username.match?(USERNAME_REGEX)
|
16
15
|
end
|
17
16
|
end
|
18
17
|
end
|
@@ -3,14 +3,13 @@ module Securial
|
|
3
3
|
module RolesHelper
|
4
4
|
# This module provides helper methods related to roles.
|
5
5
|
# It can be extended with additional role-related functionality as needed.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
extend self
|
7
|
+
def protected_namespace
|
8
|
+
Securial.configuration.admin_role.to_s.strip.underscore.pluralize
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
end
|
11
|
+
def titleized_admin_role
|
12
|
+
Securial.configuration.admin_role.to_s.strip.titleize
|
14
13
|
end
|
15
14
|
end
|
16
15
|
end
|