authentication-zero 3.0.2 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +2 -3
  3. data/CHANGELOG.md +10 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +6 -0
  6. data/lib/authentication_zero/version.rb +1 -1
  7. data/lib/generators/authentication/authentication_generator.rb +5 -7
  8. data/lib/generators/authentication/templates/controllers/api/application_controller.rb.tt +0 -10
  9. data/lib/generators/authentication/templates/controllers/api/identity/password_resets_controller.rb.tt +0 -3
  10. data/lib/generators/authentication/templates/controllers/html/application_controller.rb.tt +0 -10
  11. data/lib/generators/authentication/templates/controllers/html/identity/password_resets_controller.rb.tt +1 -1
  12. data/lib/generators/authentication/templates/controllers/html/sessions/passwordlesses_controller.rb.tt +1 -1
  13. data/lib/generators/authentication/templates/javascript/controllers/web_authn_controller.js +111 -0
  14. data/lib/generators/authentication/templates/models/user.rb.tt +1 -0
  15. metadata +4 -10
  16. data/lib/generators/authentication/templates/javascript/controllers/application.js +0 -11
  17. data/lib/generators/authentication/templates/test_unit/application_system_test_case.rb.tt +0 -15
  18. data/lib/generators/authentication/templates/test_unit/system/identity/emails_test.rb.tt +0 -26
  19. data/lib/generators/authentication/templates/test_unit/system/identity/password_resets_test.rb.tt +0 -28
  20. data/lib/generators/authentication/templates/test_unit/system/passwords_test.rb.tt +0 -18
  21. data/lib/generators/authentication/templates/test_unit/system/registrations_test.rb.tt +0 -14
  22. data/lib/generators/authentication/templates/test_unit/system/sessions_test.rb.tt +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c60a566c4789323eb87ac9f024b732231815e76092b5187d608ddc6a21314427
4
- data.tar.gz: fccefad83e73b9fe383949f756cf2ad363372472a7d5a362f5e04a99ea75fefa
3
+ metadata.gz: 84ff6a85acc17f4561f1362e2e7fa15ad27c434cee6191f57dd7623f49b506ff
4
+ data.tar.gz: 75fef66fa0926ba2c2a797db8a7d85cb58cdbf90891f0cb50e3cb318873fff10
5
5
  SHA512:
6
- metadata.gz: b4a4a9bf9a05f73b0fcc4862d763fb3a4880563fbec72a3f069d7b7e56dbc4dee3c8812823c2569d7e680a4c49b86e61465191cfbd0b24a2a121ba49ab61ec37
7
- data.tar.gz: b73ed9438ecbb4a9afa463195ca88d735084853044c8d59b6f649157302aefa171ed0a6d421267c2acbe909cabe0993fdf3b81da3a68e5a654e2e82a99d1549c
6
+ metadata.gz: b80db262196da2d9107246f47949c88a73110f58298a9d29eb6c94eba2b8e255a9206f09539b3bad6702156e96fbfd1960ae789530d595e303858b36f92364b8
7
+ data.tar.gz: c234410b5f2458a41be599d2529b377ed62436ff071eb87bc8bb49479d06d7382ac835ff7e82be7caf9f4b173ea45007362fc1836cedc27cc836e82f6cc71c4b
@@ -22,7 +22,7 @@ jobs:
22
22
  bundler-cache: true
23
23
 
24
24
  - name: Install the latest Rails gem
25
- run: gem install rails -v "7.1.0"
25
+ run: gem install rails -v "7.2.1"
26
26
 
27
27
  - name: Install Rubocop
28
28
  run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
@@ -50,7 +50,6 @@ jobs:
50
50
  run: |
51
51
  cd test-app
52
52
  bin/rails test
53
- bin/rails test:system
54
53
 
55
54
  test_api:
56
55
  name: 🧪 Run API Tests
@@ -66,7 +65,7 @@ jobs:
66
65
  bundler-cache: true
67
66
 
68
67
  - name: Install the latest Rails gem
69
- run: gem install rails -v "7.1.0"
68
+ run: gem install rails -v "7.2.1"
70
69
 
71
70
  - name: Install Rubocop
72
71
  run: gem install rubocop rubocop-performance rubocop-minitest rubocop-packaging rubocop-minitest rubocop-rails
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## New version
2
+
3
+ * Remove rate limit from api generator
4
+
5
+ ## Authentication Zero 4.0.0 ##
6
+
7
+ * Remove system tests
8
+ * Use native rate_limit for lockable
9
+ * Copy web_authn_controller.js instead of depend on stimulus-web-authn
10
+
1
11
  ## Authentication Zero 3.0.2 ##
