active_authentication 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -6
  3. data/app/controllers/active_authentication/confirmations_controller.rb +1 -1
  4. data/app/controllers/active_authentication/magic_links_controller.rb +28 -0
  5. data/app/controllers/active_authentication/omniauth_callbacks_controller.rb +30 -0
  6. data/app/controllers/active_authentication/passwords_controller.rb +1 -1
  7. data/app/controllers/active_authentication/registrations_controller.rb +9 -5
  8. data/app/controllers/active_authentication/sessions_controller.rb +1 -1
  9. data/app/controllers/active_authentication/unlocks_controller.rb +1 -1
  10. data/app/controllers/active_authentication_controller.rb +2 -0
  11. data/app/mailers/active_authentication/mailer.rb +6 -0
  12. data/app/views/active_authentication/magic_links/new.html.erb +14 -0
  13. data/app/views/active_authentication/mailer/magic_link.html.erb +5 -0
  14. data/app/views/active_authentication/shared/_links.html.erb +10 -0
  15. data/config/locales/en.yml +22 -0
  16. data/config/locales/es.yml +22 -0
  17. data/lib/active_authentication/controller/authenticatable.rb +53 -0
  18. data/lib/active_authentication/controller/lockable.rb +13 -13
  19. data/lib/active_authentication/controller/timeoutable.rb +26 -0
  20. data/lib/active_authentication/controller/trackable.rb +4 -4
  21. data/lib/active_authentication/controller.rb +1 -40
  22. data/lib/active_authentication/engine.rb +7 -5
  23. data/lib/active_authentication/model/magiclinkable.rb +16 -0
  24. data/lib/active_authentication/model/omniauthable.rb +11 -0
  25. data/lib/active_authentication/model/timeoutable.rb +15 -0
  26. data/lib/active_authentication/model.rb +1 -1
  27. data/lib/active_authentication/routes.rb +9 -0
  28. data/lib/active_authentication/version.rb +1 -1
  29. data/lib/active_authentication.rb +20 -0
  30. data/lib/generators/active_authentication/install/install_generator.rb +5 -3
  31. data/lib/generators/active_authentication/install/templates/initializer.rb +15 -0
  32. data/lib/generators/active_authentication/install/templates/migration.rb +8 -8
  33. data/lib/generators/active_authentication/omniauthable/omniauthable_generator.rb +13 -0
  34. data/lib/generators/active_authentication/views/views_generator.rb +1 -1
  35. metadata +20 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8b956c6e7b16a3f007c97efaf865bd070897b57fcef56be4f6a8615c6e83b37
4
- data.tar.gz: 365972997162dcbc741186117bcb43fc45c04338ce3d4c8dd7562f9ac1d9a6ea
3
+ metadata.gz: 6f71840fc4d08d8eb9ec344aeb3f779e276b09c9812e5a380d1e3455d4d4f29b
4
+ data.tar.gz: 602d85a21c8cea03d629ca0abfd3698344cb9560e376745aea5d964a6e9b9064
5
5
  SHA512:
6
- metadata.gz: f3e34c5159ff44130cd296ece54c3e5ba02158f247775f0a1af856c70efdd2369bf20ccf522d5cb1dc2ec76e7f2bc37b488f5117c1f93d6cf5987a6adc9354e6
7
- data.tar.gz: b7bb7d568d98831ee2d18309da36ba125ee0f2a1aa76197c2e2d37aa66d78febfe5368f63f62819802907e7aba73a205e6db6255a519a4ca96c56c29916a858c
6
+ metadata.gz: d4307d0af7c4d2a319f2714c1ada81b60956f5a41e7e4d11f17a917e33bf2d2bfa7eeff3d41d3c7ded2a846af2eec55937230f8db6c44c548529d4f21bd4a8a2
7
+ data.tar.gz: f6e78e92c1298baa72ddf0be06f2bd90553fac04ece61813b0d3829eacc0861d5028ff73200131a4b8e4df51155253f8cd016a79b426c20f50be5d558535a933
data/README.md CHANGED
@@ -13,15 +13,16 @@ A pure Rails authentication solution.
13
13
  * Authenticatable: provides the standard email/password authentication. It's the only concern that can't be turned off.
14
14
  * Confirmable: allows users to confirm their email addresses.
15
15
  * Lockable: locks users after a number of failed sign in attempts.
16
+ * MagicLinkable: allows users to sign in with a magic link.
17
+ * Omniauthable: allows users to sign up and sign in using a third party service through Omniauth. Turned off by default.
16
18
  * Recoverable: allows users to reset their password.
17
19
  * Registerable: allows users to sign up and edit their profile.
20
+ * Timeoutable: expires sessions after a period of inactivity. Turned off by default.
18
21
  * Trackable: tracks users sign in count, timestamps and ip addresses.
19
22
 
20
23
  Planned concerns:
21
24
 
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
+ * Invitable: to allow users to invite other users.
25
26
 
26
27
  ## Installation
27
28
 
@@ -51,7 +52,13 @@ After installing the gem, you need to generate the `User` model. To generate it,
51
52
  $ rails generate active_authentication:install
