action_auth 1.8.9 → 2.0.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -0
  3. data/README.md +485 -36
  4. data/Rakefile +0 -2
  5. data/app/assets/javascripts/action_auth/application.js +72 -71
  6. data/app/assets/stylesheets/action_auth/application.css +38 -1
  7. data/app/controllers/action_auth/application_controller.rb +122 -1
  8. data/app/controllers/action_auth/connected_accounts_controller.rb +16 -0
  9. data/app/controllers/action_auth/identity/email_verifications_controller.rb +36 -5
  10. data/app/controllers/action_auth/identity/emails_controller.rb +16 -4
  11. data/app/controllers/action_auth/identity/password_resets_controller.rb +20 -22
  12. data/app/controllers/action_auth/magics/requests_controller.rb +27 -6
  13. data/app/controllers/action_auth/magics/sign_ins_controller.rb +22 -7
  14. data/app/controllers/action_auth/passwords_controller.rb +7 -18
  15. data/app/controllers/action_auth/provider_callbacks_controller.rb +147 -0
  16. data/app/controllers/action_auth/registrations_controller.rb +7 -25
  17. data/app/controllers/action_auth/sessions/passkeys_controller.rb +17 -9
  18. data/app/controllers/action_auth/sessions_controller.rb +27 -25
  19. data/app/controllers/action_auth/sms_auths/requests_controller.rb +40 -14
  20. data/app/controllers/action_auth/sms_auths/sign_ins_controller.rb +40 -15
  21. data/app/controllers/action_auth/users_controller.rb +35 -1
  22. data/app/controllers/action_auth/webauthn_credential_authentications_controller.rb +36 -51
  23. data/app/controllers/action_auth/webauthn_credentials_controller.rb +63 -51
  24. data/app/helpers/action_auth/application_helper.rb +83 -0
  25. data/app/mailers/action_auth/application_mailer.rb +1 -1
  26. data/app/mailers/action_auth/user_mailer.rb +7 -0
  27. data/app/models/action_auth/connected_account.rb +18 -0
  28. data/app/models/action_auth/session.rb +9 -1
  29. data/app/models/action_auth/user.rb +197 -4
  30. data/app/models/action_auth/webauthn_credential.rb +2 -0
  31. data/app/views/action_auth/identity/email_verifications/show.html.erb +23 -0
  32. data/app/views/action_auth/identity/emails/edit.html.erb +41 -42
  33. data/app/views/action_auth/identity/password_resets/edit.html.erb +10 -11
  34. data/app/views/action_auth/identity/password_resets/new.html.erb +4 -9
  35. data/app/views/action_auth/magics/requests/new.html.erb +4 -14
  36. data/app/views/action_auth/magics/sign_ins/show.html.erb +18 -0
  37. data/app/views/action_auth/passwords/edit.html.erb +9 -21
  38. data/app/views/action_auth/registrations/new.html.erb +5 -27
  39. data/app/views/action_auth/sessions/_account.html.erb +30 -0
  40. data/app/views/action_auth/sessions/_connected_accounts.html.erb +32 -0
  41. data/app/views/action_auth/sessions/_security_keys.html.erb +32 -0
  42. data/app/views/action_auth/sessions/_sessions.html.erb +28 -0
  43. data/app/views/action_auth/sessions/index.html.erb +10 -62
  44. data/app/views/action_auth/sessions/new.html.erb +3 -21
  45. data/app/views/action_auth/sessions/passkeys/new.html.erb +10 -4
  46. data/app/views/action_auth/shared/_errors.html.erb +10 -0
  47. data/app/views/action_auth/shared/_flash.html.erb +11 -0
  48. data/app/views/action_auth/shared/_links.html.erb +3 -0
  49. data/app/views/action_auth/shared/_oauth_buttons.html.erb +5 -0
  50. data/app/views/action_auth/shared/_webauthn_error.html.erb +1 -0
  51. data/app/views/action_auth/sms_auths/requests/new.html.erb +4 -14
  52. data/app/views/action_auth/sms_auths/sign_ins/show.html.erb +6 -2
  53. data/app/views/action_auth/user_mailer/account_locked.html.erb +17 -0
  54. data/app/views/action_auth/user_mailer/account_locked.text.erb +12 -0
  55. data/app/views/action_auth/user_mailer/email_verification.html.erb +5 -0
  56. data/app/views/action_auth/user_mailer/email_verification.text.erb +3 -0
  57. data/app/views/action_auth/user_mailer/magic_link.html.erb +4 -0
  58. data/app/views/action_auth/user_mailer/magic_link.text.erb +5 -0
  59. data/app/views/action_auth/user_mailer/password_reset.text.erb +1 -1
  60. data/app/views/action_auth/webauthn_credential_authentications/new.html.erb +9 -3
  61. data/app/views/action_auth/webauthn_credentials/new.html.erb +10 -3
  62. data/app/views/layouts/action_auth/application.html.erb +2 -7
  63. data/config/routes.rb +13 -3
  64. data/db/migrate/20261105120000_add_magic_link_used_at_to_users.rb +5 -0
  65. data/db/migrate/20261105130000_add_account_lockout_and_suspension_to_users.rb +7 -0
  66. data/db/migrate/20261105130100_create_action_auth_connected_accounts.rb +19 -0
  67. data/lib/action_auth/configuration.rb +96 -0
  68. data/lib/action_auth/controllers/helpers.rb +44 -11
  69. data/lib/action_auth/engine.rb +27 -1
  70. data/lib/action_auth/routing/helpers.rb +8 -0
  71. data/lib/action_auth/version.rb +1 -1
  72. data/lib/generators/action_auth/views/USAGE +8 -0
  73. data/lib/generators/action_auth/views/views_generator.rb +59 -0
  74. data/lib/tasks/action_auth_tasks.rake +19 -1
  75. metadata +28 -5
