parent
d4a6faad5a
commit
b3128ede9a
@ -1,62 +0,0 @@ |
||||
import "reflect-metadata" |
||||
import { Arg, Authorized, Ctx, Field, Mutation, ObjectType, Query, Resolver } from "type-graphql" |
||||
import { comparePassword, MyContext, signToken } from "../auth" |
||||
import { User } from "../User" |
||||
|
||||
@ObjectType() |
||||
class LoginTokens { |
||||
@Field() |
||||
accessToken: string = "" |
||||
} |
||||
|
||||
@Resolver(() => User) |
||||
export class UserResolver { |
||||
@Query(() => [User]) |
||||
async users() { |
||||
return await User.find() |
||||
} |
||||
|
||||
@Query(() => LoginTokens) |
||||
async loginTokens( |
||||
@Arg("email") email: string, |
||||
@Arg("password") password: string |
||||
): Promise<LoginTokens> { |
||||
const user = await User.findOne({ where: { email } }) |
||||
|
||||
if (!user) { |
||||
throw new Error("could not find user") |
||||
} |
||||
|
||||
const passwordValid = await comparePassword(user.password, password) |
||||
|
||||
if (!passwordValid) { |
||||
throw new Error("password not valid") |
||||
} |
||||
|
||||
const accessToken = signToken({ userId: user.id }) |
||||
|
||||
return { |
||||
accessToken, |
||||
} |
||||
} |
||||
|
||||
@Query(() => User) |
||||
@Authorized() |
||||
async me(@Ctx() { payload }: MyContext) { |
||||
const id = payload!.userId |
||||
const user = await User.findOne({ where: { id } }) |
||||
|
||||
return user |
||||
} |
||||
|
||||
@Mutation(() => User) |
||||
async createUser( |
||||
@Arg("email") email: string, |
||||
@Arg("password") password: string |
||||
): Promise<User> { |
||||
return await User.create({ |
||||
email, |
||||
password, |
||||
}).save() |
||||
} |
||||
} |
@ -0,0 +1,54 @@ |
||||
import "reflect-metadata" |
||||
import { Arg, Authorized, Ctx, Mutation, Query } from "type-graphql" |
||||
import { comparePassword, MyContext, signToken } from "./userResolver/auth" |
||||
import { Tokens } from "./userResolver/Tokens" |
||||
import { User } from "./userResolver/User" |
||||
|
||||
export class UserResolver { |
||||
@Query(() => [User]) |
||||
async users() { |
||||
return await User.find() |
||||
} |
||||
|
||||
@Query(() => Tokens) |
||||
async tokens( |
||||
@Arg("email") email: string, |
||||
@Arg("password") password: string |
||||
): Promise<Tokens> { |
||||
try { |
||||
const user = await User.findOne({ where: { email } }) |
||||
|
||||
if (!(await comparePassword(user!.password, password))) { |
||||
throw new Error() |
||||
} |
||||
|
||||
const accessToken = signToken({ userId: user!.id }) |
||||
|
||||
return { |
||||
accessToken, |
||||
} |
||||
} catch (error) { |
||||
throw new Error("login credentials are invalid") |
||||
} |
||||
} |
||||
|
||||
@Query(() => User) |
||||
@Authorized() |
||||
async me(@Ctx() { payload }: MyContext) { |
||||
const id = payload!.userId |
||||
const user = await User.findOne({ where: { id } }) |
||||
|
||||
return user |
||||
} |
||||
|
||||
@Mutation(() => User) |
||||
async createUser( |
||||
@Arg("email") email: string, |
||||
@Arg("password") password: string |
||||
): Promise<User> { |
||||
return await User.create({ |
||||
email, |
||||
password, |
||||
}).save() |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
require("dotenv").config() |
||||
import { DocumentNode, graphql, GraphQLSchema } from "graphql" |
||||
import { buildSchema } from "type-graphql" |
||||
import { ConnectionOptions } from "typeorm" |
||||
import { UserResolver } from "./UserResolver" |
||||
import { customAuthChecker } from "./userResolver/auth" |
||||
import { User } from "./userResolver/User" |
||||
|
||||
let schema: GraphQLSchema |
||||
|
||||
export const callSchema = async (document: DocumentNode, context?: any) => { |
||||
if (!schema) { |
||||
schema = await createSchema() |
||||
} |
||||
|
||||
return graphql({ |
||||
schema, |
||||
source: document.loc!.source.body, |
||||
contextValue: context, |
||||
}) |
||||
} |
||||
|
||||
export const createSchema = () => |
||||
buildSchema({ |
||||
resolvers: [UserResolver], |
||||
authChecker: customAuthChecker, |
||||
}) |
||||
|
||||
export const connectionOptions: ConnectionOptions = { |
||||
type: "postgres", |
||||
host: process.env.DB_HOST, |
||||
port: 5432, |
||||
database: process.env.DB_NAME, |
||||
username: process.env.DB_USER, |
||||
password: process.env.DB_PASS, |
||||
entities: [User], |
||||
synchronize: true, |
||||
logging: false, |
||||
} |
@ -0,0 +1,8 @@ |
||||
import "reflect-metadata" |
||||
import { Field, ObjectType } from "type-graphql" |
||||
|
||||
@ObjectType() |
||||
export class Tokens { |
||||
@Field() |
||||
accessToken: string = "" |
||||
} |
@ -0,0 +1,56 @@ |
||||
import { argon2id, hash, verify as argonVerify } from "argon2" |
||||
import { Request, Response } from "express" |
||||
import { readFileSync } from "fs" |
||||
import { sign, verify as jwtVerify } from "jsonwebtoken" |
||||
import { join } from "path" |
||||
import { AuthChecker } from "type-graphql" |
||||
|
||||
let PRIVATE_KEY: Buffer |
||||
let PUBLIC_KEY: Buffer |
||||
|
||||
export type Payload = { |
||||
userId: number |
||||
} |
||||
|
||||
export interface MyContext { |
||||
req: Request |
||||
res: Response |
||||
payload?: Payload |
||||
} |
||||
|
||||
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 signToken = (payload: Payload) => { |
||||
if (!PRIVATE_KEY) { |
||||
PRIVATE_KEY = readKeyFile("jwtRS256.key") |
||||
} |
||||
|
||||
return sign(payload, PRIVATE_KEY, { algorithm: "RS256" }) |
||||
} |
||||
|
||||
export const verifyToken = (token: string) => { |
||||
if (!PUBLIC_KEY) { |
||||
PUBLIC_KEY = readKeyFile("jwtRS256.key.pub") |
||||
} |
||||
|
||||
return jwtVerify(token, PUBLIC_KEY) |
||||
} |
||||
|
||||
export const customAuthChecker: AuthChecker<MyContext> = ({ context }) => { |
||||
try { |
||||
const authHeader = context.req.headers["authorization"] |
||||
const token = authHeader!.split(" ")[1] |
||||
const payload = verifyToken(token) |
||||
context.payload = payload as any |
||||
|
||||
return true |
||||
} catch (error) { |
||||
throw new Error("the valid authorization header is required") |
||||
} |
||||
} |
||||
|
||||
const readKeyFile = (fileName: string) => readFileSync(join(__dirname, "auth", fileName)) |
@ -1,59 +0,0 @@ |
||||
import { argon2id, hash, verify as argonVerify } from "argon2" |
||||
import { Request, Response } from "express" |
||||
import { readFileSync } from "fs" |
||||
import { sign, verify as jwtVerify } from "jsonwebtoken" |
||||
import { join } from "path" |
||||
import { AuthChecker } from "type-graphql" |
||||
|
||||
export type Payload = { |
||||
userId: number |
||||
} |
||||
|
||||
export interface MyContext { |
||||
req: Request |
||||
res: Response |
||||
payload?: Payload |
||||
} |
||||
|
||||
export async function hashPassword(password: string) { |
||||
return await hash(password, { type: argon2id }) |
||||
} |
||||
|
||||
export async function comparePassword(hash: string, plain: string) { |
||||
return await argonVerify(hash, plain, { type: argon2id }) |
||||
} |
||||
|
||||
export function signToken(payload: Payload) { |
||||
const PRIVATE_KEY = readFileSync(join(__dirname, "auth", "jwtRS256.key")) |
||||
|
||||
return sign(payload, PRIVATE_KEY, { algorithm: "RS256" }) |
||||
} |
||||
|
||||
export function verifyToken(token: string) { |
||||
const PUBLIC_KEY = readFileSync(join(__dirname, "auth", "jwtRS256.key.pub")) |
||||
|
||||
return jwtVerify(token, PUBLIC_KEY) |
||||
} |
||||
|
||||
export const customAuthChecker: AuthChecker<MyContext> = ({ context }) => { |
||||
const authHeader = context.req.headers["authorization"] |
||||
|
||||
if (!authHeader) { |
||||
throw new Error("authorization header is missing") |
||||
} |
||||
|
||||
const token = authHeader.split(" ")[1] |
||||
|
||||
if (!token) { |
||||
throw new Error("token not present in authorization header") |
||||
} |
||||
|
||||
const payload = verifyToken(token) |
||||
|
||||
if (!payload) { |
||||
throw new Error("payload not present in the token") |
||||
} |
||||
|
||||
context.payload = payload as any |
||||
return true |
||||
} |
@ -1,24 +0,0 @@ |
||||
import { DocumentNode, graphql, GraphQLSchema } from "graphql" |
||||
import { buildSchema } from "type-graphql" |
||||
import { customAuthChecker } from "./auth" |
||||
import { UserResolver } from "./User/UserResolver" |
||||
|
||||
let schema: GraphQLSchema |
||||
|
||||
export const callSchema = async (document: DocumentNode, context?: any) => { |
||||
if (!schema) { |
||||
schema = await createSchema() |
||||
} |
||||
|
||||
return graphql({ |
||||
schema, |
||||
source: document.loc!.source.body || "", |
||||
contextValue: context, |
||||
}) |
||||
} |
||||
|
||||
export const createSchema = () => |
||||
buildSchema({ |
||||
resolvers: [UserResolver], |
||||
authChecker: customAuthChecker, |
||||
}) |
Loading…
Reference in new issue