diff --git a/helpers/db.ts b/helpers/db.ts index 6a383ff..98c8bf3 100644 --- a/helpers/db.ts +++ b/helpers/db.ts @@ -3,7 +3,7 @@ import { getUnixTime, sub } from "date-fns"; import { Long, MongoClient, UpdateFilter } from "mongodb"; import NodeCache from "node-cache"; -import { Member, Position, RawMember, Submission, SubmissionStatus } from "./types"; +import { Member, RawMember, Submission, SubmissionStatus } from "./types"; let client: MongoClient | undefined; @@ -30,13 +30,6 @@ const wrapCache = Promise>(key: string, func: T) }; }; -const ROLES: [Position, string[]][] = [ - [Position.ADMIN, ["908088852567187467"]], - [Position.COMMUNITY_MANAGER, ["718006431231508481"]], - [Position.MODERATOR, ["724879492622843944", "813433839471820810"]], - [Position.HELPER, ["732712709514199110", "794438698241884200"]], -]; - export const fetchMember = wrapCache("member", async (id: string): Promise => { const db = await dbPromise; const collection = db.collection("member"); @@ -51,7 +44,6 @@ export const fetchMember = wrapCache("member", async (id: string): Promise roles?.some((x) => ids.includes(x)))?.[0] ?? Position.MEMBER, }; }); diff --git a/helpers/permissions.ts b/helpers/permissions.ts new file mode 100644 index 0000000..15860e4 --- /dev/null +++ b/helpers/permissions.ts @@ -0,0 +1,17 @@ +import { Member } from "./types"; + +const admin = "718006431231508481"; +const serverManager = "1219500880534179892"; +const botManager = "1219501453240959006"; + +const permittedRoles: Record = { + "moderator-application": [admin, serverManager], + "ban-appeal": [admin, serverManager], + "suspension-appeal": [admin, botManager], +}; + +export const permittedToViewForm = (member: Member, formId: string) => { + const roles = member.roles ?? []; + const permittedRolesForForm = permittedRoles[formId] ?? []; + return roles.some((role) => permittedRolesForForm.includes(role)); +}; diff --git a/helpers/session.ts b/helpers/session.ts index f508a88..136a642 100644 --- a/helpers/session.ts +++ b/helpers/session.ts @@ -51,7 +51,6 @@ export enum AuthMode { enum SessionStatus { REDIRECT_LOGIN, REDIRECT_DASHBOARD, - FORBIDDEN, CONTINUE, } @@ -62,39 +61,25 @@ const addMemberInfo = async (session: IronSession) => { return { user, member }; }; -const handleRequest = async ( - user: User | undefined, - member: Member | undefined, - mode: AuthMode, - position: Position | undefined, -) => { +const handleRequest = async (user: User | undefined, mode: AuthMode) => { if (mode === AuthMode.GUEST && user) return SessionStatus.REDIRECT_DASHBOARD; if (mode === AuthMode.AUTHENTICATED && !user) return SessionStatus.REDIRECT_LOGIN; - - if (position) { - if (!member) return SessionStatus.FORBIDDEN; - if (member.position < position) return SessionStatus.FORBIDDEN; - } - return SessionStatus.CONTINUE; }; export const withSession = ( handler: (req: NextIronRequest, res: NextApiResponse) => void, - mode: AuthMode, - position?: Position, + mode: AuthMode ) => { const wrapped = async (req: NextIronRequest, res: NextApiResponse) => { req.session = await getIronSession(req, res, IRON_CONFIG); - const { user, member } = await addMemberInfo(req.session); - const status = await handleRequest(user, member, mode, position); + const { user } = await addMemberInfo(req.session); + const status = await handleRequest(user, mode); if (status === SessionStatus.REDIRECT_DASHBOARD) { return res.redirect("/dashboard"); } else if (status === SessionStatus.REDIRECT_LOGIN) { return res.redirect("/"); - } else if (status === SessionStatus.FORBIDDEN) { - return res.status(403).end(); } return handler(req, res); @@ -105,25 +90,23 @@ export const withSession = ( export const withServerSideSession = < T extends { [key: string]: any } = { [key: string]: any }, - Q extends ParsedUrlQuery = ParsedUrlQuery, + Q extends ParsedUrlQuery = ParsedUrlQuery >( handler: (ctx: NextIronGetServerSidePropsContext) => Promise>, mode: AuthMode, - position?: Position, + position?: Position ) => { const wrapped = async ( - ctx: NextIronGetServerSidePropsContext, + ctx: NextIronGetServerSidePropsContext ): Promise> => { ctx.req.session = await getIronSession(ctx.req, ctx.res, IRON_CONFIG); - const { user, member } = await addMemberInfo(ctx.req.session); - const status = await handleRequest(user, member, mode, position); + const { user } = await addMemberInfo(ctx.req.session); + const status = await handleRequest(user, mode); if (status === SessionStatus.REDIRECT_DASHBOARD) { return { redirect: { permanent: false, destination: "/dashboard" } }; } else if (status === SessionStatus.REDIRECT_LOGIN) { return { redirect: { permanent: false, destination: "/" } }; - } else if (status === SessionStatus.FORBIDDEN) { - return { redirect: { permanent: false, destination: "/dashboard" } }; } return handler(ctx); diff --git a/helpers/types.ts b/helpers/types.ts index 96bffd7..8eae96a 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -29,7 +29,6 @@ export type Member = { muted?: boolean; trading_muted?: boolean; roles?: string[]; - position: Position; }; export enum Position { diff --git a/pages/a/[formId]/submissions/[submissionId].tsx b/pages/a/[formId]/submissions/[submissionId].tsx index 8069895..eb8667b 100644 --- a/pages/a/[formId]/submissions/[submissionId].tsx +++ b/pages/a/[formId]/submissions/[submissionId].tsx @@ -33,6 +33,7 @@ import ErrorAlert from "~components/formium/ErrorAlert"; import SubmissionsLayout from "~components/layouts/SubmissionsLayout"; import { fetchSubmission, fetchSubmissions } from "~helpers/db"; import { formium } from "~helpers/formium"; +import { permittedToViewForm } from "~helpers/permissions"; import { AuthMode, withServerSideSession } from "~helpers/session"; import { Position, @@ -153,7 +154,7 @@ type SubmissionContentProps = { const SubmissionContent = ({ form, submission }: SubmissionContentProps) => { const fieldNames = Object.values(form.schema?.fields ?? {}).reduce( (acc, val) => acc.set(val.slug, val.title), - new Map(), + new Map() ); const ownedFields = [...fieldNames.keys()].filter((x) => submission.data.hasOwnProperty(x)); @@ -326,12 +327,17 @@ type SubmissionPageQuery = { export const getServerSideProps = withServerSideSession( async ({ req, params, query }) => { - const user = req.session.user; if (!params) throw new Error("No params found."); - if (!user) throw new Error("User not found"); - const { formId, submissionId } = params; + const user = req.session.user; + const member = req.session.member; + if (!user || !member) throw new Error("User not found"); + + if (!permittedToViewForm(member, formId)) { + return { redirect: { permanent: false, destination: "/dashboard" } }; + } + let form; try { form = await formium.getFormBySlug(formId); @@ -363,5 +369,5 @@ export const getServerSideProps = withServerSideSession { export default SubmissionsPage; -export const getServerSideProps = withServerSideSession( +type SubmissionsPageQuery = { + formId: string; +}; + +export const getServerSideProps = withServerSideSession( async ({ req, params, query }) => { - const id = params?.formId?.toString(); + if (!params) throw new Error("No params found."); + const { formId } = params; + const user = req.session.user; + const member = req.session.member; + if (!user || !member) throw new Error("User not found"); - if (!id) throw new Error("Form ID not found"); - if (!user) throw new Error("User not found"); + if (!permittedToViewForm(member, formId)) { + return { redirect: { permanent: false, destination: "/dashboard" } }; + } let form; try { - form = await formium.getFormBySlug(id); + form = await formium.getFormBySlug(formId); } catch (e) { const err = e as any; if (err.status === 404) return { notFound: true }; @@ -55,7 +65,7 @@ export const getServerSideProps = withServerSideSession( return { props: { - id, + id: formId, form, user, submissions: submissions.map(makeSerializable), @@ -63,5 +73,5 @@ export const getServerSideProps = withServerSideSession( }; }, AuthMode.AUTHENTICATED, - Position.COMMUNITY_MANAGER, + Position.COMMUNITY_MANAGER ); diff --git a/pages/api/forms/[formId]/submissions/[submissionId].ts b/pages/api/forms/[formId]/submissions/[submissionId].ts index adad026..878c8c9 100644 --- a/pages/api/forms/[formId]/submissions/[submissionId].ts +++ b/pages/api/forms/[formId]/submissions/[submissionId].ts @@ -5,7 +5,8 @@ import { NextApiResponse } from "next"; import { AuthMode, NextIronRequest, withSession } from "helpers/session"; import { fetchSubmission, updateSubmission } from "~helpers/db"; import { formium } from "~helpers/formium"; -import { Position, Submission, SubmissionStatus } from "~helpers/types"; +import { permittedToViewForm } from "~helpers/permissions"; +import { Submission, SubmissionStatus } from "~helpers/types"; sendgrid.setApiKey(process.env.SENDGRID_KEY as string); @@ -18,7 +19,7 @@ const sendEmail = async ( submission: Submission, formId: string, status: number, - comment: string, + comment: string ) => { if (!submission.email) return; if (!(status in EMAIL_UPDATES)) return; @@ -52,7 +53,10 @@ const handler = async (req: NextIronRequest, res: NextApiResponse) => { if (typeof req.body.status !== "number") return res.status(400).end(); const user = req.session.user; - if (!user) return res.status(401); + const member = req.session.member; + if (!user || !member) return res.status(401); + + if (!permittedToViewForm(member, formId)) return res.status(403).end(); const submission = await fetchSubmission(submissionId); if (!submission) return res.status(404); @@ -68,4 +72,4 @@ const handler = async (req: NextIronRequest, res: NextApiResponse) => { res.status(204).end(); }; -export default withSession(handler, AuthMode.AUTHENTICATED, Position.COMMUNITY_MANAGER); +export default withSession(handler, AuthMode.AUTHENTICATED);