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

View file

@ -1,30 +1,34 @@
import { Token } from "@prisma/client"
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"
async function checkTokenIsValid<T>(
payload: T & { token: string; tokenType: Token["type"] }
async function checkTokenIsValid(
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
let tokenData: string | jwt.JwtPayload
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) {
// 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)
if (typeof tokenData === "string") {
return Promise.reject({
message: tokenType + "-Token data was a string. Token: " + token,
statusCode: 401,
solved: false,
})
}
if (typeof tokenData === "string")
return sendError(req, res, errors.tokenWasString(tokenType, tokenValue))
return { ...payload, tokenBody: tokenData, tokenIsValid: true }
return next(tokenData)
}
export default checkTokenIsValid

View file

@ -1,38 +1,19 @@
import { Player } from "@prisma/client"
import bcrypt from "bcrypt"
import prisma from "../../prisma"
async function createPlayerDB<T>(
payload: T & { username: string; password: string }
async function createPlayerDB(
username: string,
password: string,
next: (player: Player) => void
) {
const { username, password } = payload
return await prisma.player
.create({
data: {
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,
})
}
})
return await prisma.player.create({
data: {
username,
passwordHash: await bcrypt.hash(password, 10),
anonymous: false,
},
})
}
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 prisma from "../../prisma"
@ -7,11 +7,11 @@ const tokenLifetime = {
ACCESS: 15,
}
export default async function createTokenDB<T>(
payload: T & { player: Player; newTokenType: Token["type"] }
export default async function createTokenDB(
player: Player,
newTokenType: TokenType,
next: (token: [string, Token]) => void
) {
const { player, newTokenType } = payload
// Create token entry in DB
const newTokenDB = await prisma.token.create({
data: {
@ -28,13 +28,9 @@ export default async function createTokenDB<T>(
// Sign a new access token
const newToken = jwt.sign(
{ id: newTokenDB.id },
process.env.ACCESS_TOKEN_SECRET as string,
process.env.TOKEN_SECRET as string,
{ expiresIn: tokenLifetime[newTokenType] }
)
return {
...payload,
newToken,
newTokenDB,
}
return next([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 { NextApiRequest, NextApiResponse } from "next"
import sendError from "./sendError"
export default async function getPlayerByIdDB<T>(
payload: T & { tokenDB: Token }
export default async function getPlayerByIdDB(
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)
const player = await prisma.player.findUnique({
where: {
@ -12,15 +17,8 @@ export default async function getPlayerByIdDB<T>(
},
})
if (!player) {
return Promise.reject({
message: "Player ID not found in DB!",
statusCode: 401,
solved: false,
})
return sendError(req, res, errors.playerNotFound)
}
return {
...payload,
player,
}
next(player)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -8,12 +8,11 @@ export interface Result<T> {
type?: Logging[]
}
export default function sendResponse<T>(payload: {
req: NextApiRequest
res: NextApiResponse<T>
export default function sendResponse<T>(
req: NextApiRequest,
res: NextApiResponse<T>,
result: Result<T>
}) {
const { req, res, result } = payload
) {
res.status(result.statusCode ?? 200)
result.body ? res.json(result.body) : res.end()
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(
tokenType: Token["type"],
err: Error
) {
export default async function jwtVerifyCatch(tokenType: TokenType, err: Error) {
switch (err.message) {
case "jwt expired":
return {