diff --git a/middleware.js b/middleware.js index 175d6e2..d5c4dc2 100644 --- a/middleware.js +++ b/middleware.js @@ -1,6 +1,6 @@ // export { default } from 'next-auth/middleware'; // locks down whole site, only for JWT // https://github.com/nextauthjs/next-auth/discussions/4265 -// https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/next/middleware.ts#L99 +// https://github.com/nextauthjs/next-auth/blob/next-auth%404.22.1/packages/next-auth/src/next/middleware.ts import { getToken } from 'next-auth/jwt'; import { NextResponse } from 'next/server'; @@ -11,16 +11,22 @@ export async function middleware(req) { '/api/auth/signin/google', '/api/auth/callback/google', '/api/auth/error', + '/auth/signin', + '/login', ]; const publicPaths = ['/_next', '/favicon.ico']; const { pathname, basePath, origin, search } = req.nextUrl; - if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p))) { + if ( + signinPaths.includes(pathname) || + publicPaths.some((p) => pathname.startsWith(p)) || + pathname.startsWith('/api/auth/') + ) { return NextResponse.next(); } const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { - const signInPage = '/api/auth/signin'; + const signInPage = '/login'; const signInUrl = new URL(`${basePath}${signInPage}`, origin); signInUrl.searchParams.append('callbackUrl', `${basePath}${pathname}${search}`); return NextResponse.redirect(signInUrl); diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index e137375..5f35168 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -71,5 +71,8 @@ export const authOptions = { return token; }, }, + pages: { + signIn: '/login', + }, }; export default NextAuth(authOptions); diff --git a/pages/api/researcher/profile/edit.js b/pages/api/researcher/profile/edit.js index dde944f..1f447a2 100644 --- a/pages/api/researcher/profile/edit.js +++ b/pages/api/researcher/profile/edit.js @@ -1,5 +1,3 @@ -import { Prisma } from 'prisma/prisma-client'; - import { ResearcherProfileCreationValidator, isValidationError } from '@lib/validators'; import ApiRoute from '@lib/ApiRoute'; diff --git a/pages/api/student/profile/resumes/[profileId].js b/pages/api/student/profile/resumes/[profileId].js new file mode 100644 index 0000000..e7da89b --- /dev/null +++ b/pages/api/student/profile/resumes/[profileId].js @@ -0,0 +1,83 @@ +import ApiRoute from '@lib/ApiRoute'; + +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; + +// TODO: use edge route +class StudentResumeRoute extends ApiRoute { + /** + * application update endpoint + * @param {import('next').NextApiRequest & { session: import('next-auth').Session?}} req + * @param {import('next').NextApiResponse} res + * @param {import('prisma/prisma-client').PrismaClient} prisma + * @returns + */ + async get(req, res, prisma) { + try { + const profileId = req.query.profileId; + + if (profileId === undefined) { + return res.status(400).json({ message: 'Invalid request' }); + } + let resumeId = ''; + if (req.token.accountType === 'student') { + const result = await prisma.student.findUnique({ + where: { + email: req.session.user.email, + }, + select: { + studentProfile: { select: { id: true } }, + }, + }); + const viewerProfileId = result?.studentProfile?.id; + if (viewerProfileId === null || viewerProfileId === undefined) { + return res.status(404).json({ message: `You do not have a profile` }); + } + if (viewerProfileId !== profileId) { + return res.status(403).json({ message: `You cannot view other students' résumés.` }); + } + resumeId = req.session.user.email; + } else if (req.token.accountType === 'researcher') { + const appliedJob = await prisma.labeledJob.findFirst({ + where: { + status: 'APPLIED', + job: { poster: { email: req.session.user.email } }, + applicant: { studentProfile: { id: profileId } }, + }, + select: { applicantEmail: true }, + }); + if (appliedJob === null) { + return res.status(403).json({ + message: `You cannot view this résumé because this student has not applied to one of your posts.`, + }); + } + resumeId = appliedJob.applicantEmail; + } else { + return res.status(403); + } + const client = new S3Client({ + region: process.env.RESUMES_BUCKET_REGION, + credentials: { + accessKeyId: process.env.ACCESS_KEY_ID, + secretAccessKey: process.env.SECRET_ACCESS_KEY, + }, + }); + const command = new GetObjectCommand({ + Bucket: process.env.RESUMES_BUCKET, + Key: `${resumeId}.pdf`, + ContentType: 'application/pdf', + }); + const url = await getSignedUrl(client, command, { expiresIn: 3600 }); + + return res.status(200).json({ url }); + } catch (e) { + // check for Node.js errors (data integrity, etc) + console.error(e); + res.status(500).json({ message: 'something went wrong' }); + } finally { + res.end(); + } + } +} + +export default new StudentResumeRoute().as_handler(); diff --git a/pages/auth/error.jsx b/pages/auth/error.jsx new file mode 100644 index 0000000..ea5662c --- /dev/null +++ b/pages/auth/error.jsx @@ -0,0 +1,88 @@ + +import { signIn } from 'next-auth/react'; +import Google from 'next-auth/providers/google'; + +function LoginPage() { + + return ( +
UCLA Email required
+