rails-auth-eassy 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +206 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/rails/auth/application.css +15 -0
- data/app/controllers/concerns/rails/auth/authenticatable_controller.rb +114 -0
- data/app/controllers/rails/auth/application_controller.rb +9 -0
- data/app/controllers/rails/auth/confirmations_controller.rb +18 -0
- data/app/controllers/rails/auth/impersonations_controller.rb +46 -0
- data/app/controllers/rails/auth/mfa_controller.rb +26 -0
- data/app/controllers/rails/auth/otp_verifications_controller.rb +25 -0
- data/app/controllers/rails/auth/password_resets_controller.rb +51 -0
- data/app/controllers/rails/auth/profiles_controller.rb +24 -0
- data/app/controllers/rails/auth/registrations_controller.rb +27 -0
- data/app/controllers/rails/auth/security_controller.rb +27 -0
- data/app/controllers/rails/auth/sessions_controller.rb +69 -0
- data/app/controllers/rails/auth/unlocks_controller.rb +17 -0
- data/app/helpers/rails/auth/application_helper.rb +6 -0
- data/app/jobs/rails/auth/application_job.rb +6 -0
- data/app/mailers/rails/auth/application_mailer.rb +8 -0
- data/app/mailers/rails/auth/user_mailer.rb +20 -0
- data/app/models/concerns/rails/auth/authenticatable.rb +107 -0
- data/app/models/concerns/rails/auth/sessionable.rb +30 -0
- data/app/models/rails/auth/application_record.rb +7 -0
- data/app/models/rails/auth/current.rb +7 -0
- data/app/models/rails/auth/security_event.rb +25 -0
- data/app/views/layouts/rails/auth/application.html.erb +29 -0
- data/app/views/rails/auth/mfa/show.html.erb +24 -0
- data/app/views/rails/auth/otp_verifications/new.html.erb +13 -0
- data/app/views/rails/auth/password_resets/edit.html.erb +28 -0
- data/app/views/rails/auth/password_resets/new.html.erb +14 -0
- data/app/views/rails/auth/profiles/edit.html.erb +44 -0
- data/app/views/rails/auth/registrations/new.html.erb +40 -0
- data/app/views/rails/auth/security/sessions.html.erb +92 -0
- data/app/views/rails/auth/sessions/new.html.erb +20 -0
- data/app/views/rails/auth/user_mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/rails/auth/user_mailer/password_reset.html.erb +8 -0
- data/app/views/rails/auth/user_mailer/password_reset.text.erb +8 -0
- data/app/views/rails/auth/user_mailer/unlock_instructions.html.erb +7 -0
- data/config/routes.rb +20 -0
- data/lib/generators/rails_auth/install/install_generator.rb +21 -0
- data/lib/generators/rails_auth/install/templates/rails_auth.rb +7 -0
- data/lib/generators/rails_auth/model/model_generator.rb +27 -0
- data/lib/generators/rails_auth/model/templates/create_rails_auth_tables.rb +60 -0
- data/lib/generators/rails_auth/model/templates/session.rb +3 -0
- data/lib/generators/rails_auth/model/templates/user.rb +3 -0
- data/lib/generators/rails_auth/views/views_generator.rb +13 -0
- data/lib/rails/auth/engine.rb +7 -0
- data/lib/rails/auth/version.rb +5 -0
- data/lib/rails/auth.rb +49 -0
- data/lib/tasks/rails/auth_tasks.rake +4 -0
- metadata +177 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 022e8a17beff8064ba4e48a9e57291c6380ef6240d753f2aac9b877619ad4525
|
|
4
|
+
data.tar.gz: f9c078225e321580caaa9d47bddd3905fbd5e6af673f1fd038430189100841e9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 908356f0e6c4d3bb58dfca0f2af495b3837435a6130158f463cb235e682c780ce82183a2f7d18563677d9d94b179b18580c62599849c303e0e486741503c6e12
|
|
7
|
+
data.tar.gz: 8e42c802b4fa14ad066f7e2258ab9ed4bbaa0ce3f454b13708c33e6ae04e0dfd64a8feff0a71d437734cdd2f8b6f039ea30451bf2b161b7bf34c72d8133cbe43
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2026 Shiboshree Roy, Gemini CLI
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# 🛡️ Rails::Auth
|
|
2
|
+
|
|
3
|
+
**Rails::Auth** is a high-performance, security-first authentication engine for Ruby on Rails. Designed as a modern, transparent alternative to Devise, it empowers users with deep visibility and control over their account security through database-backed sessions and enterprise-grade protection.
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/rb/rails-auth)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://rubyonrails.org)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📖 Table of Contents
|
|
12
|
+
- [🚀 Key Features](#-key-features)
|
|
13
|
+
- [📦 Installation](#-installation)
|
|
14
|
+
- [🛠️ Getting Started](#-getting-started)
|
|
15
|
+
- [📖 Usage](#-usage)
|
|
16
|
+
- [Controller Helpers](#controller-helpers)
|
|
17
|
+
- [Role-Based Access Control (RBAC)](#role-based-access-control-rbac)
|
|
18
|
+
- [Avatar Support](#avatar-support)
|
|
19
|
+
- [🛡️ Security Dashboard](#-security-dashboard)
|
|
20
|
+
- [⚙️ Configuration](#-configuration)
|
|
21
|
+
- [🎨 Customization](#-customization)
|
|
22
|
+
- [👥 Authors & Maintainers](#-authors--maintainers)
|
|
23
|
+
- [📄 License](#-license)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Key Features
|
|
28
|
+
|
|
29
|
+
### 🔐 Enterprise Security
|
|
30
|
+
- **JWT Authentication**: Built-in support for stateless API authentication via `Authorization: Bearer <token>`.
|
|
31
|
+
- **Security Audit Logs**: Comprehensive tracking of sensitive actions (login failures, MFA toggles, password changes).
|
|
32
|
+
- **Admin Impersonation**: Securely log in as any user for customer support while tracking the original admin's actions.
|
|
33
|
+
- **Two-Factor Authentication (MFA)**: Modern TOTP support (Google Authenticator, Authy) with automated QR codes.
|
|
34
|
+
- **Account Locking (Lockable)**: Protection against brute-force attacks with email-based unlocking.
|
|
35
|
+
- **Email Confirmation (Confirmable)**: Ensure every user is verified before they can access your app.
|
|
36
|
+
- **Role-Based Access Control (RBAC)**: Built-in `user`, `moderator`, and `admin` roles with clean authorization helpers.
|
|
37
|
+
|
|
38
|
+
### 📱 Advanced Session Management
|
|
39
|
+
- **Database-Backed Sessions**: Every device login is tracked, allowing for complete transparency.
|
|
40
|
+
- **Remote Device Revocation**: Log out of lost or stolen devices remotely from the dashboard.
|
|
41
|
+
- **Global Sign-Out**: Instant "Sign out of all devices" for emergency account security.
|
|
42
|
+
- **Device Intelligence**: Automatically identifies **Browser**, **OS**, and **IP Address** for every session.
|
|
43
|
+
|
|
44
|
+
### 🛠️ Developer Experience
|
|
45
|
+
- **Active Storage Ready**: Seamlessly integrated avatar/profile picture support.
|
|
46
|
+
- **Model Independent**: Easily attach to `User`, `Admin`, `Member`, or any other model.
|
|
47
|
+
- **Minimalistic Core**: Built on top of Rails' native `has_secure_password`.
|
|
48
|
+
- **Generator Driven**: Scaffold everything you need in seconds.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📦 Installation
|
|
53
|
+
|
|
54
|
+
Add this line to your application's Gemfile:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
gem "rails-auth"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then execute:
|
|
61
|
+
```bash
|
|
62
|
+
$ bundle install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🛠️ Getting Started
|
|
68
|
+
|
|
69
|
+
### 1. Run the Installer
|
|
70
|
+
Scaffold the configuration and engine routes:
|
|
71
|
+
```bash
|
|
72
|
+
$ rails g rails_auth:install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Generate Your Authentication Model
|
|
76
|
+
Create or update your user model (e.g., `User`):
|
|
77
|
+
```bash
|
|
78
|
+
$ rails g rails_auth:model User
|
|
79
|
+
```
|
|
80
|
+
This will generate:
|
|
81
|
+
- The `User` & `Session` models.
|
|
82
|
+
- Migrations for MFA, Lockable, Confirmable, and RBAC.
|
|
83
|
+
|
|
84
|
+
### 3. Run Migrations
|
|
85
|
+
```bash
|
|
86
|
+
$ rails db:migrate
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 📖 Usage
|
|
92
|
+
|
|
93
|
+
### Base Controller Setup
|
|
94
|
+
Include the authentication concern in your `ApplicationController`:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class ApplicationController < ActionController::Base
|
|
98
|
+
include Rails::Auth::AuthenticatableController
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Controller Helpers
|
|
103
|
+
| Helper | Description |
|
|
104
|
+
| :--- | :--- |
|
|
105
|
+
| `authenticate_user!` | Before action to ensure the user is logged in. |
|
|
106
|
+
| `current_user` | Returns the currently authenticated user. |
|
|
107
|
+
| `current_session` | Returns the database record for the current session. |
|
|
108
|
+
| `user_signed_in?` | Check if a user is currently authenticated. |
|
|
109
|
+
| `require_admin!` | Authorization helper to restrict access to admins. |
|
|
110
|
+
| `authorize_role!(*roles)` | Restrict access to specific roles (e.g., `:moderator`, `:admin`). |
|
|
111
|
+
|
|
112
|
+
### Role-Based Access Control (RBAC)
|
|
113
|
+
```ruby
|
|
114
|
+
class Admin::SettingsController < ApplicationController
|
|
115
|
+
before_action :require_admin!
|
|
116
|
+
|
|
117
|
+
def update
|
|
118
|
+
# Only admins can reach here
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Avatar Support
|
|
124
|
+
Rails::Auth uses Active Storage for avatars. Once Active Storage is installed in your host app:
|
|
125
|
+
```erb
|
|
126
|
+
<% if current_user.avatar.attached? %>
|
|
127
|
+
<%= image_tag current_user.avatar.variant(resize_to_limit: [50, 50]) %>
|
|
128
|
+
<% end %>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### JWT API Authentication
|
|
132
|
+
Rails::Auth automatically supports JWTs for API endpoints.
|
|
133
|
+
1. Send a `POST` to `/auth/session.json` with email/password.
|
|
134
|
+
2. Receive a token: `{ "token": "eyJhb..." }`.
|
|
135
|
+
3. Use it in subsequent requests: `Authorization: Bearer eyJhb...`
|
|
136
|
+
|
|
137
|
+
### Admin Impersonation
|
|
138
|
+
Admins can impersonate users for support purposes:
|
|
139
|
+
```ruby
|
|
140
|
+
# Start Impersonation (Controller action)
|
|
141
|
+
sign_in(user, impersonated_by: current_user)
|
|
142
|
+
|
|
143
|
+
# Stop Impersonation
|
|
144
|
+
rails_auth.stop_impersonations_path, method: :delete
|
|
145
|
+
```
|
|
146
|
+
*Note: Impersonation events are securely logged in the `SecurityEvent` table.*
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🛡️ Security Dashboard
|
|
151
|
+
|
|
152
|
+
Users can manage their security settings, view active sessions, and enable MFA at `/auth/security/sessions`.
|
|
153
|
+
|
|
154
|
+
### Linking to the Dashboard
|
|
155
|
+
```erb
|
|
156
|
+
<%= link_to "Security Settings", rails_auth.security_sessions_path %>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## ⚙️ Configuration
|
|
162
|
+
|
|
163
|
+
Customize the gem behavior in `config/initializers/rails_auth.rb`:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
Rails::Auth.setup do |config|
|
|
167
|
+
config.user_class_name = "User"
|
|
168
|
+
config.session_class_name = "Session"
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 🎨 Customization
|
|
175
|
+
|
|
176
|
+
### Views
|
|
177
|
+
Override the default views by copying them to your application:
|
|
178
|
+
```bash
|
|
179
|
+
$ rails g rails_auth:views
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Controllers
|
|
183
|
+
You can inherit from `Rails::Auth::ApplicationController` or include the `AuthenticatableController` in your own controllers for deep integration.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🧪 Testing
|
|
188
|
+
|
|
189
|
+
Rails::Auth is tested against modern Rails versions. Run the suite:
|
|
190
|
+
```bash
|
|
191
|
+
$ bundle exec rake test
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 👥 Authors & Maintainers
|
|
197
|
+
|
|
198
|
+
- **Shiboshree Roy** - *Lead Developer*
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 📄 License
|
|
203
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
Built with ❤️ for the Rails Community by **Shiboshree Roy**.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
module AuthenticatableController
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before_action :authenticate_user!
|
|
8
|
+
helper_method :current_user, :current_session, :user_signed_in?, :impersonating?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def authenticate_user!
|
|
14
|
+
redirect_to rails_auth.new_session_path unless user_signed_in?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def current_user
|
|
18
|
+
Rails::Auth::Current.user ||= (authenticate_by_session_token || authenticate_by_jwt)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def current_session
|
|
22
|
+
Rails::Auth::Current.session
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def true_user
|
|
26
|
+
return nil unless current_session&.impersonated_by_id
|
|
27
|
+
Rails::Auth.user_class.find(current_session.impersonated_by_id)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def impersonating?
|
|
31
|
+
true_user.present?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def user_signed_in?
|
|
35
|
+
current_user.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def require_admin!
|
|
39
|
+
authorize_role!(:admin)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def authorize_role!(*roles)
|
|
43
|
+
unless user_signed_in? && roles.any? { |role| current_user.send("#{role}?") }
|
|
44
|
+
if request.format.json?
|
|
45
|
+
render json: { error: "Unauthorized" }, status: :forbidden
|
|
46
|
+
else
|
|
47
|
+
redirect_to main_app.root_path, alert: "You are not authorized to access this page."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sign_in(user, impersonated_by: nil)
|
|
53
|
+
session_record = user.sessions.create!(
|
|
54
|
+
ip_address: request.remote_ip,
|
|
55
|
+
user_agent: request.user_agent,
|
|
56
|
+
impersonated_by_id: impersonated_by&.id,
|
|
57
|
+
last_active_at: Time.current
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
unless request.format.json?
|
|
61
|
+
cookies.signed.permanent[:session_token] = {
|
|
62
|
+
value: session_record.token,
|
|
63
|
+
httponly: true,
|
|
64
|
+
secure: Rails.env.production?
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Rails::Auth::Current.user = user
|
|
69
|
+
Rails::Auth::Current.session = session_record
|
|
70
|
+
|
|
71
|
+
event = impersonated_by ? :impersonation_started : :login_success
|
|
72
|
+
user.log_security_event!(event, request, { impersonated_by_id: impersonated_by&.id })
|
|
73
|
+
|
|
74
|
+
session_record
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def sign_out
|
|
78
|
+
current_user&.log_security_event!(:logout, request)
|
|
79
|
+
current_session&.destroy
|
|
80
|
+
cookies.delete(:session_token)
|
|
81
|
+
Rails::Auth::Current.user = nil
|
|
82
|
+
Rails::Auth::Current.session = nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def authenticate_by_session_token
|
|
86
|
+
if token = cookies.signed[:session_token]
|
|
87
|
+
session_record = Rails::Auth.session_class.find_by(token: token)
|
|
88
|
+
if session_record
|
|
89
|
+
session_record.update(last_active_at: Time.current, ip_address: request.remote_ip)
|
|
90
|
+
Rails::Auth::Current.session = session_record
|
|
91
|
+
session_record.user
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def authenticate_by_jwt
|
|
97
|
+
header = request.headers["Authorization"]
|
|
98
|
+
return nil unless header.present?
|
|
99
|
+
|
|
100
|
+
token = header.split(" ").last
|
|
101
|
+
decoded = Rails::Auth.decode_jwt(token)
|
|
102
|
+
return nil unless decoded
|
|
103
|
+
|
|
104
|
+
user = Rails::Auth.user_class.find_by(id: decoded[:user_id])
|
|
105
|
+
if user
|
|
106
|
+
# Optional: We could also track JWT sessions in the database if we wanted revocation,
|
|
107
|
+
# but usually JWTs are stateless. For "Ultimate" we might want to track them.
|
|
108
|
+
# Let's keep it simple for now or use a JTI claim.
|
|
109
|
+
user
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class ConfirmationsController < ApplicationController
|
|
4
|
+
skip_before_action :authenticate_user!
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
user = Rails::Auth.user_class.find_by(confirmation_token: params[:confirmation_token])
|
|
8
|
+
if user
|
|
9
|
+
user.confirm!
|
|
10
|
+
sign_in(user)
|
|
11
|
+
redirect_to main_app.root_path, notice: "Your account has been confirmed."
|
|
12
|
+
else
|
|
13
|
+
redirect_to new_session_path, alert: "Invalid confirmation token."
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class ImpersonationsController < ApplicationController
|
|
4
|
+
before_action :require_admin!
|
|
5
|
+
skip_before_action :require_admin!, only: [ :destroy ]
|
|
6
|
+
|
|
7
|
+
def create
|
|
8
|
+
user = Rails::Auth.user_class.find(params[:user_id])
|
|
9
|
+
|
|
10
|
+
if user == current_user
|
|
11
|
+
redirect_to main_app.root_path, alert: "You cannot impersonate yourself."
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Store the current admin session so we can go back
|
|
16
|
+
admin_user = current_user
|
|
17
|
+
|
|
18
|
+
# Sign out current session (admin)
|
|
19
|
+
sign_out
|
|
20
|
+
|
|
21
|
+
# Sign in as the target user, but mark it as impersonated
|
|
22
|
+
sign_in(user, impersonated_by: admin_user)
|
|
23
|
+
|
|
24
|
+
redirect_to main_app.root_path, notice: "You are now impersonating #{user.email}."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy
|
|
28
|
+
unless impersonating?
|
|
29
|
+
redirect_to main_app.root_path, alert: "You are not impersonating anyone."
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
admin_user = true_user
|
|
34
|
+
|
|
35
|
+
# Sign out of the impersonated session
|
|
36
|
+
current_user.log_security_event!(:impersonation_stopped, request, { impersonated_by_id: admin_user.id })
|
|
37
|
+
sign_out
|
|
38
|
+
|
|
39
|
+
# Sign back in as the admin
|
|
40
|
+
sign_in(admin_user)
|
|
41
|
+
|
|
42
|
+
redirect_to main_app.root_path, notice: "Impersonation stopped. Welcome back, #{admin_user.email}."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class MfaController < ApplicationController
|
|
4
|
+
def show
|
|
5
|
+
current_user.generate_otp_secret! unless current_user.otp_secret
|
|
6
|
+
@qrcode = RQRCode::QRCode.new(current_user.otp_provisioning_uri)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
if current_user.verify_otp(params[:otp_code])
|
|
11
|
+
current_user.update(otp_enabled: true)
|
|
12
|
+
redirect_to security_sessions_path, notice: "Two-factor authentication enabled."
|
|
13
|
+
else
|
|
14
|
+
flash.now[:alert] = "Invalid OTP code. Please try again."
|
|
15
|
+
@qrcode = RQRCode::QRCode.new(current_user.otp_provisioning_uri)
|
|
16
|
+
render :show, status: :unprocessable_entity
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def destroy
|
|
21
|
+
current_user.update(otp_enabled: false, otp_secret: nil)
|
|
22
|
+
redirect_to security_sessions_path, notice: "Two-factor authentication disabled."
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class OtpVerificationsController < ApplicationController
|
|
4
|
+
skip_before_action :authenticate_user!
|
|
5
|
+
|
|
6
|
+
def new
|
|
7
|
+
unless session[:otp_user_id]
|
|
8
|
+
redirect_to new_session_path, alert: "Session expired."
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
user = Rails::Auth.user_class.find(session[:otp_user_id])
|
|
14
|
+
if user.verify_otp(params[:otp_code])
|
|
15
|
+
session.delete(:otp_user_id)
|
|
16
|
+
sign_in(user)
|
|
17
|
+
redirect_to main_app.root_path, notice: "Signed in successfully."
|
|
18
|
+
else
|
|
19
|
+
flash.now[:alert] = "Invalid OTP code."
|
|
20
|
+
render :new, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class PasswordResetsController < ApplicationController
|
|
4
|
+
skip_before_action :authenticate_user!
|
|
5
|
+
before_action :set_user, only: [ :edit, :update ]
|
|
6
|
+
|
|
7
|
+
def new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
@user = Rails::Auth.user_class.find_by(email: params[:email])
|
|
12
|
+
if @user
|
|
13
|
+
@user.generate_password_reset_token!
|
|
14
|
+
UserMailer.password_reset(@user).deliver_now
|
|
15
|
+
end
|
|
16
|
+
redirect_to rails_auth.new_session_path, notice: "If an account with that email exists, you will receive a password reset link shortly."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def edit
|
|
20
|
+
unless @user&.password_reset_token_valid?
|
|
21
|
+
redirect_to rails_auth.new_password_reset_path, alert: "Password reset link has expired or is invalid."
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def update
|
|
26
|
+
if @user.password_reset_token_valid?
|
|
27
|
+
if @user.update(password_params)
|
|
28
|
+
@user.clear_password_reset_token!
|
|
29
|
+
@user.sessions.destroy_all # Security: Sign out of all sessions after password change
|
|
30
|
+
sign_in(@user)
|
|
31
|
+
redirect_to main_app.root_path, notice: "Password has been reset successfully and all other sessions have been signed out."
|
|
32
|
+
else
|
|
33
|
+
render :edit, status: :unprocessable_entity
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
redirect_to rails_auth.new_password_reset_path, alert: "Password reset link has expired."
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_user
|
|
43
|
+
@user = Rails::Auth.user_class.find_by!(reset_token: params[:id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def password_params
|
|
47
|
+
params.require(:user).permit(:password, :password_confirmation)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class ProfilesController < ApplicationController
|
|
4
|
+
def edit
|
|
5
|
+
@user = current_user
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def update
|
|
9
|
+
@user = current_user
|
|
10
|
+
if @user.update(profile_params)
|
|
11
|
+
redirect_to security_sessions_path, notice: "Profile updated successfully."
|
|
12
|
+
else
|
|
13
|
+
render :edit, status: :unprocessable_entity
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def profile_params
|
|
20
|
+
params.require(:user).permit(:email, :password, :password_confirmation, :avatar)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class RegistrationsController < ApplicationController
|
|
4
|
+
skip_before_action :authenticate_user!, only: [ :new, :create ]
|
|
5
|
+
|
|
6
|
+
def new
|
|
7
|
+
@user = Rails::Auth.user_class.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
@user = Rails::Auth.user_class.new(user_params)
|
|
12
|
+
if @user.save
|
|
13
|
+
@user.send_confirmation_instructions
|
|
14
|
+
redirect_to rails_auth.new_session_path, notice: "A confirmation link has been sent to your email address. Please follow the link to activate your account."
|
|
15
|
+
else
|
|
16
|
+
render :new, status: :unprocessable_entity
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def user_params
|
|
23
|
+
params.require(:user).permit(:email, :password, :password_confirmation, :avatar)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Auth
|
|
3
|
+
class SecurityController < ApplicationController
|
|
4
|
+
def sessions
|
|
5
|
+
@sessions = current_user.sessions.order(last_active_at: :desc)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def revoke_session
|
|
9
|
+
session_to_revoke = current_user.sessions.find(params[:id])
|
|
10
|
+
session_to_revoke.destroy
|
|
11
|
+
|
|
12
|
+
if session_to_revoke == current_session
|
|
13
|
+
sign_out
|
|
14
|
+
redirect_to rails_auth.new_session_path, notice: "Your current session was revoked. Please sign in again."
|
|
15
|
+
else
|
|
16
|
+
redirect_to rails_auth.security_sessions_path, notice: "Session revoked successfully."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def revoke_all_sessions
|
|
21
|
+
current_user.sessions.destroy_all
|
|
22
|
+
sign_out
|
|
23
|
+
redirect_to rails_auth.new_session_path, notice: "All sessions have been revoked. Please sign in again."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|