pass the createUser and loginToken test

master
Peter Babič 5 years ago
parent 477787785e
commit 586efd5581
Signed by: peter.babic
GPG Key ID: 4BB075BC1884BA40
  1. 4
      .gitignore
  2. 2738
      package-lock.json
  3. 30
      package.json
  4. 26
      src/app.ts
  5. 15
      src/modules/User.ts
  6. 99
      src/modules/User/UserResolver.spec.ts
  7. 49
      src/modules/User/UserResolver.ts
  8. 10
      src/utils/argon2.ts
  9. 9
      src/utils/callSchema.ts
  10. 13
      src/utils/createSchema.ts
  11. 12
      src/utils/jwt.ts
  12. 7
      tsconfig.json

4
.gitignore vendored

@ -1,4 +1,5 @@
*.key
*.pub
# Created by https://www.gitignore.io/api/node
# Edit at https://www.gitignore.io/?templates=node
@ -65,7 +66,6 @@ typings/
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache

2738
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -5,29 +5,35 @@
"main": "src/app.js",
"scripts": {
"dev": "ts-node-dev --respawn src/app.ts",
"test": "jest"
"test": "jest --watch",
"gen:key": "ssh-keygen -t rsa -b 2048 -f src/utils/keys/jwtRS256.key && openssl rsa -in src/utils/keys/jwtRS256.key -pubout -outform PEM -out src/utils/keys/jwtRS256.key.pub"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"apollo-server": "^2.6.2",
"graphql": "^14.3.1",
"apollo-server": "^2.9.3",
"argon2": "^0.24.1",
"dotenv": "^8.1.0",
"graphql": "^14.5.4",
"jsonwebtoken": "^8.5.1",
"pg": "^7.12.1",
"reflect-metadata": "^0.1.13",
"type-graphql": "^0.17.4",
"type-graphql": "^0.17.5",
"typeorm": "^0.2.18"
},
"devDependencies": {
"@types/faker": "^4.1.5",
"@types/graphql": "^14.2.0",
"@types/jest": "^24.0.13",
"@types/node": "^12.0.12",
"@types/graphql": "^14.5.0",
"@types/jest": "^24.0.18",
"@types/js-cookie": "^2.2.2",
"@types/jsonwebtoken": "^8.3.3",
"@types/node": "^12.7.5",
"class-transformer": "^0.2.3",
"faker": "^4.1.0",
"jest": "^24.8.0",
"sqlite3": "^4.0.9",
"ts-jest": "^24.0.2",
"ts-node-dev": "^1.0.0-pre.39",
"typescript": "^3.5.1"
"jest": "^24.9.0",
"ts-jest": "^24.1.0",
"ts-node-dev": "^1.0.0-pre.42",
"typescript": "^3.6.3"
}
}

