First prisma integration step

This commit is contained in:
aronmal 2023-02-04 00:29:06 +01:00
parent 6918729c45
commit 0507309ab1
Signed by: aronmal
GPG key ID: 816B7707426FC612
21 changed files with 645 additions and 1 deletions

View file

@ -0,0 +1,22 @@
import { Player } from "@prisma/client"
import bcrypt from "bcrypt"
export default async function checkPasswordIsValid<T>(payload: T & { player: Player, password: string }) {
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
}
})
}

View file

@ -0,0 +1,28 @@
import { Token } from "@prisma/client"
import jwt from "jsonwebtoken"
import jwtVerifyCatch from "../jwtVerifyCatch"
async function checkTokenIsValid<T>(payload: T & { token: string, tokenType: Token['type'] }) {
const { token, tokenType } = payload
// Verify the token and get the payload
let tokenData: string | jwt.JwtPayload
try {
tokenData = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string)
} catch (err: any) {
// Deal with the problem in more detail
return Promise.reject(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
})
}
return { ...payload, tokenBody: token, tokenIsValid: true }
}
export default checkTokenIsValid

View file

@ -0,0 +1,28 @@
import prisma from "../../prisma"
async function createAnonymousDB<T>(payload: T) {
const player = await prisma.player.create({
data: {
anonymous: true
}
})
// .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 { ...payload, player }
}
export default createAnonymousDB

View file

@ -0,0 +1,34 @@
import bcrypt from "bcrypt"
import prisma from "../../prisma"
async function createPlayerDB<T>(payload: T & { username: string, password: string }) {
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
})
}
})
}
export default createPlayerDB

View file

@ -0,0 +1,36 @@
import { Player, Token } from "@prisma/client"
import jwt from "jsonwebtoken"
import { v4 as uuidv4 } from "uuid"
import prisma from "../../prisma"
const tokenLifetime = {
REFRESH: 172800,
ACCESS: 15
};
export default async function createTokenDB<T>(payload: T & { player: Player, newTokenType: Token['type'] }) {
const { player, newTokenType } = payload
// Sign a new access token
const newToken = jwt.sign({ uuid: uuidv4(), user: player.id }, process.env.ACCESS_TOKEN_SECRET as string, { expiresIn: tokenLifetime[newTokenType] })
// Save token to DB
const newTokenDB = await prisma.token.create({
data: {
token: newToken,
type: newTokenType,
expires: new Date(Date.now() + tokenLifetime[newTokenType] + '000'),
owner: {
connect: {
id: player.id
}
}
}
})
return {
...payload,
newToken,
newTokenDB
}
}

View file

@ -0,0 +1,24 @@
import { Token } from "@prisma/client"
import prisma from "../../prisma"
export default async function getPlayerByIdDB<T>(payload: T & { tokenDB: Token }) {
const { tokenDB } = payload
// Find Host in DB if it still exists (just to make sure)
const player = await prisma.player.findUnique({
where: {
id: tokenDB.ownerId
}
})
if (!player) {
return Promise.reject({
message: 'Player not found in DB!',
statusCode: 401,
solved: false
})
}
return {
...payload,
player
}
}

View file

@ -0,0 +1,29 @@
import prisma from "../../prisma"
export default async function getPlayerByNameDB<T>(payload: T & { username: string }) {
const { username } = payload
// Find Player in DB if it still exists (just to make sure)
const player = await Promise.any([
prisma.player.findUnique({
where: {
username: username
}
}), prisma.player.findUnique({
where: {
email: username
}
})
])
if (!player) {
return Promise.reject({
message: 'Player not found in DB!',
statusCode: 401,
solved: false
})
}
return {
...payload,
player
}
}

View file

@ -0,0 +1,52 @@
import { NextApiRequest, NextApiResponse } from "next"
import prisma from "../../prisma"
async function getTokenDB<T>(payload: T & {
tokenBody: string
tokenIsValid: boolean,
req: NextApiRequest,
res: NextApiResponse<any>,
}) {
const { tokenBody } = payload
// Find refresh token in DB
const tokenDB = await prisma.token.findUnique({
where: {
token: tokenBody
}
})
if (!tokenDB) {
return Promise.reject({
message: 'Access-Token not found in DB!',
statusCode: 401,
solved: true,
type: 'warn'
})
}
if (tokenDB.used) {
return Promise.reject({
message: 'DBToken was already used!',
statusCode: 401,
solved: true
})
}
await prisma.token.update({
where: {
token: tokenBody
},
data: {
used: true
}
})
// await logging('Old token has been invalidated.', ['debug'], req)
return {
...payload,
tokenDB
}
}
export default getTokenDB

