Complete backend rework
This commit is contained in:
parent
53d4ab527a
commit
eb09017dab
25 changed files with 958 additions and 866 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
26
leaky-ships/lib/backend/components/getUserFromBody.ts
Normal file
26
leaky-ships/lib/backend/components/getUserFromBody.ts
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
80
leaky-ships/lib/backend/errors.ts
Normal file
80
leaky-ships/lib/backend/errors.ts
Normal 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
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue