authenticatable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +19 -0
- data/README.md +144 -0
- data/app/controllers/authenticatable/passwords_controller.rb +51 -0
- data/app/controllers/authenticatable/registrations_controller.rb +28 -0
- data/app/controllers/authenticatable/sessions_controller.rb +49 -0
- data/app/controllers/authenticatable_controller.rb +101 -0
- data/app/mailers/authenticatable/mailer.rb +17 -0
- data/app/views/authenticatable/mailer/reset_password.html.erb +8 -0
- data/app/views/authenticatable/passwords/_edit_form.html.erb +6 -0
- data/app/views/authenticatable/passwords/_new_form.html.erb +6 -0
- data/app/views/authenticatable/passwords/edit.html.erb +7 -0
- data/app/views/authenticatable/passwords/new.html.erb +7 -0
- data/app/views/authenticatable/registrations/_form.html.erb +12 -0
- data/app/views/authenticatable/registrations/new.html.erb +5 -0
- data/app/views/authenticatable/sessions/_form.html.erb +5 -0
- data/app/views/authenticatable/sessions/new.html.erb +7 -0
- data/app/views/authenticatable/shared/_errors.html.erb +7 -0
- data/app/views/authenticatable/shared/_flash_messages.html.erb +3 -0
- data/app/views/authenticatable/shared/_links.html.erb +12 -0
- data/config/locales/en.yml +14 -0
- data/lib/authenticatable/controllers/helpers.rb +72 -0
- data/lib/authenticatable/controllers/url_helpers.rb +67 -0
- data/lib/authenticatable/controllers.rb +9 -0
- data/lib/authenticatable/engine.rb +17 -0
- data/lib/authenticatable/errors/unauthenticated_error.rb +6 -0
- data/lib/authenticatable/errors.rb +6 -0
- data/lib/authenticatable/manager.rb +16 -0
- data/lib/authenticatable/models/email_validator.rb +17 -0
- data/lib/authenticatable/models/identifier.rb +43 -0
- data/lib/authenticatable/models/password.rb +73 -0
- data/lib/authenticatable/models.rb +67 -0
- data/lib/authenticatable/proxy.rb +103 -0
- data/lib/authenticatable/rails/routes.rb +61 -0
- data/lib/authenticatable/rspec.rb +8 -0
- data/lib/authenticatable/scope.rb +110 -0
- data/lib/authenticatable/serializers/base.rb +39 -0
- data/lib/authenticatable/serializers/session.rb +36 -0
- data/lib/authenticatable/serializers.rb +9 -0
- data/lib/authenticatable/testing/controller_helpers.rb +31 -0
- data/lib/authenticatable/token.rb +13 -0
- data/lib/authenticatable/version.rb +5 -0
- data/lib/authenticatable.rb +100 -0
- data/lib/generators/active_record/authenticatable_generator.rb +63 -0
- data/lib/generators/active_record/templates/migration.tt +15 -0
- data/lib/generators/active_record/templates/migration_existing.tt +23 -0
- data/lib/generators/authenticatable/authenticatable_generator.rb +18 -0
- data/lib/generators/authenticatable/orm_helpers.rb +30 -0
- data/lib/generators/authenticatable/views_generator.rb +19 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 907a194f62507d83bdc23c1cee102bf5486544907b880637ca570fbfc55d7f78
|
4
|
+
data.tar.gz: 88a89169c344fa7e23333e41cee068195ffcd0d295ec6709fdcf20a5255a9368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 721cd0363327e1d76350df5bc14d9e2f668638b512b5fded8b999146d4a5ded469f3be10e591086b0224c12ce82afd37ea5943ae8095e5a330156a50f2848199
|
7
|
+
data.tar.gz: f072a1baefee474141d1b002a59e872a4eef10b3d8e8058a035af5514bcd5e560d690bdff055b076f402eba5186b2c61247790ef8e04bf85e834d82a4532beae
|
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2021 The Authenticatable developers.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
Authenticatable
|
2
|
+
==============
|
3
|
+
[![RuboCop Github Action](https://github.com/kiqr/authenticatable/actions/workflows/rubocop.yml/badge.svg)](https://github.com/kiqr/authenticatable/actions/workflows/rubocop.yml)
|
4
|
+
[![RSpec](https://github.com/kiqr/authenticatable/actions/workflows/rspec.yml/badge.svg)](https://github.com/kiqr/authenticatable/actions/workflows/rspec.yml)
|
5
|
+
[![codecov](https://codecov.io/gh/kiqr/authenticatable/branch/main/graph/badge.svg?token=UZMGXQKJRL)](https://codecov.io/gh/kiqr/authenticatable)
|
6
|
+
[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
|
7
|
+
|
8
|
+
An authentication solution for Rails that works on top of Rails built-in [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html). Authenticatable ships with controllers, helpers, views and model generators. The purpose of Authenticatable is to enable you to kickstart a new Rails project without having to spend time on writing repetitive code that shouldn't have to be repeated in each project.
|
9
|
+
|
10
|
+
Authenticatable also ships with some **extra security features** that very often are forgotten by developers and the authors of the many available [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) tutorials:
|
11
|
+
- Protection against [sessions fixation attacks](https://guides.rubyonrails.org/security.html#session-fixation) by clearing the session_id on authentication.
|
12
|
+
- Protection against [cross-site request forgery (CSRF)](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) by cleaning up the CSRF Token on authentication.
|
13
|
+
- Protection against [timing/enumeration attacks](https://www.kaspersky.com/blog/username-enumeration-attack/34618/) by hashing the password even if a user record isn't found.
|
14
|
+
|
15
|
+
#### What's included?
|
16
|
+
- Controllers and views for authentication & registration with support for both [turbo](https://github.com/hotwired/turbo-rails) and [turbolinks](https://github.com/turbolinks/turbolinks).
|
17
|
+
- Support for multiple authenticatable models (like users/accounts/admins or whatever you want).
|
18
|
+
- Custom identifier column (sign in with for example username or phonenumber instead of email).
|
19
|
+
- Customizable: Easily override default behaviors with your own.
|
20
|
+
|
21
|
+
#### Supported Rails/Ruby versions
|
22
|
+
Tests run against Rails versions `~> 5.2.0`, `~> 6.0.0`, `~> 6.1.0` and `~> 7.0.0.alpha2` under Ruby versions `2.6`, `2.7` and `3.0`
|
23
|
+
|
24
|
+
Installation
|
25
|
+
------------
|
26
|
+
|
27
|
+
Add the following line to Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem "authenticatable", github: "kiqr/authenticatable" # Temporary use master as source before stable release.
|
31
|
+
```
|
32
|
+
|
33
|
+
and run `bundle install` from your terminal to install it.
|
34
|
+
|
35
|
+
After you've installed the gem, you can run the generator to create an initializer file that allows further configuration:
|
36
|
+
|
37
|
+
```console
|
38
|
+
$ rails g authenticatable:install
|
39
|
+
```
|
40
|
+
|
41
|
+
Getting started
|
42
|
+
---------------
|
43
|
+
|
44
|
+
The first step is to create an authenticatable model. To generate an authenticatable model run the following command:
|
45
|
+
|
46
|
+
```console
|
47
|
+
$ rails g authenticatable NAME
|
48
|
+
```
|
49
|
+
|
50
|
+
This will generate a model with the given ```NAME``` (if one does not exist) with configuration for authenticatable, a migration file and routes. The output should be something similar to:
|
51
|
+
|
52
|
+
```console
|
53
|
+
foo@bar:~$ rails g authenticatable user
|
54
|
+
Running via Spring preloader in process 99920
|
55
|
+
invoke active_record
|
56
|
+
create db/migrate/20210909215956_authenticatable_create_users.rb
|
57
|
+
create app/models/user.rb
|
58
|
+
invoke test_unit
|
59
|
+
create test/models/user_test.rb
|
60
|
+
create test/fixtures/users.yml
|
61
|
+
insert app/models/user.rb
|
62
|
+
route authenticatable :users
|
63
|
+
```
|
64
|
+
|
65
|
+
### Securing your application
|
66
|
+
|
67
|
+
To set up a controller with authentication, just add the before_action `authenticate_{scope}!`. To require a `User` to be signed in:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
before_action :authenticate_user! # Block unauthenticated requests to the current controller
|
71
|
+
```
|
72
|
+
|
73
|
+
To restrict your whole application to signed-in users, you can add the before_action above to your ```ApplicationController```.
|
74
|
+
|
75
|
+
#### Handling Authenticatable::UnauthenticatedError
|
76
|
+
|
77
|
+
The `Authenticatable::UnauthenticatedError` exception is raised when calling `:authenticate_user!` in the controller and no valid user is signed in. You can catch the exception and modify its behavior in the ApplicationController. The behavior may vary depending on the request format and user scope. For example here we set a flash error message and redirect to the sign in page for HTML requests and return 403 Forbidden for JSON requests.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class ApplicationController < ActionController::Base
|
81
|
+
before_action :authenticate_user!
|
82
|
+
rescue_from Authenticatable::UnauthenticatedError do
|
83
|
+
respond_to do |format|
|
84
|
+
format.json { head :forbidden }
|
85
|
+
format.html { redirect_to new_user_session_path, alert: "Please sign in to continue." }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Customizing
|
92
|
+
-----------
|
93
|
+
|
94
|
+
### Views
|
95
|
+
You probably want to (and should) modify the default Authenticatable views. To copy the views to your application run the following generator:
|
96
|
+
|
97
|
+
```console
|
98
|
+
$ rails g authenticatable:views
|
99
|
+
```
|
100
|
+
|
101
|
+
### Changing the model identifier
|
102
|
+
By default, a user is identified by `email` and password when logging in. But maybe you want your users to login using a `username` or something else instead of email. Luckely, that's easy done with Authenticatable:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class User < ApplicationRecord
|
106
|
+
authenticatable
|
107
|
+
|
108
|
+
# Identifying column to use when looking up an authenticatable record in the database.
|
109
|
+
# Can be for example email or a username. Default is email.
|
110
|
+
identifiy_by :username
|
111
|
+
|
112
|
+
# You need to create your own validator for your identifier column.
|
113
|
+
validates_presence_of :username
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Just make sure that the `username` column exists with a **unique index** in your database:
|
118
|
+
|
119
|
+
```shell
|
120
|
+
$ rails g migration add_username_to_users username:string:uniq
|
121
|
+
```
|
122
|
+
|
123
|
+
Contributing
|
124
|
+
------------
|
125
|
+
If you are interested in reporting/fixing issues and contributing directly to the code base, please see [CONTRIBUTING.md](CONTRIBUTING.md) for more information on what we're looking for and how to get started.
|
126
|
+
|
127
|
+
Versioning
|
128
|
+
----------
|
129
|
+
This library aims to adhere to [Semantic Versioning 2.0.0](http://semver.org/). Violations
|
130
|
+
of this scheme should be reported as bugs. Specifically, if a minor or patch
|
131
|
+
version is released that breaks backward compatibility, that version should be
|
132
|
+
immediately yanked and/or a new version should be immediately released that
|
133
|
+
restores compatibility. Breaking changes to the public API will only be
|
134
|
+
introduced with new major versions. As a result of this policy, you can (and
|
135
|
+
should) specify a dependency on this gem using the [Pessimistic Version
|
136
|
+
Constraint](http://guides.rubygems.org/patterns/#pessimistic-version-constraint) with two digits of precision. For example:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
gem "authenticatable", "~> 1.0"
|
140
|
+
```
|
141
|
+
|
142
|
+
License
|
143
|
+
-------
|
144
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authenticatable
|
4
|
+
class PasswordsController < AuthenticatableController
|
5
|
+
before_action :require_unauthenticated!
|
6
|
+
|
7
|
+
def new
|
8
|
+
@resource = resource_class.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
if (@resource = resource_class.find_by_identifier(identifier))
|
13
|
+
@resource&.reset_password_token!
|
14
|
+
Authenticatable::Mailer.reset_password(@resource, resource_name).deliver_now
|
15
|
+
end
|
16
|
+
|
17
|
+
set_flash_message(:notice, :create, { identifier_column: identifier_column })
|
18
|
+
redirect_to new_password_path(resource_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def edit
|
22
|
+
@resource = resource_class.find_by_reset_password_token(params[:token])
|
23
|
+
return unless @resource.nil?
|
24
|
+
|
25
|
+
set_flash_message(:alert, :invalid_token)
|
26
|
+
redirect_to new_password_path(resource_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
unless (@resource = resource_class.find_by_reset_password_token(params[:token]))
|
31
|
+
set_flash_message(:alert, :invalid_token)
|
32
|
+
return redirect_to new_password_path(resource_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
return render :edit, status: :unprocessable_entity unless @resource.update_password(edit_params)
|
36
|
+
|
37
|
+
set_flash_message(:notice, :updated)
|
38
|
+
redirect_to new_session_path(resource_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def edit_params
|
44
|
+
params.require(resource_name).permit(:password, :password_confirmation)
|
45
|
+
end
|
46
|
+
|
47
|
+
def identifier
|
48
|
+
params.dig(resource_name, resource_class.identifier_column.to_s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authenticatable
|
4
|
+
class RegistrationsController < AuthenticatableController
|
5
|
+
before_action :require_unauthenticated!
|
6
|
+
|
7
|
+
def new
|
8
|
+
@resource = resource_class.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
@resource = resource_class.new(create_params)
|
13
|
+
if @resource.save
|
14
|
+
set_flash_message(:notice, :success)
|
15
|
+
redirect_to new_session_path(resource_name)
|
16
|
+
else
|
17
|
+
render :new, status: :unprocessable_entity
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Strong parameters for the create action (sign in).
|
24
|
+
def create_params
|
25
|
+
params.require(resource_name).permit(:username, :email, :password, :password_confirmation)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authenticatable
|
4
|
+
class SessionsController < AuthenticatableController
|
5
|
+
before_action :require_unauthenticated!, except: :destroy
|
6
|
+
|
7
|
+
def new
|
8
|
+
@resource = resource_class.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
@resource = sign_in(params, :session)
|
13
|
+
if signed_in?
|
14
|
+
set_flash_message(:notice, :success, { identifier: @resource.identifier })
|
15
|
+
redirect_to root_path
|
16
|
+
else
|
17
|
+
set_flash_message(:alert, :invalid_credentials, { identifier: identifier_label.downcase })
|
18
|
+
redirect_to new_session_path(resource_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
@resource = authenticatable.authenticate!(resource_name)
|
24
|
+
sign_out(@resource, :session) unless @resource.nil?
|
25
|
+
set_flash_message(:notice, :destroy)
|
26
|
+
redirect_to new_user_session_path
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def signed_in?
|
32
|
+
!!authenticatable.authenticate(resource_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Authenticate and sign in a user with a provided identifier and password
|
36
|
+
def sign_in(params, serializer)
|
37
|
+
identifier = params.dig(resource_name, resource_class.identifier_column.to_s)
|
38
|
+
password = params.dig(resource_name, "password")
|
39
|
+
record = resource_class.authenticate_with_identifier(identifier, password)
|
40
|
+
authenticatable.sign_in(record, serializer) unless record.nil?
|
41
|
+
record || nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sign out the passed resource.
|
45
|
+
def sign_out(resource, serializer)
|
46
|
+
authenticatable.sign_out(resource, serializer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AuthenticatableController < Authenticatable.config.parent_controller.constantize
|
4
|
+
helper_method :authenticatable_scope, :resource, :resource_name, :resource_class,
|
5
|
+
:identifier_column, :identifier_label
|
6
|
+
|
7
|
+
# Make @resource available in views without @ prefix.
|
8
|
+
# Example:
|
9
|
+
# resource => @resource
|
10
|
+
attr_reader :resource
|
11
|
+
|
12
|
+
# Convinience method to access request.env["authenticatable"]
|
13
|
+
# Example:
|
14
|
+
# authenticatable.sign_in() => request.env["authenticatable"].sign_in()
|
15
|
+
def authenticatable
|
16
|
+
@authenticatable ||= request.env["authenticatable"]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Attempt to find the scope for authenticatable based
|
20
|
+
# on env variable set by router constraint.
|
21
|
+
def authenticatable_scope
|
22
|
+
@authenticatable_scope ||= request.env["authenticatable.scope"]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the resource_name from current scope.
|
26
|
+
# Example:
|
27
|
+
# resource_name => user
|
28
|
+
def resource_name
|
29
|
+
authenticatable_scope.singular_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the resource_name from current scope.
|
33
|
+
# Example:
|
34
|
+
# resource_class => User
|
35
|
+
def resource_class
|
36
|
+
authenticatable_scope.klass
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the choosen identifier column for the
|
40
|
+
# resource in the current scope.
|
41
|
+
# Example:
|
42
|
+
# identifier_column => :email
|
43
|
+
delegate :identifier_column, to: :resource_class
|
44
|
+
|
45
|
+
# Returns the choosen identifier column as string.
|
46
|
+
# Example:
|
47
|
+
# identifier_label => Email
|
48
|
+
def identifier_label
|
49
|
+
identifier_column.to_s.titleize
|
50
|
+
end
|
51
|
+
|
52
|
+
# Redirect to after_signed_in_path if authenticated
|
53
|
+
def require_unauthenticated!
|
54
|
+
redirect_to after_sign_in_path(resource, resource_name) if authenticatable.authenticate(resource_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
# Sets the flash message with :key, using I18n. By default you are able
|
60
|
+
# to set up your messages using specific resource scope, and if no message is
|
61
|
+
# found we look to the default scope. Set the "now" options key to a true
|
62
|
+
# value to populate the flash.now hash in lieu of the default flash hash (so
|
63
|
+
# the flash message will be available to the current action instead of the
|
64
|
+
# next action).
|
65
|
+
def set_flash_message(key, kind, options = {})
|
66
|
+
return unless flashing_format?
|
67
|
+
|
68
|
+
message = find_message(kind, options)
|
69
|
+
return if message.blank?
|
70
|
+
|
71
|
+
if options[:now]
|
72
|
+
flash.now[key] = message
|
73
|
+
else
|
74
|
+
flash[key] = message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Find message in I18n translation files.
|
79
|
+
def find_message(kind, options = {})
|
80
|
+
options[:scope] ||= translation_scope
|
81
|
+
I18n.t(kind, **options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Default translation scope for messages
|
85
|
+
def translation_scope
|
86
|
+
"authenticatable.#{controller_name}"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check if flash messages should be emitted. Default is to do it on
|
90
|
+
# navigational formats
|
91
|
+
def flashing_format?
|
92
|
+
request.respond_to?(:flash) && navigational_format?
|
93
|
+
end
|
94
|
+
|
95
|
+
# Check if current request format is navigational. Formats like
|
96
|
+
# :html & :turbo_stream should redirect and show flash messages,
|
97
|
+
# but formats like :xml or :json, should return 401
|
98
|
+
def navigational_format?
|
99
|
+
Authenticatable.config.navigational_formats.include?(request.format.try(:ref))
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authenticatable
|
4
|
+
class Mailer < Authenticatable.config.parent_mailer.constantize
|
5
|
+
include Authenticatable::Controllers::UrlHelpers
|
6
|
+
|
7
|
+
def reset_password(resource, resource_name)
|
8
|
+
@resource = resource
|
9
|
+
@reset_password_url = edit_password_url(resource_name, @resource.reset_password_token)
|
10
|
+
|
11
|
+
mail(
|
12
|
+
to: @resource.email,
|
13
|
+
subject: I18n.t("authenticatable.passwords.email_subject")
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<p>Hello <%= @resource.email %>!</p>
|
2
|
+
|
3
|
+
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
|
4
|
+
|
5
|
+
<p><%= link_to 'Change my password', @reset_password_url %></p>
|
6
|
+
|
7
|
+
<p>If you didn't request this, please ignore this email.</p>
|
8
|
+
<p>Your password won't change until you access the link above and create a new one.</p>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<%= form_for resource, url: update_password_path(resource_name, params[:token]), method: :patch do |f| %>
|
2
|
+
<%= render 'authenticatable/shared/errors', resource: resource %>
|
3
|
+
<%= f.password_field :password, placeholder: "Password" %><br>
|
4
|
+
<%= f.password_field :password_confirmation, placeholder: "Confirm your password" %><br>
|
5
|
+
<%= f.submit "Reset password" %>
|
6
|
+
<% end %>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<%= form_for resource, url: password_path(resource_name), method: :create do |f| %>
|
2
|
+
<%= render 'authenticatable/shared/errors', resource: resource %>
|
3
|
+
|
4
|
+
<%= f.text_field identifier_column, placeholder: identifier_label %>
|
5
|
+
<%= f.submit 'Reset password' %>
|
6
|
+
<% end %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= form_for resource, url: registration_path(resource_name) do |f| %>
|
2
|
+
<%= render 'authenticatable/shared/errors', resource: resource %>
|
3
|
+
|
4
|
+
<% unless identifier_column == :email %>
|
5
|
+
<%= f.text_field identifier_column, placeholder: identifier_label %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<%= f.text_field :email, placeholder: 'Email' %>
|
9
|
+
<%= f.password_field :password, placeholder: 'Password' %>
|
10
|
+
<%= f.password_field :password_confirmation, placeholder: 'Confirm your password' %>
|
11
|
+
<%= f.submit 'Sign up' %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% if authenticatable_scope.registrations? && controller_name != 'registrations' %>
|
2
|
+
Don't have an account? <%= link_to 'Sign up', new_registration_path(resource_name) %><br>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<% if authenticatable_scope.sessions? && controller_name != 'sessions' %>
|
6
|
+
Already have an account? <%= link_to 'Sign in', new_session_path(resource_name) %><br>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<% if authenticatable_scope.passwords? && controller_name != 'passwords' %>
|
10
|
+
<%= link_to 'Forgot your password?', new_password_path(resource_name) %><br>
|
11
|
+
<% end %>
|
12
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
authenticatable:
|
3
|
+
sessions:
|
4
|
+
success: "Welcome back, %{identifier}"
|
5
|
+
invalid_credentials: "Invalid %{identifier} or password."
|
6
|
+
destroy: You've successfully signed out.
|
7
|
+
destroy_failed: Something went wrong. Couldn't sign you out.
|
8
|
+
registrations:
|
9
|
+
success: You've successfully signed up!
|
10
|
+
passwords:
|
11
|
+
create: We've sent you an email with instructions on how to reset your password.
|
12
|
+
email_subject: Reset your password.
|
13
|
+
invalid_token: Your reset password token has expired. Please request a new one to reset your password.
|
14
|
+
updated: Your password was successfully updated. Sign in with your new password below.
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Authenticatable
|
6
|
+
module Controllers
|
7
|
+
module Helpers
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include Authenticatable::Controllers::UrlHelpers
|
10
|
+
|
11
|
+
# Check if the current controller is a AuthenticatableController
|
12
|
+
def authenticatable_controller?
|
13
|
+
is_a?(::AuthenticatableController)
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Dynamically define helpers methods for the given scope
|
18
|
+
# For example, current_user, authenticate_user! and user_signed_in? will
|
19
|
+
# be generated for an authenticatable User model.
|
20
|
+
def define_helpers(scope)
|
21
|
+
scope = scope.to_s
|
22
|
+
|
23
|
+
define_current_helper(scope)
|
24
|
+
define_signed_in_helper(scope)
|
25
|
+
define_authenticate_helper(scope)
|
26
|
+
|
27
|
+
# Make current_{scope} and {scope}_signed_in? available as helpers in views.
|
28
|
+
ActiveSupport.on_load(:action_controller) do
|
29
|
+
helpers = "current_#{scope}", "#{scope}_signed_in?"
|
30
|
+
helper_method helpers if respond_to?(:helper_method)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_current_helper(scope)
|
35
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
36
|
+
# def current_user
|
37
|
+
# @current_user ||= request.env["authenticatable"].authenticate(:user)
|
38
|
+
# end
|
39
|
+
|
40
|
+
def current_#{scope}
|
41
|
+
@current_#{scope} ||= request.env["authenticatable"].authenticate(:#{scope})
|
42
|
+
end
|
43
|
+
METHOD
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_signed_in_helper(scope)
|
47
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
48
|
+
# def user_signed_in?
|
49
|
+
# !!current_user
|
50
|
+
# end
|
51
|
+
|
52
|
+
def #{scope}_signed_in?
|
53
|
+
!!current_#{scope}
|
54
|
+
end
|
55
|
+
METHOD
|
56
|
+
end
|
57
|
+
|
58
|
+
def define_authenticate_helper(scope)
|
59
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
60
|
+
# def authenticate_user!
|
61
|
+
# request.env["authenticatable"].authenticate!(:user) unless authenticatable_controller?
|
62
|
+
# end
|
63
|
+
|
64
|
+
def authenticate_#{scope}!
|
65
|
+
request.env["authenticatable"].authenticate!(:#{scope}) unless authenticatable_controller?
|
66
|
+
end
|
67
|
+
METHOD
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|