aikotoba 0.1.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.
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