Skip to content
This repository has been archived by the owner on Mar 7, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3 from mazgi/improve-error-handling
Browse files Browse the repository at this point in the history
Improve error handling
  • Loading branch information
mazgi authored Feb 5, 2020
2 parents b86c536 + eb66389 commit 9716333
Show file tree
Hide file tree
Showing 41 changed files with 606 additions and 100 deletions.
6 changes: 5 additions & 1 deletion Dockerfile.d/bff/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ FROM node:12-alpine as base
FROM base as builder
COPY rootfs /
WORKDIR /workspace
RUN npm install\

# See https://github.com/kelektiv/node.bcrypt.js/issues/615#issuecomment-407300081
RUN apk add --no-cache --virtual deps python build-base\
&& npm install\
&& apk del deps\
&& npm run test\
&& npm run build
# End builder stage
Expand Down
8 changes: 5 additions & 3 deletions bff/src/config/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ class Config {
defaultConfig = { ...defaultConfig, ...devConfig }
}

const configFromURI = await getConfigFromURI()
const configFromS3 = await getConfigFromS3()
const configFromGCS = await getConfigFromGCS()
const [configFromURI, configFromS3, configFromGCS] = await Promise.all([
getConfigFromURI,
getConfigFromS3,
getConfigFromGCS
])

const configMerged = {
isDevelopment,
Expand Down
10 changes: 3 additions & 7 deletions bff/src/db/migrations/1580144828876-Initialize.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {MigrationInterface, QueryRunner} from "typeorm";
import { MigrationInterface, QueryRunner } from 'typeorm'

export class Initialize1580144828876 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {}

public async up(queryRunner: QueryRunner): Promise<any> {
}

public async down(queryRunner: QueryRunner): Promise<any> {
}

public async down(queryRunner: QueryRunner): Promise<any> {}
}
25 changes: 15 additions & 10 deletions bff/src/db/migrations/1580144854019-CreateTheWorld.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import {MigrationInterface, QueryRunner} from "typeorm";
import { MigrationInterface, QueryRunner } from 'typeorm'

export class CreateTheWorld1580144854019 implements MigrationInterface {
name = 'CreateTheWorld1580144854019'
name = 'CreateTheWorld1580144854019'

public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query("CREATE TABLE `world` (`id` varchar(36) NOT NULL, `name` varchar(255) NOT NULL, UNIQUE INDEX `IDX_9aa9325530beea5c1fdf1791f3` (`name`), PRIMARY KEY (`id`)) ENGINE=InnoDB", undefined);
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query("DROP INDEX `IDX_9aa9325530beea5c1fdf1791f3` ON `world`", undefined);
await queryRunner.query("DROP TABLE `world`", undefined);
}
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(
'CREATE TABLE `world` (`id` varchar(36) NOT NULL, `name` varchar(255) NOT NULL, UNIQUE INDEX `IDX_9aa9325530beea5c1fdf1791f3` (`name`), PRIMARY KEY (`id`)) ENGINE=InnoDB',
undefined
)
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(
'DROP INDEX `IDX_9aa9325530beea5c1fdf1791f3` ON `world`',
undefined
)
await queryRunner.query('DROP TABLE `world`', undefined)
}
}
35 changes: 23 additions & 12 deletions bff/src/db/migrations/1580296463597-CreateUser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import {MigrationInterface, QueryRunner} from "typeorm";
import { MigrationInterface, QueryRunner } from 'typeorm'

