zinのLogin

#はじめに
GolangのZinを使いログイン処理を書きました　

#Login
```
[hoge] % tree                                                                         ~/go/src/login
.
├── SessionInfo
│   └── sessioninfo.go
├── controller
│   └── Login.go
├── crypto
│   └── crypto.go
├── db
│   └── db.go
├── go.mod
├── go.sum
├── login
├── main.go
├── migrate
│   └── migrate.go
├── model
│   ├── Login.go
│   └── User.go
└── template
    ├── login.html
    ├── logout.html
    ├── menu.html
    └── singup.html

7 directories, 15 files
```

##main
```
package main

import (
   "log"
   sessioninfo "login/SessionInfo"
   "login/controller"
   "net/http"

   "github.com/gin-contrib/sessions"
   "github.com/gin-contrib/sessions/cookie"
   "github.com/gin-gonic/gin"
)

var LoginInfo sessioninfo.SessionInfo

func main() {
   engine := gin.Default()
   engine.LoadHTMLGlob("template/*")
   store := cookie.NewStore([]byte("select"))
   engine.Use(sessions.Sessions("mysession", store))

   engine.GET("/login", func(c *gin.Context) {
       c.HTML(200, "login.html", gin.H{
           "UserId": "",
       })
   })
   engine.POST("/login", controller.NewLogin().LoginK)

   engine.GET("/singup", func(c *gin.Context) {
       c.HTML(200, "singup.html", gin.H{})
   })
   engine.POST("/singup", controller.NewLogin().SingUp)
   menu := engine.Group("/menu")
   menu.Use(sessionCheck())
   {
       menu.GET("/top", controller.GetMenu)
       
   }

   engine.POST("/logout", controller.PostLogout)
   engine.Run(":8080")
}

func sessionCheck() gin.HandlerFunc {
   return func(c *gin.Context) {
       session := sessions.Default(c)
       LoginInfo.Name = session.Get("name")
       // セッションがない場合、ログインフォームをだす
       if LoginInfo.Name == nil {
           log.Println(session)
           log.Println("ログインしていません")
           c.Redirect(http.StatusMovedPermanently, "/login")
           c.Abort() // これがないと続けて処理されてしまう
       } else {
           c.Set("name", LoginInfo.Name) // ユーザidをセット
           c.Next()
       }
       log.Println("ログインチェック終わり")
   }
}
```



##db
```
package db

import (
 "fmt"
 "os"

 "github.com/jinzhu/gorm"
 _ "github.com/jinzhu/gorm/dialects/mysql"
 "github.com/joho/godotenv"
)

func Connection() *gorm.DB {
 err := godotenv.Load(fmt.Sprintf("./%s.env", os.Getenv("GO_ENV")))
 if err != nil {
   // .env読めなかった場合の処理
 }
 DBMS := os.Getenv("login_DBMS")
 USER := os.Getenv("login_USER")
 PASS := os.Getenv("login_PASS")
 DBNAME := os.Getenv("login_DBNAME")
 CONNECT := USER + ":" + PASS + "@/" + DBNAME + "?parseTime=true"
 db, err := gorm.Open(DBMS, CONNECT)

 if err != nil {
   panic(err.Error())
 }
 db.LogMode(true)
 return db
}
```




##migrate
```
package main

import (
   "login/db"
   "login/model"
)

func main() {
   db := db.Connection()
   defer db.Close()

   db.AutoMigrate(&model.Login{})
   db.AutoMigrate(&model.User{})
}
```



##model
###DB
```
package model

import (
   _ "github.com/go-sql-driver/mysql"
   "github.com/jinzhu/gorm"
   _ "github.com/jinzhu/gorm/dialects/mysql"
)

type Login struct {
   gorm.Model
   Name string `gorm:"unique; not null"`
   Pass string `gorm:"not null"`
}
```



##crypto
```
package crypto

import (
   "golang.org/x/crypto/bcrypt"
)

// PasswordEncrypt パスワードをhash化
func PasswordEncrypt(password string) (string, error) {
   hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
   return string(hash), err
}

// CompareHashAndPassword hashと非hashパスワード比較
func CompareHashAndPassword(hash, password string) error {
   return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}
```
##Sessions 
```
package sessioninfo

type SessionInfo struct {
   Name interface{}
}
```
 

##Controller
```
package controller

import (
   "log"
   "login/crypto"
   "login/db"
   "login/model"
   "net/http"

   "github.com/gin-contrib/sessions"
   "github.com/gin-gonic/gin"
)

type Login struct{}

func NewLogin() *Login {
   return &Login{}
}

func LoginM(c *gin.Context, name string) {
   session := sessions.Default(c)
   session.Set("name", name)
   session.Save()
}
func getUser(username string) model.Login {
   db := db.Connection()
   var user model.Login
   db.First(&user, "name = ?", username)
   db.Close()
   return user
}
func (l *Login) LoginK(c *gin.Context) {
   db := db.Connection()
   defer db.Close()
   log.Println("ログイン処理")
   name := c.PostForm("name")

   LoginM(c, name) // // 同じパッケージ内のログイン処理

   dbPassword := getUser(c.PostForm("name")).Pass
   log.Println(dbPassword)
   // フォームから取得したユーザーパスワード
   formPassword := c.PostForm("pass")

   // ユーザーパスワードの比較
   if err := crypto.CompareHashAndPassword(dbPassword, formPassword); err != nil {
       log.Println("ログインできませんでした")

       c.Abort()
   } else {
       log.Println("ログインできました")
       c.Redirect(http.StatusMovedPermanently, "/menu/top")
   }
}
func (l *Login) SingUp(c *gin.Context) {
   var form Login
   if err := c.Bind(&form); err != nil {
       c.Abort()
   } else {
       username := c.PostForm("name")
       password := c.PostForm("pass")
       // 登録ユーザーが重複していた場合にはじく処理PasswordEncrypt

       passwordEncrypt, _ := crypto.PasswordEncrypt(password)
       db := db.Connection()
       defer db.Close()

       if err := db.Create(&model.Login{Name: username, Pass: passwordEncrypt}).GetErrors(); err != nil {

       }
       c.Redirect(302, "/login")   }
}
func PostLogout(c *gin.Context) {
   log.Println("ログアウト処理")
   Logout(c) // 同じパッケージ内のログアウト処理

   // ログインフォームに戻す
   c.HTML(http.StatusOK, "login.html", gin.H{
       "name":         "",
       "ErrorMessage": "",
   })
}
func Logout(c *gin.Context) {
   session := sessions.Default(c)
   log.Println("セッション取得")
   session.Clear()
   log.Println("クリア処理")
   session.Save()
}
func GetMenu(c *gin.Context) {
   name, _ := c.Get("name") // ログインユーザの取得

   c.HTML(http.StatusOK, "menu", gin.H{"name": name})
}

```