@@ -1,5 +1,11 @@
1
+ <%= render "action_auth/shared/flash" %>
2
+
3
+ <%= action_auth_webauthn_tags %>
4
+
1
5
  <h2 class="action-auth--text-center">Use a passkey to sign in</h2>
2
6
 
7
+ <%= render "action_auth/shared/webauthn_error" %>
8
+
3
9
  <%= content_tag :div,
4
10
  id: "webauthn_credential_form",
5
11
  data: {
@@ -13,6 +19,6 @@
13
19
  </div>
14
20
  <% end %>
15
21
 
16
- <%= content_for :cancel_path do %>
17
- <%= link_to "Cancel", action_auth.sign_in_path %>
18
- <% end %>
22
+ <div class="action-auth--text-center">
23
+ <%= link_to "Cancel", sign_in_path %>
24
+ </div>
@@ -1,4 +1,11 @@
1
+ <%= render "action_auth/shared/flash" %>
2
+
3
+ <%= action_auth_webauthn_tags %>
4
+
1
5
  <h2 class="action-auth--text-center">Add a security key</h2>
6
+
7
+ <%= render "action_auth/shared/webauthn_error" %>
8
+
2
9
  <%= form_with scope: :webauthn_credential,
3
10
  url: options_for_webauthn_credentials_path,
4
11
  id: "webauthn_credential_form",
@@ -20,6 +27,6 @@
20
27
  </div>
21
28
  <% end %>
22
29
 
23
- <%= content_for :cancel_path do %>
24
- <%= link_to "Cancel", action_auth.sessions_path %>
25
- <% end %>
30
+ <div class="action-auth--text-center">
31
+ <%= link_to "Cancel", sessions_path %>
32
+ </div>
@@ -1,17 +1,12 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Action Auth</title>
4
+ <title><%= content_for(:title).presence || "Action Auth" %></title>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
8
8
  <%= stylesheet_link_tag "action_auth/application", media: "all" %>
9
- <%= javascript_include_tag "action_auth/application", "data-turbo-track": "reload", type: "module" %>
10
- <% if ActionAuth.configuration.webauthn_enabled? %>
11
- <%= tag :meta, name: :webauthn_auth_url, content: action_auth.webauthn_credential_authentications_url %>
12
- <%= tag :meta, name: :webauthn_cred_url, content: action_auth.webauthn_credentials_url %>
13
- <%= tag :meta, name: :webauthn_redirect_url, content: action_auth.sessions_url %>
14
- <% end %>
9
+ <%= action_auth_webauthn_tags if action_auth_webauthn_screen? %>
15
10
  </head>
16
11
  <body class="bg-light">
17
12
  <div class="<%= defined?(@action_auth_wide) ? 'container-fluid' : 'container' %> bg-white border pb-3">
data/config/routes.rb CHANGED
@@ -6,7 +6,7 @@ ActionAuth::Engine.routes.draw do
6
6
 
7
7
  namespace :identity do
8
8
  resource :email, only: [:edit, :update]
9
- resource :email_verification, only: [:show, :create]
9
+ resource :email_verification, only: [:show, :create, :update]
10
10
  resource :password_reset, only: [:new, :edit, :create, :update]
11
11
  end
12
12
  resource :password, only: [:edit, :update]
@@ -15,7 +15,7 @@ ActionAuth::Engine.routes.draw do
15
15
  resources :passkeys, only: [:new, :create]
16
16
  end
17
17
  end
18
- resources :sessions, only: [:index, :show, :destroy]
18
+ resources :sessions, only: [:index, :destroy]
19
19
 
20
20
  if ActionAuth.configuration.allow_user_deletion?
21
21
  resource :users, only: [:destroy]
@@ -31,7 +31,7 @@ ActionAuth::Engine.routes.draw do
31
31
 
32
32
  if ActionAuth.configuration.magic_link_enabled?
33
33
  namespace :magics do
34
- resource :sign_ins, only: [:show]
34
+ resource :sign_ins, only: [:show, :create]
35
35
  resource :requests, only: [:new, :create]
36
36
  end
37
37
  end
@@ -42,4 +42,14 @@ ActionAuth::Engine.routes.draw do
42
42
  resource :requests, only: [:new, :create]
43
43
  end
44
44
  end
45
+
46
+ if ActionAuth.configuration.oauth_enabled?
47
+ # OmniAuth answers POST auth/:provider for every configured provider before
48
+ # the router sees it. This route names the path, and catches any other provider.
49
+ post "auth/:provider", to: "provider_callbacks#failure", as: :oauth_request
50
+ get "auth/failure", to: "provider_callbacks#failure", as: :oauth_failure
51
+ match "auth/:provider/callback", to: "provider_callbacks#create", as: :oauth_callback, via: [:get, :post]
52
+
53
+ resources :connected_accounts, only: [:destroy]
54
+ end
45
55
  end
@@ -0,0 +1,5 @@
1
+ class AddMagicLinkUsedAtToUsers < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :users, :magic_link_used_at, :datetime
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class AddAccountLockoutAndSuspensionToUsers < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :users, :failed_sign_in_attempts, :integer, null: false, default: 0
4
+ add_column :users, :account_locked_at, :datetime
5
+ add_column :users, :account_suspended_at, :datetime
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class CreateActionAuthConnectedAccounts < ActiveRecord::Migration[7.2]
2
+ def change
3
+ # Copied from the existing column, because an app's users.id isn't always a
4
+ # bigint, and a foreign key of another type is refused by some databases.
5
+ user_id_type = columns(:users).find { |column| column.name == "id" }.sql_type
6
+
7
+ create_table :action_auth_connected_accounts do |t|
8
+ # Cascades so that deleting a user works even if OAuth has since been turned off.
9
+ t.references :user, null: false, type: user_id_type, foreign_key: { on_delete: :cascade }
10
+ t.string :provider, null: false
11
+ t.string :uid, null: false
12
+ t.string :display_name
13
+
14
+ t.timestamps
15
+
16
+ t.index [:provider, :uid], unique: true
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,20 @@
1
1
  module ActionAuth
2
2
  class Configuration
3
+ OAUTH_PROVIDER_LABELS = {
4
+ "facebook" => "Facebook",
5
+ "github" => "GitHub",
6
+ "google_oauth2" => "Google",
7
+ "twitter" => "Twitter",
8
+ "twitter2" => "X"
9
+ }.freeze
10
+
11
+ # One OmniAuth strategy and the arguments to start it with. The key is what
12
+ # OmniAuth calls it, and appears in its paths: auth/:key and auth/:key/callback.
13
+ OauthProvider = Struct.new(:strategy, :args, :options, :label) do
14
+ def key
15
+ (options[:name] || strategy).to_s
16
+ end
17
+ end
3
18
 
4
19
  attr_accessor :allow_user_deletion
5
20
  attr_accessor :default_from_email
@@ -15,8 +30,41 @@ module ActionAuth
15
30
  attr_accessor :password_complexity_check
16
31
  attr_accessor :session_timeout
17
32
 
33
+ # Temporarily stops password and SMS code sign in after too many failures in a
34
+ # row. A nil duration keeps the account locked until it's unlocked or the
35
+ # password is reset.
36
+ attr_accessor :account_lockout_enabled
37
+ attr_accessor :account_lockout_max_attempts
38
+ attr_accessor :account_lockout_duration
39
+
40
+ # Lets your app block an account from signing in at all, with
41
+ # user.suspend_account!. ActionAuth never suspends anyone by itself.
42
+ attr_accessor :account_suspension_enabled
43
+
44
+ # Sign in through other sites. Needs the omniauth gem and at least one
45
+ # config.oauth_provider. Must be set in an initializer, not in an
46
+ # after_initialize block, because the routes depend on it.
47
+ attr_accessor :oauth_enabled
48
+ attr_reader :oauth_providers
49
+
18
50
  attr_accessor :insert_cookie_domain
19
51
 
52
+ # The layout for ActionAuth's screens, e.g. "application". The default (nil)
53
+ # uses layouts/action_auth/application, which an app can also override by
54
+ # creating that file.
55
+ attr_accessor :layout
56
+
57
+ # The class ActionAuth's controllers inherit from, as a String. Set this to
58
+ # "ApplicationController" when a custom layout or view needs your app's
59
+ # helpers. Must be set in an initializer, not in an after_initialize block.
60
+ attr_accessor :parent_controller
61
+
62
+ # Where to send people after signing in and out. Either a path String or a
63
+ # Proc evaluated in the controller, e.g. -> { main_app.dashboard_path }.
64
+ # The default (nil) is your app's root path.
65
+ attr_accessor :after_sign_in_path
66
+ attr_accessor :after_sign_out_path
67
+
20
68
  def initialize
21
69
  @allow_user_deletion = true
22
70
  @default_from_email = Rails.application.config.action_mailer.default_options&.dig(:from) || "noreply@#{ENV['HOST'] || 'example.com'}"
@@ -32,7 +80,20 @@ module ActionAuth
32
80
  @webauthn_rp_name = Rails.application.class.to_s.deconstantize
33
81
  @session_timeout = 2.weeks
34
82
 
83
+ @account_lockout_enabled = true
84
+ @account_lockout_max_attempts = 10
85
+ @account_lockout_duration = 30.minutes
86
+ @account_suspension_enabled = true
87
+
88
+ @oauth_enabled = defined?(OmniAuth)
89
+ @oauth_providers = []
90
+
35
91
  @insert_cookie_domain = false
92
+
93
+ @layout = nil
94
+ @parent_controller = "ActionController::Base"
95
+ @after_sign_in_path = nil
96
+ @after_sign_out_path = nil
36
97
  end
37
98
 
38
99
  def allow_user_deletion?
@@ -51,11 +112,17 @@ module ActionAuth
51
112
  webauthn_enabled? && @passkey_only == true
52
113
  end
53
114
 
115
+ # The webauthn and pwned gems are optional, so these can only be on when the
116
+ # gem is actually loaded, whatever the initializer says.
54
117
  def webauthn_enabled?
118
+ return false unless defined?(WebAuthn)
119
+
55
120
  @webauthn_enabled.respond_to?(:call) ? @webauthn_enabled.call : @webauthn_enabled
56
121
  end
57
122
 
58
123
  def pwned_enabled?
124
+ return false unless defined?(Pwned)
125
+
59
126
  @pwned_enabled.respond_to?(:call) ? @pwned_enabled.call : @pwned_enabled
60
127
  end
61
128
 
@@ -63,5 +130,34 @@ module ActionAuth
63
130
  @password_complexity_check == true
64
131
  end
65
132
 
133
+ def account_lockout_enabled?
134
+ @account_lockout_enabled == true
135
+ end
136
+
137
+ def account_suspension_enabled?
138
+ @account_suspension_enabled == true
139
+ end
140
+
141
+ # Adds a provider. Takes the same arguments as OmniAuth's provider method,
142
+ # plus an optional label for the buttons:
143
+ #
144
+ # config.oauth_provider :github, ENV["GITHUB_ID"], ENV["GITHUB_SECRET"], scope: "user:email"
145
+ def oauth_provider(strategy, *args, label: nil, **options)
146
+ provider = OauthProvider.new(strategy, args, options)
147
+ provider.label = label || OAUTH_PROVIDER_LABELS.fetch(provider.key) { provider.key.titleize }
148
+
149
+ @oauth_providers.reject! { |existing| existing.key == provider.key }
150
+ @oauth_providers << provider
151
+ end
152
+
153
+ # Having the gem isn't enough, since an app may use OmniAuth for something of
154
+ # its own. It takes a provider added here for ActionAuth to get involved.
155
+ def oauth_enabled?
156
+ return false unless defined?(OmniAuth)
157
+ return false if @oauth_providers.empty?
158
+
159
+ @oauth_enabled.respond_to?(:call) ? @oauth_enabled.call : @oauth_enabled
160
+ end
161
+
66
162
  end
67
163
  end
@@ -3,9 +3,12 @@ module ActionAuth
3
3
  module Helpers
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ ACCOUNT_SUSPENDED_MESSAGE = "This account has been suspended.".freeze
7
+
6
8
  included do
7
9
  before_action :set_current_request_details
8
10
  before_action :check_session_timeout
11
+ before_action :check_action_auth_suspension
9
12
 
10
13
  def current_user; Current.user; end
11
14
  helper_method :current_user
@@ -21,12 +24,19 @@ module ActionAuth
21
24
  redirect_to new_user_session_path
22
25
  end
23
26
  helper_method :authenticate_user!
27
+
28
+ # True inside ActionAuth's own controllers. Lets an app that uses
29
+ # config.parent_controller exempt them from its other filters:
30
+ # before_action :require_subscription, unless: :action_auth_controller?
31
+ def action_auth_controller?; false; end
32
+ helper_method :action_auth_controller?
24
33
  end
25
34
 
26
35
  private
27
36
 
28
37
  def set_current_request_details
29
- Current.session = Session.find_by(id: cookies.signed[:session_token])
38
+ session_token = cookies.signed[:session_token]
39
+ Current.session = Session.find_by(id: session_token) if session_token.present?
30
40
  Current.user_agent = request.user_agent
31
41
  Current.ip_address = request.ip
32
42
 
@@ -35,28 +45,51 @@ module ActionAuth
35
45
  Current.session.user_agent != Current.user_agent)
