authentication-zero 2.3.4 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70f497dd8ed75e1f18c31ef969a0813537d5f80ac72b178fef396347840fd752
4
- data.tar.gz: 5d8ab5264ece31d0fe9b14865353bae79d603fac87fcf9417c0dfa58b7c67361
3
+ metadata.gz: 3aaf162f00b6413821ae603869fb83f1cf577f956490821ffa051a6bf3b314ce
4
+ data.tar.gz: 9d4d1efd1aeddc346f39d4aec8752ec3f4a693e12908ec7c6ebc4cdcc0888684
5
5
  SHA512:
6
- metadata.gz: 3d7ed564b16eba4c19fd2a107b8b8e5e256a2b8d7203130c0b1c4c6dac802503bef8c5a98fac02053a20660ab11f5cc2b39f0fc0d46d2fbe8720cd385573adf5
7
- data.tar.gz: f9214fcb6e2653acd3dc4687992142848cd1e6a71cb050a1fa1ea02d2f57ec672b346d4ab234027bb37b170a20cdbce4d8d63adac81894b4b06519fefab1a060
6
+ metadata.gz: 112be1974500241c1e8a78411ba1b1846a7de6ab6a04c81b76963ded1b5150a7f7a12097ba06202210c73446c8404939718d9e7f14d93be3d96a9995235cd56e
7
+ data.tar.gz: d0d7c2965efdc83e16fcc068c1a1069269a60d68adafc0714291f310e726d90a698f0105752607a747894b683d7e544481b56cad3dee949ca52262f0c34e1250
data/CHANGELOG.md CHANGED
@@ -1,4 +1,8 @@
1
- ## Rails 2.3.0 (February 26, 2022) ##
1
+ ## Authentication Zero 2.4.0 (February 28, 2022) ##
2
+
3
+ * Implemented lockable
4
+
5
+ ## Authentication Zero 2.3.0 (February 26, 2022) ##
2
6
 
3
7
  * Implemented sudo
4
8
  * Destroy sessions after change password
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (2.3.4)
4
+ authentication-zero (2.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -8,13 +8,14 @@ The purpose of authentication zero is to generate a pre-built authentication sys
8
8
  - **Inspired by hey.com**
9
9
  - Sign up
10
10
  - Email and password validations
11
- - Authentication by cookie (html)
12
- - Authentication by token (api)
11
+ - Authentication by cookie
12
+ - Authentication by token (--api)
13
13
  - Ask password before sensitive data changes, aka: sudo
14
14
  - Reset the user password and send reset instructions
15
15
  - Reset the user password only from verified emails
16
- - Send e-mail verification when your email has been changed
17
- - Send email when someone has logged into your account
16
+ - Lock sending reset password email after many attempts (--lockable)
17
+ - Send e-mail notification when your email has been changed
18
+ - Send e-mail notification when someone has logged into your account
18
19
  - Manage multiple sessions & devices
19
20
  - Cancel my account
20
21
  - Log out
@@ -93,6 +94,10 @@ $ rails generate authentication user
93
94
 
94
95
  Then run `bundle install` again!
95
96
 
97
+ #### --lockable
98
+
99
+ Run `rails kredis:install`, to add a default configuration at `config/redis/shared.yml`.
100
+
96
101
  ## Development
97
102
 
98
103
  To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "2.3.4"
2
+ VERSION = "2.4.0"
3
3
  end
@@ -5,18 +5,24 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
5
5
 
6
6
  class_option :api, type: :boolean, desc: "Generates API authentication"
7
7
 
8
+ class_option :lockable, type: :boolean, desc: "Generates password reset locking"
9
+
10
+ class_option :skip_routes, type: :boolean
11
+
8
12
  class_option :migration, type: :boolean, default: true
9
13
  class_option :test_framework, type: :string, desc: "Test framework to be invoked"
10
14
 
11
15
  class_option :fixture, type: :boolean, default: true
12
16
  class_option :system_tests, type: :string, desc: "Skip system test files"
13
17
 
14
- class_option :skip_routes, type: :boolean, default: false
18
+ class_option :skip_routes, type: :boolean
15
19
 
16
20
  source_root File.expand_path("templates", __dir__)
17
21
 
18
22
  def add_bcrypt
19
- uncomment_lines "Gemfile", /bcrypt/
23
+ uncomment_lines "Gemfile", /"bcrypt"/
24
+ uncomment_lines "Gemfile", /"redis"/ if options.lockable
25
+ uncomment_lines "Gemfile", /"kredis"/ if options.lockable
20
26
  end
21
27
 
22
28
  def create_migrations
@@ -30,6 +36,7 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
30
36
  template "models/model.rb", "app/models/#{file_name}.rb"
31
37
  template "models/session.rb", "app/models/session.rb"
32
38
  template "models/current.rb", "app/models/current.rb"
39
+ template "models/locking.rb", "app/models/locking.rb" if options.lockable
33
40
  end
34
41
 
35
42
  hook_for :fixture_replacement
@@ -55,7 +62,7 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
55
62
  end
56
63
 
57
64
  def require_sudo
58
- if Time.current > 30.minutes.after(Current.session.sudo_at)
65
+ if Current.session.sudo_at < 30.minutes.ago
59
66
  render json: { error: "Enter your password to continue" }, status: :forbidden
60
67
  end
61
68
  end
@@ -73,7 +80,7 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
73
80
  end
74
81
 
75
82
  def require_sudo
76
- if Time.current > 30.minutes.after(Current.session.sudo_at)
83
+ if Current.session.sudo_at < 30.minutes.ago
77
84
  redirect_to new_sudo_path(proceed_to_url: request.url)
78
85
  end
79
86
  end
@@ -1,11 +1,14 @@
1
1
  class PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
+ <% if options.lockable? -%>
5
+ before_action :require_locking, only: :create
6
+ <% end -%>
4
7
  before_action :set_<%= singular_table_name %>, only: :update
5
8
 
6
9
  def create
7
- if <%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email], verified: true)
8
- IdentityMailer.with(<%= singular_table_name %>: <%= singular_table_name %>).password_reset_provision.deliver_later
10
+ if @<%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email], verified: true)
11
+ IdentityMailer.with(<%= singular_table_name %>: @<%= singular_table_name %>).password_reset_provision.deliver_later
9
12
  else
