From 861f8e55f43c496c125cd03432a03312ecc5eab8 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Tue, 7 May 2024 16:59:00 -0400 Subject: [PATCH] bug fixed + add support for google profile pics --- components/CollectionListing.tsx | 6 ++- components/ProfilePhoto.tsx | 2 +- lib/api/controllers/users/postUser.ts | 54 ++++++++----------- .../users/userId/deleteUserById.ts | 6 ++- lib/api/isServerAdmin.ts | 44 +++++++++++++++ next.config.js | 9 ++++ pages/api/v1/auth/[...nextauth].ts | 14 +++++ pages/api/v1/users/index.ts | 2 +- 8 files changed, 100 insertions(+), 37 deletions(-) create mode 100644 lib/api/isServerAdmin.ts diff --git a/components/CollectionListing.tsx b/components/CollectionListing.tsx index 171117b..18d36f3 100644 --- a/components/CollectionListing.tsx +++ b/components/CollectionListing.tsx @@ -201,7 +201,11 @@ const CollectionListing = () => { }; if (!tree) { - return <>; + return ( +

+ You Have No Collections... +

+ ); } else return ( { - if (src && !src?.includes("base64")) + if (src && !src?.includes("base64") && !src.startsWith("http")) setImage(`/api/v1/${src.replace("uploads/", "").replace(".jpg", "")}`); else if (!src) setImage(""); else { diff --git a/lib/api/controllers/users/postUser.ts b/lib/api/controllers/users/postUser.ts index 84ab2e0..6a85e5f 100644 --- a/lib/api/controllers/users/postUser.ts +++ b/lib/api/controllers/users/postUser.ts @@ -1,7 +1,7 @@ import { prisma } from "@/lib/api/db"; import type { NextApiRequest, NextApiResponse } from "next"; import bcrypt from "bcrypt"; -import verifyUser from "../../verifyUser"; +import isServerAdmin from "../../isServerAdmin"; const emailEnabled = process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; @@ -9,6 +9,7 @@ const stripeEnabled = process.env.STRIPE_SECRET_KEY ? true : false; interface Data { response: string | object; + status: number; } interface User { @@ -20,18 +21,12 @@ interface User { export default async function postUser( req: NextApiRequest, - res: NextApiResponse -) { - let isServerAdmin = false; + res: NextApiResponse +): Promise { + let isAdmin = await isServerAdmin({ req }); - const user = await verifyUser({ req, res }); - if (process.env.ADMINISTRATOR === user?.username) isServerAdmin = true; - - if ( - process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true" && - !isServerAdmin - ) { - return res.status(400).json({ response: "Registration is disabled." }); + if (process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true" && !isAdmin) { + return { response: "Registration is disabled.", status: 400 }; } const body: User = req.body; @@ -41,39 +36,36 @@ export default async function postUser( : !body.username || !body.password || !body.name; if (!body.password || body.password.length < 8) - return res - .status(400) - .json({ response: "Password must be at least 8 characters." }); + return { response: "Password must be at least 8 characters.", status: 400 }; if (checkHasEmptyFields) - return res - .status(400) - .json({ response: "Please fill out all the fields." }); + return { response: "Please fill out all the fields.", status: 400 }; // Check email (if enabled) const checkEmail = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; if (emailEnabled && !checkEmail.test(body.email?.toLowerCase() || "")) - return res.status(400).json({ - response: "Please enter a valid email.", - }); + return { response: "Please enter a valid email.", status: 400 }; // Check username (if email was disabled) const checkUsername = RegExp("^[a-z0-9_-]{3,31}$"); if (!emailEnabled && !checkUsername.test(body.username?.toLowerCase() || "")) - return res.status(400).json({ + return { response: "Username has to be between 3-30 characters, no spaces and special characters are allowed.", - }); + status: 400, + }; const checkIfUserExists = await prisma.user.findFirst({ where: { OR: [ { - email: body.email?.toLowerCase().trim(), + email: body.email ? body.email.toLowerCase().trim() : undefined, }, { - username: (body.username as string).toLowerCase().trim(), + username: body.username + ? body.username.toLowerCase().trim() + : undefined, }, ], }, @@ -89,7 +81,7 @@ export default async function postUser( const currentPeriodEnd = new Date(); currentPeriodEnd.setFullYear(currentPeriodEnd.getFullYear() + 1000); // end date is in 1000 years... - if (isServerAdmin) { + if (isAdmin) { const user = await prisma.user.create({ data: { name: body.name, @@ -123,7 +115,7 @@ export default async function postUser( }, }); - return res.status(201).json({ response: user }); + return { response: user, status: 201 }; } else { await prisma.user.create({ data: { @@ -136,11 +128,9 @@ export default async function postUser( }, }); - return res.status(201).json({ response: "User successfully created." }); + return { response: "User successfully created.", status: 201 }; } - } else if (checkIfUserExists) { - return res.status(400).json({ - response: `Email or Username already exists.`, - }); + } else { + return { response: "Email or Username already exists.", status: 400 }; } } diff --git a/lib/api/controllers/users/userId/deleteUserById.ts b/lib/api/controllers/users/userId/deleteUserById.ts index 9c01dcd..19dce14 100644 --- a/lib/api/controllers/users/userId/deleteUserById.ts +++ b/lib/api/controllers/users/userId/deleteUserById.ts @@ -25,8 +25,10 @@ export default async function deleteUserById( }; } - // Then, we check if the provided password matches the one stored in the database (disabled in Keycloak integration) - if (!keycloakEnabled && !authentikEnabled && !isServerAdmin) { + // Then, we check if the provided password matches the one stored in the database (disabled in SSO/OAuth integrations) + if (user.password && !isServerAdmin) { + console.log("isServerAdmin", isServerAdmin); + console.log("isServerAdmin", body.password); const isPasswordValid = bcrypt.compareSync( body.password, user.password as string diff --git a/lib/api/isServerAdmin.ts b/lib/api/isServerAdmin.ts new file mode 100644 index 0000000..e58c329 --- /dev/null +++ b/lib/api/isServerAdmin.ts @@ -0,0 +1,44 @@ +import { NextApiRequest } from "next"; +import { getToken } from "next-auth/jwt"; +import { prisma } from "./db"; + +type Props = { + req: NextApiRequest; +}; + +export default async function isServerAdmin({ req }: Props): Promise { + const token = await getToken({ req }); + const userId = token?.id; + + if (!userId) { + return false; + } + + if (token.exp < Date.now() / 1000) { + return false; + } + + // check if token is revoked + const revoked = await prisma.accessToken.findFirst({ + where: { + token: token.jti, + revoked: true, + }, + }); + + if (revoked) { + return false; + } + + const findUser = await prisma.user.findFirst({ + where: { + id: userId, + }, + }); + + if (findUser?.username === process.env.ADMINISTRATOR) { + return true; + } else { + return false; + } +} diff --git a/next.config.js b/next.config.js index ebb9b54..665803e 100644 --- a/next.config.js +++ b/next.config.js @@ -4,7 +4,16 @@ const { version } = require("./package.json"); const nextConfig = { reactStrictMode: true, images: { + // For fetching the favicons domains: ["t2.gstatic.com"], + + // For profile pictures (Google OAuth) + remotePatterns: [ + { + hostname: "*.googleusercontent.com", + }, + ], + minimumCacheTTL: 10, }, env: { diff --git a/pages/api/v1/auth/[...nextauth].ts b/pages/api/v1/auth/[...nextauth].ts index c8ca51f..c1d4c1d 100644 --- a/pages/api/v1/auth/[...nextauth].ts +++ b/pages/api/v1/auth/[...nextauth].ts @@ -1103,6 +1103,20 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) { }, callbacks: { async signIn({ user, account, profile, email, credentials }) { + // console.log( + // "User sign in attempt...", + // "User", + // user, + // "Account", + // account, + // "Profile", + // profile, + // "Email", + // email, + // "Credentials", + // credentials + // ); + if (account?.provider !== "credentials") { // registration via SSO can be separately disabled const existingUser = await prisma.account.findFirst({ diff --git a/pages/api/v1/users/index.ts b/pages/api/v1/users/index.ts index a4768fa..c0d4f7b 100644 --- a/pages/api/v1/users/index.ts +++ b/pages/api/v1/users/index.ts @@ -6,7 +6,7 @@ import verifyUser from "@/lib/api/verifyUser"; export default async function users(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") { const response = await postUser(req, res); - return response; + return res.status(response.status).json({ response: response.response }); } else if (req.method === "GET") { const user = await verifyUser({ req, res }); if (!user || process.env.ADMINISTRATOR !== user.username)