36
46
  Rails.logger.warn "Session environment changed for user #{Current.user&.id}: IP or User-Agent mismatch"
37
47
  # Optional: Force re-authentication for suspicious activity
38
- # cookies.delete(:session_token, secure: Rails.env.production?, same_site: :lax)
48
+ # delete_action_auth_session_cookie
39
49
  # Current.session = nil
40
50
  end
41
51
  end
42
52
 
43
53
  def check_session_timeout
44
- return unless Current.session
45
- return if Rails.env.test?
46
-
47
54
  timeout = ActionAuth.configuration.session_timeout
55
+ return unless Current.session && timeout
56
+
48
57
  if Current.session.updated_at < timeout.ago
49
- Rails.logger.info "Session timed out for user #{Current.user&.id}"
58
+ Rails.logger.info "Session timed out for user #{Current.session.user_id}"
50
59
  Current.session.destroy
51
- cookie_options = {}
52
- cookie_options[:secure] = Rails.env.production? if Rails.env.production?
53
- cookie_options[:same_site] = :lax unless Rails.env.test?
54
- cookies.delete(:session_token, cookie_options)
60
+ Current.session = nil
61
+ delete_action_auth_session_cookie
55
62
  redirect_to new_user_session_path, alert: "Your session has expired. Please sign in again."