10
13
  render json: { error: "You can't reset your password until you verify your email" }, status: :not_found
11
14
  end
@@ -29,4 +32,11 @@ class PasswordResetsController < ApplicationController
29
32
  def <%= "#{singular_table_name}_params" %>
30
33
  params.permit(:password, :password_confirmation)
31
34
  end
35
+ <% if options.lockable? %>
36
+ def require_locking
37
+ Locking.lock_on("password_reset_lock_#{request.remote_ip}", wait: 1.hour, attempts: 10) do
38
+ render json: { error: "You've exceeded the maximum number of attempts" }, status: :too_many_requests
39
+ end
40
+ end
41
+ <% end -%>
32
42
  end
@@ -2,12 +2,12 @@ class RegistrationsController < ApplicationController
2
2
  skip_before_action :authenticate, only: :create
3
3
 
4
4
  def create
5
- <%= singular_table_name %> = <%= class_name %>.new(<%= "#{singular_table_name}_params" %>)
5
+ @<%= singular_table_name %> = <%= class_name %>.new(<%= "#{singular_table_name}_params" %>)
6
6
 
7
- if <%= singular_table_name %>.save
8
- render json: <%= singular_table_name %>, status: :created
7
+ if @<%= singular_table_name %>.save
8
+ render json: @<%= singular_table_name %>, status: :created
9
9
  else
10
- render json: <%= singular_table_name %>.errors, status: :unprocessable_entity
10
+ render json: @<%= singular_table_name %>.errors, status: :unprocessable_entity
11
11
  end
12
12
  end
13
13
 
@@ -12,13 +12,13 @@ class SessionsController < ApplicationController
12
12
  end
13
13
 
14
14
  def create
15
- <%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
15
+ <%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email])
16
16
 
17
17
  if <%= singular_table_name %> && <%= singular_table_name %>.authenticate(params[:password])
18
- session = <%= singular_table_name %>.sessions.create!(session_params)
19
- response.set_header("X-Session-Token", session.signed_id)
18
+ @session = <%= singular_table_name %>.sessions.create!(session_params)
19
+ response.set_header("X-Session-Token", @session.signed_id)
20
20
 
21
- render json: session, status: :created
21
+ render json: @session, status: :created
22
22
  else
23
23
  render json: { error: "That email or password is incorrect" }, status: :unauthorized
24
24
  end
@@ -1,6 +1,9 @@
1
1
  class PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
+ <% if options.lockable? -%>
5
+ before_action :require_locking, only: :create
6
+ <% end -%>
4
7
  before_action :set_<%= singular_table_name %>, only: %i[ edit update ]
5
8
 
6
9
  def new
@@ -10,8 +13,8 @@ class PasswordResetsController < ApplicationController
10
13
  end
11
14
 
12
15
  def create
13
- if <%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email], verified: true)
14
- IdentityMailer.with(<%= singular_table_name %>: <%= singular_table_name %>).password_reset_provision.deliver_later
16
+ if @<%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email], verified: true)
17
+ IdentityMailer.with(<%= singular_table_name %>: @<%= singular_table_name %>).password_reset_provision.deliver_later
15
18
  redirect_to sign_in_path, notice: "Check your email for reset instructions"
16
19
  else
17
20
  redirect_to new_password_reset_path, alert: "You can't reset your password until you verify your email"
@@ -36,4 +39,11 @@ class PasswordResetsController < ApplicationController
36
39
  def <%= "#{singular_table_name}_params" %>
37
40
  params.require(:<%= singular_table_name %>).permit(:password, :password_confirmation)
38
41
  end
42
+ <% if options.lockable? %>
43
+ def require_locking
44
+ Locking.lock_on("password_reset_lock_#{request.remote_ip}", wait: 1.hour, attempts: 10) do
45
+ redirect_to new_password_reset_path, alert: "You've exceeded the maximum number of attempts"
46
+ end
47
+ end
48
+ <% end -%>
39
49
  end
