SinatraでDeviseの中身を実装・解説/ハッシュ解説

#前説
Railsで用いられるユーザー管理gemであるDeviseって便利だなあ。
環境構築、データベース接続設定は[こちら](https://qiita.com/networkyohan69/items/ee2d2514fd34652d85c6)

今回はSinatraで一から実装

#環境
- Sinatra
- MySQL

#すべきこと
- ログイン認証
 - パスワードをひた隠し
 - ログイン・ログアウト
 - 新規アカウント作成
- ユーザーに関するデータベース作成
-  アソシエーション

#必要なページとHTTPメソッド
- GET log_in
- GET log_out
- GET sign_up

- POST session(log in)
- DELETE session(log out)
- POST new(sign up)

#必要なカラム
- id
- name
- email
- password
- password_confirmation
- updated_date
- created_date

#Databse作成
 
```sql:mysql
mysql>CREATE [DATABASE_NAME];
mysql>USE [DATABASE_NAME];

mysql>CREATE TABLE [TABLE_NAME] (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  email VARCHAR(255) NOT NULL,
  url VARCHAR(255) NOT NULL,
  password VARCHAR(255) NOT NULL,
  password_confirmation VARCHAR(255) NOT NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL,
  PRIMARY KEY(id)
);
```

#main.rb
中身のプログラムを細かく書いていきます

```ruby:main.rb
#メソッドのところだけ説明します！！
#sign_upページを参照
get '/sign_up' do
  #sessionにuser_idが保存されている -> ログインできてるやん
  session[:user_id] ||= nil
  if session[:user_id]
    redirect '/log_out'
  end

  erb :sign_up
end

#user作成を行う
post '/new' do
  #登録時のパスワード確認がうまくいきませんでした -> もう一回登録して、どうぞ
  if params[:password] != params[:confirm_password]
    redirect "/sign_up"
  end

  #Userクラスより新規データを追加、インスタンス作成
  user = User.new(email: params[:email], name: params[:name])
  #パスワードを暗号化
  user.encrypt_password(params[:password])
  #保存できた！
  if user.save!
    #ログイン完了 -> mainページへ
    session[:user_id] = user.id
    redirect "/"
  else
    redirect "/sign_up"
  end
end

#ログインページ
get '/log_in' do
  #sessionにuser_idが保存されている -> ログインできてるやん
  if session[:user_id]
    redirect '/log_out'
  end
  erb :log_in
end

#ログイン
post '/session' do
  #ログイン、できてるよー
  if session[:user_id]
    redirect "/"
  end
  
  #ユーザー認証
  user = User.authenticate(params[:email], params[:password])
  #oK! -> 現在のuserにuser.idが当てはまる
  if user
    session[:user_id] = user.id
    user_id = session[:user_id]
    redirect '/'
  #もう一度ログインして、どうぞ
  else
    redirect "/log_in"
  end
end


#ログアウトページの参照
get '/log_out' do
  unless session[:user_id]
    redirect '/log_in'
  end 

  erb :log_out
end

#ログアウト
delete '/session' do
  session[:user_id] = nil
  user_id = session[:user_id]
  redirect '/log_in'
end
```

忘れがちなところ(省いています・・・)

```ruby:main.rb
#modelsディレクトリの中のuser.rbから呼び出し
require_relative 'models/user'

-----

#sessionを使いたいのならば、以下を書いてください
class Server < Sinatra::Base
  enable :sessions
  set :session_secret, "My session secret"
end
```

#user.rb

```ruby:user.rb
Bundler.require

class User <ActiveRecord::Base
  #データベースとの連携をmain.rbと同様に
  ActiveRecord::Base.configurations = YAML.load_file('database.yml')
  ActiveRecord::Base.establish_connection(:development)

  #name,email,passwordは必須かつ一意
  validates_presence_of :name
  validates_uniqueness_of :name
  validates_presence_of :email
  validates_uniqueness_of :email
  validates :password_hash, confirmation: true
  validates :password_hash, presence: true
  validates :password_salt, presence: true

 #passwordをハッシュ化
 def encrypt_password(password)
    if password.present?
      #詳細は次の章
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end 
  end


  #emailとpasswordで認証メソッド
  def self.authenticate(email, password)
    #現ログイン時userのemailを取得
    user = self.where(email: email).first
    #認証　（ハッシュ化したパスワードで認証　詳細は次の章)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil 
    end 
  end 

end
```

#ハッシュ化認証
Papper(胡椒)とSalt(塩)というフレーズがありますが、あらかじめ打ち込んだ平文パスワードに設定されている文字列(=暗号パターン)を胡椒とすると、ランダムで生成されるハッシュを塩と例えています。平文を塩胡椒でまぶしてしまおうというわけです。
今回は塩でまぶしてやります。

つまり・・・

```ruby:user.rb

#passwordの暗号化
 def encrypt_password(password)
    if password.present?
      #パスワードにおけるランダム文字列生成-saltと定義
      self.password_salt = BCrypt::Engine.generate_salt
      　#ランダム生成した暗号,パスワードをハッシュとして
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end 
  end


#emailとpasswordで認証メソッド
  def self.authenticate(email, password)
    #現ログイン時userのemailを取得
    user = self.where(email: email).first

    #ログインしたいuserのemailを取得できるか
    #打ち込まれたpasswordがログインしたいユーザーのハッシュと等しいか調査
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil 
    end 
  end 
```

#最後にView
今回はSignupページだけ載せておきます。

```erb:sign_up.erb
<form action="/users" method="POST">
    <div>
    name: <input type="text" name="name" value="">
    </div>
    <div>
    email: <input type="text" name="email" value="">
    </div>
    <div>
    password: <input type="password" name="password" value="">
    </div>
    <div>
    password_confirm: <input type="password" name="confirm_password" value="">
    </div>
    <div>
    <input type="submit" value="登録">
    </div>
</form>
```

#結果

<img width="246" alt="スクリーンショット 2018-06-29 22.50.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/253129/e37095bc-c79a-249e-4643-3135f73238d2.png">

↓

<img width="532" alt="スクリーンショット 2018-06-29 22.51.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/253129/d7083295-c4ec-0a79-8c89-ea350d704d08.png">


#参考文献
ハッシュ関連
- https://www.rubydoc.info/github/codahale/bcrypt-ruby/BCrypt%2FEngine.hash_secret
- https://qiita.com/kenjiszk/items/d38e398bc120cf4bbd1c
ヒントになりました
- https://dyama.org/2015/03/sinatra-mongoid%E3%81%A7%E7%B0%A1%E6%98%93%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86/