From 39ddf8fde91e0b804dd255cd1b2b0d7d9cc8850a Mon Sep 17 00:00:00 2001 From: aronmal Date: Mon, 10 Apr 2023 18:51:38 +0200 Subject: [PATCH] Improved passing of req, res, and tokens --- .../components/checkPasswordIsValid.ts | 8 +-- .../backend/components/checkTokenIsValid.ts | 29 ++++----- .../lib/backend/components/createTokenDB.ts | 13 +++- .../lib/backend/components/getPlayerByIdDB.ts | 8 +-- .../backend/components/getPlayerByNameDB.ts | 8 +-- .../lib/backend/components/getTokenDB.ts | 22 ++++--- .../backend/components/getTokenFromBody.ts | 18 +++--- .../backend/components/getTokenFromCookie.ts | 16 +++-- .../lib/backend/components/getUserFromBody.ts | 12 ++-- .../lib/backend/components/sendError.ts | 12 ++-- .../lib/backend/components/sendResponse.ts | 9 +-- leaky-ships/lib/frontend/checkIsLoggedIn.ts | 30 ---------- leaky-ships/pages/api/auth.ts | 26 ++++---- leaky-ships/pages/api/data.ts | 14 +++-- leaky-ships/pages/api/login.ts | 59 +++++++++---------- leaky-ships/pages/api/logout.ts | 12 ++-- leaky-ships/pages/api/register.ts | 8 ++- 17 files changed, 134 insertions(+), 170 deletions(-) delete mode 100644 leaky-ships/lib/frontend/checkIsLoggedIn.ts diff --git a/leaky-ships/lib/backend/components/checkPasswordIsValid.ts b/leaky-ships/lib/backend/components/checkPasswordIsValid.ts index b4615f9..6746d62 100644 --- a/leaky-ships/lib/backend/components/checkPasswordIsValid.ts +++ b/leaky-ships/lib/backend/components/checkPasswordIsValid.ts @@ -1,19 +1,17 @@ import { Player } from "@prisma/client" import bcrypt from "bcrypt" import errors from "../errors" -import sendError from "./sendError" -import { NextApiRequest, NextApiResponse } from "next" +import sendError, { API } from "./sendError" export default async function checkPasswordIsValid( - req: NextApiRequest, - res: NextApiResponse, + context: API, player: Player, password: string, next: () => void ) { // Validate for correct password const result = await bcrypt.compare(password, player.passwordHash) - if (!result) return sendError(req, res, errors.wrongPassword) + if (!result) return sendError(context, errors.wrongPassword) return next() } diff --git a/leaky-ships/lib/backend/components/checkTokenIsValid.ts b/leaky-ships/lib/backend/components/checkTokenIsValid.ts index 716bb5d..ca82beb 100644 --- a/leaky-ships/lib/backend/components/checkTokenIsValid.ts +++ b/leaky-ships/lib/backend/components/checkTokenIsValid.ts @@ -1,34 +1,29 @@ -import { TokenType } from "@prisma/client" import jwt from "jsonwebtoken" import errors from "../errors" import jwtVerifyCatch from "../jwtVerifyCatch" -import { NextApiRequest, NextApiResponse } from "next" -import sendError from "./sendError" +import sendError, { API } from "./sendError" +import { IdToken, RawToken } from "./createTokenDB" async function checkTokenIsValid( - req: NextApiRequest, - res: NextApiResponse, - rawToken: [string, TokenType], - next: (tokenBody: jwt.JwtPayload) => void + context: API, + rawToken: RawToken, + next: (token: IdToken) => void ) { - const [tokenValue, tokenType] = rawToken + const { value, type } = rawToken // Verify the token and get the payload - let tokenData: string | jwt.JwtPayload + let data: string | jwt.JwtPayload try { - tokenData = jwt.verify( - tokenValue, - process.env.ACCESS_TOKEN_SECRET as string - ) + data = jwt.verify(value, process.env.ACCESS_TOKEN_SECRET as string) } catch (err: any) { // Deal with the problem in more detail - return sendError(req, res, jwtVerifyCatch(tokenType, err)) + return sendError(context, jwtVerifyCatch(type, err)) } // Making sure the token data is not a string (because it should be an object) - if (typeof tokenData === "string") - return sendError(req, res, errors.tokenWasString(tokenType, tokenValue)) + if (typeof data === "string") + return sendError(context, errors.tokenWasString(type, value)) - return next(tokenData) + return next({ id: data.id, type }) } export default checkTokenIsValid diff --git a/leaky-ships/lib/backend/components/createTokenDB.ts b/leaky-ships/lib/backend/components/createTokenDB.ts index 8161365..cc08090 100644 --- a/leaky-ships/lib/backend/components/createTokenDB.ts +++ b/leaky-ships/lib/backend/components/createTokenDB.ts @@ -2,6 +2,15 @@ import { Player, Token, TokenType } from "@prisma/client" import jwt from "jsonwebtoken" import prisma from "../../prisma" +export interface RawToken { + value: string + type: TokenType +} +export interface IdToken { + id: string + type: TokenType +} + const tokenLifetime = { REFRESH: 172800, ACCESS: 15, @@ -10,7 +19,7 @@ const tokenLifetime = { export default async function createTokenDB( player: Player, newTokenType: TokenType, - next: (token: [string, Token]) => void + next: (newToken: RawToken, newTokenDB: Token) => void ) { // Create token entry in DB const newTokenDB = await prisma.token.create({ @@ -32,5 +41,5 @@ export default async function createTokenDB( { expiresIn: tokenLifetime[newTokenType] } ) - return next([newToken, newTokenDB]) + return next({ value: newToken, type: "ACCESS" }, newTokenDB) } diff --git a/leaky-ships/lib/backend/components/getPlayerByIdDB.ts b/leaky-ships/lib/backend/components/getPlayerByIdDB.ts index 3a084b4..5667fde 100644 --- a/leaky-ships/lib/backend/components/getPlayerByIdDB.ts +++ b/leaky-ships/lib/backend/components/getPlayerByIdDB.ts @@ -1,12 +1,10 @@ import { Player, Token } from "@prisma/client" import errors from "../errors" import prisma from "../../prisma" -import { NextApiRequest, NextApiResponse } from "next" -import sendError from "./sendError" +import sendError, { API } from "./sendError" export default async function getPlayerByIdDB( - req: NextApiRequest, - res: NextApiResponse, + context: API, tokenDB: Token, next: (player: Player) => void ) { @@ -17,7 +15,7 @@ export default async function getPlayerByIdDB( }, }) if (!player) { - return sendError(req, res, errors.playerNotFound) + return sendError(context, errors.playerNotFound) } return next(player) diff --git a/leaky-ships/lib/backend/components/getPlayerByNameDB.ts b/leaky-ships/lib/backend/components/getPlayerByNameDB.ts index 63df418..009e286 100644 --- a/leaky-ships/lib/backend/components/getPlayerByNameDB.ts +++ b/leaky-ships/lib/backend/components/getPlayerByNameDB.ts @@ -1,12 +1,10 @@ import { Player } from "@prisma/client" import errors from "../errors" import prisma from "../../prisma" -import sendError from "./sendError" -import { NextApiRequest, NextApiResponse } from "next" +import sendError, { API } from "./sendError" export default async function getPlayerByNameDB( - req: NextApiRequest, - res: NextApiResponse, + context: API, username: string, next: (player: Player) => void ) { @@ -23,7 +21,7 @@ export default async function getPlayerByNameDB( }, }), ]).catch(() => null) - if (player === null) return sendError(req, res, errors.playerNotFound) + if (player === null) return sendError(context, errors.playerNotFound) return next(player) } diff --git a/leaky-ships/lib/backend/components/getTokenDB.ts b/leaky-ships/lib/backend/components/getTokenDB.ts index d256e00..cebc79a 100644 --- a/leaky-ships/lib/backend/components/getTokenDB.ts +++ b/leaky-ships/lib/backend/components/getTokenDB.ts @@ -1,30 +1,28 @@ import prisma from "../../prisma" -import jwt from "jsonwebtoken" -import { Token, TokenType } from "@prisma/client" +import { Token } from "@prisma/client" import errors from "../errors" -import sendError from "./sendError" -import { NextApiRequest, NextApiResponse } from "next" +import sendError, { API } from "./sendError" +import { IdToken } from "./createTokenDB" async function getTokenDB( - req: NextApiRequest, - res: NextApiResponse, - tokenBody: jwt.JwtPayload, - tokenType: TokenType, + context: API, + token: IdToken, next: (tokenDB: Token) => void ) { + const { id, type } = token // Find refresh token in DB const tokenDB = await prisma.token.findUnique({ where: { - id: tokenBody.id, + id, }, }) - if (!tokenDB) return sendError(req, res, errors.tokenNotFound(tokenType)) + if (!tokenDB) return sendError(context, errors.tokenNotFound(type)) - if (tokenDB.used) return sendError(req, res, errors.tokenUsed) + if (tokenDB.used) return sendError(context, errors.tokenUsed) await prisma.token.update({ where: { - id: tokenBody.id, + id, }, data: { used: true, diff --git a/leaky-ships/lib/backend/components/getTokenFromBody.ts b/leaky-ships/lib/backend/components/getTokenFromBody.ts index 5bcef1b..4119a43 100644 --- a/leaky-ships/lib/backend/components/getTokenFromBody.ts +++ b/leaky-ships/lib/backend/components/getTokenFromBody.ts @@ -1,13 +1,11 @@ -import { TokenType } from "@prisma/client" -import { NextApiRequest, NextApiResponse } from "next" -import sendError from "./sendError" +import sendError, { API } from "./sendError" +import { RawToken } from "./createTokenDB" async function getTokenFromBody( - req: NextApiRequest, - res: NextApiResponse, - next: (refreshToken: [string, TokenType]) => void + context: API, + next: (accessToken: RawToken) => void ) { - const body = JSON.parse(req.body) + const body = JSON.parse(context.req.body) // Checking for cookie presens, because it is necessary if ( typeof body !== "object" || @@ -15,14 +13,14 @@ async function getTokenFromBody( !("token" in body) || typeof body.token !== "string" ) - return sendError(req, res, { + return sendError(context, { message: "Unauthorized. No Access-Token.", statusCode: 401, solved: true, }) - const tokenValue = body.token + const value = body.token - return next([tokenValue, "ACCESS"]) + return next({ value, type: "ACCESS" }) } export default getTokenFromBody diff --git a/leaky-ships/lib/backend/components/getTokenFromCookie.ts b/leaky-ships/lib/backend/components/getTokenFromCookie.ts index 2ead769..f7c8e99 100644 --- a/leaky-ships/lib/backend/components/getTokenFromCookie.ts +++ b/leaky-ships/lib/backend/components/getTokenFromCookie.ts @@ -1,19 +1,17 @@ -import { TokenType } from "@prisma/client" -import { NextApiRequest, NextApiResponse } from "next" import errors from "../errors" -import sendError from "./sendError" +import { RawToken } from "./createTokenDB" +import sendError, { API } from "./sendError" async function getTokenFromCookie( - req: NextApiRequest, - res: NextApiResponse, - next: (refreshToken: [string, TokenType]) => void + context: API, + next: (refreshToken: RawToken) => void ) { - const tokenValue = req.cookies.token + const value = context.req.cookies.token // Checking for cookie presens, because it is necessary - if (!tokenValue) return sendError(req, res, errors.noCookie) + if (!value) return sendError(context, errors.noCookie) - return next([tokenValue, "REFRESH"]) + return next({ value, type: "REFRESH" }) } export default getTokenFromCookie diff --git a/leaky-ships/lib/backend/components/getUserFromBody.ts b/leaky-ships/lib/backend/components/getUserFromBody.ts index d6ba445..b994081 100644 --- a/leaky-ships/lib/backend/components/getUserFromBody.ts +++ b/leaky-ships/lib/backend/components/getUserFromBody.ts @@ -1,23 +1,21 @@ -import { NextApiRequest, NextApiResponse } from "next" -import sendError from "./sendError" +import sendError, { API } from "./sendError" import errors from "../errors" async function getUserFromBody( - req: NextApiRequest, - res: NextApiResponse, + context: API, next: (username: string, password: string) => void ) { - const body = JSON.parse(req.body) + const body = JSON.parse(context.req.body) if ( typeof body !== "object" || !body || !("username" in body) || typeof body.username !== "string" ) - return sendError(req, res, errors.noUsername) + return sendError(context, errors.noUsername) const { username } = body if (!("password" in body) || typeof body.password !== "string") - return sendError(req, res, errors.noPassword) + return sendError(context, errors.noPassword) const { password } = body return next(username, password) diff --git a/leaky-ships/lib/backend/components/sendError.ts b/leaky-ships/lib/backend/components/sendError.ts index f9dad72..5a08f73 100644 --- a/leaky-ships/lib/backend/components/sendError.ts +++ b/leaky-ships/lib/backend/components/sendError.ts @@ -1,11 +1,13 @@ import { NextApiRequest, NextApiResponse } from "next" import logging from "../logging" -export default function sendError( - req: NextApiRequest, - res: NextApiResponse, - err: any -) { +export interface API { + req: NextApiRequest + res: NextApiResponse +} + +export default function sendError(context: API, err: any) { + const { res, req } = context // If something went wrong, let the client know with status 500 res.status(err.statusCode ?? 500).end() logging(err.message, [err.type ?? (err.solved ? "debug" : "error")], req) diff --git a/leaky-ships/lib/backend/components/sendResponse.ts b/leaky-ships/lib/backend/components/sendResponse.ts index 81a6f7f..f6ad848 100644 --- a/leaky-ships/lib/backend/components/sendResponse.ts +++ b/leaky-ships/lib/backend/components/sendResponse.ts @@ -1,5 +1,5 @@ -import { NextApiRequest, NextApiResponse } from "next" import logging, { Logging } from "../logging" +import type { API } from "./sendError" export interface Result { message: string @@ -8,11 +8,8 @@ export interface Result { type?: Logging[] } -export default function sendResponse( - req: NextApiRequest, - res: NextApiResponse, - result: Result -) { +export default function sendResponse(context: API, result: Result) { + const { req, res } = context res.status(result.statusCode ?? 200) result.body ? res.json(result.body) : res.end() logging(result.message, result.type ?? ["debug"], req) diff --git a/leaky-ships/lib/frontend/checkIsLoggedIn.ts b/leaky-ships/lib/frontend/checkIsLoggedIn.ts deleted file mode 100644 index d81e5ea..0000000 --- a/leaky-ships/lib/frontend/checkIsLoggedIn.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { GetServerSidePropsContext, PreviewData } from "next" -import { ParsedUrlQuery } from "querystring" -import getTokenFromCookie from "../backend/components/getTokenFromCookie" -import checkTokenIsValid from "../backend/components/checkTokenIsValid" -import getTokenDB from "../backend/components/getTokenDB" -import getPlayerByIdDB from "../backend/components/getPlayerByIdDB" -import logging from "../backend/logging" - -export default async function checkIsLoggedIn( - context: GetServerSidePropsContext -) { - const req: any = context.req - const res: any = context.res - - const isLoggedIn = await getTokenFromCookie(req, res, (token) => - checkTokenIsValid(req, res, token, (tokenBody) => - getTokenDB(req, res, tokenBody, token[1], (tokenDB) => - getPlayerByIdDB(req, res, tokenDB, (player) => !!player) - ) - ) - ).catch(() => false) - - logging( - "loginCheck " + (isLoggedIn ? true : "-> loggedIn: " + false), - ["debug", "infoCyan"], - req - ) - - return isLoggedIn -} diff --git a/leaky-ships/pages/api/auth.ts b/leaky-ships/pages/api/auth.ts index 33f780d..280e8f1 100644 --- a/leaky-ships/pages/api/auth.ts +++ b/leaky-ships/pages/api/auth.ts @@ -6,7 +6,9 @@ import getTokenFromCookie from "../../lib/backend/components/getTokenFromCookie" import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid" import getTokenDB from "../../lib/backend/components/getTokenDB" import getPlayerByIdDB from "../../lib/backend/components/getPlayerByIdDB" -import createTokenDB from "../../lib/backend/components/createTokenDB" +import createTokenDB, { + RawToken, +} from "../../lib/backend/components/createTokenDB" import sendResponse from "../../lib/backend/components/sendResponse" interface Data { @@ -17,26 +19,26 @@ export default async function auth( req: NextApiRequest, res: NextApiResponse ) { - getTokenFromCookie(req, res, (token) => { - checkTokenIsValid(req, res, token, (tokenBody) => { - getTokenDB(req, res, tokenBody, token[1], (tokenDB) => { - getPlayerByIdDB(req, res, tokenDB, (player) => { - createTokenDB(player, "ACCESS", (newToken) => { - sendResponse(req, res, authResponse(newToken, tokenDB)) + const context = { req, res } + + getTokenFromCookie(context, (refreshToken) => { + checkTokenIsValid(context, refreshToken, (token) => { + getTokenDB(context, token, (tokenDB) => { + getPlayerByIdDB(context, tokenDB, (player) => { + createTokenDB(player, "ACCESS", (newToken, newTokenDB) => { + sendResponse(context, authResponse(newToken, newTokenDB, tokenDB)) }) }) }) }) - }).catch((err) => sendError(req, res, err)) + }).catch((err) => sendError(context, err)) } -function authResponse(newToken: [string, Token], tokenDB: Token) { - const [newTokenValue, newTokenDB] = newToken - +function authResponse(newToken: RawToken, newTokenDB: Token, tokenDB: Token) { // Successfull response return { message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`, - body: { token: newTokenValue }, + body: { token: newToken.value }, type: ["debug", "info.cyan"] as Logging[], } } diff --git a/leaky-ships/pages/api/data.ts b/leaky-ships/pages/api/data.ts index 8393ad4..9bdd5f0 100644 --- a/leaky-ships/pages/api/data.ts +++ b/leaky-ships/pages/api/data.ts @@ -16,15 +16,17 @@ export default async function data( req: NextApiRequest, res: NextApiResponse ) { - return getTokenFromBody(req, res, (token) => { - checkTokenIsValid(req, res, token, (tokenBody) => { - getTokenDB(req, res, tokenBody, token[1], (tokenDB) => { - getPlayerByIdDB(req, res, tokenDB, (player) => { - sendResponse(req, res, dataResponse(player, tokenDB)) + const context = { req, res } + + return getTokenFromBody(context, (accessToken) => { + checkTokenIsValid(context, accessToken, (token) => { + getTokenDB(context, token, (tokenDB) => { + getPlayerByIdDB(context, tokenDB, (player) => { + sendResponse(context, dataResponse(player, tokenDB)) }) }) }) - }).catch((err) => sendError(req, res, err)) + }).catch((err) => sendError(context, err)) } function dataResponse(player: Player, tokenDB: Token) { diff --git a/leaky-ships/pages/api/login.ts b/leaky-ships/pages/api/login.ts index fcc7834..37f5340 100644 --- a/leaky-ships/pages/api/login.ts +++ b/leaky-ships/pages/api/login.ts @@ -2,14 +2,17 @@ import { NextApiRequest, NextApiResponse } from "next" import logging, { Logging } from "../../lib/backend/logging" import getPlayerByNameDB from "../../lib/backend/components/getPlayerByNameDB" import checkPasswordIsValid from "../../lib/backend/components/checkPasswordIsValid" -import createTokenDB from "../../lib/backend/components/createTokenDB" +import createTokenDB, { + IdToken, + RawToken, +} from "../../lib/backend/components/createTokenDB" import sendResponse from "../../lib/backend/components/sendResponse" import { setCookie } from "cookies-next" import { Player, Token } from "@prisma/client" import prisma from "../../lib/prisma" import jwt from "jsonwebtoken" import errors from "../../lib/backend/errors" -import sendError from "../../lib/backend/components/sendError" +import sendError, { API } from "../../lib/backend/components/sendError" import getUserFromBody from "../../lib/backend/components/getUserFromBody" interface Data { @@ -18,30 +21,32 @@ interface Data { export default async function login( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse ) { - return preCheck(req, res, () => - getUserFromBody(req, res, (username, password) => - getPlayerByNameDB(req, res, username, (player) => { - checkPasswordIsValid(req, res, player, password, () => { - createTokenDB(player, "REFRESH", (newToken) => { - sendResponse(req, res, loginResponse(player, newToken, req, res)) + const context = { req, res } + + return preCheck(context, () => + getUserFromBody(context, (username, password) => + getPlayerByNameDB(context, username, (player) => { + checkPasswordIsValid(context, player, password, () => { + createTokenDB(player, "REFRESH", (newToken, newTokenDB) => { + sendResponse( + context, + loginResponse(context, player, newToken, newTokenDB) + ) }) }) }) ) - ).catch((err) => sendError(req, res, err)) + ).catch((err) => sendError(context, err)) } -async function preCheck( - req: NextApiRequest, - res: NextApiResponse, - next: () => void -) { +async function preCheck(context: API, next: () => void) { + const { req } = context const oldRefreshToken = req.cookies.token try { - if (!oldRefreshToken) return sendError(req, res, errors.noCookie) + if (!oldRefreshToken) return sendError(context, errors.noCookie) // Check for old cookie, if unused invalidate it const tokenData = jwt.verify( oldRefreshToken, @@ -76,17 +81,18 @@ async function preCheck( return next() } -function loginResponse( +function loginResponse( + context: API, player: Player, - newToken: [string, Token], - req: NextApiRequest, - res: NextApiResponse + newToken: RawToken, + newTokenDB: Token ) { + const { req, res } = context // const { player, req, res } = payload - const [newTokenValue, newTokenDB] = newToken + const { value } = newToken // Set login cookie - setCookie("token", newTokenValue, { + setCookie("token", value, { req, res, maxAge: 172800, @@ -96,15 +102,6 @@ function loginResponse( path: "/api", }) - // deleteCookie("token", { - // req, - // res, - // httpOnly: true, - // sameSite: true, - // secure: true, - // path: "/api", - // }) - // Successfull response return { message: diff --git a/leaky-ships/pages/api/logout.ts b/leaky-ships/pages/api/logout.ts index 1abc3a8..de1a157 100644 --- a/leaky-ships/pages/api/logout.ts +++ b/leaky-ships/pages/api/logout.ts @@ -16,13 +16,15 @@ export default async function logout( req: NextApiRequest, res: NextApiResponse ) { - return getTokenFromCookie(req, res, (token) => { - checkTokenIsValid(req, res, token, (tokenBody) => { - getTokenDB(req, res, tokenBody, token[1], (tokenDB) => { - sendResponse(req, res, logoutResponse(tokenDB, req, res)) + const context = { req, res } + + return getTokenFromCookie(context, (refreshToken) => { + checkTokenIsValid(context, refreshToken, (token) => { + getTokenDB(context, token, (tokenDB) => { + sendResponse(context, logoutResponse(tokenDB, req, res)) }) }) - }).catch((err) => sendError(req, res, err)) + }).catch((err) => sendError(context, err)) } function logoutResponse( diff --git a/leaky-ships/pages/api/register.ts b/leaky-ships/pages/api/register.ts index 70301e9..ca30e9a 100644 --- a/leaky-ships/pages/api/register.ts +++ b/leaky-ships/pages/api/register.ts @@ -14,10 +14,12 @@ export default async function register( req: NextApiRequest, res: NextApiResponse ) { - return getUserFromBody(req, res, (username, password) => + const context = { req, res } + + return getUserFromBody(context, (username, password) => createPlayerDB(username, password, (player) => { - sendResponse(req, res, registerResponse(player)) - }).catch((err) => sendError(req, res, err)) + sendResponse(context, registerResponse(player)) + }).catch((err) => sendError(context, err)) ) }