export class CreateUser1580296463597 implements MigrationInterface {
name = 'CreateUser1580296463597'
name = 'CreateUser1580296463597'

public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query("CREATE TABLE `user` (`id` varchar(36) NOT NULL, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `displayName` varchar(255) NOT NULL, `hashedPassword` varchar(255) NULL, UNIQUE INDEX `IDX_065d4d8f3b5adb4a08841eae3c` (`name`), UNIQUE INDEX `IDX_e12875dfb3b1d92d7d7c5377e2` (`email`), UNIQUE INDEX `IDX_059e69c318702e93998f26d152` (`displayName`), PRIMARY KEY (`id`)) ENGINE=InnoDB", undefined);
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query("DROP INDEX `IDX_059e69c318702e93998f26d152` ON `user`", undefined);
await queryRunner.query("DROP INDEX `IDX_e12875dfb3b1d92d7d7c5377e2` ON `user`", undefined);
await queryRunner.query("DROP INDEX `IDX_065d4d8f3b5adb4a08841eae3c` ON `user`", undefined);
await queryRunner.query("DROP TABLE `user`", undefined);
}
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(
'CREATE TABLE `user` (`id` varchar(36) NOT NULL, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `displayName` varchar(255) NOT NULL, `hashedPassword` varchar(255) NULL, UNIQUE INDEX `IDX_065d4d8f3b5adb4a08841eae3c` (`name`), UNIQUE INDEX `IDX_e12875dfb3b1d92d7d7c5377e2` (`email`), UNIQUE INDEX `IDX_059e69c318702e93998f26d152` (`displayName`), PRIMARY KEY (`id`)) ENGINE=InnoDB',
undefined
)
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(
'DROP INDEX `IDX_059e69c318702e93998f26d152` ON `user`',
undefined
)
await queryRunner.query(
'DROP INDEX `IDX_e12875dfb3b1d92d7d7c5377e2` ON `user`',
undefined
)
await queryRunner.query(
'DROP INDEX `IDX_065d4d8f3b5adb4a08841eae3c` ON `user`',
undefined
)
await queryRunner.query('DROP TABLE `user`', undefined)
}
}
16 changes: 16 additions & 0 deletions bff/src/db/migrations/1580309144281-CreateResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'

export class CreateResource1580309144281 implements MigrationInterface {
name = 'CreateResource1580309144281'

public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(
'CREATE TABLE `resource` (`id` varchar(36) NOT NULL, `name` varchar(255) NOT NULL, `description` text NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB',
undefined
)
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query('DROP TABLE `resource`', undefined)
}
}
20 changes: 20 additions & 0 deletions bff/src/entities/Resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Field, ID, ObjectType } from 'type-graphql'

