lockify 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +87 -0
- data/Rakefile +8 -0
- data/app/controllers/users/confirmations_controller.rb +62 -0
- data/app/controllers/users/passwords_controller.rb +62 -0
- data/app/controllers/users/registrations_controller.rb +126 -0
- data/app/controllers/users/sessions_controller.rb +68 -0
- data/app/mailers/user_mailer.rb +21 -0
- data/app/models/concerns/lockify_user.rb +138 -0
- data/app/views/lockify/confirmations/new.html.erb +16 -0
- data/app/views/lockify/passwords/edit.html.erb +25 -0
- data/app/views/lockify/passwords/new.html.erb +16 -0
- data/app/views/lockify/registrations/new.html.erb +29 -0
- data/app/views/lockify/sessions/new.html.erb +26 -0
- data/app/views/lockify/shared/_error_messages.html.erb +15 -0
- data/app/views/lockify/shared/_links.html.erb +19 -0
- data/config/locales/en.yml +55 -0
- data/config/locales/es.yml +55 -0
- data/config/routes.rb +28 -0
- data/lib/generators/lockify/install/install_generator.rb +95 -0
- data/lib/generators/lockify/install/templates/README +37 -0
- data/lib/generators/lockify/install/templates/migration.rb +38 -0
- data/lib/generators/lockify/views/views_generator.rb +13 -0
- data/lib/lockify/controllers/helpers.rb +76 -0
- data/lib/lockify/engine.rb +44 -0
- data/lib/lockify/version.rb +3 -0
- data/lib/lockify.rb +33 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 76ccb3952ec8db598291189dd3eeb1829d30a70bc898875c80f6fbf0ab92df29
|
4
|
+
data.tar.gz: fff2724bd616d40bbc4c80401abd247e552ffd27aa0a6c474781a6182b07ca88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d3d08d984ea634f0518a9f79a207a68aa3a66c9d8c0e71f8bdf27e7d56dcaec7011c28a1745335c119821b45d54628331b0810cc6c786f444e3695abf9b3744
|
7
|
+
data.tar.gz: 3be69e2b3e0a9ae0e04b9399ac6e31332c1010418e6f04f26c4519ac28224fe3bddce761cdba1799f7a4f2c2c702c3e5c74d8e973890d67ba4ff529c5ae10a29
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2024 Liz - soyprogramador.liz.mx
|
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,87 @@
|
|
1
|
+
# Lockify - Drop-in Replacement for Devise
|
2
|
+
|
3
|
+
Lockify es un reemplazo directo y transparente de Devise para Rails 8, diseñado para funcionar sin modificar código existente.
|
4
|
+
|
5
|
+
## Instalación
|
6
|
+
|
7
|
+
1. Remover Devise del Gemfile:
|
8
|
+
```ruby
|
9
|
+
# gem 'devise' # Comentar o eliminar esta línea
|
10
|
+
```
|
11
|
+
|
12
|
+
2. Agregar Lockify al Gemfile:
|
13
|
+
```ruby
|
14
|
+
gem 'lockify', path: 'path/to/lockify'
|
15
|
+
```
|
16
|
+
|
17
|
+
3. Instalar:
|
18
|
+
```bash
|
19
|
+
bundle install
|
20
|
+
rails generate lockify:install
|
21
|
+
rails db:migrate
|
22
|
+
```
|
23
|
+
|
24
|
+
## Compatibilidad con Devise
|
25
|
+
|
26
|
+
### Rutas idénticas:
|
27
|
+
- `/users/sign_in`
|
28
|
+
- `/users/sign_up`
|
29
|
+
- `/users/sign_out`
|
30
|
+
- `/users/password/new`
|
31
|
+
- `/users/password/edit`
|
32
|
+
- `/users/confirmation`
|
33
|
+
|
34
|
+
### Helpers idénticos:
|
35
|
+
- `current_user`
|
36
|
+
- `user_signed_in?`
|
37
|
+
- `authenticate_user!`
|
38
|
+
- `sign_in(user)`
|
39
|
+
- `sign_out`
|
40
|
+
|
41
|
+
### Controladores compatibles:
|
42
|
+
- Variables de instancia: `@user`, `resource`
|
43
|
+
- Métodos: `after_sign_in_path_for`, `stored_location_for`
|
44
|
+
- Callbacks y filtros funcionan igual
|
45
|
+
|
46
|
+
### Vistas compatibles:
|
47
|
+
- Misma estructura de directorios
|
48
|
+
- Mismos helpers y variables
|
49
|
+
- Generador: `rails generate lockify:views`
|
50
|
+
|
51
|
+
## Migración desde Devise
|
52
|
+
|
53
|
+
1. **Backup de la base de datos**
|
54
|
+
2. **Remover Devise del Gemfile**
|
55
|
+
3. **Instalar Lockify**:
|
56
|
+
```bash
|
57
|
+
bundle install
|
58
|
+
rails generate lockify:install
|
59
|
+
rails db:migrate
|
60
|
+
```
|
61
|
+
4. **Listo** - La aplicación funciona igual
|
62
|
+
|
63
|
+
## Campos de base de datos
|
64
|
+
|
65
|
+
Lockify maneja automáticamente la migración de:
|
66
|
+
- `encrypted_password` → `password_digest`
|
67
|
+
- Agrega campos faltantes de confirmación, reset, remember, etc.
|
68
|
+
|
69
|
+
## Configuración
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# config/initializers/lockify.rb
|
73
|
+
Lockify.setup do |config|
|
74
|
+
config.password_length = 6..128
|
75
|
+
config.reset_password_within = 6.hours
|
76
|
+
config.maximum_attempts = 20
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
## Diferencias técnicas
|
81
|
+
|
82
|
+
- Usa `has_secure_password` en lugar de `bcrypt` directo
|
83
|
+
- Implementación nativa de Rails 8
|
84
|
+
- Sin dependencias externas
|
85
|
+
- Mejor rendimiento
|
86
|
+
|
87
|
+
La transición es **100% transparente** para el usuario final y el código de la aplicación.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
class Users::ConfirmationsController < ApplicationController
|
2
|
+
def new
|
3
|
+
self.resource = User.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
self.resource = User.find_by(email: resource_params[:email]&.downcase)
|
8
|
+
|
9
|
+
if resource
|
10
|
+
if resource.confirmed?
|
11
|
+
set_flash_message!(:notice, :already_confirmed)
|
12
|
+
else
|
13
|
+
resource.send_confirmation_instructions
|
14
|
+
set_flash_message!(:notice, :send_instructions)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
set_flash_message!(:alert, :not_found)
|
18
|
+
end
|
19
|
+
|
20
|
+
redirect_to new_user_session_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def show
|
24
|
+
self.resource = User.find_by(confirmation_token: params[:confirmation_token])
|
25
|
+
|
26
|
+
if resource&.confirmation_period_expired?
|
27
|
+
set_flash_message!(:alert, :expired)
|
28
|
+
redirect_to new_user_confirmation_path
|
29
|
+
elsif resource
|
30
|
+
resource.confirm!
|
31
|
+
sign_in(resource)
|
32
|
+
set_flash_message!(:notice, :confirmed)
|
33
|
+
redirect_to after_confirmation_path_for(:user, resource)
|
34
|
+
else
|
35
|
+
set_flash_message!(:alert, :invalid_token)
|
36
|
+
redirect_to new_user_confirmation_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def resource_params
|
43
|
+
params.require(:user).permit(:email)
|
44
|
+
end
|
45
|
+
|
46
|
+
def after_confirmation_path_for(resource_name, resource)
|
47
|
+
after_sign_in_path_for(resource)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_flash_message!(type, key, options = {})
|
51
|
+
message = find_message(key, options)
|
52
|
+
flash[type] = message if message.present?
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_message(key, options = {})
|
56
|
+
I18n.t("lockify.confirmations.#{key}", **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_accessor :resource
|
62
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Users::PasswordsController < ApplicationController
|
2
|
+
def new
|
3
|
+
self.resource = User.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
self.resource = User.find_by(email: resource_params[:email]&.downcase)
|
8
|
+
|
9
|
+
if resource&.confirmed?
|
10
|
+
resource.generate_password_reset_token!
|
11
|
+
UserMailer.reset_password_instructions(resource).deliver_now
|
12
|
+
end
|
13
|
+
|
14
|
+
set_flash_message!(:notice, :send_instructions)
|
15
|
+
redirect_to new_user_session_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def edit
|
19
|
+
self.resource = User.find_by(reset_password_token: params[:reset_password_token])
|
20
|
+
|
21
|
+
if resource.nil? || !resource.reset_password_period_valid?
|
22
|
+
set_flash_message!(:alert, :expired)
|
23
|
+
redirect_to new_user_password_path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def update
|
28
|
+
self.resource = User.find_by(reset_password_token: params[:user][:reset_password_token])
|
29
|
+
|
30
|
+
if resource&.reset_password_period_valid?
|
31
|
+
if resource.reset_password!(resource_params[:password], resource_params[:password_confirmation])
|
32
|
+
sign_in(resource)
|
33
|
+
set_flash_message!(:notice, :updated)
|
34
|
+
redirect_to after_sign_in_path_for(resource)
|
35
|
+
else
|
36
|
+
render :edit, status: :unprocessable_entity
|
37
|
+
end
|
38
|
+
else
|
39
|
+
set_flash_message!(:alert, :expired)
|
40
|
+
redirect_to new_user_password_path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def resource_params
|
47
|
+
params.require(:user).permit(:email, :password, :password_confirmation, :reset_password_token)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_flash_message!(type, key, options = {})
|
51
|
+
message = find_message(key, options)
|
52
|
+
flash[type] = message if message.present?
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_message(key, options = {})
|
56
|
+
I18n.t("lockify.passwords.#{key}", **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_accessor :resource
|
62
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
class Users::RegistrationsController < ApplicationController
|
2
|
+
before_action :configure_sign_up_params, only: [:create]
|
3
|
+
before_action :configure_account_update_params, only: [:update]
|
4
|
+
|
5
|
+
def new
|
6
|
+
build_resource
|
7
|
+
redirect_to after_sign_in_path_for(resource) if user_signed_in?
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
build_resource(sign_up_params)
|
12
|
+
|
13
|
+
if resource.save
|
14
|
+
if resource.confirmation_required?
|
15
|
+
resource.send_confirmation_instructions
|
16
|
+
set_flash_message!(:notice, :signed_up_but_unconfirmed)
|
17
|
+
redirect_to new_user_session_path
|
18
|
+
else
|
19
|
+
sign_in(resource)
|
20
|
+
set_flash_message!(:notice, :signed_up)
|
21
|
+
respond_with resource, location: after_sign_up_path_for(resource)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
clean_up_passwords resource
|
25
|
+
render :new, status: :unprocessable_entity
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def show
|
30
|
+
redirect_to edit_user_registration_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def edit
|
34
|
+
authenticate_user!
|
35
|
+
self.resource = current_user
|
36
|
+
end
|
37
|
+
|
38
|
+
def update
|
39
|
+
authenticate_user!
|
40
|
+
self.resource = current_user
|
41
|
+
|
42
|
+
if update_resource(resource, account_update_params)
|
43
|
+
bypass_sign_in resource, scope: :user
|
44
|
+
set_flash_message!(:notice, :updated)
|
45
|
+
redirect_to after_update_path_for(resource)
|
46
|
+
else
|
47
|
+
clean_up_passwords resource
|
48
|
+
render :edit, status: :unprocessable_entity
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy
|
53
|
+
authenticate_user!
|
54
|
+
resource = current_user
|
55
|
+
resource.destroy
|
56
|
+
sign_out(:user)
|
57
|
+
set_flash_message!(:notice, :destroyed)
|
58
|
+
redirect_to after_sign_out_path_for(:user)
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def build_resource(hash = {})
|
64
|
+
self.resource = User.new(hash)
|
65
|
+
end
|
66
|
+
|
67
|
+
def sign_up_params
|
68
|
+
params.require(:user).permit(:email, :password, :password_confirmation)
|
69
|
+
end
|
70
|
+
|
71
|
+
def account_update_params
|
72
|
+
params.require(:user).permit(:email, :password, :password_confirmation, :current_password)
|
73
|
+
end
|
74
|
+
|
75
|
+
def configure_sign_up_params
|
76
|
+
# Override in application if needed
|
77
|
+
end
|
78
|
+
|
79
|
+
def configure_account_update_params
|
80
|
+
# Override in application if needed
|
81
|
+
end
|
82
|
+
|
83
|
+
def after_sign_up_path_for(resource)
|
84
|
+
after_sign_in_path_for(resource)
|
85
|
+
end
|
86
|
+
|
87
|
+
def after_update_path_for(resource)
|
88
|
+
edit_user_registration_path
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_resource(resource, params)
|
92
|
+
if params[:password].present?
|
93
|
+
resource.update(params)
|
94
|
+
else
|
95
|
+
params.delete(:password)
|
96
|
+
params.delete(:password_confirmation)
|
97
|
+
resource.update_without_password(params)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def clean_up_passwords(resource)
|
102
|
+
resource.password = nil
|
103
|
+
resource.password_confirmation = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def bypass_sign_in(resource, options = {})
|
107
|
+
sign_in(resource, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def respond_with(resource, _opts = {})
|
111
|
+
redirect_to after_sign_up_path_for(resource)
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_flash_message!(type, key, options = {})
|
115
|
+
message = find_message(key, options)
|
116
|
+
flash[type] = message if message.present?
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_message(key, options = {})
|
120
|
+
I18n.t("lockify.registrations.#{key}", **options)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
attr_accessor :resource
|
126
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Users::SessionsController < ApplicationController
|
2
|
+
before_action :configure_sign_in_params, only: [:create]
|
3
|
+
before_action :authenticate_user!, only: [:destroy]
|
4
|
+
|
5
|
+
def new
|
6
|
+
redirect_to after_sign_in_path_for(:user) if user_signed_in?
|
7
|
+
end
|
8
|
+
|
9
|
+
def create
|
10
|
+
self.resource = User.find_by(email: sign_in_params[:email]&.downcase)
|
11
|
+
|
12
|
+
if resource&.authenticate(sign_in_params[:password])
|
13
|
+
if resource.valid_for_authentication?
|
14
|
+
resource.failed_attempts = 0 if resource.respond_to?(:failed_attempts=)
|
15
|
+
resource.save(validate: false) if resource.respond_to?(:failed_attempts)
|
16
|
+
|
17
|
+
sign_in(resource, remember_me: sign_in_params[:remember_me] == '1')
|
18
|
+
respond_with resource, location: after_sign_in_path_for(resource)
|
19
|
+
else
|
20
|
+
set_flash_message!(:alert, :unconfirmed) unless resource.confirmed?
|
21
|
+
set_flash_message!(:alert, :locked) if resource.access_locked?
|
22
|
+
redirect_to new_user_session_path
|
23
|
+
end
|
24
|
+
else
|
25
|
+
resource&.increment_failed_attempts
|
26
|
+
set_flash_message!(:alert, :invalid)
|
27
|
+
render :new, status: :unprocessable_entity
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
signed_out = (user_signed_in?)
|
33
|
+
sign_out(:user)
|
34
|
+
set_flash_message!(:notice, :signed_out) if signed_out
|
35
|
+
respond_to_on_destroy
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def sign_in_params
|
41
|
+
params.require(:user).permit(:email, :password, :remember_me)
|
42
|
+
end
|
43
|
+
|
44
|
+
def configure_sign_in_params
|
45
|
+
# Override in application if needed
|
46
|
+
end
|
47
|
+
|
48
|
+
def respond_with(resource, _opts = {})
|
49
|
+
redirect_to after_sign_in_path_for(resource)
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond_to_on_destroy
|
53
|
+
redirect_to after_sign_out_path_for(:user)
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_flash_message!(type, key, options = {})
|
57
|
+
message = find_message(key, options)
|
58
|
+
flash[type] = message if message.present?
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_message(key, options = {})
|
62
|
+
I18n.t("lockify.sessions.#{key}", **options)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_accessor :resource
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class UserMailer < ApplicationMailer
|
2
|
+
def confirmation_instructions(user)
|
3
|
+
@user = user
|
4
|
+
@confirmation_url = user_confirmation_url(confirmation_token: @user.confirmation_token)
|
5
|
+
|
6
|
+
mail(
|
7
|
+
to: @user.email,
|
8
|
+
subject: I18n.t('lockify.mailer.confirmation_instructions.subject')
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset_password_instructions(user)
|
13
|
+
@user = user
|
14
|
+
@reset_password_url = edit_user_password_url(reset_password_token: @user.reset_password_token)
|
15
|
+
|
16
|
+
mail(
|
17
|
+
to: @user.email,
|
18
|
+
subject: I18n.t('lockify.mailer.reset_password_instructions.subject')
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module LockifyUser
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_secure_password validations: false
|
6
|
+
|
7
|
+
validates :email, presence: true, uniqueness: true, format: { with: Lockify.email_regexp }
|
8
|
+
validates :password, length: Lockify.password_length, allow_nil: true, confirmation: true
|
9
|
+
|
10
|
+
before_create :generate_confirmation_token, if: :confirmation_required?
|
11
|
+
before_save :downcase_email
|
12
|
+
end
|
13
|
+
|
14
|
+
def confirmed?
|
15
|
+
!!confirmed_at
|
16
|
+
end
|
17
|
+
|
18
|
+
def confirmation_required?
|
19
|
+
respond_to?(:confirmation_token)
|
20
|
+
end
|
21
|
+
|
22
|
+
def confirmation_period_expired?
|
23
|
+
return false unless respond_to?(:confirmation_sent_at)
|
24
|
+
return false unless Lockify.confirm_within
|
25
|
+
confirmation_sent_at && confirmation_sent_at.utc <= Lockify.confirm_within.ago
|
26
|
+
end
|
27
|
+
|
28
|
+
def confirm!
|
29
|
+
if respond_to?(:confirmed_at=)
|
30
|
+
self.confirmed_at = Time.current
|
31
|
+
self.confirmation_token = nil if respond_to?(:confirmation_token=)
|
32
|
+
save(validate: false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_confirmation_instructions
|
37
|
+
generate_confirmation_token
|
38
|
+
save(validate: false)
|
39
|
+
UserMailer.confirmation_instructions(self).deliver_now
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_password_reset_token!
|
43
|
+
if respond_to?(:reset_password_token=)
|
44
|
+
self.reset_password_token = SecureRandom.urlsafe_base64
|
45
|
+
self.reset_password_sent_at = Time.current
|
46
|
+
save(validate: false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_password_period_valid?
|
51
|
+
return false unless respond_to?(:reset_password_sent_at)
|
52
|
+
reset_password_sent_at && reset_password_sent_at.utc >= Lockify.reset_password_within.ago
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset_password!(new_password, new_password_confirmation = nil)
|
56
|
+
self.password = new_password
|
57
|
+
self.password_confirmation = new_password_confirmation || new_password
|
58
|
+
|
59
|
+
if respond_to?(:reset_password_token=)
|
60
|
+
self.reset_password_token = nil
|
61
|
+
self.reset_password_sent_at = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
save
|
65
|
+
end
|
66
|
+
|
67
|
+
def remember_me!
|
68
|
+
if respond_to?(:remember_token=)
|
69
|
+
self.remember_token = SecureRandom.urlsafe_base64
|
70
|
+
self.remember_created_at = Time.current if respond_to?(:remember_created_at=)
|
71
|
+
save(validate: false)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def forget_me!
|
76
|
+
if respond_to?(:remember_token=)
|
77
|
+
self.remember_token = nil
|
78
|
+
self.remember_created_at = nil if respond_to?(:remember_created_at=)
|
79
|
+
save(validate: false)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def rememberable_value
|
84
|
+
remember_token if respond_to?(:remember_token)
|
85
|
+
end
|
86
|
+
|
87
|
+
def rememberable_options
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
|
91
|
+
def lock_access!
|
92
|
+
if respond_to?(:locked_at=)
|
93
|
+
self.locked_at = Time.current
|
94
|
+
save(validate: false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def unlock_access!
|
99
|
+
if respond_to?(:locked_at=)
|
100
|
+
self.locked_at = nil
|
101
|
+
self.failed_attempts = 0 if respond_to?(:failed_attempts=)
|
102
|
+
save(validate: false)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def access_locked?
|
107
|
+
return false unless respond_to?(:locked_at)
|
108
|
+
!!locked_at
|
109
|
+
end
|
110
|
+
|
111
|
+
def increment_failed_attempts
|
112
|
+
if respond_to?(:failed_attempts)
|
113
|
+
self.failed_attempts ||= 0
|
114
|
+
self.failed_attempts += 1
|
115
|
+
lock_access! if failed_attempts >= Lockify.maximum_attempts
|
116
|
+
save(validate: false)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def valid_for_authentication?
|
121
|
+
return false if respond_to?(:confirmed_at) && !confirmed?
|
122
|
+
return false if access_locked?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def generate_confirmation_token
|
129
|
+
if respond_to?(:confirmation_token=)
|
130
|
+
self.confirmation_token = SecureRandom.urlsafe_base64
|
131
|
+
self.confirmation_sent_at = Time.current if respond_to?(:confirmation_sent_at=)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def downcase_email
|
136
|
+
self.email = email.downcase if email.present?
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<h2><%= t('.resend_confirmation_instructions') %></h2>
|
2
|
+
|
3
|
+
<%= form_with(model: resource, as: :user, url: user_confirmation_path, local: true) do |f| %>
|
4
|
+
<%= render "lockify/shared/error_messages", resource: resource %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %>
|
8
|
+
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="actions">
|
12
|
+
<%= f.submit t('.resend_confirmation_instructions') %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= render "lockify/shared/links" %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<h2><%= t('.change_your_password') %></h2>
|
2
|
+
|
3
|
+
<%= form_with(model: resource, as: :user, url: user_password_path, local: true) do |f| %>
|
4
|
+
<%= render "lockify/shared/error_messages", resource: resource %>
|
5
|
+
<%= f.hidden_field :reset_password_token %>
|
6
|
+
|
7
|
+
<div class="field">
|
8
|
+
<%= f.label :password, t('.new_password') %>
|
9
|
+
<% if @minimum_password_length %>
|
10
|
+
<em>(<%= @minimum_password_length %> characters minimum)</em>
|
11
|
+
<% end %>
|
12
|
+
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="field">
|
16
|
+
<%= f.label :password_confirmation, t('.confirm_new_password') %>
|
17
|
+
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="actions">
|
21
|
+
<%= f.submit t('.change_my_password') %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<%= render "lockify/shared/links" %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<h2><%= t('.forgot_your_password') %></h2>
|
2
|
+
|
3
|
+
<%= form_with(model: resource, as: :user, url: user_password_path, local: true) do |f| %>
|
4
|
+
<%= render "lockify/shared/error_messages", resource: resource %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %>
|
8
|
+
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="actions">
|
12
|
+
<%= f.submit t('.send_me_reset_password_instructions') %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= render "lockify/shared/links" %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<h2><%= t('.sign_up') %></h2>
|
2
|
+
|
3
|
+
<%= form_with(model: resource, as: :user, url: user_registration_path, local: true) do |f| %>
|
4
|
+
<%= render "lockify/shared/error_messages", resource: resource %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %>
|
8
|
+
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="field">
|
12
|
+
<%= f.label :password %>
|
13
|
+
<% if @minimum_password_length %>
|
14
|
+
<em>(<%= @minimum_password_length %> characters minimum)</em>
|
15
|
+
<% end %>
|
16
|
+
<%= f.password_field :password, autocomplete: "new-password" %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="field">
|
20
|
+
<%= f.label :password_confirmation %>
|
21
|
+
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="actions">
|
25
|
+
<%= f.submit t('.sign_up') %>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
28
|
+
|
29
|
+
<%= render "lockify/shared/links" %>
|