View file

@ -0,0 +1,19 @@
import { NextApiRequest } from "next"
async function getTokenFromBody<T>(payload: T & { req: NextApiRequest }) {
const { req } = payload
const token: string = req.body.token
// Checking for cookie presens, because it is necessary
if (!token) {
return Promise.reject({
message: 'Unauthorized. No Access-Token.',
statusCode: 401,
solved: true,
})
}
return { ...payload, token }
}
export default getTokenFromBody

View file

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

View file

@ -0,0 +1,15 @@
import { Token } from "@prisma/client"
import { Logging } from "../logging"
export default async function loginCheck<T>(payload: T & { loginCheck: boolean, tokenDB: Token, tokenType: 'REFRESH' }) {
const { loginCheck, tokenDB } = payload
// True login check response
if (loginCheck) {
return Promise.resolve({
message: 'loginCheck ' + loginCheck + ' of ' + tokenDB.id,
body: { loggedIn: true },
type: ['debug', 'info.cyan'] as Logging[]
})
}
return payload
}

View file

@ -0,0 +1,12 @@
import { NextApiRequest, NextApiResponse } from "next"
import logging from "../logging"
export default function sendError<T>(
req: NextApiRequest,
res: NextApiResponse<T>,
err: any
) {
// If something went wrong, let the client know with status 500
res.status(err.statusCode ?? 500).end()
logging(err.message, [err.type ?? (err.solved ? 'debug' : 'error')], req)
}

View file

@ -0,0 +1,22 @@
import { NextApiRequest, NextApiResponse } from "next"
import logging, { Logging } from "../logging"
export interface Result<T> {
message: string,
statusCode?: number,
body?: T,
type?: Logging[],
}
export default function sendResponse<T>(payload: {
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

@ -1,5 +1,7 @@
import { Token } from "@prisma/client"
export default async function jwtVerifyCatch(
tokenType: 'refreshToken' | 'accessToken',
tokenType: Token['type'],
err: Error
) {
switch (err.message) {

View file

@ -0,0 +1,23 @@
import { GetServerSidePropsContext, PreviewData } from "next"
import { ParsedUrlQuery } from "querystring"
import getTokenFromCookie from "../backend/components/getTokenFromCookie"
import checkTokenIsValid from "../backend/components/checkTokenIsValid"
import getTokenDB from "../backend/components/getTokenDB"
import getPlayerByIdDB from "../backend/components/getPlayerByIdDB"
import logging from "../backend/logging"
export default async function checkIsLoggedIn(context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) {
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)
logging('loginCheck ' + (isLoggedIn ? true : '-> loggedIn: ' + false), ['debug', 'info.cyan'], req)
return isLoggedIn
}

View file

@ -0,0 +1,7 @@
export default function getAccessToken(): Promise<string> {
return fetch('/api/auth', {
method: 'GET',
})
.then(res => res.json())
.then(res => res.newAccessToken)
}

View file

@ -0,0 +1,49 @@
import { NextApiRequest, NextApiResponse } from "next"
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
}
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))
}
async function authResponse<T>(payload: {
newToken: string,
newTokenDB: Token,
tokenDB: Token,
req: NextApiRequest,
res: NextApiResponse<T>
}) {
const { newToken, newTokenDB, tokenDB, req, res } = payload
// 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[]
}
}
}

View file

@ -0,0 +1,48 @@
import { NextApiRequest, NextApiResponse } from "next"
import getTokenFromBody from "../../lib/backend/components/getTokenFromBody"
import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid"
import getTokenDB from "../../lib/backend/components/getTokenDB"
import getPlayerByIdDB from "../../lib/backend/components/getPlayerByIdDB"
import sendResponse from "../../lib/backend/components/sendResponse"
import sendError from "../../lib/backend/components/sendError"
import { Logging } from "../../lib/backend/logging"
import { Game, Player, Token } from "@prisma/client"
interface Data {
games: Game[]
}
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))
}
async function dataResponse<T>(payload: {
player: Player,
tokenDB: Token,
// games: Game[],
req: NextApiRequest,
res: NextApiResponse<T>
}) {
const { player, tokenDB, req, res } = payload
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[]
}
}
}

