authentication-zero 3.0.1 → 4.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 (22) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +0 -1
  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 +4 -6
  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 +1 -1
  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 +8 -2
  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: 92112d3c18d8e744aa40aed7fef55a2d0297e90e1975c2dd8efbcd4197314cd7
4
- data.tar.gz: 7ec0f1e5283f8035bdcbcfe0d8d497a618158689fc7c85d550c96ec3324d5d48
3
+ metadata.gz: 10cffeda250c03651c8f0d1f60ae0c726fe9884c4ac40b62de7a7deceb27374c
4
+ data.tar.gz: 1b3421b4b3053fd76db8500d332e8ee0db4478bb8bf547988192ba646b1a8534
5
5
  SHA512:
6
- metadata.gz: 8b0af8f623956d8cbd08046c2c30e2e3705542b975236585a8420d26d8b7fdb450ddea61abd2b6e67b675c677869126cebfc0c898b7c2d88cde869d0715c72cb
7
- data.tar.gz: 5015012ed105a8e4cf26f2a801dda0d91785a99bf45497964f1a806311a5ff822348afa1533209d8c847f2732bfa4d424e59e5854eabb2824a3b16f9b42b9696
6
+ metadata.gz: 7f8c89fb438dfc43fd47416f5320b4a98e14617896e30e3e0060245f71e2f0e356b1b8d9f709b2c0c930a9cffc8a20e9eff168ffe48cf74c288a3ce191e5a1d6
7
+ data.tar.gz: 37f2810abcec42035ece7447836eb966a5de77359ef907bdbd336d6918d4ef2b4ca2330e80824084ec4941a250a55f31f21863f23d21fa9e89503b5b3f5fc94f
@@ -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
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## Authentication Zero 4.0.0 ##
2
+
3
+ * Remove system tests
4
+ * Use native rate_limit for lockable
5
+ * Copy web_authn_controller.js instead of depend on stimulus-web-authn
6
+
7
+ ## Authentication Zero 3.0.2 ##
8
+
9
+ * Fix bug where token is not expired/invalid
10
+
1
11
  ## Authentication Zero 3.0.0 ##
2
12
 
3
13
  * Use the new normalizes API
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (3.0.1)
4
+ authentication-zero (4.0.0)
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 < 8, 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.1"
2
+ VERSION = "4.0.0"
3
3
  end
@@ -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
@@ -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
6
6
  <%- end -%>
7
7
  before_action :set_user, only: :update
8
8
 
@@ -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
+ }
@@ -1,8 +1,14 @@
1
1
  class User < ApplicationRecord
2
2
  has_secure_password
3
3
 
4
- generates_token_for :email_verification, expires_in: 2.days { email }
5
- generates_token_for :password_reset, expires_in: 20.minutes { password_salt.last(10) }
4
+ generates_token_for :email_verification, expires_in: 2.days do
5
+ email
6
+ end
7
+
8
+ generates_token_for :password_reset, expires_in: 20.minutes do
9
+ password_salt.last(10)
10
+ end
11
+
6
12
  <%- if options.tenantable? %>
7
13
  belongs_to :account
8
14
  <%- 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.1
4
+ version: 4.0.0
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-14 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