@ObjectType()
@Entity()
class Resource {
@Field(type => ID)
@PrimaryGeneratedColumn('uuid')
id!: string

@Field()
@Column()
name!: string

@Field({ nullable: true })
@Column({ type: 'text', nullable: true })
description?: string
}
export default Resource
2 changes: 2 additions & 0 deletions bff/src/lib/aaa/AuthenticationError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { AppError } from 'lib/error'
export class AuthenticationError extends AppError {}
10 changes: 9 additions & 1 deletion bff/src/lib/aaa/authChecker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { generateToken, verifyToken } from 'lib/jwt'
import { AuthChecker } from 'type-graphql'
import { AuthenticationError } from './AuthenticationError'
import Config from 'config'
import { Context } from './Context'
import User from 'entities/User'
Expand All @@ -18,6 +19,11 @@ const authChecker: AuthChecker<Context, RoleDefinition> = async (
const config = await Config.getConfig()
const repository = getRepository(User)
let token = context.request.cookies.token
if (!token) {
const e = new AuthenticationError('The token is null.')
console.log(e)
throw e
}
config.isDevelopment && console.log(`roles: `, roles, `, token: `, token)
if (config.isDevelopment && !token) {
const user = await repository.findOneOrFail()
Expand All @@ -29,7 +35,9 @@ const authChecker: AuthChecker<Context, RoleDefinition> = async (
}
const [verified, payload] = await verifyToken(token)
if (!verified) {
// TODO:
const e = new AuthenticationError('The token cannot be verified.')
console.log(e)
return false
}
const user = await repository.findOneOrFail({
where: { id: payload.id }
Expand Down
12 changes: 12 additions & 0 deletions bff/src/lib/error/AppError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class AppError extends Error {
constructor(message?: string) {
super(message)
Object.setPrototypeOf(this, new.target.prototype)
Object.defineProperty(this, 'name', {
get: () => this.constructor.name
})
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}
1 change: 1 addition & 0 deletions bff/src/lib/error/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AppError } from './AppError'
8 changes: 7 additions & 1 deletion bff/src/lib/graphql/middleware/setupGraphQLMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IncomingMessage, ServerResponse } from 'http'
import {
ResourceResolver,
UserResolver,
UserSessionResolver,
WorldResolver
Expand All @@ -12,7 +13,12 @@ import graphqlHTTP from 'express-graphql'
export const setupGraphQLMiddleware = async (): Promise<graphqlHTTP.Middleware> => {
const config = await Config.getConfig()
const schema = await buildSchema({
resolvers: [UserResolver, UserSessionResolver, WorldResolver],
resolvers: [
ResourceResolver,
UserResolver,
UserSessionResolver,
WorldResolver
],
authChecker: authChecker
})

Expand Down
16 changes: 2 additions & 14 deletions bff/src/lib/graphql/resolvers/CannotSignInError.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,2 @@
export class CannotSignInError extends Error {
constructor(message: string) {
super()
Object.defineProperty(this, 'name', {
get: () => this.constructor.name
})
Object.defineProperty(this, 'message', {
get: () => {
return `${message}`
}
})
Error.captureStackTrace(this, this.constructor)
}
}
import { AppError } from 'lib/error'
export class CannotSignInError extends AppError {}
2 changes: 2 additions & 0 deletions bff/src/lib/graphql/resolvers/ResourceNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { AppError } from 'lib/error'
export class ResourceNotFoundError extends AppError {}
75 changes: 75 additions & 0 deletions bff/src/lib/graphql/resolvers/ResourceResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Arg, Authorized, Mutation, Query, Resolver } from 'type-graphql'
import Resource from 'entities/Resource'
import { ResourceNotFoundError } from './ResourceNotFoundError'
import { getRepository } from 'typeorm'

@Resolver()
class ResourceResolver {
repository = getRepository(Resource)

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Mutation(returns => Resource)
async createResource(
@Arg('name') name: string,
@Arg('description', { nullable: true }) description?: string
): Promise<Resource> {
const resource = new Resource()
resource.name = name
resource.description = description
await this.repository.save(resource)
return resource
}

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query(returns => Resource)
async resource(@Arg('id') id: string): Promise<Resource> {
const resource = await this.repository
.findOneOrFail({ where: { id } })
.catch(reason => {
const e = new ResourceNotFoundError(reason)
console.log(e)
throw e
})
return resource
}

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query(returns => [Resource])
async resources(): Promise<Resource[]> {
const resources = await this.repository.find()
return resources
}

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Mutation(returns => Resource)
async updateResource(
@Arg('id') id: string,
@Arg('name', { nullable: true }) name?: string,
@Arg('description', { nullable: true }) description?: string,
@Arg('deleteDescription', { defaultValue: false })
deleteDescription?: boolean
): Promise<Resource> {
const resource = await this.repository.findOneOrFail({ where: { id } })
resource.name = name || resource.name
resource.description = description || resource.description
if (deleteDescription) {
resource.description = undefined
}
await this.repository.save(resource)
return resource
}

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Mutation(returns => Boolean)
async deleteResource(@Arg('id') id: string): Promise<boolean> {
const resource = await this.repository.findOneOrFail({ where: { id } })
await this.repository.remove(resource)
return true
}
}
export default ResourceResolver
2 changes: 2 additions & 0 deletions bff/src/lib/graphql/resolvers/UserSessionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class UserSessionResolver {
repository = getRepository(User)

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query(returns => User)
async authenticated(@Ctx() context: Context): Promise<User> {
const id = context.user.id
Expand All @@ -30,6 +31,7 @@ class UserSessionResolver {
}

@UseMiddleware(SignInResultMiddleware)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Mutation(returns => User, { nullable: true })
async signIn(
@Arg('email') email: string,
Expand Down
4 changes: 3 additions & 1 deletion bff/src/lib/graphql/resolvers/WorldResolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Arg, Query, Resolver } from 'type-graphql'
import { Arg, Authorized, Query, Resolver } from 'type-graphql'
import World from 'entities/World'
import { getRepository } from 'typeorm'

Expand All @@ -7,6 +7,7 @@ import { getRepository } from 'typeorm'
class WorldResolver {
repository = getRepository(World)

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query(returns => World)
async world(
Expand All @@ -21,6 +22,7 @@ class WorldResolver {
return w as World
}

@Authorized()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query(returns => [World])
async worlds(): Promise<World[]> {
Expand Down
1 change: 1 addition & 0 deletions bff/src/lib/graphql/resolvers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as ResourceResolver } from './ResourceResolver'
export { default as UserResolver } from './UserResolver'
export { default as UserSessionResolver } from './UserSessionResolver'
export { default as WorldResolver } from './WorldResolver'
11 changes: 10 additions & 1 deletion bff/src/lib/jwt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ export const verifyToken = async (
return [false, { id: null }]
}
const config = await Config.getConfig()
const decoded = jwt.verify(token, config.publicKey)
let decoded = null
try {
decoded = jwt.verify(token, config.publicKey)
} catch (e) {
console.log(e)
}
if (!decoded) {
console.log(`The token could not be verified.`)
return [false, { id: null }]
}
const payload = decoded as TokenPayload
console.log(`The token verified: `, payload)
return [!!payload, payload]
Expand Down
5 changes: 5 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9716333

Please sign in to comment.