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. 14194
      package-lock.json
  3. 72
      package.json
  4. 26
      src/app.ts
  5. 17
      src/modules/User.ts
  6. 131
      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 # Created by https://www.gitignore.io/api/node
# Edit at https://www.gitignore.io/?templates=node # Edit at https://www.gitignore.io/?templates=node
@ -65,7 +66,6 @@ typings/
# dotenv environment variables file # dotenv environment variables file
.env .env
.env.test
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache

14194
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,33 +1,39 @@
{ {
"name": "pcbizr", "name": "pcbizr",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {
"dev": "ts-node-dev --respawn src/app.ts", "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": "", "keywords": [],
"license": "ISC", "author": "",
"dependencies": { "license": "ISC",
"apollo-server": "^2.6.2", "dependencies": {
"graphql": "^14.3.1", "apollo-server": "^2.9.3",
"reflect-metadata": "^0.1.13", "argon2": "^0.24.1",
"type-graphql": "^0.17.4", "dotenv": "^8.1.0",
"typeorm": "^0.2.18" "graphql": "^14.5.4",
}, "jsonwebtoken": "^8.5.1",
"devDependencies": { "pg": "^7.12.1",
"@types/faker": "^4.1.5", "reflect-metadata": "^0.1.13",
"@types/graphql": "^14.2.0", "type-graphql": "^0.17.5",
"@types/jest": "^24.0.13", "typeorm": "^0.2.18"
"@types/node": "^12.0.12", },
"class-transformer": "^0.2.3", "devDependencies": {
"faker": "^4.1.0", "@types/faker": "^4.1.5",
"jest": "^24.8.0", "@types/graphql": "^14.5.0",
"sqlite3": "^4.0.9", "@types/jest": "^24.0.18",
"ts-jest": "^24.0.2", "@types/js-cookie": "^2.2.2",
"ts-node-dev": "^1.0.0-pre.39", "@types/jsonwebtoken": "^8.3.3",
"typescript": "^3.5.1" "@types/node": "^12.7.5",
} "class-transformer": "^0.2.3",
} "faker": "^4.1.0",
"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"; require("dotenv").config()
import { buildSchema } from "type-graphql"; import { ApolloServer } from "apollo-server"
import { UserResolver } from "./modules/User/UserResolver"; import { createConnection } from "typeorm"
import { User } from "./modules/User"
import { createSchema } from "./utils/createSchema"
const PORT = process.env.PORT || 4000 const PORT = process.env.PORT || 4000
async function bootstrap() { async function bootstrap() {
// ... Building schema here
const schema = await buildSchema({ await createConnection({
resolvers: [UserResolver], 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 // Create the GraphQL server
const server = new ApolloServer({ const server = new ApolloServer({
schema, schema,

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

@ -1,59 +1,90 @@
import faker from "faker";
import { createConnection, getConnection } from "typeorm"; import faker = require("faker")
import { callSchema } from "../../utils/callSchema"; import { createConnection, getConnection } from "typeorm"
import { User } from "../User"; import { callSchema } from "../../utils/callSchema"
import * as jwt from "../../utils/jwt"
const usersQuery = ` import { User } from "../User"
query {
users { beforeAll(async () => {
email return await createConnection({
} type: "postgres",
}` host: "localhost",
port: 5432,
beforeEach(() => { database: "testing",
return createConnection({ username: "postgres",
type: "sqlite", password: "postgres",
database: ":memory:", // dropSchema: true,
dropSchema: true,
entities: [User], entities: [User],
synchronize: true, synchronize: true,
logging: false logging: false,
}); })
}); })
afterEach(() => { afterAll(async () => {
let conn = getConnection(); return await getConnection().close()
return conn.close(); })
});
afterEach(async () => {
describe("resolver of", () => { return await getConnection().synchronize(true)
describe("users query", () => { })
it("should return an empty array when no users are created", async () => {
const response = await callSchema({ describe("resolver of user", () => {
source: usersQuery, it("returns email as it creates user with mutation", async () => {
})
const fakeEmail = faker.internet.email()
expect(response).toMatchObject({ const fakePassword = faker.internet.password(6)
data: { const createUserMutation = `mutation {
users: [], createUser(email: "${fakeEmail}", password: "${fakePassword}") {
}, email
}) }
}`
const response = await callSchema(createUserMutation)
expect(response).toMatchObject({
data: {
createUser: { email: fakeEmail },
},
}) })
})
it("should return emails of registered users", async () => {
const usersQuery = `{
users {
email
}
}`
it("should return a populated array when an user is created", async () => { const user = await User.create({
const user = await User.create({ email: faker.internet.email(),
email: faker.internet.email(), }).save()
}).save()
const response = await callSchema({ const response = await callSchema(usersQuery)
source: usersQuery,
})
expect(response).toMatchObject({ expect(response).toMatchObject({
data: { data: {
users: [{ email: user.email }], users: [{ email: user.email }],
}, },
})
}) })
}) })
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 "reflect-metadata"
import { Query, Resolver } from "type-graphql"; import { Arg, Mutation, Query, Resolver } from "type-graphql"
import { getRepository } from "typeorm"; import * as argon2 from "../../utils/argon2"
import { User } from "../User"; import * as jwt from "../../utils/jwt"
import { User } from "../User"
@Resolver(_of => User) @Resolver(() => User)
export class UserResolver { export class UserResolver {
@Query(() => [User])
@Query(_returns => [User])
async users() { async users() {
const userRepository = getRepository(User) return await User.find()
return userRepository.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 { graphql, GraphQLSchema } from "graphql";
import { createSchema } from "./createSchema" import { createSchema } from "./createSchema";
interface Options {
source: string
}
let schema: GraphQLSchema let schema: GraphQLSchema
export const callSchema = async ({ source }: Options) => { export const callSchema = async ( source : string) => {
if (!schema) { if (!schema) {
schema = await createSchema() schema = await createSchema()
} }

@ -1,7 +1,16 @@
import { buildSchema } from "type-graphql"; import { buildSchema, MiddlewareFn } from "type-graphql"
import { UserResolver } from "../modules/User/UserResolver"; import { UserResolver } from "../modules/User/UserResolver"
const ErrorInterceptor: MiddlewareFn<any> = async ({}, next) => {
try {
return await next()
} catch (err) {
console.error(err)
}
}
export const createSchema = () => export const createSchema = () =>
buildSchema({ buildSchema({
resolvers: [UserResolver], 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", "target": "es2017",
// "lib": ["es2017"], // "lib": ["es2017"],
"module": "commonjs", "module": "commonjs",
// "allowSyntheticDefaultImports": true,
// "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
@ -12,9 +14,6 @@
"strict": true, "strict": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true
// needed for faker
"esModuleInterop": true
} }
} }

Loading…
Cancel
Save