Skip to content

Commit 3a9b401

Browse files
authored
Add Devise’s lockable to prevent multiple consecutive failed login attempts (#365)
1 parent 010f81f commit 3a9b401

4 files changed

Lines changed: 19 additions & 7 deletions

File tree

app/models/user.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class User < ApplicationRecord
55
validates :password, length: { within: 8..128, allow_blank: true }, presence: { if: :password_required? }
66

77
# Devise
8-
devise :database_authenticatable, :rememberable, :trackable, :recoverable, :password_archivable, :session_limitable
8+
devise :database_authenticatable, :rememberable, :trackable, :recoverable, :password_archivable, :session_limitable, :lockable
99

1010
# FriendlyId
1111
extend FriendlyId

config/initializers/devise.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,24 @@
164164
# Defines which strategy will be used to lock an account.
165165
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
166166
# :none = No lock strategy. You should handle locking by yourself.
167-
# config.lock_strategy = :failed_attempts
167+
config.lock_strategy = :failed_attempts
168168

169169
# Defines which key will be used when locking and unlocking an account
170-
# config.unlock_keys = [ :email ]
170+
config.unlock_keys = [:email]
171171

172172
# Defines which strategy will be used to unlock an account.
173173
# :email = Sends an unlock link to the user email
174174
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
175175
# :both = Enables both strategies
176176
# :none = No unlock strategy. You should handle unlocking by yourself.
177-
# config.unlock_strategy = :both
177+
config.unlock_strategy = :time
178178

179179
# Number of authentication tries before locking an account if lock_strategy
180180
# is failed attempts.
181-
# config.maximum_attempts = 20
181+
config.maximum_attempts = 10
182182

183183
# Time interval to unlock the account if :time is enabled as unlock_strategy.
184-
# config.unlock_in = 1.hour
184+
config.unlock_in = 1.hour
185185

186186
# ==> Configuration for :recoverable
187187
#
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class AddLockableToUsers < ActiveRecord::Migration[8.0]
2+
def change
3+
add_column :users, :failed_attempts, :integer, default: 0, null: false
4+
add_column :users, :unlock_token, :string
5+
add_column :users, :locked_at, :datetime
6+
add_index :users, :unlock_token, unique: true
7+
end
8+
end

db/schema.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[8.0].define(version: 2025_07_31_014203) do
13+
ActiveRecord::Schema[8.0].define(version: 2026_04_23_112300) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "pg_catalog.plpgsql"
1616

@@ -107,6 +107,10 @@
107107
t.string "reset_password_token"
108108
t.datetime "reset_password_sent_at", precision: nil
109109
t.string "unique_session_id"
110+
t.integer "failed_attempts", default: 0, null: false
111+
t.string "unlock_token"
112+
t.datetime "locked_at"
113+
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
110114
end
111115

112116
create_table "versions", id: :serial, force: :cascade do |t|

0 commit comments

Comments
 (0)