52
53
  ```
53
54
 
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
+ 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. By default, this command enables all concerns. If you want to use a subset of the concerns, you can specify them:
56
+
57
+ ```bash
58
+ $ rails generate active_authentication:install confirmable
59
+ ```
60
+
61
+ In this example, only the confirmable concern will be enabled (along with authenticatable, which can't be turned off).
55
62
 
56
63
  You will need to set up the default url options in your `config/environments/development.rb`:
57
64
 
@@ -69,13 +76,13 @@ If you look at the `User` model (in `app/models/user.rb`), you will notice there
69
76
 
70
77
  ```ruby
71
78
  class User < ApplicationRecord
72
- authenticates_with :confirmable, :lockable, :recoverable, :registerable, :trackable
79
+ authenticates_with :confirmable, :lockable, :recoverable, :registerable, :timeoutable, :trackable
73
80
  end
74
81
  ```
75
82
 
76
83
  Notice that `:authenticatable` is not in the list. This is because you cannot turn it off.
77
84
 
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`.
85
+ By default, all concerns are turned on except omniauthable. But you can turn it on by adding it to the list, and similarly, you can turn any concern off by just removing them from the list. If you plan to not use any concerns, you can replace `authenticates_with` with `authenticates`.
79
86
 
80
87
  ### Filters and helpers
81
88
 
@@ -91,12 +98,62 @@ Then, to verify if there's an authenticated user, you can use the `user_signed_i
91
98
 
92
99
  Similarly, you can use `current_user` to access the current authenticated user.
93
100
 
101
+ If you want to close your application entirely, you can add the before action to your application controller, in conjunction with `active_authentication_controller?`, like this:
102
+
103
+ ```ruby
104
+ before_action :authenticate_user!, unless: :active_authentication_controller?
105
+ ```
106
+
107
+ ### Omniauthable
108
+
109
+ ActiveAuthentication's implementation of OmniAuth allows you to sign in and/or sign up with your third party accounts or sign up with ActiveAuthentication and later connect your third party accounts to ActiveAuthentication's User. To accomplish this, ActiveAuthentication relies on an `Authentication` model which can be created with the `active_authentication:omniauthable` generator.
110
+
111
+ To set up the omniauthable concern you must configure your OmniAuth providers as you would do with plain OmniAuth. There's no OmniAuth config in ActiveAuthentication. For example, in `config/initializers/omniauth.rb` you would set the middleware:
112
+
113
+ ```ruby
114
+ Rails.application.config.middleware.use OmniAuth::Builder do
115
+ provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"]
116
+ provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"]
117
+ # ... and any other omniauth strategies
118
+ end
119
+ ```
120
+
121
+ And then you need to run the omniauthable generator to generate the `Authentication` model:
122
+
123
+ ```bash
124
+ $ rails g active_authentication:omniauthable
125
+ ```
126
+
127
+ The User model has many Authentication models associated, to allow you to connect your user with multiple third party services if required.
128
+
129
+ By adding the `:omniauthable` concern to your `User` model, the following routes will be added to your app:
130
+
131
+ * `/auth/:provider` to redirect your users to the provider consent screen
132
+ * `/auth/:provider/callback` to actually sign in/sign up with the given providers
133
+
134
+ The sign in and sign up views will show a link to sign in or sign up with each provider you configured if and only if you set the `ActiveAuthentication.omniauth_providers` setting in your ActiveAuthentication initializer.
135
+
94
136
  ## Customization
95
137
 
96
138
  ### Concerns configuration
97
139
 
98
140
  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
141
 
142
+ ### Customize registration and profile params
143
+
144
+ If you add extra fields to your User model, you will likely want to allow users to fill in those fields upon registration or when editing their profile. By default, only email, password and password confirmation are allowed. To change this behavior, just add these lines to your `config/initializers/active_authentication.rb` file:
145
+
146
+ ```ruby
147
+ ActiveAuthentication.configure do |config|
148
+ config.profile_params = ->(controller) {
149
+ controller.params.require(:user).permit(:first_name, :email, :last_name, :password, :password_confirmation) # first_name and last_name were added in this example
150
+ }
151
+ config.registration_params = config.profile_params
152
+ end
153
+ ```
154
+
155
+ We believe that the configuration of a gem should be placed in just one place. For this gem, it's the initializer. The `profile_params` and `registration_params` take a lambda and that lambda receives a controller. Why a lambda? and why does it take a controller? We could have allowed the params to be just an array of symbols instead of the whole `params.require.permit` call, but in edge cases you might want to post-process the required params, or call tap, or whatever. And to be able to call `params.require.permit`, you need to run this lambda in the context of the registrations controller. That's why the lambda receives the controller.
156
+
100
157
  ### Views
101
158
 
102
159
  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:
@@ -111,6 +168,26 @@ If you're not using all the concerns, you might want to copy only the views you
111
168
  $ rails generate active_authentication:views -v sessions
