rails_simple_auth 1.0.2 → 1.0.4
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/CHANGELOG.md +61 -0
- data/README.md +304 -41
- data/app/controllers/rails_simple_auth/confirmations_controller.rb +9 -3
- data/app/controllers/rails_simple_auth/sessions_controller.rb +7 -1
- data/app/mailers/rails_simple_auth/auth_mailer.rb +3 -0
- data/lib/rails_simple_auth/engine.rb +8 -0
- data/lib/rails_simple_auth/model.rb +49 -0
- data/lib/rails_simple_auth/models/concerns/confirmable.rb +5 -1
- data/lib/rails_simple_auth/models/concerns/temporary_user.rb +12 -3
- data/lib/rails_simple_auth/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd84335df56b3bc4fbb386f841bfb66bc74eadae1dc3864873bcda904a946cd2
|
|
4
|
+
data.tar.gz: 00d31290be4bdccf36c5f9a326896e9506936f6b122f9298f53fabc0aadcd8a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c0cf144576a950a1aff4a1958197b9ef93a3edc405bf1de36339f3effed3c09087158b821f6e2c7617af9b9ae9b3ef184783b08c9428da0df65d79ac5a2ba3e
|
|
7
|
+
data.tar.gz: 0cc8bd3f8ce5094910f25daf12496f1efe6f447d3ee22f3744ad47d7ff4b65ad46209ecd2004fe9024f2fcad85c38efb6478261bd408251f728aad1c2fc89824
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.4] - 2025-01-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`authenticates_with` DSL** - Cleaner model setup inspired by Devise syntax
|
|
15
|
+
```ruby
|
|
16
|
+
# Before
|
|
17
|
+
include RailsSimpleAuth::Models::Concerns::Authenticatable
|
|
18
|
+
include RailsSimpleAuth::Models::Concerns::Confirmable
|
|
19
|
+
|
|
20
|
+
# After
|
|
21
|
+
authenticates_with :confirmable, :magic_linkable, :oauth, :temporary
|
|
22
|
+
```
|
|
23
|
+
- **Devise comparison article** - Comprehensive comparison at `docs/devise-comparison.md`
|
|
24
|
+
- **Admin Users documentation** - Guide for implementing admin functionality
|
|
25
|
+
- **Rate Limiting documentation** - Default limits and customization guide
|
|
26
|
+
- **Session Management documentation** - Expiration, querying, and cleanup
|
|
27
|
+
|
|
28
|
+
## [1.0.3] - 2025-01-19
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Temporary Users Support** - Allow users to try the app without signing up, then convert to permanent accounts
|
|
33
|
+
- `TemporaryUser` concern with `temporary?` and `permanent?` methods
|
|
34
|
+
- Scopes: `temporary`, `permanent`, `temporary_expired`
|
|
35
|
+
- `convert_to_permanent!(email:, password:)` method for account conversion
|
|
36
|
+
- Generator: `rails g rails_simple_auth:temporary_users` for migration
|
|
37
|
+
- Configuration options: `temporary_users_enabled`, `temporary_user_cleanup_days`
|
|
38
|
+
- Automatic cleanup of expired temporary users via `User.cleanup_expired_temporary!`
|
|
39
|
+
- Session invalidation on account conversion
|
|
40
|
+
- Automatic destruction of temporary user when signing in with different account
|
|
41
|
+
- **Email Reconfirmation Flow** - Support for users changing their email address
|
|
42
|
+
- `unconfirmed_email` column support for pending email changes
|
|
43
|
+
- `reconfirming?` and `unconfirmed_or_reconfirming?` helper methods
|
|
44
|
+
- `confirmable_email` helper returns the email needing confirmation
|
|
45
|
+
- Confirmation emails sent to new email address during reconfirmation
|
|
46
|
+
- Comprehensive test suite (126 tests, 247 assertions)
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `confirm!` now uses `has_attribute?(:temporary)` instead of `respond_to?(:temporary?)` to prevent errors when `Authenticatable` is included without the temporary database column
|
|
51
|
+
- `confirm!` properly handles race conditions with `RecordNotUnique` rescue during reconfirmation
|
|
52
|
+
- `convert_to_permanent!` validates password presence to prevent users being left without credentials
|
|
53
|
+
- `convert_to_permanent!` reloads after transaction to check actual database state (not stale in-memory state)
|
|
54
|
+
- `convert_to_permanent!` resets `confirmed_at` to require email verification for new address
|
|
55
|
+
- `cleanup_expired_temporary!` now returns accurate count (only increments when destroy succeeds)
|
|
56
|
+
- `magic_link_login` checks `confirm!` return value and shows error if confirmation fails
|
|
57
|
+
- `confirmations_controller#show` checks `confirm!` return value and displays appropriate error message
|
|
58
|
+
- `destroy_temporary_user_session` skips destruction when user is re-authenticating as themselves
|
|
59
|
+
- `AuthMailer#confirmation` sends to `confirmable_email` for correct recipient during reconfirmation
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Confirmation token purpose changed from `:email_confirmation` to `:confirm_email` (**Breaking**: existing confirmation tokens will be invalidated)
|
|
64
|
+
|
|
65
|
+
## [1.0.2] - 2025-01-18
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Session invalidation and batch cleanup for temporary users
|
|
70
|
+
|
|
10
71
|
## [1.0.0] - 2025-01-18
|
|
11
72
|
|
|
12
73
|
### Added
|
data/README.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Simple, secure authentication for Rails 8+ applications. Built on Rails primitives with no magic.
|
|
4
4
|
|
|
5
|
+
**Coming from Devise?** Read our [detailed comparison](docs/devise-comparison.md).
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
- **Email/Password authentication**
|
|
8
|
-
- **Magic link**
|
|
9
|
-
- **Email confirmation**
|
|
10
|
-
- **Password reset**
|
|
11
|
-
- **OAuth support**
|
|
12
|
-
- **Temporary users**
|
|
13
|
-
- **Rate limiting** built-in
|
|
14
|
-
- **Session tracking**
|
|
15
|
-
- **Customizable styling**
|
|
16
|
-
- **
|
|
9
|
+
- [**Email/Password authentication**](#installation) - secure session-based auth
|
|
10
|
+
- [**Magic link authentication**](#routes) - passwordless sign-in via email
|
|
11
|
+
- [**Email confirmation**](#routes) - verify user email addresses
|
|
12
|
+
- [**Password reset**](#routes) - secure password recovery flow
|
|
13
|
+
- [**OAuth support**](#oauth-setup) - Google, GitHub, and more
|
|
14
|
+
- [**Temporary users**](#temporary-users-guest-accounts) - guest accounts that convert to permanent
|
|
15
|
+
- [**Rate limiting**](#rate-limiting) - built-in protection on all endpoints
|
|
16
|
+
- [**Session tracking**](#session-management) - IP and user agent logging
|
|
17
|
+
- [**Customizable styling**](#styling) - CSS variables for easy theming
|
|
18
|
+
- [**Custom mailers**](#mailer) - use your own branded email templates
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -36,20 +38,31 @@ rails generate rails_simple_auth:install
|
|
|
36
38
|
rails db:migrate
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
Add
|
|
41
|
+
Add authentication to your User model:
|
|
40
42
|
|
|
41
43
|
```ruby
|
|
42
44
|
class User < ApplicationRecord
|
|
43
|
-
|
|
44
|
-
include RailsSimpleAuth::Models::Concerns::Confirmable # optional
|
|
45
|
-
include RailsSimpleAuth::Models::Concerns::MagicLinkable # optional
|
|
46
|
-
include RailsSimpleAuth::Models::Concerns::OAuthConnectable # optional
|
|
45
|
+
authenticates_with :confirmable, :magic_linkable, :oauth, :temporary
|
|
47
46
|
|
|
48
47
|
# Your custom fields and validations
|
|
49
48
|
validates :company_name, presence: true
|
|
50
49
|
end
|
|
51
50
|
```
|
|
52
51
|
|
|
52
|
+
Available modules:
|
|
53
|
+
- `:confirmable` - Email confirmation for new accounts
|
|
54
|
+
- `:magic_linkable` - Passwordless sign-in via email
|
|
55
|
+
- `:oauth` - OAuth provider support (Google, GitHub, etc.)
|
|
56
|
+
- `:temporary` - Guest accounts that convert to permanent
|
|
57
|
+
|
|
58
|
+
For basic email/password auth only:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
class User < ApplicationRecord
|
|
62
|
+
authenticates_with
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
53
66
|
Protect your routes:
|
|
54
67
|
|
|
55
68
|
```ruby
|
|
@@ -204,9 +217,20 @@ class User < ApplicationRecord
|
|
|
204
217
|
end
|
|
205
218
|
```
|
|
206
219
|
|
|
207
|
-
## Temporary Users (Guest
|
|
220
|
+
## Temporary Users (Guest Accounts)
|
|
208
221
|
|
|
209
|
-
|
|
222
|
+
Temporary users allow visitors to try your app without creating an account. They get a real user record with full functionality, then can convert to a permanent account later by providing email and password.
|
|
223
|
+
|
|
224
|
+
### Why Use Temporary Users?
|
|
225
|
+
|
|
226
|
+
**Reduce friction**: Let users experience your app's value before asking them to sign up. This is especially useful for:
|
|
227
|
+
|
|
228
|
+
- **E-commerce**: Users can add items to cart, save preferences, then checkout as guest or create account
|
|
229
|
+
- **Productivity apps**: Users can create documents, try features, then save their work by signing up
|
|
230
|
+
- **Games**: Users can start playing immediately, then create account to save progress
|
|
231
|
+
- **Collaboration tools**: Users can join a shared workspace via link, then register to keep access
|
|
232
|
+
|
|
233
|
+
**Preserve data**: Unlike anonymous sessions, temporary users have real database records. When they convert, all their data (orders, documents, settings) stays linked to their account.
|
|
210
234
|
|
|
211
235
|
### Setup
|
|
212
236
|
|
|
@@ -217,12 +241,11 @@ rails generate rails_simple_auth:temporary_users
|
|
|
217
241
|
rails db:migrate
|
|
218
242
|
```
|
|
219
243
|
|
|
220
|
-
2.
|
|
244
|
+
2. Add the `:temporary` module to your User model:
|
|
221
245
|
|
|
222
246
|
```ruby
|
|
223
247
|
class User < ApplicationRecord
|
|
224
|
-
|
|
225
|
-
include RailsSimpleAuth::Models::Concerns::TemporaryUser # Add this
|
|
248
|
+
authenticates_with :confirmable, :temporary
|
|
226
249
|
end
|
|
227
250
|
```
|
|
228
251
|
|
|
@@ -237,42 +260,110 @@ end
|
|
|
237
260
|
|
|
238
261
|
### Creating Temporary Users
|
|
239
262
|
|
|
263
|
+
Create a temporary user when someone needs to use your app without signing up:
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
# In your controller
|
|
267
|
+
def try_without_account
|
|
268
|
+
user = User.create!(
|
|
269
|
+
email: "temp_#{SecureRandom.hex(8)}@temporary.local",
|
|
270
|
+
password: SecureRandom.hex(32),
|
|
271
|
+
temporary: true
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Sign them in
|
|
275
|
+
create_session_for(user)
|
|
276
|
+
redirect_to dashboard_path
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Or create via an invite link:
|
|
281
|
+
|
|
240
282
|
```ruby
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
283
|
+
def accept_invite
|
|
284
|
+
# Create temporary user to access shared content
|
|
285
|
+
user = User.create!(temporary: true, ...)
|
|
286
|
+
create_session_for(user)
|
|
287
|
+
redirect_to shared_workspace_path(params[:workspace_id])
|
|
288
|
+
end
|
|
247
289
|
```
|
|
248
290
|
|
|
249
291
|
### Converting to Permanent Account
|
|
250
292
|
|
|
293
|
+
When a temporary user is ready to create a real account:
|
|
294
|
+
|
|
251
295
|
```ruby
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
296
|
+
# In your controller
|
|
297
|
+
def convert_account
|
|
298
|
+
if current_user.convert_to_permanent!(
|
|
299
|
+
email: params[:email],
|
|
300
|
+
password: params[:password]
|
|
301
|
+
)
|
|
302
|
+
redirect_to dashboard_path, notice: "Account created! Please check your email to confirm."
|
|
303
|
+
else
|
|
304
|
+
# Validation failed (email taken, password blank, etc.)
|
|
305
|
+
render :convert_form, status: :unprocessable_entity
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The conversion:
|
|
311
|
+
- Updates email and password
|
|
312
|
+
- Sets `temporary: false`
|
|
313
|
+
- Resets `confirmed_at` (requires email confirmation for new address)
|
|
314
|
+
- Invalidates all existing sessions (security measure)
|
|
315
|
+
- Sends confirmation email automatically
|
|
316
|
+
|
|
317
|
+
### What Happens on Sign In?
|
|
318
|
+
|
|
319
|
+
When a temporary user signs in with a different account (or signs up), the temporary user is automatically destroyed:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
Temporary User (browsing) → Signs in with existing account → Temp user deleted
|
|
323
|
+
Temporary User (browsing) → Creates new account → Temp user deleted
|
|
324
|
+
Temporary User (browsing) → Converts their temp account → Keeps same user record
|
|
258
325
|
```
|
|
259
326
|
|
|
260
|
-
|
|
327
|
+
This prevents orphaned temporary records and ensures clean data.
|
|
328
|
+
|
|
329
|
+
### Querying Users
|
|
261
330
|
|
|
262
331
|
```ruby
|
|
263
|
-
User.temporary
|
|
264
|
-
User.permanent
|
|
265
|
-
User.temporary_expired
|
|
266
|
-
|
|
332
|
+
User.temporary # All temporary users
|
|
333
|
+
User.permanent # All permanent users
|
|
334
|
+
User.temporary_expired # Temporary users older than cleanup_days
|
|
335
|
+
|
|
336
|
+
current_user.temporary? # Is this a guest?
|
|
337
|
+
current_user.permanent? # Is this a real account?
|
|
267
338
|
```
|
|
268
339
|
|
|
269
|
-
### Cleanup
|
|
340
|
+
### Cleanup
|
|
270
341
|
|
|
271
|
-
|
|
342
|
+
Temporary users are automatically eligible for cleanup after `temporary_user_cleanup_days`. Run cleanup manually or via scheduled job:
|
|
272
343
|
|
|
273
344
|
```ruby
|
|
274
|
-
#
|
|
275
|
-
User.
|
|
345
|
+
# In a rake task or background job
|
|
346
|
+
User.cleanup_expired_temporary!
|
|
347
|
+
|
|
348
|
+
# With custom retention period
|
|
349
|
+
User.cleanup_expired_temporary!(days: 14)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Add to your scheduler (e.g., `config/recurring.yml` for Solid Queue):
|
|
353
|
+
|
|
354
|
+
```yaml
|
|
355
|
+
cleanup_temporary_users:
|
|
356
|
+
schedule: every day at 3am
|
|
357
|
+
class: CleanupTemporaryUsersJob
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
class CleanupTemporaryUsersJob < ApplicationJob
|
|
362
|
+
def perform
|
|
363
|
+
count = User.cleanup_expired_temporary!
|
|
364
|
+
Rails.logger.info "Cleaned up #{count} expired temporary users"
|
|
365
|
+
end
|
|
366
|
+
end
|
|
276
367
|
```
|
|
277
368
|
|
|
278
369
|
## Controller Customization
|
|
@@ -402,6 +493,178 @@ The gem adds these routes:
|
|
|
402
493
|
| POST | `/request_magic_link` | Send magic link |
|
|
403
494
|
| GET | `/magic_link` | Login via magic link |
|
|
404
495
|
|
|
496
|
+
## Rate Limiting
|
|
497
|
+
|
|
498
|
+
All authentication endpoints are rate limited using Rails 8's `rate_limit` DSL to prevent brute force attacks.
|
|
499
|
+
|
|
500
|
+
### Default Limits
|
|
501
|
+
|
|
502
|
+
| Action | Limit | Period | Scope |
|
|
503
|
+
|--------|-------|--------|-------|
|
|
504
|
+
| Sign in | 5 requests | 15 minutes | per IP |
|
|
505
|
+
| Sign up | 5 requests | 1 hour | per IP |
|
|
506
|
+
| Magic link request | 3 requests | 10 minutes | per email |
|
|
507
|
+
| Password reset | 3 requests | 1 hour | per IP |
|
|
508
|
+
| Email confirmation | 3 requests | 1 hour | per IP |
|
|
509
|
+
|
|
510
|
+
### Customizing Limits
|
|
511
|
+
|
|
512
|
+
```ruby
|
|
513
|
+
RailsSimpleAuth.configure do |config|
|
|
514
|
+
config.rate_limits = {
|
|
515
|
+
sign_in: { limit: 10, period: 30.minutes },
|
|
516
|
+
sign_up: { limit: 3, period: 1.hour },
|
|
517
|
+
magic_link: { limit: 5, period: 15.minutes },
|
|
518
|
+
password_reset: { limit: 5, period: 1.hour },
|
|
519
|
+
confirmation: { limit: 5, period: 1.hour }
|
|
520
|
+
}
|
|
521
|
+
end
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Disabling Rate Limiting
|
|
525
|
+
|
|
526
|
+
To disable rate limiting for a specific action, set it to `nil`:
|
|
527
|
+
|
|
528
|
+
```ruby
|
|
529
|
+
config.rate_limits = {
|
|
530
|
+
sign_in: nil, # No rate limiting on sign in
|
|
531
|
+
sign_up: { limit: 5, period: 1.hour }
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
When rate limited, users see a "Too many requests" error and must wait for the period to expire.
|
|
536
|
+
|
|
537
|
+
## Session Management
|
|
538
|
+
|
|
539
|
+
Sessions track user authentication state with IP address and user agent for security auditing.
|
|
540
|
+
|
|
541
|
+
### What's Tracked
|
|
542
|
+
|
|
543
|
+
Each session stores:
|
|
544
|
+
- **user_id** - The authenticated user
|
|
545
|
+
- **ip_address** - Client IP at sign-in time
|
|
546
|
+
- **user_agent** - Browser/device information
|
|
547
|
+
- **created_at** - When the session was created
|
|
548
|
+
|
|
549
|
+
### Session Expiration
|
|
550
|
+
|
|
551
|
+
Sessions expire after 30 days by default:
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
RailsSimpleAuth.configure do |config|
|
|
555
|
+
config.session_expiry = 30.days # Default
|
|
556
|
+
# config.session_expiry = 7.days # Shorter sessions
|
|
557
|
+
end
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Querying Sessions
|
|
561
|
+
|
|
562
|
+
```ruby
|
|
563
|
+
# All sessions for a user
|
|
564
|
+
current_user.sessions
|
|
565
|
+
|
|
566
|
+
# Recent sessions first
|
|
567
|
+
current_user.sessions.recent
|
|
568
|
+
|
|
569
|
+
# Active sessions (not expired)
|
|
570
|
+
current_user.sessions.active
|
|
571
|
+
|
|
572
|
+
# Expired sessions
|
|
573
|
+
current_user.sessions.expired
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Session Cleanup
|
|
577
|
+
|
|
578
|
+
Expired sessions can be cleaned up manually or via scheduled job:
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
# Clean up all expired sessions
|
|
582
|
+
RailsSimpleAuth::Session.cleanup_expired!
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Add to your scheduler:
|
|
586
|
+
|
|
587
|
+
```ruby
|
|
588
|
+
class CleanupExpiredSessionsJob < ApplicationJob
|
|
589
|
+
def perform
|
|
590
|
+
count = RailsSimpleAuth::Session.cleanup_expired!
|
|
591
|
+
Rails.logger.info "Cleaned up #{count} expired sessions"
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Security Behaviors
|
|
597
|
+
|
|
598
|
+
- **Password change**: All sessions are invalidated when a user changes their password
|
|
599
|
+
- **Account conversion**: All sessions are invalidated when a temporary user converts to permanent
|
|
600
|
+
- **Sign out**: Only the current session is destroyed (other devices stay signed in)
|
|
601
|
+
|
|
602
|
+
## Admin Users
|
|
603
|
+
|
|
604
|
+
RailsSimpleAuth uses a single table with role-based access — the Rails way. No separate admin models or authentication flows needed.
|
|
605
|
+
|
|
606
|
+
### Setup
|
|
607
|
+
|
|
608
|
+
Add an admin column to your users table:
|
|
609
|
+
|
|
610
|
+
```ruby
|
|
611
|
+
# Migration
|
|
612
|
+
add_column :users, :admin, :boolean, default: false
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Add a helper method to your model:
|
|
616
|
+
|
|
617
|
+
```ruby
|
|
618
|
+
class User < ApplicationRecord
|
|
619
|
+
authenticates_with :confirmable
|
|
620
|
+
|
|
621
|
+
def admin?
|
|
622
|
+
admin == true
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### Protecting Admin Routes
|
|
628
|
+
|
|
629
|
+
```ruby
|
|
630
|
+
class AdminController < ApplicationController
|
|
631
|
+
before_action :require_admin
|
|
632
|
+
|
|
633
|
+
private
|
|
634
|
+
|
|
635
|
+
def require_admin
|
|
636
|
+
redirect_to root_path, alert: "Not authorized" unless current_user&.admin?
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Or as a concern
|
|
641
|
+
module AdminAuthentication
|
|
642
|
+
extend ActiveSupport::Concern
|
|
643
|
+
|
|
644
|
+
included do
|
|
645
|
+
before_action :require_admin
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
private
|
|
649
|
+
|
|
650
|
+
def require_admin
|
|
651
|
+
redirect_to root_path, alert: "Not authorized" unless current_user&.admin?
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Creating Admin Users
|
|
657
|
+
|
|
658
|
+
```ruby
|
|
659
|
+
# Console
|
|
660
|
+
User.find_by(email: "admin@example.com").update!(admin: true)
|
|
661
|
+
|
|
662
|
+
# Seeds
|
|
663
|
+
User.create!(email: "admin@example.com", password: "secure123", admin: true)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
For more complex role systems, consider adding a `role` enum or using an authorization gem like [Pundit](https://github.com/varvet/pundit).
|
|
667
|
+
|
|
405
668
|
## Security Features
|
|
406
669
|
|
|
407
670
|
- **BCrypt password hashing** with salts
|
|
@@ -15,9 +15,15 @@ module RailsSimpleAuth
|
|
|
15
15
|
user = user_class.find_signed(params[:token], purpose: :confirm_email)
|
|
16
16
|
|
|
17
17
|
if user
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
confirmed = user.respond_to?(:confirm!) ? user.confirm! : true
|
|
19
|
+
|
|
20
|
+
if confirmed
|
|
21
|
+
run_after_confirmation_callback(user)
|
|
22
|
+
redirect_to resolve_path(:after_confirmation_path), notice: 'Email confirmed! You can now sign in.'
|
|
23
|
+
else
|
|
24
|
+
error_message = user.errors.full_messages.first || 'Could not confirm email.'
|
|
25
|
+
redirect_to new_confirmation_path, alert: error_message
|
|
26
|
+
end
|
|
21
27
|
else
|
|
22
28
|
redirect_to new_confirmation_path, alert: 'Invalid or expired confirmation link.'
|
|
23
29
|
end
|
|
@@ -70,7 +70,13 @@ module RailsSimpleAuth
|
|
|
70
70
|
user = user_class.find_signed(params[:token], purpose: :magic_link)
|
|
71
71
|
|
|
72
72
|
if user
|
|
73
|
-
|
|
73
|
+
# Auto-confirm unconfirmed users via magic link (email ownership verified)
|
|
74
|
+
if user.respond_to?(:confirm!) && user.respond_to?(:unconfirmed?) && user.unconfirmed? && !user.confirm!
|
|
75
|
+
# Confirmation failed (e.g., email already taken during reconfirmation)
|
|
76
|
+
error_message = user.errors.full_messages.first || 'Could not confirm email.'
|
|
77
|
+
redirect_to new_session_path, alert: error_message
|
|
78
|
+
return
|
|
79
|
+
end
|
|
74
80
|
sign_in_and_redirect(user)
|
|
75
81
|
else
|
|
76
82
|
redirect_to new_session_path, alert: 'Invalid or expired magic link.'
|
|
@@ -4,6 +4,9 @@ module RailsSimpleAuth
|
|
|
4
4
|
class AuthMailer < ApplicationMailer
|
|
5
5
|
default from: -> { RailsSimpleAuth.configuration.mailer_sender }
|
|
6
6
|
|
|
7
|
+
# Use the mailers folder for templates instead of auth_mailer
|
|
8
|
+
self.mailer_name = 'rails_simple_auth/mailers'
|
|
9
|
+
|
|
7
10
|
def confirmation(user, token)
|
|
8
11
|
@user = user
|
|
9
12
|
@token = token
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'rails_simple_auth/model'
|
|
4
|
+
|
|
3
5
|
module RailsSimpleAuth
|
|
4
6
|
class Engine < ::Rails::Engine
|
|
5
7
|
isolate_namespace RailsSimpleAuth
|
|
@@ -14,5 +16,11 @@ module RailsSimpleAuth
|
|
|
14
16
|
include RailsSimpleAuth::Controllers::Concerns::SessionManagement
|
|
15
17
|
end
|
|
16
18
|
end
|
|
19
|
+
|
|
20
|
+
initializer 'rails_simple_auth.model' do
|
|
21
|
+
ActiveSupport.on_load(:active_record) do
|
|
22
|
+
include RailsSimpleAuth::Model
|
|
23
|
+
end
|
|
24
|
+
end
|
|
17
25
|
end
|
|
18
26
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsSimpleAuth
|
|
4
|
+
module Model
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
MODULES = {
|
|
8
|
+
confirmable: 'RailsSimpleAuth::Models::Concerns::Confirmable',
|
|
9
|
+
magic_linkable: 'RailsSimpleAuth::Models::Concerns::MagicLinkable',
|
|
10
|
+
oauth: 'RailsSimpleAuth::Models::Concerns::OAuthConnectable',
|
|
11
|
+
temporary: 'RailsSimpleAuth::Models::Concerns::TemporaryUser'
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
class_methods do
|
|
15
|
+
# Configure authentication for this model
|
|
16
|
+
#
|
|
17
|
+
# @example Basic authentication only
|
|
18
|
+
# authenticates_with
|
|
19
|
+
#
|
|
20
|
+
# @example With optional modules
|
|
21
|
+
# authenticates_with :confirmable, :magic_linkable
|
|
22
|
+
#
|
|
23
|
+
# @example Full featured
|
|
24
|
+
# authenticates_with :confirmable, :magic_linkable, :oauth, :temporary
|
|
25
|
+
#
|
|
26
|
+
# Available modules:
|
|
27
|
+
# - :confirmable - Email confirmation for new accounts
|
|
28
|
+
# - :magic_linkable - Passwordless sign-in via email
|
|
29
|
+
# - :oauth - OAuth provider support (Google, GitHub, etc.)
|
|
30
|
+
# - :temporary - Guest accounts that convert to permanent
|
|
31
|
+
#
|
|
32
|
+
def authenticates_with(*modules)
|
|
33
|
+
# Always include base authentication
|
|
34
|
+
include RailsSimpleAuth::Models::Concerns::Authenticatable
|
|
35
|
+
|
|
36
|
+
# Include requested optional modules
|
|
37
|
+
modules.each do |mod|
|
|
38
|
+
mod_name = mod.to_sym
|
|
39
|
+
unless MODULES.key?(mod_name)
|
|
40
|
+
raise ArgumentError, "Unknown authentication module: #{mod.inspect}. " \
|
|
41
|
+
"Available modules: #{MODULES.keys.join(', ')}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
include MODULES[mod_name].constantize
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -44,7 +44,7 @@ module RailsSimpleAuth
|
|
|
44
44
|
# Returns true on success, false on failure (with errors populated)
|
|
45
45
|
def confirm!
|
|
46
46
|
attrs = { confirmed_at: Time.current }
|
|
47
|
-
attrs[:temporary] = false if
|
|
47
|
+
attrs[:temporary] = false if has_attribute?(:temporary)
|
|
48
48
|
|
|
49
49
|
if reconfirming?
|
|
50
50
|
# Email change confirmation - check email uniqueness first
|
|
@@ -62,6 +62,10 @@ module RailsSimpleAuth
|
|
|
62
62
|
# Already confirmed and not reconfirming
|
|
63
63
|
true
|
|
64
64
|
end
|
|
65
|
+
rescue ActiveRecord::RecordNotUnique
|
|
66
|
+
# Race condition: email was taken between check and update
|
|
67
|
+
errors.add(:email, 'is already taken by another user')
|
|
68
|
+
false
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
# Generate email confirmation token using Rails signed_id
|
|
@@ -28,6 +28,12 @@ module RailsSimpleAuth
|
|
|
28
28
|
# Convert a temporary user to a permanent user with email and password
|
|
29
29
|
# Returns self on success, false on failure (with errors populated)
|
|
30
30
|
def convert_to_permanent!(email:, password:)
|
|
31
|
+
# Validate password presence upfront
|
|
32
|
+
if password.blank?
|
|
33
|
+
errors.add(:password, "can't be blank")
|
|
34
|
+
return false
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
# Validate email uniqueness upfront (better UX than failing inside transaction)
|
|
32
38
|
if self.class.where.not(id: id).exists?(email: email)
|
|
33
39
|
errors.add(:email, 'has already been taken')
|
|
@@ -55,7 +61,11 @@ module RailsSimpleAuth
|
|
|
55
61
|
raise ActiveRecord::Rollback unless update(attrs)
|
|
56
62
|
end
|
|
57
63
|
|
|
58
|
-
#
|
|
64
|
+
# Reload to get actual database state after transaction
|
|
65
|
+
# (in-memory attributes may be stale if transaction was rolled back)
|
|
66
|
+
reload
|
|
67
|
+
|
|
68
|
+
# Check if conversion actually succeeded
|
|
59
69
|
return false if errors.any? || temporary?
|
|
60
70
|
|
|
61
71
|
invalidate_all_sessions!
|
|
@@ -75,8 +85,7 @@ module RailsSimpleAuth
|
|
|
75
85
|
def cleanup_expired_temporary!(days: nil, batch_size: 100)
|
|
76
86
|
count = 0
|
|
77
87
|
temporary_expired(days).find_each(batch_size: batch_size) do |user|
|
|
78
|
-
user.destroy
|
|
79
|
-
count += 1
|
|
88
|
+
count += 1 if user.destroy
|
|
80
89
|
end
|
|
81
90
|
Rails.logger.info("[RailsSimpleAuth] Cleaned up #{count} expired temporary users")
|
|
82
91
|
count
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_simple_auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Kuznetsov
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-01-19 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: bcrypt
|
|
@@ -81,6 +82,7 @@ files:
|
|
|
81
82
|
- lib/rails_simple_auth/controllers/concerns/authentication.rb
|
|
82
83
|
- lib/rails_simple_auth/controllers/concerns/session_management.rb
|
|
83
84
|
- lib/rails_simple_auth/engine.rb
|
|
85
|
+
- lib/rails_simple_auth/model.rb
|
|
84
86
|
- lib/rails_simple_auth/models/concerns/authenticatable.rb
|
|
85
87
|
- lib/rails_simple_auth/models/concerns/confirmable.rb
|
|
86
88
|
- lib/rails_simple_auth/models/concerns/magic_linkable.rb
|
|
@@ -100,6 +102,7 @@ metadata:
|
|
|
100
102
|
bug_tracker_uri: https://github.com/ivankuznetsov/rails_simple_auth/issues
|
|
101
103
|
documentation_uri: https://github.com/ivankuznetsov/rails_simple_auth#readme
|
|
102
104
|
rubygems_mfa_required: 'true'
|
|
105
|
+
post_install_message:
|
|
103
106
|
rdoc_options: []
|
|
104
107
|
require_paths:
|
|
105
108
|
- lib
|
|
@@ -114,7 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
114
117
|
- !ruby/object:Gem::Version
|
|
115
118
|
version: '0'
|
|
116
119
|
requirements: []
|
|
117
|
-
rubygems_version: 3.
|
|
120
|
+
rubygems_version: 3.5.22
|
|
121
|
+
signing_key:
|
|
118
122
|
specification_version: 4
|
|
119
123
|
summary: Simple, secure authentication for Rails 8+ applications
|
|
120
124
|
test_files: []
|