refactor auth related identifiers

master
Peter Babič 5 years ago
parent b2a95e3bb8
commit 1ca1d8b2c6
Signed by: peter.babic
GPG Key ID: 4BB075BC1884BA40
  1. 4
      src/server.spec.ts
  2. 6
      src/server.ts
  3. 87
      src/server/UserResolver.spec.ts
  4. 9
      src/server/UserResolver.ts
  5. 51
      src/server/userResolver/auth.ts

@ -38,11 +38,11 @@ describe("server should", () => {
})
const meResponse = await client.rawRequest(gqlToString(meQuery))
const response = await fetch(refreshTokenUri, {
const refreshTokenResponse = await fetch(refreshTokenUri, {
method: "POST",
headers: { cookie: cookieHeader },
})
const jsonResponse = await response.json()
const jsonResponse = await refreshTokenResponse.json()
expect(cookieHeader).toMatch(/HttpOnly/)
expect(parsedCookie.Path).toBe("/refresh_token")

@ -2,8 +2,8 @@ import express = require("express")
import { ApolloServer } from "apollo-server-express"
import { createSchema } from "./server/schema"
import {
accessTokenWithRefreshCookie,
contextFunction,
refreshTokens,
verifiedRefreshTokenPayload,
} from "./server/userResolver/auth"
import cookie = require("cookie")
@ -21,8 +21,8 @@ export const createServer = async (port: number) => {
app.post("/refresh_token", (req, res) => {
try {
const parsedCookie = cookie.parse(req.headers.cookie!)
const refreshTokenPayload = verifiedRefreshTokenPayload(parsedCookie.rt)
const accessToken = refreshTokens(refreshTokenPayload.userId, res)
const refreshPayload = verifiedRefreshTokenPayload(parsedCookie.rt)
const accessToken = accessTokenWithRefreshCookie(refreshPayload.userId, res)
res.json({ data: accessToken })
} catch (error) {
res.json({ data: null, errors: "Refresh failed: " + error })

@ -11,31 +11,11 @@ import { AccessToken } from "./userResolver/AccessToken"
import { Context, signAccessToken, verifiedAccessTokenPayload } from "./userResolver/auth"
import { User } from "./userResolver/User"
beforeAll(async () => {
initializeRollbackTransactions()
await createConnection(testingConnectionOptions())
})
afterAll(async () => {
await getConnection().close()
})
describe("resolver of user", () => {
describe("createUser mutation should", () => {
it(
"return email as it creates user with mutation",
runInRollbackTransaction(async () => {
const createUserMutation = gql`
mutation {
createUser(
email: "user-mutation@user-resolver.com"
password: "password"
) {
email
}
}
`
const response = await callSchema(createUserMutation)
expect(response.errors).toBeUndefined()
@ -50,14 +30,6 @@ describe("resolver of user", () => {
it(
"return emails of registered users",
runInRollbackTransaction(async () => {
const usersQuery = gql`
query {
users {
email
}
}
`
await User.create({
email: "users-query@user-resolver.com",
}).save()
@ -73,18 +45,6 @@ describe("resolver of user", () => {
})
describe("accessToken query should", () => {
const accessTokenQuery = gql`
query {
accessToken(
email: "access-token@user-resolver.com"
password: "password"
) {
jwt
jwtExpiry
}
}
`
it(
"return error for bad password or not-existent user",
runInRollbackTransaction(async () => {
@ -126,14 +86,6 @@ describe("resolver of user", () => {
})
describe("me query should", () => {
const meQuery = gql`
query {
me {
email
}
}
`
it(
"return an error without a valid access token",
runInRollbackTransaction(async () => {
@ -168,6 +120,45 @@ describe("resolver of user", () => {
})
})
beforeAll(async () => {
initializeRollbackTransactions()
await createConnection(testingConnectionOptions())
})
afterAll(async () => {
await getConnection().close()
})
const createUserMutation = gql`
mutation {
createUser(email: "user-mutation@user-resolver.com", password: "password") {
email
}
}
`
const usersQuery = gql`
query {
users {
email
}
}
`
const accessTokenQuery = gql`
query {
accessToken(email: "access-token@user-resolver.com", password: "password") {
jwt
jwtExpiry
}
}
`
const meQuery = gql`
query {
me {
email
}
}
`
const contextWithAuthHeader = (header: string): Context => ({
req: {
headers: {

@ -1,7 +1,7 @@
import "reflect-metadata"
import { Arg, Authorized, Ctx, Mutation, Query } from "type-graphql"
import { AccessToken } from "./userResolver/AccessToken"
import { comparePassword, Context, refreshTokens } from "./userResolver/auth"
import { comparePasswords, Context, accessTokenWithRefreshCookie } from "./userResolver/auth"
import { User } from "./userResolver/User"
export class UserResolver {
@ -18,12 +18,9 @@ export class UserResolver {
) {
try {
const user = await User.findOne({ where: { email } })
await comparePasswords(user!.password, password)
if (!(await comparePassword(user!.password, password))) {
throw new Error()
}
return refreshTokens(user!.id, res)
return accessTokenWithRefreshCookie(user!.id, res)
} catch (error) {
throw new Error("Login credentials are invalid: " + error)
}

@ -1,35 +1,24 @@
import { argon2id, hash, verify as argonVerify } from "argon2"
import { argon2id, hash as argonHash, verify as argonVerify } from "argon2"
import { Request, Response } from "express"
import { sign, verify as jwtVerify } from "jsonwebtoken"
import { sign as jwtSign, verify as jwtVerify } from "jsonwebtoken"
import { AuthChecker } from "type-graphql"
import { AccessToken } from "./AccessToken"
export type Context = {
req: Request
res: Response
payload?: ContextPayload
}
export const hashPassword = async (password: string) =>
await argonHash(password, { type: argon2id })
type ContextPayload = {
userId: number
}
export const comparePasswords = async (hash: string, plain: string) => {
if (!(await argonVerify(hash, plain, { type: argon2id }))) {
throw new Error("Passwords do not match")
}
type JWTPayload = {
userId: number
iat: number
exp?: number
return true
}
export const hashPassword = async (password: string) =>
await hash(password, { type: argon2id })
export const comparePassword = async (hash: string, plain: string) =>
await argonVerify(hash, plain, { type: argon2id })
export const signAccessToken = (payload: ContextPayload) => {
const accessTokenSecret = process.env.ACCESS_SECRET as string
return sign(payload, accessTokenSecret, {
return jwtSign(payload, accessTokenSecret, {
expiresIn: parseInt(process.env.ACCESS_EXPIRY as string),
})
}
@ -37,7 +26,7 @@ export const signAccessToken = (payload: ContextPayload) => {
export const signRefreshToken = (payload: ContextPayload) => {
const accessTokenSecret = process.env.REFRESH_SECRET as string
return sign(payload, accessTokenSecret, {
return jwtSign(payload, accessTokenSecret, {
expiresIn: parseInt(process.env.REFRESH_EXPIRY as string),
})
}
@ -54,7 +43,7 @@ export const verifiedRefreshTokenPayload = (token: string) => {
return jwtVerify(token, refreshTokenSecret) as JWTPayload
}
export const refreshTokens = (userId: number, res: Response) => {
export const accessTokenWithRefreshCookie = (userId: number, res: Response) => {
const accessToken = new AccessToken()
accessToken.jwt = signAccessToken({ userId })
accessToken.jwtExpiry = parseInt(process.env.ACCESS_EXPIRY as string)
@ -83,3 +72,19 @@ export const customAuthChecker: AuthChecker<Context> = ({ context }) => {
}
export const contextFunction = ({ req, res }: Context) => ({ req, res })
export type Context = {
req: Request
res: Response
payload?: ContextPayload
}
type ContextPayload = {
userId: number
}
type JWTPayload = {
userId: number
iat: number
exp?: number
}

Loading…
Cancel
Save