56
- else
63
+ elsif Current.session.updated_at < 1.minute.ago
64
+ # Inactivity is tracked to the minute, so most requests don't need a write.
57
65
  Current.session.touch
58
66
  end
59
67
  end
68
+
69
+ # suspend_account! already ends the account's sessions. This catches the rest:
70
+ # a suspension set some other way, or a sign in that finished at the same moment.
71
+ def check_action_auth_suspension
72
+ return unless Current.session && User.account_suspension_available?
73
+ return unless Current.session.user.account_suspended?
74
+
75
+ Rails.logger.info "Ended a session of suspended user #{Current.session.user_id}"
76
+ Current.session.destroy
77
+ Current.session = nil
78
+ delete_action_auth_session_cookie
79
+ redirect_to new_user_session_path, alert: ACCOUNT_SUSPENDED_MESSAGE
80
+ end
81
+
82
+ def action_auth_session_cookie_options
83
+ options = { httponly: true, same_site: :lax }
84
+ options[:secure] = true if Rails.env.production? || request.ssl?
85
+ options[:domain] = :all if ActionAuth.configuration.insert_cookie_domain
86
+ options
87
+ end
88
+
89
+ # A cookie can only be deleted with the domain it was set with.
90
+ def delete_action_auth_session_cookie
91
+ cookies.delete(:session_token, action_auth_session_cookie_options)
92
+ end
60
93
  end
