aikotoba 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +334 -0
  4. data/Rakefile +18 -0
  5. data/app/controllers/aikotoba/accounts_controller.rb +61 -0
  6. data/app/controllers/aikotoba/application_controller.rb +13 -0
  7. data/app/controllers/aikotoba/confirms_controller.rb +103 -0
  8. data/app/controllers/aikotoba/recoveries_controller.rb +120 -0
  9. data/app/controllers/aikotoba/sessions_controller.rb +78 -0
  10. data/app/controllers/aikotoba/unlocks_controller.rb +103 -0
  11. data/app/controllers/concerns/aikotoba/authenticatable.rb +40 -0
  12. data/app/controllers/concerns/aikotoba/protection/session_fixation_attack.rb +21 -0
  13. data/app/controllers/concerns/aikotoba/protection/timing_atack.rb +23 -0
  14. data/app/mailers/aikotoba/account_mailer.rb +24 -0
  15. data/app/mailers/aikotoba/application_mailer.rb +5 -0
  16. data/app/models/aikotoba/account/confirmation_token.rb +22 -0
  17. data/app/models/aikotoba/account/recovery_token.rb +22 -0
  18. data/app/models/aikotoba/account/service/authentication.rb +65 -0
  19. data/app/models/aikotoba/account/service/confirmation.rb +31 -0
  20. data/app/models/aikotoba/account/service/lock.rb +42 -0
  21. data/app/models/aikotoba/account/service/recovery.rb +31 -0
  22. data/app/models/aikotoba/account/service/registration.rb +30 -0
  23. data/app/models/aikotoba/account/unlock_token.rb +22 -0
  24. data/app/models/aikotoba/account/value/password.rb +48 -0
  25. data/app/models/aikotoba/account/value/token.rb +18 -0
  26. data/app/models/aikotoba/account.rb +120 -0
  27. data/app/models/concerns/aikotoba/enabled_feature_checkable.rb +41 -0
  28. data/app/models/concerns/aikotoba/token_encryptable.rb +27 -0
  29. data/app/views/aikotoba/account_mailer/confirm.html.erb +3 -0
  30. data/app/views/aikotoba/account_mailer/recover.html.erb +3 -0
  31. data/app/views/aikotoba/account_mailer/unlock.html.erb +3 -0
  32. data/app/views/aikotoba/accounts/new.html.erb +19 -0
  33. data/app/views/aikotoba/common/_errors.html.erb +9 -0
  34. data/app/views/aikotoba/common/_message.html.erb +8 -0
  35. data/app/views/aikotoba/confirms/new.html.erb +14 -0
  36. data/app/views/aikotoba/recoveries/edit.html.erb +15 -0
  37. data/app/views/aikotoba/recoveries/new.html.erb +14 -0
  38. data/app/views/aikotoba/sessions/_links.html.erb +20 -0
  39. data/app/views/aikotoba/sessions/new.html.erb +16 -0
  40. data/app/views/aikotoba/unlocks/new.html.erb +21 -0
  41. data/app/views/layouts/aikotoba/application.html.erb +11 -0
  42. data/app/views/layouts/aikotoba/mailer.html.erb +13 -0
  43. data/app/views/layouts/aikotoba/mailer.text.erb +1 -0
  44. data/config/locales/en.yml +49 -0
  45. data/config/routes.rb +32 -0
  46. data/db/migrate/20211204121532_create_aikotoba_accounts.rb +50 -0
  47. data/lib/aikotoba/constraints/confirmable_constraint.rb +7 -0
  48. data/lib/aikotoba/constraints/lockable_constraint.rb +7 -0
  49. data/lib/aikotoba/constraints/recoverable_constraint.rb +7 -0
  50. data/lib/aikotoba/constraints/registerable_constraint.rb +7 -0
  51. data/lib/aikotoba/engine.rb +7 -0
  52. data/lib/aikotoba/errors.rb +7 -0
  53. data/lib/aikotoba/test/authentication_helper.rb +48 -0
  54. data/lib/aikotoba/version.rb +5 -0
  55. data/lib/aikotoba.rb +45 -0
  56. data/lib/tasks/aikotoba_tasks.rake +4 -0
  57. metadata +128 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b0d639e691f230c3bcb02686f18ecef8dab6a4fecf9310121c8e581a4079ab62
