Complete backend rework
This commit is contained in:
parent
53d4ab527a
commit
eb09017dab
25 changed files with 958 additions and 866 deletions
4
leaky-ships/global.d.ts
vendored
4
leaky-ships/global.d.ts
vendored
|
@ -1,3 +1,5 @@
|
|||
declare module globalThis {
|
||||
import "@total-typescript/ts-reset"
|
||||
|
||||
declare global {
|
||||
var prismaClient: PrismaClient
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -12,16 +12,17 @@ export default async function checkIsLoggedIn(
|
|||
const req: any = context.req
|
||||
const res: any = context.res
|
||||
|
||||
const isLoggedIn = await getTokenFromCookie({ req, res })
|
||||
.then(checkTokenIsValid)
|
||||
.then(getTokenDB)
|
||||
.then(getPlayerByIdDB)
|
||||
.then(({ player }) => !!player)
|
||||
.catch(() => false)
|
||||
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", "info.cyan"],
|
||||
["debug", "infoCyan"],
|
||||
req
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
export default function getAccessToken(): Promise<string> {
|
||||
return fetch("/api/auth", {
|
||||
async function getAccessToken() {
|
||||
const response = await fetch("/api/auth", {
|
||||
method: "GET",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => res.token)
|
||||
const res = await response.json()
|
||||
|
||||
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
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"typescript": "4.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/ts-reset": "^0.3.7",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/node": "^18.13.0",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
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 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 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 {
|
||||
token: string
|
||||
|
@ -17,41 +17,26 @@ export default async function auth(
|
|||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
return getTokenFromCookie({
|
||||
req,
|
||||
res,
|
||||
newTokenType: "ACCESS" as Token["type"],
|
||||
})
|
||||
.then(checkTokenIsValid)
|
||||
.then(getTokenDB)
|
||||
.then(getPlayerByIdDB)
|
||||
.then(createTokenDB)
|
||||
.then(authResponse<Data>)
|
||||
.then(sendResponse<Data>)
|
||||
.catch((err) => sendError(req, res, err))
|
||||
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))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch((err) => sendError(req, res, err))
|
||||
}
|
||||
|
||||
async function authResponse<T>(payload: {
|
||||
newToken: string
|
||||
newTokenDB: Token
|
||||
tokenDB: Token
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<T>
|
||||
}) {
|
||||
const { newToken, newTokenDB, tokenDB, req, res } = payload
|
||||
function authResponse(newToken: [string, Token], tokenDB: Token) {
|
||||
const [newTokenValue, newTokenDB] = newToken
|
||||
|
||||
// Successfull response
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
result: {
|
||||
message:
|
||||
"Access-Token generated: " +
|
||||
newTokenDB.id +
|
||||
" with Refreshtoken-Token: " +
|
||||
tokenDB.id,
|
||||
body: { token: newToken },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
},
|
||||
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
||||
body: { token: newTokenValue },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,37 +16,23 @@ export default async function data(
|
|||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
return getTokenFromBody({ req, res, tokenType: "ACCESS" as Token["type"] })
|
||||
.then(checkTokenIsValid)
|
||||
.then(getTokenDB)
|
||||
.then(getPlayerByIdDB)
|
||||
.then(dataResponse<Data>)
|
||||
.then(sendResponse<Data>)
|
||||
.catch((err) => sendError(req, res, err))
|
||||
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))
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch((err) => sendError(req, res, err))
|
||||
}
|
||||
|
||||
async function dataResponse<T>(payload: {
|
||||
player: Player
|
||||
tokenDB: Token
|
||||
// games: Game[],
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<T>
|
||||
}) {
|
||||
const { player, tokenDB, req, res } = payload
|
||||
|
||||
function dataResponse<T>(player: Player, tokenDB: Token) {
|
||||
const games: any = {}
|
||||
// Successfull response
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
result: {
|
||||
message:
|
||||
"Requested data of user: " +
|
||||
player.id +
|
||||
" with Access-Token: " +
|
||||
tokenDB.id,
|
||||
body: { games },
|
||||
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[],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import getPlayerByNameDB from "../../lib/backend/components/getPlayerByNameDB"
|
|||
import checkPasswordIsValid from "../../lib/backend/components/checkPasswordIsValid"
|
||||
import createTokenDB from "../../lib/backend/components/createTokenDB"
|
||||
import sendResponse from "../../lib/backend/components/sendResponse"
|
||||
import sendError from "../../lib/backend/components/sendError"
|
||||
import { deleteCookie, setCookie } from "cookies-next"
|
||||
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 getUserFromBody from "../../lib/backend/components/getUserFromBody"
|
||||
|
||||
interface Data {
|
||||
loggedIn: boolean
|
||||
|
@ -18,54 +20,44 @@ export default async function login(
|
|||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
) {
|
||||
const { username, password } = JSON.parse(req.body)
|
||||
const payload = {
|
||||
req,
|
||||
res,
|
||||
username,
|
||||
password,
|
||||
newTokenType: "REFRESH" as Token["type"],
|
||||
}
|
||||
return preCheck(payload)
|
||||
.then(getPlayerByNameDB)
|
||||
.then(checkPasswordIsValid)
|
||||
.then(createTokenDB)
|
||||
.then(loginResponse<Data>)
|
||||
.then(sendResponse<Data>)
|
||||
.catch((err) => sendError(req, res, err))
|
||||
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))
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
).catch((err) => sendError(req, res, err))
|
||||
}
|
||||
|
||||
async function preCheck<T>(
|
||||
payload: T & {
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<T>
|
||||
}
|
||||
async function preCheck(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
next: () => void
|
||||
) {
|
||||
const { req, res } = payload
|
||||
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")
|
||||
deleteCookie("token", { req, res })
|
||||
else {
|
||||
const oldDBToken =
|
||||
oldRefreshToken &&
|
||||
(await prisma.token.findUnique({
|
||||
where: {
|
||||
id: tokenData.id,
|
||||
},
|
||||
}))
|
||||
if (!oldDBToken)
|
||||
logging(
|
||||
"Cookie could be verified but wasn't found in DB:" + tokenData,
|
||||
["debug"],
|
||||
req
|
||||
)
|
||||
else if (!oldDBToken.used)
|
||||
logging("Old token was used: " + oldDBToken.id, ["debug"], req)
|
||||
try {
|
||||
if (!oldRefreshToken) return sendError(req, res, errors.noCookie)
|
||||
// Check for old cookie, if unused invalidate it
|
||||
const tokenData = jwt.verify(
|
||||
oldRefreshToken,
|
||||
process.env.ACCESS_TOKEN_SECRET as string
|
||||
)
|
||||
|
||||
if (!tokenData || typeof tokenData === "string") return
|
||||
|
||||
const oldDBToken = await prisma.token.findUniqueOrThrow({
|
||||
where: {
|
||||
id: tokenData.id,
|
||||
},
|
||||
})
|
||||
|
||||
if (oldDBToken.used)
|
||||
logging("Old login token was used: " + oldDBToken.id, ["debug"], req)
|
||||
else {
|
||||
await prisma.token.update({
|
||||
where: {
|
||||
|
@ -75,44 +67,52 @@ async function preCheck<T>(
|
|||
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: {
|
||||
player: Player
|
||||
passwordIsValid: boolean
|
||||
newToken: string
|
||||
newTokenDB: Token
|
||||
req: NextApiRequest
|
||||
function loginResponse<T>(
|
||||
player: Player,
|
||||
newToken: [string, Token],
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<T>
|
||||
}) {
|
||||
const { player, newToken, newTokenDB, req, res } = payload
|
||||
) {
|
||||
// const { player, req, res } = payload
|
||||
const [newTokenValue, newTokenDB] = newToken
|
||||
|
||||
// Set login cookie
|
||||
setCookie("token", newToken, {
|
||||
setCookie("token", newTokenValue, {
|
||||
req,
|
||||
res,
|
||||
maxAge: 172800000,
|
||||
maxAge: 172800,
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: true,
|
||||
path: "/api",
|
||||
})
|
||||
|
||||
// deleteCookie("token", {
|
||||
// req,
|
||||
// res,
|
||||
// httpOnly: true,
|
||||
// sameSite: true,
|
||||
// secure: true,
|
||||
// path: "/api",
|
||||
// })
|
||||
|
||||
// Successfull response
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
result: {
|
||||
message:
|
||||
"User " +
|
||||
player.id +
|
||||
" logged in and generated Refresh-Token: " +
|
||||
newTokenDB.id,
|
||||
body: { loggedIn: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
},
|
||||
message:
|
||||
"User " +
|
||||
player.id +
|
||||
" logged in and generated Refresh-Token: " +
|
||||
newTokenDB.id,
|
||||
body: { loggedIn: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,32 +16,27 @@ export default async function logout(
|
|||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
) {
|
||||
return getTokenFromCookie({ req, res })
|
||||
.then(checkTokenIsValid)
|
||||
.then(getTokenDB)
|
||||
.then(logoutResponse<Data>)
|
||||
.then(sendResponse<Data>)
|
||||
.catch((err) => sendError(req, res, err))
|
||||
return getTokenFromCookie(req, res, (token) => {
|
||||
checkTokenIsValid(req, res, token, (tokenBody) => {
|
||||
getTokenDB(req, res, tokenBody, token[1], (tokenDB) => {
|
||||
sendResponse(req, res, logoutResponse(tokenDB, req, res))
|
||||
})
|
||||
})
|
||||
}).catch((err) => sendError(req, res, err))
|
||||
}
|
||||
|
||||
async function logoutResponse<T>(payload: {
|
||||
tokenDB: Token
|
||||
req: NextApiRequest
|
||||
function logoutResponse<T>(
|
||||
tokenDB: Token,
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<T>
|
||||
}) {
|
||||
const { tokenDB, req, res } = payload
|
||||
|
||||
) {
|
||||
// Set login cookie
|
||||
deleteCookie("token", { req, res })
|
||||
|
||||
// Successfull response
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
result: {
|
||||
message: "User of Token " + tokenDB.id + " logged out.",
|
||||
body: { loggedOut: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
},
|
||||
message: "User of Token " + tokenDB.id + " logged out.",
|
||||
body: { loggedOut: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import sendError from "../../lib/backend/components/sendError"
|
|||
import sendResponse from "../../lib/backend/components/sendResponse"
|
||||
import { Logging } from "../../lib/backend/logging"
|
||||
import { Player } from "@prisma/client"
|
||||
import getUserFromBody from "../../lib/backend/components/getUserFromBody"
|
||||
|
||||
interface Data {
|
||||
registered: boolean
|
||||
|
@ -11,31 +12,21 @@ interface Data {
|
|||
|
||||
export default async function register(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
const { username, password } = JSON.parse(req.body)
|
||||
return createPlayerDB({ req, res, username, password })
|
||||
.then(registerResponse<Data>)
|
||||
.then(sendResponse<Data>)
|
||||
.catch((err) => sendError(req, res, err))
|
||||
return getUserFromBody(req, res, (username, password) =>
|
||||
createPlayerDB(username, password, (player) => {
|
||||
sendResponse(req, res, registerResponse(player))
|
||||
}).catch((err) => sendError(req, res, err))
|
||||
)
|
||||
}
|
||||
|
||||
async function registerResponse<T>(payload: {
|
||||
player: Player
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<T>
|
||||
}) {
|
||||
const { player, req, res } = payload
|
||||
|
||||
function registerResponse<T>(player: Player) {
|
||||
// Successfull response
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
result: {
|
||||
message: "Player created : " + player.id,
|
||||
statusCode: 201,
|
||||
body: { registered: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
},
|
||||
message: "Player created : " + player.id,
|
||||
statusCode: 201,
|
||||
body: { registered: true },
|
||||
type: ["debug", "info.cyan"] as Logging[],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { NextApiRequest } from "next"
|
||||
import { Server } from "socket.io"
|
||||
import { NextApiResponseWithSocket } from "../../interfaces/NextApiSocket"
|
||||
import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid"
|
||||
import jwt from "jsonwebtoken"
|
||||
import prisma from "../../lib/prisma"
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
|
@ -37,6 +37,21 @@ interface SocketData {
|
|||
// 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) => {
|
||||
if (res.socket.server.io) {
|
||||
console.log("Socket is already running " + req.url)
|
||||
|
@ -59,10 +74,7 @@ const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
|
|||
})
|
||||
|
||||
socket.on("authenticate", (payload) => {
|
||||
checkTokenIsValid({
|
||||
token: payload.token,
|
||||
tokenType: "ACCESS",
|
||||
})
|
||||
checkTokenIsValid(payload.token)
|
||||
.then(async ({ tokenBody }) => {
|
||||
const token = await prisma.token.findUnique({
|
||||
where: { id: tokenBody.id },
|
||||
|
|
1074
leaky-ships/pnpm-lock.yaml
generated
1074
leaky-ships/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue