Complete backend rework

This commit is contained in:
aronmal 2023-04-08 16:56:03 +02:00
parent 53d4ab527a
commit eb09017dab
Signed by: aronmal
GPG key ID: 816B7707426FC612
25 changed files with 958 additions and 866 deletions

View file

@ -1,3 +1,5 @@
declare module globalThis { import "@total-typescript/ts-reset"
declare global {
var prismaClient: PrismaClient var prismaClient: PrismaClient
} }

View file

@ -1,23 +1,19 @@
import { Player } from "@prisma/client" import { Player } from "@prisma/client"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import errors from "../errors"
import sendError from "./sendError"
import { NextApiRequest, NextApiResponse } from "next"
export default async function checkPasswordIsValid<T>( export default async function checkPasswordIsValid(
payload: T & { player: Player; password: string } req: NextApiRequest,
res: NextApiResponse,
player: Player,
password: string,
next: () => void
) { ) {
const { player, password } = payload
// Validate for correct password // Validate for correct password
return bcrypt.compare(password, player.passwordHash).then(async (result) => { const result = await bcrypt.compare(password, player.passwordHash)
if (!result) { if (!result) return sendError(req, res, errors.wrongPassword)
return Promise.reject({
message: "Passwords do not match!", next()
statusCode: 401,
solved: true,
})
}
return {
...payload,
passwordIsValid: true,
}
})
} }

View file

@ -1,30 +1,34 @@
import { Token } from "@prisma/client" import { TokenType } from "@prisma/client"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import errors from "../errors"
import jwtVerifyCatch from "../jwtVerifyCatch" import jwtVerifyCatch from "../jwtVerifyCatch"
import { NextApiRequest, NextApiResponse } from "next"
import sendError from "./sendError"
async function checkTokenIsValid<T>( async function checkTokenIsValid(
payload: T & { token: string; tokenType: Token["type"] } req: NextApiRequest,
res: NextApiResponse,
rawToken: [string, TokenType],
next: (tokenBody: jwt.JwtPayload) => void
) { ) {
const { token, tokenType } = payload const [tokenValue, tokenType] = rawToken
// Verify the token and get the payload // Verify the token and get the payload
let tokenData: string | jwt.JwtPayload let tokenData: string | jwt.JwtPayload
try { try {
tokenData = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string) tokenData = jwt.verify(
tokenValue,
process.env.ACCESS_TOKEN_SECRET as string
)
} catch (err: any) { } catch (err: any) {
// Deal with the problem in more detail // Deal with the problem in more detail
return Promise.reject(jwtVerifyCatch(tokenType, err)) return sendError(req, res, jwtVerifyCatch(tokenType, err))
} }
// Making sure the token data is not a string (because it should be an object) // Making sure the token data is not a string (because it should be an object)
if (typeof tokenData === "string") { if (typeof tokenData === "string")
return Promise.reject({ return sendError(req, res, errors.tokenWasString(tokenType, tokenValue))
message: tokenType + "-Token data was a string. Token: " + token,
statusCode: 401,
solved: false,
})
}
return { ...payload, tokenBody: tokenData, tokenIsValid: true } return next(tokenData)
} }
export default checkTokenIsValid export default checkTokenIsValid

View file

@ -1,38 +1,19 @@
import { Player } from "@prisma/client"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import prisma from "../../prisma" import prisma from "../../prisma"
async function createPlayerDB<T>( async function createPlayerDB(
payload: T & { username: string; password: string } username: string,
password: string,
next: (player: Player) => void
) { ) {
const { username, password } = payload return await prisma.player.create({
data: {
return await prisma.player username,
.create({ passwordHash: await bcrypt.hash(password, 10),
data: { anonymous: false,
username, },
passwordHash: await bcrypt.hash(password, 10), })
anonymous: false,
},
})
.then((player) => {
return { ...payload, player }
})
.catch((err: any) => {
if (err.code === 11000) {
return Promise.reject({
message: `Duplicate key error while creating Player in DB!`,
statusCode: 409,
solved: true,
type: "warn",
})
} else {
console.log(err)
return Promise.reject({
message: `Unknown error while creating Player in DB.`,
solved: false,
})
}
})
} }
export default createPlayerDB export default createPlayerDB

