securial 0.7.0 → 0.8.0

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -5
  3. data/app/controllers/concerns/securial/identity.rb +17 -16
  4. data/app/controllers/securial/accounts_controller.rb +2 -2
  5. data/app/controllers/securial/passwords_controller.rb +2 -2
  6. data/app/controllers/securial/role_assignments_controller.rb +5 -7
  7. data/app/controllers/securial/roles_controller.rb +1 -0
  8. data/app/controllers/securial/sessions_controller.rb +6 -8
  9. data/app/controllers/securial/users_controller.rb +8 -7
  10. data/app/mailers/securial/securial_mailer.rb +17 -6
  11. data/app/models/concerns/securial/password_resettable.rb +1 -1
  12. data/app/models/securial/application_record.rb +1 -1
  13. data/app/models/securial/session.rb +16 -5
  14. data/app/models/securial/user.rb +1 -3
  15. data/app/views/securial/securial_mailer/forgot_password.html.erb +20 -0
  16. data/app/views/securial/securial_mailer/forgot_password.text.erb +14 -0
  17. data/app/views/securial/securial_mailer/sign_in.html.erb +31 -0
  18. data/app/views/securial/securial_mailer/sign_in.text.erb +17 -0
  19. data/app/views/securial/securial_mailer/update_account.html.erb +15 -0
  20. data/app/views/securial/securial_mailer/update_account.text.erb +11 -0
  21. data/app/views/securial/securial_mailer/welcome.html.erb +11 -0
  22. data/app/views/securial/securial_mailer/welcome.text.erb +8 -0
  23. data/app/views/securial/users/_securial_user.json.jbuilder +9 -3
  24. data/config/routes.rb +5 -1
  25. data/db/migrate/{20250515104930_create_securial_roles.rb → 20250603130214_create_securial_roles.rb} +0 -2
  26. data/db/migrate/{20250517155521_create_securial_users.rb → 20250604110520_create_securial_users.rb} +8 -3
  27. data/db/migrate/{20250519075407_create_securial_sessions.rb → 20250604184841_create_securial_sessions.rb} +4 -0
  28. data/lib/generators/securial/install/templates/securial_initializer.erb +1 -1
  29. data/lib/generators/securial/scaffold/templates/controller.erb +1 -0
  30. data/lib/generators/securial/scaffold/templates/routes.erb +1 -1
  31. data/lib/securial/auth/auth_encoder.rb +8 -6
  32. data/lib/securial/auth/session_creator.rb +6 -3
  33. data/lib/securial/auth/token_generator.rb +26 -0
  34. data/lib/securial/auth.rb +7 -5
  35. data/lib/securial/config/configuration.rb +31 -13
  36. data/lib/securial/config/validation/logger_validation.rb +29 -0
  37. data/lib/securial/config/validation/mailer_validation.rb +24 -0
  38. data/lib/securial/config/validation/password_validation.rb +91 -0
  39. data/lib/securial/config/validation/response_validation.rb +37 -0
  40. data/lib/securial/config/validation/roles_validation.rb +32 -0
  41. data/lib/securial/config/validation/security_validation.rb +56 -0
  42. data/lib/securial/config/validation/session_validation.rb +87 -0
  43. data/lib/securial/config/validation.rb +16 -239
  44. data/lib/securial/config.rb +20 -4
  45. data/lib/securial/engine.rb +5 -79
  46. data/lib/securial/engine_initializers.rb +33 -0
  47. data/lib/securial/error/auth.rb +21 -0
  48. data/lib/securial/error/base_securial_error.rb +16 -0
  49. data/lib/securial/error/config.rb +37 -0
  50. data/lib/securial/error.rb +9 -0
  51. data/lib/securial/helpers/roles_helper.rb +17 -0
  52. data/lib/securial/helpers.rb +1 -0
  53. data/lib/securial/logger/broadcaster.rb +36 -24
  54. data/lib/securial/logger/builder.rb +29 -44
  55. data/lib/securial/logger/formatter.rb +35 -0
  56. data/lib/securial/logger.rb +10 -3
  57. data/lib/securial/middleware/request_tag_logger.rb +39 -0
  58. data/lib/securial/middleware.rb +13 -0
  59. data/lib/securial/version.rb +1 -1
  60. data/lib/securial.rb +2 -26
  61. metadata +39 -149
  62. data/app/views/securial/securial_mailer/reset_password.html.erb +0 -5
  63. data/app/views/securial/securial_mailer/reset_password.text.erb +0 -4
  64. data/lib/securial/auth/errors.rb +0 -15
  65. data/lib/securial/config/errors.rb +0 -20
  66. data/lib/securial/inspectors/route_inspector.rb +0 -52
  67. data/lib/securial/inspectors.rb +0 -8
  68. data/lib/securial/key_transformer.rb +0 -32
  69. data/lib/securial/logger/colors.rb +0 -14
  70. data/lib/securial/middlewares/request_logger_tag.rb +0 -19
  71. data/lib/securial/middlewares/transform_request_keys.rb +0 -33
  72. data/lib/securial/middlewares/transform_response_keys.rb +0 -52
  73. data/lib/securial/middlewares.rb +0 -10
  74. data/lib/securial/security/request_rate_limiter.rb +0 -68
  75. data/lib/securial/security.rb +0 -8
  76. data/lib/securial/version_checker.rb +0 -31
  77. /data/db/migrate/{20250518122749_create_securial_role_assignments.rb → 20250604123805_create_securial_role_assignments.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6955f517ae29e92b5a870f5e951782374d39f7c5eeb19de024d818106a57cc82
4
- data.tar.gz: a91673febf7143ab6b245feab4154d1d511ca62b7f78f6a94c758b974c2860ed
3
+ metadata.gz: d5d32016af493f09599093d6a47f3ad2ec6b1be080a82d1ef275374249802808
4
+ data.tar.gz: 75b64439f12e4b9f581a625967d83b39ebd07215be6c722ce4fbe81c50d3b0c9
5
5
  SHA512:
6
- metadata.gz: 18a435b9714861bf5f76e516d54b5ed0a3ad4b9d1e192825f2766fab494f90d0579a89d5f5d812b67cc33bb876a174e9341980997ead6a6a5e134bce26637fed
7
- data.tar.gz: f584db4b3d36d73d0a5fe83439ef75b409e2fe971337a3ad8b25bb9335727053fed1f272057062a2061b0b2b8c01d866f91e894570c77e4320277ea69ead268c
6
+ metadata.gz: 0da8943af4c5a85090d711e862d0e00185f0af951c9a619796fc9d3de3cfb37ab939fea73c690b6378495e66794b9f5ebe26f010fd44bc95118acc727654114a
7
+ data.tar.gz: 6785f1890063c01eac1a2b8f3b15aee18eb6cc8429eead79c15c65aadf5854a5496ab8bf5f0ac572f5a47020108dce77d23bbf49bd091062b3f5900a0fb4f002
data/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/AlyBadawy/Securial?tab=MIT-1-ov-file#readme)
6
6
 
7
7
  [![Tests](https://github.com/alybadawy/securial/actions/workflows/ci.yml/badge.svg)](https://github.com/alybadawy/securial/actions)
8
- [![Coverage Status](https://coveralls.io/repos/github/AlyBadawy/Securial/badge.svg?branch=main)](https://coveralls.io/github/AlyBadawy/Securial?branch=main)
8
+ [![Coveralls](https://img.shields.io/coverallsCoverage/github/AlyBadawy/Securial?branch=main&logo=coveralls&logoColor=%233F5767&labelColor=ddeedd)
9
+ ](https://coveralls.io/github/AlyBadawy/Securial?branch=main)
9
10
 
10
11
  > [!WARNING]
11
12
  >
@@ -26,15 +27,12 @@
26
27
  - ✅ Clean, JSON-based API responses
27
28
  - ✅ Database-agnostic support
28
29
 
29
-
30
30
  ### 🚀 Why Securial?
31
31
 
32
32
  Securial was built to offer a clean, modular, and API-first authentication system for Rails developers who want full control without the black-box complexity. Whether you're building for the web, mobile, or both, Securial gives you the flexibility to implement exactly what you need — from simple JWT authentication to more advanced setups involving sessions, API tokens, and role-based access.
33
33
 
34
34
  It follows familiar Rails conventions, stays lightweight and database-agnostic, and keeps security at the core. With fully customizable controllers, serializers, and logic, Securial is designed to grow with your project — making it an ideal choice for everything from side projects to production-grade APIs.
35
35
 
36
-
37
-
38
36
  ## 🚀 Installation
39
37
 
40
38
  Securial can be installed on an existing Rails application or use the `securial new app_name` command to create a new Securial-ready Rails app.
@@ -94,7 +92,6 @@ Explore all modules in the [Wiki](https://github.com/AlyBadawy/Securial/wiki).
94
92
  - Run `bundle install`
95
93
  - Start coding right away 🏃‍♂️
96
94
 
97
-
98
95
  To run the test suite:
99
96
 
100
97
  ```bash
@@ -14,9 +14,10 @@ module Securial
14
14
  end
15
15
 
16
16
  def authenticate_admin!
17
- unless current_user.is_admin?
18
- render status: :forbidden, json: { error: "You are not authorized to perform this action" } and return
19
- end
17
+ authenticate_user!
18
+ return if current_user.blank? || current_user.is_admin?
19
+
20
+ render status: :forbidden, json: { error: "You are not authorized to perform this action" }
20
21
  end
21
22
 
22
23
  def current_user
@@ -28,26 +29,26 @@ module Securial
28
29
  def authenticate_user!
29
30
  return if internal_rails_request?
30
31
 
32
+ Current.session = nil
31
33
  auth_header = request.headers["Authorization"]
32
34
  if auth_header.present? && auth_header.start_with?("Bearer ")
33
35
  token = auth_header.split(" ").last
34
36
  begin
35
37
  decoded_token = Securial::Auth::AuthEncoder.decode(token)
36
- Current.session = Session.find_by!(id: decoded_token["jti"], revoked: false)
37
- rescue Securial::Auth::Errors::AuthDecodeError, ActiveRecord::RecordNotFound => e
38
- render status: :unauthorized, json: { error: "Invalid token: #{e.message}" } and return
38
+ session = Session.find_by(id: decoded_token["jti"], revoked: false)
39
+ if session.present? &&
40
+ session.is_valid_session? &&
41
+ session.ip_address == request.remote_ip &&
42
+ session.user_agent == request.user_agent
43
+ Current.session = session
44
+ return # Authenticated
45
+ end
46
+ rescue Securial::Error::Auth::TokenDecodeError, ActiveRecord::RecordNotFound => e
47
+ Securial.logger.debug "Authentication failed: #{e.message}"
39
48
  end
40
- else
41
- render status: :unauthorized, json: { error: "Missing or invalid Authorization header" } and return
42
49
  end
43
- end
44
-
45
- def start_new_session_for(user)
46
- Securial::Auth::SessionCreator.create_session(user, request)
47
- end
48
-
49
- def create_jwt_for_current_session
50
- Securial::Auth::AuthEncoder.encode(Current.session)
50
+ # If we reach here, authentication failed
51
+ render status: :unauthorized, json: { error: "You are not signed in" } and return
51
52
  end
52
53
 
53
54
  def internal_rails_request?
@@ -7,12 +7,12 @@ module Securial
7
7
  end
8
8
 
9
9
  def show
10
- @securial_user = User.find_by(username: params.expect(:username))
10
+ @securial_user = Securial::User.find_by(username: params.expect(:username))
11
11
  render_user_profile
12
12
  end
13
13
 
14
14
  def register
15
- @securial_user = User.new(user_params)
15
+ @securial_user = Securial::User.new(user_params)
16
16
  if @securial_user.save
17
17
  render :show, status: :created, location: @securial_user
18
18
  else
@@ -6,7 +6,7 @@ module Securial
6
6
  def forgot_password
7
7
  if user = User.find_by(email_address: params.require(:email_address))
8
8
  user.generate_reset_password_token!
9
- Securial::SecurialMailer.reset_password(user).deliver_later
9
+ Securial::SecurialMailer.forgot_password(user).deliver_later
10
10
  end
11
11
 
12
12
  render status: :ok, json: { message: "Password reset instructions sent (if user with that email address exists)." }
@@ -26,7 +26,7 @@ module Securial
26
26
  def set_user_by_password_token
27
27
  @user = User.find_by_reset_password_token!(params[:token]) # rubocop:disable Rails/DynamicFindBy
28
28
  unless @user.reset_password_token_valid?
29
- render status: :unprocessable_entity, json: { errors: { token: "is invalid or has expired" } }
29
+ render status: :unprocessable_entity, json: { errors: { token: "is invalid or has expired" } } and return
30
30
  end
31
31
  rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveRecord::RecordNotFound
32
32
  render status: :unprocessable_entity, json: { errors: { token: "is invalid or has expired" } } and return
@@ -3,13 +3,12 @@ module Securial
3
3
  def create
4
4
  return unless define_user_and_role
5
5
 
6
- if @user.roles.exists?(@role.id)
6
+ if @securial_user.roles.exists?(@securial_role.id)
7
7
  render json: { error: "Role already assigned to user" }, status: :unprocessable_entity
8
8
  return
9
9
  end
10
10
  @securial_role_assignment = RoleAssignment.new(securial_role_assignment_params)
11
11
  @securial_role_assignment.save
12
- @securial_user = @user
13
12
  render :show, status: :created
14
13
  end
15
14
 
@@ -18,7 +17,6 @@ module Securial
18
17
  @role_assignment = RoleAssignment.find_by(securial_role_assignment_params)
19
18
  if @role_assignment
20
19
  @role_assignment.destroy!
21
- @securial_user = @user
22
20
  render :show, status: :ok
23
21
  else
24
22
  render json: { error: "Role is not assigned to user" }, status: :unprocessable_entity
@@ -28,13 +26,13 @@ module Securial
28
26
  private
29
27
 
30
28
  def define_user_and_role
31
- @user = User.find_by(id: params.expect(securial_role_assignment: [:user_id]).dig(:user_id))
32
- @role = Role.find_by(id: params.expect(securial_role_assignment: [:role_id]).dig(:role_id))
33
- if @user.nil?
29
+ @securial_user = User.find_by(id: params.expect(securial_role_assignment: [:user_id]).dig(:user_id))
30
+ @securial_role = Role.find_by(id: params.expect(securial_role_assignment: [:role_id]).dig(:role_id))
31
+ if @securial_user.nil?
34
32
  render json: { error: "User not found" }, status: :unprocessable_entity
35
33
  return false
36
34
  end
37
- if @role.nil?
35
+ if @securial_role.nil?
38
36
  render json: { error: "Role not found" }, status: :unprocessable_entity
39
37
  return false
40
38
  end
@@ -29,6 +29,7 @@ module Securial
29
29
 
30
30
  def destroy
31
31
  @securial_role.destroy
32
+ head :no_content
32
33
  end
33
34
 
34
35
  private
@@ -31,14 +31,12 @@ module Securial
31
31
  end
32
32
 
33
33
  def refresh
34
- if Current.session = Session.find_by(refresh_token: params[:refresh_token])
35
- if Current.session.is_valid_session? &&
36
- Current.session.ip_address == request.ip &&
37
- Current.session.user_agent == request.user_agent
34
+ if Current.session = Securial::Session.find_by(refresh_token: params[:refresh_token])
35
+ if Current.session.is_valid_session_request?(request)
38
36
  Current.session.refresh!
39
37
  render status: :created,
40
38
  json: {
41
- access_token: create_jwt_for_current_session,
39
+ access_token: Securial::Auth::AuthEncoder.encode(Current.session),
42
40
  refresh_token: Current.session.refresh_token,
43
41
  refresh_token_expires_at: Current.session.refresh_token_expires_at,
44
42
  }
@@ -64,7 +62,7 @@ module Securial
64
62
 
65
63
  def set_session
66
64
  id = params[:id]
67
- @securial_session = id ? Current.user.sessions.find(params.expect(:id)) : Current.session
65
+ @securial_session = id ? Current.user.sessions.find(params[:id]) : Current.session
68
66
  end
69
67
 
70
68
  def render_login_response(user)
@@ -75,10 +73,10 @@ module Securial
75
73
  instructions: "Please reset your password before logging in.",
76
74
  }
77
75
  else
78
- start_new_session_for user
76
+ Securial::Auth::SessionCreator.create_session!(user, request)
79
77
  render status: :created,
80
78
  json: {
81
- access_token: create_jwt_for_current_session,
79
+ access_token: Securial::Auth::AuthEncoder.encode(Current.session),
82
80
  refresh_token: Current.session.refresh_token,
83
81
  refresh_token_expires_at: Current.session.refresh_token_expires_at,
84
82
  }
@@ -28,26 +28,27 @@ module Securial
28
28
  end
29
29
 
30
30
  def destroy
31
- @securial_user.destroy!
31
+ @securial_user.destroy
32
+ head :no_content
32
33
  end
33
34
 
34
35
  private
35
36
 
36
37
  def set_securial_user
37
- @securial_user = User.find(params.expect(:id))
38
+ @securial_user = User.find(params[:id])
38
39
  end
39
40
 
40
41
  def securial_user_params
41
42
  params.expect(securial_user: [
42
43
  :email_address,
43
- :password,
44
- :password_confirmation,
44
+ :username,
45
45
  :first_name,
46
46
  :last_name,
47
47
  :phone,
48
- :username,
49
- :bio
50
- ])
48
+ :bio,
49
+ :password,
50
+ :password_confirmation
51
+ ])
51
52
  end
52
53
  end
53
54
  end
@@ -1,17 +1,28 @@
1
1
  module Securial
2
2
  class SecurialMailer < ApplicationMailer
3
- default from: Securial.configuration.mailer_sender
3
+ def welcome(securial_user)
4
+ @user = securial_user
5
+ subject = Securial.configuration.mailer_sign_up_subject
6
+ mail subject: subject, to: securial_user.email_address
7
+ end
4
8
 
5
- def reset_password(securial_user)
9
+ def sign_in(securial_user, securial_session)
6
10
  @user = securial_user
7
- subject = reset_password_subject
11
+ @session = securial_session
12
+ subject = Securial.configuration.mailer_sign_in_subject
8
13
  mail subject: subject, to: securial_user.email_address
9
14
  end
10
15
 
11
- private
16
+ def update_account(securial_user)
17
+ @user = securial_user
18
+ subject = Securial.configuration.mailer_update_account_subject
19
+ mail subject: subject, to: securial_user.email_address
20
+ end
12
21
 
13
- def reset_password_subject
14
- Securial.configuration.password_reset_email_subject
22
+ def forgot_password(securial_user)
23
+ @user = securial_user
24
+ subject = Securial.configuration.mailer_forgot_password_subject
25
+ mail subject: subject, to: securial_user.email_address
15
26
  end
16
27
  end
17
28
  end
@@ -25,7 +25,7 @@ module Securial
25
25
 
26
26
  def generate_reset_password_token!
27
27
  update!(
28
- reset_password_token: SecureRandom.urlsafe_base64,
28
+ reset_password_token: Auth::TokenGenerator.generate_password_reset_token,
29
29
  reset_password_token_created_at: Time.current
30
30
  )
31
31
  end
@@ -12,7 +12,7 @@ module Securial
12
12
  def generate_uuid_v7
13
13
  return if self.id.present? || self.class.type_for_attribute(:id).type != :string
14
14
 
15
- self.id ||= Random.uuid_v7
15
+ self.id = Random.uuid_v7
16
16
  end
17
17
  end
18
18
  end
@@ -11,17 +11,28 @@ module Securial
11
11
  end
12
12
 
13
13
  def is_valid_session?
14
- !revoked && refresh_token_expires_at > Time.current
14
+ revoked? || expired? ? false : true
15
+ end
16
+
17
+ def is_valid_session_request?(request)
18
+ is_valid_session? && ip_address == request.ip && user_agent == request.user_agent
15
19
  end
16
20
 
17
21
  def refresh!
18
- raise Securial::Auth::Errors::AuthRevokedError, "Session is revoked" if revoked
19
- raise Securial::Auth::Errors::AuthExpiredError, "Session is expired" if refresh_token_expires_at < Time.current
22
+ raise Securial::Error::Auth::TokenRevokedError if revoked?
23
+ raise Securial::Error::Auth::TokenExpiredError if expired?
24
+
25
+ new_refresh_token = Securial::Auth::TokenGenerator.generate_refresh_token
20
26
 
21
- update!(refresh_token: SecureRandom.hex(64),
27
+ refresh_token_duration = Securial.configuration.session_refresh_token_expires_in
28
+
29
+ update!(refresh_token: new_refresh_token,
22
30
  refresh_count: self.refresh_count + 1,
23
31
  last_refreshed_at: Time.current,
24
- refresh_token_expires_at: 1.week.from_now)
32
+ refresh_token_expires_at: refresh_token_duration.from_now)
25
33
  end
34
+
35
+ def revoked?; revoked; end
36
+ def expired?; refresh_token_expires_at < Time.current; end
26
37
  end
27
38
  end
@@ -46,9 +46,7 @@ module Securial
46
46
 
47
47
 
48
48
  def is_admin?
49
- titleized_role_name = Securial.configuration.admin_role.to_s.strip.titleize
50
-
51
- roles.exists?(role_name: titleized_role_name)
49
+ roles.exists?(role_name: Securial.titleized_admin_role)
52
50
  end
53
51
  end
54
52
  end
@@ -0,0 +1,20 @@
1
+ <h1>Password Reset Instructions for <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %></h1>
2
+ <p>
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+ </p>
5
+ <p>
6
+ We received a request to reset your password for your account.
7
+ </p>
8
+ <p>
9
+ To reset your password, use the following token:
10
+ </p>
11
+ <p style="font-size: 1.2em; font-weight: bold; letter-spacing: 2px; background: #f7f7f7; padding: 8px 16px; display: inline-block;">
12
+ <%= @user.reset_password_token %>
13
+ </p>
14
+ <p>
15
+ If you did not request a password reset, please ignore this email.
16
+ </p>
17
+ <p>
18
+ Best regards,<br>
19
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
20
+ </p>
@@ -0,0 +1,14 @@
1
+ Password Reset Instructions for <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %>
2
+
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+
5
+ We received a request to reset your password for your account.
6
+
7
+ To reset your password, use the following token:
8
+
9
+ <%= @user.reset_password_token %>
10
+
11
+ If you did not request a password reset, please ignore this email.
12
+
13
+ Best regards,
14
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
@@ -0,0 +1,31 @@
1
+ <h1>Sign-in Alert for <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %></h1>
2
+ <p>
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+ </p>
5
+ <p>
6
+ We noticed a sign-in to your account.
7
+ </p>
8
+ <table style="border-collapse: collapse;">
9
+ <tr>
10
+ <td style="padding: 4px 8px; font-weight: bold;">IP Address:</td>
11
+ <td style="padding: 4px 8px;"><%= @session.ip_address %></td>
12
+ </tr>
13
+ <tr>
14
+ <td style="padding: 4px 8px; font-weight: bold;">Device/Browser:</td>
15
+ <td style="padding: 4px 8px;"><%= @session.user_agent %></td>
16
+ </tr>
17
+ <% if @session.respond_to?(:created_at) %>
18
+ <tr>
19
+ <td style="padding: 4px 8px; font-weight: bold;">Time:</td>
20
+ <td style="padding: 4px 8px;"><%= @session.created_at.strftime("%B %d, %Y at %H:%M %Z") %></td>
21
+ </tr>
22
+ <% end %>
23
+ </table>
24
+ <p>
25
+ If this was you, you can safely ignore this message.<br>
26
+ If you did not sign in, please reset your password immediately or contact support.
27
+ </p>
28
+ <p>
29
+ Best regards,<br>
30
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
31
+ </p>
@@ -0,0 +1,17 @@
1
+ Sign-in Alert for <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %>
2
+
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+
5
+ We noticed a sign-in to your account.
6
+
7
+ IP Address: <%= @session.ip_address %>
8
+ Device/Browser: <%= @session.user_agent %>
9
+ <% if @session.respond_to?(:created_at) %>
10
+ Time: <%= @session.created_at.strftime("%B %d, %Y at %H:%M %Z") %>
11
+ <% end %>
12
+
13
+ If this was you, you can safely ignore this message.
14
+ If you did not sign in, please reset your password immediately or contact support.
15
+
16
+ Best regards,
17
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
@@ -0,0 +1,15 @@
1
+ <h1>Your Account Has Been Updated on <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %></h1>
2
+ <p>
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+ </p>
5
+ <p>
6
+ This is a confirmation that changes were made to your account.
7
+ </p>
8
+ <p>
9
+ If you made these changes, you do not need to do anything further.<br>
10
+ If you did not update your account, please review your account security or contact support immediately.
11
+ </p>
12
+ <p>
13
+ Best regards,<br>
14
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
15
+ </p>
@@ -0,0 +1,11 @@
1
+ Your Account Has Been Updated on <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %>
2
+
3
+ Hello <%= @user.first_name || @user.email_address %>,
4
+
5
+ This is a confirmation that changes were made to your account.
6
+
7
+ If you made these changes, you do not need to do anything further.
8
+ If you did not update your account, please review your account security or contact support immediately.
9
+
10
+ Best regards,
11
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
@@ -0,0 +1,11 @@
1
+ <h1>Welcome to <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %>, <%= @user.first_name || @user.email_address %>!</h1>
2
+ <p>
3
+ Thank you for signing up. We're excited to have you on board.
4
+ </p>
5
+ <p>
6
+ If you have any questions or need help getting started, feel free to reply to this email.
7
+ </p>
8
+ <p>
9
+ Best regards,<br>
10
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
11
+ </p>
@@ -0,0 +1,8 @@
1
+ Welcome to <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %>, <%= @user.first_name || @user.email_address %>!
2
+
3
+ Thank you for signing up. We're excited to have you on board.
4
+
5
+ If you have any questions or need help getting started, feel free to reply to this email.
6
+
7
+ Best regards,
8
+ The <%= Securial.configuration.app_name || Rails.application.class.module_parent_name %> Team
@@ -1,15 +1,21 @@
1
1
  json.id securial_user.id
2
2
 
3
+ json.email_address securial_user.email_address
4
+ json.username securial_user.username
3
5
  json.first_name securial_user.first_name
4
6
  json.last_name securial_user.last_name
5
7
  json.phone securial_user.phone
6
- json.username securial_user.username
7
8
  json.bio securial_user.bio
9
+ json.email_verified securial_user.email_verified
10
+ json.locked securial_user.locked
11
+ json.locked_at securial_user.locked_at
8
12
 
9
- json.password_expired securial_user.password_expired?
13
+ # json.password_expired securial_user.password_expired?
14
+ # This field is currently commented out because it is not required in the JSON response.
15
+ # Uncomment this line if the `password_expired` field needs to be included in the future.
10
16
 
11
17
  json.roles securial_user.roles, partial: "securial/roles/securial_role", as: :securial_role
12
18
 
13
19
  json.partial! "securial/shared/timestamps", record: securial_user
14
20
 
15
- json.url securial.user_url(securial_user, format: :json)
21
+ json.url securial.users_url(securial_user, format: :json)
data/config/routes.rb CHANGED
@@ -1,8 +1,12 @@
1
+ Rails.application.routes.default_url_options = { host: "localhost", port: 3000, protocol: "http" }
2
+
1
3
  Securial::Engine.routes.draw do
4
+ default_url_options host: "example.com"
5
+
2
6
  defaults format: :json do
3
7
  get "/status", to: "status#show", as: :status
4
8
 
5
- scope Securial.admin_namespace do
9
+ scope Securial.protected_namespace do
6
10
  resources :roles
7
11
  resources :users
8
12
  namespace :role_assignments, as: "role_assignments" do
@@ -6,7 +6,5 @@ class CreateSecurialRoles < ActiveRecord::Migration[8.0]
6
6
 
7
7
  t.timestamps
8
8
  end
9
-
10
- add_index :securial_roles, :role_name, unique: true
11
9
  end
12
10
  end
@@ -1,16 +1,21 @@
1
1
  class CreateSecurialUsers < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :securial_users, id: :string do |t|
4
- t.string :email_address
4
+ t.string :email_address, null: false
5
+ t.boolean :email_verified, null: false, default: false
6
+ t.string :email_verification_token
7
+ t.datetime :email_verification_token_created_at
8
+ t.string :username, null: false
5
9
  t.string :first_name
6
10
  t.string :last_name
7
11
  t.string :phone
8
- t.string :username
9
12
  t.string :bio
10
13
  t.string :password_digest
14
+ t.datetime :password_changed_at
11
15
  t.string :reset_password_token
12
16
  t.datetime :reset_password_token_created_at
13
- t.datetime :password_changed_at
17
+ t.boolean :locked, null: false, default: false
18
+ t.datetime :locked_at
14
19
 
15
20
  t.timestamps
16
21
  end
@@ -2,13 +2,17 @@ class CreateSecurialSessions < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :securial_sessions, id: :string do |t|
4
4
  t.references :user, null: false, type: :string, foreign_key: { to_table: :securial_users }
5
+
5
6
  t.string :ip_address, null: false
6
7
  t.string :user_agent, null: false
8
+
7
9
  t.string :refresh_token, null: false
8
10
  t.integer :refresh_count, default: 0
9
11
  t.datetime :last_refreshed_at
10
12
  t.datetime :refresh_token_expires_at
13
+
11
14
  t.boolean :revoked, default: false, null: false
15
+
12
16
  t.timestamps
13
17
  end
14
18
  end
@@ -60,7 +60,7 @@ Securial.configure do |config|
60
60
  ## Set the password reset email subject
61
61
  # This is the subject line that will be used for the password reset
62
62
  # email. The default is "Password Reset Instructions".
63
- config.password_reset_email_subject = "Password Reset Instructions"
63
+ config.mailer_forgot_password_subject = "Password Reset Instructions"
64
64
 
65
65
  ## Set the minimum password length
66
66
  # This is the minimum length that a password must have
@@ -29,6 +29,7 @@ module Securial
29
29
 
30
30
  def destroy
31
31
  @<%= singular_table_name %>.destroy
32
+ head :no_content
32
33
  end
33
34
 
34
35
  private
@@ -2,7 +2,7 @@ Securial::Engine.routes.draw do
2
2
  defaults format: :json do
3
3
  get "/status", to: "status#show", as: :status
4
4
 
5
- namespace Securial.admin_namespace do
5
+ namespace Securial.protected_namespace do
6
6
  # Placeholder for admin-specific routes. Add routes here as needed.
7
7
  # For example:
8
8
  # resources :users, only: [:index, :show]