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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +102 -0
- data/README.md +485 -36
- data/Rakefile +0 -2
- data/app/assets/javascripts/action_auth/application.js +72 -71
- data/app/assets/stylesheets/action_auth/application.css +38 -1
- data/app/controllers/action_auth/application_controller.rb +122 -1
- data/app/controllers/action_auth/connected_accounts_controller.rb +16 -0
- data/app/controllers/action_auth/identity/email_verifications_controller.rb +36 -5
- data/app/controllers/action_auth/identity/emails_controller.rb +16 -4
- data/app/controllers/action_auth/identity/password_resets_controller.rb +20 -22
- data/app/controllers/action_auth/magics/requests_controller.rb +27 -6
- data/app/controllers/action_auth/magics/sign_ins_controller.rb +22 -7
- data/app/controllers/action_auth/passwords_controller.rb +7 -18
- data/app/controllers/action_auth/provider_callbacks_controller.rb +147 -0
- data/app/controllers/action_auth/registrations_controller.rb +7 -25
- data/app/controllers/action_auth/sessions/passkeys_controller.rb +17 -9
- data/app/controllers/action_auth/sessions_controller.rb +27 -25
- data/app/controllers/action_auth/sms_auths/requests_controller.rb +40 -14
- data/app/controllers/action_auth/sms_auths/sign_ins_controller.rb +40 -15
- data/app/controllers/action_auth/users_controller.rb +35 -1
- data/app/controllers/action_auth/webauthn_credential_authentications_controller.rb +36 -51
- data/app/controllers/action_auth/webauthn_credentials_controller.rb +63 -51
- data/app/helpers/action_auth/application_helper.rb +83 -0
- data/app/mailers/action_auth/application_mailer.rb +1 -1
- data/app/mailers/action_auth/user_mailer.rb +7 -0
- data/app/models/action_auth/connected_account.rb +18 -0
- data/app/models/action_auth/session.rb +9 -1
- data/app/models/action_auth/user.rb +197 -4
- data/app/models/action_auth/webauthn_credential.rb +2 -0
- data/app/views/action_auth/identity/email_verifications/show.html.erb +23 -0
- data/app/views/action_auth/identity/emails/edit.html.erb +41 -42
- data/app/views/action_auth/identity/password_resets/edit.html.erb +10 -11
- data/app/views/action_auth/identity/password_resets/new.html.erb +4 -9
- data/app/views/action_auth/magics/requests/new.html.erb +4 -14
- data/app/views/action_auth/magics/sign_ins/show.html.erb +18 -0
- data/app/views/action_auth/passwords/edit.html.erb +9 -21
- data/app/views/action_auth/registrations/new.html.erb +5 -27
- data/app/views/action_auth/sessions/_account.html.erb +30 -0
- data/app/views/action_auth/sessions/_connected_accounts.html.erb +32 -0
- data/app/views/action_auth/sessions/_security_keys.html.erb +32 -0
- data/app/views/action_auth/sessions/_sessions.html.erb +28 -0
- data/app/views/action_auth/sessions/index.html.erb +10 -62
- data/app/views/action_auth/sessions/new.html.erb +3 -21
- data/app/views/action_auth/sessions/passkeys/new.html.erb +10 -4
- data/app/views/action_auth/shared/_errors.html.erb +10 -0
- data/app/views/action_auth/shared/_flash.html.erb +11 -0
- data/app/views/action_auth/shared/_links.html.erb +3 -0
- data/app/views/action_auth/shared/_oauth_buttons.html.erb +5 -0
- data/app/views/action_auth/shared/_webauthn_error.html.erb +1 -0
- data/app/views/action_auth/sms_auths/requests/new.html.erb +4 -14
- data/app/views/action_auth/sms_auths/sign_ins/show.html.erb +6 -2
- data/app/views/action_auth/user_mailer/account_locked.html.erb +17 -0
- data/app/views/action_auth/user_mailer/account_locked.text.erb +12 -0
- data/app/views/action_auth/user_mailer/email_verification.html.erb +5 -0
- data/app/views/action_auth/user_mailer/email_verification.text.erb +3 -0
- data/app/views/action_auth/user_mailer/magic_link.html.erb +4 -0
- data/app/views/action_auth/user_mailer/magic_link.text.erb +5 -0
- data/app/views/action_auth/user_mailer/password_reset.text.erb +1 -1
- data/app/views/action_auth/webauthn_credential_authentications/new.html.erb +9 -3
- data/app/views/action_auth/webauthn_credentials/new.html.erb +10 -3
- data/app/views/layouts/action_auth/application.html.erb +2 -7
- data/config/routes.rb +13 -3
- data/db/migrate/20261105120000_add_magic_link_used_at_to_users.rb +5 -0
- data/db/migrate/20261105130000_add_account_lockout_and_suspension_to_users.rb +7 -0
- data/db/migrate/20261105130100_create_action_auth_connected_accounts.rb +19 -0
- data/lib/action_auth/configuration.rb +96 -0
- data/lib/action_auth/controllers/helpers.rb +44 -11
- data/lib/action_auth/engine.rb +27 -1
- data/lib/action_auth/routing/helpers.rb +8 -0
- data/lib/action_auth/version.rb +1 -1
- data/lib/generators/action_auth/views/USAGE +8 -0
- data/lib/generators/action_auth/views/views_generator.rb +59 -0
- data/lib/tasks/action_auth_tasks.rake +19 -1
- 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
|
-
|
|
17
|
-
<%= link_to "Cancel",
|
|
18
|
-
|
|
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
|
-
|
|
24
|
-
<%= link_to "Cancel",
|
|
25
|
-
|
|
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
|
|
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
|
-
<%=
|
|
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, :
|
|
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,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
|
-
|
|
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
|
-
#
|
|
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.
|
|
58
|
+
Rails.logger.info "Session timed out for user #{Current.session.user_id}"
|
|
50
59
|
Current.session.destroy
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
data/lib/action_auth/engine.rb
CHANGED
|
@@ -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
|
data/lib/action_auth/version.rb
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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://
|
|
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.
|
|
157
|
+
rubygems_version: 4.0.16
|
|
135
158
|
specification_version: 4
|
|
136
159
|
summary: A simple Rails engine for authorization.
|
|
137
160
|
test_files: []
|