diff --git a/src/server/UserResolver.spec.ts b/src/server/UserResolver.spec.ts index 1da7d53..332640f 100644 --- a/src/server/UserResolver.spec.ts +++ b/src/server/UserResolver.spec.ts @@ -60,6 +60,7 @@ describe("resolver of user", () => { expect(jwtLifetime).toBeGreaterThanOrEqual(oneMinute) expect(jwtLifetime).not.toBeGreaterThan(sixteenMinutes) expect(jwtPayload.userId).toBe(user.id) + expect(jwtPayload.ms).toBeLessThan(1000) expect(response.errors).toBeUndefined() }) ) diff --git a/src/server/userResolver/auth.ts b/src/server/userResolver/auth.ts index 8efc016..b76517c 100644 --- a/src/server/userResolver/auth.ts +++ b/src/server/userResolver/auth.ts @@ -4,84 +4,86 @@ import { sign as jwtSign, verify as jwtVerify } from "jsonwebtoken" import { AuthChecker } from "type-graphql" export const hashPassword = async (password: string) => - await argonHash(password, { type: argon2id }) + await argonHash(password, { type: argon2id }) export const comparePasswords = async (hash: string, plain: string) => { - if (!(await argonVerify(hash, plain, { type: argon2id }))) { - throw new Error("Passwords do not match") - } + if (!(await argonVerify(hash, plain, { type: argon2id }))) { + throw new Error("Passwords do not match") + } - return true + return true } export const signAccessToken = (payload: ContextPayload) => { - const accessTokenSecret = process.env.ACCESS_SECRET as string + const accessTokenSecret = process.env.ACCESS_SECRET as string + const payloadWithMs = { ...payload, ms: Date.now() % 1000 } - return jwtSign(payload, accessTokenSecret, { - expiresIn: parseInt(process.env.ACCESS_EXPIRY as string), - }) + return jwtSign(payloadWithMs, accessTokenSecret, { + expiresIn: parseInt(process.env.ACCESS_EXPIRY as string), + }) } export const signRefreshToken = (payload: ContextPayload) => { - const accessTokenSecret = process.env.REFRESH_SECRET as string + const accessTokenSecret = process.env.REFRESH_SECRET as string - return jwtSign(payload, accessTokenSecret, { - expiresIn: parseInt(process.env.REFRESH_EXPIRY as string), - }) + return jwtSign(payload, accessTokenSecret, { + expiresIn: parseInt(process.env.REFRESH_EXPIRY as string), + }) } export const verifiedAccessTokenPayload = (token: string) => { - const accessTokenSecret = process.env.ACCESS_SECRET as string + const accessTokenSecret = process.env.ACCESS_SECRET as string - return jwtVerify(token, accessTokenSecret) as JWTPayload + return jwtVerify(token, accessTokenSecret) as JWTPayload } export const verifiedRefreshTokenPayload = (token: string) => { - const refreshTokenSecret = process.env.REFRESH_SECRET as string + const refreshTokenSecret = process.env.REFRESH_SECRET as string - return jwtVerify(token, refreshTokenSecret) as JWTPayload + return jwtVerify(token, refreshTokenSecret) as JWTPayload } export const accessTokenWithRefreshCookie = (userId: number, res: Response) => { - const accessToken = signAccessToken({ userId }) + const accessToken = signAccessToken({ userId }) - const refreshExpiryMs = parseInt(process.env.REFRESH_EXPIRY as string) * 1000 - res.cookie("rt", signRefreshToken({ userId }), { - httpOnly: true, - path: "/refresh_token", - expires: new Date(new Date().getTime() + refreshExpiryMs), - }) + const refreshExpiryMs = parseInt(process.env.REFRESH_EXPIRY as string) * 1000 + res.cookie("rt", signRefreshToken({ userId }), { + httpOnly: true, + path: "/refresh_token", + expires: new Date(new Date().getTime() + refreshExpiryMs), + }) - return accessToken + return accessToken } export const customAuthChecker: AuthChecker = ({ context }) => { - try { - const authHeader = context.req.headers["authorization"] - const accessToken = authHeader!.split(" ")[1] - const accessTokenPayload = verifiedAccessTokenPayload(accessToken) - context.payload = accessTokenPayload as ContextPayload - - return true - } catch (error) { - throw new Error("the valid authorization header is required: " + error) - } + try { + const authHeader = context.req.headers["authorization"] + const accessToken = authHeader!.split(" ")[1] + const accessTokenPayload = verifiedAccessTokenPayload(accessToken) + context.payload = accessTokenPayload as ContextPayload + + return true + } catch (error) { + throw new Error("the valid authorization header is required: " + error) + } } export const contextFunction = ({ req, res }: Context) => ({ req, res }) export type Context = { - req: Request - res: Response - payload?: ContextPayload + req: Request + res: Response + payload?: ContextPayload } type ContextPayload = { - userId: number + userId: number } type JWTPayload = { - userId: number - iat: number - exp?: number + userId: number + iat: number + exp?: number + ms?: number }