diff --git a/.gitignore b/.gitignore index 5a704b2..6c4888e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ dist-ssr *.local .env __generated__/ +vite.config.ts.timestamp-*.mjs # Editor directories and files diff --git a/app/server/graphql/resolvers/auth/login.ts b/app/server/graphql/resolvers/auth/login.ts index b9408cc..8aa8b42 100644 --- a/app/server/graphql/resolvers/auth/login.ts +++ b/app/server/graphql/resolvers/auth/login.ts @@ -1,9 +1,14 @@ +import { IdentityContext } from '@app/server/identity'; import { prisma } from '@app/server/prisma/client'; import { MutationLoginArgs, Session } from '@generated/types'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; -export async function LoginResolver(_: any, { input }: MutationLoginArgs): Promise { +export async function LoginResolver( + _: any, + { input }: MutationLoginArgs, + { identity }: IdentityContext +): Promise { const error = 'Invalid username or password'; const user = await prisma.user.findUnique({ diff --git a/app/server/graphql/resolvers/auth/register.ts b/app/server/graphql/resolvers/auth/register.ts index 10e975d..51ee647 100644 --- a/app/server/graphql/resolvers/auth/register.ts +++ b/app/server/graphql/resolvers/auth/register.ts @@ -1,8 +1,13 @@ +import { IdentityContext } from '@app/server/identity'; import { prisma, uuid } from '@app/server/prisma/client'; import { RegisterInput } from '@generated/types'; import bcrypt from 'bcrypt'; -export async function SignUpResolver(_: any, input: RegisterInput): Promise { +export async function SignUpResolver( + _: any, + input: RegisterInput, + { identity }: IdentityContext +): Promise { const existingUsername = await prisma.user.findUnique({ where: { username: input.username }, select: { id: true } diff --git a/app/server/graphql/resolvers/bookOneResolver.ts b/app/server/graphql/resolvers/bookOneResolver.ts index 3bb7055..fe9011a 100644 --- a/app/server/graphql/resolvers/bookOneResolver.ts +++ b/app/server/graphql/resolvers/bookOneResolver.ts @@ -1,7 +1,12 @@ +import { IdentityContext } from '@app/server/identity'; import { Book, QueryBookOneArgs } from '@generated/types'; import { books } from './booksManyResolver'; -export async function BookOneResolver(_: any, { id }: QueryBookOneArgs): Promise { +export async function BookOneResolver( + _: any, + { id }: QueryBookOneArgs, + { identity }: IdentityContext +): Promise { const book = books.filter(item => item.id === id)[0]; return book; diff --git a/app/server/graphql/resolvers/booksManyResolver.ts b/app/server/graphql/resolvers/booksManyResolver.ts index 1b6a878..316c5f7 100644 --- a/app/server/graphql/resolvers/booksManyResolver.ts +++ b/app/server/graphql/resolvers/booksManyResolver.ts @@ -1,3 +1,4 @@ +import { IdentityContext } from '@app/server/identity'; import { Book } from '@generated/types'; export const books = [ @@ -13,6 +14,10 @@ export const books = [ } ]; -export async function BooksManyResolver(_: any): Promise { +export async function BooksManyResolver( + _: any, + __: any, + { identity }: IdentityContext +): Promise { return books; } diff --git a/app/server/identity.ts b/app/server/identity.ts new file mode 100644 index 0000000..f5b1811 --- /dev/null +++ b/app/server/identity.ts @@ -0,0 +1,39 @@ +import 'dotenv/config'; +import { Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; + +import { prisma } from './prisma/client'; + +export interface IdentityContext { + identity?: { + id: string; + username: string; + }; +} + +export async function getIdentityContext({ + req, + res +}: { + req: Request; + res: Response; +}): Promise { + const authHeader = req.headers.authorization; + if (authHeader == null) return {}; + const token = authHeader.split(' ')[1]; + + const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET; + if (accessTokenSecret == null) throw new Error('No `accessToken`.'); + + const decoded = jwt.verify(token, accessTokenSecret) as { userId: string }; + const identity = await prisma.user.findUnique({ + where: { id: decoded.userId }, + select: { + id: true, + email: true, + username: true + } + }); + if (identity == null) throw new Error("Couldn't find the user."); + return { identity }; +} diff --git a/app/server/main.ts b/app/server/main.ts index c9b62ea..9320b87 100644 --- a/app/server/main.ts +++ b/app/server/main.ts @@ -3,15 +3,14 @@ import { expressMiddleware } from '@apollo/server/express4'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import cors from 'cors'; import 'dotenv/config'; -import express, { Request, Response } from 'express'; +import express from 'express'; import fs from 'fs'; import http from 'http'; -import jwt from 'jsonwebtoken'; import path from 'path'; import ViteExpress from 'vite-express'; +import { getIdentityContext, IdentityContext } from './identity'; import { Mutation, Query } from './graphql/graphql'; -import { prisma } from './prisma/client'; const __dirname = import.meta.dirname; const HOST = process.env.VITE_HOST || 'localhost'; @@ -29,10 +28,6 @@ const resolvers = { // Subscription, }; -interface IdentityContext { - authScope?: String; -} - const apollo = new ApolloServer({ typeDefs, resolvers, @@ -46,7 +41,7 @@ app.use( cors(), express.json(), expressMiddleware(apollo, { - context: getUser + context: getIdentityContext }) ); @@ -55,16 +50,3 @@ await new Promise(resolve => server.listen({ hostname: HOST, port: PORT }, process.stdout.write(MOTD); ViteExpress.bind(app, server); - -async function getUser({ req, res }: { req: Request; res: Response }) { - const authHeader = req.headers.authorization; - if (!authHeader) return { identity: null }; - const token = authHeader.split(' ')[1]; - - const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET; - if (accessTokenSecret == null) throw Error('No `accessToken`'); - - const decoded = jwt.verify(token, accessTokenSecret) as { userId: string }; - const identity = await prisma.user.findUnique({ where: { id: decoded.userId } }); - return { identity }; -}