active_authentication 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +120 -0
  3. data/Rakefile +8 -0
  4. data/app/controllers/active_authentication/confirmations_controller.rb +29 -0
  5. data/app/controllers/active_authentication/passwords_controller.rb +37 -0
  6. data/app/controllers/active_authentication/registrations_controller.rb +42 -0
  7. data/app/controllers/active_authentication/sessions_controller.rb +36 -0
  8. data/app/controllers/active_authentication/unlocks_controller.rb +29 -0
  9. data/app/mailers/active_authentication/mailer.rb +21 -0
  10. data/app/views/active_authentication/confirmations/new.html.erb +14 -0
  11. data/app/views/active_authentication/mailer/email_confirmation_instructions.html.erb +5 -0
  12. data/app/views/active_authentication/mailer/password_reset_instructions.html.erb +5 -0
  13. data/app/views/active_authentication/mailer/unlock_instructions.html.erb +5 -0
  14. data/app/views/active_authentication/passwords/edit.html.erb +28 -0
  15. data/app/views/active_authentication/passwords/new.html.erb +14 -0
  16. data/app/views/active_authentication/registrations/edit.html.erb +42 -0
  17. data/app/views/active_authentication/registrations/new.html.erb +35 -0
  18. data/app/views/active_authentication/sessions/new.html.erb +19 -0
  19. data/app/views/active_authentication/shared/_links.html.erb +19 -0
  20. data/app/views/active_authentication/unlocks/new.html.erb +14 -0
  21. data/config/i18n-tasks.yml +159 -0
  22. data/config/locales/en.yml +90 -0
  23. data/config/locales/es.yml +90 -0
  24. data/lib/active_authentication/controller/lockable.rb +31 -0
  25. data/lib/active_authentication/controller/trackable.rb +17 -0
  26. data/lib/active_authentication/controller.rb +48 -0
  27. data/lib/active_authentication/current.rb +5 -0
  28. data/lib/active_authentication/engine.rb +16 -0
  29. data/lib/active_authentication/model/authenticatable.rb +16 -0
  30. data/lib/active_authentication/model/confirmable.rb +54 -0
  31. data/lib/active_authentication/model/lockable.rb +42 -0
  32. data/lib/active_authentication/model/recoverable.rb +16 -0
  33. data/lib/active_authentication/model/registerable.rb +7 -0
  34. data/lib/active_authentication/model/trackable.rb +23 -0
  35. data/lib/active_authentication/model.rb +21 -0
  36. data/lib/active_authentication/routes.rb +34 -0
  37. data/lib/active_authentication/test/helpers.rb +13 -0
  38. data/lib/active_authentication/version.rb +3 -0
  39. data/lib/active_authentication.rb +42 -0
  40. data/lib/generators/active_authentication/install/install_generator.rb +53 -0
  41. data/lib/generators/active_authentication/install/templates/initializer.rb +14 -0
  42. data/lib/generators/active_authentication/install/templates/migration.rb +26 -0
  43. data/lib/generators/active_authentication/views/views_generator.rb +17 -0
  44. metadata +116 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8b956c6e7b16a3f007c97efaf865bd070897b57fcef56be4f6a8615c6e83b37