112
169
  ```
113
170
 
171
+ ### Omniauthable
172
+
173
+ By default, ActiveAuthentication stores the `provider`, `uid` and `auth_data` in the `Authentication` model. There are some cases where you want to store, for example, the first name and last name in the `User` model to avoid digging into the `auth_data` hash each time. Or if you have multiple authentications, you might want to pull first and last name on registration and later allow the user to change them. To pull that data from an Authentication object at sign up, you don't really need to change the controller, instead you can add a callback to your Authentication model, like this:
174
+
175
+ ```ruby
176
+ class Authentication < ApplicationRecord
177
+ before_validation :update_user_attributes, if: ->(auth) { auth.auth_data.present? && auth.user.present? }
178
+
179
+ private
180
+
181
+ def update_user_attributes
182
+ first_name, last_name = auth_data.dig("info", "first_name"), auth_data.dig("info", "last_name")
183
+
184
+ user.update first_name: first_name, last_name: last_name
185
+ end
186
+ end
187
+ ```
188
+
189
+ Note: this example assumes `first_name:string` and `last_name:string` have been added to the User model and are required. Optional first_name and last_name can be handled similarly.
190
+
114
191
  ## Contributing
115
192
 
116
193
  You can open an issue or a PR in GitHub.
@@ -1,4 +1,4 @@
1
- class ActiveAuthentication::ConfirmationsController < ApplicationController
1
+ class ActiveAuthentication::ConfirmationsController < ActiveAuthenticationController
2
2
  before_action :require_no_authentication, except: :show
3
3
  before_action :set_user, only: :show
4
4
 
@@ -0,0 +1,28 @@
1
+ class ActiveAuthentication::MagicLinksController < ActiveAuthenticationController
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_magic_link
11
+
12
+ redirect_to root_path, notice: t(".success")
13
+ end
14
+
15
+ def show
16
+ sign_in @user
17
+
18
+ redirect_to root_url, notice: t(".success")
19
+ end
20
+
21
+ private
22
+
23
+ def set_user
24
+ @user = User.find_by_token_for :magic_link, params[:token]
25
+
26
+ redirect_to root_url, alert: t(".invalid_token") unless @user.present?
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ class ActiveAuthentication::OmniauthCallbacksController < ActiveAuthenticationController
2
+ def create
3
+ auth = request.env["omniauth.auth"]
4
+ provider = auth["provider"]
5
+
6
+ @authentication = Authentication.find_or_create_by uid: auth["uid"], provider: provider
7
+
8
+ @authentication.update auth_data: auth.as_json
9
+
10
+ if user_signed_in?
11
+ if @authentication.user == current_user
12
+ redirect_to root_path, notice: t(".already_linked", provider: provider)
13
+ else
14
+ @authentication.update user: current_user
15
+ redirect_to root_path, notice: t(".successfully_linked", provider: provider)
16
+ end
17
+ elsif @authentication.user.blank?
18
+ @user = User.find_or_initialize_by email: auth.dig("info", "email")
19
+ @user.password = SecureRandom.hex if @user.new_record?
20
+
21
+ @authentication.update user: @user
22
+
23
+ sign_in @authentication.user
24
+ redirect_to root_path, notice: t(".successfully_signed_up", provider: provider)
25
+ else
26
+ sign_in @authentication.user
27
+ redirect_to root_path, notice: t("active_authentication.sessions.create.success")
28
+ end
29
+ end
30
+ end
@@ -1,4 +1,4 @@
1
- class ActiveAuthentication::PasswordsController < ApplicationController
1
+ class ActiveAuthentication::PasswordsController < ActiveAuthenticationController
2
2
  before_action :require_no_authentication
3
3
  before_action :set_user, only: [:edit, :update]
4
4
 
@@ -1,4 +1,4 @@
1
- class ActiveAuthentication::RegistrationsController < ApplicationController
1
+ class ActiveAuthentication::RegistrationsController < ActiveAuthenticationController
2
2
  before_action :require_no_authentication, only: [:new, :create]
3
3
  before_action :authenticate_user!, only: [:edit, :update, :destroy]
4
4
 
@@ -7,7 +7,7 @@ class ActiveAuthentication::RegistrationsController < ApplicationController
7
7
  end
8
8
 
9
9
  def create
10
- @user = User.new user_params
10
+ @user = User.new registration_params
11
11
 
12
12
  if @user.save
13
13
  sign_in @user
@@ -21,7 +21,7 @@ class ActiveAuthentication::RegistrationsController < ApplicationController
21
21
  end
22
22
 
23
23
  def update
24
- if current_user.update(user_params)
24
+ if current_user.update(profile_params)
25
25
  redirect_to edit_profile_path, notice: t(".success")
26
26
  else
27
27
  render :edit, status: :unprocessable_entity
@@ -36,7 +36,11 @@ class ActiveAuthentication::RegistrationsController < ApplicationController
36
36
 
37
37
  private
38
38
 
39
- def user_params
40
- params.require(:user).permit(:email, :password, :password_confirmation)
39
+ def profile_params
40
+ ActiveAuthentication.profile_params.call self
41
+ end
42
+
43
+ def registration_params
44
+ ActiveAuthentication.registration_params.call self
41
45
  end
42
46
  end
@@ -1,4 +1,4 @@
1
- class ActiveAuthentication::SessionsController < ApplicationController
1
+ class ActiveAuthentication::SessionsController < ActiveAuthenticationController
2
2
  include ActiveSupport::Callbacks
3
3
 
4
4
  before_action :require_no_authentication, except: :destroy
@@ -1,4 +1,4 @@
1
- class ActiveAuthentication::UnlocksController < ApplicationController
1
+ class ActiveAuthentication::UnlocksController < ActiveAuthenticationController
2
2
  before_action :require_no_authentication
3
3
  before_action :set_user, only: :show
4
4
 
@@ -0,0 +1,2 @@
1
+ class ActiveAuthenticationController < ApplicationController
2
+ end
@@ -6,6 +6,12 @@ module ActiveAuthentication
6
6
  mail to: @user.unconfirmed_email
7
7
  end
8
8
 
9
+ def magic_link
10
+ @token, @user = params[:token], params[:user]
11
+
12
+ mail to: @user.email
13
+ end
14
+
9
15
  def password_reset_instructions
10
16
  @token, @user = params[:token], params[:user]
11
17
 
@@ -0,0 +1,14 @@
1
+ <h1 class="text-4xl font-bold"><%=t ".send_magic_link" %></h1>
2
+
3
+ <%= form_with url: magic_links_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(".send_magic_link") %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= render "active_authentication/shared/links" %>
@@ -0,0 +1,5 @@
1
+ <p><%=t ".hello", email: @user.email %></p>
2
+
3
+ <p><%=t ".magic_link_below" %></p>
4
+
5
+ <p><%= link_to t(".sign_in"), magic_link_url(token: @token) %></p>
@@ -2,10 +2,20 @@
2
2
  <p><%= link_to t(".sign_in"), new_session_path %></p>
3
3
  <% end %>
4
4
 
5
+ <% if controller_name != "magic_links" %>
6
+ <p><%= link_to t(".send_magic_link"), new_magic_link_path %></p>
7
+ <% end %>
8
+
5
9
  <% if User.registerable? && controller_name != "registrations" %>
6
10
  <p><%= link_to t(".sign_up"), new_registration_path %></p>
7
11
  <% end %>
8
12
 
13
+ <% if User.omniauthable? && (controller_name == "sessions" || controller_name == "registrations") %>
14
+ <% ActiveAuthentication.omniauth_providers.each do |provider| %>
15
+ <p><%= link_to t(".sign_in_or_sign_up_with", provider: provider), omniauth_path(provider) %></p>
16
+ <% end %>
17
+ <% end %>
18
+
9
19
  <% if User.recoverable? && controller_name != "registrations" && controller_name != "passwords" %>
10
20
  <p><%= link_to t(".reset_password"), new_password_path %></p>
11
21
  <% end %>
@@ -14,13 +14,28 @@ en:
14
14
  already_signed_in: You are already signed in.
15
15
  form_errors: "%{errors} prohibited this user from being saved:"
16
16
  locked: Your account has been locked after %{count} failed attempts. Unlock instructions will be sent to your email.
17
+ timedout: Your session expired. Sign in again to continue.
17
18
  unauthenticated: You need to sign in or sign up before continuing.
19
+ magic_links:
20
+ create:
21
+ success: Magic link will be sent to your email.
22
+ new:
23
+ send_magic_link: Send magic link
24
+ set_user:
25
+ invalid_token: Magic link is invalid.
26
+ show:
27
+ success: Signed in successfully.
18
28
  mailer:
19
29
  email_confirmation_instructions:
20
30
  confirm_email: Confirm my account
21
31
  confirm_email_below: 'You can confirm your account email by clicking the link below:'
22
32
  hello: Hello, %{email}!
23
33
  subject: Confirmation instructions
34
+ magic_link:
35
+ hello: Hello, %{email}!
36
+ magic_link_below: 'You can sign in to your account by clicking the link below:'
37
+ sign_in: Sign in
38
+ subject: Magic link
24
39
  password_reset_instructions:
25
40
  hello: Hello, %{email}!
26
41
  reset_password: Reset your password
@@ -31,6 +46,11 @@ en:
31
46
  subject: Unlock instructions
32
47
  unlock: Unlock your account
33
48
  unlock_below: 'You can unlock your account by clicking the link below:'
49
+ omniauth_callbacks:
50
+ create:
51
+ already_linked: Your %{provider} account has already been linked.
52
+ successfully_linked: Your %{provider} account has been succesfully linked.
53
+ successfully_signed_up: You have signed up successfully with %{provider}.
34
54
  passwords:
35
55
  create:
36
56
  success: Password reset instructions will be sent to your email.
@@ -70,8 +90,10 @@ en:
70
90
  links:
71
91
  reset_password: Reset password
72
92
  send_email_confirmation_instructions: Didn't receive confirmation instructions?
93
+ send_magic_link: Send me a magic link
73
94
  send_unlock_instructions: Didn't receive unlock instructions?
74
95
  sign_in: Sign in
96
+ sign_in_or_sign_up_with: Sign in or sign up with %{provider}
75
97
  sign_up: Sign up
76
98
  unlocks:
77
99
  create:
@@ -14,13 +14,28 @@ es:
14
14
  already_signed_in: Ya iniciaste sesión.
15
15
  form_errors: "%{errors} no permitieron guardar este elemento:"
16
16
  locked: Tu cuenta fue bloqueada después de %{count} intentos fallidos. Te enviaremos un email con las instrucciones de desbloqueo.
17
+ timedout: Tu sesión expiró. Iniciá sesión nuevamente para continuar.
17
18
  unauthenticated: Tenés que iniciar sesión antes de continuar.
19
+ magic_links:
20
+ create:
21
+ success: Te enviaremos un email con un enlace mágico para iniciar sesión.
22
+ new:
23
+ send_magic_link: Enviar enlace mágico
24
+ set_user:
25
+ invalid_token: El enlace mágico es inválido.
26
+ show:
27
+ success: Inicio de sesión existoso.
18
28
  mailer:
19
29
  email_confirmation_instructions:
20
30
  confirm_email: Confirmar mi cuenta
21
31
  confirm_email_below: 'Podés confirmar tu cuenta haciendo click en el siguiente link:'
22
32
  hello: Hola, %{email}!
23
33
  subject: Instrucciones de confirmación
34
+ magic_link:
35
+ hello: Hola, %{email}!
36
+ magic_link_below: 'Podés iniciar sesión haciendo click en el siguiente link:'
37
+ sign_in: Iniciar sesión
38
+ subject: Enlace mágico
24
39
  password_reset_instructions:
25
40
  hello: Hola, %{email}!
26
41
  reset_password: Recuperar contraseña
@@ -31,6 +46,11 @@ es:
31
46
  subject: Instrucciones de desbloqueo
32
47
  unlock: Desbloquear cuenta
33
48
  unlock_below: 'Podés desbloquear tu contraseña haciendo click en el siguiente link:'
49
+ omniauth_callbacks:
50
+ create:
51
+ already_linked: Tu cuenta de %{provider} ya fue enlazada.
52
+ successfully_linked: Tu cuenta de %{provider} fue enlazada exitosamente.
53
+ successfully_signed_up: Te registraste exitosamente con %{provider}.
34
54
  passwords:
35
55
  create:
36
56
  success: Te enviaremos un email con las instrucciones de para recuperar tu contraseña.
@@ -70,8 +90,10 @@ es:
70
90
  links:
71
91
  reset_password: Recuperar contraseña
72
92
  send_email_confirmation_instructions: "¿No recibiste las instrucciones de confirmación?"
93
+ send_magic_link: Enviarme un enlace mágico
73
94
  send_unlock_instructions: "¿No recibiste las instrucciones de desbloqueo?"
74
95
  sign_in: Iniciar sesión
96
+ sign_in_or_sign_up_with: Iniciar sesión o crear una cuenta con %{provider}
75
97
  sign_up: Crear una cuenta
76
98
  unlocks:
77
99
  create:
@@ -0,0 +1,53 @@
1
+ module ActiveAuthentication
2
+ module Controller
3
+ module Authenticatable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper_method :current_user
8
+ helper_method :user_signed_in?
9
+ end
10
+
11
+ def active_authentication_controller?
12
+ is_a? ::ActiveAuthenticationController
13
+ end
14
+
15
+ def authenticate_user!
16
+ redirect_to new_session_path, alert: t("active_authentication.failure.unauthenticated") unless user_signed_in?
17
+ end
18
+
19
+ def current_user
20
+ Current.user ||= user_from_session
21
+ end
22
+
23
+ def require_no_authentication
24
+ redirect_to root_path, alert: t("active_authentication.failure.already_signed_in") if user_signed_in?
25
+ end
26
+
27
+ def sign_in(user)
28
+ reset_session
29
+ Current.user = user
30
+ session[:user_id] = user.id
31
+ end
32
+
33
+ def sign_out
34
+ reset_session
35
+ Current.user = nil
36
+ end
37
+
38
+ def user_signed_in?
39
+ current_user.present?
40
+ end
41
+
42
+ private
43
+
44
+ def scope
45
+ User
46
+ end
47
+
48
+ def user_from_session
49
+ User.find_by id: session[:user_id]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -6,24 +6,24 @@ module ActiveAuthentication
6
6
  included do
7
7
  set_callback :failed_sign_in, :before, :increment_failed_attempts
8
8
  set_callback :failed_sign_in, :after, :set_alert
9
+ end
9
10
 
10
- private
11
+ private
11
12
 
12
- def increment_failed_attempts
13
- user = User.find_by email: params[:email]
14
- user&.increment_failed_attempts
15
- end
13
+ def increment_failed_attempts
14
+ user = User.find_by email: params[:email]
15
+ user&.increment_failed_attempts
16
+ end
16
17
 
17
- def scope
18
- User.unlocked
19
- end
18
+ def scope
19
+ User.unlocked
20
+ end
20
21
 
21
- def set_alert
22
- user = User.find_by email: params[:email]
22
+ def set_alert
23
+ user = User.find_by email: params[:email]
23
24
 
24
- if user.locked?
25
- flash[:alert] = t "active_authentication.failure.locked", count: user.failed_attempts
26
- end
25
+ if user&.locked?
26
+ flash[:alert] = t "active_authentication.failure.locked", count: user.failed_attempts
27
27
  end
28
28
  end
29
29
  end
@@ -0,0 +1,26 @@
1
+ module ActiveAuthentication
2
+ module Controller
3
+ module Timeoutable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :sign_out_user_if_timedout, if: :user_signed_in?
8
+ end
9
+
10
+ private
11
+
12
+ def sign_out_user_if_timedout
13
+ last_request_at = session[:last_request_at].yield_self do |timestamp|
14
+ Time.at(timestamp).utc if timestamp.present?
15
+ end
16
+
17
+ if current_user.timedout?(last_request_at)
18
+ sign_out
19
+ redirect_to root_path, alert: t("active_authentication.failure.timedout")
20
+ end
21
+
22
+ session[:last_request_at] = Time.now.utc.to_i
23
+ end
24
+ end
25
+ end
26
+ end
@@ -5,12 +5,12 @@ module ActiveAuthentication
5
5
 
6
6
  included do
7
7
  set_callback :successful_sign_in, :after, :track
8
+ end
8
9
 
9
- private
10
+ private
10
11
 
11
- def track
12
- current_user.track request
13
- end
12
+ def track
13
+ current_user.track request
14
14
  end
15
15
  end
16
16
  end
@@ -1,48 +1,9 @@
1
1
  module ActiveAuthentication
2
2
  module Controller
3
3
  extend ActiveSupport::Concern
4
- include ActiveSupport::Callbacks
5
4
 
6
5
  included do
7
- helper_method :current_user
8
- helper_method :user_signed_in?
9
- end
10
-
11
- def authenticate_user!
12
- redirect_to new_session_path, alert: t("active_authentication.failure.unauthenticated") unless user_signed_in?
13
- end
14
-
15
- def current_user
16
- Current.user ||= user_from_session
17
- end
18
-
19
- def require_no_authentication
20
- redirect_to root_path, alert: t("active_authentication.failure.already_signed_in") if user_signed_in?
21
- end
22
-
23
- def sign_in(user)
24
- reset_session
25
- Current.user = user
26
- session[:user_id] = user.id
27
- end
28
-
29
- def sign_out
30
- reset_session
31
- Current.user = nil
32
- end
33
-
34
- def user_signed_in?
35
- current_user.present?
36
- end
37
-
38
- private
39
-
40
- def scope
41
- User
42
- end
43
-
44
- def user_from_session
45
- User.find_by id: session[:user_id]
6
+ include Authenticatable
46
7
  end
47
8
  end
48
9
  end
@@ -3,14 +3,16 @@ require "active_authentication/model"
3
3
 
4
4
  module ActiveAuthentication
5
5
  class Engine < ::Rails::Engine
6
- initializer :active_authentication_controller do
7
- ActiveSupport.on_load :action_controller_base do
8
- include ActiveAuthentication::Controller
9
- end
10
-
6
+ initializer "active_authentication.model" do
11
7
  ActiveSupport.on_load :active_record do
12
8
  include ActiveAuthentication::Model
13
9
  end
14
10
  end
11
+
12
+ initializer "active_authentication.controller" do
13
+ ActiveSupport.on_load :action_controller_base do
14
+ include ActiveAuthentication::Controller
15
+ end
16
+ end
15
17
  end
16
18
  end
@@ -0,0 +1,16 @@
1
+ module ActiveAuthentication
2
+ module Model
3
+ module Magiclinkable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ generates_token_for :magic_link, expires_in: ActiveAuthentication.magic_link_token_expires_in
8
+ end
9
+
10
+ def send_magic_link
11
+ token = generate_token_for :magic_link
12
+ ActiveAuthentication::Mailer.with(token: token, user: self).magic_link.deliver
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveAuthentication
2
+ module Model
3
+ module Omniauthable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :authentications, dependent: :destroy
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveAuthentication
2
+ module Model
3
+ module Timeoutable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ ApplicationController.send :include, ActiveAuthentication::Controller::Timeoutable
8
+ end
9
+
10
+ def timedout?(last_request_at)
11
+ last_request_at && last_request_at <= ActiveAuthentication.timeout_in.ago
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,7 +2,7 @@ module ActiveAuthentication
2
2
  module Model
3
3
  extend ActiveSupport::Concern
4
4
 
5
- CONCERNS = %i[authenticatable confirmable lockable recoverable registerable trackable]
5
+ CONCERNS = %i[authenticatable confirmable lockable magiclinkable omniauthable recoverable registerable timeoutable trackable]
6
6
 
7
7
  class_methods do
8
8
  def authenticates_with(*concerns)
@@ -22,6 +22,15 @@ module ActionDispatch::Routing
22
22
  resources :unlocks, param: :token, only: [:new, :create, :show]
23
23
  end
24
24
 
25
+ def magiclinkable
26
+ resources :magic_links, param: :token, only: [:new, :create, :show]
27
+ end
28
+
29
+ def omniauthable
30
+ get "auth/:provider", to: "omniauth_callbacks#pass", as: :omniauth
31
+ get "auth/:provider/callback", to: "omniauth_callbacks#create", as: :omniauth_callback
32
+ end
33
+
25
34
  def registerable
26
35
  resources :registrations, only: [:new, :create]
27
36
  resource :profile, only: [:edit, :update, :destroy], path: :profile, controller: :registrations
@@ -1,3 +1,3 @@
1
1
  module ActiveAuthentication
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -10,7 +10,9 @@ module ActiveAuthentication
10
10
  autoload :Model, "active_authentication/model"
11
11
 
12
12
  module Controller
13
+ autoload :Authenticatable, "active_authentication/controller/authenticatable"
13
14
  autoload :Lockable, "active_authentication/controller/lockable"
15
+ autoload :Timeoutable, "active_authentication/controller/timeoutable"
14
16
  autoload :Trackable, "active_authentication/controller/trackable"
15
17
  end
16
18
 
@@ -18,8 +20,11 @@ module ActiveAuthentication
18
20
  autoload :Authenticatable, "active_authentication/model/authenticatable"
19
21
  autoload :Confirmable, "active_authentication/model/confirmable"
20
22
  autoload :Lockable, "active_authentication/model/lockable"
23
+ autoload :Magiclinkable, "active_authentication/model/magiclinkable"
24
+ autoload :Omniauthable, "active_authentication/model/omniauthable"
21
25
  autoload :Recoverable, "active_authentication/model/recoverable"
22
26
  autoload :Registerable, "active_authentication/model/registerable"
27
+ autoload :Timeoutable, "active_authentication/model/timeoutable"
23
28
  autoload :Trackable, "active_authentication/model/trackable"
24
29
  end
25
30
 
@@ -37,6 +42,21 @@ module ActiveAuthentication
37
42
  config_accessor :unlock_token_expires_in, default: 24.hours
38
43
  config_accessor :max_failed_attempts, default: 10
39
44
 
45
+ # magiclinkable
46
+ config_accessor :magic_link_token_expires_in, default: 24.hours
47
+
48
+ # omniauthable
49
+ config_accessor :omniauth_providers, default: []
50
+
40
51
  # recoverable
41
52
  config_accessor :password_reset_token_expires_in, default: 1.hour
53
+
54
+ # registerable
55
+ config_accessor :profile_params, default: ->(controller) {
56
+ controller.params.require(:user).permit(:email, :password, :password_confirmation)
57
+ }
58
+ config_accessor :registration_params, default: profile_params
59
+
60
+ # timeoutable
61
+ config_accessor :timeout_in, default: 30.minutes
42
62
  end
@@ -3,6 +3,8 @@ class ActiveAuthentication::InstallGenerator < Rails::Generators::Base
3
3
 
4
4
  source_root File.expand_path("templates", __dir__)
5
5
 
6
+ argument :concerns, type: :array, default: %w[confirmable lockable recoverable registerable trackable], banner: "concern concern"
7
+
6
8
  desc "Creates the User model, the active_authentication initializer, and adds the active_authentication route."
7
9
 
8
10
  def self.next_migration_number(dirname)
@@ -13,12 +15,12 @@ class ActiveAuthentication::InstallGenerator < Rails::Generators::Base
13
15
  invoke "active_record:model", %w[User], migration: false, skip_collision_check: true
14
16
 
15
17
  if behavior == :invoke
16
- inject_into_class "app/models/user.rb", "User", " authenticates_with :confirmable, :lockable, :recoverable, :registerable, :trackable\n"
18
+ inject_into_class "app/models/user.rb", "User", " authenticates_with #{concerns.map { ":#{_1}" }.join(", ")}\n"
17
19
  end
18
20
  end
19
21
 
20
22
  def generate_migration
21
- migration_template "migration.rb", "db/migrate/create_users.rb", migration_version: migration_version, ip_column: ip_column
23
+ migration_template "migration.rb", "db/migrate/create_users.rb", concerns: concerns, migration_version: migration_version, ip_column: ip_column
22
24
  end
23
25
 
24
26
  def add_route
@@ -48,6 +50,6 @@ class ActiveAuthentication::InstallGenerator < Rails::Generators::Base
48
50
  end
49
51
 
50
52
  def postgresql?
51
- ar_config.present? && ar_config["adapter"] == "postgresql"
53
+ ar_config.present? && ar_config.with_indifferent_access[:adapter] == "postgresql"
52
54
  end
53
55
  end
@@ -9,6 +9,21 @@ ActiveAuthentication.configure do |config|
9
9
  # config.unlock_token_expires_in = 24.hours
10
10
  # config.max_failed_attempts = 10
11
11
 
12
+ # configuration for the magiclinkable concern
13
+ # config.magic_link_token_expires_in = 24.hours
14
+
12
15
  # configuration for the recoverable concern
13
16
  # config.password_reset_token_expires_in = 1.hour
17
+
18
+ # configuration for the registerable concern
19
+ # config_accessor :profile_params, default: ->(controller) {
20
+ # controller.params.require(:user).permit(:email, :password, :password_confirmation)
21
+ # }
22
+ # by default the registration_params take the same lambda as the profile_params. if you redefine the
23
+ # profile_params setting, you will need to uncomment this line as otherwise it will take the default value,
24
+ # previously defined.
25
+ # config_accessor :registration_params, default: profile_params
26
+
27
+ # configuration for the timeoutable concern
28
+ # config.timeout_in = 30.minutes
14
29
  end
@@ -6,18 +6,18 @@ class CreateUsers < ActiveRecord::Migration<%= migration_version %>
6
6
  t.string :password_digest, null: false
7
7
 
8
8
  # confirmable
9
- t.string :unconfirmed_email
9
+ <%= "# " unless concerns.include? "confirmable" -%>t.string :unconfirmed_email
10
10
 
11
11
  # lockable
12
- t.integer :failed_attempts, null: false, default: 0
13
- t.datetime :locked_at
12
+ <%= "# " unless concerns.include? "lockable" -%>t.integer :failed_attempts, null: false, default: 0
13
+ <%= "# " unless concerns.include? "lockable" -%>t.datetime :locked_at
14
14
 
15
15
  # trackable
16
- t.integer :sign_in_count, null: false, default: 0
17
- t.datetime :current_sign_in_at
18
- t.datetime :last_sign_in_at
19
- t.<%= ip_column %> :current_sign_in_ip
20
- t.<%= ip_column %> :last_sign_in_ip
16
+ <%= "# " unless concerns.include? "trackable" -%>t.integer :sign_in_count, null: false, default: 0
17
+ <%= "# " unless concerns.include? "trackable" -%>t.datetime :current_sign_in_at
18
+ <%= "# " unless concerns.include? "trackable" -%>t.datetime :last_sign_in_at
19
+ <%= "# " unless concerns.include? "trackable" -%>t.<%= ip_column %> :current_sign_in_ip
20
+ <%= "# " unless concerns.include? "trackable" -%>t.<%= ip_column %> :last_sign_in_ip
21
21
 
22
22
  t.timestamps
23
23
  end
@@ -0,0 +1,13 @@
1
+ class ActiveAuthentication::OmniauthableGenerator < Rails::Generators::Base
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ desc "Creates the Authentication model"
5
+
6
+ def generate_model
7
+ invoke "active_record:model", %w[Authentication uid:string provider:string user:references auth_data:json], skip_collision_check: true
8
+
9
+ if behavior == :invoke
10
+ inject_into_class "app/models/authentication.rb", "Authentication", " validates :provider, presence: true\n validates :uid, presence: true, uniqueness: {scope: :provider}\n"
11
+ end
12
+ end
13
+ end
@@ -3,7 +3,7 @@ class ActiveAuthentication::ViewsGenerator < Rails::Generators::Base
3
3
 
4
4
  desc "Generates active_authentication's views."
5
5
 
6
- class_option :views, aliases: "-v", type: :array, desc: "Views to generate (available: confirmations, mailer, passwords, registrations, sessions, shared, unlocks)"
6
+ class_option :views, aliases: "-v", type: :array, desc: "Views to generate (available: confirmations, magic_links, mailer, passwords, registrations, sessions, shared, unlocks)"
7
7
 
8
8
  def copy_views
9
9
  if options[:views]
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patricio Mac Adden
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-10 00:00:00.000000000 Z
11
+ date: 2024-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
@@ -49,13 +49,18 @@ files:
49
49
  - README.md
50
50
  - Rakefile
51
51
  - app/controllers/active_authentication/confirmations_controller.rb
52
+ - app/controllers/active_authentication/magic_links_controller.rb
53
+ - app/controllers/active_authentication/omniauth_callbacks_controller.rb
52
54
  - app/controllers/active_authentication/passwords_controller.rb
53
55
  - app/controllers/active_authentication/registrations_controller.rb
54
56
  - app/controllers/active_authentication/sessions_controller.rb
55
57
  - app/controllers/active_authentication/unlocks_controller.rb
58
+ - app/controllers/active_authentication_controller.rb
56
59
  - app/mailers/active_authentication/mailer.rb
57
60
  - app/views/active_authentication/confirmations/new.html.erb
61
+ - app/views/active_authentication/magic_links/new.html.erb
58
62
  - app/views/active_authentication/mailer/email_confirmation_instructions.html.erb
63
+ - app/views/active_authentication/mailer/magic_link.html.erb
59
64
  - app/views/active_authentication/mailer/password_reset_instructions.html.erb
60
65
  - app/views/active_authentication/mailer/unlock_instructions.html.erb
61
66
  - app/views/active_authentication/passwords/edit.html.erb
@@ -70,7 +75,9 @@ files:
70
75
  - config/locales/es.yml
71
76
  - lib/active_authentication.rb
72
77
  - lib/active_authentication/controller.rb
78
+ - lib/active_authentication/controller/authenticatable.rb
73
79
  - lib/active_authentication/controller/lockable.rb
80
+ - lib/active_authentication/controller/timeoutable.rb
74
81
  - lib/active_authentication/controller/trackable.rb
75
82
  - lib/active_authentication/current.rb
76
83
  - lib/active_authentication/engine.rb
@@ -78,8 +85,11 @@ files:
78
85
  - lib/active_authentication/model/authenticatable.rb
79
86
  - lib/active_authentication/model/confirmable.rb
80
87
  - lib/active_authentication/model/lockable.rb
88
+ - lib/active_authentication/model/magiclinkable.rb
89
+ - lib/active_authentication/model/omniauthable.rb
81
90
  - lib/active_authentication/model/recoverable.rb
82
91
  - lib/active_authentication/model/registerable.rb
92
+ - lib/active_authentication/model/timeoutable.rb
83
93
  - lib/active_authentication/model/trackable.rb
84
94
  - lib/active_authentication/routes.rb
85
95
  - lib/active_authentication/test/helpers.rb
@@ -87,6 +97,7 @@ files:
87
97
  - lib/generators/active_authentication/install/install_generator.rb
88
98
  - lib/generators/active_authentication/install/templates/initializer.rb
89
99
  - lib/generators/active_authentication/install/templates/migration.rb
100
+ - lib/generators/active_authentication/omniauthable/omniauthable_generator.rb
90
101
  - lib/generators/active_authentication/views/views_generator.rb
91
102
  homepage: https://github.com/sinaptia/active_authentication
92
103
  licenses:
@@ -94,7 +105,8 @@ licenses:
94
105
  metadata:
95
106
  homepage_uri: https://github.com/sinaptia/active_authentication
96
107
  source_code_uri: https://github.com/sinaptia/active_authentication
97
- post_install_message:
108
+ changelog_uri: https://github.com/sinaptia/active_authentication/blob/main/CHANGELOG.md
109
+ post_install_message:
98
110
  rdoc_options: []
99
111
  require_paths:
100
112
  - lib
@@ -109,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
121
  - !ruby/object:Gem::Version
110
122
  version: '0'
111
123
  requirements: []
112
- rubygems_version: 3.0.3.1
113
- signing_key:
124
+ rubygems_version: 3.5.22
125
+ signing_key:
114
126
  specification_version: 4
115
127
  summary: A pure Rails authentication solution
116
128
  test_files: []