@@ -6,10 +6,10 @@ class RegistrationsController < ApplicationController
6
6
  end
7
7
 
8
8
  def create
9
- <%= singular_table_name %> = <%= class_name %>.new(<%= "#{singular_table_name}_params" %>)
9
+ @<%= singular_table_name %> = <%= class_name %>.new(<%= "#{singular_table_name}_params" %>)
10
10
 
11
- if <%= singular_table_name %>.save
12
- session = <%= singular_table_name %>.sessions.create!(session_params)
11
+ if @<%= singular_table_name %>.save
12
+ session = @<%= singular_table_name %>.sessions.create!(session_params)
13
13
  cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
14
14
 
15
15
  redirect_to root_path, notice: "Welcome! You have signed up successfully"
@@ -12,11 +12,11 @@ class SessionsController < ApplicationController
12
12
  end
13
13
 
14
14
  def create
15
- <%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
15
+ <%= singular_table_name %> = <%= class_name %>.find_by(email: params[:email])
16
16
 
17
17
  if <%= singular_table_name %> && <%= singular_table_name %>.authenticate(params[:password])
18
- session = <%= singular_table_name %>.sessions.create!(session_params)
19
- cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
18
+ @session = <%= singular_table_name %>.sessions.create!(session_params)
19
+ cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
20
20
 
21
21
  redirect_to root_path, notice: "Signed in successfully"
22
22
  else
@@ -8,7 +8,7 @@ class IdentityMailer < ApplicationMailer
8
8
 
9
9
  def email_verify_confirmation
10
10
  @<%= singular_table_name %> = params[:<%= singular_table_name %>]
11
- @signed_id = @<%= singular_table_name %>.signed_id(purpose: @<%= singular_table_name %>.email, expires_in: 20.minutes)
11
+ @signed_id = @<%= singular_table_name %>.signed_id(purpose: @<%= singular_table_name %>.email, expires_in: 3.days)
12
12
 
13
13
  mail to: @<%= singular_table_name %>.email, subject: "Verify your email"
14
14
  end
@@ -1,7 +1,7 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def change
3
3
  create_table :sessions do |t|
4
- t.references :user, null: false, foreign_key: true
4
+ t.references :<%= singular_table_name %>, null: false, foreign_key: true
5
5
 
6
6
  t.string :user_agent, null: false
7
7
  t.string :ip_address, null: false
@@ -0,0 +1,10 @@
1
+ class Locking
2
+ def self.lock_on(key, wait:, attempts:, &block)
3
+ counter = Kredis.counter(key, expires_in: wait)
4
+ counter.increment
5
+
6
+ if counter.value > attempts
7
+ yield
8
+ end
9
+ end
10
+ end
@@ -6,6 +6,9 @@ class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
6
6
  @sid = @<%= singular_table_name %>.signed_id(purpose: :password_reset, expires_in: 20.minutes)
7
7
  @sid_exp = @<%= singular_table_name %>.signed_id(purpose: :password_reset, expires_in: 0.minutes)
8
8
  end
9
+ <% if options.lockable? %>
10
+ teardown { Kredis.clear_all }
11
+ <% end -%>
9
12
 
10
13
  test "should send a password reset email" do
11
14
  assert_enqueued_email_with IdentityMailer, :password_reset_provision, args: { <%= singular_table_name %>: @<%= singular_table_name %> } do
@@ -6,6 +6,9 @@ class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
6
6
  @sid = @<%= singular_table_name %>.signed_id(purpose: :password_reset, expires_in: 20.minutes)
7
7
  @sid_exp = @<%= singular_table_name %>.signed_id(purpose: :password_reset, expires_in: 0.minutes)
8
8
  end
9
+ <% if options.lockable? %>
10
+ teardown { Kredis.clear_all }
11
+ <% end -%>
9
12
 
10
13
  test "should get new" do
11
14
  get new_password_reset_url
@@ -5,6 +5,9 @@ class PasswordResetsTest < ApplicationSystemTestCase
5
5
  @<%= singular_table_name %> = <%= table_name %>(:lazaro_nixon)
6
6
  @sid = @<%= singular_table_name %>.signed_id(purpose: :password_reset, expires_in: 20.minutes)
7
7
  end
8
+ <% if options.lockable? %>
9
+ teardown { Kredis.clear_all }
10
+ <% end -%>
8
11
 
9
12
  test "sending a password reset email" do
10
13
  visit sign_in_url
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: 2.3.4
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nixon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-26 00:00:00.000000000 Z
11
+ date: 2022-02-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -64,6 +64,7 @@ files:
64
64
  - lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt
65
65
  - lib/generators/authentication/templates/migrations/create_table_migration.rb.tt
66
66
  - lib/generators/authentication/templates/models/current.rb.tt
67
+ - lib/generators/authentication/templates/models/locking.rb.tt
67
68
  - lib/generators/authentication/templates/models/model.rb.tt
68
69
  - lib/generators/authentication/templates/models/session.rb.tt
69
70
  - lib/generators/authentication/templates/test_unit/controllers/api/email_verifications_controller_test.rb.tt