Я использую драгоценный камень Bcrypt для шифрования паролей.
Модель пользователя:
class User < ApplicationRecord
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, confirmation: true
# ...
end
Как сделать так, чтобы пароль был пустым при обновлении пользователя?:
expect(user.update(name: 'Joana', password: '')).to be(true)
Но не обновить пароль?
expect { user.update(name: 'Joana', password: '') }
.not_to(change { user.reload.password_digest })
Если вы хотите протестировать его, не забудьте найти пользователя перед обновлением, чтобы не получить ложных срабатываний:
user = User.find(user.id)
🤔 А знаете ли вы, что...
Фреймворк предоставляет средства для масштабирования приложений, включая работу с множеством серверов и балансировку нагрузки.
Я справляюсь с этим в своем приложении следующим образом. Этой части моего кода уже несколько лет, поэтому в более поздних версиях Rails могут быть некоторые улучшения, но она работает нормально.
Конечно, вы шифруете пароль перед сохранением в базе данных. Допустим, поле базы данных, в котором хранится зашифрованный пароль, — crypted_password
.
Теперь в вашей модели User
у вас есть:
class User < ApplicationRecord
attr_accessor :password # this is the name of the parameter that comes in from the form on the front end
before_save :encrypt_password
def encrypt_password
return if password.blank? # return early and don't assign any value to crypted_password, and it will not be changed
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if salt.blank?
self.crypted_password = encrypt(password) # encrypt, here, is a method provided by my included authentication module
end
end
ОБНОВЛЕНИЕ Теперь, когда мы увидели реальную модель пользователя...
has_secure_password уже работает так, как вы описываете. Он добавляет кучу собственных проверок и игнорирует пустой пароль.
class User < ApplicationRecord
has_secure_password
validates :password,
presence: true,
length: { minimum: 6 },
confirmation: true
end
И несколько быстрых проверок в консоли Rails.
3.2.2 :001 > user = User.create!(name: "Foo", password: "abc123")
TRANSACTION (0.1ms) begin transaction
User Create (0.7ms) INSERT INTO "users" ("name", "password", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id" [["name", "Foo"], ["password", "[FILTERED]"], ["password_digest", "[FILTERED]"], ["created_at", "2024-06-20 19:09:14.107560"], ["updated_at", "2024-06-20 19:09:14.107560"]]
TRANSACTION (1.1ms) commit transaction
=>
#<User:0x00000001096d28d8
...
3.2.2 :002 > user.update!(name: 'Joana', password: '')
TRANSACTION (0.1ms) begin transaction
User Update (0.4ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Joana"], ["updated_at", "2024-06-20 19:09:17.852613"], ["id", 6]]
TRANSACTION (0.1ms) commit transaction
=> true
3.2.2 :003 > user.update!(name: 'Joana', password: '')
=> true
3.2.2 :004 > user.update!(name: 'Joana', password: nil)
(irb):4:in `<main>': Validation failed: Password can't be blank, Password can't be blank, Password is too short (minimum is 6 characters) (ActiveRecord::RecordInvalid)
user.update!(name: 'Joana', password: '')
обновляет только имя, а не пароль. Это функция, предоставленная has_secure_password
.
class User
before_update -> { self.restore_password! if self.password.blank? }
...
end
Однако обратные вызовы моделей могут вызвать проблемы, особенно когда они обеспечивают соблюдение «бизнес-правил», которые могут измениться или не быть универсально применимыми. А иногда обратные вызовы пропускаются и могут усложнить массовое обновление базы данных. Это проблема «толстой модели».
Вместо этого сделайте это как проверку модели.
class User
validates :password, presence: true
...
end
Обычно вы оставляете все как есть и используете проверку, чтобы сообщить пользователю о проблеме.
Если вы хотите обновить другие атрибуты пользователя, даже если один из них недействителен, это сомнительная практика, вам следует удалить пустой параметр пароля в вашем контроллере. Лучшая причина — если пользователь случайно вставит пробел в поле пароля или что-то в этом роде.
class UserController
...
def update
# or `params.compact_blank!` to do this for all parameters
params.delete(:password) if params[:password].blank?
...
end
end
Но не изобретайте заново учетные записи пользователей. Используйте придумать . Он безопасен, хорошо документирован и работает со многими другими драгоценными камнями.
class User < ApplicationRecord
has_secure_password
validates(
:password,
allow_nil: { on: :update }, # add this
length: { minimum: 6 }
)
# ...
end
Таким образом, эти ожидания оправдываются:
user = User.find(user.id) # Refresh object as if you were in controller
expect(user.update(name: 'Joana')).to be(true)
expect(user.update(name: 'Joana', password: '')).to be(true)
expect { user.update(name: 'Joana', password: '') }
.not_to(change { user.reload.password_digest })
Однако имейте в виду, что он не будет обновляться с фактическим паролем nil
:
expect(user.update(name: 'Joana', password: nil)).to be(false)
Итак, это противоречит здравому смыслу, но это работает.
Подтверждение и проверка присутствия не требуются, поскольку они предоставляются Rails.