61
94
  end
62
95
  end
@@ -14,8 +14,34 @@ module ActionAuth
14
14
  ActiveSupport.on_load :action_controller_base do
15
15
  helper_method :user_sessions_path, :user_session_path, :new_user_session_path
16
16
  helper_method :new_user_registration_path
17
- helper_method :edit_user_password_path
17
+ helper_method :edit_user_password_path, :user_password_path
18
18
  end
19
19
  end
20
+
21
+ # Propshaft picks up engine assets by itself; Sprockets needs to be told.
22
+ initializer 'action_auth.assets' do |app|
23
+ app.config.assets.precompile << "action_auth_manifest.js" if defined?(Sprockets::Rails)
24
+ end
25
+
26
+ # OmniAuth runs inside the engine, so its paths follow wherever the app mounts
27
+ # ActionAuth and the rest of the app never goes through it.
28
+ initializer 'action_auth.oauth', after: :load_config_initializers do
29
+ next unless ActionAuth.configuration.oauth_enabled?
30
+
31
+ OmniAuth.config.logger = Rails.logger
32
+
33
+ config.middleware.use(OmniAuth::Builder) do
34
+ ActionAuth.configuration.oauth_providers.each do |oauth|
35
+ provider(oauth.strategy, *oauth.args, **oauth.options)
36
+ end
37
+ end
38
+ end
39
+
40
+ # An app that keeps the generated, fully commented out initializer never
41
+ # calls ActionAuth.configure, but still gets WebAuthn routes if the gem is
42
+ # installed. Those need the origin set too.
43
+ config.after_initialize do
44
+ ActionAuth.configure_webauthn
45
+ end
20
46
  end