View file

@ -1,4 +1,4 @@
import { Player, Token } from "@prisma/client" import { Player, Token, TokenType } from "@prisma/client"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import prisma from "../../prisma" import prisma from "../../prisma"
@ -7,11 +7,11 @@ const tokenLifetime = {
ACCESS: 15, ACCESS: 15,
} }
export default async function createTokenDB<T>( export default async function createTokenDB(
payload: T & { player: Player; newTokenType: Token["type"] } player: Player,
newTokenType: TokenType,
next: (token: [string, Token]) => void
) { ) {
const { player, newTokenType } = payload
// Create token entry in DB // Create token entry in DB
const newTokenDB = await prisma.token.create({ const newTokenDB = await prisma.token.create({
data: { data: {
@ -28,13 +28,9 @@ export default async function createTokenDB<T>(
// Sign a new access token // Sign a new access token
const newToken = jwt.sign( const newToken = jwt.sign(
{ id: newTokenDB.id }, { id: newTokenDB.id },
process.env.ACCESS_TOKEN_SECRET as string, process.env.TOKEN_SECRET as string,
{ expiresIn: tokenLifetime[newTokenType] } { expiresIn: tokenLifetime[newTokenType] }
) )
return { return next([newToken, newTokenDB])
...payload,
newToken,
newTokenDB,
}
} }

View file

@ -1,10 +1,15 @@
import { Token } from "@prisma/client" import { Player, Token } from "@prisma/client"
import errors from "../errors"
import prisma from "../../prisma" import prisma from "../../prisma"
import { NextApiRequest, NextApiResponse } from "next"
import sendError from "./sendError"
export default async function getPlayerByIdDB<T>( export default async function getPlayerByIdDB(
payload: T & { tokenDB: Token } req: NextApiRequest,
res: NextApiResponse,
tokenDB: Token,
next: (player: Player) => void
) { ) {
const { tokenDB } = payload
// Find Host in DB if it still exists (just to make sure) // Find Host in DB if it still exists (just to make sure)
const player = await prisma.player.findUnique({ const player = await prisma.player.findUnique({
where: { where: {
@ -12,15 +17,8 @@ export default async function getPlayerByIdDB<T>(
}, },
}) })
if (!player) { if (!player) {
return Promise.reject({ return sendError(req, res, errors.playerNotFound)
message: "Player ID not found in DB!",
statusCode: 401,
solved: false,
})
} }
return { next(player)
...payload,
player,
}
} }

View file

@ -1,9 +1,15 @@
import { Player } from "@prisma/client"
import errors from "../errors"
import prisma from "../../prisma" import prisma from "../../prisma"
import sendError from "./sendError"
import { NextApiRequest, NextApiResponse } from "next"
export default async function getPlayerByNameDB<T>( export default async function getPlayerByNameDB(
payload: T & { username: string } req: NextApiRequest,
res: NextApiResponse,
username: string,
next: (player: Player) => void
) { ) {
const { username } = payload
// Find Player in DB if it still exists (just to make sure) // Find Player in DB if it still exists (just to make sure)
const player = await Promise.any([ const player = await Promise.any([
prisma.player.findUniqueOrThrow({ prisma.player.findUniqueOrThrow({
@ -16,17 +22,8 @@ export default async function getPlayerByNameDB<T>(
email: username, email: username,
}, },
}), }),
]) ]).catch(() => null)
if (!player) { if (player === null) return sendError(req, res, errors.playerNotFound)
return Promise.reject({
message: "Player name not found in DB!",
statusCode: 401,
solved: false,
})
}
return { next(player)
...payload,
player,
}
} }

View file

@ -1,39 +1,26 @@
import { NextApiRequest, NextApiResponse } from "next"
import prisma from "../../prisma" import prisma from "../../prisma"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import { Token, TokenType } from "@prisma/client"
import errors from "../errors"
import sendError from "./sendError"
import { NextApiRequest, NextApiResponse } from "next"
async function getTokenDB<T>( async function getTokenDB(
payload: T & { req: NextApiRequest,
tokenBody: jwt.JwtPayload res: NextApiResponse,
tokenIsValid: boolean tokenBody: jwt.JwtPayload,
req: NextApiRequest tokenType: TokenType,
res: NextApiResponse<any> next: (tokenDB: Token) => void
}
) { ) {
const { tokenBody } = payload
// Find refresh token in DB // Find refresh token in DB
const tokenDB = await prisma.token.findUnique({ const tokenDB = await prisma.token.findUnique({
where: { where: {
id: tokenBody.id, id: tokenBody.id,
}, },
}) })
if (!tokenDB) { if (!tokenDB) return sendError(req, res, errors.tokenNotFound(tokenType))
return Promise.reject({
message: "Access-Token not found in DB!",
statusCode: 401,
solved: true,
type: "warn",
})
}
if (tokenDB.used) { if (tokenDB.used) return sendError(req, res, errors.tokenUsed)
return Promise.reject({
message: "DBToken was already used!",
statusCode: 401,
solved: true,
})
}
await prisma.token.update({ await prisma.token.update({
where: { where: {
@ -46,10 +33,7 @@ async function getTokenDB<T>(
// await logging('Old token has been invalidated.', ['debug'], req) // await logging('Old token has been invalidated.', ['debug'], req)
return { return next(tokenDB)
...payload,
tokenDB,
}
} }
export default getTokenDB export default getTokenDB

View file

@ -1,19 +1,28 @@
import { NextApiRequest } from "next" import { TokenType } from "@prisma/client"
import { NextApiRequest, NextApiResponse } from "next"
async function getTokenFromBody<T>(payload: T & { req: NextApiRequest }) { import sendError from "./sendError"
const { req } = payload
const token: string = JSON.parse(req.body).token
async function getTokenFromBody(
req: NextApiRequest,
res: NextApiResponse,
next: (refreshToken: [string, TokenType]) => void
) {
const body = JSON.parse(req.body)
// Checking for cookie presens, because it is necessary // Checking for cookie presens, because it is necessary
if (!token) { if (
return Promise.reject({ typeof body !== "object" ||
!body ||
!("token" in body) ||
typeof body.token !== "string"
)
return sendError(req, res, {
message: "Unauthorized. No Access-Token.", message: "Unauthorized. No Access-Token.",
statusCode: 401, statusCode: 401,
solved: true, solved: true,
}) })
} const tokenValue = body.token
return { ...payload, token } return next([tokenValue, "ACCESS"])
} }
export default getTokenFromBody export default getTokenFromBody

View file

@ -1,20 +1,19 @@
import { Token } from "@prisma/client" import { TokenType } from "@prisma/client"
import { NextApiRequest } from "next" import { NextApiRequest, NextApiResponse } from "next"
import errors from "../errors"
import sendError from "./sendError"
async function getTokenFromCookie<T>(payload: T & { req: NextApiRequest }) { async function getTokenFromCookie(
const { req } = payload req: NextApiRequest,
const token = req.cookies.token res: NextApiResponse,
next: (refreshToken: [string, TokenType]) => void
) {
const tokenValue = req.cookies.token
// Checking for cookie presens, because it is necessary // Checking for cookie presens, because it is necessary
if (!token) { if (!tokenValue) return sendError(req, res, errors.noCookie)
return Promise.reject({
message: "Unauthorized. No cookie.",
statusCode: 401,
solved: true,
})
}
return { ...payload, token, tokenType: "REFRESH" as Token["type"] } return next([tokenValue, "REFRESH"])
} }
export default getTokenFromCookie export default getTokenFromCookie

View file

@ -0,0 +1,26 @@
import { NextApiRequest, NextApiResponse } from "next"
import sendError from "./sendError"
import errors from "../errors"
async function getUserFromBody(
req: NextApiRequest,
res: NextApiResponse,
next: (username: string, password: string) => void
) {
const body = JSON.parse(req.body)
if (
typeof body !== "object" ||
!body ||
!("username" in body) ||
typeof body.username !== "string"
)
return sendError(req, res, errors.noUsername)
const { username } = body
if (!("password" in body) || typeof body.password !== "string")
return sendError(req, res, errors.noPassword)
const { password } = body
return next(username, password)
}
export default getUserFromBody

View file

@ -1,9 +1,9 @@
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
import logging from "../logging" import logging from "../logging"
export default function sendError<T>( export default function sendError(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<T>, res: NextApiResponse,
err: any err: any
) { ) {
// If something went wrong, let the client know with status 500 // If something went wrong, let the client know with status 500

View file

@ -8,12 +8,11 @@ export interface Result<T> {
type?: Logging[] type?: Logging[]
} }
export default function sendResponse<T>(payload: { export default function sendResponse<T>(
req: NextApiRequest req: NextApiRequest,
res: NextApiResponse<T> res: NextApiResponse<T>,
result: Result<T> result: Result<T>
}) { ) {
const { req, res, result } = payload
res.status(result.statusCode ?? 200) res.status(result.statusCode ?? 200)
result.body ? res.json(result.body) : res.end() result.body ? res.json(result.body) : res.end()
logging(result.message, result.type ?? ["debug"], req) logging(result.message, result.type ?? ["debug"], req)

View file

@ -0,0 +1,80 @@
import { TokenType } from "@prisma/client"
export interface rejectionError {
rejected?: boolean
message: string
statusCode: number
solved: boolean
}
export interface rejectionErrors {
[key: string]: rejectionError
}
const rejectionErrors = {
noCookie: {
rejected: true,
message: "Unauthorized. No cookie.",
statusCode: 401,
solved: true,
},
noBody: {
rejected: true,
message: "Unauthorized. No Body.",
statusCode: 401,
solved: true,
},
noUsername: {
rejected: true,
message: "No username in request body!",
statusCode: 401,
solved: true,
},
noPassword: {
rejected: true,
message: "No password in request body!",
statusCode: 401,
solved: true,
},
wrongPassword: {
message: "Passwords do not match!",
statusCode: 401,
solved: true,
},
playerNotFound: {
message: "Player name not found in DB!",
statusCode: 401,
solved: false,
},
tokenWasString(tokenType: TokenType, tokenValue: string) {
return {
rejected: true,
message: `${tokenType}-Token data was a string. Token: ${tokenValue}`,
statusCode: 401,
solved: false,
}
},
tokenNotFound(tokenType: TokenType) {
return {
rejected: true,
message: `${tokenType}-Token not found in DB!`,
statusCode: 401,
solved: true,
type: "warn",
}
},
tokenUsed: {
rejected: true,
message: "DBToken was already used!",
statusCode: 401,
solved: true,
},
noToken(tokenType: TokenType) {
return {
rejected: true,
message: `Unauthorized. No ${tokenType}-Token.`,
statusCode: 401,
solved: true,
}
},
}
export default rejectionErrors

View file

@ -1,9 +1,6 @@
import { Token } from "@prisma/client" import { TokenType } from "@prisma/client"
export default async function jwtVerifyCatch( export default async function jwtVerifyCatch(tokenType: TokenType, err: Error) {
tokenType: Token["type"],
err: Error
) {
switch (err.message) { switch (err.message) {
case "jwt expired": case "jwt expired":
return { return {

View file

@ -12,16 +12,17 @@ export default async function checkIsLoggedIn(
const req: any = context.req const req: any = context.req
const res: any = context.res const res: any = context.res
const isLoggedIn = await getTokenFromCookie({ req, res }) const isLoggedIn = await getTokenFromCookie(req, res, (token) =>
.then(checkTokenIsValid) checkTokenIsValid(req, res, token, (tokenBody) =>
.then(getTokenDB) getTokenDB(req, res, tokenBody, token[1], (tokenDB) =>
.then(getPlayerByIdDB) getPlayerByIdDB(req, res, tokenDB, (player) => !!player)
.then(({ player }) => !!player) )
.catch(() => false) )
).catch(() => false)
logging( logging(
"loginCheck " + (isLoggedIn ? true : "-> loggedIn: " + false), "loginCheck " + (isLoggedIn ? true : "-> loggedIn: " + false),
["debug", "info.cyan"], ["debug", "infoCyan"],
req req
) )

View file

@ -1,7 +1,18 @@
export default function getAccessToken(): Promise<string> { async function getAccessToken() {
return fetch("/api/auth", { const response = await fetch("/api/auth", {
method: "GET", method: "GET",
}) })
.then((res) => res.json()) const res = await response.json()
.then((res) => res.token)
if (
typeof res === "object" &&
res &&
"token" in res &&
typeof res.token === "string"
)
return res.token
throw new Error("Access token not found")
} }
export default getAccessToken

View file

@ -35,6 +35,7 @@
"typescript": "4.9.4" "typescript": "4.9.4"
}, },
"devDependencies": { "devDependencies": {
"@total-typescript/ts-reset": "^0.3.7",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/node": "^18.13.0", "@types/node": "^18.13.0",

View file

@ -1,13 +1,13 @@
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
import { Logging } from "../../lib/backend/logging"
import { Token } from "@prisma/client"
import sendError from "../../lib/backend/components/sendError"
import getTokenFromCookie from "../../lib/backend/components/getTokenFromCookie" import getTokenFromCookie from "../../lib/backend/components/getTokenFromCookie"
import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid" import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid"
import getTokenDB from "../../lib/backend/components/getTokenDB" import getTokenDB from "../../lib/backend/components/getTokenDB"
import getPlayerByIdDB from "../../lib/backend/components/getPlayerByIdDB" import getPlayerByIdDB from "../../lib/backend/components/getPlayerByIdDB"
import createTokenDB from "../../lib/backend/components/createTokenDB" import createTokenDB from "../../lib/backend/components/createTokenDB"
import sendResponse from "../../lib/backend/components/sendResponse" import sendResponse from "../../lib/backend/components/sendResponse"
import sendError from "../../lib/backend/components/sendError"
import { Logging } from "../../lib/backend/logging"
import { Token } from "@prisma/client"
interface Data { interface Data {
token: string token: string
@ -17,41 +17,26 @@ export default async function auth(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<Data> res: NextApiResponse<Data>
) { ) {
return getTokenFromCookie({ getTokenFromCookie(req, res, (token) => {
req, checkTokenIsValid(req, res, token, (tokenBody) => {
res, getTokenDB(req, res, tokenBody, token[1], (tokenDB) => {
newTokenType: "ACCESS" as Token["type"], getPlayerByIdDB(req, res, tokenDB, (player) => {
}) createTokenDB(player, "ACCESS", (newToken) => {
.then(checkTokenIsValid) sendResponse(req, res, authResponse(newToken, tokenDB))
.then(getTokenDB) })
.then(getPlayerByIdDB) })
.then(createTokenDB) })
.then(authResponse<Data>) })
.then(sendResponse<Data>) }).catch((err) => sendError(req, res, err))
.catch((err) => sendError(req, res, err))
} }
async function authResponse<T>(payload: { function authResponse(newToken: [string, Token], tokenDB: Token) {
newToken: string const [newTokenValue, newTokenDB] = newToken
newTokenDB: Token
tokenDB: Token
req: NextApiRequest
res: NextApiResponse<T>
}) {
const { newToken, newTokenDB, tokenDB, req, res } = payload
// Successfull response // Successfull response
return { return {
req, message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
res, body: { token: newTokenValue },
result: { type: ["debug", "info.cyan"] as Logging[],
message:
"Access-Token generated: " +
newTokenDB.id +
" with Refreshtoken-Token: " +
tokenDB.id,
body: { token: newToken },
type: ["debug", "info.cyan"] as Logging[],
},
} }
} }

View file

@ -16,37 +16,23 @@ export default async function data(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<Data> res: NextApiResponse<Data>
) { ) {
return getTokenFromBody({ req, res, tokenType: "ACCESS" as Token["type"] }) return getTokenFromBody(req, res, (token) => {
.then(checkTokenIsValid) checkTokenIsValid(req, res, token, (tokenBody) => {
.then(getTokenDB) getTokenDB(req, res, tokenBody, token[1], (tokenDB) => {
.then(getPlayerByIdDB) getPlayerByIdDB(req, res, tokenDB, (player) => {
.then(dataResponse<Data>) sendResponse(req, res, dataResponse(player, tokenDB))
.then(sendResponse<Data>) })
.catch((err) => sendError(req, res, err)) })
})
}).catch((err) => sendError(req, res, err))
} }
async function dataResponse<T>(payload: { function dataResponse<T>(player: Player, tokenDB: Token) {
player: Player
tokenDB: Token
// games: Game[],
req: NextApiRequest
res: NextApiResponse<T>
}) {
const { player, tokenDB, req, res } = payload
const games: any = {} const games: any = {}
// Successfull response // Successfull response
return { return {
req, message: `Requested data of user: ${player.id} with Access-Token: ${tokenDB.id}`,
res, body: { games },
result: { type: ["debug", "info.cyan"] as Logging[],
message:
"Requested data of user: " +
player.id +
" with Access-Token: " +
tokenDB.id,
body: { games },
type: ["debug", "info.cyan"] as Logging[],
},
} }
} }

View file

@ -4,11 +4,13 @@ import getPlayerByNameDB from "../../lib/backend/components/getPlayerByNameDB"
import checkPasswordIsValid from "../../lib/backend/components/checkPasswordIsValid" import checkPasswordIsValid from "../../lib/backend/components/checkPasswordIsValid"
import createTokenDB from "../../lib/backend/components/createTokenDB" import createTokenDB from "../../lib/backend/components/createTokenDB"
import sendResponse from "../../lib/backend/components/sendResponse" import sendResponse from "../../lib/backend/components/sendResponse"
import sendError from "../../lib/backend/components/sendError" import { setCookie } from "cookies-next"
import { deleteCookie, setCookie } from "cookies-next"
import { Player, Token } from "@prisma/client" import { Player, Token } from "@prisma/client"
import prisma from "../../lib/prisma" import prisma from "../../lib/prisma"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import errors from "../../lib/backend/errors"
import sendError from "../../lib/backend/components/sendError"
import getUserFromBody from "../../lib/backend/components/getUserFromBody"
interface Data { interface Data {
loggedIn: boolean loggedIn: boolean
@ -18,54 +20,44 @@ export default async function login(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<any>
) { ) {
const { username, password } = JSON.parse(req.body) return preCheck(req, res, () =>
const payload = { getUserFromBody(req, res, (username, password) =>
req, getPlayerByNameDB(req, res, username, (player) => {
res, checkPasswordIsValid(req, res, player, password, () => {
username, createTokenDB(player, "REFRESH", (newToken) => {
password, sendResponse(req, res, loginResponse(player, newToken, req, res))
newTokenType: "REFRESH" as Token["type"], })
} })
return preCheck(payload) })
.then(getPlayerByNameDB) )
.then(checkPasswordIsValid) ).catch((err) => sendError(req, res, err))
.then(createTokenDB)
.then(loginResponse<Data>)
.then(sendResponse<Data>)
.catch((err) => sendError(req, res, err))
} }
async function preCheck<T>( async function preCheck(
payload: T & { req: NextApiRequest,
req: NextApiRequest res: NextApiResponse,
res: NextApiResponse<T> next: () => void
}
) { ) {
const { req, res } = payload
const oldRefreshToken = req.cookies.token const oldRefreshToken = req.cookies.token
// Check for old cookie, if unused invalidate it
const tokenData =
oldRefreshToken &&
jwt.verify(oldRefreshToken, process.env.ACCESS_TOKEN_SECRET as string)
if (!tokenData || typeof tokenData === "string") try {
deleteCookie("token", { req, res }) if (!oldRefreshToken) return sendError(req, res, errors.noCookie)
else { // Check for old cookie, if unused invalidate it
const oldDBToken = const tokenData = jwt.verify(
oldRefreshToken && oldRefreshToken,
(await prisma.token.findUnique({ process.env.ACCESS_TOKEN_SECRET as string
where: { )
id: tokenData.id,
}, if (!tokenData || typeof tokenData === "string") return
}))
if (!oldDBToken) const oldDBToken = await prisma.token.findUniqueOrThrow({
logging( where: {
"Cookie could be verified but wasn't found in DB:" + tokenData, id: tokenData.id,
["debug"], },
req })
)
else if (!oldDBToken.used) if (oldDBToken.used)
logging("Old token was used: " + oldDBToken.id, ["debug"], req) logging("Old login token was used: " + oldDBToken.id, ["debug"], req)
else { else {
await prisma.token.update({ await prisma.token.update({
where: { where: {
@ -75,44 +67,52 @@ async function preCheck<T>(
used: true, used: true,
}, },
}) })
await logging("Old login token has been invalidated.", ["debug"], req)
} }
await logging("Old token has been invalidated.", ["debug"], req) } catch (err) {
// err is expected if no correct cookie, just continue, otherwise it has been invalidated above
} }
return { ...payload, noCookiePresent: true }
return next()
} }
async function loginResponse<T>(payload: { function loginResponse<T>(
player: Player player: Player,
passwordIsValid: boolean newToken: [string, Token],
newToken: string req: NextApiRequest,
newTokenDB: Token
req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { ) {
const { player, newToken, newTokenDB, req, res } = payload // const { player, req, res } = payload
const [newTokenValue, newTokenDB] = newToken
// Set login cookie // Set login cookie
setCookie("token", newToken, { setCookie("token", newTokenValue, {
req, req,
res, res,
maxAge: 172800000, maxAge: 172800,
httpOnly: true, httpOnly: true,
sameSite: true, sameSite: true,
secure: true, secure: true,
path: "/api",
}) })
// deleteCookie("token", {
// req,
// res,
// httpOnly: true,
// sameSite: true,
// secure: true,
// path: "/api",
// })
// Successfull response // Successfull response
return { return {
req, message:
res, "User " +
result: { player.id +
message: " logged in and generated Refresh-Token: " +
"User " + newTokenDB.id,
player.id + body: { loggedIn: true },
" logged in and generated Refresh-Token: " + type: ["debug", "info.cyan"] as Logging[],
newTokenDB.id,
body: { loggedIn: true },
type: ["debug", "info.cyan"] as Logging[],
},
} }
} }

View file

@ -16,32 +16,27 @@ export default async function logout(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<any>
) { ) {
return getTokenFromCookie({ req, res }) return getTokenFromCookie(req, res, (token) => {
.then(checkTokenIsValid) checkTokenIsValid(req, res, token, (tokenBody) => {
.then(getTokenDB) getTokenDB(req, res, tokenBody, token[1], (tokenDB) => {
.then(logoutResponse<Data>) sendResponse(req, res, logoutResponse(tokenDB, req, res))
.then(sendResponse<Data>) })
.catch((err) => sendError(req, res, err)) })
}).catch((err) => sendError(req, res, err))
} }
async function logoutResponse<T>(payload: { function logoutResponse<T>(
tokenDB: Token tokenDB: Token,
req: NextApiRequest req: NextApiRequest,
res: NextApiResponse<T> res: NextApiResponse<T>
}) { ) {
const { tokenDB, req, res } = payload
// Set login cookie // Set login cookie
deleteCookie("token", { req, res }) deleteCookie("token", { req, res })
// Successfull response // Successfull response
return { return {
req, message: "User of Token " + tokenDB.id + " logged out.",
res, body: { loggedOut: true },
result: { type: ["debug", "info.cyan"] as Logging[],
message: "User of Token " + tokenDB.id + " logged out.",
body: { loggedOut: true },
type: ["debug", "info.cyan"] as Logging[],
},
} }
} }

View file

@ -4,6 +4,7 @@ import sendError from "../../lib/backend/components/sendError"
import sendResponse from "../../lib/backend/components/sendResponse" import sendResponse from "../../lib/backend/components/sendResponse"
import { Logging } from "../../lib/backend/logging" import { Logging } from "../../lib/backend/logging"
import { Player } from "@prisma/client" import { Player } from "@prisma/client"
import getUserFromBody from "../../lib/backend/components/getUserFromBody"
interface Data { interface Data {
registered: boolean registered: boolean
@ -11,31 +12,21 @@ interface Data {
export default async function register( export default async function register(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<Data>
) { ) {
const { username, password } = JSON.parse(req.body) return getUserFromBody(req, res, (username, password) =>
return createPlayerDB({ req, res, username, password }) createPlayerDB(username, password, (player) => {
.then(registerResponse<Data>) sendResponse(req, res, registerResponse(player))
.then(sendResponse<Data>) }).catch((err) => sendError(req, res, err))
.catch((err) => sendError(req, res, err)) )
} }
async function registerResponse<T>(payload: { function registerResponse<T>(player: Player) {
player: Player
req: NextApiRequest
res: NextApiResponse<T>
}) {
const { player, req, res } = payload
// Successfull response // Successfull response
return { return {
req, message: "Player created : " + player.id,
res, statusCode: 201,
result: { body: { registered: true },
message: "Player created : " + player.id, type: ["debug", "info.cyan"] as Logging[],
statusCode: 201,
body: { registered: true },
type: ["debug", "info.cyan"] as Logging[],
},
} }
} }

View file

@ -1,7 +1,7 @@
import type { NextApiRequest } from "next" import type { NextApiRequest } from "next"
import { Server } from "socket.io" import { Server } from "socket.io"
import { NextApiResponseWithSocket } from "../../interfaces/NextApiSocket" import { NextApiResponseWithSocket } from "../../interfaces/NextApiSocket"
import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid" import jwt from "jsonwebtoken"
import prisma from "../../lib/prisma" import prisma from "../../lib/prisma"
export interface ServerToClientEvents { export interface ServerToClientEvents {
@ -37,6 +37,21 @@ interface SocketData {
// next() // next()
// } // }
async function checkTokenIsValid(rawToken: string) {
// Verify the token and get the payload
let tokenData: string | jwt.JwtPayload
try {
tokenData = jwt.verify(rawToken, process.env.ACCESS_TOKEN_SECRET as string)
} catch (err: any) {
// Deal with the problem in more detail
return Promise.reject()
}
// Making sure the token data is not a string (because it should be an object)
if (typeof tokenData === "string") return Promise.reject()
return tokenData
}
const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => { const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
if (res.socket.server.io) { if (res.socket.server.io) {
console.log("Socket is already running " + req.url) console.log("Socket is already running " + req.url)
@ -59,10 +74,7 @@ const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
}) })
socket.on("authenticate", (payload) => { socket.on("authenticate", (payload) => {
checkTokenIsValid({ checkTokenIsValid(payload.token)
token: payload.token,
tokenType: "ACCESS",
})
.then(async ({ tokenBody }) => { .then(async ({ tokenBody }) => {
const token = await prisma.token.findUnique({ const token = await prisma.token.findUnique({
where: { id: tokenBody.id }, where: { id: tokenBody.id },

File diff suppressed because it is too large Load diff