action_auth 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.