securial 1.0.0 → 1.0.2
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 +14 -9
- 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/bin/securial +6 -23
- data/lib/generators/factory_bot/model/model_generator.rb +1 -0
- data/lib/generators/securial/install/install_generator.rb +2 -2
- 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/auth.rb +44 -6
- data/lib/securial/cli.rb +282 -0
- data/lib/securial/config/signature.rb +1 -1
- data/lib/securial/config/validation.rb +44 -45
- data/lib/securial/config.rb +63 -17
- data/lib/securial/engine.rb +41 -0
- data/lib/securial/error/auth.rb +52 -0
- data/lib/securial/error/base_securial_error.rb +56 -3
- data/lib/securial/error/config.rb +33 -0
- data/lib/securial/error.rb +33 -3
- data/lib/securial/helpers/key_transformer.rb +1 -1
- 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 +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 +48 -8
- data/lib/securial/middleware.rb +40 -9
- data/lib/securial/security/request_rate_limiter.rb +48 -2
- data/lib/securial/security.rb +37 -6
- data/lib/securial/version.rb +8 -1
- data/lib/securial.rb +40 -4
- metadata +15 -11
- data/lib/securial/cli/run.rb +0 -11
- data/lib/securial/cli/securial_new.rb +0 -53
- data/lib/securial/cli/show_help.rb +0 -26
- data/lib/securial/cli/show_version.rb +0 -9
data/app/models/securial/user.rb
CHANGED
@@ -1,4 +1,34 @@
|
|
1
1
|
module Securial
|
2
|
+
#
|
3
|
+
# User
|
4
|
+
#
|
5
|
+
# This class represents a user in the Securial authentication and authorization system.
|
6
|
+
#
|
7
|
+
# Users can have multiple roles assigned to them, which define their permissions
|
8
|
+
# and access levels within the application.
|
9
|
+
#
|
10
|
+
# The User model includes functionality for password reset, email normalization,
|
11
|
+
# and various validations to ensure data integrity.
|
12
|
+
#
|
13
|
+
# ## Attributes
|
14
|
+
# - `email_address`: The user's email address, which is normalized
|
15
|
+
# - `username`: A unique username for the user, with specific format requirements
|
16
|
+
# - `first_name`: The user's first name, required and limited in length
|
17
|
+
# - `last_name`: The user's last name, required and limited in length
|
18
|
+
# - `phone`: An optional phone number, limited in length
|
19
|
+
# - `bio`: An optional biography, limited in length
|
20
|
+
#
|
21
|
+
# ## Validations
|
22
|
+
# - Email address must be present, unique, and formatted correctly
|
23
|
+
# - Username must be present, unique (case insensitive), and formatted correctly
|
24
|
+
# - First and last names must be present and limited in length
|
25
|
+
# - Phone and bio fields are optional but have length restrictions
|
26
|
+
#
|
27
|
+
# ## Associations
|
28
|
+
# - Has many role assignments, allowing users to have multiple roles
|
29
|
+
# - Has many roles through role assignments, enabling flexible permission management
|
30
|
+
# - Has many sessions, allowing for session management and tracking
|
31
|
+
#
|
2
32
|
class User < ApplicationRecord
|
3
33
|
include Securial::PasswordResettable
|
4
34
|
|
@@ -45,6 +75,10 @@ module Securial
|
|
45
75
|
has_many :sessions, dependent: :destroy
|
46
76
|
|
47
77
|
|
78
|
+
# Checks if the user has the specified role.
|
79
|
+
#
|
80
|
+
# @param [String] role_name The name of the role to check
|
81
|
+
# @return [Boolean] Returns true if the user has the specified role, false otherwise.
|
48
82
|
def is_admin?
|
49
83
|
roles.exists?(role_name: Securial.titleized_admin_role)
|
50
84
|
end
|
data/bin/securial
CHANGED
@@ -1,26 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require_relative "../lib/securial/cli/run"
|
7
|
-
require_relative "../lib/securial/cli/securial_new"
|
3
|
+
# Set up the load path for local development or gem installation
|
4
|
+
lib = File.expand_path("../../lib", __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
when "-h", "--help"
|
13
|
-
show_help
|
14
|
-
when "new"
|
15
|
-
if ARGV[1]
|
16
|
-
app_name = ARGV[1]
|
17
|
-
rails_options = ARGV[2..] || []
|
18
|
-
securial_new(app_name, rails_options)
|
19
|
-
else
|
20
|
-
show_help
|
21
|
-
exit(1)
|
22
|
-
end
|
23
|
-
else
|
24
|
-
show_help
|
25
|
-
exit(1)
|
26
|
-
end
|
7
|
+
require "securial/cli"
|
8
|
+
|
9
|
+
Securial::CLI.start(ARGV)
|
@@ -4,10 +4,10 @@ require "rake"
|
|
4
4
|
module Securial
|
5
5
|
module Generators
|
6
6
|
class InstallGenerator < Rails::Generators::Base
|
7
|
-
source_root File.expand_path("templates", __dir__)
|
8
|
-
|
9
7
|
desc "initializes Securial in your application."
|
10
8
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
10
|
+
|
11
11
|
def copy_initializer
|
12
12
|
say_status("copying", "Securial Initializers", :green)
|
13
13
|
template "securial_initializer.erb", "config/initializers/securial.rb"
|
@@ -5,9 +5,10 @@ module Securial
|
|
5
5
|
module Generators
|
6
6
|
module Install
|
7
7
|
class ViewsGenerator < Rails::Generators::Base
|
8
|
-
source_root Securial::Engine.root.join("app", "views", "securial").to_s
|
9
8
|
desc "Copies Securial model-related views to your application for customization."
|
10
9
|
|
10
|
+
source_root Securial::Engine.root.join("app", "views", "securial").to_s
|
11
|
+
|
11
12
|
def copy_model_views
|
12
13
|
Dir.glob(File.join(self.class.source_root, "**/*")).each do |path|
|
13
14
|
relative_path = Pathname.new(path).relative_path_from(Pathname.new(self.class.source_root))
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Securial
|
2
2
|
module Generators
|
3
3
|
class JbuilderGenerator < Rails::Generators::NamedBase
|
4
|
+
desc "Generates Jbuilder view files for a given resource."
|
5
|
+
|
4
6
|
source_root File.expand_path("templates", __dir__)
|
5
7
|
|
6
8
|
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
@@ -5,6 +5,8 @@ require "rails/generators/named_base"
|
|
5
5
|
module Securial
|
6
6
|
module Generators
|
7
7
|
class ScaffoldGenerator < Rails::Generators::NamedBase
|
8
|
+
desc "Creates a scaffold for a given resource, including model, controller, views, routes, and tests."
|
9
|
+
|
8
10
|
include Rails::Generators::ResourceHelpers
|
9
11
|
|
10
12
|
allow_incompatible_default_type!
|
@@ -3,7 +3,7 @@ require "jwt"
|
|
3
3
|
module Securial
|
4
4
|
module Auth
|
5
5
|
module AuthEncoder
|
6
|
-
|
6
|
+
extend self
|
7
7
|
|
8
8
|
def encode(session)
|
9
9
|
return nil unless session && session.class == Securial::Session
|
@@ -37,6 +37,8 @@ module Securial
|
|
37
37
|
decoded.first
|
38
38
|
end
|
39
39
|
|
40
|
+
private
|
41
|
+
|
40
42
|
def secret
|
41
43
|
Securial.configuration.session_secret
|
42
44
|
end
|
@@ -48,8 +50,6 @@ module Securial
|
|
48
50
|
def expiry_duration
|
49
51
|
Securial.configuration.session_expiration_duration
|
50
52
|
end
|
51
|
-
|
52
|
-
private_class_method :secret, :algorithm, :expiry_duration
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -4,22 +4,22 @@ require "securerandom"
|
|
4
4
|
module Securial
|
5
5
|
module Auth
|
6
6
|
module TokenGenerator
|
7
|
-
|
8
|
-
def generate_refresh_token
|
9
|
-
secret = Securial.configuration.session_secret
|
10
|
-
algo = "SHA256"
|
7
|
+
extend self
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
def generate_refresh_token
|
10
|
+
secret = Securial.configuration.session_secret
|
11
|
+
algo = "SHA256"
|
15
12
|
|
16
|
-
|
17
|
-
|
13
|
+
random_data = SecureRandom.hex(32)
|
14
|
+
digest = OpenSSL::Digest.new(algo)
|
15
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, secret, random_data)
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
"#{hmac}#{random_data}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_password_reset_token
|
21
|
+
token = SecureRandom.alphanumeric(12)
|
22
|
+
"#{token[0, 6]}-#{token[6, 6]}"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
data/lib/securial/auth.rb
CHANGED
@@ -1,12 +1,50 @@
|
|
1
|
+
# @title Securial Authentication System
|
2
|
+
#
|
3
|
+
# Core authentication components for the Securial framework.
|
4
|
+
#
|
5
|
+
# This file serves as the entry point for authentication-related functionality in Securial,
|
6
|
+
# loading specialized modules that handle token generation, encoding/decoding, and session management.
|
7
|
+
# These components work together to provide a secure, flexible authentication system supporting
|
8
|
+
# token-based and session-based authentication patterns.
|
9
|
+
#
|
10
|
+
# @example Encoding a session token
|
11
|
+
# # Create an encoded JWT representing a user session
|
12
|
+
# token = Securial::Auth::AuthEncoder.encode(user_session)
|
13
|
+
# # => "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTY3ODkwIiwic3ViIjoiM..."
|
14
|
+
#
|
15
|
+
# @example Creating a new user session
|
16
|
+
# # Authenticate user and create a new session with tokens
|
17
|
+
# Securial::Auth::SessionCreator.create_session!(user, request)
|
18
|
+
#
|
19
|
+
# # Access the newly created session
|
20
|
+
# current_session = Current.session
|
21
|
+
#
|
22
|
+
# @example Generating secure tokens
|
23
|
+
# # Generate a password reset token
|
24
|
+
# reset_token = Securial::Auth::TokenGenerator.friendly_token
|
25
|
+
# # => "aBc123DeF456gHi789"
|
26
|
+
#
|
1
27
|
require "securial/auth/auth_encoder"
|
2
28
|
require "securial/auth/session_creator"
|
3
29
|
require "securial/auth/token_generator"
|
4
30
|
|
5
31
|
module Securial
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
32
|
+
# Namespace for authentication-related functionality in the Securial framework.
|
33
|
+
#
|
34
|
+
# The Auth module contains components that work together to provide a complete
|
35
|
+
# authentication system for Securial-powered applications:
|
36
|
+
#
|
37
|
+
# - {AuthEncoder} - Handles JWT token encoding and decoding with security measures
|
38
|
+
# - {SessionCreator} - Creates and manages authenticated user sessions
|
39
|
+
# - {TokenGenerator} - Generates secure random tokens for various authentication needs
|
40
|
+
#
|
41
|
+
# These components handle the cryptographic and security aspects of user authentication,
|
42
|
+
# ensuring that best practices are followed for token generation, validation, and session
|
43
|
+
# management throughout the application lifecycle.
|
44
|
+
#
|
45
|
+
# @see Securial::Auth::AuthEncoder
|
46
|
+
# @see Securial::Auth::SessionCreator
|
47
|
+
# @see Securial::Auth::TokenGenerator
|
48
|
+
#
|
49
|
+
module Auth; end
|
12
50
|
end
|
data/lib/securial/cli.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
# @title Securial Command Line Interface
|
2
|
+
#
|
3
|
+
# Command line interface for the Securial framework.
|
4
|
+
#
|
5
|
+
# This file implements the CLI tool for Securial, providing command-line utilities
|
6
|
+
# for creating new Rails applications with Securial pre-installed. It handles
|
7
|
+
# command parsing, option flags, and orchestrates the application setup process.
|
8
|
+
#
|
9
|
+
# @example Creating a new Rails application with Securial
|
10
|
+
# # Create a basic Securial application
|
11
|
+
# $ securial new myapp
|
12
|
+
#
|
13
|
+
# # Create an API-only Securial application with PostgreSQL
|
14
|
+
# $ securial new myapi --api --database=postgresql
|
15
|
+
#
|
16
|
+
require "optparse"
|
17
|
+
|
18
|
+
# rubocop:disable Rails/Exit, Rails/Output
|
19
|
+
|
20
|
+
module Securial
|
21
|
+
# Command-line interface for the Securial gem.
|
22
|
+
#
|
23
|
+
# This class provides the command-line functionality for Securial, enabling users
|
24
|
+
# to create new Rails applications with Securial pre-installed and configured.
|
25
|
+
# It handles command parsing, flag processing, and orchestrates the setup of
|
26
|
+
# new applications.
|
27
|
+
#
|
28
|
+
class CLI
|
29
|
+
# Entry point for the CLI application.
|
30
|
+
#
|
31
|
+
# Creates a new CLI instance and delegates to its start method.
|
32
|
+
#
|
33
|
+
# @param argv [Array<String>] command line arguments
|
34
|
+
# @return [Integer] exit status code (0 for success, non-zero for errors)
|
35
|
+
#
|
36
|
+
def self.start(argv)
|
37
|
+
new.start(argv)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Processes command line arguments and executes the appropriate action.
|
41
|
+
#
|
42
|
+
# This method handles both option flags (like --version) and commands
|
43
|
+
# (like 'new'), delegating to specialized handlers and returning
|
44
|
+
# the appropriate exit code.
|
45
|
+
#
|
46
|
+
# @param argv [Array<String>] command line arguments
|
47
|
+
# @return [Integer] exit status code (0 for success, non-zero for errors)
|
48
|
+
#
|
49
|
+
def start(argv)
|
50
|
+
# Process options and exit if a flag was handled
|
51
|
+
result = handle_flags(argv)
|
52
|
+
exit(result) if result
|
53
|
+
|
54
|
+
# Otherwise handle commands
|
55
|
+
exit(handle_commands(argv))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Processes option flags from the command line arguments.
|
61
|
+
#
|
62
|
+
# Parses options like --version and --help, executing their actions
|
63
|
+
# if present and removing them from the argument list.
|
64
|
+
#
|
65
|
+
# @param argv [Array<String>] command line arguments
|
66
|
+
# @return [nil, Integer] nil to continue processing, or exit code
|
67
|
+
#
|
68
|
+
def handle_flags(argv)
|
69
|
+
parser = create_option_parser
|
70
|
+
|
71
|
+
begin
|
72
|
+
parser.order!(argv)
|
73
|
+
nil # Continue to command handling
|
74
|
+
rescue OptionParser::InvalidOption => e
|
75
|
+
warn "ERROR: Illegal option(s): #{e.args.join(' ')}"
|
76
|
+
puts parser
|
77
|
+
1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Processes commands from the command line arguments.
|
82
|
+
#
|
83
|
+
# Identifies the command (e.g., 'new') and delegates to the appropriate
|
84
|
+
# handler method for that command.
|
85
|
+
#
|
86
|
+
# @param argv [Array<String>] command line arguments
|
87
|
+
# @return [Integer] exit status code (0 for success, non-zero for errors)
|
88
|
+
#
|
89
|
+
def handle_commands(argv)
|
90
|
+
cmd = argv.shift
|
91
|
+
|
92
|
+
case cmd
|
93
|
+
when "new"
|
94
|
+
handle_new_command(argv)
|
95
|
+
else
|
96
|
+
puts create_option_parser
|
97
|
+
1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Handles the 'new' command for creating Rails applications.
|
102
|
+
#
|
103
|
+
# Validates that an application name is provided, then delegates to
|
104
|
+
# securial_new to create the application with Securial.
|
105
|
+
#
|
106
|
+
# @param argv [Array<String>] remaining command line arguments
|
107
|
+
# @return [Integer] exit status code (0 for success, non-zero for errors)
|
108
|
+
#
|
109
|
+
def handle_new_command(argv)
|
110
|
+
app_name = argv.shift
|
111
|
+
|
112
|
+
if app_name.nil?
|
113
|
+
puts "ERROR: Please provide an app name."
|
114
|
+
puts create_option_parser
|
115
|
+
return 1
|
116
|
+
end
|
117
|
+
|
118
|
+
securial_new(app_name, argv)
|
119
|
+
0
|
120
|
+
end
|
121
|
+
|
122
|
+
# Creates and configures the option parser.
|
123
|
+
#
|
124
|
+
# Sets up the command-line options and their handlers.
|
125
|
+
#
|
126
|
+
# @return [OptionParser] configured option parser instance
|
127
|
+
#
|
128
|
+
def create_option_parser
|
129
|
+
OptionParser.new do |opts|
|
130
|
+
opts.banner = "Usage: securial [options] <command> [command options]\n\n"
|
131
|
+
|
132
|
+
opts.separator ""
|
133
|
+
opts.separator "Commands:"
|
134
|
+
opts.separator " new APP_NAME [rails_options...] # Create a new Rails app with Securial pre-installed"
|
135
|
+
opts.separator ""
|
136
|
+
opts.separator "Options:"
|
137
|
+
|
138
|
+
opts.on("-v", "--version", "Show Securial version") do
|
139
|
+
show_version
|
140
|
+
exit(0)
|
141
|
+
end
|
142
|
+
|
143
|
+
opts.on("-h", "--help", "Show this help message") do
|
144
|
+
puts opts
|
145
|
+
exit(0)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Displays the current Securial version.
|
151
|
+
#
|
152
|
+
# @return [void]
|
153
|
+
#
|
154
|
+
def show_version
|
155
|
+
require "securial/version"
|
156
|
+
puts "Securial v#{Securial::VERSION}"
|
157
|
+
rescue LoadError
|
158
|
+
puts "Securial version information not available."
|
159
|
+
end
|
160
|
+
|
161
|
+
# Creates a new Rails application with Securial pre-installed.
|
162
|
+
#
|
163
|
+
# Orchestrates the process of creating a Rails application, adding the
|
164
|
+
# Securial gem, installing dependencies, and configuring the application.
|
165
|
+
#
|
166
|
+
# @param app_name [String] name of the Rails application to create
|
167
|
+
# @param rails_options [Array<String>] options to pass to 'rails new'
|
168
|
+
# @return [void]
|
169
|
+
#
|
170
|
+
def securial_new(app_name, rails_options)
|
171
|
+
puts "🏗️ Creating new Rails app: #{app_name}"
|
172
|
+
|
173
|
+
create_rails_app(app_name, rails_options)
|
174
|
+
add_securial_gem(app_name)
|
175
|
+
install_gems(app_name)
|
176
|
+
install_securial(app_name)
|
177
|
+
mount_securial_engine(app_name)
|
178
|
+
print_final_instructions(app_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Creates a new Rails application.
|
182
|
+
#
|
183
|
+
# @param app_name [String] name of the Rails application to create
|
184
|
+
# @param rails_options [Array<String>] options to pass to 'rails new'
|
185
|
+
# @return [Integer] command exit status
|
186
|
+
#
|
187
|
+
def create_rails_app(app_name, rails_options)
|
188
|
+
rails_command = ["rails", "new", app_name, *rails_options]
|
189
|
+
run(rails_command)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Adds the Securial gem to the application's Gemfile.
|
193
|
+
#
|
194
|
+
# @param app_name [String] name of the Rails application
|
195
|
+
# @return [void]
|
196
|
+
#
|
197
|
+
def add_securial_gem(app_name)
|
198
|
+
puts "📦 Adding Securial gem to Gemfile"
|
199
|
+
gemfile_path = File.join(app_name, "Gemfile")
|
200
|
+
File.open(gemfile_path, "a") { |f| f.puts "\ngem 'securial'" }
|
201
|
+
end
|
202
|
+
|
203
|
+
# Installs gems for the application using Bundler.
|
204
|
+
#
|
205
|
+
# @param app_name [String] name of the Rails application
|
206
|
+
# @return [Integer] command exit status
|
207
|
+
#
|
208
|
+
def install_gems(app_name)
|
209
|
+
run("bundle install", chdir: app_name)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Installs and configures Securial in the application.
|
213
|
+
#
|
214
|
+
# @param app_name [String] name of the Rails application
|
215
|
+
# @return [Integer] command exit status
|
216
|
+
#
|
217
|
+
def install_securial(app_name)
|
218
|
+
puts "🔧 Installing Securial"
|
219
|
+
run("bin/rails generate securial:install", chdir: app_name)
|
220
|
+
run("bin/rails db:migrate", chdir: app_name)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Mounts the Securial engine in the application's routes.
|
224
|
+
#
|
225
|
+
# @param app_name [String] name of the Rails application
|
226
|
+
# @return [void]
|
227
|
+
#
|
228
|
+
def mount_securial_engine(app_name)
|
229
|
+
puts "🔗 Mounting Securial engine in routes"
|
230
|
+
routes_path = File.join(app_name, "config/routes.rb")
|
231
|
+
routes = File.read(routes_path)
|
232
|
+
updated = routes.sub("Rails.application.routes.draw do") do |match|
|
233
|
+
"#{match}\n mount Securial::Engine => '/securial'"
|
234
|
+
end
|
235
|
+
File.write(routes_path, updated)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Prints final setup instructions after successful installation.
|
239
|
+
#
|
240
|
+
# @param app_name [String] name of the Rails application
|
241
|
+
# @return [void]
|
242
|
+
#
|
243
|
+
def print_final_instructions(app_name)
|
244
|
+
puts <<~INSTRUCTIONS
|
245
|
+
🎉 Securial has been successfully installed in your Rails app!
|
246
|
+
✅ Your app is ready at: ./#{app_name}
|
247
|
+
|
248
|
+
➡️ Next steps:
|
249
|
+
cd #{app_name}
|
250
|
+
⚙️ Optional: Configure Securial in config/initializers/securial.rb
|
251
|
+
rails server
|
252
|
+
INSTRUCTIONS
|
253
|
+
end
|
254
|
+
|
255
|
+
# Runs a system command with optional directory change.
|
256
|
+
#
|
257
|
+
# Executes the provided command, optionally in a different directory,
|
258
|
+
# and handles success/failure conditions.
|
259
|
+
#
|
260
|
+
# @param command [String, Array<String>] command to run
|
261
|
+
# @param chdir [String, nil] directory to change to before running command
|
262
|
+
# @return [Integer] command exit status code (0 for success)
|
263
|
+
# @raise [SystemExit] if the command fails
|
264
|
+
#
|
265
|
+
def run(command, chdir: nil)
|
266
|
+
puts "→ #{command.inspect}"
|
267
|
+
result =
|
268
|
+
if chdir
|
269
|
+
Dir.chdir(chdir) { system(*command) }
|
270
|
+
else
|
271
|
+
system(*command)
|
272
|
+
end
|
273
|
+
|
274
|
+
unless result
|
275
|
+
abort("❌ Command failed: #{command}")
|
276
|
+
end
|
277
|
+
0
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# rubocop:enable Rails/Exit, Rails/Output
|
@@ -3,68 +3,67 @@ require "securial/logger"
|
|
3
3
|
module Securial
|
4
4
|
module Config
|
5
5
|
module Validation
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
extend self
|
7
|
+
def validate_all!(securial_config)
|
8
|
+
signature = Securial::Config::Signature.config_signature
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
validate_required_fields!(signature, securial_config)
|
11
|
+
validate_types_and_values!(signature, securial_config)
|
12
|
+
validate_password_lengths!(securial_config)
|
13
|
+
end
|
14
14
|
|
15
|
-
|
15
|
+
private
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
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.")
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
if options[:type] == ActiveSupport::Duration && value <= 0
|
44
|
-
raise_error("#{key} must be a positive duration, but got #{value}.")
|
45
|
-
end
|
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
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
if options[:type] == ActiveSupport::Duration && value <= 0
|
44
|
+
raise_error("#{key} must be a positive duration, but got #{value}.")
|
45
|
+
end
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
end
|
47
|
+
if options[:type] == Numeric && value < 0
|
48
|
+
raise_error("#{key} must be a non-negative numeric value, but got #{value}.")
|
54
49
|
end
|
55
|
-
end
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
raise_error("password_min_length cannot be greater than password_max_length.")
|
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}.")
|
60
53
|
end
|
61
54
|
end
|
55
|
+
end
|
62
56
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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.")
|
66
60
|
end
|
67
61
|
end
|
62
|
+
|
63
|
+
def raise_error(msg)
|
64
|
+
Securial.logger.fatal msg
|
65
|
+
raise Securial::Error::Config::InvalidConfigurationError, msg
|
66
|
+
end
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|