21
47
  end
@@ -17,6 +17,14 @@ module ActionAuth
17
17
  action_auth.sign_up_path
18
18
  end
19
19
 
20
+ def edit_user_password_path
21
+ action_auth.edit_password_path
22
+ end
23
+
24
+ def user_password_path
25
+ action_auth.password_path
26
+ end
27
+
20
28
  def edit_password_path
21
29
  action_auth.edit_password_path
22
30
  end
@@ -1,3 +1,3 @@
1
1
  module ActionAuth
2
- VERSION = "1.8.9"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies ActionAuth's views into app/views/action_auth so you can edit them.
3
+ With no arguments it copies everything. Pass scopes to copy only some.
4
+
5
+ Examples:
6
+ bin/rails generate action_auth:views
7
+ bin/rails generate action_auth:views sessions registrations
8
+ bin/rails generate action_auth:views sessions --layout
@@ -0,0 +1,59 @@
1
+ require "rails/generators"
2
+
3
+ module ActionAuth
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ SCOPES = %w[
7
+ sessions
8
+ registrations
9
+ passwords
10
+ identity
11
+ magics
12
+ sms_auths
13
+ webauthn_credentials
14
+ webauthn_credential_authentications
15
+ user_mailer
16
+ shared
17
+ ].freeze
18
+
19
+ source_root File.expand_path("../../../../app/views", __dir__)
20
+
21
+ desc "Copies ActionAuth's views into your app so you can change them."
22
+
23
+ argument :scopes, type: :array, default: [], banner: "scope scope ...",
24
+ desc: "Only copy these groups of views: #{SCOPES.join(', ')}"
25
+
26
+ class_option :layout, type: :boolean, default: false,
27
+ desc: "Also copy layouts/action_auth/application.html.erb"
28
+
29
+ def check_scopes
30
+ unknown = scopes - SCOPES
31
+ return if unknown.empty?
32
+
33
+ raise Thor::Error, "Unknown scope: #{unknown.join(', ')}. Choose from: #{SCOPES.join(', ')}"
34
+ end
35
+
36
+ def copy_views
37
+ (scopes.presence || SCOPES).each do |scope|
38
+ directory "action_auth/#{scope}", "app/views/action_auth/#{scope}"
39
+ end
40
+ end
41
+
42
+ def copy_layout
43
+ return unless options[:layout]
44
+
45
+ copy_file "layouts/action_auth/application.html.erb", "app/views/layouts/action_auth/application.html.erb"
46
+ end
47
+
48
+ def show_next_steps
49
+ say <<~MESSAGE
50
+
51
+ Your copies now take priority over the ones in the gem, but they also
52
+ won't pick up changes from future ActionAuth versions. Delete any you
53
+ didn't change so they keep following the gem.
54
+
55
+ MESSAGE
56
+ end
57
+ end
58
+ end
59
+ end
@@ -19,6 +19,23 @@ namespace :action_auth do
19
19
  # config.webauthn_origin = "http://localhost:3000" # or "https://example.com"
