action_auth 0.3.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa8d3dbc8281ff24b428e82568470f21268c085d94352302c4f5bf4134041526
4
- data.tar.gz: e266314b2359d22db1983ae7a9a13ddda8fd589819c834ccb5fb5e28418176a6
3
+ metadata.gz: 6c85ae94ede51ba040295cfca1ec2f6a46f412642bc3e3a3ada8fd102bec41ab
4
+ data.tar.gz: bf218c5419f6cf1a7f8eb5b70f0cbb58f9af1710be3222ee0a333075af32e5c5
5
5
  SHA512:
6
- metadata.gz: dd44e5d7bc676a69f0b7d60196a68b02c77ea9c7c042faa3251acce7b937e6a13fa7f8e44d4c982dc1d45a28d6e29124d7489f06649bd97296278964c409d12e
7
- data.tar.gz: 288208466865e199fd803d5dc57fcb537467c166084be4812261083742de66d3314a86b287f612e261bfba7ed4ebd4c35ddcfd7199ad99de9aac8ea04937dbf1
6
+ metadata.gz: e09cef2c34868ff6e6bd0e4d81f6e5fa577d91c0b38d957f09b36aa48a2cf4a183b2ff0bc400c321eb03829aef23c3976188d108b3e9a2f6f887e2e3a86f7043
7
+ data.tar.gz: c3cce12a87a5bfdc1b785ed01cb2cbed07325f9e65150e4fe5e5a7b86c6f6a52f5357aad6d51510a9e87a92f389441b268462917f0ea619980d6d130bd681ad6
data/README.md CHANGED
@@ -22,6 +22,43 @@ user experience akin to that offered by the well-regarded Devise gem.
22
22
  7. [License](#license)
23
23
  8. [Credits](#credits)
24
24
 
25
+ ## Breaking Changes
26
+
27
+ With the release of v1.0.0, there are some breaking changes that have been introduced. The
28
+ biggest change is that the `ActionAuth::User` model now uses the table name of `users` instead
29
+ of `action_auth_users`. This was done to make it easier to integrate with your application
30
+ without having to worry about the table name. If you have an existing application that is
31
+ using ActionAuth, you will need to rename the table to `users` with a migration like
32
+
33
+ ```ruby
34
+ rename_table :action_auth_users, :users
35
+ ```
36
+
37
+ Coming from `v0.3.0` to `v1.0.0`, you will need to create a migration to rename the table and foreign keys.
38
+
39
+ ```ruby
40
+ class UpgradeActionAuth < ActiveRecord::Migration[7.1]
41
+ def change
42
+ rename_table :action_auth_users, :users
43
+
44
+ rename_table :action_auth_sessions, :sessions
45
+ rename_column :sessions, :action_auth_user_id, :user_id
46
+
47
+ rename_table :action_auth_webauthn_credentials, :webauthn_credentials
48
+ rename_column :webauthn_credentials, :action_auth_user_id, :user_id
49
+ end
50
+ end
51
+ ```
52
+
53
+ You will then need to undo the migrations where the foreign keys were added in cases where `foreign_key: true` was
54
+ changed to `foreign_key: { to_table: 'action_auth_users' }`. You can do this for each table with a migration like:
55
+
56
+ ```ruby
57
+ add_foreign_key :user_settings, :users, column: :user_id unless foreign_key_exists?(:user_settings, :users)
58
+ add_foreign_key :profiles, :users, column: :user_id unless foreign_key_exists?(:profiles, :users)
59
+ add_foreign_key :nfcs, :users, column: :user_id unless foreign_key_exists?(:nfcs, :users)
60
+ ```
61
+
25
62
  ## Installation
26
63
  Add this line to your application's Gemfile:
27
64
 
@@ -65,6 +102,7 @@ ActionAuth.configure do |config|
65
102
  config.webauthn_origin = "http://localhost:3000" # or "https://example.com"
66
103
  config.webauthn_rp_name = Rails.application.class.to_s.deconstantize
67
104
  config.verify_email_on_sign_in = true
105
+ config.magic_link_enabled = true
68
106
  config.default_from_email = "from@example.com"
69
107
  end
70
108
  ```
@@ -87,7 +125,7 @@ These are the planned features for ActionAuth. The ones that are checked off are
87
125
 
88
126
  ✅ - Passkeys/Hardware Security Keys
89
127
 
90
- - Magic Links
128
+ - Magic Links
91
129
 
92
130
  ⏳ - OAuth with Google, Facebook, Github, Twitter, etc.
93
131
 
@@ -235,37 +273,19 @@ We can set the user to become a User record instead of an ActionAuth::User recor
235
273
  class Current < ActiveSupport::CurrentAttributes
236
274
  def user
237
275
  return unless ActionAuth::Current.user
238
- ActionAuth::Current.user.becomes(User)
276
+ ActionAuth::Current.user&.becomes(User)
239
277
  end
240
278
  end
241
279
  ```
242
280
 
243
281
  #### Generating an association
244
282
 
245
- There's one little gotcha when generating the associations. We are using `user:belongs_to` instead of
246
- `action_auth_user:belongs_to`. However, when the foreign key is generated, it will look for the users table
247
- instead of the action_auth_users table. To get around this, we'll need to modify the migration.
283
+ We are using `user:belongs_to` instead of `action_auth_user:belongs_to`.
248
284
 
249
285
  ```bash
250
286
  bin/rails g scaffold posts user:belongs_to title
251
287
  ```
252
288
 
253
- We can update the `foreign_key` from `true` to `{ to_table: :action_auth_users }` to get around this.
254
-
255
- ```ruby
256
- # db/migrate/XXXXXXXXXXX_create_posts.rb
257
- class CreatePosts < ActiveRecord::Migration[7.1]
258
- def change
259
- create_table :posts do |t|
260
- t.belongs_to :user, null: false, foreign_key: { to_table: :action_auth_users }
261
- t.string :title
262
-
263
- t.timestamps
264
- end
265
- end
266
- end
267
- ```
268
-
269
289
  And the post model doesn't need anything special to ActionAuth.
270
290
 
271
291
  ```ruby
@@ -0,0 +1,20 @@
1
+ module ActionAuth
2
+ class Magics::RequestsController < ApplicationController
3
+ def new
4
+ end
5
+
6
+ def create
7
+ user = User.find_or_initialize_by(email: params[:email])
8
+ if user.new_record?
9
+ password = SecureRandom.hex(32)
10
+ user.password = password
11
+ user.password_confirmation = password
12
+ user.save!
13
+ end
14
+
15
+ UserMailer.with(user: user).magic_link.deliver_later
16
+
17
+ redirect_to sign_in_path, notice: "Check your email for a magic link."
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module ActionAuth
2
+ class Magics::SignInsController < ApplicationController
3
+ def show
4
+ user = ActionAuth::User.find_by_token_for(:magic_token, params[:token])
5
+ if user
6
+ @session = user.sessions.create
7
+ cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
8
+ user.update(verified: true)
9
+ redirect_to main_app.root_path, notice: "Signed In"
10
+ else
11
+ redirect_to sign_in_path, alert: "Authentication failed, please try again."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -12,7 +12,7 @@ module ActionAuth
12
12
  send_email_verification
13
13
  redirect_to sign_in_path, notice: "Welcome! You have signed up successfully. Please check your email to verify your account."
14
14
  else
15
- session_record = @user.action_auth_sessions.create!
15
+ session_record = @user.sessions.create!
16
16
  cookies.signed.permanent[:session_token] = { value: session_record.id, httponly: true }
17
17
 
18
18
  redirect_to sign_in_path, notice: "Welcome! You have signed up successfully"
@@ -5,7 +5,7 @@ module ActionAuth
5
5
 
6
6
  def index
7
7
  @action_auth_wide = true
8
- @sessions = Current.user.action_auth_sessions.order(created_at: :desc)
8
+ @sessions = Current.user.sessions.order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def new
@@ -18,7 +18,7 @@ module ActionAuth
18
18
  redirect_to new_webauthn_credential_authentications_path
19
19
  else
20
20
  return if check_if_email_is_verified(user)
21
- @session = user.action_auth_sessions.create
21
+ @session = user.sessions.create
22
22
  cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
23
23
  redirect_to main_app.root_path, notice: "Signed in successfully"
24
24
  end
@@ -28,7 +28,7 @@ module ActionAuth
28
28
  end
29
29
 
30
30
  def destroy
31
- session = Current.user.action_auth_sessions.find(params[:id])
31
+ session = Current.user.sessions.find(params[:id])
32
32
  session.destroy
33
33
  redirect_to main_app.root_path, notice: "That session has been logged out"
34
34
  end
@@ -4,7 +4,7 @@ class ActionAuth::WebauthnCredentialAuthenticationsController < ApplicationContr
4
4
  layout "action_auth/application"
5
5
 
6
6
  def new
7
- get_options = WebAuthn::Credential.options_for_get(allow: user.action_auth_webauthn_credentials.pluck(:external_id))
7
+ get_options = WebAuthn::Credential.options_for_get(allow: user.webauthn_credentials.pluck(:external_id))
8
8
  session[:current_challenge] = get_options.challenge
9
9
  @options = get_options
10
10
  end
@@ -12,7 +12,7 @@ class ActionAuth::WebauthnCredentialAuthenticationsController < ApplicationContr
12
12
  def create
13
13
  webauthn_credential = WebAuthn::Credential.from_get(params)
14
14
 
15
- credential = user.action_auth_webauthn_credentials.find_by(external_id: webauthn_credential.id)
15
+ credential = user.webauthn_credentials.find_by(external_id: webauthn_credential.id)
16
16
 
17
17
  begin
18
18
  webauthn_credential.verify(
@@ -23,7 +23,7 @@ class ActionAuth::WebauthnCredentialAuthenticationsController < ApplicationContr
23
23
 
24
24
  credential.update!(sign_count: webauthn_credential.sign_count)
25
25
  session.delete(:webauthn_user_id)
26
- session = user.action_auth_sessions.create
26
+ session = user.sessions.create
27
27
  cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
28
28
  render json: { status: "ok" }, status: :ok
29
29
  rescue WebAuthn::Error => e
@@ -15,7 +15,7 @@ class ActionAuth::WebauthnCredentialsController < ApplicationController
15
15
  id: current_user.webauthn_id,
16
16
  name: current_user.email
17
17
  },
18
- exclude: current_user.action_auth_webauthn_credentials.pluck(:external_id)
18
+ exclude: current_user.webauthn_credentials.pluck(:external_id)
19
19
  )
20
20
 
21
21
  session[:current_challenge] = create_options.challenge
@@ -34,7 +34,7 @@ class ActionAuth::WebauthnCredentialsController < ApplicationController
34
34
  begin
35
35
  webauthn_credential.verify(session[:current_challenge])
36
36
 
37
- credential = current_user.action_auth_webauthn_credentials.build(
37
+ credential = current_user.webauthn_credentials.build(
38
38
  external_id: webauthn_credential.id,
39
39
  nickname: params[:credential_nickname],
40
40
  public_key: webauthn_credential.public_key,
@@ -53,7 +53,7 @@ class ActionAuth::WebauthnCredentialsController < ApplicationController
53
53
  end
54
54
 
55
55
  def destroy
56
- current_user.action_auth_webauthn_credentials.destroy(params[:id])
56
+ current_user.webauthn_credentials.destroy(params[:id])
57
57
 
58
58
  redirect_to sessions_path
59
59
  end
@@ -13,5 +13,12 @@ module ActionAuth
13
13
 
14
14
  mail to: @user.email, subject: "Verify your email"
15
15
  end
16
+
17
+ def magic_link
18
+ @user = params[:user]
19
+ @signed_id = @user.generate_token_for(:magic_token)
20
+
21
+ mail to: @user.email, subject: "Sign in to your account"
22
+ end
16
23
  end
17
24
  end
@@ -3,10 +3,6 @@ module ActionAuth
3
3
  attribute :session
4
4
  attribute :user_agent, :ip_address
5
5
 
6
- delegate :action_auth_user, to: :session, allow_nil: true
7
-
8
- def user
9
- action_auth_user
10
- end
6
+ delegate :user, to: :session, allow_nil: true
11
7
  end
12
8
  end
@@ -1,6 +1,8 @@
1
1
  module ActionAuth
2
2
  class Session < ApplicationRecord
3
- belongs_to :action_auth_user, class_name: "ActionAuth::User", foreign_key: "action_auth_user_id"
3
+ self.table_name = "sessions"
4
+
5
+ belongs_to :user, class_name: "ActionAuth::User", foreign_key: "user_id"
4
6
 
5
7
  before_create do
6
8
  self.user_agent = Current.user_agent
@@ -1,13 +1,15 @@
1
1
  module ActionAuth
2
2
  class User < ApplicationRecord
3
+ self.table_name = "users"
4
+
3
5
  has_secure_password
4
6
 
5
- has_many :action_auth_sessions, dependent: :destroy,
6
- class_name: "ActionAuth::Session", foreign_key: "action_auth_user_id"
7
+ has_many :sessions, dependent: :destroy,
8
+ class_name: "ActionAuth::Session", foreign_key: "user_id"
7
9
 
8
10
  if ActionAuth.configuration.webauthn_enabled?
9
- has_many :action_auth_webauthn_credentials, dependent: :destroy,
10
- class_name: "ActionAuth::WebauthnCredential", foreign_key: "action_auth_user_id"
11
+ has_many :webauthn_credentials, dependent: :destroy,
12
+ class_name: "ActionAuth::WebauthnCredential", foreign_key: "user_id"
11
13
  end
12
14
 
13
15
  generates_token_for :email_verification, expires_in: 2.days do
@@ -18,6 +20,10 @@ module ActionAuth
18
20
  password_salt.last(10)
19
21
  end
20
22
 
23
+ generates_token_for :magic_token, expires_in: 20.minutes do
24
+ password_salt.last(10)
25
+ end
26
+
21
27
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
22
28
  validates :password, allow_nil: true, length: { minimum: 12 }
23
29
 
@@ -28,12 +34,12 @@ module ActionAuth
28
34
  end
29
35
 
30
36
  after_update if: :password_digest_previously_changed? do
31
- action_auth_sessions.where.not(id: Current.session).delete_all
37
+ sessions.where.not(id: Current.session).delete_all
32
38
  end
33
39
 
34
40
  def second_factor_enabled?
35
41
  return false unless ActionAuth.configuration.webauthn_enabled?
36
- action_auth_webauthn_credentials.any?
42
+ webauthn_credentials.any?
37
43
  end
38
44
  end
39
45
  end
@@ -1,5 +1,7 @@
1
1
  module ActionAuth
2
2
  class WebauthnCredential < ApplicationRecord
3
+ self.table_name = "webauthn_credentials"
4
+
3
5
  validates :external_id, :public_key, :nickname, :sign_count, presence: true
4
6
  validates :external_id, uniqueness: true
5
7
  validates :sign_count,
@@ -0,0 +1,21 @@
1
+ <h1>Sign up</h1>
2
+
3
+ <%= form_with(url: magics_requests_path) do |form| %>
4
+ <div class="mb-3">
5
+ <%= form.label :email, style: "display: block" %>
6
+ <%= form.email_field :email, required: true, autofocus: true, autocomplete: "email" %>
7
+ </div>
8
+
9
+ <div class="mb-3">
10
+ <%= form.submit "Request Magic Link", class: "btn btn-primary" %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="mb-3">
15
+ <%= link_to "Sign In", sign_in_path %> |
16
+ <%= link_to "Sign Up", sign_up_path %> |
17
+ <%= link_to "Reset Password", new_identity_password_reset_path %>
18
+ <% if ActionAuth.configuration.verify_email_on_sign_in %>
19
+ | <%= link_to "Verify Email", identity_email_verification_path %>
20
+ <% end %>
21
+ </div>
@@ -36,6 +36,9 @@
36
36
 
37
37
  <div class="mb-3">
38
38
  <%= link_to "Sign In", sign_in_path %> |
39
+ <% if ActionAuth.configuration.magic_link_enabled? %>
40
+ <%= link_to "Magic Link", new_magics_requests_path %> |
41
+ <% end %>
39
42
  <%= link_to "Reset Password", new_identity_password_reset_path %>
40
43
  <% if ActionAuth.configuration.verify_email_on_sign_in %>
41
44
  | <%= link_to "Verify Email", identity_email_verification_path %>
@@ -41,7 +41,7 @@
41
41
  </tr>
42
42
  </thead>
43
43
  <tbody>
44
- <% current_user.action_auth_webauthn_credentials.each do |credential| %>
44
+ <% current_user.webauthn_credentials.each do |credential| %>
45
45
  <%= content_tag :tr, id: dom_id(credential) do %>
46
46
  <td><%= credential.nickname %></td>
47
47
  <td nowrap><%= credential.created_at.strftime('%B %d, %Y') %></td>
@@ -21,6 +21,9 @@
21
21
 
22
22
  <div class="mb-3">
23
23
  <%= link_to "Sign Up", sign_up_path %> |
24
+ <% if ActionAuth.configuration.magic_link_enabled? %>
25
+ <%= link_to "Magic Link", new_magics_requests_path %> |
26
+ <% end %>
24
27
  <%= link_to "Reset Password", new_identity_password_reset_path %>
25
28
  <% if ActionAuth.configuration.verify_email_on_sign_in %>
26
29
  | <%= link_to "Verify Email", identity_email_verification_path %>
@@ -0,0 +1,3 @@
1
+ <p>
2
+ Use this <%= link_to "link", magics_sign_ins_url(token: @signed_id) %> to sign in.
3
+ </p>
data/config/routes.rb CHANGED
@@ -18,4 +18,11 @@ ActionAuth::Engine.routes.draw do
18
18
 
19
19
  resource :webauthn_credential_authentications, only: [:new, :create]
20
20
  end
21
+
22
+ if ActionAuth.configuration.magic_link_enabled?
23
+ namespace :magics do
24
+ resource :sign_ins, only: [:show]
25
+ resource :requests, only: [:new, :create]
26
+ end
27
+ end
21
28
  end
@@ -1,12 +1,12 @@
1
1
  class CreateActionAuthUsers < ActiveRecord::Migration[7.1]
2
2
  def change
3
- create_table :action_auth_users do |t|
3
+ create_table :users do |t|
4
4
  t.string :email
5
5
  t.string :password_digest
6
6
  t.boolean :verified
7
7
 
8
8
  t.timestamps
9
9
  end
10
- add_index :action_auth_users, :email, unique: true
10
+ add_index :users, :email, unique: true
11
11
  end
12
12
  end
@@ -1,7 +1,7 @@
1
1
  class CreateActionAuthSessions < ActiveRecord::Migration[7.1]
2
2
  def change
3
- create_table :action_auth_sessions do |t|
4
- t.references :action_auth_user, null: false, foreign_key: true
3
+ create_table :sessions do |t|
4
+ t.references :user, null: false, foreign_key: true
5
5
  t.string :user_agent
6
6
  t.string :ip_address
7
7
 
@@ -1,6 +1,6 @@
1
1
  class AddWebauthnCredentials < ActiveRecord::Migration[7.1]
2
2
  def change
3
- create_table :action_auth_webauthn_credentials do |t|
3
+ create_table :webauthn_credentials do |t|
4
4
  t.string :external_id, null: false
5
5
  t.string :public_key, null: false
6
6
  t.string :nickname, null: false
@@ -8,7 +8,7 @@ class AddWebauthnCredentials < ActiveRecord::Migration[7.1]
8
8
 
9
9
  t.index :external_id, unique: true
10
10
 
11
- t.references :action_auth_user, foreign_key: true
11
+ t.references :user, foreign_key: true
12
12
 
13
13
  t.timestamps
14
14
  end
@@ -1,5 +1,5 @@
1
1
  class AddWebauthnIdToUsers < ActiveRecord::Migration[7.1]
2
2
  def change
3
- add_column :action_auth_users, :webauthn_id, :string
3
+ add_column :users, :webauthn_id, :string
4
4
  end
5
5
  end
@@ -5,6 +5,7 @@ module ActionAuth
5
5
  attr_accessor :webauthn_origin
6
6
  attr_accessor :webauthn_rp_name
7
7
  attr_accessor :verify_email_on_sign_in
8
+ attr_accessor :magic_link_enabled
8
9
  attr_accessor :default_from_email
9
10
 
10
11
  def initialize
@@ -12,6 +13,7 @@ module ActionAuth
12
13
  @webauthn_origin = "http://localhost:3000"
13
14
  @webauthn_rp_name = Rails.application.class.to_s.deconstantize
14
15
  @verify_email_on_sign_in = true
16
+ @magic_link_enabled = true
15
17
  @default_from_email = "from@example.com"
16
18
  end
17
19
 
@@ -19,5 +21,9 @@ module ActionAuth
19
21
  @webauthn_enabled.respond_to?(:call) ? @webauthn_enabled.call : @webauthn_enabled
20
22
  end
21
23
 
24
+ def magic_link_enabled?
25
+ @magic_link_enabled.respond_to?(:call) ? @magic_link_enabled.call : @magic_link_enabled
26
+ end
27
+
22
28
  end
23
29
  end
@@ -1,3 +1,3 @@
1
1
  module ActionAuth
2
- VERSION = "0.3.0"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Kimura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-22 00:00:00.000000000 Z
11
+ date: 2024-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -56,6 +56,8 @@ files:
56
56
  - app/controllers/action_auth/identity/email_verifications_controller.rb
57
57
  - app/controllers/action_auth/identity/emails_controller.rb
58
58
  - app/controllers/action_auth/identity/password_resets_controller.rb
59
+ - app/controllers/action_auth/magics/requests_controller.rb
60
+ - app/controllers/action_auth/magics/sign_ins_controller.rb
59
61
  - app/controllers/action_auth/passwords_controller.rb
60
62
  - app/controllers/action_auth/registrations_controller.rb
61
63
  - app/controllers/action_auth/sessions_controller.rb
@@ -73,12 +75,14 @@ files:
73
75
  - app/views/action_auth/identity/emails/edit.html.erb
74
76
  - app/views/action_auth/identity/password_resets/edit.html.erb
75
77
  - app/views/action_auth/identity/password_resets/new.html.erb
78
+ - app/views/action_auth/magics/requests/new.html.erb
76
79
  - app/views/action_auth/passwords/edit.html.erb
77
80
  - app/views/action_auth/registrations/new.html.erb
78
81
  - app/views/action_auth/sessions/index.html.erb
79
82
  - app/views/action_auth/sessions/new.html.erb
80
83
  - app/views/action_auth/user_mailer/email_verification.html.erb
81
84
  - app/views/action_auth/user_mailer/email_verification.text.erb
85
+ - app/views/action_auth/user_mailer/magic_link.html.erb
82
86
  - app/views/action_auth/user_mailer/password_reset.html.erb
83
87
  - app/views/action_auth/user_mailer/password_reset.text.erb
84
88
  - app/views/action_auth/webauthn_credential_authentications/new.html.erb
@@ -120,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
124
  - !ruby/object:Gem::Version
121
125
  version: '0'
122
126
  requirements: []
123
- rubygems_version: 3.5.6
127
+ rubygems_version: 3.5.16
124
128
  signing_key:
125
129
  specification_version: 4
126
130
  summary: A simple Rails engine for authorization.