searls-auth 0.2.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -1
- data/README.md +162 -0
- data/app/controllers/searls/auth/base_controller.rb +42 -21
- data/app/controllers/searls/auth/email_verifications_controller.rb +57 -0
- data/app/controllers/searls/auth/logins_controller.rb +60 -39
- data/app/controllers/searls/auth/registrations_controller.rb +84 -32
- data/app/controllers/searls/auth/requests_password_resets_controller.rb +55 -0
- data/app/controllers/searls/auth/resets_passwords_controller.rb +73 -0
- data/app/controllers/searls/auth/settings_controller.rb +83 -0
- data/app/controllers/searls/auth/verifications_controller.rb +31 -61
- data/app/helpers/searls/auth/application_helper.rb +9 -5
- data/app/mailers/searls/auth/base_mailer.rb +1 -1
- data/app/mailers/searls/auth/email_verification_mailer.rb +29 -0
- data/app/mailers/searls/auth/login_link_mailer.rb +3 -3
- data/app/mailers/searls/auth/password_reset_mailer.rb +29 -0
- data/app/views/searls/auth/email_verification_mailer/verification_email.html.erb +23 -0
- data/app/views/searls/auth/email_verification_mailer/verification_email.text.erb +6 -0
- data/app/views/searls/auth/login_link_mailer/login_link.html.erb +5 -5
- data/app/views/searls/auth/login_link_mailer/login_link.text.erb +4 -5
- data/app/views/searls/auth/logins/show.html.erb +12 -4
- data/app/views/searls/auth/password_reset_mailer/password_reset.html.erb +23 -0
- data/app/views/searls/auth/password_reset_mailer/password_reset.text.erb +6 -0
- data/app/views/searls/auth/registrations/pending_email_verification.html.erb +12 -0
- data/app/views/searls/auth/registrations/show.html.erb +1 -2
- data/app/views/searls/auth/requests_password_resets/show.html.erb +17 -0
- data/app/views/searls/auth/resets_passwords/show.html.erb +26 -0
- data/app/views/searls/auth/settings/edit.html.erb +31 -0
- data/app/views/searls/auth/shared/_login_fields.html.erb +11 -0
- data/app/views/searls/auth/shared/_register_fields.html.erb +15 -0
- data/config/routes.rb +11 -0
- data/lib/searls/auth/authenticates_user.rb +54 -10
- data/lib/searls/auth/builds_target_redirect_url.rb +72 -0
- data/lib/searls/auth/config.rb +259 -12
- data/lib/searls/auth/creates_user.rb +12 -4
- data/lib/searls/auth/delivers_password_reset.rb +18 -0
- data/lib/searls/auth/emails_link.rb +2 -2
- data/lib/searls/auth/emails_verification.rb +33 -0
- data/lib/searls/auth/parses_time_safely.rb +32 -0
- data/lib/searls/auth/railtie.rb +0 -1
- data/lib/searls/auth/resets_password.rb +41 -0
- data/lib/searls/auth/updates_settings.rb +149 -0
- data/lib/searls/auth/version.rb +1 -1
- data/lib/searls/auth.rb +62 -13
- metadata +23 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fbce83eb529408eab5ccd2d5bb1812e3a498cf0c850df112080e043d7a3dc0c
|
4
|
+
data.tar.gz: '029224229936f9e33585fa99bb11cf077912b8cda143b1c348f27cc85d74556c'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99f4d3c9ac6659ab032153726042d4280c2aa8f273b6d18ca2abe72ff337301671dd92a85227c615d01405193a769c20b09297e4f84c020448786aa99c035b52
|
7
|
+
data.tar.gz: 26d57bfb5d1b1cb69ffabad31618577a7cdf7f3ed3101c5f4b0424ca2e445a419c15d506eca82dce640ed9ccba7f9d91d7aa7b7e105e0f3efca966b7740b139f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.0.1] - 2025-10-11
|
4
|
+
|
5
|
+
* Skip database-aware configuration validations when Active Record migrations are pending
|
6
|
+
|
7
|
+
## [1.0.0] - 2025-10-04
|
8
|
+
|
9
|
+
* **BREAKING:** Rename `default_redirect_path_after_register` to `redirect_path_after_register`
|
10
|
+
* **BREAKING:** Rename `flash_notice_after_verification` to `flash_notice_after_login`
|
11
|
+
* Add password reset flow with default controllers, mailer, views, and configuration hooks
|
12
|
+
* Add `before_password_reset` hook to optionally throttle or reject reset requests
|
13
|
+
* Add configurable `password_reset_request_view` and `password_reset_edit_view` settings
|
14
|
+
* Add `password_reset_enabled` flag to disable the forgot-password link/flow when email delivery is unavailable
|
15
|
+
* Add account settings controller/view for password rotation and email changes, plus related configuration hooks
|
16
|
+
* Switch from flash[:error] to the conventional flash[:alert] (TIL, 20 years in that :alert is more common)
|
17
|
+
|
3
18
|
## [0.2.0] - 2025-09-11
|
4
19
|
|
5
20
|
* Add `auth_methods` configuration with default `[:email_link, :email_otp]`
|
@@ -10,7 +25,7 @@
|
|
10
25
|
|
11
26
|
## [0.1.0] - 2025-04-26
|
12
27
|
|
13
|
-
* Add `
|
28
|
+
* Add `max_allowed_email_otp_attempts` configuration, beyond which the code is erased from the session and the user needs to login again (default: 10)
|
14
29
|
* Allow configuration of flash messages
|
15
30
|
* Fix a routing error if the user is already registered
|
16
31
|
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# searls-auth
|
2
2
|
|
3
|
+
[](https://justin.searls.co/shovelware/)
|
4
|
+
|
3
5
|
This gem provides a Ruby on Rails engine that implements a minimal, opinionated, and pleasant email-based authentication system. It has zero other dependencies, which is the correct number of dependencies.
|
4
6
|
|
5
7
|
For a detailed walk-through with pictures and whatnot, check out this [example app README](/example/simple_app/README.md). Below you'll find the basic steps for getting started.
|
@@ -93,6 +95,9 @@ Rails.application.config.after_initialize do
|
|
93
95
|
# Defaults:
|
94
96
|
config.auth_methods = [:email_link, :email_otp]
|
95
97
|
|
98
|
+
# Email OTPs expire after 30 minutes by default.
|
99
|
+
# config.email_otp_expiry_minutes = 10
|
100
|
+
|
96
101
|
# Link-only (no code in emails, no OTP input shown):
|
97
102
|
# config.auth_methods = :email_link
|
98
103
|
|
@@ -104,6 +109,163 @@ end
|
|
104
109
|
|
105
110
|
One reason you might want to disable e-mail OTP is that it exposes your users to [a pretty easy-to-implement man-in-the-middle attack](https://blog.danielh.cc/blog/passwords).
|
106
111
|
|
112
|
+
### Email verification modes
|
113
|
+
|
114
|
+
Control whether registration triggers verification emails and whether password login requires a verified email.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# config/initializers/searls_auth.rb
|
118
|
+
Rails.application.config.after_initialize do
|
119
|
+
Searls::Auth.configure do |config|
|
120
|
+
# :none (default): No verification emails on registration; password login allowed immediately.
|
121
|
+
# :optional: Send a verification email on registration, allow password login, but remind users until verified.
|
122
|
+
# :required: Send a verification email on registration and block password login until verified.
|
123
|
+
config.email_verification_mode = :none # or :optional, :required
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
If you enable the built‑in password login (`config.auth_methods` includes `:password`), we assume your `User` model uses `has_secure_password` (or you can provide custom hooks via `password_verifier` and `password_setter`). Verification status is checked via `email_verified_at` by default and can be customized with `email_verified_predicate`/`email_verified_setter`.
|
129
|
+
|
130
|
+
### Password login
|
131
|
+
|
132
|
+
Enabling `:password` adds email+password fields to the login and registration flows. Minimal setup looks like this:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# app/models/user.rb
|
136
|
+
class User < ApplicationRecord
|
137
|
+
has_secure_password
|
138
|
+
|
139
|
+
# uncomment if enabling auth_methods :email_link or :email_otp
|
140
|
+
# generates_token_for :email_auth, expires_in: 30.minutes
|
141
|
+
end
|
142
|
+
|
143
|
+
# db/migrate/XXXX_add_password_columns.rb
|
144
|
+
class AddPasswordColumns < ActiveRecord::Migration[8.0]
|
145
|
+
def change
|
146
|
+
add_column :users, :password_digest, :string
|
147
|
+
add_column :users, :email_verified_at, :datetime
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# config/initializers/searls_auth.rb
|
152
|
+
Rails.application.config.after_initialize do
|
153
|
+
Searls::Auth.configure do |config|
|
154
|
+
config.auth_methods = [:password] # or any combination like [:password, :email_link, :email_otp]
|
155
|
+
config.email_verification_mode = :required # :none and :optional supported too
|
156
|
+
end
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
If you already have legacy password hashing, override `password_verifier`/`password_setter` to wrap it, otherwise we'll use conventional `bcrypt` with `has_secure_password` and `password_digest` comparisons. Likewise, if email verification lives on a different column or association, use `email_verified_predicate`/`email_verified_setter` to adapt.
|
161
|
+
|
162
|
+
All successful logins still render through the same flows, so make sure your app handles `session[:user_id]` uniformly regardless of which auth method succeeded.
|
163
|
+
|
164
|
+
If a registration request doesn't supply a `redirect_path` parameter, searls-auth now falls back to `config.redirect_path_after_register` when choosing both the post-registration redirect and the link embedded in verification emails. Override that proc to point brand-new users somewhere more purposeful than the default root path.
|
165
|
+
|
166
|
+
Likewise, successful logins fall back to `config.redirect_path_after_login` whenever no redirect parameters are supplied. Set it to a dashboard or home screen to spare users from landing on `/`.
|
167
|
+
|
168
|
+
### Password reset
|
169
|
+
|
170
|
+
When `auth_methods` includes `:password`, the engine renders a "Forgot your password?" link beneath the login form. Clicking it walks through a two-step flow: request a reset email and then choose a new password. To enable it, make sure your `User` model issues a token named `:password_reset`. If your app cannot send email, disable the link entirely with `config.password_reset_enabled = false`.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# app/models/user.rb
|
174
|
+
class User < ApplicationRecord
|
175
|
+
has_secure_password
|
176
|
+
|
177
|
+
# You can skip this if password reset is not enabled:
|
178
|
+
generates_token_for :password_reset, expires_in: 30.minutes
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
Adjust the expiry window by updating the `expires_in` value above or by providing a custom generator via configuration.
|
183
|
+
|
184
|
+
Need rate limiting or business rules before delivering reset emails? Return `false` or `:halt` from `before_password_reset` to silently skip sending while preserving the standard response.
|
185
|
+
|
186
|
+
By default we generate tokens via `generates_token_for`, send mail from `Searls::Auth::PasswordResetMailer`, and log the user in immediately after a successful reset. You can override any piece of that behavior:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
Searls::Auth.configure do |config|
|
190
|
+
config.password_reset_token_generator = ->(user) { user.generate_token_for(:password_reset) }
|
191
|
+
config.password_reset_token_finder = ->(token) { PasswordResetTokenStore.lookup(token) }
|
192
|
+
config.auto_login_after_password_reset = false # redirect back to login instead
|
193
|
+
config.mail_password_reset_template_path = "my_auth/password_reset_mailer"
|
194
|
+
config.mail_password_reset_template_name = "email"
|
195
|
+
config.before_password_reset = ->(user, params, controller) do
|
196
|
+
PasswordResetThrottle.allow?(user_id: user&.id, ip: controller.request.remote_ip)
|
197
|
+
end
|
198
|
+
config.password_reset_request_view = "my_auth/password_resets/request"
|
199
|
+
config.password_reset_edit_view = "my_auth/password_resets/edit"
|
200
|
+
config.password_reset_enabled = false if Rails.env.development? # hide link without SMTP
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
### Account settings
|
205
|
+
|
206
|
+
When password authentication is enabled you can wire any settings UI to the engine by posting to `searls_auth.settings_path`. A simple Rails form looks like:
|
207
|
+
|
208
|
+
```erb
|
209
|
+
<%= form_with scope: :settings,
|
210
|
+
url: searls_auth.settings_path,
|
211
|
+
method: :patch,
|
212
|
+
local: true do |f| %>
|
213
|
+
<%= f.password_field :current_password %>
|
214
|
+
<%= f.password_field :password %>
|
215
|
+
<%= f.password_field :password_confirmation %>
|
216
|
+
<%= f.submit "Save" %>
|
217
|
+
<% end %>
|
218
|
+
```
|
219
|
+
|
220
|
+
The controller will rotate or set passwords, requiring the current password when appropriate.
|
221
|
+
|
222
|
+
Configure where users land afterwards by overriding `config.redirect_path_after_settings_change` in your initializer so it points back to your own settings page:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
Searls::Auth.configure do |config|
|
226
|
+
config.redirect_path_after_settings_change = ->(_user, _params, _request, routes) { routes.settings_path }
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
If you track password state differently, provide your own `config.password_present_predicate`. You can also adjust the flash messages: `flash_notice_after_settings_update`, `flash_error_after_settings_current_password_missing`, and `flash_error_after_settings_current_password_invalid`.
|
231
|
+
|
232
|
+
Want to tweak copy? Override the flash messages `flash_notice_after_password_reset_email`, `flash_notice_after_password_reset`, `flash_error_after_password_reset_token_invalid`, `flash_error_after_password_reset_password_mismatch`, and `flash_error_after_password_reset_password_blank`, or shadow the mailer templates at `app/views/searls/auth/password_reset_mailer/password_reset.html.erb` and `.text.erb`.
|
233
|
+
|
234
|
+
#### Triggering a (re)verification email
|
235
|
+
|
236
|
+
Users can request another verification email. The engine exposes a PATCH endpoint and helper you can call from your app:
|
237
|
+
|
238
|
+
```erb
|
239
|
+
<%# Anywhere in your app %>
|
240
|
+
<%= link_to "Resend verification email",
|
241
|
+
searls_auth.resend_email_verification_path,
|
242
|
+
data: { turbo_method: :patch } %>
|
243
|
+
```
|
244
|
+
|
245
|
+
This uses the same mailer and template as login emails. You can override the template in two ways:
|
246
|
+
|
247
|
+
- Configure the template path/name:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
Searls::Auth.configure do |config|
|
251
|
+
config.mail_login_template_path = "my_auth/mailer"
|
252
|
+
config.mail_login_template_name = "login_link"
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
- Or create views that shadow the engine’s defaults at `app/views/searls/auth/login_link_mailer/login_link.html.erb` and `.text.erb` in your app.
|
257
|
+
|
258
|
+
### Common configurations
|
259
|
+
|
260
|
+
| `auth_methods` | `email_verification_mode` | Behavior |
|
261
|
+
| --- | --- | --- |
|
262
|
+
| `[:email_link, :email_otp]` (default) | `:none` | Passwordless magic link + email OTP. Registration links go straight to the verify screen. |
|
263
|
+
| `[:password]` | `:none` | Classic email/password. No email is sent; verify routes redirect back to login. |
|
264
|
+
| `[:password, :email_link, :email_otp]` | `:optional` | Users can log in with either password or email. Registration logs the user in immediately and also emails a verification link. |
|
265
|
+
| `[:password, :email_link]` | `:required` | Registration emails a link and blocks password login until verified. Resend verification is exposed at `searls_auth.resend_email_verification_path`. |
|
266
|
+
|
267
|
+
In every case, `redirect_path` values are normalized to on-site URLs, so forwarding someone to login with `redirect_path: some_path` keeps the eventual redirect on your domain (cross-subdomain redirects still work via `redirect_subdomain`).
|
268
|
+
|
107
269
|
## Use it
|
108
270
|
|
109
271
|
Of course, having a user be "logged in" or not doesn't mean anything if your application doesn't do anything with the knowledge. Users that are logged in will have `session[:user_id]` set to the value of the logged-in user's ID. Logged out users won't have anything set to `session[:user_id]`. What you do with that is your job, not this gem. (Wait, after 20 years does this mean I finally understand the difference between authentication and authorization? Better late than never.)
|
@@ -2,40 +2,61 @@ require "securerandom"
|
|
2
2
|
|
3
3
|
module Searls
|
4
4
|
module Auth
|
5
|
-
class BaseController < ApplicationController
|
5
|
+
class BaseController < ApplicationController
|
6
6
|
helper Rails.application.helpers
|
7
7
|
helper Rails.application.routes.url_helpers
|
8
|
+
helper_method :forwardable_params
|
9
|
+
|
10
|
+
def forwardable_params
|
11
|
+
{redirect_path: params[:redirect_path], redirect_subdomain: params[:redirect_subdomain]}.compact_blank
|
12
|
+
end
|
8
13
|
|
9
14
|
protected
|
10
15
|
|
11
|
-
def
|
12
|
-
|
16
|
+
def attach_email_otp_to_session!(user)
|
17
|
+
session[:searls_auth_email_otp_user_id] = user.id
|
18
|
+
session[:searls_auth_email_otp] = SecureRandom.random_number(1000000).to_s.rjust(6, "0")
|
19
|
+
session[:searls_auth_email_otp_generated_at] = Time.current
|
20
|
+
session[:searls_auth_email_otp_verification_attempts] = 0
|
13
21
|
end
|
14
22
|
|
15
|
-
def
|
16
|
-
session[:
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
def reset_expired_email_otp
|
24
|
+
generated_at = session[:searls_auth_email_otp_generated_at]
|
25
|
+
cutoff = Searls::Auth.config.email_otp_expiry_minutes.minutes.ago
|
26
|
+
unless generated_at.present? && (parsed = Searls::Auth::ParsesTimeSafely.new.parse(generated_at)) && parsed > cutoff
|
27
|
+
clear_email_otp_from_session!
|
28
|
+
end
|
20
29
|
end
|
21
30
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
def clear_email_otp_from_session!
|
32
|
+
session.delete(:searls_auth_email_otp_user_id)
|
33
|
+
session.delete(:searls_auth_email_otp_generated_at)
|
34
|
+
session.delete(:searls_auth_email_otp)
|
35
|
+
session.delete(:searls_auth_email_otp_verification_attempts)
|
27
36
|
end
|
28
37
|
|
29
|
-
def
|
30
|
-
session
|
31
|
-
session
|
32
|
-
session.delete(:searls_auth_short_code)
|
33
|
-
session.delete(:searls_auth_short_code_verification_attempts)
|
38
|
+
def log_email_otp_verification_attempt!
|
39
|
+
session[:searls_auth_email_otp_verification_attempts] ||= 0
|
40
|
+
session[:searls_auth_email_otp_verification_attempts] += 1
|
34
41
|
end
|
35
42
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
43
|
+
def target_redirect_url
|
44
|
+
Searls::Auth::BuildsTargetRedirectUrl.new.build(request, params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def redirect_with_host_awareness(target)
|
48
|
+
redirect_to target, allow_other_host: target&.start_with?("http://", "https://")
|
49
|
+
end
|
50
|
+
|
51
|
+
def redirect_after_login(user)
|
52
|
+
if (target = target_redirect_url)
|
53
|
+
redirect_with_host_awareness(target)
|
54
|
+
else
|
55
|
+
redirect_to Searls::Auth.config.resolve(
|
56
|
+
:redirect_path_after_login,
|
57
|
+
user, params, request, main_app
|
58
|
+
) || searls_auth.login_path
|
59
|
+
end
|
39
60
|
end
|
40
61
|
end
|
41
62
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class EmailVerificationsController < BaseController
|
4
|
+
def resend
|
5
|
+
user = if session[:user_id].present?
|
6
|
+
Searls::Auth.config.user_finder_by_id.call(session[:user_id])
|
7
|
+
elsif session[:searls_auth_pending_email].present?
|
8
|
+
Searls::Auth.config.user_finder_by_email.call(session[:searls_auth_pending_email])
|
9
|
+
end
|
10
|
+
|
11
|
+
if user.blank? || Searls::Auth.config.email_verification_mode == :none
|
12
|
+
flash[:alert] = Searls::Auth.config.resolve(:flash_error_after_verify_attempt_invalid_link, params)
|
13
|
+
else
|
14
|
+
EmailsVerification.new.email(user: user, **forwardable_params)
|
15
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_verification_email_resent, params)
|
16
|
+
end
|
17
|
+
|
18
|
+
if session[:user_id].present?
|
19
|
+
redirect_to searls_auth.verify_path
|
20
|
+
else
|
21
|
+
fallback = if session[:searls_auth_pending_email].present?
|
22
|
+
searls_auth.pending_email_verification_path({
|
23
|
+
email: session[:searls_auth_pending_email],
|
24
|
+
redirect_path: session[:searls_auth_pending_redirect_path],
|
25
|
+
redirect_subdomain: session[:searls_auth_pending_redirect_subdomain]
|
26
|
+
}.compact_blank)
|
27
|
+
else
|
28
|
+
searls_auth.login_path
|
29
|
+
end
|
30
|
+
redirect_back fallback_location: fallback
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def show
|
35
|
+
user = Searls::Auth.config.user_finder_by_token.call(params[:token])
|
36
|
+
|
37
|
+
if user.present?
|
38
|
+
unless Searls::Auth.config.email_verified_predicate.call(user)
|
39
|
+
Searls::Auth.config.email_verified_setter.call(user)
|
40
|
+
end
|
41
|
+
|
42
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_email_verified, user, params)
|
43
|
+
|
44
|
+
Searls::Auth.config.after_login_success.call(user)
|
45
|
+
session[:user_id] = user.id
|
46
|
+
session[:has_logged_in_before] = true
|
47
|
+
|
48
|
+
redirect_after_login(user)
|
49
|
+
else
|
50
|
+
flash[:alert] = Searls::Auth.config.resolve(:flash_error_after_verify_attempt_invalid_link, params)
|
51
|
+
|
52
|
+
redirect_to searls_auth.login_path(**forwardable_params)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,58 +1,79 @@
|
|
1
1
|
module Searls
|
2
2
|
module Auth
|
3
3
|
class LoginsController < BaseController
|
4
|
-
before_action :
|
4
|
+
before_action :reset_expired_email_otp
|
5
5
|
|
6
6
|
def show
|
7
|
-
render
|
7
|
+
render Searls::Auth.config.login_view, layout: Searls::Auth.config.layout
|
8
8
|
end
|
9
9
|
|
10
10
|
def create
|
11
|
-
|
11
|
+
if Searls::Auth.config.auth_methods.include?(:password) && params[:send_login_email].blank?
|
12
|
+
handle_password_login
|
13
|
+
else
|
14
|
+
handle_email_login
|
15
|
+
end
|
16
|
+
end
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
|
18
|
+
def destroy
|
19
|
+
ResetsSession.new.reset(self, except_for: [:has_logged_in_before])
|
20
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_logout, params)
|
21
|
+
redirect_to searls_auth.login_path
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def handle_password_login
|
27
|
+
authenticator = AuthenticatesUser.new
|
28
|
+
result = authenticator.authenticate_by_password(params[:email], params[:password], session)
|
29
|
+
|
30
|
+
if result.success?
|
31
|
+
session[:user_id] = result.user.id
|
32
|
+
session[:has_logged_in_before] = true
|
33
|
+
|
34
|
+
flash[:notice] = if Searls::Auth.config.email_verification_mode != :none && !Searls::Auth.config.email_verified_predicate.call(result.user)
|
35
|
+
resend_path = searls_auth.resend_email_verification_path(**forwardable_params)
|
36
|
+
Searls::Auth.config.resolve(:flash_notice_after_login_with_unverified_email, resend_path, params)
|
16
37
|
else
|
17
|
-
|
38
|
+
Searls::Auth.config.resolve(:flash_notice_after_login, result.user, params)
|
18
39
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
)
|
26
|
-
flash[:notice] = searls_auth_config.resolve(
|
27
|
-
:flash_notice_after_login_attempt,
|
28
|
-
user, params
|
29
|
-
)
|
30
|
-
redirect_to searls_auth.verify_path(
|
31
|
-
redirect_path: params[:redirect_path],
|
32
|
-
redirect_subdomain: params[:redirect_subdomain]
|
33
|
-
)
|
40
|
+
redirect_after_login(result.user)
|
41
|
+
elsif result.email_unverified?
|
42
|
+
session[:searls_auth_pending_email] = params[:email]
|
43
|
+
resend_path = searls_auth.resend_email_verification_path(**forwardable_params)
|
44
|
+
flash.now[:alert] = Searls::Auth.config.resolve(:flash_error_after_login_attempt_unverified_email, resend_path, params)
|
45
|
+
render Searls::Auth.config.login_view, layout: Searls::Auth.config.layout, status: :unprocessable_content
|
34
46
|
else
|
35
|
-
|
36
|
-
|
37
|
-
searls_auth.register_path(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
params
|
43
|
-
)
|
44
|
-
render searls_auth_config.login_view, layout: searls_auth_config.layout, status: :unprocessable_entity
|
47
|
+
user = Searls::Auth.config.user_finder_by_email.call(params[:email])
|
48
|
+
flash.now[:alert] = if user.blank?
|
49
|
+
Searls::Auth.config.resolve(:flash_error_after_login_attempt_unknown_email, searls_auth.register_path(email: params[:email], **forwardable_params), params)
|
50
|
+
else
|
51
|
+
Searls::Auth.config.resolve(:flash_error_after_login_attempt_invalid_password, params)
|
52
|
+
end
|
53
|
+
render Searls::Auth.config.login_view, layout: Searls::Auth.config.layout, status: :unprocessable_content
|
45
54
|
end
|
55
|
+
rescue NameError
|
56
|
+
flash.now[:alert] = Searls::Auth.config.resolve(:flash_error_after_password_misconfigured, params)
|
57
|
+
render Searls::Auth.config.login_view, layout: Searls::Auth.config.layout, status: :unprocessable_content
|
46
58
|
end
|
47
59
|
|
48
|
-
def
|
49
|
-
|
60
|
+
def handle_email_login
|
61
|
+
user = Searls::Auth.config.user_finder_by_email.call(params[:email])
|
50
62
|
|
51
|
-
|
52
|
-
:
|
53
|
-
|
54
|
-
|
55
|
-
|
63
|
+
if user.present?
|
64
|
+
if Searls::Auth.config.auth_methods.include?(:email_otp)
|
65
|
+
attach_email_otp_to_session!(user)
|
66
|
+
else
|
67
|
+
clear_email_otp_from_session!
|
68
|
+
end
|
69
|
+
|
70
|
+
EmailsLink.new.email(user:, email_otp: session[:searls_auth_email_otp], **forwardable_params)
|
71
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_login_attempt, user, params)
|
72
|
+
redirect_to searls_auth.verify_path(**forwardable_params)
|
73
|
+
else
|
74
|
+
flash.now[:alert] = Searls::Auth.config.resolve(:flash_error_after_login_attempt_unknown_email, searls_auth.register_path(email: params[:email], **forwardable_params), params)
|
75
|
+
render Searls::Auth.config.login_view, layout: Searls::Auth.config.layout, status: :unprocessable_content
|
76
|
+
end
|
56
77
|
end
|
57
78
|
end
|
58
79
|
end
|
@@ -2,47 +2,99 @@ module Searls
|
|
2
2
|
module Auth
|
3
3
|
class RegistrationsController < BaseController
|
4
4
|
def show
|
5
|
-
render
|
5
|
+
render Searls::Auth.config.register_view, layout: Searls::Auth.config.layout
|
6
6
|
end
|
7
7
|
|
8
8
|
def create
|
9
9
|
result = CreatesUser.new.call(params)
|
10
10
|
|
11
11
|
if result.success?
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
)
|
19
|
-
}
|
20
|
-
|
21
|
-
EmailsLink.new.email(
|
22
|
-
user: result.user,
|
23
|
-
short_code: session[:searls_auth_short_code],
|
24
|
-
**redirect_params
|
25
|
-
)
|
26
|
-
flash[:notice] = searls_auth_config.resolve(
|
27
|
-
:flash_notice_after_registration,
|
28
|
-
result.user, params
|
29
|
-
)
|
30
|
-
|
31
|
-
redirect_to searls_auth.verify_path(**redirect_params)
|
12
|
+
user = result.user
|
13
|
+
if password_registration?
|
14
|
+
handle_password_registration(user)
|
15
|
+
else
|
16
|
+
handle_email_registration(user)
|
17
|
+
end
|
32
18
|
else
|
33
|
-
flash.now[:
|
34
|
-
|
35
|
-
result.error_messages,
|
36
|
-
searls_auth.login_path(
|
37
|
-
email: params[:email],
|
38
|
-
redirect_path: params[:redirect_path],
|
39
|
-
redirect_subdomain: params[:redirect_subdomain]
|
40
|
-
),
|
41
|
-
params
|
42
|
-
)
|
43
|
-
render searls_auth_config.register_view, layout: searls_auth_config.layout, status: :unprocessable_entity
|
19
|
+
flash.now[:alert] = Searls::Auth.config.resolve(:flash_error_after_register_attempt, result.error_messages, searls_auth.login_path(email: params[:email], **forwardable_params), params)
|
20
|
+
render Searls::Auth.config.register_view, layout: Searls::Auth.config.layout, status: :unprocessable_content
|
44
21
|
end
|
45
22
|
end
|
23
|
+
|
24
|
+
def pending_email_verification
|
25
|
+
render Searls::Auth.config.pending_email_verification_view, layout: Searls::Auth.config.layout
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def password_registration?
|
31
|
+
Searls::Auth.config.auth_methods.include?(:password) && params[:password].present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def email_methods_enabled?
|
35
|
+
(Searls::Auth.config.auth_methods & [:email_link, :email_otp]).any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_password_registration(user)
|
39
|
+
target_path, target_subdomain = registration_redirect_destination(user)
|
40
|
+
if Searls::Auth.config.email_verification_mode != :none
|
41
|
+
EmailsVerification.new.email(user: user, redirect_path: target_path, redirect_subdomain: target_subdomain)
|
42
|
+
session[:searls_auth_pending_email] = user.email
|
43
|
+
session[:searls_auth_pending_redirect_path] = target_path
|
44
|
+
session[:searls_auth_pending_redirect_subdomain] = target_subdomain
|
45
|
+
end
|
46
|
+
|
47
|
+
if Searls::Auth.config.email_verification_mode == :required
|
48
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_registration, user, params)
|
49
|
+
redirect_to searls_auth.pending_email_verification_path({email: user.email, redirect_path: target_path, redirect_subdomain: target_subdomain}.compact_blank)
|
50
|
+
else
|
51
|
+
session[:user_id] = user.id
|
52
|
+
session[:has_logged_in_before] = true
|
53
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_login, user, params)
|
54
|
+
if (target = target_redirect_url)
|
55
|
+
redirect_with_host_awareness(target)
|
56
|
+
else
|
57
|
+
fallback = Searls::Auth.config.resolve(:redirect_path_after_login, user, params, request, main_app)
|
58
|
+
redirect_to(fallback || searls_auth.login_path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_email_registration(user)
|
64
|
+
return unless email_methods_enabled?
|
65
|
+
target_path, target_subdomain = registration_redirect_destination(user)
|
66
|
+
enqueue_login_verification_email(user, target_path:, target_subdomain:)
|
67
|
+
flash[:notice] = Searls::Auth.config.resolve(:flash_notice_after_registration, user, params)
|
68
|
+
redirect_to searls_auth.verify_path(**forwardable_params)
|
69
|
+
end
|
70
|
+
|
71
|
+
def registration_redirect_destination(user)
|
72
|
+
if redirect_params_supplied?
|
73
|
+
[params[:redirect_path], params[:redirect_subdomain]]
|
74
|
+
else
|
75
|
+
[Searls::Auth.config.resolve(:redirect_path_after_register, user, params, request, main_app), nil]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def redirect_params_supplied?
|
80
|
+
params[:redirect_path].present? || params[:redirect_subdomain].present?
|
81
|
+
end
|
82
|
+
|
83
|
+
def enqueue_login_verification_email(user, target_path:, target_subdomain:)
|
84
|
+
email_otp = nil
|
85
|
+
if Searls::Auth.config.auth_methods.include?(:email_otp)
|
86
|
+
attach_email_otp_to_session!(user)
|
87
|
+
email_otp = session[:searls_auth_email_otp]
|
88
|
+
else
|
89
|
+
clear_email_otp_from_session!
|
90
|
+
end
|
91
|
+
EmailsLink.new.email(
|
92
|
+
user: user,
|
93
|
+
email_otp: email_otp,
|
94
|
+
redirect_path: target_path,
|
95
|
+
redirect_subdomain: target_subdomain
|
96
|
+
)
|
97
|
+
end
|
46
98
|
end
|
47
99
|
end
|
48
100
|
end
|