20
20
  # config.webauthn_rp_name = Rails.application.class.to_s.deconstantize
21
21
  # config.insert_cookie_domain = false
22
+ # config.password_complexity_check = true
23
+ # config.session_timeout = 2.weeks
24
+ #
25
+ # # Both are on by default. See the ActionAuth README.
26
+ # config.account_lockout_enabled = true
27
+ # config.account_lockout_max_attempts = 10
28
+ # config.account_lockout_duration = 30.minutes
29
+ # config.account_suspension_enabled = true
30
+ #
31
+ # # Sign in through other sites. Needs the omniauth gems, see "OAuth" in the ActionAuth README.
32
+ # config.oauth_provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email"
33
+ #
34
+ # # Customizing the screens. See "Customizing" in the ActionAuth README.
35
+ # config.layout = "application"
36
+ # config.parent_controller = "ApplicationController"
37
+ # config.after_sign_in_path = "/dashboard" # or -> { main_app.dashboard_path }
38
+ # config.after_sign_out_path = "/"
22
39
  # end
23
40
  #
24
41
  # Rails.application.config.after_initialize do
@@ -42,7 +59,8 @@ namespace :action_auth do
42
59
  routes_file_path = Rails.root.join('config', 'routes.rb')
43
60
  route_line = "mount ActionAuth::Engine => \"/action_auth\""
44
61
  routes_content = File.read(routes_file_path)
