authenticatable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +19 -0
  3. data/README.md +144 -0
  4. data/app/controllers/authenticatable/passwords_controller.rb +51 -0
  5. data/app/controllers/authenticatable/registrations_controller.rb +28 -0
  6. data/app/controllers/authenticatable/sessions_controller.rb +49 -0
  7. data/app/controllers/authenticatable_controller.rb +101 -0
  8. data/app/mailers/authenticatable/mailer.rb +17 -0
  9. data/app/views/authenticatable/mailer/reset_password.html.erb +8 -0
  10. data/app/views/authenticatable/passwords/_edit_form.html.erb +6 -0
  11. data/app/views/authenticatable/passwords/_new_form.html.erb +6 -0
  12. data/app/views/authenticatable/passwords/edit.html.erb +7 -0
  13. data/app/views/authenticatable/passwords/new.html.erb +7 -0
  14. data/app/views/authenticatable/registrations/_form.html.erb +12 -0
  15. data/app/views/authenticatable/registrations/new.html.erb +5 -0
  16. data/app/views/authenticatable/sessions/_form.html.erb +5 -0
  17. data/app/views/authenticatable/sessions/new.html.erb +7 -0
  18. data/app/views/authenticatable/shared/_errors.html.erb +7 -0
  19. data/app/views/authenticatable/shared/_flash_messages.html.erb +3 -0
  20. data/app/views/authenticatable/shared/_links.html.erb +12 -0
  21. data/config/locales/en.yml +14 -0
  22. data/lib/authenticatable/controllers/helpers.rb +72 -0
  23. data/lib/authenticatable/controllers/url_helpers.rb +67 -0
  24. data/lib/authenticatable/controllers.rb +9 -0
  25. data/lib/authenticatable/engine.rb +17 -0
  26. data/lib/authenticatable/errors/unauthenticated_error.rb +6 -0
  27. data/lib/authenticatable/errors.rb +6 -0
  28. data/lib/authenticatable/manager.rb +16 -0
  29. data/lib/authenticatable/models/email_validator.rb +17 -0
  30. data/lib/authenticatable/models/identifier.rb +43 -0
  31. data/lib/authenticatable/models/password.rb +73 -0
  32. data/lib/authenticatable/models.rb +67 -0
  33. data/lib/authenticatable/proxy.rb +103 -0
  34. data/lib/authenticatable/rails/routes.rb +61 -0
  35. data/lib/authenticatable/rspec.rb +8 -0
  36. data/lib/authenticatable/scope.rb +110 -0
  37. data/lib/authenticatable/serializers/base.rb +39 -0
  38. data/lib/authenticatable/serializers/session.rb +36 -0
  39. data/lib/authenticatable/serializers.rb +9 -0
  40. data/lib/authenticatable/testing/controller_helpers.rb +31 -0
  41. data/lib/authenticatable/token.rb +13 -0
  42. data/lib/authenticatable/version.rb +5 -0
  43. data/lib/authenticatable.rb +100 -0
  44. data/lib/generators/active_record/authenticatable_generator.rb +63 -0
  45. data/lib/generators/active_record/templates/migration.tt +15 -0
  46. data/lib/generators/active_record/templates/migration_existing.tt +23 -0
  47. data/lib/generators/authenticatable/authenticatable_generator.rb +18 -0
  48. data/lib/generators/authenticatable/orm_helpers.rb +30 -0
  49. data/lib/generators/authenticatable/views_generator.rb +19 -0
  50. 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,7 @@
1
+ <h2>Reset your password</h2>
2
+
3
+ <%= render 'authenticatable/shared/flash_messages' %>
4
+
5
+ <%= render 'edit_form' %>
6
+
7
+ <%= render 'authenticatable/shared/links' %>
@@ -0,0 +1,7 @@
1
+ <h2>Reset your password</h2>
2
+
3
+ <%= render 'authenticatable/shared/flash_messages' %>
4
+
5
+ <%= render 'new_form' %>
6
+
7
+ <%= render 'authenticatable/shared/links' %>
@@ -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,5 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <%= render 'form' %>
4
+
5
+ <%= render 'authenticatable/shared/links' %>
@@ -0,0 +1,5 @@
1
+ <%= form_for resource, url: session_path(resource_name) do |f| %>
2
+ <%= f.text_field identifier_column, placeholder: identifier_label %>
3
+ <%= f.password_field :password, placeholder: 'Password' %>
4
+ <%= f.submit 'Sign in' %>
5
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <h2>Sign in</h2>
2
+
3
+ <%= render 'authenticatable/shared/flash_messages' %>
4
+
5
+ <%= render 'form' %>
6
+
7
+ <%= render 'authenticatable/shared/links' %>
@@ -0,0 +1,7 @@
1
+ <% if resource.errors.any? %>
2
+ <ul>
3
+ <% resource.errors.full_messages.each do |message| %>
4
+ <li><%= message %></li>
5
+ <% end %>
6
+ </ul>
7
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% flash.each do |key, value| %>
2
+ <%= content_tag :div, value, class: "flash #{key}" %>
3
+ <% 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