4
+ data.tar.gz: 365972997162dcbc741186117bcb43fc45c04338ce3d4c8dd7562f9ac1d9a6ea
5
+ SHA512:
6
+ metadata.gz: f3e34c5159ff44130cd296ece54c3e5ba02158f247775f0a1af856c70efdd2369bf20ccf522d5cb1dc2ec76e7f2bc37b488f5117c1f93d6cf5987a6adc9354e6
7
+ data.tar.gz: b7bb7d568d98831ee2d18309da36ba125ee0f2a1aa76197c2e2d37aa66d78febfe5368f63f62819802907e7aba73a205e6db6255a519a4ca96c56c29916a858c
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # ActiveAuthentication
2
+
3
+ A pure Rails authentication solution.
4
+
5
+ ## Main features
6
+
7
+ * Pure Rails implementation, uses [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password), [generates_token_for](https://api.rubyonrails.org/classes/ActiveRecord/TokenFor/ClassMethods.html#method-i-generates_token_for), [find_by_token_for](https://api.rubyonrails.org/classes/ActiveRecord/TokenFor/ClassMethods.html#method-i-find_by_token_for) and [authenticate_by](https://api.rubyonrails.org/classes/ActiveRecord/SecurePassword/ClassMethods.html#method-i-authenticate_by).
8
+ * ActiveAuthentication authenticates users and only users. If you need to authenticate other models you should be asking yourself if you shouldn't handle authorization differently.
9
+ * Turn on/off the features you need by using concerns.
10
+
11
+ ### Concerns
12
+
13
+ * Authenticatable: provides the standard email/password authentication. It's the only concern that can't be turned off.
14
+ * Confirmable: allows users to confirm their email addresses.
15
+ * Lockable: locks users after a number of failed sign in attempts.
16
+ * Recoverable: allows users to reset their password.
17
+ * Registerable: allows users to sign up and edit their profile.
18
+ * Trackable: tracks users sign in count, timestamps and ip addresses.
19
+
20
+ Planned concerns:
21
+
22
+ * MagicLinkable: to allow users to sign in with a magic link.
23
+ * Omniauthable: to allow users to sign up and sign in using a third party service through Omniauth.
24
+ * Timeoutable: to expire sessions after a period of inactivity.
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem "active_authentication"
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ ```bash
37
+ $ bundle
38
+ ```
39
+
40
+ Or install it yourself as:
41
+
42
+ ```bash
43
+ $ gem install active_authentication
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ After installing the gem, you need to generate the `User` model. To generate it, run:
49
+
50
+ ```bash
51
+ $ rails generate active_authentication:install
52
+ ```
53
+
54
+ This command will generate the `User` model, add the `active_authentication` route, and generate an initializer (`config/initializers/active_authentication.rb`) where you can configure the concerns.
55
+
56
+ You will need to set up the default url options in your `config/environments/development.rb`:
57
+
58
+ ```ruby
59
+ config.action_mailer.default_url_options = {host: "localhost", port: 3000}
60
+ ```
61
+
62
+ And the `root` path in `config/routes.rb`.
63
+
64
+ Finally, run `rails db:migrate`.
65
+
66
+ ### Concerns
67
+
68
+ If you look at the `User` model (in `app/models/user.rb`), you will notice there's only a sentence:
69
+
70
+ ```ruby
71
+ class User < ApplicationRecord
72
+ authenticates_with :confirmable, :lockable, :recoverable, :registerable, :trackable
73
+ end
74
+ ```
75
+
76
+ Notice that `:authenticatable` is not in the list. This is because you cannot turn it off.
77
+
78
+ By default, all concerns are turned on. But you can turn them off by just removing them from the list. If you plan to not use any concerns, you can replace `authenticates_with` with `authenticates`.
79
+
80
+ ### Filters and helpers
81
+
82
+ ActiveAuthentication comes with filters and helpers you can use in your controllers and views.
83
+
84
+ To protect actions from being accessed by unauthenticated users, use the `authenticate_user!` filter:
85
+
86
+ ```ruby
87
+ before_action :authenticate_user!
88
+ ```
89
+
90
+ Then, to verify if there's an authenticated user, you can use the `user_signed_in?` helper.
91
+
92
+ Similarly, you can use `current_user` to access the current authenticated user.
93
+
94
+ ## Customization
95
+
96
+ ### Concerns configuration
97
+
98
+ When you run the `active_authentication:install` generator, an initializer will be copied to your app at `config/initializers/active_authentication.rb`. There's a section per concern where you can configure certain aspects of their behavior.
99
+
100
+ ### Views
101
+
102
+ The default views are good enough to get you started, but you'll want to customize them sooner than later. To copy the default views into your app, run the following command:
103
+
104
+ ```bash
105
+ $ rails generate active_authentication:views
106
+ ```
107
+
108
+ If you're not using all the concerns, you might want to copy only the views you need. To do that, you can use the `--views` (`-v`) option:
109
+
110
+ ```bash
111
+ $ rails generate active_authentication:views -v sessions
112
+ ```
113
+
114
+ ## Contributing
115
+
116
+ You can open an issue or a PR in GitHub.
117
+
118
+ ## License
119
+
120
+ 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,8 @@
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"
@@ -0,0 +1,29 @@
1
+ class ActiveAuthentication::ConfirmationsController < ApplicationController
2
+ before_action :require_no_authentication, except: :show
3
+ before_action :set_user, only: :show
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ @user = User.find_by unconfirmed_email: params[:email]
10
+ @user&.send_email_confirmation_instructions
11
+
12
+ redirect_to root_path, notice: t(".success")
13
+ end
14
+
15
+ def show
16
+ @user.confirm
17
+ sign_in(@user) unless user_signed_in?
18
+
19
+ redirect_to root_url, notice: t(".success")
20
+ end
21
+
22
+ private
23
+
24
+ def set_user
25
+ @user = User.find_by_token_for :email_confirmation, params[:token]
26
+
27
+ redirect_to root_url, alert: t(".invalid_token") unless @user.present?
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ class ActiveAuthentication::PasswordsController < ApplicationController
2
+ before_action :require_no_authentication
3
+ before_action :set_user, only: [:edit, :update]
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ @user = User.find_by email: params[:email]
10
+ @user&.send_password_reset_instructions
11
+ redirect_to root_url, notice: t(".success")
12
+ end
13
+
14
+ def edit
15
+ end
16
+
17
+ def update
18
+ if @user.update(user_params)
19
+ sign_in @user
20
+ redirect_to root_url, notice: t(".success")
21
+ else
22
+ render :edit, status: :unprocessable_entity
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def set_user
29
+ @user = User.find_by_token_for :password_reset, params[:token]
30
+
31
+ redirect_to root_url, alert: t(".invalid_token") unless @user.present?
32
+ end
33
+
34
+ def user_params
35
+ params.require(:user).permit(:password, :password_confirmation)
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ class ActiveAuthentication::RegistrationsController < ApplicationController
2
+ before_action :require_no_authentication, only: [:new, :create]
3
+ before_action :authenticate_user!, only: [:edit, :update, :destroy]
4
+
5
+ def new
6
+ @user = User.new
7
+ end
8
+
9
+ def create
10
+ @user = User.new user_params
11
+
12
+ if @user.save
13
+ sign_in @user
14
+ redirect_to root_path, notice: t(".success")
15
+ else
16
+ render :new, status: :unprocessable_entity
17
+ end
18
+ end
19
+
20
+ def edit
21
+ end
22
+
23
+ def update
24
+ if current_user.update(user_params)
25
+ redirect_to edit_profile_path, notice: t(".success")
26
+ else
27
+ render :edit, status: :unprocessable_entity
28
+ end
29
+ end
30
+
31
+ def destroy
32
+ current_user.destroy
33
+ sign_out
34
+ redirect_to root_url, notice: t(".success")
35
+ end
36
+
37
+ private
38
+
39
+ def user_params
40
+ params.require(:user).permit(:email, :password, :password_confirmation)
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ class ActiveAuthentication::SessionsController < ApplicationController
2
+ include ActiveSupport::Callbacks
3
+
4
+ before_action :require_no_authentication, except: :destroy
5
+ before_action :authenticate_user!, only: :destroy
6
+
7
+ define_callbacks :successful_sign_in, :failed_sign_in, :sign_out
8
+
9
+ def new
10
+ end
11
+
12
+ def create
13
+ @user = scope.authenticate_by email: params[:email], password: params[:password]
14
+
15
+ if @user.present?
16
+ run_callbacks :successful_sign_in do
17
+ sign_in @user
18
+ end
19
+
20
+ redirect_to root_path, notice: t(".success")
21
+ else
22
+ run_callbacks :failed_sign_in do
23
+ flash[:alert] = t(".invalid_email_or_password")
24
+ end
25
+
26
+ render :new, status: :unprocessable_entity
27
+ end
28
+ end
29
+
30
+ def destroy
31
+ run_callbacks :sign_out do
32
+ sign_out
33
+ end
34
+ redirect_to root_url, notice: t(".success")
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ class ActiveAuthentication::UnlocksController < ApplicationController
2
+ before_action :require_no_authentication
3
+ before_action :set_user, only: :show
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ @user = User.find_by email: params[:email]
10
+ @user&.send_unlock_instructions if @user&.locked?
11
+
12
+ redirect_to root_path, notice: t(".success")
13
+ end
14
+
15
+ def show
16
+ @user.unlock
17
+ sign_in @user
18
+
19
+ redirect_to root_url, notice: t(".success")
20
+ end
21
+
22
+ private
23
+
24
+ def set_user
25
+ @user = User.find_by_token_for :unlock, params[:token]
26
+
27
+ redirect_to root_url, alert: t(".invalid_token") unless @user.present?
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveAuthentication
2
+ class Mailer < ApplicationMailer
3
+ def email_confirmation_instructions
4
+ @token, @user = params[:token], params[:user]
5
+
6
+ mail to: @user.unconfirmed_email
7
+ end
8
+
9
+ def password_reset_instructions
10
+ @token, @user = params[:token], params[:user]
11
+
12
+ mail to: @user.email
13
+ end
14
+
15
+ def unlock_instructions
16
+ @token, @user = params[:token], params[:user]
17
+
18
+ mail to: @user.email
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".email_confirmation_instructions" %></h1>
2
+
3
+ <%= form_with url: confirmations_path do |form| %>
4
+ <div>
5
+ <%= form.label :email, t("activerecord.attributes.user.email") %>
6
+ <%= form.email_field :email %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= form.submit t(".email_confirmation_instructions") %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= render "active_authentication/shared/links" %>
@@ -0,0 +1,5 @@
1
+ <p><%=t ".hello", email: @user.unconfirmed_email %></p>
2
+
3
+ <p><%=t ".confirm_email_below" %></p>
4
+
5
+ <p><%= link_to t(".confirm_email"), confirmation_url(token: @token) %></p>
@@ -0,0 +1,5 @@
1
+ <p><%=t ".hello", email: @user.email %></p>
2
+
3
+ <p><%=t ".reset_password_below" %></p>
4
+
5
+ <p><%= link_to t(".reset_password"), edit_password_url(token: @token) %></p>
@@ -0,0 +1,5 @@
1
+ <p><%=t ".hello", email: @user.email %></p>
2
+
3
+ <p><%=t ".unlock_below" %></p>
4
+
5
+ <p><%= link_to t(".unlock"), unlock_url(token: @token) %></p>
@@ -0,0 +1,28 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".set_new_password" %></h1>
2
+
3
+ <%= form_with model: @user, url: password_path(token: params[:token]), method: :put, data: {turbo: false} do |form| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation" class="px-3 py-2 mt-3 font-medium text-red-500 rounded-lg bg-red-50">
6
+ <h2><%=t "active_authentication.failure.form_errors", errors: pluralize(@user.errors.count, "error") %></h2>
7
+ <ul>
8
+ <% @user.errors.each do |error| %>
9
+ <li><%= error.full_message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div>
16
+ <%= form.label :password %>
17
+ <%= form.password_field :password %>
18
+ </div>
19
+
20
+ <div>
21
+ <%= form.label :password_confirmation %>
22
+ <%= form.password_field :password_confirmation %>
23
+ </div>
24
+
25
+ <div>
26
+ <%= form.submit t(".set_new_password") %>
27
+ </div>
28
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".reset_password_instructions" %></h1>
2
+
3
+ <%= form_with url: passwords_path, data: {turbo: false} do |form| %>
4
+ <div>
5
+ <%= form.label :email, t("activerecord.attributes.user.email") %>
6
+ <%= form.email_field :email %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= form.submit t(".reset_password_instructions") %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= render "active_authentication/shared/links" %>
@@ -0,0 +1,42 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".edit_profile" %></h1>
2
+
3
+ <% if current_user.unconfirmed? %>
4
+ <div class="px-3 py-2 mt-3 font-medium text-yellow-500 rounded-lg bg-yellow-50">
5
+ <%=t ".pending_confirmation", unconfirmed_email: current_user.unconfirmed_email %>
6
+ </div>
7
+ <% end %>
8
+
9
+ <%= form_with model: current_user, url: profile_path, data: {turbo: false} do |form| %>
10
+ <% if current_user.errors.any? %>
11
+ <div id="error_explanation" class="px-3 py-2 mt-3 font-medium text-red-500 rounded-lg bg-red-50">
12
+ <h2><%=t "active_authentication.failure.form_errors", errors: pluralize(current_user.errors.count, "error") %></h2>
13
+ <ul>
14
+ <% current_user.errors.each do |error| %>
15
+ <li><%= error.full_message %></li>
16
+ <% end %>
17
+ </ul>
18
+ </div>
19
+ <% end %>
20
+
21
+ <div>
22
+ <%= form.label :email %>
23
+ <%= form.email_field :email %>
24
+ </div>
25
+
26
+ <div>
27
+ <%= form.label :password %>
28
+ <%= form.password_field :password %>
29
+ </div>
30
+
31
+ <div>
32
+ <%= form.label :password_confirmation %>
33
+ <%= form.password_field :password_confirmation %>
34
+ </div>
35
+
36
+ <div>
37
+ <%= form.submit t(".save") %>
38
+ </div>
39
+ <% end %>
40
+
41
+ <h2 class="text-2xl font-bold"><%=t ".danger_zone" %></h2>
42
+ <%= button_to t(".cancel_account"), profile_path, method: :delete, data: {turbo_confirm: t(".confirm")} %>
@@ -0,0 +1,35 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".sign_up" %></h1>
2
+
3
+ <%= form_with model: @user, url: registrations_path, data: {turbo: false} do |form| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation" class="px-3 py-2 mt-3 font-medium text-red-500 rounded-lg bg-red-50">
6
+ <h2><%=t "active_authentication.failure.form_errors", errors: pluralize(@user.errors.count, "error") %></h2>
7
+ <ul>
8
+ <% @user.errors.each do |error| %>
9
+ <li><%= error.full_message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div>
16
+ <%= form.label :email %>
17
+ <%= form.email_field :email %>
18
+ </div>
19
+
20
+ <div>
21
+ <%= form.label :password %>
22
+ <%= form.password_field :password %>
23
+ </div>
24
+
25
+ <div>
26
+ <%= form.label :password_confirmation %>
27
+ <%= form.password_field :password_confirmation %>
28
+ </div>
29
+
30
+ <div>
31
+ <%= form.submit t(".sign_up") %>
32
+ </div>
33
+ <% end %>
34
+
35
+ <%= render "active_authentication/shared/links" %>
@@ -0,0 +1,19 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".sign_in" %></h1>
2
+
3
+ <%= form_with url: session_path, data: {turbo: false} do |form| %>
4
+ <div>
5
+ <%= form.label :email, t("activerecord.attributes.user.email") %>
6
+ <%= form.email_field :email %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= form.label :password, t("activerecord.attributes.user.password") %>
11
+ <%= form.password_field :password %>
12
+ </div>
13
+
14
+ <div>
15
+ <%= form.submit t(".sign_in") %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <%= render "active_authentication/shared/links" %>
@@ -0,0 +1,19 @@
1
+ <% if controller_name != "sessions" %>
2
+ <p><%= link_to t(".sign_in"), new_session_path %></p>
3
+ <% end %>
4
+
5
+ <% if User.registerable? && controller_name != "registrations" %>
6
+ <p><%= link_to t(".sign_up"), new_registration_path %></p>
7
+ <% end %>
8
+
9
+ <% if User.recoverable? && controller_name != "registrations" && controller_name != "passwords" %>
10
+ <p><%= link_to t(".reset_password"), new_password_path %></p>
11
+ <% end %>
12
+
13
+ <% if User.confirmable? && controller_name != "confirmations" %>
14
+ <p><%= link_to t(".send_email_confirmation_instructions"), new_confirmation_path %></p>
15
+ <% end %>
16
+
17
+ <% if User.lockable? && controller_name != "unlocks" %>
18
+ <p><%= link_to t(".send_unlock_instructions"), new_unlock_path %></p>
19
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".unlock_instructions" %></h1>
2
+
3
+ <%= form_with url: unlocks_path do |form| %>
4
+ <div>
5
+ <%= form.label :email, t("activerecord.attributes.user.email") %>
6
+ <%= form.email_field :email %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= form.submit t(".unlock_instructions") %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= render "active_authentication/shared/links" %>