45
- unless routes_content.include?(route_line)
62
+ # Any mount counts, however it's written. Mounting twice stops the app from booting.
63
+ unless routes_content.match?(/^[^#\n]*ActionAuth::Engine/)
46
64
  insert_after = "Rails.application.routes.draw do"
47
65
  new_routes_content = routes_content.sub(/(#{insert_after})/i, "\\1\n #{route_line}\n")
48
66
  File.open(routes_file_path, 'w') do |file|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.9
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Kimura
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.1'
18
+ version: '7.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '7.1'
25
+ version: '7.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bcrypt
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -45,6 +45,7 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - CHANGELOG.md
48
49
  - MIT-LICENSE
49
50
  - README.md
50
51
  - Rakefile
@@ -52,12 +53,14 @@ files:
52
53
  - app/assets/javascripts/action_auth/application.js
53
54
  - app/assets/stylesheets/action_auth/application.css
54
55
  - app/controllers/action_auth/application_controller.rb
56
+ - app/controllers/action_auth/connected_accounts_controller.rb
55
57
  - app/controllers/action_auth/identity/email_verifications_controller.rb
56
58
  - app/controllers/action_auth/identity/emails_controller.rb
57
59
  - app/controllers/action_auth/identity/password_resets_controller.rb
58
60
  - app/controllers/action_auth/magics/requests_controller.rb
59
61
  - app/controllers/action_auth/magics/sign_ins_controller.rb
60
62
  - app/controllers/action_auth/passwords_controller.rb
63
+ - app/controllers/action_auth/provider_callbacks_controller.rb
61
64
  - app/controllers/action_auth/registrations_controller.rb
62
65
  - app/controllers/action_auth/sessions/passkeys_controller.rb
63
66
  - app/controllers/action_auth/sessions_controller.rb
@@ -71,24 +74,39 @@ files:
71
74
  - app/mailers/action_auth/application_mailer.rb
72
75
  - app/mailers/action_auth/user_mailer.rb
73
76
  - app/models/action_auth/application_record.rb
77
+ - app/models/action_auth/connected_account.rb
74
78
  - app/models/action_auth/current.rb
75
79
  - app/models/action_auth/session.rb
76
80
  - app/models/action_auth/user.rb
77
81
  - app/models/action_auth/webauthn_credential.rb
82
+ - app/views/action_auth/identity/email_verifications/show.html.erb
78
83
  - app/views/action_auth/identity/emails/edit.html.erb
79
84
  - app/views/action_auth/identity/password_resets/edit.html.erb
80
85
  - app/views/action_auth/identity/password_resets/new.html.erb
81
86
  - app/views/action_auth/magics/requests/new.html.erb
87
+ - app/views/action_auth/magics/sign_ins/show.html.erb
82
88
  - app/views/action_auth/passwords/edit.html.erb
83
89
  - app/views/action_auth/registrations/new.html.erb
90
+ - app/views/action_auth/sessions/_account.html.erb
91
+ - app/views/action_auth/sessions/_connected_accounts.html.erb
92
+ - app/views/action_auth/sessions/_security_keys.html.erb
93
+ - app/views/action_auth/sessions/_sessions.html.erb
84
94
  - app/views/action_auth/sessions/index.html.erb
85
95
  - app/views/action_auth/sessions/new.html.erb
86
96
  - app/views/action_auth/sessions/passkeys/new.html.erb
97
+ - app/views/action_auth/shared/_errors.html.erb
98
+ - app/views/action_auth/shared/_flash.html.erb
99
+ - app/views/action_auth/shared/_links.html.erb
100
+ - app/views/action_auth/shared/_oauth_buttons.html.erb
101
+ - app/views/action_auth/shared/_webauthn_error.html.erb
87
102
  - app/views/action_auth/sms_auths/requests/new.html.erb
88
103
  - app/views/action_auth/sms_auths/sign_ins/show.html.erb
104
+ - app/views/action_auth/user_mailer/account_locked.html.erb
105
+ - app/views/action_auth/user_mailer/account_locked.text.erb
89
106
  - app/views/action_auth/user_mailer/email_verification.html.erb
90
107
  - app/views/action_auth/user_mailer/email_verification.text.erb
91
108
  - app/views/action_auth/user_mailer/magic_link.html.erb
109
+ - app/views/action_auth/user_mailer/magic_link.text.erb
92
110
  - app/views/action_auth/user_mailer/password_reset.html.erb
93
111
  - app/views/action_auth/user_mailer/password_reset.text.erb
94
112
  - app/views/action_auth/webauthn_credential_authentications/new.html.erb
@@ -103,12 +121,17 @@ files:
103
121
  - db/migrate/20240111142545_add_webauthn_id_to_users.rb
104
122
  - db/migrate/20240818032321_add_type_to_webauthn_credentials.rb
105
123
  - db/migrate/20241020172209_add_phone_number_to_users.rb
124
+ - db/migrate/20261105120000_add_magic_link_used_at_to_users.rb
125
+ - db/migrate/20261105130000_add_account_lockout_and_suspension_to_users.rb
126
+ - db/migrate/20261105130100_create_action_auth_connected_accounts.rb
106
127
  - lib/action_auth.rb
107
128
  - lib/action_auth/configuration.rb
108
129
  - lib/action_auth/controllers/helpers.rb
109
130
  - lib/action_auth/engine.rb
110
131
  - lib/action_auth/routing/helpers.rb
111
132
  - lib/action_auth/version.rb
133
+ - lib/generators/action_auth/views/USAGE
134
+ - lib/generators/action_auth/views/views_generator.rb
112
135
  - lib/tasks/action_auth_tasks.rake
113
136
  homepage: https://www.github.com/kobaltz/action_auth
114
137
  licenses:
@@ -116,7 +139,7 @@ licenses:
116
139
  metadata:
117
140
  homepage_uri: https://www.github.com/kobaltz/action_auth
118
141
  source_code_uri: https://www.github.com/kobaltz/action_auth
119
- changelog_uri: https://www.github.com/kobaltz/action_auth/CHANGELOG.md
142
+ changelog_uri: https://github.com/kobaltz/action_auth/blob/main/CHANGELOG.md
120
143
  rdoc_options: []
121
144
  require_paths:
122
145
  - lib
@@ -131,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
154
  - !ruby/object:Gem::Version
132
155
  version: '0'
133
156
  requirements: []
134
- rubygems_version: 4.0.4
157
+ rubygems_version: 4.0.16
135
158
  specification_version: 4
136
159
  summary: A simple Rails engine for authorization.
137
160
  test_files: []