View file

@ -0,0 +1,86 @@
import { NextApiRequest, NextApiResponse } from "next"
import logging, { Logging } from "../../lib/backend/logging"
import getPlayerByNameDB from "../../lib/backend/components/getPlayerByNameDB"
import checkPasswordIsValid from "../../lib/backend/components/checkPasswordIsValid"
import createTokenDB from "../../lib/backend/components/createTokenDB"
import sendResponse from "../../lib/backend/components/sendResponse"
import sendError from "../../lib/backend/components/sendError"
import { setCookie } from "cookies-next"
import { Player, Token } from "@prisma/client"
import prisma from "../../lib/prisma"
interface Data {
loggedIn: boolean
}
export default async function login(
req: NextApiRequest,
res: NextApiResponse<any>
) {
const { username, password } = req.body
return preCheck({ req, res, username, password, newTokenType: 'REFRESH' as Token['type'] })
.then(getPlayerByNameDB)
.then(checkPasswordIsValid)
.then(createTokenDB)
.then(loginResponse<Data>)
.then(sendResponse<Data>)
.catch(err => sendError(req, res, err))
}
async function preCheck<T>(payload: T & {
req: NextApiRequest,
res: NextApiResponse<T>
}) {
const { req } = payload
const oldRefreshToken = req.cookies.token
// Check for old cookie, if unused invalidate it
const oldDBToken = await prisma.token.findUnique({
where: {
token: oldRefreshToken
}
})
if (oldDBToken?.used) {
await prisma.token.update({
where: {
token: oldRefreshToken
},
data: {
used: true
}
})
await logging('Old token has been invalidated.', ['debug'], req)
}
return { ...payload, noCookiePresent: true }
}
async function loginResponse<T>(payload: {
player: Player,
passwordIsValid: boolean,
refreshToken: string,
refreshTokenDB: Token,
req: NextApiRequest,
res: NextApiResponse<T>
}) {
const { player, refreshToken, refreshTokenDB, req, res } = payload
// Set login cookie
setCookie('token', refreshToken, {
req,
res,
maxAge: 172800000,
httpOnly: true,
sameSite: true,
secure: true,
})
// Successfull response
return {
req,
res,
result: {
message: 'User ' + player.id + ' logged in and generated Refresh-Token: ' + refreshTokenDB.id,
body: { loggedIn: true },
type: ['debug', 'info.cyan'] as Logging[]
}
}
}

View file

@ -0,0 +1,47 @@
import { NextApiRequest, NextApiResponse } from "next"
import checkTokenIsValid from "../../lib/backend/components/checkTokenIsValid"
import sendResponse from "../../lib/backend/components/sendResponse"
import sendError from "../../lib/backend/components/sendError"
import { deleteCookie } from "cookies-next"
import { Token } from "@prisma/client"
import getTokenDB from "../../lib/backend/components/getTokenDB"
import getTokenFromCookie from "../../lib/backend/components/getTokenFromCookie"
import logging, { Logging } from "../../lib/backend/logging"
interface Data {
loggedOut: boolean
}
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))
}
async function logoutResponse<T>(payload: {
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[]
}
}
}

View file

@ -0,0 +1,41 @@
import { NextApiRequest, NextApiResponse } from "next"
import createPlayerDB from "../../lib/backend/components/createPlayerDB"
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"
interface Data {
registered: boolean
}
export default async function register(
req: NextApiRequest,
res: NextApiResponse<any>
) {
const { username, password } = req.body
return createPlayerDB({ req, res, username, password })
.then(registerResponse<Data>)
.then(sendResponse<Data>)
.catch(err => sendError(req, res, err))
}
async function registerResponse<T>(payload: {
player: Player,
req: NextApiRequest,
res: NextApiResponse<T>
}) {
const { player, req, res } = payload
// Successfull response
return {
req,
res,
result: {
message: 'Player created : ' + player.id,
statusCode: 201,
body: { registered: true },
type: ['debug', 'info.cyan'] as Logging[]
}
}
}