2021-01-03 [長年日記]
_ 「目標」よりも「目的地」
目標というものが昔から嫌いだ。制約を課されている窮屈な感じに嫌悪感を感じているようだ。社会人になってからは評価と結びついたりもして、良い印象がない。じゃあそれが嫌いになった原因なのか、いつから嫌いだったかと思い出して見ると、子供の頃から嫌いだったので、単にずっと嫌いなようだ。新年に目標を立てるのが嫌になって、「目標をつくらないのが目標」とした年もあったくらいだ。
一方で作戦を考えるのは好きで、どういう方向へ進むかを考えるのは嫌いではない。なので、「目標」という道具が私にあってないだけで、じゃあどういう道具だったらいいのかを考えていたら、「旅」のメタファーが良いのではないかと思いついた。
「目標」よりも「目的地」がいいのではないか。旅はきままなものなので目標のような束縛感も薄く、そこへ行くまでの旅程もたのしむように考えられる。さらに目的地からさらに先に足をのばしたい場所をいろいろ考えられるので、目標の達成時の断絶感も緩和される。
「目標を立てる」ことは「旅程をつくる」ことに相当する。以下のようなテンプレートに沿って考える。
- 目的地
- 目的地から見える世界の景色を妄想する
- さらにその先に進むとしたらどんなところがあるか
- 目的地へ行くための旅程を分解してみる
- 旅程のたのしみを考える
「目標」よりもたのしさ成分がかなり多く、良い感じだ。過程も考えられる。「目標」は「局所的に切り出した」感が強く、ゴール付近にだけ焦点が当てられているが、実際は過程の方がずっと長いから、そこをどうするかが重要なはずだ。
今回考えたメタファーはもうひとつあって、「街づくり」も良いなと考えている。「目的地」についたら世界がそれによって変わっているから、そこに人が集まっているかもしれない。そう考えると「旅」よりも「街づくり」のメタファーが向いているケースがありそうだ。旅は1人で、街づくりはみんなでやるイメージなので、世界への影響を重視するならば「街づくり」がいいかもしれない。ただ、旅はほとんどの人がやったことあるからイメージしやすいが、街づくりの方がイメージが難しい。とくに「過程のたのしさ」のイメージがむずかしいが、創作にたのしさを感じる人にはこちらの方が向いているかもしれない。
「街づくり」のメタファーでは「目標を立てる」ことを以下のようなテンプレートに沿って考える。
- つくりたい場所(店、公園、集会場などなど)
- それによって作られる未来と、そこでみんながたのしんでいる様子を妄想する
- それをさらに発展させたらどうなるか
- それをつくるための過程を分解してみる
- その場所をつくる過程でみんながたのしめることを考える
今年はこれらをつかってやりたいことを分解して考えてみたい。
2021-01-15 [長年日記]
_ has_secure_password から devise へ移行
has_secure_passwordをつかってdigest化してたパスワードデータをそのまま使ってdeviseへ移行できることを確認した。調べたことまとめ。
結論
- has_secure_passwordもdeviseもBCryptをつかってdigest化しているので、アルゴリズムはそのままで、BCrypt::Engine.costの値を揃えればどちらでも利用できる
- DBとモデルの移行について書いた
- deviseはコントローラ、ビューでもいろいろやってくれるので、そちらの対応は別途必要
- サンプルコード: https://github.com/igaiga/rails611_ruby300_secure_password_to_devise
バージョン
- Ruby 3.0.0
- Rails 6.1.1
- devise 4.7.3
- bcrypt 3.1.16
has_secure_passwordのdigest化コード
- ActiveModel::SecurePassword
- gems/activemodel-6.1.1/lib/active_model/secure_password.rb
digest化
-
BCrypt::Password.create(unencrypted_password, cost: 12)
でdigest化している -
secure_password.rb
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.public_send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
- 実行して確認したところ、デフォルトでは以下の値だった
- BCrypt::Engine::MIN_COST= 4
- BCrypt::Engine.cost= 12
- cost= 12
authenticate
-
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password)
で確認している -
secure_password.rb
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = public_send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
alias_method :authenticate, :authenticate_password if attribute == :password
deviseの動作について
コード
- lib/devise/models/database_authenticatable.rb
# authenticate
def valid_password?(password)
Devise::Encryptor.compare(self.class, encrypted_password, password)
end
...
# digest
def password_digest(password)
Devise::Encryptor.digest(self.class, password)
end
- lib/devise/encryptor.rb
module Devise
module Encryptor
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end
def self.compare(klass, hashed_password, password)
return false if hashed_password.blank?
bcrypt = ::BCrypt::Password.new(hashed_password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
Devise.secure_compare(password, hashed_password)
end
end
end
authenticate
BCrypt::Engine.hash_secret(password, bcrypt.salt)
とBCrypt::Password.new(hashed_password)
とで比較- secure_compareは以下のようなコードで、技はあるようだが一致確認をやっているだけと考えてよさそう
def self.secure_compare(a, b)
return false if a.blank? || b.blank? || a.bytesize != b.bytesize
l = a.unpack "C#{a.bytesize}"
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
digest化
BCrypt::Password.create(password, cost: klass.stretches).to_s
- has_secure_passwordでは
BCrypt::Password.create(unencrypted_password, cost: 12)
で暗号化している- costはデフォルト値
- deviseとhas_secure_password でどちらもBCrypt::Password.createをつかっているので、cost値を同じに調整すればいい
- deviseではstretches設定がcost値になる
- stretchesはモデルに書くdeviseメソッドの引数で指定できる
- 例:
devise :database_authenticatable, :registerable, :confirmable, :recoverable, stretches: 12
- has_secure_passwordでは
has_secure_passwordからdeviseへ移行
-
動作確認のために、has_secure_password時代にユーザーデータを作成しておく
-
devise gemをGemfileへ追加
-
$ bin/rails generate devise:install
- ちなみに config/initializers/devise.rbにもstrech設定があり、デフォルトは12になっている
config.stretches = Rails.env.test? ? 1 : 12
-
usersテーブルのカラム名を
password_digest
からencrypted_password
へ変更
class ChangeUserPasswordColumnName < ActiveRecord::Migration[6.1]
def change
rename_column :users, :password_digest, :encrypted_password
end
end
- Userモデルからhas_secure_passwordを消してdeviseの設定を追加
- stretchesは上述のconfig/initializers/devise.rbにデフォルトで書けるならその方がスッキリしてよさそう
class User < ApplicationRecord
devise :database_authenticatable, :registerable, stretches: 12
end
user.valid_password?(password)
でencrypted_password(=digest)が一致するか確認する
user.valid_password?("correct_password") #=> true
user.valid_password?("incorrect_password") #=> false