hasura/graphql-engine

docs: add guide on custom JWT server with express & node

Open

#6,247 创建于 2020年11月24日

在 GitHub 查看
 (1 评论) (1 反应) (1 负责人)TypeScript (31,371 star) (2,787 fork)batch import
c/learnhelp wanteds/wip

描述

A guide on custom JWT server with express & node needs to be added to this PR.

This is the boilerplate code:

import express from "express"
import fetch from "cross-fetch"
import jwt from "jsonwebtoken"
import bcrypt from "bcryptjs"

const app = express()
const PORT = 3000

const HASURA_ENDPOINT = "http://localhost:8080/v1/graphql"
const HASURA_ADMIN_SECRET = "my-secret"
// Don't actually do this, read this from process.env in a real app
const HASURA_GRAPHQL_JWT_SECRET = "XM9RnWahz+qrjSJjG/RNCMTR55AWhj0BKkru9Ksr/rY="
const JWT_EXPIRE_TIME = "60m"

// This is a function which takes the URL and headers for Hasura queries
// and returns a function which sends GraphQL requests to the Hasura instance
const makeGraphQLClient = ({ url, headers }) => async ({
  query,
  variables,
}) => {
  const request = await fetch(url, {
    headers,
    method: "POST",
    body: JSON.stringify({ query, variables }),
  })
  return request.json()
}

const sendQuery = makeGraphQLClient({
  url: HASURA_ENDPOINT,
  headers: {
    "X-Hasura-Admin-Secret": HASURA_ADMIN_SECRET,
  },
})

function generateJWT({ allowedRoles, defaultRole, otherClaims }) {
  const payload = {
    "https://hasura.io/jwt/claims": {
      "x-hasura-allowed-roles": allowedRoles,
      "x-hasura-default-role": defaultRole,
      ...otherClaims,
    },
  }
  return jwt.sign(payload, HASURA_GRAPHQL_JWT_SECRET, {
    algorithm: "HS256",
    expiresIn: JWT_EXPIRE_TIME,
  })
}

/**
 * We will turn these REST API endpoints into Hasura Actions so that the client can call them through GraphQL queries within Hasura
 * https://hasura.io/docs/1.0/graphql/core/actions/action-handlers.html#http-handler
 *
 * To do this, we just need to define GraphQL types for:
 *  - The function's input arguments
 *  - The function's return type
 *
 * This can be done in the web console UI in Hasura, under the "Actions" tab
 */

app.post("/api/actions/signup", async (req, res) => {
  const user = req.body.input.user
  user.password = bcrypt.hashSync(user.password)

  const request = await sendQuery({
    query: `
      mutation InsertUser($user: user_insert_input!){
          insert_user_one(object: $user) {
            id
          }
        }`,
    variables: { user },
  })

  const token = generateJWT({
    defaultRole: "user",
    allowedRoles: ["user"],
    otherClaims: {
      "x-hasura-user-id": request.insert_user_one.id,
    },
  })

  return res.json({ token })
})

app.post("/api/actions/login", async (req, res) => {
  const user = req.body.input.user
  const request = await sendQuery({
    query: `
      query FindUserByEmail($email: String!){
          user(where: { email: { _eq: $email } }, limit: 1) {
            id
            password
          }
        }`,
    variables: { email: user.email },
  })

  const dbUser = request.user[0]
  if (!dbUser) return res.status(400).json({ error: "No user found" })

  const validPassword = bcrypt.compareSync(user.password, dbUser.password)
  if (!validPassword) return res.status(400).json({ error: "Invalid" })

  const token = generateJWT({
    defaultRole: "user",
    allowedRoles: ["user"],
    otherClaims: {
      "x-hasura-user-id": dbUser.id,
    },
  })
  return res.json({ token })
})

// Bind to 0.0.0.0 host, so it'll work in Docker too
app.listen(PORT, "0.0.0.0", () => {
  console.log(`Server started on http://localhost:${PORT}`)
})

The guide needs to be added here.

The structure can be similar to this.

贡献者指南