From 333b0259ba5dd420a27a1c4146efe14da862a419 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 27 May 2022 12:38:06 +0800 Subject: [PATCH] Fix tests --- packages/api/src/apollo.ts | 14 +++++--- packages/api/src/resolvers/api_key/index.ts | 4 +-- packages/api/src/resolvers/user/index.ts | 6 ++-- packages/api/src/utils/auth.ts | 36 ++++++++++++------- .../api/test/gql/sanitize-directive.test.ts | 4 +-- packages/api/test/resolvers/api_key.test.ts | 25 +++++-------- packages/api/test/resolvers/user.test.ts | 4 +-- 7 files changed, 50 insertions(+), 43 deletions(-) diff --git a/packages/api/src/apollo.ts b/packages/api/src/apollo.ts index 2db7d48bb..ab27e26e0 100644 --- a/packages/api/src/apollo.ts +++ b/packages/api/src/apollo.ts @@ -49,10 +49,16 @@ const contextFunc: ContextFunction = async ({ }) if (token) { - jwt.verify(token, env.server.jwtSecret) && - (claims = jwt.decode(token) as Claims) - if (!claims) { - claims = await claimsFromApiKey(token) + try { + jwt.verify(token, env.server.jwtSecret) && + (claims = jwt.decode(token) as Claims) + } catch (e) { + if (e instanceof jwt.JsonWebTokenError) { + logger.info(`not a jwt token, checking api key`, { token }) + claims = await claimsFromApiKey(token) + } else { + throw e + } } } diff --git a/packages/api/src/resolvers/api_key/index.ts b/packages/api/src/resolvers/api_key/index.ts index 078076b16..b6f55f92e 100644 --- a/packages/api/src/resolvers/api_key/index.ts +++ b/packages/api/src/resolvers/api_key/index.ts @@ -10,7 +10,7 @@ import { authorized } from '../../utils/helpers' import { getRepository } from '../../entity/utils' import { User } from '../../entity/user' import { ApiKey } from '../../entity/api_key' -import { generateApiKey, hashKey } from '../../utils/auth' +import { generateApiKey, hashApiKey } from '../../utils/auth' export const generateApiKeyResolver = authorized< GenerateApiKeySuccess, @@ -41,7 +41,7 @@ export const generateApiKeyResolver = authorized< await getRepository(ApiKey).save({ user: { id: uid }, name, - key: hashKey(apiKey), + key: hashApiKey(apiKey), expiresAt: exp, }) diff --git a/packages/api/src/resolvers/user/index.ts b/packages/api/src/resolvers/user/index.ts index d6c16092a..3c8673c5f 100644 --- a/packages/api/src/resolvers/user/index.ts +++ b/packages/api/src/resolvers/user/index.ts @@ -33,7 +33,7 @@ import { env } from '../../env' import { validateUsername } from '../../utils/usernamePolicy' import * as jwt from 'jsonwebtoken' import { createUser } from '../../services/create_user' -import { compareHashedKey, hashKey } from '../../utils/auth' +import { comparePassword, hashPassword } from '../../utils/auth' export const updateUserResolver = authorized< UpdateUserSuccess, @@ -310,7 +310,7 @@ export const loginResolver: ResolverFn< } // check if password is correct - const validPassword = compareHashedKey(password, user.password) + const validPassword = await comparePassword(password, user.password) if (!validPassword) { return { errorCodes: [LoginErrorCode.InvalidCredentials] } } @@ -331,7 +331,7 @@ export const signupResolver: ResolverFn< try { // hash password - const hashedPassword = hashKey(password) + const hashedPassword = await hashPassword(password) const [user, profile] = await createUser({ email, diff --git a/packages/api/src/utils/auth.ts b/packages/api/src/utils/auth.ts index f1581e1c4..2d2481916 100644 --- a/packages/api/src/utils/auth.ts +++ b/packages/api/src/utils/auth.ts @@ -3,13 +3,14 @@ import { v4 as uuidv4 } from 'uuid' import { Claims } from '../resolvers/types' import { getRepository } from '../entity/utils' import { ApiKey } from '../entity/api_key' +import crypto from 'crypto' -export const hashKey = (key: string, salt = 10) => { - return bcrypt.hashSync(key, salt) +export const hashPassword = async (password: string, salt = 10) => { + return bcrypt.hash(password, salt) } -export const compareHashedKey = (rawKey: string, hash: string) => { - return bcrypt.compareSync(rawKey, hash) +export const comparePassword = async (password: string, hash: string) => { + return bcrypt.compare(password, hash) } export const generateApiKey = (): string => { @@ -17,17 +18,26 @@ export const generateApiKey = (): string => { return uuidv4() } -export const claimsFromApiKey = async ( - key: string -): Promise => { - const hashedKey = hashKey(key) +export const hashApiKey = (apiKey: string) => { + return crypto.createHash('sha256').update(apiKey).digest('hex') +} + +export const claimsFromApiKey = async (key: string): Promise => { + const hashedKey = hashApiKey(key) const apiKey = await getRepository(ApiKey).findOne({ - where: { key: hashedKey }, + where: { + key: hashedKey, + }, relations: ['user'], }) if (!apiKey) { - console.error('api key not found') - return undefined + throw new Error('api key not found') + } + + const iat = Math.floor(Date.now() / 1000) + const exp = Math.floor(new Date(apiKey.expiresAt).getTime() / 1000) + if (exp < iat) { + throw new Error('api key expired') } // update last used @@ -35,7 +45,7 @@ export const claimsFromApiKey = async ( return { uid: apiKey.user.id, - iat: new Date().getTime(), - exp: apiKey.expiresAt.getTime(), + iat, + exp, } } diff --git a/packages/api/test/gql/sanitize-directive.test.ts b/packages/api/test/gql/sanitize-directive.test.ts index f60bfcab6..d4d8820b7 100644 --- a/packages/api/test/gql/sanitize-directive.test.ts +++ b/packages/api/test/gql/sanitize-directive.test.ts @@ -1,7 +1,7 @@ import { createTestUser, deleteTestUser } from '../db' import { graphqlRequest, request } from '../util' import { User } from '../../src/entity/user' -import { hashKey } from '../../src/utils/auth' +import { hashPassword } from '../../src/utils/auth' import 'mocha' describe('Sanitize Directive', () => { @@ -12,7 +12,7 @@ describe('Sanitize Directive', () => { let user: User before(async () => { - const hashedPassword = hashKey(correctPassword) + const hashedPassword = await hashPassword(correctPassword) user = await createTestUser(username, '', hashedPassword) const res = await request .post('/local/debug/fake-user-login') diff --git a/packages/api/test/resolvers/api_key.test.ts b/packages/api/test/resolvers/api_key.test.ts index 32aac8eab..a9a51d109 100644 --- a/packages/api/test/resolvers/api_key.test.ts +++ b/packages/api/test/resolvers/api_key.test.ts @@ -28,7 +28,8 @@ describe('generate api key', () => { let authToken: string let user: User let query: string - let expiredAt: string + let expiresAt: string + let name: string before(async () => { // create test user and login @@ -49,7 +50,8 @@ describe('generate api key', () => { query = ` mutation { generateApiKey(input: { - expiredAt: "${expiredAt}" + name: "${name}" + expiresAt: "${expiresAt}" }) { ... on GenerateApiKeySuccess { apiKey @@ -62,22 +64,10 @@ describe('generate api key', () => { ` }) - context('when no expiredAt is specified', () => { - before(() => { - expiredAt = '' - }) - - it('should generate an api key with no expiration date', async () => { - const response = await graphqlRequest(query, authToken) - expect(response.body.data.generateApiKey.apiKey).to.be.a('string') - - return testAPIKey(response.body.data.generateApiKey.apiKey).expect(200) - }) - }) - context('when api key is not expired', () => { before(() => { - expiredAt = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString() + name = 'test' + expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString() }) it('should generate an api key', async () => { @@ -90,7 +80,8 @@ describe('generate api key', () => { context('when api key is expired', () => { before(() => { - expiredAt = new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString() + name = 'test-expired' + expiresAt = new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString() }) it('should generate an expired api key', async () => { diff --git a/packages/api/test/resolvers/user.test.ts b/packages/api/test/resolvers/user.test.ts index cc065f6be..7115ca7ed 100644 --- a/packages/api/test/resolvers/user.test.ts +++ b/packages/api/test/resolvers/user.test.ts @@ -8,7 +8,7 @@ import { UpdateUserProfileErrorCode, } from '../../src/generated/graphql' import { User } from '../../src/entity/user' -import { hashKey } from '../../src/utils/auth' +import { hashPassword } from '../../src/utils/auth' import 'mocha' describe('User API', () => { @@ -21,7 +21,7 @@ describe('User API', () => { let anotherUser: User before(async () => { - const hashedPassword = hashKey(correctPassword) + const hashedPassword = await hashPassword(correctPassword) // create test user and login user = await createTestUser(username, '', hashedPassword) const res = await request