@ -1,15 +1,29 @@
import { ApolloServer } from "apollo-server";
import { buildSchema } from "type-graphql";
import { UserResolver } from "./modules/User/UserResolver";
require("dotenv").config()
import { ApolloServer } from "apollo-server"
import { createConnection } from "typeorm"
import { User } from "./modules/User"
import { createSchema } from "./utils/createSchema"
const PORT = process.env.PORT || 4000
async function bootstrap() {
// ... Building schema here
const schema = await buildSchema({
resolvers: [UserResolver],
await createConnection({
type: "postgres",
host: "localhost",
port: 5432,
database: "postgres",
username: "postgres",
password: "postgres",
// dropSchema: true,
entities: [User],
synchronize: true,
logging: false,
})
// ... Building schema here
const schema = await createSchema()
// Create the GraphQL server
const server = new ApolloServer({
schema,

@ -1,6 +1,7 @@
import "reflect-metadata";
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import "reflect-metadata"
import { Field, ObjectType } from "type-graphql"
import { BaseEntity, BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm"
import * as argon2 from "../utils/argon2"
@ObjectType()
@Entity()
@ -11,4 +12,12 @@ export class User extends BaseEntity {
@Field()
@Column()
email: string = ""
@Column()
password: string = ""
@BeforeInsert()
async hashPassword() {
this.password = await argon2.hashIncludingOptions(this.password)
}
}

@ -1,53 +1,66 @@
import faker from "faker";
import { createConnection, getConnection } from "typeorm";
import { callSchema } from "../../utils/callSchema";
import { User } from "../User";
const usersQuery = `
query {
users {
email
}
}`
import faker = require("faker")
import { createConnection, getConnection } from "typeorm"
import { callSchema } from "../../utils/callSchema"
import * as jwt from "../../utils/jwt"
import { User } from "../User"
beforeEach(() => {
return createConnection({
type: "sqlite",
database: ":memory:",
dropSchema: true,
beforeAll(async () => {
return await createConnection({
type: "postgres",
host: "localhost",
port: 5432,
database: "testing",
username: "postgres",
password: "postgres",
// dropSchema: true,
entities: [User],
synchronize: true,
logging: false
});
});
afterEach(() => {
let conn = getConnection();
return conn.close();
});
describe("resolver of", () => {
describe("users query", () => {
it("should return an empty array when no users are created", async () => {
const response = await callSchema({
source: usersQuery,
logging: false,
})
})
afterAll(async () => {
return await getConnection().close()
})
afterEach(async () => {
return await getConnection().synchronize(true)
})
describe("resolver of user", () => {
it("returns email as it creates user with mutation", async () => {
const fakeEmail = faker.internet.email()
const fakePassword = faker.internet.password(6)
const createUserMutation = `mutation {
createUser(email: "${fakeEmail}", password: "${fakePassword}") {
email
}
}`
const response = await callSchema(createUserMutation)
expect(response).toMatchObject({
data: {
users: [],
createUser: { email: fakeEmail },
},
})
})
it("should return a populated array when an user is created", async () => {
it("should return emails of registered users", async () => {
const usersQuery = `{
users {
email
}
}`
const user = await User.create({
email: faker.internet.email(),
}).save()
const response = await callSchema({
source: usersQuery,
})
const response = await callSchema(usersQuery)
expect(response).toMatchObject({
data: {
@ -55,5 +68,23 @@ describe("resolver of", () => {
},
})
})
it("should return a valid login token", async () => {
const fakeEmail = faker.internet.email()
const fakePassword = faker.internet.password(6)
await User.create({
email: fakeEmail,
password: fakePassword,
}).save()
const loginTokenQuery = `{
loginToken(email: "${fakeEmail}", password: "${fakePassword}")
}`
const response = await callSchema(loginTokenQuery)
const token = response.data!.loginToken;
expect(jwt.verify(token, jwt.PUBLIC_KEY)).toBeTruthy()
})
})

@ -1,14 +1,45 @@
import "reflect-metadata";
import { Query, Resolver } from "type-graphql";
import { getRepository } from "typeorm";
import { User } from "../User";
import "reflect-metadata"
import { Arg, Mutation, Query, Resolver } from "type-graphql"
import * as argon2 from "../../utils/argon2"
import * as jwt from "../../utils/jwt"
import { User } from "../User"
@Resolver(_of => User)
@Resolver(() => User)
export class UserResolver {
@Query(_returns => [User])
@Query(() => [User])
async users() {
const userRepository = getRepository(User)
return userRepository.find()
return await User.find()
}
@Query(() => String, { nullable: true })
async loginToken(
@Arg("email") email: string,
@Arg("password") password: string
): Promise<string | null> {
const user = await User.findOne({ where: { email } })
if (!user) {
return null
}
const passwordValid = await argon2.verify(user.password, password)
if (!passwordValid) {
return null
}
const token = jwt.signWithRS256({ userId: user.id })
return token
}
@Mutation(() => User)
async createUser(
@Arg("email") email: string,
@Arg("password") password: string
): Promise<User> {
return await User.create({
email,
password,
}).save()
}
}

@ -0,0 +1,10 @@
import * as agron2 from "argon2"
export * from "argon2"
/**
* Override the defaul agron2i option with agron2id
* @param password Pasword to has using argon2id
*/
export async function hashIncludingOptions(password: string) {
return await agron2.hash(password, { type: agron2.argon2id })
}

@ -1,13 +1,10 @@
import { graphql, GraphQLSchema } from "graphql"
import { createSchema } from "./createSchema"
import { graphql, GraphQLSchema } from "graphql";
import { createSchema } from "./createSchema";
interface Options {
source: string
}
let schema: GraphQLSchema
export const callSchema = async ({ source }: Options) => {
export const callSchema = async ( source : string) => {
if (!schema) {
schema = await createSchema()
}

@ -1,7 +1,16 @@
import { buildSchema } from "type-graphql";
import { UserResolver } from "../modules/User/UserResolver";
import { buildSchema, MiddlewareFn } from "type-graphql"
import { UserResolver } from "../modules/User/UserResolver"
const ErrorInterceptor: MiddlewareFn<any> = async ({}, next) => {
try {
return await next()
} catch (err) {
console.error(err)
}
}
export const createSchema = () =>
buildSchema({
resolvers: [UserResolver],
globalMiddlewares: [ErrorInterceptor],
})

@ -0,0 +1,12 @@
import * as jwt from "jsonwebtoken"
export * from "jsonwebtoken"
import fs = require('fs')
import path = require('path')
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, 'keys', 'jwtRS256.key'))
export const PUBLIC_KEY = fs.readFileSync(path.join(__dirname, 'keys', 'jwtRS256.key.pub'))
export function signWithRS256(payload: string | object) {
return jwt.sign(payload, PRIVATE_KEY, {algorithm: "RS256"})
}

@ -3,6 +3,8 @@
"target": "es2017",
// "lib": ["es2017"],
"module": "commonjs",
// "allowSyntheticDefaultImports": true,
// "esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
@ -12,9 +14,6 @@
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// needed for faker
"esModuleInterop": true
"noUnusedParameters": true
}
}

Loading…
Cancel
Save