4
+ data.tar.gz: 169419bd1301489504a2d1049ee9f1c939432b95b0b6eea33df195b43bee0c6d
5
+ SHA512:
6
+ metadata.gz: 3bae2cfbf6a1ccacc972abf66057b83b85261ac642800a563ea81b416d83f19f154049d5c9ccc8af0fa6d49c31eec38a0a19cdf3e9ffcd1920e556f02941d0f4
7
+ data.tar.gz: 347e3fbde3a2af2a02828faabebd94ca67adfe1c71e97f93c7c42e1ab2d83ee6bdee3df30ad5af7d760509f0d41b1f82fedbe9981423c2bec52fbc8ffa9122aa
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Madogiwa
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,334 @@
1
+ [![CI](https://github.com/madogiwa0124/aikotoba/actions/workflows/ci.yml/badge.svg)](https://github.com/madogiwa0124/aikotoba/actions/workflows/ci.yml)
2
+
3
+ # Aikotoba
4
+
5
+ Aikotoba meaning password in Japanese.
6
+
7
+ Aikotoba is a Rails engine that makes it easy to implement simple email and password authentication.
8
+
9
+ **Motivation**
10
+
11
+ - Simple implementation using the Rails engine.
12
+ - Modern hashing algorithm.
13
+ - Separate the authentication logic from User.
14
+ - Implementation for multiple DB.
15
+ - Encrypting tokens using Active Record Encryption.
16
+
17
+ **Features**
18
+
19
+ - Authenticatable : Authenticate account using email and password.
20
+ - Registrable(optional) : Register an account using your email address and password.
21
+ - Confirmable(optional) : After registration, send an email with a token to confirm account.
22
+ - Lockable(optional) : Lock account if make a mistake with password more than a certain number of times.
23
+ - Recoverable(optional) : Recover account by resetting password.
24
+
25
+ [For more information](#features)
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'aikotoba'
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Getting Start
38
+
39
+ Aikotoba use `Aikotoba::Account` for authentication. Add it to the migration for `Aikotoba::Account`.
40
+
41
+ ```sh
42
+ $ bin/rails aikotoba:install:migrations
43
+ ```
44
+
45
+ Mount `Aikotoba::Engine` your application.
46
+
47
+ ```ruby
48
+ Rails.application.routes.draw do
49
+ mount Aikotoba::Engine => "/"
50
+ end
51
+ ```
52
+
53
+ Aikotoba enabled routes for registration(`/sign_up`) and authentication(`/sign_in`).
54
+
55
+ include `Aikotoba::Authenticatable` to the controller(ex. `ApplicationController`) use authentication.
56
+
57
+ ```ruby
58
+ class ApplicationController < ActionController::Base
59
+ include Aikotoba::Authenticatable
60
+
61
+ # NOTE: You can implement the get authenticated account process as follows.
62
+ alias_method :current_account, :aikotoba_current_account
63
+ helper_method :current_account
64
+
65
+ # NOTE: You can implement the authorization process as follows
66
+ def authenticate_account!
67
+ return if current_account
68
+ redirect_to aikotoba.new_session_path, flash: {alert: "Oops. You need to Signed up or Signed in." }
69
+ end
70
+ end
71
+ ```
72
+
73
+ ## Features
74
+
75
+ ### Authenticatable
76
+
77
+ Authenticate an account using email and password.
78
+
79
+ | HTTP Verb | Path | Overview |
80
+ | --------- | --------- | ----------------------------------------- |
81
+ | GET | /sign_in | Display sign in page. |
82
+ | POST | /sign_in | Create a login session by authenticating. |
83
+ | DELETE | /sign_out | Clear aikotoba login session. |
84
+
85
+ Aikotoba enable helper methods for authentication. The method name can be changed by `alias_method`.
86
+
87
+ - `aikotoba_current_account` : Returns the logged in instance of `Aikotoba::Account`.
88
+
89
+ ### Registrable
90
+
91
+ To enable it, set `Aikotoba.registerable` to `true`. (It is enabled by default.)
92
+
93
+ ```ruby
94
+ Aikotoba.registerable = true
95
+ ```
96
+
97
+ Register an account using email and password.
98
+
99
+ | HTTP Verb | Path | Overview |
100
+ | --------- | -------- | --------------------- |
101
+ | GET | /sign_up | Display sign up page. |
102
+ | POST | /sign_up | Create an account. |
103
+
104
+ The password is stored as a hash in [Argon2](https://github.com/technion/ruby-argon2).
105
+
106
+ ### Confirmable
107
+
108
+ To enable it, set `Aikotoba.confirmable` to `true`.
109
+
110
+ ```ruby
111
+ Aikotoba.confirmable = true
112
+ ```
113
+
114
+ Aikotoba enable routes for confirmation account. Also, when account registers, a confirmation email is sent to the email address. Only accounts that are confirmed will be authenticated.
115
+
116
+ | HTTP Verb | Path | Overview |
117
+ | --------- | --------------- | -------------------------------------- |
118
+ | GET | /confirm | Display page for create confirm token. |
119
+ | POST | /confirm | Create a confirm token to account. |
120
+ | GET | /confirm/:token | Confirm account by token. |
121
+
122
+ ### Lockable
123
+
124
+ To enable it, set `Aikotoba.lockable` to `true`.
125
+
126
+ ```ruby
127
+ Aikotoba.lockable = true
128
+ ```
129
+
130
+ Aikotoba enables a route to unlock an account. Also, if the authentication fails a certain number of times, the account will be locked. Only accounts that are not locked will be authenticated.
131
+
132
+ | HTTP Verb | Path | Overview |
133
+ | --------- | -------------- | ------------------------------------- |
134
+ | GET | /unlock | Display page for create unlock token. |
135
+ | POST | /unlock | Create a unlock token to account. |
136
+ | GET | /unlock/:token | Unlock account by token. |
137
+
138
+ ### Recoverable
139
+
140
+ To enable it, set `Aikotoba.recoverable` to `true`.
141
+
142
+ ```ruby
143
+ Aikotoba.recoverable = true
144
+ ```
145
+
146
+ Aikotoba enables a route to recover an account by password reset.
147
+
148
+ | HTTP Verb | Path | Overview |
149
+ | --------- | --------------- | --------------------------------------------------- |
150
+ | GET | /recover | Display page for create recover token. |
151
+ | POST | /recover | Create a recover token to account. |
152
+ | GET | /recover/:token | Display page for recover account by password reset. |
153
+ | PATCH | /recover/:token | Recover account by password reset. |
154
+
155
+ ## Configuration
156
+
157
+ The following configuration parameters are supported. You can override it. (ex. `initializers/aikotoba.rb`)
158
+
159
+ ```ruby
160
+ require 'aikotoba'
161
+
162
+ Aikotoba.parent_controller = "ApplicationController"
163
+ Aikotoba.parent_mailer = "ActionMailer::Base"
164
+ Aikotoba.mailer_sender = "from@example.com"
165
+ Aikotoba.email_format = /\A[^\s]+@[^\s]+\z/
166
+ Aikotoba.prevent_timing_atack = true
167
+ Aikotoba.password_pepper = "aikotoba-default-pepper"
168
+ Aikotoba.password_length_range = 8..100
169
+ Aikotoba.sign_in_path = "/sign_in"
170
+ Aikotoba.sign_out_path = "/sign_out"
171
+ Aikotoba.after_sign_in_path = "/"
172
+ Aikotoba.after_sign_out_path = "/sign_in"
173
+
174
+ # for registerable
175
+ Aikotoba.registerable = true
176
+ Aikotoba.sign_up_path = "/sign_up"
177
+
178
+ # for confirmable
179
+ Aikotoba.confirmable = false
180
+ Aikotoba.confirm_path = "/confirm"
181
+ Aikotoba.confirmation_token_expiry = 1.day
182
+
183
+ # for lockable
184
+ Aikotoba.lockable = false
185
+ Aikotoba.unlock_path = "/unlock"
186
+ Aikotoba.max_failed_attempts = 10
187
+ Aikotoba.unlock_token_expiry = 1.day
188
+
189
+ # for Recoverable
190
+ Aikotoba.recoverable = false
191
+ Aikotoba.recover_path = "/recover"
192
+ Aikotoba.recovery_token_expiry = 4.hours
193
+ ```
194
+
195
+ ## Tips
196
+
197
+ ### Customize Message
198
+
199
+ All Messages are managed by `i18n` and can be freely overridden.
200
+
201
+ ### Manually create an `Aikotoba::Account` for authentication.
202
+
203
+ By running the following script, you can hash and store passwords.
204
+
205
+ ```ruby
206
+ Aikotoba::Account.create!(email: "sample@example.com", password: "password")
207
+ Aikotoba::Account.authenticate_by(attributes: {email: "sample@example.com", password: "password"})
208
+ # => created account instance.
209
+ ```
210
+
211
+ ### Create other model with `Aikotoba::Account`.
212
+
213
+ You can override `Aikotoba::AccountsController#after_create_account_process` to create the other models together.
214
+
215
+ ```ruby
216
+ require 'aikotoba'
217
+
218
+ Rails.application.config.to_prepare do
219
+ Aikotoba::AccountsController.class_eval do
220
+ def after_create_account_process
221
+ profile = Profile.new(nickname: "foo")
222
+ profile.save!
223
+ @account.update!(authenticate_target: profile)
224
+ end
225
+ end
226
+ end
227
+
228
+ class Profile < ApplicationRecord
229
+ has_one :account, class_name: 'Aikotoba::Account'
230
+ end
231
+
232
+ current_account.profile #=> Profile instance
233
+ profile.account #=> Aikotoba::Account instance
234
+ ```
235
+
236
+ ### Do something on before, after, failure.
237
+
238
+ Controllers provides methods to execute the overridden process.
239
+
240
+ For example, if you want to record an error log when the account creation fails, you can do the following.
241
+
242
+ ```ruby
243
+ require 'aikotoba'
244
+
245
+ Rails.application.config.to_prepare do
246
+ Aikotoba::AccountsController.class_eval do
247
+ def failed_create_account_process(e)
248
+ logger.error(e)
249
+ end
250
+ end
251
+ end
252
+ ```
253
+
254
+ ### Using encrypted token
255
+
256
+ Tokens can be encrypted using Active Record Encryption, introduced in Active Record 7 and later.
257
+ To use it, enable Aikotoba.encipted_token in the initializer.
258
+
259
+ ```ruby
260
+ Aikotoba.encypted_token = true
261
+ ```
262
+
263
+ ### How to identify the controller provided.
264
+
265
+ The controller provided by Aikotoba is designed to inherit from `Aikotoba::ApplicationController`.
266
+
267
+ Therefore, when implementing authorization based on login status, you can disable only the controllers provided by Aikotoba as follows.
268
+
269
+ ```ruby
270
+ class ApplicationController < ApplicationController
271
+ include Aikotoba::Authenticatable
272
+
273
+ alias_method :current_account, :aikotoba_current_account
274
+
275
+ before_action :authenticate_account!, unless: :aikotoba_controller?
276
+
277
+ def authenticate_account!
278
+ return if current_account
279
+ redirect_to aikotoba.new_session_path, flash: {alert: "Oops. You need to Signed up or Signed in." }
280
+ end
281
+
282
+ private
283
+
284
+ def aikotoba_controller?
285
+ is_a?(::Aikotoba::ApplicationController)
286
+ end
287
+ end
288
+ ```
289
+
290
+ ### Testing
291
+
292
+ You can use a helper to login/logout by Aikotoba.
293
+ :warning: It only supports rack testing.
294
+
295
+ ```ruby
296
+ require "aikotoba/test/authentication_helper"
297
+ require "test_helper"
298
+
299
+ class HelperTest < ActionDispatch::SystemTestCase
300
+ include Aikotoba::Test::AuthenticationHelper::System
301
+ driven_by :rack_test
302
+
303
+ def setup
304
+ email, password = ["email@example.com", "password"]
305
+ @account = ::Aikotoba::Account.build_by(attributes: {email: email, password: password})
306
+ @account.save
307
+ end
308
+
309
+ test "sign_in by helper" do
310
+ aikotoba_sign_in(@account)
311
+ visit "/sensitives"
312
+ assert_selector "h1", text: "Sensitive Page"
313
+ click_on "Sign out"
314
+ assert_selector ".message", text: "Signed out."
315
+ end
316
+
317
+ test "sign_out by helper" do
318
+ aikotoba_sign_in(@account)
319
+ visit "/sensitives"
320
+ aikotoba_sign_out
321
+ visit "/sensitives"
322
+ assert_selector "h1", text: "Sign in"
323
+ assert_selector ".message", text: "Oops. You need to Signed up or Signed in."
324
+ end
325
+ end
326
+ ```
327
+
328
+ ## Contributing
329
+
330
+ Bug reports and pull requests are welcome on GitHub.
331
+
332
+ ## License
333
+
334
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/**/*_test.rb"
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikotoba
4
+ class AccountsController < ApplicationController
5
+ def new
6
+ @account = build_account({email: "", password: ""})
7
+ end
8
+
9
+ def create
10
+ @account = build_account(accounts_params.to_h.symbolize_keys)
11
+ ActiveRecord::Base.transaction do
12
+ before_create_account_process
13
+ save_with_callbacks!(@account)
14
+ after_create_account_process
15
+ end
16
+ redirect_to after_sign_up_path, flash: {notice: successed_message}
17
+ rescue ActiveRecord::RecordInvalid => e
18
+ failed_create_account_process(e)
19
+ flash[:alert] = failed_message
20
+ render :new, status: :unprocessable_entity
21
+ end
22
+
23
+ private
24
+
25
+ def accounts_params
26
+ params.require(:account).permit(:email, :password)
27
+ end
28
+
29
+ def build_account(params)
30
+ Account.build_by(attributes: params)
31
+ end
32
+
33
+ def save_with_callbacks!(account)
34
+ Account::Service::Registration.call!(account: account)
35
+ end
36
+
37
+ def after_sign_up_path
38
+ aikotoba.new_session_path
39
+ end
40
+
41
+ def successed_message
42
+ I18n.t(".aikotoba.messages.registration.success")
43
+ end
44
+
45
+ def failed_message
46
+ I18n.t(".aikotoba.messages.registration.failed")
47
+ end
48
+
49
+ # NOTE: Methods to override if you want to do something before account creation.
50
+ def before_create_account_process
51
+ end
52
+
53
+ # NOTE: Methods to override if you want to do something after account creation.
54
+ def after_create_account_process
55
+ end
56
+
57
+ # NOTE: Methods to override if you want to do something failed account creation.
58
+ def failed_create_account_process(e)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikotoba
4
+ class ApplicationController < Aikotoba.parent_controller.constantize
5
+ include EnabledFeatureCheckable
6
+
7
+ helper_method :confirmable?, :lockable?, :recoverable?, :registerable?
8
+
9
+ def aikotoba_controller?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikotoba
4
+ class ConfirmsController < ApplicationController
5
+ include Protection::TimingAtack
6
+
7
+ before_action :prevent_timing_atack, only: [:update]
8
+
9
+ def new
10
+ @account = build_account({email: "", password: ""})
11
+ end
12
+
13
+ def create
14
+ account = find_by_send_token_account!(confirm_accounts_params)
15
+ before_send_confirmation_token_process
16
+ send_token_account!(account)
17
+ after_send_confirmation_token_process
18
+ redirect_to success_send_confirmation_token_path, flash: {notice: success_send_confirmation_token_message}
19
+ rescue ActiveRecord::RecordNotFound => e
20
+ failed_send_confirmation_token_process(e)
21
+ @account = build_account({email: "", password: ""})
22
+ flash[:alert] = failed_send_confirmation_token_message
23
+ render :new, status: :unprocessable_entity
24
+ end
25
+
26
+ def update
27
+ account = find_by_has_token_account!(params)
28
+ before_confirm_process
29
+ confirm_account!(account)
30
+ after_confirm_process
31
+ redirect_to after_confirmed_path, flash: {notice: confirmed_message}
32
+ end
33
+
34
+ private
35
+
36
+ def confirm_accounts_params
37
+ params.require(:account).permit(:email)
38
+ end
39
+
40
+ def build_account(params)
41
+ Account.build_by(attributes: params)
42
+ end
43
+
44
+ def find_by_send_token_account!(params)
45
+ Account.unconfirmed.find_by!(email: params[:email])
46
+ end
47
+
48
+ def send_token_account!(account)
49
+ Account::Service::Confirmation.create_token!(account: account, notify: true)
50
+ end
51
+
52
+ def find_by_has_token_account!(params)
53
+ Account::ConfirmationToken.active.find_by!(token: params[:token]).account
54
+ end
55
+
56
+ def confirm_account!(account)
57
+ # NOTE: Confirmation is done using URL tokens, so it is done in the writing role.
58
+ ActiveRecord::Base.connected_to(role: :writing) do
59
+ Account::Service::Confirmation.confirm!(account: account)
60
+ end
61
+ end
62
+
63
+ def after_confirmed_path
64
+ aikotoba.new_session_path
65
+ end
66
+
67
+ def success_send_confirmation_token_path
68
+ aikotoba.new_session_path
69
+ end
70
+
71
+ def confirmed_message
72
+ I18n.t(".aikotoba.messages.confirmation.success")
73
+ end
74
+
75
+ def success_send_confirmation_token_message
76
+ I18n.t(".aikotoba.messages.confirmation.sent")
77
+ end
78
+
79
+ def failed_send_confirmation_token_message
80
+ I18n.t(".aikotoba.messages.confirmation.failed")
81
+ end
82
+
83
+ # NOTE: Methods to override if you want to do something before send confirm token.
84
+ def before_send_confirmation_token_process
85
+ end
86
+
87
+ # NOTE: Methods to override if you want to do something after send confirm token.
88
+ def after_send_confirmation_token_process
89
+ end
90
+
91
+ # NOTE: Methods to override if you want to do something failed send confirm token.
92
+ def failed_send_confirmation_token_process(e)
93
+ end
94
+
95
+ # NOTE: Methods to override if you want to do something before confirm.
96
+ def before_confirm_process
97
+ end
98
+
99
+ # NOTE: Methods to override if you want to do something after confirm.
100
+ def after_confirm_process
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikotoba
4
+ class RecoveriesController < ApplicationController
5
+ include Protection::TimingAtack
6
+
7
+ before_action :prevent_timing_atack, only: [:edit, :update]
8
+
9
+ def new
10
+ @account = build_account({email: "", password: ""})
11
+ end
12
+
13
+ def create
14
+ account = find_by_send_token_account!(send_recovery_token_params)
15
+ before_send_recovery_token_process
16
+ send_recovery_token!(account)
17
+ after_send_recovery_token_process
18
+ redirect_to success_send_recovery_token_path, flash: {notice: success_send_recovery_token_message}
19
+ rescue ActiveRecord::RecordNotFound => e
20
+ failed_send_recovery_token_process(e)
21
+ @account = build_account({email: "", password: ""})
22
+ flash[:alert] = failed_send_recovery_token_message
23
+ render :new, status: :unprocessable_entity
24
+ end
25
+
26
+ def edit
27
+ @account = find_by_has_token_account!(params)
28
+ end
29
+
30
+ def update
31
+ @account = find_by_has_token_account!(params)
32
+ before_recover_process
33
+ recover_account!(@account, recover_accounts_params[:password])
34
+ after_recover_process
35
+ redirect_to success_recovered_path, flash: {notice: success_recovered_message}
36
+ rescue ActiveRecord::RecordInvalid => e
37
+ failed_recover_process(e)
38
+ flash[:alert] = failed_message
39
+ render :edit, status: :unprocessable_entity
40
+ end
41
+
42
+ private
43
+
44
+ def send_recovery_token_params
45
+ params.require(:account).permit(:email)
46
+ end
47
+
48
+ def recover_accounts_params
49
+ params.require(:account).permit(:password)
50
+ end
51
+
52
+ def build_account(params)
53
+ Account.build_by(attributes: params)
54
+ end
55
+
56
+ def find_by_send_token_account!(params)
57
+ Account.find_by!(email: params[:email])
58
+ end
59
+
60
+ def find_by_has_token_account!(params)
61
+ Account::RecoveryToken.active.find_by!(token: params[:token]).account
62
+ end
63
+
64
+ def send_recovery_token!(account)
65
+ Account::Service::Recovery.create_token!(account: account, notify: true)
66
+ end
67
+
68
+ def recover_account!(account, new_password)
69
+ Account::Service::Recovery.recover!(account: account, new_password: new_password)
70
+ end
71
+
72
+ def success_recovered_path
73
+ aikotoba.new_session_path
74
+ end
75
+
76
+ def success_send_recovery_token_path
77
+ aikotoba.new_session_path
78
+ end
79
+
80
+ def failed_message
81
+ I18n.t(".aikotoba.messages.recovery.failed")
82
+ end
83
+
84
+ def success_recovered_message
85
+ I18n.t(".aikotoba.messages.recovery.success")
86
+ end
87
+
88
+ def success_send_recovery_token_message
89
+ I18n.t(".aikotoba.messages.recovery.sent")
90
+ end
91
+
92
+ def failed_send_recovery_token_message
93
+ I18n.t(".aikotoba.messages.recovery.sent_failed")
94
+ end
95
+
96
+ # NOTE: Methods to override if you want to do something before send recover token.
97
+ def before_send_recovery_token_process
98
+ end
99
+
100
+ # NOTE: Methods to override if you want to do something after send recover token.
101
+ def after_send_recovery_token_process
102
+ end
103
+
104
+ # NOTE: Methods to override if you want to do something failed send recover token.
105
+ def failed_send_recovery_token_process(e)
106
+ end
107
+
108
+ # NOTE: Methods to override if you want to do something before recover.
109
+ def before_recover_process
110
+ end
111
+
112
+ # NOTE: Methods to override if you want to do something after recover.
113
+ def after_recover_process
114
+ end
115
+
116
+ # NOTE: Methods to override if you want to do something failed recover.
117
+ def failed_recover_process(e)
118
+ end
119
+ end
120
+ end