【Go】GraphQL の Mutation(新規登録)とパスワードハッシュ化

Mutation(新規登録)とパスワードハッシュ化して登録、ハッシュ値と平文の比較の実装。

開発環境

  • go 1.17
  • github.com/99designs/gqlgen v0.17.1
  • github.com/graph-gophers/dataloader v5.0.0+incompatible

本文

Mutation(新規登録)

「ブログを新規登録する」というケースを題材として Mutation を作った。

完成品は下記。

https://gitlab.com/k1350/daybreak_sample/-/tree/graphql_mutation_sample

まず定義から。今回はまだ入力値のバリデーションは実装していない。

type Mutation {
    createBlog(input: CreateBlogInput!): Blog
}

input CreateBlogInput {
    author: String!
    name: String!
    description: String
    publishOption: PublishOption!
    password: String
    links: [BlogLinkInput!]
}

input BlogLinkInput {
    name: String
    url: String!
}

Query の実装と比較し、特筆するのはトランザクション処理が必要なケース。

ブログ本体は blog というテーブルに、リンク(著者の Twitter アカウントとかをブログと紐づけるようなケースを想定)というのを blog_link というテーブルに格納しているので、ブログ新規登録時はトランザクション処理が必要となる。

ここ からトランザクション処理の実装となるが、基本的には

  • tx, err := db.Begin() で begin
  • defer で panic が起こったらロールバックする処理を書いておく
  • tx.Exec というように、いつも db を使っている部分を tx に置き換えて処理を書く
  • エラーが発生したら tx.Rollback()
  • エラーなく最後まで到達したら tx.Commit()

となる。

ラッパー関数書いたほうが最終的には良さそうだが、とりあえず今は愚直に書いた。

パスワードハッシュ化

パスワードで閲覧制限されたブログというのを想定しているので、新規登録時にパスワードをハッシュ化して blog テーブルに入れる。

パスワードハッシュ化には bcrypt を使う。

ハッシュ化に使うのは GenerateFromPassword

hashed, err := bcrypt.GenerateFromPassword([]byte(*input.Password), 12)

こんな感じで使う。cost はデフォルトが 10 なので大体それくらいにしておいた。

戻り値は []byte なので、文字列として扱うときはキャストする。

ハッシュ化されたパスワードと平文のパスワードを比較する場合、CompareHashAndPassword を使う。

pErr := bcrypt.CompareHashAndPassword([]byte(password), []byte(*opts.Password))
switch {
case pErr == bcrypt.ErrMismatchedHashAndPassword:
    // パスワードが一致しない
case err != nil:
    // パスワードが一致しない以外のエラー
default:
    // パスワード一致
}

こんな感じになる。

注意点として GenerateFromPassword で生成したハッシュ値はソルトが付与されていて、同じ文字列をハッシュ化しても毎回違う値が生成される。

そのため平文のパスワードをハッシュ化して DB に保存してある値と一致するか調べてもダメで、DB から取り出したハッシュ値と平文を CompareHashAndPassword で比較する必要がある。

以上