authentication-zero 2.3.4 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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