2
12
 
3
13
  * Fix bug where token is not expired/invalid
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (3.0.2)
4
+ authentication-zero (4.0.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -8,6 +8,12 @@ The purpose of authentication zero is to generate a pre-built authentication sys
8
8
  $ bundle add authentication-zero
9
9
  ```
10
10
 
11
+ If you are using Rails < 7.2, you must use version 3.
12
+
13
+ ```
14
+ $ bundle add authentication-zero --version "~> 3"
15
+ ```
16
+
11
17
  If you are using Rails < 7.1, you must use version 2.
12
18
 
13
19
  ```
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "3.0.2"
2
+ VERSION = "4.0.1"
3
3
  end
@@ -8,7 +8,7 @@ class AuthenticationGenerator < Rails::Generators::Base
8
8
  class_option :sudoable, type: :boolean, desc: "Add password request before sensitive data changes"
9
9
  class_option :lockable, type: :boolean, desc: "Add password reset locking"
10
10
  class_option :ratelimit, type: :boolean, desc: "Add request rate limiting"
11
- class_option :passwordless, type: :boolean, desc: "Add passwordless sign"
11
+ class_option :passwordless, type: :boolean, desc: "Add passwordless sign in"
12
12
  class_option :omniauthable, type: :boolean, desc: "Add social login support"
13
13
  class_option :trackable, type: :boolean, desc: "Add activity log support"
14
14
  class_option :two_factor, type: :boolean, desc: "Add two factor authentication"
@@ -123,9 +123,9 @@ class AuthenticationGenerator < Rails::Generators::Base
123
123
 
124
124
  def install_javascript
125
125
  return unless webauthn?
126
- copy_file "javascript/controllers/application.js", "app/javascript/controllers/application.js", force: true
127
- run "bin/importmap pin stimulus-web-authn" if importmaps?
128
- run "yarn add stimulus-web-authn" if node?
126
+ copy_file "javascript/controllers/web_authn_controller.js", "app/javascript/controllers/web_authn_controller.js"
127
+ run "bin/importmap pin @rails/request.js" if importmaps?
128
+ run "yarn add @rails/request.js" if node?
129
129
  end
130
130
 
131
131
  def create_views
@@ -222,9 +222,7 @@ class AuthenticationGenerator < Rails::Generators::Base
222
222
  def create_test_files
223
223
  directory "test_unit/controllers/#{format}", "test/controllers"
224
224
  directory "test_unit/mailers/", "test/mailers"
225
- directory "test_unit/system", "test/system" unless options.api?
226
225
  template "test_unit/test_helper.rb", "test/test_helper.rb", force: true
227
- template "test_unit/application_system_test_case.rb", "test/application_system_test_case.rb", force: true unless options.api?
228
226
  end
229
227
 
230
228
  private
@@ -261,7 +259,7 @@ class AuthenticationGenerator < Rails::Generators::Base
261
259
  end
262
260
 
263
261
  def redis?
264
- options.lockable? || options.ratelimit? || sudoable?
262
+ options.ratelimit? || sudoable?
265
263
  end
266
264
 
267
265
  def importmaps?
@@ -17,14 +17,4 @@ class ApplicationController < ActionController::API
17
17
  Current.user_agent = request.user_agent
18
18
  Current.ip_address = request.ip
19
19
  end
20
- <%- if options.lockable? %>
21
- def require_lock(wait: 1.hour, attempts: 10)
22
- counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
23
- counter.increment
24
-
25
- if counter.value > attempts
26
- render json: { error: "You've exceeded the maximum number of attempts" }, status: :too_many_requests
27
- end
28
- end
29
- <%- end -%>
30
20
  end
@@ -1,9 +1,6 @@
1
1
  class Identity::PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
- <%- if options.lockable? -%>
5
- before_action :require_lock, only: :create
6
- <%- end -%>
7
4
  before_action :set_user, only: :update
8
5
 
9
6
  def edit
@@ -15,16 +15,6 @@ class ApplicationController < ActionController::Base
15
15
  Current.user_agent = request.user_agent
16
16
  Current.ip_address = request.ip
17
17
  end
18
- <%- if options.lockable? %>
19
- def require_lock(wait: 1.hour, attempts: 10)
20
- counter = Kredis.counter("require_lock:#{request.remote_ip}:#{controller_path}:#{action_name}", expires_in: wait)
21
- counter.increment
22
-
23
- if counter.value > attempts
24
- redirect_to root_path, alert: "You've exceeded the maximum number of attempts"
25
- end
26
- end
27
- <%- end -%>
28
18
  <%- if sudoable? %>
29
19
  def require_sudo
30
20
  unless Current.session.sudo?
@@ -2,7 +2,7 @@ class Identity::PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
4
  <%- if options.lockable? -%>
5
- before_action :require_lock, only: :create
5
+ rate_limit to: 10, within: 1.hour, only: :create, with: -> { redirect_to root_path, alert: "Try again later" }
6
6
  <%- end -%>
7
7
  before_action :set_user, only: %i[ edit update ]
8
8
 
@@ -2,7 +2,7 @@ class Sessions::PasswordlessesController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
4
  <%- if options.lockable? -%>
5
- before_action :require_lock, only: :create
5
+ rate_limit to: 10, within: 1.hour, only: :create, with: -> { redirect_to root_path, alert: "Try again later" }
6
6
  <%- end -%>
7
7
  before_action :set_user, only: :edit
8
8
 
@@ -0,0 +1,111 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { create, get, supported } from "@github/webauthn-json"
3
+ import { FetchRequest } from "@rails/request.js"
4
+
5
+ export default class WebAuthnController extends Controller {
6
+ static targets = [ "error", "button", "supportText" ]
7
+ static classes = [ "loading" ]
8
+ static values = {
9
+ challengeUrl: String,
10
+ verificationUrl: String,
11
+ fallbackUrl: String,
12
+ retryText: { type: String, default: "Retry" },
13
+ notAllowedText: { type: String, default: "That didn't work. Either it was cancelled or took too long. Please try again." },
14
+ invalidStateText: { type: String, default: "We couldn't add that security key. Please confirm you haven't already registered it, then try again." }
15
+ }
16
+
17
+ connect() {
18
+ if (!supported()) {
19
+ this.handleUnsupportedBrowser()
20
+ }
21
+ }
22
+
23
+ getCredential() {
24
+ this.hideError()
25
+ this.disableForm()
26
+ this.requestChallengeAndVerify(get)
27
+ }
28
+
29
+ createCredential() {
30
+ this.hideError()
31
+ this.disableForm()
32
+ this.requestChallengeAndVerify(create)
33
+ }
34
+
35
+ // Private
36
+
37
+ handleUnsupportedBrowser() {
38
+ this.buttonTarget.parentNode.removeChild(this.buttonTarget)
39
+
40
+ if (this.fallbackUrlValue) {
41
+ window.location.replace(this.fallbackUrlValue)
42
+ } else {
43
+ this.supportTextTargets.forEach(target => target.hidden = !target.hidden)
44
+ }
45
+ }
46
+
47
+ async requestChallengeAndVerify(fn) {
48
+ try {
49
+ const challengeResponse = await this.requestPublicKeyChallenge()
50
+ const credentialResponse = await fn({ publicKey: challengeResponse })
51
+ this.onCompletion(await this.verify(credentialResponse))
52
+ } catch (error) {
53
+ this.onError(error)
54
+ }
55
+ }
56
+
57
+ async requestPublicKeyChallenge() {
58
+ return await this.request("get", this.challengeUrlValue)
59
+ }
60
+
61
+ async verify(credentialResponse) {
62
+ return await this.request("post", this.verificationUrlValue, {
63
+ body: JSON.stringify({ credential: credentialResponse }),
64
+ contentType: "application/json",
65
+ responseKind: "json"
66
+ })
67
+ }
68
+
69
+ onCompletion(response) {
70
+ window.location.replace(response.location)
71
+ }
72
+
73
+ onError(error) {
74
+ if (error.code === 0 && error.name === "NotAllowedError") {
75
+ this.errorTarget.textContent = this.notAllowedTextValue
76
+ } else if (error.code === 11 && error.name === "InvalidStateError") {
77
+ this.errorTarget.textContent = this.invalidStateTextValue
78
+ } else {
79
+ this.errorTarget.textContent = error.message
80
+ }
81
+ this.showError()
82
+ }
83
+
84
+ hideError() {
85
+ if (this.hasErrorTarget) this.errorTarget.hidden = true
86
+ }
87
+
88
+ showError() {
89
+ if (this.hasErrorTarget) {
90
+ this.errorTarget.hidden = false
91
+ this.buttonTarget.textContent = this.retryTextValue
92
+ this.enableForm()
93
+ }
94
+ }
95
+
96
+ enableForm() {
97
+ this.element.classList.remove(this.loadingClass)
98
+ this.buttonTarget.disabled = false
99
+ }
100
+
101
+ disableForm() {
102
+ this.element.classList.add(this.loadingClass)
103
+ this.buttonTarget.disabled = true
104
+ }
105
+
106
+ async request(method, url, options) {
107
+ const request = new FetchRequest(method, url, { responseKind: "json", ...options })
108
+ const response = await request.perform()
109
+ return response.json
110
+ }
111
+ }
@@ -4,6 +4,7 @@ class User < ApplicationRecord
4
4
  generates_token_for :email_verification, expires_in: 2.days do
5
5
  email
6
6
  end
7
+
7
8
  generates_token_for :password_reset, expires_in: 20.minutes do
8
9
  password_salt.last(10)
9
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authentication-zero
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nixon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-28 00:00:00.000000000 Z
11
+ date: 2024-10-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -94,7 +94,7 @@ files:
94
94
  - lib/generators/authentication/templates/erb/user_mailer/invitation_instructions.html.erb.tt
95
95
  - lib/generators/authentication/templates/erb/user_mailer/password_reset.html.erb.tt
96
96
  - lib/generators/authentication/templates/erb/user_mailer/passwordless.html.erb.tt
97
- - lib/generators/authentication/templates/javascript/controllers/application.js
97
+ - lib/generators/authentication/templates/javascript/controllers/web_authn_controller.js
98
98
  - lib/generators/authentication/templates/lib/account_middleware.rb
99
99
  - lib/generators/authentication/templates/mailers/user_mailer.rb.tt
100
100
  - lib/generators/authentication/templates/migrations/create_accounts_migration.rb.tt
@@ -113,7 +113,6 @@ files:
113
113
  - lib/generators/authentication/templates/models/session.rb.tt
114
114
  - lib/generators/authentication/templates/models/sign_in_token.rb.tt
115
115
  - lib/generators/authentication/templates/models/user.rb.tt
116
- - lib/generators/authentication/templates/test_unit/application_system_test_case.rb.tt
117
116
  - lib/generators/authentication/templates/test_unit/controllers/api/identity/email_verifications_controller_test.rb.tt
118
117
  - lib/generators/authentication/templates/test_unit/controllers/api/identity/emails_controller_test.rb.tt
119
118
  - lib/generators/authentication/templates/test_unit/controllers/api/identity/password_resets_controller_test.rb.tt
@@ -127,11 +126,6 @@ files:
127
126
  - lib/generators/authentication/templates/test_unit/controllers/html/registrations_controller_test.rb.tt
128
127
  - lib/generators/authentication/templates/test_unit/controllers/html/sessions_controller_test.rb.tt
129
128
  - lib/generators/authentication/templates/test_unit/mailers/user_mailer_test.rb.tt
130
- - lib/generators/authentication/templates/test_unit/system/identity/emails_test.rb.tt
131
- - lib/generators/authentication/templates/test_unit/system/identity/password_resets_test.rb.tt
132
- - lib/generators/authentication/templates/test_unit/system/passwords_test.rb.tt
133
- - lib/generators/authentication/templates/test_unit/system/registrations_test.rb.tt
134
- - lib/generators/authentication/templates/test_unit/system/sessions_test.rb.tt
135
129
  - lib/generators/authentication/templates/test_unit/test_helper.rb.tt
136
130
  - lib/generators/authentication/templates/test_unit/users.yml
137
131
  homepage: https://github.com/lazaronixon/authentication-zero
@@ -156,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
150
  - !ruby/object:Gem::Version
157
151
  version: '0'
158
152
  requirements: []
159
- rubygems_version: 3.4.20
153
+ rubygems_version: 3.5.5
160
154
  signing_key:
161
155
  specification_version: 4
162
156
  summary: An authentication system generator for Rails applications
@@ -1,11 +0,0 @@
1
- import { Application } from "@hotwired/stimulus"
2
- import WebAuthnController from "stimulus-web-authn"
3
-
4
- const application = Application.start()
5
- application.register("web-authn", WebAuthnController)
6
-
7
- // Configure Stimulus development experience
8
- application.debug = false
9
- window.Stimulus = application
10
-
11
- export { application }
@@ -1,15 +0,0 @@
1
- require "test_helper"
2
-
3
- class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4
- driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
5
-
6
- def sign_in_as(user)
7
- visit sign_in_url
8
- fill_in :email, with: user.email
9
- fill_in :password, with: "Secret1*3*5*"
10
- click_on "Sign in"
11
-
12
- assert_current_path root_url
13
- user
14
- end
15
- end
@@ -1,26 +0,0 @@
1
- require "application_system_test_case"
2
-
3
- class Identity::EmailsTest < ApplicationSystemTestCase
4
- setup do
5
- @user = sign_in_as(users(:lazaro_nixon))
6
- end
7
-
8
- test "updating the email" do
9
- click_on "Change email address"
10
-
11
- fill_in "New email", with: "new_email@hey.com"
12
- fill_in "Password challenge", with: "Secret1*3*5*"
13
- click_on "Save changes"
14
-
15
- assert_text "Your email has been changed"
16
- end
17
-
18
- test "sending a verification email" do
19
- @user.update! verified: false
20
-
21
- click_on "Change email address"
22
- click_on "Re-send verification email"
23
-
24
- assert_text "We sent a verification email to your email address"
25
- end
26
- end
@@ -1,28 +0,0 @@
1
- require "application_system_test_case"
2
-
3
- class Identity::PasswordResetsTest < ApplicationSystemTestCase
4
- setup do
5
- @user = users(:lazaro_nixon)
6
- @sid = @user.generate_token_for(:password_reset)
7
- end
8
-
9
- test "sending a password reset email" do
10
- visit sign_in_url
11
- click_on "Forgot your password?"
12
-
13
- fill_in "Email", with: @user.email
14
- click_on "Send password reset email"
15
-
16
- assert_text "Check your email for reset instructions"
17
- end
18
-
19
- test "updating password" do
20
- visit edit_identity_password_reset_url(sid: @sid)
21
-
22
- fill_in "New password", with: "Secret6*4*2*"
23
- fill_in "Confirm new password", with: "Secret6*4*2*"
24
- click_on "Save changes"
25
-
26
- assert_text "Your password was reset successfully. Please sign in"
27
- end
28
- end
@@ -1,18 +0,0 @@
1
- require "application_system_test_case"
2
-
3
- class PasswordsTest < ApplicationSystemTestCase
4
- setup do
5
- @user = sign_in_as(users(:lazaro_nixon))
6
- end
7
-
8
- test "updating the password" do
9
- click_on "Change password"
10
-
11
- fill_in "Password challenge", with: "Secret1*3*5*"
12
- fill_in "New password", with: "Secret6*4*2*"
13
- fill_in "Confirm new password", with: "Secret6*4*2*"
14
- click_on "Save changes"
15
-
16
- assert_text "Your password has been changed"
17
- end
18
- end
@@ -1,14 +0,0 @@
1
- require "application_system_test_case"
2
-
3
- class RegistrationsTest < ApplicationSystemTestCase
4
- test "signing up" do
5
- visit sign_up_url
6
-
7
- fill_in "Email", with: "lazaronixon@hey.com"
8
- fill_in "Password", with: "Secret6*4*2*"
9
- fill_in "Password confirmation", with: "Secret6*4*2*"
10
- click_on "Sign up"
11
-
12
- assert_text "Welcome! You have signed up successfully"
13
- end
14
- end
@@ -1,30 +0,0 @@
1
- require "application_system_test_case"
2
-
3
- class SessionsTest < ApplicationSystemTestCase
4
- setup do
5
- @user = users(:lazaro_nixon)
6
- end
7
-
8
- test "visiting the index" do
9
- sign_in_as @user
10
-
11
- click_on "Devices & Sessions"
12
- assert_selector "h1", text: "Sessions"
13
- end
14
-
15
- test "signing in" do
16
- visit sign_in_url
17
- fill_in "Email", with: @user.email
18
- fill_in "Password", with: "Secret1*3*5*"
19
- click_on "Sign in"
20
-
21
- assert_text "Signed in successfully"
22
- end
23
-
24
- test "signing out" do
25
- sign_in_as @user
26
-
27
- click_on "Log out"
28
- assert_text "That session has been logged out"
29
- end
30
- end