aikotoba 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +244 -13
- data/Rakefile +0 -2
- data/app/controllers/aikotoba/accounts_controller.rb +2 -2
- data/app/controllers/aikotoba/application_controller.rb +2 -1
- data/app/controllers/aikotoba/confirms_controller.rb +14 -14
- data/app/controllers/aikotoba/recoveries_controller.rb +14 -14
- data/app/controllers/aikotoba/sessions_controller.rb +3 -3
- data/app/controllers/aikotoba/unlocks_controller.rb +14 -14
- data/app/controllers/concerns/aikotoba/authenticatable.rb +93 -17
- data/app/controllers/concerns/aikotoba/protection/rate_limiting.rb +32 -0
- data/app/controllers/concerns/aikotoba/scopable.rb +36 -0
- data/app/models/aikotoba/account/{service/confirmation.rb → confirmation.rb} +1 -1
- data/app/models/aikotoba/account/confirmation_token.rb +2 -1
- data/app/models/aikotoba/account/{service/lock.rb → lock.rb} +1 -1
- data/app/models/aikotoba/account/password.rb +1 -1
- data/app/models/aikotoba/account/{service/recovery.rb → recovery.rb} +1 -1
- data/app/models/aikotoba/account/recovery_token.rb +2 -1
- data/app/models/aikotoba/account/session.rb +40 -0
- data/app/models/aikotoba/account/token.rb +2 -2
- data/app/models/aikotoba/account/unlock_token.rb +2 -1
- data/app/models/aikotoba/account.rb +68 -10
- data/app/models/concerns/aikotoba/token_encryptable.rb +1 -1
- data/app/views/aikotoba/accounts/new.html.erb +4 -4
- data/app/views/aikotoba/confirms/new.html.erb +3 -3
- data/app/views/aikotoba/recoveries/edit.html.erb +3 -3
- data/app/views/aikotoba/recoveries/new.html.erb +3 -3
- data/app/views/aikotoba/sessions/_links.html.erb +4 -4
- data/app/views/aikotoba/sessions/new.html.erb +3 -3
- data/app/views/aikotoba/unlocks/new.html.erb +3 -3
- data/config/locales/en.yml +5 -6
- data/config/routes.rb +30 -22
- data/db/migrate/20211204121532_create_aikotoba_accounts.rb +11 -0
- data/lib/aikotoba/version.rb +1 -1
- data/lib/aikotoba.rb +58 -18
- metadata +9 -13
- data/app/controllers/concerns/aikotoba/protection/timing_atack.rb +0 -23
- data/app/models/aikotoba/account/service/authentication.rb +0 -66
- data/app/models/aikotoba/account/service/registration.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8e76b0ed9268bcddffdb90924b637de6f65effe09b3da217dde7c26bbe97127
|
|
4
|
+
data.tar.gz: 363e1aea2df7ba058d4cde72c17cfe78bb51bbdc52d91f14ccd8594304d938b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ddf99bfd428b5bf537f36933413809fac6451eef82fb7becc62830fa38c3c478f9008269c8b83969d3974c5c7ae9a7041199dd8739ac1c604050ef424decc2e
|
|
7
|
+
data.tar.gz: cdbcd7e23e21a87bdd36ede42d74f0c446ecc8da75c1a48bac54e54a42ca00179e9cc7d9fde945f990200673d638ee7c5084d374379ab019be4666a80f6d867d
|
data/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
[](https://github.com/madogiwa0124/aikotoba/actions/workflows/ci.yml)
|
|
2
|
+
[](https://badge.fury.io/rb/aikotoba)
|
|
2
3
|
|
|
3
4
|
# Aikotoba
|
|
4
5
|
|
|
@@ -152,6 +153,76 @@ Aikotoba enables a route to recover an account by password reset.
|
|
|
152
153
|
| GET | /recover/:token | Display page for recover account by password reset. |
|
|
153
154
|
| PATCH | /recover/:token | Recover account by password reset. |
|
|
154
155
|
|
|
156
|
+
### Rate Limiting (Rails 8+ only)
|
|
157
|
+
|
|
158
|
+
Aikotoba provides built-in rate limiting for email-sending endpoints to prevent email bombing attacks. This feature requires **Rails 8.0 or later**.
|
|
159
|
+
|
|
160
|
+
Rate limiting is available for:
|
|
161
|
+
|
|
162
|
+
- **Confirmation token requests** (`/confirm` POST endpoint)
|
|
163
|
+
- **Unlock token requests** (`/unlock` POST endpoint)
|
|
164
|
+
- **Password recovery token requests** (`/recover` POST endpoint)
|
|
165
|
+
|
|
166
|
+
By default, rate limiting is disabled (empty configuration). To enable it, configure the respective options:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
Aikotoba.confirmation_rate_limit_options = {
|
|
170
|
+
to: 10,
|
|
171
|
+
within: 1.hour,
|
|
172
|
+
by: -> { request.params.dig(:account, :email).presence || request.remote_ip },
|
|
173
|
+
only: :create
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
When rate limiting is triggered, requests that exceed the limit will receive a 429 (Too Many Requests) response.
|
|
178
|
+
|
|
179
|
+
For detailed configuration examples and options, see the [Configuration](#configuration) section below.
|
|
180
|
+
|
|
181
|
+
### Multiple Scopes
|
|
182
|
+
|
|
183
|
+
Aikotoba supports multiple scopes.
|
|
184
|
+
|
|
185
|
+
You can add a scope by `Aikotoba.add_scope` method. For example, the following code adds an `admin` scope. Unspecified values will be copied from the `default` scope.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
Aikotoba.add_scope(:admin, {
|
|
189
|
+
session_key: "aikotoba_admin_session_token",
|
|
190
|
+
root_path: "/admin",
|
|
191
|
+
sign_in_path: "/admin/sign_in",
|
|
192
|
+
sign_up_path: "/admin/sign_up",
|
|
193
|
+
after_sign_in_path: "/admin/sensitives",
|
|
194
|
+
after_sign_out_path: "/admin/sign_in",
|
|
195
|
+
sign_out_path: "/admin/sign_out",
|
|
196
|
+
confirm_path: "/admin/confirm",
|
|
197
|
+
unlock_path: "/admin/unlock",
|
|
198
|
+
recover_path: "/admin/recover"
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
As shown below, you can perform separate authentication with `/sign_in` and `/admin/sign_in`.
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
$ bin/rails routes
|
|
206
|
+
Routes for Aikotoba::Engine:
|
|
207
|
+
Prefix Verb URI Pattern Controller#Action
|
|
208
|
+
new_session GET /sign_in(.:format) aikotoba/sessions#new
|
|
209
|
+
admin_new_session GET /admin/sign_in(.:format) aikotoba/sessions#new
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The scope is determined dynamically by the root path. For example, when accessing `/admin/sign_in`, the `admin` scope is selected.
|
|
213
|
+
|
|
214
|
+
To automatically switch scopes and get paths, use the `Aikotoba::Scopable#aikotoba_scoped_path` helper method.
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
include Aikotoba::Scopable
|
|
218
|
+
|
|
219
|
+
helper_method :aikotoba_scoped_path
|
|
220
|
+
|
|
221
|
+
aikotoba_scoped_path(:new_session)
|
|
222
|
+
#=> "/sign_in" (if current scope is :default)
|
|
223
|
+
#=> "/admin/sign_in" (if current scope is :admin)
|
|
224
|
+
```
|
|
225
|
+
|
|
155
226
|
## Configuration
|
|
156
227
|
|
|
157
228
|
The following configuration parameters are supported. You can override it. (ex. `initializers/aikotoba.rb`)
|
|
@@ -159,37 +230,177 @@ The following configuration parameters are supported. You can override it. (ex.
|
|
|
159
230
|
```ruby
|
|
160
231
|
require 'aikotoba'
|
|
161
232
|
|
|
233
|
+
# ============================================
|
|
234
|
+
# Global settings
|
|
235
|
+
# ============================================
|
|
236
|
+
|
|
162
237
|
Aikotoba.parent_controller = "ApplicationController"
|
|
163
238
|
Aikotoba.parent_mailer = "ActionMailer::Base"
|
|
164
239
|
Aikotoba.mailer_sender = "from@example.com"
|
|
165
240
|
Aikotoba.email_format = /\A[^\s]+@[^\s]+\z/
|
|
166
|
-
Aikotoba.prevent_timing_atack = true
|
|
167
241
|
Aikotoba.password_pepper = "aikotoba-default-pepper"
|
|
168
242
|
Aikotoba.password_length_range = 8..100
|
|
169
|
-
Aikotoba.
|
|
170
|
-
|
|
171
|
-
Aikotoba.after_sign_in_path = "/"
|
|
172
|
-
Aikotoba.after_sign_out_path = "/sign_in"
|
|
243
|
+
Aikotoba.session_expiry = 7.days
|
|
244
|
+
|
|
173
245
|
|
|
174
246
|
# for registerable
|
|
175
247
|
Aikotoba.registerable = true
|
|
176
|
-
Aikotoba.sign_up_path = "/sign_up"
|
|
177
248
|
|
|
178
249
|
# for confirmable
|
|
179
250
|
Aikotoba.confirmable = false
|
|
180
|
-
Aikotoba.confirm_path = "/confirm"
|
|
181
251
|
Aikotoba.confirmation_token_expiry = 1.day
|
|
182
252
|
|
|
183
253
|
# for lockable
|
|
184
254
|
Aikotoba.lockable = false
|
|
185
|
-
Aikotoba.unlock_path = "/unlock"
|
|
186
255
|
Aikotoba.max_failed_attempts = 10
|
|
187
256
|
Aikotoba.unlock_token_expiry = 1.day
|
|
188
257
|
|
|
189
258
|
# for Recoverable
|
|
190
259
|
Aikotoba.recoverable = false
|
|
191
|
-
Aikotoba.recover_path = "/recover"
|
|
192
260
|
Aikotoba.recovery_token_expiry = 4.hours
|
|
261
|
+
|
|
262
|
+
# ============================================
|
|
263
|
+
# Rate Limiting (Rails 8+ required)
|
|
264
|
+
# ============================================
|
|
265
|
+
# Rate limiting protects email-sending endpoints (confirm, unlock, recover) from email bombing attacks.
|
|
266
|
+
# Default (empty hash): no rate limiting
|
|
267
|
+
|
|
268
|
+
# Requires Rails 8.0+ to use the built-in rate_limit feature.
|
|
269
|
+
#
|
|
270
|
+
# Configuration format: { to: <max_requests>, within: <time_duration>, by: <identifier_proc>, only: <action> }
|
|
271
|
+
#
|
|
272
|
+
# SECURITY RECOMMENDATION:
|
|
273
|
+
# Use .dig() with fallback to prevent nil errors and ensure rate limiting always works:
|
|
274
|
+
# by: -> { request.params.dig(:account, :email).presence || request.remote_ip }
|
|
275
|
+
#
|
|
276
|
+
# This ensures:
|
|
277
|
+
# - Invalid or missing email params don't bypass rate limiting
|
|
278
|
+
# - Fallback to IP address when email is not provided
|
|
279
|
+
# - Protection against email enumeration attacks
|
|
280
|
+
#
|
|
281
|
+
# Examples:
|
|
282
|
+
|
|
283
|
+
# Limit confirmation token requests to 10 per hour, per email address (with IP fallback)
|
|
284
|
+
Aikotoba.confirmation_rate_limit_options = {
|
|
285
|
+
to: 10,
|
|
286
|
+
within: 1.hour,
|
|
287
|
+
by: -> { request.params.dig(:account, :email).presence || request.remote_ip },
|
|
288
|
+
only: :create
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Limit unlock token requests to 5 per hour, per email address (stricter for security)
|
|
292
|
+
Aikotoba.unlock_rate_limit_options = {
|
|
293
|
+
to: 5,
|
|
294
|
+
within: 1.hour,
|
|
295
|
+
by: -> { request.params.dig(:account, :email).presence || request.remote_ip },
|
|
296
|
+
only: :create
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Limit recovery token requests to 3 per hour, per email address (most strict for password recovery)
|
|
300
|
+
Aikotoba.recovery_rate_limit_options = {
|
|
301
|
+
to: 3,
|
|
302
|
+
within: 1.hour,
|
|
303
|
+
by: -> { request.params.dig(:account, :email).presence || request.remote_ip },
|
|
304
|
+
only: :create
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Rate limiting by IP address only (simpler, but less precise)
|
|
308
|
+
# Aikotoba.confirmation_rate_limit_options = {
|
|
309
|
+
# to: 20,
|
|
310
|
+
# within: 1.hour,
|
|
311
|
+
# by: -> { request.remote_ip },
|
|
312
|
+
# only: :create
|
|
313
|
+
# }
|
|
314
|
+
|
|
315
|
+
# ============================================
|
|
316
|
+
# Scope settings
|
|
317
|
+
# ============================================
|
|
318
|
+
|
|
319
|
+
# for Default Scope
|
|
320
|
+
# You can override only the necessary keys.
|
|
321
|
+
Aikotoba.default_scope = {
|
|
322
|
+
authenticate_for: nil, # No restriction for default scope
|
|
323
|
+
session_key: "aikotoba_session_token",
|
|
324
|
+
root_path: "/",
|
|
325
|
+
sign_in_path: "/sign_in",
|
|
326
|
+
sign_out_path: "/sign_out",
|
|
327
|
+
sign_up_path: "/sign_up",
|
|
328
|
+
confirm_path: "/confirm",
|
|
329
|
+
unlock_path: "/unlock",
|
|
330
|
+
recover_path: "/recover",
|
|
331
|
+
after_sign_in_path: "/sensitives",
|
|
332
|
+
after_sign_out_path: "/sign_in"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# for Additional Scopes
|
|
336
|
+
Aikotoba.add_scope(:admin, {
|
|
337
|
+
authenticate_for: "Admin", # Restrict authentication to Admin accounts
|
|
338
|
+
session_key: "aikotoba_admin_session_token",
|
|
339
|
+
root_path: "/admin",
|
|
340
|
+
sign_in_path: "/admin/sign_in",
|
|
341
|
+
sign_out_path: "/admin/sign_out",
|
|
342
|
+
sign_up_path: "/admin/sign_up",
|
|
343
|
+
confirm_path: "/admin/confirm",
|
|
344
|
+
unlock_path: "/admin/unlock",
|
|
345
|
+
recover_path: "/admin/recover",
|
|
346
|
+
after_sign_in_path: "/admin/sensitives",
|
|
347
|
+
after_sign_out_path: "/admin/sign_in"
|
|
348
|
+
})
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Scope Configuration Details
|
|
352
|
+
|
|
353
|
+
`Aikotoba.default_scope=` **merges** the provided hash into the existing default scope (does not replace):
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
# Single key update
|
|
357
|
+
Aikotoba.default_scope[:sign_in_path] = "/custom"
|
|
358
|
+
|
|
359
|
+
# Multiple keys update (recommended for bulk changes)
|
|
360
|
+
Aikotoba.default_scope = {
|
|
361
|
+
sign_in_path: "/custom_sign_in",
|
|
362
|
+
after_sign_in_path: "/dashboard"
|
|
363
|
+
}
|
|
364
|
+
# Other keys (root_path, session_key, etc.) remain unchanged
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Both approaches are valid. Use direct key assignment for single changes, or use `default_scope=` for updating multiple keys at once.
|
|
368
|
+
|
|
369
|
+
#### Filtering by authenticate target type
|
|
370
|
+
|
|
371
|
+
You can restrict authentication to specific target types by setting `authenticate_for`:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
Aikotoba.add_scope(:admin, {
|
|
375
|
+
authenticate_for: "Admin", # Only Admin accounts can sign in to this scope
|
|
376
|
+
root_path: "/admin",
|
|
377
|
+
sign_in_path: "/admin/sign_in",
|
|
378
|
+
# ...
|
|
379
|
+
})
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
When `authenticate_for` is set, only accounts with matching `authenticate_target_type` will authenticate successfully in that scope.
|
|
383
|
+
This is useful for separating authentication between different user types (e.g., Admin vs User).
|
|
384
|
+
|
|
385
|
+
**Note:** The account type is determined by the model associated via `authenticate_target`. Set up your associations in `after_create_account_process`:
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
Rails.application.config.to_prepare do
|
|
389
|
+
Aikotoba::AccountsController.class_eval do
|
|
390
|
+
def after_create_account_process
|
|
391
|
+
if aikotoba_scope.admin?
|
|
392
|
+
admin = Admin.new(nickname: "admin_foo")
|
|
393
|
+
@account.authenticate_target = admin
|
|
394
|
+
admin.save!
|
|
395
|
+
else
|
|
396
|
+
user = User.new(nickname: "foo")
|
|
397
|
+
@account.authenticate_target = user
|
|
398
|
+
user.save!
|
|
399
|
+
end
|
|
400
|
+
@account.save!
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
193
404
|
```
|
|
194
405
|
|
|
195
406
|
## Tips
|
|
@@ -218,9 +429,17 @@ require 'aikotoba'
|
|
|
218
429
|
Rails.application.config.to_prepare do
|
|
219
430
|
Aikotoba::AccountsController.class_eval do
|
|
220
431
|
def after_create_account_process
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
432
|
+
# You can get the scope name by `aikotoba_scope` method.
|
|
433
|
+
if aikotoba_scope.admin?
|
|
434
|
+
admin = Admin.new(nickname: "admin_foo")
|
|
435
|
+
@account.authenticate_target = admin
|
|
436
|
+
admin.save!
|
|
437
|
+
else
|
|
438
|
+
user = User.new(nickname: "foo")
|
|
439
|
+
@account.authenticate_target = user
|
|
440
|
+
user.save!
|
|
441
|
+
end
|
|
442
|
+
@account.save!
|
|
224
443
|
end
|
|
225
444
|
end
|
|
226
445
|
end
|
|
@@ -229,8 +448,20 @@ class Profile < ApplicationRecord
|
|
|
229
448
|
has_one :account, class_name: 'Aikotoba::Account', as: :authenticate_target
|
|
230
449
|
end
|
|
231
450
|
|
|
451
|
+
class Admin < ApplicationRecord
|
|
452
|
+
has_one :account, class_name: 'Aikotoba::Account', as: :authenticate_target
|
|
453
|
+
end
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Then, you can get the associated model from `Aikotoba::Account` instance.
|
|
457
|
+
|
|
458
|
+
```ruby
|
|
459
|
+
|
|
232
460
|
current_account.profile #=> Profile instance
|
|
233
461
|
profile.account #=> Aikotoba::Account instance
|
|
462
|
+
|
|
463
|
+
current_account.admin #=> Admin instance
|
|
464
|
+
admin.account #=> Aikotoba::Account instance
|
|
234
465
|
```
|
|
235
466
|
|
|
236
467
|
### Do something on before, after, failure.
|
|
@@ -257,7 +488,7 @@ Tokens can be encrypted using Active Record Encryption, introduced in Active Rec
|
|
|
257
488
|
To use it, enable Aikotoba.encipted_token in the initializer.
|
|
258
489
|
|
|
259
490
|
```ruby
|
|
260
|
-
Aikotoba.
|
|
491
|
+
Aikotoba.encrypted_token = true
|
|
261
492
|
```
|
|
262
493
|
|
|
263
494
|
### How to identify the controller provided.
|
data/Rakefile
CHANGED
|
@@ -31,11 +31,11 @@ module Aikotoba
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def save_with_callbacks!(account)
|
|
34
|
-
|
|
34
|
+
account.register!
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def after_sign_up_path
|
|
38
|
-
|
|
38
|
+
aikotoba_scoped_path(:new_session_path)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def successed_message
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Aikotoba
|
|
4
4
|
class ApplicationController < Aikotoba.parent_controller.constantize
|
|
5
5
|
include EnabledFeatureCheckable
|
|
6
|
+
include Scopable
|
|
6
7
|
|
|
7
|
-
helper_method :confirmable?, :lockable?, :recoverable?, :registerable
|
|
8
|
+
helper_method :confirmable?, :lockable?, :recoverable?, :registerable?, :aikotoba_scoped_path, :aikotoba_scoped_path, :aikotoba_scope
|
|
8
9
|
|
|
9
10
|
def aikotoba_controller?
|
|
10
11
|
true
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Aikotoba
|
|
4
4
|
class ConfirmsController < ApplicationController
|
|
5
|
-
include Protection::
|
|
5
|
+
include Protection::RateLimiting
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
def self.confirmation_rate_limit_options
|
|
8
|
+
Aikotoba.confirmation_rate_limit_options
|
|
9
|
+
end
|
|
10
|
+
private_class_method :confirmation_rate_limit_options
|
|
11
|
+
|
|
12
|
+
rate_limit(**confirmation_rate_limit_options)
|
|
8
13
|
|
|
9
14
|
def new
|
|
10
15
|
@account = build_account({email: "", password: ""})
|
|
@@ -15,12 +20,11 @@ module Aikotoba
|
|
|
15
20
|
before_send_confirmation_token_process
|
|
16
21
|
send_token_account!(account)
|
|
17
22
|
after_send_confirmation_token_process
|
|
18
|
-
redirect_to success_send_confirmation_token_path, flash: {notice: success_send_confirmation_token_message}
|
|
19
23
|
rescue ActiveRecord::RecordNotFound => e
|
|
20
24
|
failed_send_confirmation_token_process(e)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
ensure
|
|
26
|
+
# NOTE: Always show success message to avoid account enumeration.
|
|
27
|
+
redirect_to success_send_confirmation_token_path, flash: {notice: success_send_confirmation_token_message}
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def update
|
|
@@ -46,7 +50,7 @@ module Aikotoba
|
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
def send_token_account!(account)
|
|
49
|
-
Account::
|
|
53
|
+
Account::Confirmation.create_token!(account: account, notify: true)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
def find_by_has_token_account!(params)
|
|
@@ -56,16 +60,16 @@ module Aikotoba
|
|
|
56
60
|
def confirm_account!(account)
|
|
57
61
|
# NOTE: Confirmation is done using URL tokens, so it is done in the writing role.
|
|
58
62
|
ActiveRecord::Base.connected_to(role: :writing) do
|
|
59
|
-
Account::
|
|
63
|
+
Account::Confirmation.confirm!(account: account)
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
def after_confirmed_path
|
|
64
|
-
|
|
68
|
+
aikotoba_scoped_path(:new_session_path)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
def success_send_confirmation_token_path
|
|
68
|
-
|
|
72
|
+
aikotoba_scoped_path(:new_session_path)
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def confirmed_message
|
|
@@ -76,10 +80,6 @@ module Aikotoba
|
|
|
76
80
|
I18n.t(".aikotoba.messages.confirmation.sent")
|
|
77
81
|
end
|
|
78
82
|
|
|
79
|
-
def failed_send_confirmation_token_message
|
|
80
|
-
I18n.t(".aikotoba.messages.confirmation.failed")
|
|
81
|
-
end
|
|
82
|
-
|
|
83
83
|
# NOTE: Methods to override if you want to do something before send confirm token.
|
|
84
84
|
def before_send_confirmation_token_process
|
|
85
85
|
end
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Aikotoba
|
|
4
4
|
class RecoveriesController < ApplicationController
|
|
5
|
-
include Protection::
|
|
5
|
+
include Protection::RateLimiting
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
def self.recovery_rate_limit_options
|
|
8
|
+
Aikotoba.recovery_rate_limit_options
|
|
9
|
+
end
|
|
10
|
+
private_class_method :recovery_rate_limit_options
|
|
11
|
+
|
|
12
|
+
rate_limit(**recovery_rate_limit_options)
|
|
8
13
|
|
|
9
14
|
def new
|
|
10
15
|
@account = build_account({email: "", password: ""})
|
|
@@ -15,12 +20,11 @@ module Aikotoba
|
|
|
15
20
|
before_send_recovery_token_process
|
|
16
21
|
send_recovery_token!(account)
|
|
17
22
|
after_send_recovery_token_process
|
|
18
|
-
redirect_to success_send_recovery_token_path, flash: {notice: success_send_recovery_token_message}
|
|
19
23
|
rescue ActiveRecord::RecordNotFound => e
|
|
20
24
|
failed_send_recovery_token_process(e)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
ensure
|
|
26
|
+
# NOTE: Always show success message to avoid account enumeration.
|
|
27
|
+
redirect_to success_send_recovery_token_path, flash: {notice: success_send_recovery_token_message}
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def edit
|
|
@@ -62,19 +66,19 @@ module Aikotoba
|
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
def send_recovery_token!(account)
|
|
65
|
-
Account::
|
|
69
|
+
Account::Recovery.create_token!(account: account, notify: true)
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
def recover_account!(account, new_password)
|
|
69
|
-
Account::
|
|
73
|
+
Account::Recovery.recover!(account: account, new_password: new_password)
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
def success_recovered_path
|
|
73
|
-
|
|
77
|
+
aikotoba_scoped_path(:new_session_path)
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def success_send_recovery_token_path
|
|
77
|
-
|
|
81
|
+
aikotoba_scoped_path(:new_session_path)
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def failed_message
|
|
@@ -89,10 +93,6 @@ module Aikotoba
|
|
|
89
93
|
I18n.t(".aikotoba.messages.recovery.sent")
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
def failed_send_recovery_token_message
|
|
93
|
-
I18n.t(".aikotoba.messages.recovery.sent_failed")
|
|
94
|
-
end
|
|
95
|
-
|
|
96
96
|
# NOTE: Methods to override if you want to do something before send recover token.
|
|
97
97
|
def before_send_recovery_token_process
|
|
98
98
|
end
|
|
@@ -40,15 +40,15 @@ module Aikotoba
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def authenticate_account(params)
|
|
43
|
-
Account.authenticate_by(attributes: params)
|
|
43
|
+
Account.authenticate_by(attributes: params, target_type_name: aikotoba_authenticate_target)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def after_sign_in_path
|
|
47
|
-
|
|
47
|
+
aikotoba_scope_config[:after_sign_in_path]
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def after_sign_out_path
|
|
51
|
-
|
|
51
|
+
aikotoba_scope_config[:after_sign_out_path]
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def successed_message
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Aikotoba
|
|
4
4
|
class UnlocksController < ApplicationController
|
|
5
|
-
include Protection::
|
|
5
|
+
include Protection::RateLimiting
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
def self.unlock_rate_limit_options
|
|
8
|
+
Aikotoba.unlock_rate_limit_options
|
|
9
|
+
end
|
|
10
|
+
private_class_method :unlock_rate_limit_options
|
|
11
|
+
|
|
12
|
+
rate_limit(**unlock_rate_limit_options)
|
|
8
13
|
|
|
9
14
|
def new
|
|
10
15
|
@account = build_account({email: "", password: ""})
|
|
@@ -15,12 +20,11 @@ module Aikotoba
|
|
|
15
20
|
before_send_unlock_token_process
|
|
16
21
|
send_token_account!(account)
|
|
17
22
|
after_send_unlock_token_process
|
|
18
|
-
redirect_to success_send_unlock_token_path, flash: {notice: success_send_unlock_token_message}
|
|
19
23
|
rescue ActiveRecord::RecordNotFound => e
|
|
20
24
|
failed_send_unlock_token_process(e)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
ensure
|
|
26
|
+
# NOTE: Always show success message to avoid account enumeration.
|
|
27
|
+
redirect_to success_send_unlock_token_path, flash: {notice: success_send_unlock_token_message}
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def update
|
|
@@ -46,7 +50,7 @@ module Aikotoba
|
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
def send_token_account!(account)
|
|
49
|
-
Account::
|
|
53
|
+
Account::Lock.create_unlock_token!(account: account, notify: true)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
def find_by_has_token_account!(params)
|
|
@@ -56,16 +60,16 @@ module Aikotoba
|
|
|
56
60
|
def unlock_account!(account)
|
|
57
61
|
# NOTE: Unlocking is done using URL tokens, so it is done in the writing role.
|
|
58
62
|
ActiveRecord::Base.connected_to(role: :writing) do
|
|
59
|
-
Account::
|
|
63
|
+
Account::Lock.unlock!(account: account)
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
def after_unlocked_path
|
|
64
|
-
|
|
68
|
+
aikotoba_scoped_path(:new_session_path)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
def success_send_unlock_token_path
|
|
68
|
-
|
|
72
|
+
aikotoba_scoped_path(:new_session_path)
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def unlocked_message
|
|
@@ -76,10 +80,6 @@ module Aikotoba
|
|
|
76
80
|
I18n.t(".aikotoba.messages.unlocking.sent")
|
|
77
81
|
end
|
|
78
82
|
|
|
79
|
-
def failed_send_unlock_token_message
|
|
80
|
-
I18n.t(".aikotoba.messages.unlocking.failed")
|
|
81
|
-
end
|
|
82
|
-
|
|
83
83
|
# NOTE: Methods to override if you want to do something before send unlock token.
|
|
84
84
|
def before_send_unlock_token_process
|
|
85
85
|
end
|