From 7964d7d3d6ba6d144e20e9d843ef0822be277c53 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 15:49:35 -0400 Subject: [PATCH 01/49] add loginCheckedPut --- backend/src/api.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/api.ts b/backend/src/api.ts index 6f5c8e8ed..5f886bfdf 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -177,6 +177,11 @@ const loginCheckedDelete = ( handler: (req: Request, user: IdolMember) => Promise> ) => router.delete(path, loginCheckedHandler(handler)); +const loginCheckedPut = ( + path: string, + handler: (req: Request, user: IdolMember) => Promise> +) => router.put(path, loginCheckedHandler(handler)); + // Members router.get('/allMembers', async (_, res) => { const members = await allMembers(); From f9431b3610d3e9999df027c062b6a36080efa359 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 15:56:24 -0400 Subject: [PATCH 02/49] update member endpoint paths --- backend/src/api.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 5f886bfdf..6896dad47 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -183,17 +183,21 @@ const loginCheckedPut = ( ) => router.put(path, loginCheckedHandler(handler)); // Members -router.get('/allMembers', async (_, res) => { - const members = await allMembers(); - res.status(200).json({ members }); -}); -router.get('/allApprovedMembers', async (_, res) => { - const members = await allApprovedMembers(); +router.get('/member', async (req, res) => { + const type = req.query.type as string | undefined; + let members; + switch (type) { + case 'all-semesters': + members = await MembersDao.getMembersFromAllSemesters(); + break; + case 'approved': + members = await allApprovedMembers(); + break; + default: + members = await allMembers(); + } res.status(200).json({ members }); }); -router.get('/membersFromAllSemesters', async (_, res) => { - res.status(200).json(await MembersDao.getMembersFromAllSemesters()); -}); router.get('/hasIDOLAccess/:email', async (req, res) => { const member = await getMember(req.params.email); const adminEmails = await AdminsDao.getAllAdminEmails(); @@ -206,21 +210,21 @@ router.get('/hasIDOLAccess/:email', async (req, res) => { }); }); -loginCheckedPost('/setMember', async (req, user) => ({ +loginCheckedPost('/member', async (req, user) => ({ member: await setMember(req.body, user) })); -loginCheckedDelete('/deleteMember/:email', async (req, user) => { +loginCheckedDelete('/member/:email', async (req, user) => { await deleteMember(req.params.email, user); return {}; }); -loginCheckedPost('/updateMember', async (req, user) => ({ +loginCheckedPut('/updateMember', async (req, user) => ({ member: await updateMember(req, req.body, user) })); loginCheckedGet('/memberDiffs', async (_, user) => ({ diffs: await getUserInformationDifference(user) })); -loginCheckedPost('/reviewMemberDiffs', async (req, user) => ({ +loginCheckedPut('/memberDiffs', async (req, user) => ({ member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) })); From b14dae898ff4d2270ce02cb3ecc135c368794836 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 22:24:43 -0400 Subject: [PATCH 03/49] update team endpoints --- backend/src/api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 6896dad47..087c4fc7f 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -229,11 +229,11 @@ loginCheckedPut('/memberDiffs', async (req, user) => ({ })); // Teams -loginCheckedGet('/allTeams', async () => ({ teams: await allTeams() })); -loginCheckedPost('/setTeam', async (req, user) => ({ +loginCheckedGet('/team', async () => ({ teams: await allTeams() })); +loginCheckedPut('/team', async (req, user) => ({ team: await setTeam(req.body, user) })); -loginCheckedPost('/deleteTeam', async (req, user) => ({ +loginCheckedDelete('/team/:uuid', async (req, user) => ({ team: await deleteTeam(req.body, user) })); From a0ce03e2fac855eceeefcba7815163c7eb075aac Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 22:26:43 -0400 Subject: [PATCH 04/49] update member image endpoints --- backend/src/api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 087c4fc7f..2776dd036 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -238,13 +238,13 @@ loginCheckedDelete('/team/:uuid', async (req, user) => ({ })); // Images -loginCheckedGet('/getMemberImage', async (_, user) => ({ +loginCheckedGet('/memberImage/:email', async (_, user) => ({ url: await getMemberImage(user) })); -loginCheckedGet('/getImageSignedURL', async (_, user) => ({ +loginCheckedGet('/memberImage/signedURL', async (_, user) => ({ url: await setMemberImage(user) })); -router.get('/allMemberImages', async (_, res) => { +router.get('/memberImage', async (_, res) => { const images = await allMemberImages(); res.status(200).json({ images }); }); From 65b144f5e30bfffb1cfad13c42a6f60135eb3b99 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 22:51:38 -0400 Subject: [PATCH 05/49] change frontend endpoints --- backend/src/api.ts | 3 ++- frontend/src/API/ImagesAPI.ts | 10 +++++++--- frontend/src/API/MembersAPI.ts | 8 ++++---- frontend/src/API/TeamsAPI.ts | 4 ++-- .../src/components/Admin/MemberReview/MemberReview.tsx | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 2776dd036..96260b584 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -233,7 +233,8 @@ loginCheckedGet('/team', async () => ({ teams: await allTeams() })); loginCheckedPut('/team', async (req, user) => ({ team: await setTeam(req.body, user) })); -loginCheckedDelete('/team/:uuid', async (req, user) => ({ +// TODO: should eventually make this a delete request +loginCheckedPost('/team', async (req, user) => ({ team: await deleteTeam(req.body, user) })); diff --git a/frontend/src/API/ImagesAPI.ts b/frontend/src/API/ImagesAPI.ts index dd0d06bb0..1dbceea24 100644 --- a/frontend/src/API/ImagesAPI.ts +++ b/frontend/src/API/ImagesAPI.ts @@ -4,8 +4,10 @@ import HeadshotPlaceholder from '../static/images/headshot-placeholder.png'; export default class ImagesAPI { // member images - public static getMemberImage(): Promise { - const responseProm = APIWrapper.get(`${backendURL}/getMemberImage`).then((res) => res.data); + public static getMemberImage(email: string): Promise { + const responseProm = APIWrapper.get(`${backendURL}/memberImage/${email}`).then( + (res) => res.data + ); return responseProm.then((val) => { if (val.error) { @@ -16,7 +18,9 @@ export default class ImagesAPI { } private static getSignedURL(): Promise { - const responseProm = APIWrapper.get(`${backendURL}/getImageSignedURL`).then((res) => res.data); + const responseProm = APIWrapper.get(`${backendURL}/memberImage/signedURL`).then( + (res) => res.data + ); return responseProm.then((val) => val.url); } diff --git a/frontend/src/API/MembersAPI.ts b/frontend/src/API/MembersAPI.ts index 8e1722e47..a28229ea2 100644 --- a/frontend/src/API/MembersAPI.ts +++ b/frontend/src/API/MembersAPI.ts @@ -10,19 +10,19 @@ export type Member = IdolMember; export class MembersAPI { public static async getMembersFromAllSemesters(): Promise> { - return APIWrapper.get(`${backendURL}/membersFromAllSemesters`).then((res) => res.data); + return APIWrapper.get(`${backendURL}/member?type=all-semesters`).then((res) => res.data); } public static setMember(member: Member): Promise { - return APIWrapper.post(`${backendURL}/setMember`, member).then((res) => res.data); + return APIWrapper.post(`${backendURL}/member`, member).then((res) => res.data); } public static deleteMember(memberEmail: string): Promise<{ status: number; error?: string }> { - return APIWrapper.delete(`${backendURL}/deleteMember/${memberEmail}`).then((res) => res.data); + return APIWrapper.delete(`${backendURL}/member/${memberEmail}`).then((res) => res.data); } public static updateMember(member: Member): Promise { - return APIWrapper.post(`${backendURL}/updateMember`, member).then((res) => res.data); + return APIWrapper.put(`${backendURL}/member`, member).then((res) => res.data); } public static hasIDOLAccess(email: string): Promise { diff --git a/frontend/src/API/TeamsAPI.ts b/frontend/src/API/TeamsAPI.ts index 702da197f..532b86afc 100644 --- a/frontend/src/API/TeamsAPI.ts +++ b/frontend/src/API/TeamsAPI.ts @@ -17,10 +17,10 @@ export type Team = { export class TeamsAPI { public static setTeam(team: Team): Promise { - return APIWrapper.post(`${backendURL}/setTeam`, team).then((res) => res.data); + return APIWrapper.put(`${backendURL}/team`, team).then((res) => res.data); } public static deleteTeam(team: Team): Promise { - return APIWrapper.post(`${backendURL}/deleteTeam`, team).then((res) => res.data); + return APIWrapper.post(`${backendURL}/team`, team).then((res) => res.data); } } diff --git a/frontend/src/components/Admin/MemberReview/MemberReview.tsx b/frontend/src/components/Admin/MemberReview/MemberReview.tsx index b654dce00..ad797004b 100644 --- a/frontend/src/components/Admin/MemberReview/MemberReview.tsx +++ b/frontend/src/components/Admin/MemberReview/MemberReview.tsx @@ -65,7 +65,7 @@ const MemberReview: React.FC = () => { .map((it) => it.email); const sendReviewRequest = async () => { - await APIWrapper.post(`${backendURL}/reviewMemberDiffs`, { + await APIWrapper.put(`${backendURL}/memberDiffs`, { approved: approvedEmails, rejected: rejectedEmails }); From 6b445d166f0e099792b31c9a2c2cc70add981015 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 22:57:45 -0400 Subject: [PATCH 06/49] add missing func param --- .../components/Forms/UserProfile/UserProfileImage.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx index 683d1ddc9..4b9ceccb8 100644 --- a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx +++ b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx @@ -3,6 +3,7 @@ import { Card, Image, Button, Modal } from 'semantic-ui-react'; import AvatarEditor from 'react-avatar-editor'; import ProfileImageEditor from './ProfileImageEditor'; import ImagesAPI from '../../../API/ImagesAPI'; +import { useSelf } from '../../Common/FirestoreDataProvider'; const UserProfileImage: React.FC = () => { const [open, setOpen] = React.useState(false); @@ -11,14 +12,18 @@ const UserProfileImage: React.FC = () => { const [editor, setEditor] = useState(null); const setEditorRef = (editor: AvatarEditor) => setEditor(editor); + // When the user is logged in, `useSelf` always return non-null data. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const userInfo = useSelf()!; + useEffect(() => { if (process.env.NODE_ENV === 'test') { return; } - ImagesAPI.getMemberImage().then((url: string) => { + ImagesAPI.getMemberImage(userInfo.email).then((url: string) => { setProfilePhoto(url); }); - }, []); + }, [userInfo.email]); const cropAndSubmitImage = () => { if (editor !== null) { From ce8cc81ae43844898216e9b78cb6ff8a46ecf482 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 13 May 2023 23:05:43 -0400 Subject: [PATCH 07/49] same issue --- .../src/components/Forms/UserProfile/UserProfileImage.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx index 4b9ceccb8..6682fd32a 100644 --- a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx +++ b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx @@ -12,18 +12,16 @@ const UserProfileImage: React.FC = () => { const [editor, setEditor] = useState(null); const setEditorRef = (editor: AvatarEditor) => setEditor(editor); - // When the user is logged in, `useSelf` always return non-null data. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const userInfo = useSelf()!; + const userInfo = useSelf(); useEffect(() => { if (process.env.NODE_ENV === 'test') { return; } - ImagesAPI.getMemberImage(userInfo.email).then((url: string) => { + ImagesAPI.getMemberImage(userInfo ? userInfo.email : '').then((url: string) => { setProfilePhoto(url); }); - }, [userInfo.email]); + }, [userInfo]); const cropAndSubmitImage = () => { if (editor !== null) { From 9e004be44515dacb2866c6d8dc9a0059c87a40e5 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sun, 14 May 2023 10:53:10 -0400 Subject: [PATCH 08/49] update shoutouts endpoints --- backend/src/api.ts | 14 +++++++------- frontend/src/API/ShoutoutsAPI.ts | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index 96260b584..a3bcc1c48 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -217,7 +217,7 @@ loginCheckedDelete('/member/:email', async (req, user) => { await deleteMember(req.params.email, user); return {}; }); -loginCheckedPut('/updateMember', async (req, user) => ({ +loginCheckedPut('/member', async (req, user) => ({ member: await updateMember(req, req.body, user) })); @@ -251,25 +251,25 @@ router.get('/memberImage', async (_, res) => { }); // Shoutouts -loginCheckedGet('/getShoutouts/:email/:type', async (req, user) => ({ +loginCheckedGet('/shoutout/:email/:type', async (req, user) => ({ shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) })); -loginCheckedGet('/allShoutouts', async () => ({ +loginCheckedGet('/shoutout', async () => ({ shoutouts: await getAllShoutouts() })); -loginCheckedPost('/giveShoutout', async (req, user) => ({ +loginCheckedPost('/shoutout', async (req, user) => ({ shoutout: await giveShoutout(req.body, user) })); -loginCheckedPost('/hideShoutout', async (req, user) => { +loginCheckedPut('/shoutout', async (req, user) => { await hideShoutout(req.body.uuid, req.body.hide, user); return {}; }); -loginCheckedPost('/deleteShoutout', async (req, user) => { - await deleteShoutout(req.body.uuid, user); +loginCheckedDelete('/shoutout/:uuid', async (req, user) => { + await deleteShoutout(req.params.uuid, user); return {}; }); diff --git a/frontend/src/API/ShoutoutsAPI.ts b/frontend/src/API/ShoutoutsAPI.ts index 401272df1..e0d15d8b6 100644 --- a/frontend/src/API/ShoutoutsAPI.ts +++ b/frontend/src/API/ShoutoutsAPI.ts @@ -9,7 +9,7 @@ type ShoutoutResponseObj = { export default class ShoutoutsAPI { public static getAllShoutouts(): Promise { - const responseProm = APIWrapper.get(`${backendURL}/allShoutouts`).then((res) => res.data); + const responseProm = APIWrapper.get(`${backendURL}/shoutout`).then((res) => res.data); return responseProm.then((val) => { if (val.error) { Emitters.generalError.emit({ @@ -24,7 +24,7 @@ export default class ShoutoutsAPI { } public static getShoutouts(email: string, type: 'given' | 'received'): Promise { - const responseProm = APIWrapper.get(`${backendURL}/getShoutouts/${email}/${type}`).then( + const responseProm = APIWrapper.get(`${backendURL}/shoutout/${email}/${type}`).then( (res) => res.data ); return responseProm.then((val) => { @@ -41,14 +41,14 @@ export default class ShoutoutsAPI { } public static giveShoutout(shoutout: Shoutout): Promise { - return APIWrapper.post(`${backendURL}/giveShoutout`, shoutout).then((res) => res.data); + return APIWrapper.post(`${backendURL}/shoutout`, shoutout).then((res) => res.data); } public static hideShoutout(uuid: string, hide: boolean): Promise { - return APIWrapper.post(`${backendURL}/hideShoutout`, { uuid, hide }).then((res) => res.data); + return APIWrapper.put(`${backendURL}/shoutout`, { uuid, hide }).then((res) => res.data); } public static async deleteShoutout(uuid: string): Promise { - await APIWrapper.post(`${backendURL}/deleteShoutout`, { uuid }); + await APIWrapper.delete(`${backendURL}/deleteShoutout/${uuid}`); } } From 3166e0f0455262d11086bffda279833991d8a7d6 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sun, 14 May 2023 10:54:06 -0400 Subject: [PATCH 09/49] remove /isAdmin endpoint --- backend/src/api.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index a3bcc1c48..6bf89db2c 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -273,11 +273,6 @@ loginCheckedDelete('/shoutout/:uuid', async (req, user) => { return {}; }); -// Permissions -loginCheckedGet('/isAdmin', async (_, user) => ({ - isAdmin: await PermissionsManager.isAdmin(user) -})); - // Pull from IDOL loginCheckedPost('/pullIDOLChanges', (_, user) => requestIDOLPullDispatch(user)); loginCheckedGet('/getIDOLChangesPR', (_, user) => getIDOLChangesPR(user)); From 2dc4c4d0a592d4dcf0e3055c15111ec1affbb7fe Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 12:35:41 -0400 Subject: [PATCH 10/49] update team event endpoints --- backend/src/API/teamEventsAPI.ts | 7 +++-- backend/src/api.ts | 27 +++++++++---------- frontend/src/API/TeamEventsAPI.ts | 24 ++++++++--------- .../TeamEventCreditsForm.tsx | 2 +- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/backend/src/API/teamEventsAPI.ts b/backend/src/API/teamEventsAPI.ts index b81baae7b..f3075ea6e 100644 --- a/backend/src/API/teamEventsAPI.ts +++ b/backend/src/API/teamEventsAPI.ts @@ -36,11 +36,14 @@ export const createTeamEvent = async ( return teamEventInfo; }; -export const deleteTeamEvent = async (teamEvent: TeamEvent, user: IdolMember): Promise => { +export const deleteTeamEvent = async (uuid: string, user: IdolMember): Promise => { if (!PermissionsManager.canEditTeamEvent(user)) { throw new PermissionError("You don't have permission to delete a team event!"); } - const allAttendances = teamEvent.attendees.concat(teamEvent.requests); + const teamEvent = await TeamEventsDao.getTeamEvent(uuid); // TODO: need to make a dao method for getting full team event + if (!teamEvent) return; + + const allAttendances = await teamEventAttendanceDao.getTeamEventAttendanceByEventId(uuid); await Promise.all( allAttendances.map((attendance) => diff --git a/backend/src/api.ts b/backend/src/api.ts index 6bf89db2c..f89709ca0 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -300,40 +300,39 @@ loginCheckedGet('/signInPrompt/:id', async (req, _) => ({ })); // Team Events -loginCheckedPost('/createTeamEvent', async (req, user) => { +loginCheckedPost('/team-event', async (req, user) => { await createTeamEvent(req.body, user); return {}; }); -loginCheckedGet('/getTeamEvent/:uuid', async (req, user) => ({ +loginCheckedGet('/team-event/:uuid', async (req, user) => ({ event: await getTeamEvent(req.params.uuid, user) })); -loginCheckedGet('/getAllTeamEvents', async (_, user) => ({ events: await getAllTeamEvents(user) })); -loginCheckedPost('/updateTeamEvent', async (req, user) => ({ +loginCheckedGet('/team-event', async (req, user) => ({ + events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() +})); +loginCheckedPut('/team-event', async (req, user) => ({ event: await updateTeamEvent(req.body, user) })); -loginCheckedPost('/deleteTeamEvent', async (req, user) => { +loginCheckedDelete('/team-event/:uuid', async (req, user) => { await deleteTeamEvent(req.body, user); return {}; }); -loginCheckedDelete('/clearAllTeamEvents', async (_, user) => { +loginCheckedDelete('/team-event', async (_, user) => { await clearAllTeamEvents(user); return {}; }); -loginCheckedGet('/getAllTeamEventInfo', async () => ({ - allTeamEventInfo: await getAllTeamEventInfo() -})); -loginCheckedPost('/requestTeamEventCredit', async (req, user) => { +loginCheckedPost('/team-event/attendance', async (req, user) => { await requestTeamEventCredit(req.body.request, user); return {}; }); -loginCheckedGet('/getTeamEventAttendanceByUser', async (_, user) => ({ +loginCheckedGet('/team-event/attendance/:email', async (_, user) => ({ teamEventAttendance: await getTeamEventAttendanceByUser(user) })); -loginCheckedPost('/updateTeamEventAttendance', async (req, user) => ({ +loginCheckedPut('/team-event/attendance', async (req, user) => ({ teamEventAttendance: await updateTeamEventAttendance(req.body, user) })); -loginCheckedPost('/deleteTeamEventAttendance', async (req, user) => { - await deleteTeamEventAttendance(req.body.uuid, user); +loginCheckedDelete('/team-event/attendance/:uuid', async (req, user) => { + await deleteTeamEventAttendance(req.params.uuid, user); return {}; }); diff --git a/frontend/src/API/TeamEventsAPI.ts b/frontend/src/API/TeamEventsAPI.ts index 01f552a24..cf4f5edc1 100644 --- a/frontend/src/API/TeamEventsAPI.ts +++ b/frontend/src/API/TeamEventsAPI.ts @@ -18,7 +18,7 @@ export type MemberTECRequests = { export class TeamEventsAPI { public static getAllTeamEvents(): Promise { - const eventsProm = APIWrapper.get(`${backendURL}/getAllTeamEvents`).then((res) => res.data); + const eventsProm = APIWrapper.get(`${backendURL}/team-event`).then((res) => res.data); return eventsProm.then((val) => { if (val.error) { Emitters.generalError.emit({ @@ -33,7 +33,7 @@ export class TeamEventsAPI { } public static getAllTeamEventInfo(): Promise { - const res = APIWrapper.get(`${backendURL}/getAllTeamEventInfo`).then((res) => res.data); + const res = APIWrapper.get(`${backendURL}/team-event?meta_only=true`).then((res) => res.data); return res.then((val) => { if (val.error) { Emitters.generalError.emit({ @@ -48,7 +48,7 @@ export class TeamEventsAPI { } public static getTeamEventForm(uuid: string): Promise { - const eventProm = APIWrapper.get(`${backendURL}/getTeamEvent/${uuid}`).then((res) => res.data); + const eventProm = APIWrapper.get(`${backendURL}/team-event/${uuid}`).then((res) => res.data); return eventProm.then((val) => { const event = val.event as Event; return event; @@ -56,41 +56,41 @@ export class TeamEventsAPI { } public static createTeamEventForm(teamEventInfo: TeamEventInfo): Promise { - return APIWrapper.post(`${backendURL}/createTeamEvent`, teamEventInfo).then((res) => res.data); + return APIWrapper.post(`${backendURL}/team-event`, teamEventInfo).then((res) => res.data); } public static async deleteTeamEventForm(teamEvent: Event): Promise { - await APIWrapper.post(`${backendURL}/deleteTeamEvent`, teamEvent); + await APIWrapper.delete(`${backendURL}/team-event/${teamEvent.uuid}`); } public static updateTeamEventForm(teamEventInfo: TeamEventInfo): Promise { - return APIWrapper.post(`${backendURL}/updateTeamEvent`, teamEventInfo).then( + return APIWrapper.put(`${backendURL}/team-event`, teamEventInfo).then( (rest) => rest.data.event ); } public static async clearAllTeamEvents(): Promise { - await APIWrapper.delete(`${backendURL}/clearAllTeamEvents`); + await APIWrapper.delete(`${backendURL}/team-event`); } public static async requestTeamEventCredit(request: TeamEventAttendance): Promise { - APIWrapper.post(`${backendURL}/requestTeamEventCredit`, { request }); + APIWrapper.post(`${backendURL}/team-event/attendance`, { request }); } public static async deleteTeamEventAttendance(uuid: string): Promise { - await APIWrapper.post(`${backendURL}/deleteTeamEventAttendance`, { uuid }); + await APIWrapper.post(`${backendURL}/team-event/attendance/${uuid}`, { uuid }); } public static async updateTeamEventAttendance( teamEventAttendance: TeamEventAttendance ): Promise { - return APIWrapper.post(`${backendURL}/updateTeamEventAttendance`, teamEventAttendance).then( + return APIWrapper.put(`${backendURL}/team-event/attendance`, teamEventAttendance).then( (res) => res.data ); } - public static async getTeamEventAttendanceByUser(): Promise { - const res = APIWrapper.get(`${backendURL}/getTeamEventAttendanceByUser`).then( + public static async getTeamEventAttendanceByUser(email: string): Promise { + const res = APIWrapper.get(`${backendURL}/team-event/attendance/${email}`).then( (res) => res.data ); return res.then((val) => { diff --git a/frontend/src/components/Forms/TeamEventCreditsForm/TeamEventCreditsForm.tsx b/frontend/src/components/Forms/TeamEventCreditsForm/TeamEventCreditsForm.tsx index 35c6262b0..c1512af77 100644 --- a/frontend/src/components/Forms/TeamEventCreditsForm/TeamEventCreditsForm.tsx +++ b/frontend/src/components/Forms/TeamEventCreditsForm/TeamEventCreditsForm.tsx @@ -21,7 +21,7 @@ const TeamEventCreditForm: React.FC = () => { useEffect(() => { TeamEventsAPI.getAllTeamEventInfo().then((teamEvents) => setTeamEventInfoList(teamEvents)); - TeamEventsAPI.getTeamEventAttendanceByUser().then((attendance) => { + TeamEventsAPI.getTeamEventAttendanceByUser(userInfo.email).then((attendance) => { setApprovedAttendance(attendance.filter((attendee) => attendee.pending === false)); setPendingAttendance(attendance.filter((attendee) => attendee.pending === true)); setIsAttendanceLoading(false); From 528b40f1bd8c8af492f9024b3b0bd87a8632ea33 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 14:40:12 -0400 Subject: [PATCH 11/49] update dev portfolio endpoints --- backend/src/api.ts | 34 ++++++++++++++--------------- frontend/src/API/DevPortfolioAPI.ts | 25 ++++++++++----------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index f89709ca0..dee5092f9 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -375,38 +375,36 @@ loginCheckedPost('/sendMail', async (req, user) => ({ })); // Dev Portfolios -loginCheckedGet('/getAllDevPortfolios', async (req, user) => ({ - portfolios: await getAllDevPortfolios(user) +loginCheckedGet('/dev-portfolio', async (req, user) => ({ + portfolios: !req.query.meta_only + ? await getAllDevPortfolios(user) + : await getAllDevPortfolioInfo() })); -loginCheckedGet('/getAllDevPortfolioInfo', async (req, user) => ({ - portfolioInfo: await getAllDevPortfolioInfo() +loginCheckedGet('/dev-portfolio/:uuid', async (req, user) => ({ + portfolioInfo: !req.query.meta_only + ? await getDevPortfolio(req.params.uuid, user) + : await getDevPortfolioInfo(req.params.uuid) })); -loginCheckedGet('/getDevPortfolioInfo/:uuid', async (req, user) => ({ - portfolioInfo: await getDevPortfolioInfo(req.params.uuid) -})); -loginCheckedGet('/getUsersDevPortfolioSubmissions/:uuid', async (req, user) => ({ +loginCheckedGet('/dev-portfolio/:uuid/submission/:email', async (req, user) => ({ submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) })); -loginCheckedGet('/getDevPortfolio/:uuid', async (req, user) => ({ - portfolio: await getDevPortfolio(req.params.uuid, user) -})); -loginCheckedPost('/createNewDevPortfolio', async (req, user) => ({ +loginCheckedPost('/dev-portfolio', async (req, user) => ({ portfolio: await createNewDevPortfolio(req.body, user) })); -loginCheckedPost('/deleteDevPortfolio', async (req, user) => - deleteDevPortfolio(req.body.uuid, user).then(() => ({})) +loginCheckedDelete('/dev-portfolio/:uuid', async (req, user) => + deleteDevPortfolio(req.params.uuid, user).then(() => ({})) ); -loginCheckedPost('/makeDevPortfolioSubmission', async (req, user) => { +loginCheckedPost('/dev-portfolio/submission', async (req, user) => { await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); return { submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) }; }); -loginCheckedPost('/regradeDevPortfolioSubmissions', async (req, user) => ({ +loginCheckedPut('/dev-portfolio/submission', async (req, user) => ({ portfolio: await regradeSubmissions(req.body.uuid, user) })); -loginCheckedPost('/updateDevPortfolioSubmissions', async (req, user) => ({ - portfolio: await updateSubmissions(req.body.uuid, req.body.updatedSubmissions, user) +loginCheckedPut('/dev-portfolio/submission/:uuid', async (req, user) => ({ + portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) })); app.use('/.netlify/functions/api', router); diff --git a/frontend/src/API/DevPortfolioAPI.ts b/frontend/src/API/DevPortfolioAPI.ts index d3be97e4d..e1ca1d5a2 100644 --- a/frontend/src/API/DevPortfolioAPI.ts +++ b/frontend/src/API/DevPortfolioAPI.ts @@ -7,17 +7,17 @@ type DevPortfolioSubmissionResponseObj = { export default class DevPortfolioAPI { static async getAllDevPortfolios(): Promise { - const response = APIWrapper.get(`${backendURL}/getAllDevPortfolios`); + const response = APIWrapper.get(`${backendURL}/dev-portfolio`); return response.then((val) => val.data.portfolios); } static async getAllDevPortfolioInfo(): Promise { - const response = APIWrapper.get(`${backendURL}/getAllDevPortfolioInfo`); + const response = APIWrapper.get(`${backendURL}/dev-portfolio?meta_only=true`); return response.then((val) => val.data.portfolioInfo); } public static async createDevPortfolio(devPortfolio: DevPortfolio): Promise { - return APIWrapper.post(`${backendURL}/createNewDevPortfolio`, devPortfolio).then( + return APIWrapper.post(`${backendURL}/dev-portfolio`, devPortfolio).then( (res) => res.data.portfolio ); } @@ -26,38 +26,37 @@ export default class DevPortfolioAPI { uuid: string, submission: DevPortfolioSubmission ): Promise { - return APIWrapper.post(`${backendURL}/makeDevPortfolioSubmission`, { + return APIWrapper.post(`${backendURL}/dev-portfolio/submission`, { uuid, submission }).then((res) => res.data); } public static async deleteDevPortfolio(uuid: string): Promise { - APIWrapper.post(`${backendURL}/deleteDevPortfolio`, { uuid }); + APIWrapper.delete(`${backendURL}/deleteDevPortfolio/${uuid}`); } public static async getDevPortfolio(uuid: string): Promise { - return APIWrapper.get(`${backendURL}/getDevPortfolio/${uuid}`).then( - (res) => res.data.portfolio - ); + return APIWrapper.get(`${backendURL}/dev-portfolio/${uuid}`).then((res) => res.data.portfolio); } public static async getDevPortfolioInfo(uuid: string): Promise { - return APIWrapper.get(`${backendURL}/getDevPortfolioInfo/${uuid}`).then( + return APIWrapper.get(`${backendURL}/dev-portfolio/${uuid}?meta_only=true`).then( (res) => res.data.portfolioInfo ); } public static async getUsersDevPortfolioSubmissions( - uuid: string + uuid: string, + email: string ): Promise { - return APIWrapper.get(`${backendURL}/getUsersDevPortfolioSubmissions/${uuid}`).then( + return APIWrapper.get(`${backendURL}/dev-portfolio/${uuid}/submission/${email}`).then( (res) => res.data.submissions ); } public static async regradeSubmissions(uuid: string): Promise { - return APIWrapper.post(`${backendURL}/regradeDevPortfolioSubmissions`, { uuid }).then( + return APIWrapper.put(`${backendURL}/dev-portfolio/submission`, { uuid }).then( (res) => res.data.portfolio ); } @@ -66,7 +65,7 @@ export default class DevPortfolioAPI { uuid: string, updatedSubmissions: DevPortfolioSubmission[] ): Promise { - return APIWrapper.post(`${backendURL}/updateDevPortfolioSubmissions`, { + return APIWrapper.post(`${backendURL}/dev-portfolio/submission/${uuid}`, { uuid, updatedSubmissions }).then((res) => res.data.portfolio); From 4ae2224ca1fc403341dbe19cba4bfa1c9e40588f Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 14:45:01 -0400 Subject: [PATCH 12/49] update candidate decider endpoints --- backend/src/api.ts | 24 +++++++++++++----------- frontend/src/API/CandidateDeciderAPI.ts | 14 +++++++------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/backend/src/api.ts b/backend/src/api.ts index dee5092f9..2469a3037 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -349,26 +349,28 @@ loginCheckedPost('/deleteEventProofImage', async (req, user) => { }); // Candidate Decider -loginCheckedGet('/getAllCandidateDeciderInstances', async (_, user) => ({ +loginCheckedGet('/candidate-decider', async (_, user) => ({ instances: await getAllCandidateDeciderInstances(user) })); -loginCheckedGet('/getCandidateDeciderInstance/:uuid', async (req, user) => ({ +loginCheckedGet('/candidate-decider/:uuid', async (req, user) => ({ instance: await getCandidateDeciderInstance(req.params.uuid, user) })); -loginCheckedPost('/createNewCandidateDeciderInstance', async (req, user) => ({ +loginCheckedPost('/candider-decider', async (req, user) => ({ instance: await createNewCandidateDeciderInstance(req.body, user) })); -loginCheckedPost('/toggleCandidateDeciderInstance', async (req, user) => - toggleCandidateDeciderInstance(req.body.uuid, user).then(() => ({})) +loginCheckedPut('/candidate-decider/:uuid', async (req, user) => + toggleCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) ); -loginCheckedPost('/deleteCandidateDeciderInstance', async (req, user) => - deleteCandidateDeciderInstance(req.body.uuid, user).then(() => ({})) +loginCheckedDelete('/candidate-decider/:uuid', async (req, user) => + deleteCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) ); -loginCheckedPost('/updateCandidateDeciderRating', (req, user) => - updateCandidateDeciderRating(user, req.body.uuid, req.body.id, req.body.rating).then(() => ({})) +loginCheckedPut('/candidate-decider/:uuid/rating', (req, user) => + updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) ); -loginCheckedPost('/updateCandidateDeciderComment', (req, user) => - updateCandidateDeciderComment(user, req.body.uuid, req.body.id, req.body.comment).then(() => ({})) +loginCheckedPost('/candidate-decider/:uuid/comment', (req, user) => + updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( + () => ({}) + ) ); loginCheckedPost('/sendMail', async (req, user) => ({ info: await sendMail(req.body.to, req.body.subject, req.body.text) diff --git a/frontend/src/API/CandidateDeciderAPI.ts b/frontend/src/API/CandidateDeciderAPI.ts index 0083aff55..02fb3851f 100644 --- a/frontend/src/API/CandidateDeciderAPI.ts +++ b/frontend/src/API/CandidateDeciderAPI.ts @@ -3,35 +3,35 @@ import { backendURL } from '../environment'; export default class CandidateDeciderAPI { static async getAllInstances(): Promise { - const response = APIWrapper.get(`${backendURL}/getAllCandidateDeciderInstances`); + const response = APIWrapper.get(`${backendURL}/candidate-decider`); return response.then((val) => val.data.instances); } static async getInstance(uuid: string): Promise { - const response = APIWrapper.get(`${backendURL}/getCandidateDeciderInstance/${uuid}`); + const response = APIWrapper.get(`${backendURL}/candidate-decider/${uuid}`); return response.then((val) => val.data.instance); } static async createNewInstance( instance: CandidateDeciderInstance ): Promise { - const response = APIWrapper.post(`${backendURL}/createNewCandidateDeciderInstance`, instance); + const response = APIWrapper.post(`${backendURL}/candidate-decider`, instance); return response.then((val) => val.data.instance); } static async toggleInstance(uuid: string): Promise { - APIWrapper.post(`${backendURL}/toggleCandidateDeciderInstance`, { uuid }); + APIWrapper.put(`${backendURL}/candidate-decider/${uuid}`, {}); } static async deleteInstance(uuid: string): Promise { - APIWrapper.post(`${backendURL}/deleteCandidateDeciderInstance`, { uuid }); + APIWrapper.delete(`${backendURL}/candidate-decider/${uuid}`); } static async updateRating(uuid: string, id: number, rating: number): Promise { - APIWrapper.post(`${backendURL}/updateCandidateDeciderRating`, { uuid, id, rating }); + APIWrapper.post(`${backendURL}/candidate-decider/${uuid}/rating`, { uuid, id, rating }); } static async updateComment(uuid: string, id: number, comment: string): Promise { - APIWrapper.post(`${backendURL}/updateCandidateDeciderComment`, { uuid, id, comment }); + APIWrapper.post(`${backendURL}/candidate-decider/${uuid}/comment`, { uuid, id, comment }); } } From 2e04bb8ad2798e0943f6cc831829950aa4aaf41b Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 14:47:29 -0400 Subject: [PATCH 13/49] fix devportfoliodetails --- .../Admin/DevPortfolio/DevPortfolioDetails.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Admin/DevPortfolio/DevPortfolioDetails.tsx b/frontend/src/components/Admin/DevPortfolio/DevPortfolioDetails.tsx index 02cc83ce1..12a8d901c 100644 --- a/frontend/src/components/Admin/DevPortfolio/DevPortfolioDetails.tsx +++ b/frontend/src/components/Admin/DevPortfolio/DevPortfolioDetails.tsx @@ -5,6 +5,7 @@ import DevPortfolioTextModal from '../../Modals/DevPortfolioTextModal'; import DevPortfolioAPI from '../../../API/DevPortfolioAPI'; import { Emitters } from '../../../utils'; import styles from './DevPortfolioDetails.module.css'; +import { useSelf } from '../../Common/FirestoreDataProvider'; type Props = { uuid: string; @@ -18,18 +19,23 @@ const DevPortfolioDetails: React.FC = ({ uuid, isAdminView }) => { const [portfolio, setPortfolio] = useState(null); const [isRegrading, setIsRegrading] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const userInfo = useSelf()!; + useEffect(() => { if (isAdminView) { DevPortfolioAPI.getDevPortfolio(uuid).then((portfolio) => setPortfolio(portfolio)); } else { DevPortfolioAPI.getDevPortfolioInfo(uuid).then((portfolioInfo) => { const portfolio = portfolioInfo as DevPortfolio; - DevPortfolioAPI.getUsersDevPortfolioSubmissions(uuid).then((portfolioSubmissions) => { - setPortfolio({ ...portfolio, submissions: portfolioSubmissions }); - }); + DevPortfolioAPI.getUsersDevPortfolioSubmissions(uuid, userInfo.email).then( + (portfolioSubmissions) => { + setPortfolio({ ...portfolio, submissions: portfolioSubmissions }); + } + ); }); } - }, [uuid, isAdminView]); + }, [uuid, isAdminView, userInfo.email]); const handleExportToCsv = () => { if (portfolio?.submissions === undefined || portfolio?.submissions.length <= 0) { From 7bba70f7eab9d17b938f4bbfd444b23683fa7765 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 15:03:28 -0400 Subject: [PATCH 14/49] create auth.ts --- backend/src/api.ts | 106 ++------------------------------------ backend/src/utils/auth.ts | 64 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 101 deletions(-) create mode 100644 backend/src/utils/auth.ts diff --git a/backend/src/api.ts b/backend/src/api.ts index 2469a3037..b656b6812 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -1,12 +1,9 @@ -import express, { RequestHandler, Request, Response } from 'express'; +import express from 'express'; import serverless from 'serverless-http'; import cors from 'cors'; -import admin from 'firebase-admin'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; -import { app as adminApp, env } from './firebase'; -import PermissionsManager from './utils/permissionsManager'; -import { HandlerError } from './utils/errors'; +import { env } from './firebase'; import { acceptIDOLChanges, getIDOLChangesPR, @@ -15,16 +12,7 @@ import { } from './API/siteIntegrationAPI'; import { sendMail } from './API/mailAPI'; import MembersDao from './dao/MembersDao'; -import { - allMembers, - allApprovedMembers, - setMember, - deleteMember, - updateMember, - getUserInformationDifference, - reviewUserInformationChange, - getMember -} from './API/memberAPI'; +import { memberRouter, memberDiffRouter, getMember } from './API/memberAPI'; import { getMemberImage, setMemberImage, allMemberImages } from './API/imageAPI'; import { allTeams, setTeam, deleteTeam } from './API/teamAPI'; import { @@ -128,76 +116,7 @@ app.use( }) ); -const getUserEmailFromRequest = async (request: Request): Promise => { - const idToken = request.headers['auth-token']; - if (typeof idToken !== 'string') return undefined; - const decodedToken = await admin.auth(adminApp).verifyIdToken(idToken); - return decodedToken.email; -}; - -const loginCheckedHandler = - (handler: (req: Request, user: IdolMember) => Promise>): RequestHandler => - async (req: Request, res: Response): Promise => { - const userEmail = await getUserEmailFromRequest(req); - if (userEmail == null) { - res.status(440).json({ error: 'Not logged in!' }); - return; - } - const user = await MembersDao.getCurrentOrPastMemberByEmail(userEmail); - if (!user) { - res.status(401).send({ error: `No user with email: ${userEmail}` }); - return; - } - if (env === 'staging' && !(await PermissionsManager.isAdmin(user))) { - res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); - } - try { - res.status(200).send(await handler(req, user)); - } catch (error) { - if (error instanceof HandlerError) { - res.status(error.errorCode).send({ error: error.reason }); - return; - } - res.status(500).send({ error: `Failed to handle the request due to ${error}.` }); - } - }; - -const loginCheckedGet = ( - path: string, - handler: (req: Request, user: IdolMember) => Promise> -) => router.get(path, loginCheckedHandler(handler)); - -const loginCheckedPost = ( - path: string, - handler: (req: Request, user: IdolMember) => Promise> -) => router.post(path, loginCheckedHandler(handler)); - -const loginCheckedDelete = ( - path: string, - handler: (req: Request, user: IdolMember) => Promise> -) => router.delete(path, loginCheckedHandler(handler)); - -const loginCheckedPut = ( - path: string, - handler: (req: Request, user: IdolMember) => Promise> -) => router.put(path, loginCheckedHandler(handler)); - // Members -router.get('/member', async (req, res) => { - const type = req.query.type as string | undefined; - let members; - switch (type) { - case 'all-semesters': - members = await MembersDao.getMembersFromAllSemesters(); - break; - case 'approved': - members = await allApprovedMembers(); - break; - default: - members = await allMembers(); - } - res.status(200).json({ members }); -}); router.get('/hasIDOLAccess/:email', async (req, res) => { const member = await getMember(req.params.email); const adminEmails = await AdminsDao.getAllAdminEmails(); @@ -210,23 +129,8 @@ router.get('/hasIDOLAccess/:email', async (req, res) => { }); }); -loginCheckedPost('/member', async (req, user) => ({ - member: await setMember(req.body, user) -})); -loginCheckedDelete('/member/:email', async (req, user) => { - await deleteMember(req.params.email, user); - return {}; -}); -loginCheckedPut('/member', async (req, user) => ({ - member: await updateMember(req, req.body, user) -})); - -loginCheckedGet('/memberDiffs', async (_, user) => ({ - diffs: await getUserInformationDifference(user) -})); -loginCheckedPut('/memberDiffs', async (req, user) => ({ - member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) -})); +router.use('/member', memberRouter); +router.use('/memberDiffs', memberDiffRouter); // Teams loginCheckedGet('/team', async () => ({ teams: await allTeams() })); diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts new file mode 100644 index 000000000..109822d9c --- /dev/null +++ b/backend/src/utils/auth.ts @@ -0,0 +1,64 @@ +import { RequestHandler, Request, Response, Router } from 'express'; +import admin from 'firebase-admin'; +import { HandlerError } from './errors'; +import PermissionsManager from './permissionsManager'; +import { app as adminApp, env } from '../firebase'; +import MembersDao from '../dao/MembersDao'; + +const getUserEmailFromRequest = async (request: Request): Promise => { + const idToken = request.headers['auth-token']; + if (typeof idToken !== 'string') return undefined; + const decodedToken = await admin.auth(adminApp).verifyIdToken(idToken); + return decodedToken.email; +}; + +const loginCheckedHandler = + (handler: (req: Request, user: IdolMember) => Promise>): RequestHandler => + async (req: Request, res: Response): Promise => { + const userEmail = await getUserEmailFromRequest(req); + if (userEmail == null) { + res.status(440).json({ error: 'Not logged in!' }); + return; + } + const user = await MembersDao.getCurrentOrPastMemberByEmail(userEmail); + if (!user) { + res.status(401).send({ error: `No user with email: ${userEmail}` }); + return; + } + if (env === 'staging' && !(await PermissionsManager.isAdmin(user))) { + res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); + } + try { + res.status(200).send(await handler(req, user)); + } catch (error) { + if (error instanceof HandlerError) { + res.status(error.errorCode).send({ error: error.reason }); + return; + } + res.status(500).send({ error: `Failed to handle the request due to ${error}.` }); + } + }; + +export const loginCheckedGet = ( + router: Router, + path: string, + handler: (req: Request, user: IdolMember) => Promise> +): RequestHandler => router.get(path, loginCheckedHandler(handler)); + +export const loginCheckedPost = ( + router: Router, + path: string, + handler: (req: Request, user: IdolMember) => Promise> +): RequestHandler => router.post(path, loginCheckedHandler(handler)); + +export const loginCheckedDelete = ( + router: Router, + path: string, + handler: (req: Request, user: IdolMember) => Promise> +): RequestHandler => router.delete(path, loginCheckedHandler(handler)); + +export const loginCheckedPut = ( + router: Router, + path: string, + handler: (req: Request, user: IdolMember) => Promise> +): RequestHandler => router.put(path, loginCheckedHandler(handler)); From 35616f1c1183d5371c1c2b031529ddcf26d9c9f8 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 15 May 2023 15:03:36 -0400 Subject: [PATCH 15/49] create memberRouter --- backend/src/API/memberAPI.ts | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/backend/src/API/memberAPI.ts b/backend/src/API/memberAPI.ts index 66978c4a1..f71e113d8 100644 --- a/backend/src/API/memberAPI.ts +++ b/backend/src/API/memberAPI.ts @@ -1,10 +1,16 @@ -import { Request } from 'express'; +import { Request, Router } from 'express'; import MembersDao from '../dao/MembersDao'; import PermissionsManager from '../utils/permissionsManager'; import { BadRequestError, PermissionError } from '../utils/errors'; import { bucket } from '../firebase'; import { getNetIDFromEmail, computeMembersDiff } from '../utils/memberUtil'; import { sendMemberUpdateNotifications } from './mailAPI'; +import { + loginCheckedDelete, + loginCheckedGet, + loginCheckedPost, + loginCheckedPut +} from '../utils/auth'; const membersDao = new MembersDao(); @@ -119,3 +125,40 @@ export const reviewUserInformationChange = async ( MembersDao.revertMemberInformationChanges(rejected) ]); }; + +export const memberRouter = Router(); +export const memberDiffRouter = Router(); + +memberRouter.get('/', async (req, res) => { + const type = req.query.type as string | undefined; + let members; + switch (type) { + case 'all-semesters': + members = await MembersDao.getMembersFromAllSemesters(); + break; + case 'approved': + members = await allApprovedMembers(); + break; + default: + members = await allMembers(); + } + res.status(200).json({ members }); +}); + +loginCheckedPost(memberRouter, '/', async (req, user) => ({ + member: await setMember(req.body, user) +})); +loginCheckedDelete(memberRouter, '/:email', async (req, user) => { + await deleteMember(req.params.email, user); + return {}; +}); +loginCheckedPut(memberRouter, '/', async (req, user) => ({ + member: await updateMember(req, req.body, user) +})); + +loginCheckedGet(memberDiffRouter, '/', async (_, user) => ({ + diffs: await getUserInformationDifference(user) +})); +loginCheckedPut(memberDiffRouter, '/', async (req, user) => ({ + member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) +})); From 71271d9d1dc8687803bbc26cfe4da89a66ad527e Mon Sep 17 00:00:00 2001 From: Henry Li Date: Tue, 16 May 2023 12:43:54 -0400 Subject: [PATCH 16/49] add more routers --- backend/src/API/imageAPI.ts | 17 +++ backend/src/API/shoutoutAPI.ts | 31 +++++ backend/src/API/signInFormAPI.ts | 24 ++++ backend/src/API/siteIntegrationAPI.ts | 14 +++ backend/src/API/teamAPI.ts | 15 +++ backend/src/API/teamEventsAPI.ts | 45 +++++++ backend/src/API/teamEventsImageAPI.ts | 15 +++ backend/src/api.ts | 168 +++----------------------- 8 files changed, 175 insertions(+), 154 deletions(-) diff --git a/backend/src/API/imageAPI.ts b/backend/src/API/imageAPI.ts index d4e7587b9..8aef70237 100644 --- a/backend/src/API/imageAPI.ts +++ b/backend/src/API/imageAPI.ts @@ -1,6 +1,8 @@ +import { Router } from 'express'; import { bucket } from '../firebase'; import { getNetIDFromEmail, filterImagesResponse } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; +import { loginCheckedGet } from '../utils/auth'; export const allMemberImages = async (): Promise => { const files = await bucket.getFiles({ prefix: 'images/' }); @@ -44,3 +46,18 @@ export const getMemberImage = async (user: IdolMember): Promise => { }); return signedUrl[0]; }; + +export const memberImageRouter = Router(); + +loginCheckedGet(memberImageRouter, '/:email', async (_, user) => ({ + url: await getMemberImage(user) +})); + +loginCheckedGet(memberImageRouter, '/signedURL', async (_, user) => ({ + url: await setMemberImage(user) +})); + +memberImageRouter.get('/', async (_, res) => { + const images = await allMemberImages(); + res.status(200).json({ images }); +}); diff --git a/backend/src/API/shoutoutAPI.ts b/backend/src/API/shoutoutAPI.ts index b748d50dd..53c91cdb0 100644 --- a/backend/src/API/shoutoutAPI.ts +++ b/backend/src/API/shoutoutAPI.ts @@ -1,6 +1,13 @@ +import { Router } from 'express'; import PermissionsManager from '../utils/permissionsManager'; import { NotFoundError, PermissionError } from '../utils/errors'; import ShoutoutsDao from '../dao/ShoutoutsDao'; +import { + loginCheckedGet, + loginCheckedPost, + loginCheckedPut, + loginCheckedDelete +} from '../utils/auth'; const shoutoutsDao = new ShoutoutsDao(); @@ -58,3 +65,27 @@ export const deleteShoutout = async (uuid: string, user: IdolMember): Promise ({ + shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) +})); + +loginCheckedGet(shoutoutRouter, '/', async () => ({ + shoutouts: await getAllShoutouts() +})); + +loginCheckedPost(shoutoutRouter, '/', async (req, user) => ({ + shoutout: await giveShoutout(req.body, user) +})); + +loginCheckedPut(shoutoutRouter, '/', async (req, user) => { + await hideShoutout(req.body.uuid, req.body.hide, user); + return {}; +}); + +loginCheckedDelete(shoutoutRouter, '/:uuid', async (req, user) => { + await deleteShoutout(req.params.uuid, user); + return {}; +}); diff --git a/backend/src/API/signInFormAPI.ts b/backend/src/API/signInFormAPI.ts index 6965babc7..f1d1774a4 100644 --- a/backend/src/API/signInFormAPI.ts +++ b/backend/src/API/signInFormAPI.ts @@ -1,7 +1,9 @@ +import { Router } from 'express'; import SignInFormDao from '../dao/SignInFormDao'; import { PermissionError, BadRequestError, NotFoundError } from '../utils/errors'; import { signInFormCollection, memberCollection } from '../firebase'; import PermissionsManager from '../utils/permissionsManager'; +import { loginCheckedGet, loginCheckedPost } from '../utils/auth'; const checkIfDocExists = async (id: string): Promise => (await signInFormCollection.doc(id).get()).exists; @@ -87,3 +89,25 @@ export const getSignInPrompt = async (id: string): Promise = if (!signInForm) throw new NotFoundError(`Sign-in form with id ${id} does not exist!`); return signInForm.prompt; }; + +export const signInRouter = Router(); +loginCheckedPost(signInRouter, '/signInExists', async (req, _) => ({ + exists: await signInFormExists(req.body.id) +})); +loginCheckedPost(signInRouter, '/signInExpired', async (req, _) => ({ + expired: await signInFormExpired(req.body.id) +})); +loginCheckedPost(signInRouter, '/signInCreate', async (req, user) => + createSignInForm(req.body.id, req.body.expireAt, req.body.prompt, user) +); +loginCheckedPost(signInRouter, '/signInDelete', async (req, user) => { + await deleteSignInForm(req.body.id, user); + return {}; +}); +loginCheckedPost(signInRouter, '/signIn', async (req, user) => + signIn(req.body.id, req.body.response, user) +); +loginCheckedPost(signInRouter, '/signInAll', async (_, user) => allSignInForms(user)); +loginCheckedGet(signInRouter, '/signInPrompt/:id', async (req, _) => ({ + prompt: await getSignInPrompt(req.params.id) +})); diff --git a/backend/src/API/siteIntegrationAPI.ts b/backend/src/API/siteIntegrationAPI.ts index cdba7ff83..780bcb44d 100644 --- a/backend/src/API/siteIntegrationAPI.ts +++ b/backend/src/API/siteIntegrationAPI.ts @@ -1,7 +1,9 @@ +import { Router } from 'express'; import { Octokit } from '@octokit/rest'; import { PRResponse } from '../types/GithubTypes'; import PermissionsManager from '../utils/permissionsManager'; import { PermissionError, BadRequestError } from '../utils/errors'; +import { loginCheckedGet, loginCheckedPost } from '../utils/auth'; require('dotenv').config(); @@ -108,3 +110,15 @@ const checkPermissions = async (user: IdolMember): Promise => { ); } }; + +export const siteIntegrationRouter = Router(); + +loginCheckedPost(siteIntegrationRouter, '/pullIDOLChanges', (_, user) => + requestIDOLPullDispatch(user) +); + +loginCheckedGet(siteIntegrationRouter, '/getIDOLChangesPR', (_, user) => getIDOLChangesPR(user)); + +loginCheckedPost(siteIntegrationRouter, '/acceptIDOLChanges', (_, user) => acceptIDOLChanges(user)); + +loginCheckedPost(siteIntegrationRouter, '/rejectIDOLChanges', (_, user) => rejectIDOLChanges(user)); diff --git a/backend/src/API/teamAPI.ts b/backend/src/API/teamAPI.ts index f0c1645e9..77bdaf5d7 100644 --- a/backend/src/API/teamAPI.ts +++ b/backend/src/API/teamAPI.ts @@ -1,8 +1,10 @@ +import { Router } from 'express'; import { v4 as uuidv4 } from 'uuid'; import PermissionsManager from '../utils/permissionsManager'; import { Team } from '../types/DataTypes'; import { BadRequestError, PermissionError } from '../utils/errors'; import MembersDao from '../dao/MembersDao'; +import { loginCheckedGet, loginCheckedPost, loginCheckedPut } from '../utils/auth'; const membersDao = new MembersDao(); @@ -119,3 +121,16 @@ export const deleteTeam = async (teamBody: Team, member: IdolMember): Promise ({ teams: await allTeams() })); + +loginCheckedPut(teamRouter, '/', async (req, user) => ({ + team: await setTeam(req.body, user) +})); + +// TODO: should eventually make this a delete request +loginCheckedPost(teamRouter, '/', async (req, user) => ({ + team: await deleteTeam(req.body, user) +})); diff --git a/backend/src/API/teamEventsAPI.ts b/backend/src/API/teamEventsAPI.ts index f3075ea6e..ec72368bf 100644 --- a/backend/src/API/teamEventsAPI.ts +++ b/backend/src/API/teamEventsAPI.ts @@ -1,7 +1,14 @@ +import { Router } from 'express'; import TeamEventAttendanceDao from '../dao/TeamEventAttendanceDao'; import TeamEventsDao from '../dao/TeamEventsDao'; import { PermissionError } from '../utils/errors'; import PermissionsManager from '../utils/permissionsManager'; +import { + loginCheckedDelete, + loginCheckedPost, + loginCheckedGet, + loginCheckedPut +} from '../utils/auth'; const teamEventAttendanceDao = new TeamEventAttendanceDao(); @@ -135,3 +142,41 @@ export const deleteTeamEventAttendance = async (uuid: string, user: IdolMember): } await teamEventAttendanceDao.deleteTeamEventAttendance(uuid); }; + +export const teamEventRouter = Router(); + +loginCheckedPost(teamEventRouter, '/', async (req, user) => { + await createTeamEvent(req.body, user); + return {}; +}); +loginCheckedGet(teamEventRouter, '/:uuid', async (req, user) => ({ + event: await getTeamEvent(req.params.uuid, user) +})); +loginCheckedGet(teamEventRouter, '/', async (req, user) => ({ + events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() +})); +loginCheckedPut(teamEventRouter, '/', async (req, user) => ({ + event: await updateTeamEvent(req.body, user) +})); +loginCheckedDelete(teamEventRouter, '/:uuid', async (req, user) => { + await deleteTeamEvent(req.body, user); + return {}; +}); +loginCheckedDelete(teamEventRouter, '/', async (_, user) => { + await clearAllTeamEvents(user); + return {}; +}); +loginCheckedPost(teamEventRouter, '/attendance', async (req, user) => { + await requestTeamEventCredit(req.body.request, user); + return {}; +}); +loginCheckedGet(teamEventRouter, '/attendance/:email', async (_, user) => ({ + teamEventAttendance: await getTeamEventAttendanceByUser(user) +})); +loginCheckedPut(teamEventRouter, '/attendance', async (req, user) => ({ + teamEventAttendance: await updateTeamEventAttendance(req.body, user) +})); +loginCheckedDelete(teamEventRouter, '/attendance/:uuid', async (req, user) => { + await deleteTeamEventAttendance(req.params.uuid, user); + return {}; +}); diff --git a/backend/src/API/teamEventsImageAPI.ts b/backend/src/API/teamEventsImageAPI.ts index 61703b558..d747bd69a 100644 --- a/backend/src/API/teamEventsImageAPI.ts +++ b/backend/src/API/teamEventsImageAPI.ts @@ -1,6 +1,8 @@ +import { Router } from 'express'; import { bucket } from '../firebase'; import { getNetIDFromEmail } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; +import { loginCheckedGet, loginCheckedDelete } from '../utils/auth'; export const setEventProofImage = async (name: string, user: IdolMember): Promise => { const file = bucket.file(`${name}.jpg`); @@ -68,3 +70,16 @@ export const deleteEventProofImagesForMember = async (user: IdolMember): Promise }) ); }; + +export const eventProofImageRouter = Router(); + +loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ + url: await getEventProofImage(req.params.name, user) +})); +loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ + url: await setEventProofImage(req.params.name, user) +})); +loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { + await deleteEventProofImage(req.params.name, user); + return {}; +}); diff --git a/backend/src/api.ts b/backend/src/api.ts index b656b6812..a36897b76 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -4,46 +4,15 @@ import cors from 'cors'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import { env } from './firebase'; -import { - acceptIDOLChanges, - getIDOLChangesPR, - rejectIDOLChanges, - requestIDOLPullDispatch -} from './API/siteIntegrationAPI'; +import { siteIntegrationRouter } from './API/siteIntegrationAPI'; import { sendMail } from './API/mailAPI'; import MembersDao from './dao/MembersDao'; import { memberRouter, memberDiffRouter, getMember } from './API/memberAPI'; -import { getMemberImage, setMemberImage, allMemberImages } from './API/imageAPI'; -import { allTeams, setTeam, deleteTeam } from './API/teamAPI'; -import { - getAllShoutouts, - getShoutouts, - giveShoutout, - hideShoutout, - deleteShoutout -} from './API/shoutoutAPI'; -import { - allSignInForms, - createSignInForm, - deleteSignInForm, - signIn, - signInFormExists, - signInFormExpired, - getSignInPrompt -} from './API/signInFormAPI'; -import { - createTeamEvent, - deleteTeamEvent, - getAllTeamEventInfo, - getAllTeamEvents, - getTeamEvent, - updateTeamEvent, - clearAllTeamEvents, - requestTeamEventCredit, - getTeamEventAttendanceByUser, - updateTeamEventAttendance, - deleteTeamEventAttendance -} from './API/teamEventsAPI'; +import { memberImageRouter } from './API/imageAPI'; +import { teamRouter } from './API/teamAPI'; +import { shoutoutRouter } from './API/shoutoutAPI'; +import { signInRouter } from './API/signInFormAPI'; +import { teamEventRouter } from './API/teamEventsAPI'; import { getAllCandidateDeciderInstances, createNewCandidateDeciderInstance, @@ -53,11 +22,7 @@ import { updateCandidateDeciderRating, updateCandidateDeciderComment } from './API/candidateDeciderAPI'; -import { - deleteEventProofImage, - getEventProofImage, - setEventProofImage -} from './API/teamEventsImageAPI'; +import { eventProofImageRouter } from './API/teamEventsImageAPI'; import { getAllDevPortfolios, createNewDevPortfolio, @@ -131,126 +96,21 @@ router.get('/hasIDOLAccess/:email', async (req, res) => { router.use('/member', memberRouter); router.use('/memberDiffs', memberDiffRouter); - -// Teams -loginCheckedGet('/team', async () => ({ teams: await allTeams() })); -loginCheckedPut('/team', async (req, user) => ({ - team: await setTeam(req.body, user) -})); -// TODO: should eventually make this a delete request -loginCheckedPost('/team', async (req, user) => ({ - team: await deleteTeam(req.body, user) -})); - -// Images -loginCheckedGet('/memberImage/:email', async (_, user) => ({ - url: await getMemberImage(user) -})); -loginCheckedGet('/memberImage/signedURL', async (_, user) => ({ - url: await setMemberImage(user) -})); -router.get('/memberImage', async (_, res) => { - const images = await allMemberImages(); - res.status(200).json({ images }); -}); - -// Shoutouts -loginCheckedGet('/shoutout/:email/:type', async (req, user) => ({ - shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) -})); - -loginCheckedGet('/shoutout', async () => ({ - shoutouts: await getAllShoutouts() -})); - -loginCheckedPost('/shoutout', async (req, user) => ({ - shoutout: await giveShoutout(req.body, user) -})); - -loginCheckedPut('/shoutout', async (req, user) => { - await hideShoutout(req.body.uuid, req.body.hide, user); - return {}; -}); - -loginCheckedDelete('/shoutout/:uuid', async (req, user) => { - await deleteShoutout(req.params.uuid, user); - return {}; -}); +router.use('/team', teamRouter); +router.use('/memberImage', memberImageRouter); +router.use('/shoutout', shoutoutRouter); +router.use('/', siteIntegrationRouter); +router.use('/', signInRouter); +router.use('/team-event', teamEventRouter); +router.use('/event-proof-image'); // TODO: have to update frontend endpoints // Pull from IDOL -loginCheckedPost('/pullIDOLChanges', (_, user) => requestIDOLPullDispatch(user)); -loginCheckedGet('/getIDOLChangesPR', (_, user) => getIDOLChangesPR(user)); -loginCheckedPost('/acceptIDOLChanges', (_, user) => acceptIDOLChanges(user)); -loginCheckedPost('/rejectIDOLChanges', (_, user) => rejectIDOLChanges(user)); // Sign In Form -loginCheckedPost('/signInExists', async (req, _) => ({ - exists: await signInFormExists(req.body.id) -})); -loginCheckedPost('/signInExpired', async (req, _) => ({ - expired: await signInFormExpired(req.body.id) -})); -loginCheckedPost('/signInCreate', async (req, user) => - createSignInForm(req.body.id, req.body.expireAt, req.body.prompt, user) -); -loginCheckedPost('/signInDelete', async (req, user) => { - await deleteSignInForm(req.body.id, user); - return {}; -}); -loginCheckedPost('/signIn', async (req, user) => signIn(req.body.id, req.body.response, user)); -loginCheckedPost('/signInAll', async (_, user) => allSignInForms(user)); -loginCheckedGet('/signInPrompt/:id', async (req, _) => ({ - prompt: await getSignInPrompt(req.params.id) -})); // Team Events -loginCheckedPost('/team-event', async (req, user) => { - await createTeamEvent(req.body, user); - return {}; -}); -loginCheckedGet('/team-event/:uuid', async (req, user) => ({ - event: await getTeamEvent(req.params.uuid, user) -})); -loginCheckedGet('/team-event', async (req, user) => ({ - events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() -})); -loginCheckedPut('/team-event', async (req, user) => ({ - event: await updateTeamEvent(req.body, user) -})); -loginCheckedDelete('/team-event/:uuid', async (req, user) => { - await deleteTeamEvent(req.body, user); - return {}; -}); -loginCheckedDelete('/team-event', async (_, user) => { - await clearAllTeamEvents(user); - return {}; -}); -loginCheckedPost('/team-event/attendance', async (req, user) => { - await requestTeamEventCredit(req.body.request, user); - return {}; -}); -loginCheckedGet('/team-event/attendance/:email', async (_, user) => ({ - teamEventAttendance: await getTeamEventAttendanceByUser(user) -})); -loginCheckedPut('/team-event/attendance', async (req, user) => ({ - teamEventAttendance: await updateTeamEventAttendance(req.body, user) -})); -loginCheckedDelete('/team-event/attendance/:uuid', async (req, user) => { - await deleteTeamEventAttendance(req.params.uuid, user); - return {}; -}); // Team Events Proof Image -loginCheckedGet('/getEventProofImage/:name(*)', async (req, user) => ({ - url: await getEventProofImage(req.params.name, user) -})); -loginCheckedGet('/getEventProofImageSignedURL/:name(*)', async (req, user) => ({ - url: await setEventProofImage(req.params.name, user) -})); -loginCheckedPost('/deleteEventProofImage', async (req, user) => { - await deleteEventProofImage(req.body.name, user); - return {}; -}); // Candidate Decider loginCheckedGet('/candidate-decider', async (_, user) => ({ From 8b98db74bcd0bf18e7f0e5f9f2566f08fff6ac7d Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 19 May 2023 13:21:47 -0400 Subject: [PATCH 17/49] more stuff --- backend/src/API/candidateDeciderAPI.ts | 33 ++++++++++++++++++ backend/src/API/teamEventsImageAPI.ts | 2 +- backend/src/api.ts | 47 ++------------------------ frontend/src/API/ImagesAPI.ts | 6 ++-- 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/backend/src/API/candidateDeciderAPI.ts b/backend/src/API/candidateDeciderAPI.ts index 29754da43..8b49d0f73 100644 --- a/backend/src/API/candidateDeciderAPI.ts +++ b/backend/src/API/candidateDeciderAPI.ts @@ -1,6 +1,13 @@ +import { Router } from 'express'; import CandidateDeciderDao from '../dao/CandidateDeciderDao'; import { NotFoundError, PermissionError } from '../utils/errors'; import PermissionsManager from '../utils/permissionsManager'; +import { + loginCheckedDelete, + loginCheckedGet, + loginCheckedPost, + loginCheckedPut +} from '../utils/auth'; const candidateDeciderDao = new CandidateDeciderDao(); @@ -142,3 +149,29 @@ export const updateCandidateDeciderComment = async ( }; candidateDeciderDao.updateInstance(updatedInstance); }; + +const candidateDeciderRouter = Router(); + +loginCheckedGet(candidateDeciderRouter, '/', async (_, user) => ({ + instances: await getAllCandidateDeciderInstances(user) +})); +loginCheckedGet(candidateDeciderRouter, '/:uuid', async (req, user) => ({ + instance: await getCandidateDeciderInstance(req.params.uuid, user) +})); +loginCheckedPost(candidateDeciderRouter, '/', async (req, user) => ({ + instance: await createNewCandidateDeciderInstance(req.body, user) +})); +loginCheckedPut(candidateDeciderRouter, '/:uuid', async (req, user) => + toggleCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) +); +loginCheckedDelete(candidateDeciderRouter, '/:uuid', async (req, user) => + deleteCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) +); +loginCheckedPut(candidateDeciderRouter, '/:uuid/rating', (req, user) => + updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) +); +loginCheckedPost(candidateDeciderRouter, '/decider/:uuid/comment', (req, user) => + updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( + () => ({}) + ) +); diff --git a/backend/src/API/teamEventsImageAPI.ts b/backend/src/API/teamEventsImageAPI.ts index d747bd69a..697cb2897 100644 --- a/backend/src/API/teamEventsImageAPI.ts +++ b/backend/src/API/teamEventsImageAPI.ts @@ -76,7 +76,7 @@ export const eventProofImageRouter = Router(); loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ url: await getEventProofImage(req.params.name, user) })); -loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ +loginCheckedGet(eventProofImageRouter, '/:name(*)/signed-url', async (req, user) => ({ url: await setEventProofImage(req.params.name, user) })); loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { diff --git a/backend/src/api.ts b/backend/src/api.ts index a36897b76..ee6587549 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -13,15 +13,7 @@ import { teamRouter } from './API/teamAPI'; import { shoutoutRouter } from './API/shoutoutAPI'; import { signInRouter } from './API/signInFormAPI'; import { teamEventRouter } from './API/teamEventsAPI'; -import { - getAllCandidateDeciderInstances, - createNewCandidateDeciderInstance, - toggleCandidateDeciderInstance, - deleteCandidateDeciderInstance, - getCandidateDeciderInstance, - updateCandidateDeciderRating, - updateCandidateDeciderComment -} from './API/candidateDeciderAPI'; +import { candidateDeciderRouter } from './API/candidateDeciderAPI'; import { eventProofImageRouter } from './API/teamEventsImageAPI'; import { getAllDevPortfolios, @@ -103,42 +95,7 @@ router.use('/', siteIntegrationRouter); router.use('/', signInRouter); router.use('/team-event', teamEventRouter); router.use('/event-proof-image'); // TODO: have to update frontend endpoints - -// Pull from IDOL - -// Sign In Form - -// Team Events - -// Team Events Proof Image - -// Candidate Decider -loginCheckedGet('/candidate-decider', async (_, user) => ({ - instances: await getAllCandidateDeciderInstances(user) -})); -loginCheckedGet('/candidate-decider/:uuid', async (req, user) => ({ - instance: await getCandidateDeciderInstance(req.params.uuid, user) -})); -loginCheckedPost('/candider-decider', async (req, user) => ({ - instance: await createNewCandidateDeciderInstance(req.body, user) -})); -loginCheckedPut('/candidate-decider/:uuid', async (req, user) => - toggleCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) -); -loginCheckedDelete('/candidate-decider/:uuid', async (req, user) => - deleteCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) -); -loginCheckedPut('/candidate-decider/:uuid/rating', (req, user) => - updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) -); -loginCheckedPost('/candidate-decider/:uuid/comment', (req, user) => - updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( - () => ({}) - ) -); -loginCheckedPost('/sendMail', async (req, user) => ({ - info: await sendMail(req.body.to, req.body.subject, req.body.text) -})); +router.use('/candidate-decider', candidateDeciderRouter); // Dev Portfolios loginCheckedGet('/dev-portfolio', async (req, user) => ({ diff --git a/frontend/src/API/ImagesAPI.ts b/frontend/src/API/ImagesAPI.ts index 1dbceea24..5ddc91b4f 100644 --- a/frontend/src/API/ImagesAPI.ts +++ b/frontend/src/API/ImagesAPI.ts @@ -33,7 +33,7 @@ export default class ImagesAPI { // Event proof images public static getEventProofImage(name: string): Promise { - const responseProm = APIWrapper.get(`${backendURL}/getEventProofImage/${name}`).then( + const responseProm = APIWrapper.get(`${backendURL}/event-proof-image/${name}`).then( (res) => res.data ); return responseProm.then((val) => { @@ -45,7 +45,7 @@ export default class ImagesAPI { } private static getEventProofImageSignedURL(name: string): Promise { - const responseProm = APIWrapper.get(`${backendURL}/getEventProofImageSignedURL/${name}`).then( + const responseProm = APIWrapper.get(`${backendURL}/event-proof-image/${name}/signed-url`).then( (res) => res.data ); return responseProm.then((val) => val.url); @@ -59,6 +59,6 @@ export default class ImagesAPI { } public static async deleteEventProofImage(name: string): Promise { - await APIWrapper.post(`${backendURL}/deleteEventProofImage`, { name }); + await APIWrapper.post(`${backendURL}/event-proof-image/${name}`, { name }); } } From 3e740b00f006fc61d68905dc951092e2a45fa43b Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 19 May 2023 13:26:44 -0400 Subject: [PATCH 18/49] more stuff --- backend/src/API/candidateDeciderAPI.ts | 2 +- backend/src/API/devPortfolioAPI.ts | 46 +++++++++++++++ backend/src/API/teamEventsImageAPI.ts | 80 +++++++++++++------------- backend/src/api.ts | 52 ++--------------- 4 files changed, 92 insertions(+), 88 deletions(-) diff --git a/backend/src/API/candidateDeciderAPI.ts b/backend/src/API/candidateDeciderAPI.ts index 8b49d0f73..f95a17f1e 100644 --- a/backend/src/API/candidateDeciderAPI.ts +++ b/backend/src/API/candidateDeciderAPI.ts @@ -150,7 +150,7 @@ export const updateCandidateDeciderComment = async ( candidateDeciderDao.updateInstance(updatedInstance); }; -const candidateDeciderRouter = Router(); +export const candidateDeciderRouter = Router(); loginCheckedGet(candidateDeciderRouter, '/', async (_, user) => ({ instances: await getAllCandidateDeciderInstances(user) diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index df15f14b7..994332a87 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -1,8 +1,16 @@ +import { Router } from 'express'; import { DateTime } from 'luxon'; import DevPortfolioDao from '../dao/DevPortfolioDao'; import PermissionsManager from '../utils/permissionsManager'; import { PermissionError, BadRequestError, NotFoundError } from '../utils/errors'; import { validateSubmission, isWithinDates } from '../utils/githubUtil'; +import { + loginCheckedDelete, + loginCheckedGet, + loginCheckedPost, + loginCheckedPut +} from '../utils/auth'; +import DPSubmissionRequestLogDao from '../dao/DPSubmissionRequestLogDao'; const zonedTime = (timestamp: number, ianatz = 'America/New_York') => DateTime.fromMillis(timestamp, { zone: ianatz }); @@ -158,3 +166,41 @@ export const regradeSubmissions = async (uuid: string, user: IdolMember): Promis await devPortfolioDao.updateInstance(updatedDP); return updatedDP; }; + +export const devPortfolioRouter = Router(); + +loginCheckedGet(devPortfolioRouter, '/dev-portfolio', async (req, user) => ({ + portfolios: !req.query.meta_only + ? await getAllDevPortfolios(user) + : await getAllDevPortfolioInfo() +})); +loginCheckedGet(devPortfolioRouter, '/dev-portfolio/:uuid', async (req, user) => ({ + portfolioInfo: !req.query.meta_only + ? await getDevPortfolio(req.params.uuid, user) + : await getDevPortfolioInfo(req.params.uuid) +})); +loginCheckedGet( + devPortfolioRouter, + '/dev-portfolio/:uuid/submission/:email', + async (req, user) => ({ + submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) + }) +); +loginCheckedPost(devPortfolioRouter, '/dev-portfolio', async (req, user) => ({ + portfolio: await createNewDevPortfolio(req.body, user) +})); +loginCheckedDelete(devPortfolioRouter, '/dev-portfolio/:uuid', async (req, user) => + deleteDevPortfolio(req.params.uuid, user).then(() => ({})) +); +loginCheckedPost(devPortfolioRouter, '/dev-portfolio/submission', async (req, user) => { + await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); + return { + submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) + }; +}); +loginCheckedPut(devPortfolioRouter, '/dev-portfolio/submission', async (req, user) => ({ + portfolio: await regradeSubmissions(req.body.uuid, user) +})); +loginCheckedPut(devPortfolioRouter, '/dev-portfolio/submission/:uuid', async (req, user) => ({ + portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) +})); diff --git a/backend/src/API/teamEventsImageAPI.ts b/backend/src/API/teamEventsImageAPI.ts index 697cb2897..66b986ac2 100644 --- a/backend/src/API/teamEventsImageAPI.ts +++ b/backend/src/API/teamEventsImageAPI.ts @@ -4,7 +4,7 @@ import { getNetIDFromEmail } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; import { loginCheckedGet, loginCheckedDelete } from '../utils/auth'; -export const setEventProofImage = async (name: string, user: IdolMember): Promise => { +const setEventProofImage = async (name: string, user: IdolMember): Promise => { const file = bucket.file(`${name}.jpg`); const signedURL = await file.getSignedUrl({ action: 'write', @@ -14,7 +14,7 @@ export const setEventProofImage = async (name: string, user: IdolMember): Promis return signedURL[0]; }; -export const getEventProofImage = async (name: string, user: IdolMember): Promise => { +const getEventProofImage = async (name: string, user: IdolMember): Promise => { const file = bucket.file(`${name}.jpg`); const fileExists = await file.exists().then((result) => result[0]); if (!fileExists) { @@ -27,51 +27,51 @@ export const getEventProofImage = async (name: string, user: IdolMember): Promis return signedUrl[0]; }; -export const allEventProofImagesForMember = async ( - user: IdolMember -): Promise => { - const netId: string = getNetIDFromEmail(user.email); - const files = await bucket.getFiles({ prefix: `eventProofs/${netId}` }); - const images = await Promise.all( - files[0].map(async (file) => { - const signedURL = await file.getSignedUrl({ - action: 'read', - expires: Date.now() + 15 * 60000 // 15 min - }); - const fileName = await file.getMetadata().then((data) => data[1].body.name); - return { - fileName, - url: signedURL[0] - }; - }) - ); +// export const allEventProofImagesForMember = async ( +// user: IdolMember +// ): Promise => { +// const netId: string = getNetIDFromEmail(user.email); +// const files = await bucket.getFiles({ prefix: `eventProofs/${netId}` }); +// const images = await Promise.all( +// files[0].map(async (file) => { +// const signedURL = await file.getSignedUrl({ +// action: 'read', +// expires: Date.now() + 15 * 60000 // 15 min +// }); +// const fileName = await file.getMetadata().then((data) => data[1].body.name); +// return { +// fileName, +// url: signedURL[0] +// }; +// }) +// ); - images - .filter((image) => image.fileName.length > 'eventProofs/'.length) - .map((image) => ({ - ...image, - fileName: image.fileName.slice(image.fileName.indexOf('/') + 1) - })); +// images +// .filter((image) => image.fileName.length > 'eventProofs/'.length) +// .map((image) => ({ +// ...image, +// fileName: image.fileName.slice(image.fileName.indexOf('/') + 1) +// })); - return images; -}; +// return images; +// }; -export const deleteEventProofImage = async (name: string, user: IdolMember): Promise => { +const deleteEventProofImage = async (name: string, user: IdolMember): Promise => { const imageFile = bucket.file(`${name}.jpg`); await imageFile.delete(); }; -export const deleteEventProofImagesForMember = async (user: IdolMember): Promise => { - const netId: string = getNetIDFromEmail(user.email); - const files = await bucket.getFiles({ prefix: `eventProofs/${netId}` }); - Promise.all( - files[0].map(async (file) => { - file.delete(); - }) - ); -}; +// const deleteEventProofImagesForMember = async (user: IdolMember): Promise => { +// const netId: string = getNetIDFromEmail(user.email); +// const files = await bucket.getFiles({ prefix: `eventProofs/${netId}` }); +// Promise.all( +// files[0].map(async (file) => { +// file.delete(); +// }) +// ); +// }; -export const eventProofImageRouter = Router(); +const eventProofImageRouter = Router(); loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ url: await getEventProofImage(req.params.name, user) @@ -83,3 +83,5 @@ loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { await deleteEventProofImage(req.params.name, user); return {}; }); + +export default eventProofsImage; diff --git a/backend/src/api.ts b/backend/src/api.ts index ee6587549..1f0293372 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -5,8 +5,6 @@ import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import { env } from './firebase'; import { siteIntegrationRouter } from './API/siteIntegrationAPI'; -import { sendMail } from './API/mailAPI'; -import MembersDao from './dao/MembersDao'; import { memberRouter, memberDiffRouter, getMember } from './API/memberAPI'; import { memberImageRouter } from './API/imageAPI'; import { teamRouter } from './API/teamAPI'; @@ -14,20 +12,8 @@ import { shoutoutRouter } from './API/shoutoutAPI'; import { signInRouter } from './API/signInFormAPI'; import { teamEventRouter } from './API/teamEventsAPI'; import { candidateDeciderRouter } from './API/candidateDeciderAPI'; -import { eventProofImageRouter } from './API/teamEventsImageAPI'; -import { - getAllDevPortfolios, - createNewDevPortfolio, - deleteDevPortfolio, - makeDevPortfolioSubmission, - getDevPortfolio, - getAllDevPortfolioInfo, - getDevPortfolioInfo, - getUsersDevPortfolioSubmissions, - regradeSubmissions, - updateSubmissions -} from './API/devPortfolioAPI'; -import DPSubmissionRequestLogDao from './dao/DPSubmissionRequestLogDao'; +import eventProofImageRouter from './API/teamEventsImageAPI'; +import { devPortfolioRouter } from './API/devPortfolioAPI'; import AdminsDao from './dao/AdminsDao'; // Constants and configurations @@ -94,41 +80,11 @@ router.use('/shoutout', shoutoutRouter); router.use('/', siteIntegrationRouter); router.use('/', signInRouter); router.use('/team-event', teamEventRouter); -router.use('/event-proof-image'); // TODO: have to update frontend endpoints +router.use('/event-proof-image', eventProofImageRouter); router.use('/candidate-decider', candidateDeciderRouter); +router.use('/dev-portfolio', devPortfolioRouter); // Dev Portfolios -loginCheckedGet('/dev-portfolio', async (req, user) => ({ - portfolios: !req.query.meta_only - ? await getAllDevPortfolios(user) - : await getAllDevPortfolioInfo() -})); -loginCheckedGet('/dev-portfolio/:uuid', async (req, user) => ({ - portfolioInfo: !req.query.meta_only - ? await getDevPortfolio(req.params.uuid, user) - : await getDevPortfolioInfo(req.params.uuid) -})); -loginCheckedGet('/dev-portfolio/:uuid/submission/:email', async (req, user) => ({ - submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) -})); -loginCheckedPost('/dev-portfolio', async (req, user) => ({ - portfolio: await createNewDevPortfolio(req.body, user) -})); -loginCheckedDelete('/dev-portfolio/:uuid', async (req, user) => - deleteDevPortfolio(req.params.uuid, user).then(() => ({})) -); -loginCheckedPost('/dev-portfolio/submission', async (req, user) => { - await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); - return { - submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) - }; -}); -loginCheckedPut('/dev-portfolio/submission', async (req, user) => ({ - portfolio: await regradeSubmissions(req.body.uuid, user) -})); -loginCheckedPut('/dev-portfolio/submission/:uuid', async (req, user) => ({ - portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) -})); app.use('/.netlify/functions/api', router); From f3d95cb9bb61417e053cd3907b9a2d29e37415cc Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 19 May 2023 14:58:37 -0400 Subject: [PATCH 19/49] more router stuff --- backend/src/API/devPortfolioAPI.ts | 24 ++++++++++-------------- backend/src/API/teamEventsImageAPI.ts | 4 ++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index 994332a87..f2cc474ef 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -169,38 +169,34 @@ export const regradeSubmissions = async (uuid: string, user: IdolMember): Promis export const devPortfolioRouter = Router(); -loginCheckedGet(devPortfolioRouter, '/dev-portfolio', async (req, user) => ({ +loginCheckedGet(devPortfolioRouter, '/', async (req, user) => ({ portfolios: !req.query.meta_only ? await getAllDevPortfolios(user) : await getAllDevPortfolioInfo() })); -loginCheckedGet(devPortfolioRouter, '/dev-portfolio/:uuid', async (req, user) => ({ +loginCheckedGet(devPortfolioRouter, '/:uuid', async (req, user) => ({ portfolioInfo: !req.query.meta_only ? await getDevPortfolio(req.params.uuid, user) : await getDevPortfolioInfo(req.params.uuid) })); -loginCheckedGet( - devPortfolioRouter, - '/dev-portfolio/:uuid/submission/:email', - async (req, user) => ({ - submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) - }) -); -loginCheckedPost(devPortfolioRouter, '/dev-portfolio', async (req, user) => ({ +loginCheckedGet(devPortfolioRouter, '/:uuid/submission/:email', async (req, user) => ({ + submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) +})); +loginCheckedPost(devPortfolioRouter, '/', async (req, user) => ({ portfolio: await createNewDevPortfolio(req.body, user) })); -loginCheckedDelete(devPortfolioRouter, '/dev-portfolio/:uuid', async (req, user) => +loginCheckedDelete(devPortfolioRouter, '/:uuid', async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})) ); -loginCheckedPost(devPortfolioRouter, '/dev-portfolio/submission', async (req, user) => { +loginCheckedPost(devPortfolioRouter, '/submission', async (req, user) => { await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); return { submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) }; }); -loginCheckedPut(devPortfolioRouter, '/dev-portfolio/submission', async (req, user) => ({ +loginCheckedPut(devPortfolioRouter, '/submission', async (req, user) => ({ portfolio: await regradeSubmissions(req.body.uuid, user) })); -loginCheckedPut(devPortfolioRouter, '/dev-portfolio/submission/:uuid', async (req, user) => ({ +loginCheckedPut(devPortfolioRouter, '/submission/:uuid', async (req, user) => ({ portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) })); diff --git a/backend/src/API/teamEventsImageAPI.ts b/backend/src/API/teamEventsImageAPI.ts index 66b986ac2..972aa99a7 100644 --- a/backend/src/API/teamEventsImageAPI.ts +++ b/backend/src/API/teamEventsImageAPI.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import { bucket } from '../firebase'; -import { getNetIDFromEmail } from '../utils/memberUtil'; +// import { getNetIDFromEmail } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; import { loginCheckedGet, loginCheckedDelete } from '../utils/auth'; @@ -84,4 +84,4 @@ loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { return {}; }); -export default eventProofsImage; +export default eventProofImageRouter; From 507eb0add38f8f1b81b629f2fb6f6a75b879359e Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 11:19:56 -0400 Subject: [PATCH 20/49] setup sub-routers for dev portfolio --- backend/src/API/devPortfolioAPI.ts | 27 +++++++++++++++++++-------- frontend/src/API/DevPortfolioAPI.ts | 12 ++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index f2cc474ef..eaa5dbfec 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -35,7 +35,10 @@ export const getDevPortfolioInfo = async (uuid: string): Promise => devPortfolioDao.getUsersDevPortfolioSubmissions(uuid, user); +): Promise => { + console.log(`UUID: ${uuid}`); + return devPortfolioDao.getUsersDevPortfolioSubmissions(uuid, user); +}; export const getDevPortfolio = async (uuid: string, user: IdolMember): Promise => { const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); @@ -168,35 +171,43 @@ export const regradeSubmissions = async (uuid: string, user: IdolMember): Promis }; export const devPortfolioRouter = Router(); +const devPortfolioSubmissionRouter = Router({ mergeParams: true }); +devPortfolioRouter.use('/:uuid/submission', devPortfolioSubmissionRouter); +// /dev-portfolio loginCheckedGet(devPortfolioRouter, '/', async (req, user) => ({ portfolios: !req.query.meta_only ? await getAllDevPortfolios(user) : await getAllDevPortfolioInfo() })); loginCheckedGet(devPortfolioRouter, '/:uuid', async (req, user) => ({ - portfolioInfo: !req.query.meta_only + portfolio: !req.query.meta_only ? await getDevPortfolio(req.params.uuid, user) : await getDevPortfolioInfo(req.params.uuid) })); -loginCheckedGet(devPortfolioRouter, '/:uuid/submission/:email', async (req, user) => ({ - submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) -})); + loginCheckedPost(devPortfolioRouter, '/', async (req, user) => ({ portfolio: await createNewDevPortfolio(req.body, user) })); loginCheckedDelete(devPortfolioRouter, '/:uuid', async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})) ); -loginCheckedPost(devPortfolioRouter, '/submission', async (req, user) => { + +// devPortfolioSubmissionRouter: /dev-portfolio/:uuid/submission +loginCheckedPost(devPortfolioSubmissionRouter, '/', async (req, user) => { await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); return { submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) }; }); -loginCheckedPut(devPortfolioRouter, '/submission', async (req, user) => ({ +// this is asking for the data to be changed on the server +loginCheckedPut(devPortfolioSubmissionRouter, '/regrade', async (req, user) => ({ portfolio: await regradeSubmissions(req.body.uuid, user) })); -loginCheckedPut(devPortfolioRouter, '/submission/:uuid', async (req, user) => ({ +// this is providing data from the client to be changed +loginCheckedPut(devPortfolioSubmissionRouter, '/', async (req, user) => ({ portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) })); +loginCheckedGet(devPortfolioSubmissionRouter, '/:email', async (req, user) => ({ + submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) +})); diff --git a/frontend/src/API/DevPortfolioAPI.ts b/frontend/src/API/DevPortfolioAPI.ts index e1ca1d5a2..4e1d6c4df 100644 --- a/frontend/src/API/DevPortfolioAPI.ts +++ b/frontend/src/API/DevPortfolioAPI.ts @@ -13,7 +13,7 @@ export default class DevPortfolioAPI { static async getAllDevPortfolioInfo(): Promise { const response = APIWrapper.get(`${backendURL}/dev-portfolio?meta_only=true`); - return response.then((val) => val.data.portfolioInfo); + return response.then((val) => val.data.portfolios); } public static async createDevPortfolio(devPortfolio: DevPortfolio): Promise { @@ -26,14 +26,14 @@ export default class DevPortfolioAPI { uuid: string, submission: DevPortfolioSubmission ): Promise { - return APIWrapper.post(`${backendURL}/dev-portfolio/submission`, { + return APIWrapper.post(`${backendURL}/dev-portfolio/${uuid}/submission`, { uuid, submission }).then((res) => res.data); } public static async deleteDevPortfolio(uuid: string): Promise { - APIWrapper.delete(`${backendURL}/deleteDevPortfolio/${uuid}`); + APIWrapper.delete(`${backendURL}/dev-portfolio/${uuid}`); } public static async getDevPortfolio(uuid: string): Promise { @@ -42,7 +42,7 @@ export default class DevPortfolioAPI { public static async getDevPortfolioInfo(uuid: string): Promise { return APIWrapper.get(`${backendURL}/dev-portfolio/${uuid}?meta_only=true`).then( - (res) => res.data.portfolioInfo + (res) => res.data.portfolio ); } @@ -56,7 +56,7 @@ export default class DevPortfolioAPI { } public static async regradeSubmissions(uuid: string): Promise { - return APIWrapper.put(`${backendURL}/dev-portfolio/submission`, { uuid }).then( + return APIWrapper.put(`${backendURL}/dev-portfolio/${uuid}/submission/regrade`, { uuid }).then( (res) => res.data.portfolio ); } @@ -65,7 +65,7 @@ export default class DevPortfolioAPI { uuid: string, updatedSubmissions: DevPortfolioSubmission[] ): Promise { - return APIWrapper.post(`${backendURL}/dev-portfolio/submission/${uuid}`, { + return APIWrapper.put(`${backendURL}/dev-portfolio/${uuid}/submission`, { uuid, updatedSubmissions }).then((res) => res.data.portfolio); From 45d8a4592611bab51e7b7b5adf4ca21154014f30 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:48:28 -0400 Subject: [PATCH 21/49] create auth role types --- backend/src/types/DataTypes.d.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/src/types/DataTypes.d.ts b/backend/src/types/DataTypes.d.ts index a9bd66cab..ce52ed4e3 100644 --- a/backend/src/types/DataTypes.d.ts +++ b/backend/src/types/DataTypes.d.ts @@ -92,3 +92,19 @@ export type DevPortfolioSubmissionRequestLog = { uuid: string; }; }; + +type AuthLead = + | 'lead' + | 'dev_lead' + | 'ops_lead' + | 'lead' + | 'design_lead' + | 'business_lead' + | 'ci_lead' + | 'pm_lead'; + +export type AuthRole = 'admin' | 'dev' | 'tpm' | 'pm' | 'designer' | AuthLead; + +export interface AuthRoleDoc { + role: AuthRole; +} From 4b7e5a6a30eed31ca37d178ff928d227a77d647f Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:48:52 -0400 Subject: [PATCH 22/49] setup firebase for auth roles collection --- backend/src/firebase.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/src/firebase.ts b/backend/src/firebase.ts index d86a0807a..6a6a5451f 100644 --- a/backend/src/firebase.ts +++ b/backend/src/firebase.ts @@ -5,7 +5,8 @@ import { DBCandidateDeciderInstance, DBDevPortfolio, DevPortfolioSubmissionRequestLog, - DBTeamEventAttendance + DBTeamEventAttendance, + AuthRoleDoc } from './types/DataTypes'; import { configureAccount } from './utils/firebase-utils'; @@ -133,3 +134,14 @@ export const devPortfolioSubmissionRequestLogCollection: admin.firestore.Collect }); export const adminCollection: admin.firestore.CollectionReference = db.collection('admins'); + +export const authRoleCollection: admin.firestore.CollectionReference = db + .collection('auth-role') + .withConverter({ + fromFirestore(snapshot): AuthRoleDoc { + return snapshot.data() as AuthRoleDoc; + }, + toFirestore(authRoleDoc: AuthRoleDoc) { + return authRoleDoc; + } + }); From 5b89be8f4185b3d141c11a5cb53b5d2510b6bda4 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:49:51 -0400 Subject: [PATCH 23/49] add resource names to dev portfolio api login checked routes --- backend/src/API/devPortfolioAPI.ts | 107 +++++++++++++++++++---------- 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index eaa5dbfec..a4a9cdde0 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -35,10 +35,7 @@ export const getDevPortfolioInfo = async (uuid: string): Promise => { - console.log(`UUID: ${uuid}`); - return devPortfolioDao.getUsersDevPortfolioSubmissions(uuid, user); -}; +): Promise => devPortfolioDao.getUsersDevPortfolioSubmissions(uuid, user); export const getDevPortfolio = async (uuid: string, user: IdolMember): Promise => { const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); @@ -175,39 +172,75 @@ const devPortfolioSubmissionRouter = Router({ mergeParams: true }); devPortfolioRouter.use('/:uuid/submission', devPortfolioSubmissionRouter); // /dev-portfolio -loginCheckedGet(devPortfolioRouter, '/', async (req, user) => ({ - portfolios: !req.query.meta_only - ? await getAllDevPortfolios(user) - : await getAllDevPortfolioInfo() -})); -loginCheckedGet(devPortfolioRouter, '/:uuid', async (req, user) => ({ - portfolio: !req.query.meta_only - ? await getDevPortfolio(req.params.uuid, user) - : await getDevPortfolioInfo(req.params.uuid) -})); - -loginCheckedPost(devPortfolioRouter, '/', async (req, user) => ({ - portfolio: await createNewDevPortfolio(req.body, user) -})); -loginCheckedDelete(devPortfolioRouter, '/:uuid', async (req, user) => - deleteDevPortfolio(req.params.uuid, user).then(() => ({})) +loginCheckedGet( + devPortfolioRouter, + '/', + async (req, user) => ({ + portfolios: !req.query.meta_only + ? await getAllDevPortfolios(user) + : await getAllDevPortfolioInfo() + }), + 'dev-portfolio' +); +loginCheckedGet( + devPortfolioRouter, + '/:uuid', + async (req, user) => ({ + portfolio: !req.query.meta_only + ? await getDevPortfolio(req.params.uuid, user) + : await getDevPortfolioInfo(req.params.uuid) + }), + 'dev-portfolio' +); + +loginCheckedPost( + devPortfolioRouter, + '/', + async (req, user) => ({ + portfolio: await createNewDevPortfolio(req.body, user) + }), + 'dev-portfolio' +); +loginCheckedDelete( + devPortfolioRouter, + '/:uuid', + async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})), + 'dev-portfolio' ); // devPortfolioSubmissionRouter: /dev-portfolio/:uuid/submission -loginCheckedPost(devPortfolioSubmissionRouter, '/', async (req, user) => { - await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); - return { - submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) - }; -}); -// this is asking for the data to be changed on the server -loginCheckedPut(devPortfolioSubmissionRouter, '/regrade', async (req, user) => ({ - portfolio: await regradeSubmissions(req.body.uuid, user) -})); -// this is providing data from the client to be changed -loginCheckedPut(devPortfolioSubmissionRouter, '/', async (req, user) => ({ - portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) -})); -loginCheckedGet(devPortfolioSubmissionRouter, '/:email', async (req, user) => ({ - submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) -})); +loginCheckedPost( + devPortfolioSubmissionRouter, + '/', + async (req, user) => { + await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); + return { + submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) + }; + }, + 'dev-portfolio-submission' +); +loginCheckedPut( + devPortfolioSubmissionRouter, + '/regrade', + async (req, user) => ({ + portfolio: await regradeSubmissions(req.body.uuid, user) + }), + 'dev-portfolio-submission' +); +loginCheckedPut( + devPortfolioSubmissionRouter, + '/', + async (req, user) => ({ + portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) + }), + 'dev-portfolio-submission' +); +loginCheckedGet( + devPortfolioSubmissionRouter, + '/:email', + async (req, user) => ({ + submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) + }), + 'dev-portfolio-submission' +); From b4d3b14f442c9577004c60c73584db1509f32f1e Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:50:08 -0400 Subject: [PATCH 24/49] add rbac auth middleware --- backend/src/utils/auth.ts | 82 ++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 109822d9c..ffc332bdb 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -1,9 +1,12 @@ -import { RequestHandler, Request, Response, Router } from 'express'; +import { RequestHandler, Request, Response, Router, NextFunction } from 'express'; import admin from 'firebase-admin'; import { HandlerError } from './errors'; import PermissionsManager from './permissionsManager'; import { app as adminApp, env } from '../firebase'; import MembersDao from '../dao/MembersDao'; +import rbacConfig from '../../rbac.json'; +import { AuthRole } from '../types/DataTypes'; +import AuthRoleDao from '../dao/AuthRoleDao'; const getUserEmailFromRequest = async (request: Request): Promise => { const idToken = request.headers['auth-token']; @@ -12,6 +15,11 @@ const getUserEmailFromRequest = async (request: Request): Promise => { + const authRoleDao = new AuthRoleDao(); + return authRoleDao.getAuthRole(user); +}; + const loginCheckedHandler = (handler: (req: Request, user: IdolMember) => Promise>): RequestHandler => async (req: Request, res: Response): Promise => { @@ -39,26 +47,82 @@ const loginCheckedHandler = } }; +const isAuthorized = async ( + req: Request, + resource: string, + user: IdolMember, + rbacConfig: any +): Promise => { + const userRole = await getUserRole(user); + const resourceRBACConfig = rbacConfig.resources[resource]; + + if (userRole === 'admin') return true; + + if (req.method === 'GET') { + if (resourceRBACConfig.attributes.includes('meta_only') && req.query.meta_only) return true; + const canReadRoles = resourceRBACConfig.read_only.push(...resourceRBACConfig.read_and_write); + if (canReadRoles.includes(userRole)) return true; + } + + const canWriteRoles = resourceRBACConfig.read_and_write; + if (resourceRBACConfig.attributes.includes('email') && req.params.email) { + if (req.params.email === user.email) return true; + } + + if (canWriteRoles.includes('userRole')) return true; + + return false; +}; + +const getRBACMiddleware = + (resource?: string | undefined): RequestHandler => + async (req: Request, res: Response, next: NextFunction) => { + const userEmail = await getUserEmailFromRequest(req); + if (userEmail == null) { + res.status(440).json({ error: 'Not logged in!' }); + return; + } + const user = await MembersDao.getCurrentOrPastMemberByEmail(userEmail); + if (!user) { + res.status(401).send({ error: `No user with email: ${userEmail}` }); + return; + } + if ( + resource && + resource in Object.keys(rbacConfig.resources) && + !(await isAuthorized(req, resource, user, rbacConfig)) + ) { + res.status(401).send({ + error: `User with email ${user.email} does not have read and/or write access to the requested resource.` + }); + } + next(); + }; + export const loginCheckedGet = ( router: Router, path: string, - handler: (req: Request, user: IdolMember) => Promise> -): RequestHandler => router.get(path, loginCheckedHandler(handler)); + handler: (req: Request, user: IdolMember) => Promise>, + resource?: string +): RequestHandler => router.get(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedPost = ( router: Router, path: string, - handler: (req: Request, user: IdolMember) => Promise> -): RequestHandler => router.post(path, loginCheckedHandler(handler)); + handler: (req: Request, user: IdolMember) => Promise>, + resource?: string +): RequestHandler => router.post(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedDelete = ( router: Router, path: string, - handler: (req: Request, user: IdolMember) => Promise> -): RequestHandler => router.delete(path, loginCheckedHandler(handler)); + handler: (req: Request, user: IdolMember) => Promise>, + resource?: string +): RequestHandler => router.delete(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedPut = ( router: Router, path: string, - handler: (req: Request, user: IdolMember) => Promise> -): RequestHandler => router.put(path, loginCheckedHandler(handler)); + handler: (req: Request, user: IdolMember) => Promise>, + resource?: string +): RequestHandler => router.put(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); From 540239b7a54b7983de2a8b4bb563c756d050adf8 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:50:27 -0400 Subject: [PATCH 25/49] add auth role dao --- backend/src/dao/AuthRoleDao.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/src/dao/AuthRoleDao.ts diff --git a/backend/src/dao/AuthRoleDao.ts b/backend/src/dao/AuthRoleDao.ts new file mode 100644 index 000000000..275d31640 --- /dev/null +++ b/backend/src/dao/AuthRoleDao.ts @@ -0,0 +1,18 @@ +import BaseDao from './BaseDao'; +import { authRoleCollection } from '../firebase'; +import { AuthRoleDoc, AuthRole } from '../types/DataTypes'; + +export default class AuthRoleDao extends BaseDao { + constructor() { + super( + authRoleCollection, + async (doc) => doc, + async (data) => data + ); + } + + async getAuthRole(user: IdolMember): Promise { + const authRoleData = await this.getDocument(user.email); + return authRoleData?.role; + } +} From fda82ec45b9b6bbbbbe9d0835f145dedf16544dd Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 20 May 2023 13:50:43 -0400 Subject: [PATCH 26/49] configure rbac for dev portfolio --- backend/rbac.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/rbac.json diff --git a/backend/rbac.json b/backend/rbac.json new file mode 100644 index 000000000..5b55e7bfd --- /dev/null +++ b/backend/rbac.json @@ -0,0 +1,14 @@ +{ + "resources": { + "dev-portfolio": { + "attributes": ["meta_only"], + "read_only": ["lead"], + "read_and_write": ["dev_lead"] + }, + "dev-portfolio-submission": { + "attributes": ["email"], + "read_only": ["lead"], + "read_and_write": ["dev_lead"] + } + } +} From 964eb6e4a8e61c70d88404c0bc16ab42dce909f4 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sun, 21 May 2023 16:06:20 -0400 Subject: [PATCH 27/49] save authed user in res.locals --- backend/src/utils/auth.ts | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index ffc332bdb..9d5e160ff 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -23,19 +23,7 @@ const getUserRole = async (user: IdolMember): Promise => { const loginCheckedHandler = (handler: (req: Request, user: IdolMember) => Promise>): RequestHandler => async (req: Request, res: Response): Promise => { - const userEmail = await getUserEmailFromRequest(req); - if (userEmail == null) { - res.status(440).json({ error: 'Not logged in!' }); - return; - } - const user = await MembersDao.getCurrentOrPastMemberByEmail(userEmail); - if (!user) { - res.status(401).send({ error: `No user with email: ${userEmail}` }); - return; - } - if (env === 'staging' && !(await PermissionsManager.isAdmin(user))) { - res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); - } + const { user } = res.locals; try { res.status(200).send(await handler(req, user)); } catch (error) { @@ -74,9 +62,10 @@ const isAuthorized = async ( return false; }; -const getRBACMiddleware = - (resource?: string | undefined): RequestHandler => +const getAuthMiddleware = + (resource?: string): RequestHandler => async (req: Request, res: Response, next: NextFunction) => { + // authentication const userEmail = await getUserEmailFromRequest(req); if (userEmail == null) { res.status(440).json({ error: 'Not logged in!' }); @@ -87,6 +76,10 @@ const getRBACMiddleware = res.status(401).send({ error: `No user with email: ${userEmail}` }); return; } + if (env === 'staging' && !(await PermissionsManager.isAdmin(user))) { + res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); + } + // RBAC if ( resource && resource in Object.keys(rbacConfig.resources) && @@ -96,6 +89,7 @@ const getRBACMiddleware = error: `User with email ${user.email} does not have read and/or write access to the requested resource.` }); } + res.locals.user = user; next(); }; @@ -104,25 +98,25 @@ export const loginCheckedGet = ( path: string, handler: (req: Request, user: IdolMember) => Promise>, resource?: string -): RequestHandler => router.get(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); +): RequestHandler => router.get(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedPost = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, resource?: string -): RequestHandler => router.post(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); +): RequestHandler => router.post(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedDelete = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, resource?: string -): RequestHandler => router.delete(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); +): RequestHandler => router.delete(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); export const loginCheckedPut = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, resource?: string -): RequestHandler => router.put(path, getRBACMiddleware(resource), loginCheckedHandler(handler)); +): RequestHandler => router.put(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); From c31d32415a5af8d2b4275187ab60669dda71782a Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 09:26:46 -0400 Subject: [PATCH 28/49] =?UTF-8?q?=F0=9F=A4=B7=F0=9F=8F=BB=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/types/DataTypes.d.ts | 2 +- backend/src/utils/auth.ts | 2 +- frontend/src/API/ShoutoutsAPI.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/types/DataTypes.d.ts b/backend/src/types/DataTypes.d.ts index ce52ed4e3..17047ddff 100644 --- a/backend/src/types/DataTypes.d.ts +++ b/backend/src/types/DataTypes.d.ts @@ -103,7 +103,7 @@ type AuthLead = | 'ci_lead' | 'pm_lead'; -export type AuthRole = 'admin' | 'dev' | 'tpm' | 'pm' | 'designer' | AuthLead; +export type AuthRole = 'admin' | 'dev' | 'tpm' | 'pm' | 'designer' | 'business' | AuthLead; export interface AuthRoleDoc { role: AuthRole; diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 9d5e160ff..525673172 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -39,7 +39,7 @@ const isAuthorized = async ( req: Request, resource: string, user: IdolMember, - rbacConfig: any + rbacConfig: any // TODO: create type for rbacconfig ): Promise => { const userRole = await getUserRole(user); const resourceRBACConfig = rbacConfig.resources[resource]; diff --git a/frontend/src/API/ShoutoutsAPI.ts b/frontend/src/API/ShoutoutsAPI.ts index e0d15d8b6..27c770c90 100644 --- a/frontend/src/API/ShoutoutsAPI.ts +++ b/frontend/src/API/ShoutoutsAPI.ts @@ -49,6 +49,6 @@ export default class ShoutoutsAPI { } public static async deleteShoutout(uuid: string): Promise { - await APIWrapper.delete(`${backendURL}/deleteShoutout/${uuid}`); + await APIWrapper.delete(`${backendURL}/shoutout/${uuid}`); } } From a71f4a3490d49a679342b8459a75c10e01ba544a Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 10:56:11 -0400 Subject: [PATCH 29/49] change meta and owner rbac config --- backend/rbac.json | 10 ++++++---- backend/src/utils/auth.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index 5b55e7bfd..91a655935 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -1,14 +1,16 @@ { "resources": { "dev-portfolio": { - "attributes": ["meta_only"], "read_only": ["lead"], - "read_and_write": ["dev_lead"] + "read_and_write": ["dev_lead"], + "has_metdata": true, + "has_owner": false }, "dev-portfolio-submission": { - "attributes": ["email"], "read_only": ["lead"], - "read_and_write": ["dev_lead"] + "read_and_write": ["dev_lead"], + "has_metdata": true, + "has_owner": false } } } diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 525673172..cd30d04cf 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -47,13 +47,13 @@ const isAuthorized = async ( if (userRole === 'admin') return true; if (req.method === 'GET') { - if (resourceRBACConfig.attributes.includes('meta_only') && req.query.meta_only) return true; + if (resourceRBACConfig.has_metadata && req.query.meta_only) return true; const canReadRoles = resourceRBACConfig.read_only.push(...resourceRBACConfig.read_and_write); if (canReadRoles.includes(userRole)) return true; } const canWriteRoles = resourceRBACConfig.read_and_write; - if (resourceRBACConfig.attributes.includes('email') && req.params.email) { + if (resourceRBACConfig.has_owner && req.params.email) { if (req.params.email === user.email) return true; } From 60e60b98abf778a5c4d03078a56e86f737cf4c97 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 11:04:22 -0400 Subject: [PATCH 30/49] setup auth rules for profile images --- backend/rbac.json | 10 +++++++-- backend/src/API/candidateDeciderAPI.ts | 2 +- backend/src/API/imageAPI.ts | 22 ++++++++++++++----- frontend/src/API/ImagesAPI.ts | 8 +++---- .../Forms/UserProfile/UserProfileImage.tsx | 2 +- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index 91a655935..649751ae0 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -3,14 +3,20 @@ "dev-portfolio": { "read_only": ["lead"], "read_and_write": ["dev_lead"], - "has_metdata": true, + "has_metadata": true, "has_owner": false }, "dev-portfolio-submission": { "read_only": ["lead"], "read_and_write": ["dev_lead"], - "has_metdata": true, + "has_metadata": true, "has_owner": false + }, + "profile-image": { + "read_only": [], + "read_and_write": ["lead"], + "has_metadata": false, + "has_owner": true } } } diff --git a/backend/src/API/candidateDeciderAPI.ts b/backend/src/API/candidateDeciderAPI.ts index f95a17f1e..16c9e58cf 100644 --- a/backend/src/API/candidateDeciderAPI.ts +++ b/backend/src/API/candidateDeciderAPI.ts @@ -170,7 +170,7 @@ loginCheckedDelete(candidateDeciderRouter, '/:uuid', async (req, user) => loginCheckedPut(candidateDeciderRouter, '/:uuid/rating', (req, user) => updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) ); -loginCheckedPost(candidateDeciderRouter, '/decider/:uuid/comment', (req, user) => +loginCheckedPost(candidateDeciderRouter, '/:uuid/comment', (req, user) => updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( () => ({}) ) diff --git a/backend/src/API/imageAPI.ts b/backend/src/API/imageAPI.ts index 8aef70237..4318dae94 100644 --- a/backend/src/API/imageAPI.ts +++ b/backend/src/API/imageAPI.ts @@ -49,13 +49,23 @@ export const getMemberImage = async (user: IdolMember): Promise => { export const memberImageRouter = Router(); -loginCheckedGet(memberImageRouter, '/:email', async (_, user) => ({ - url: await getMemberImage(user) -})); +loginCheckedGet( + memberImageRouter, + '/:email', + async (_, user) => ({ + url: await getMemberImage(user) + }), + 'profile-image' +); -loginCheckedGet(memberImageRouter, '/signedURL', async (_, user) => ({ - url: await setMemberImage(user) -})); +loginCheckedGet( + memberImageRouter, + '/:email/signed-url', + async (_, user) => ({ + url: await setMemberImage(user) + }), + 'profile-image' +); memberImageRouter.get('/', async (_, res) => { const images = await allMemberImages(); diff --git a/frontend/src/API/ImagesAPI.ts b/frontend/src/API/ImagesAPI.ts index 5ddc91b4f..6e8f48039 100644 --- a/frontend/src/API/ImagesAPI.ts +++ b/frontend/src/API/ImagesAPI.ts @@ -17,15 +17,15 @@ export default class ImagesAPI { }); } - private static getSignedURL(): Promise { - const responseProm = APIWrapper.get(`${backendURL}/memberImage/signedURL`).then( + private static getSignedURL(email: string): Promise { + const responseProm = APIWrapper.get(`${backendURL}/memberImage/${email}/signed-url`).then( (res) => res.data ); return responseProm.then((val) => val.url); } - public static uploadMemberImage(body: Blob): Promise { - return this.getSignedURL().then((url) => { + public static uploadMemberImage(body: Blob, email: string): Promise { + return this.getSignedURL(email).then((url) => { const headers = { 'content-type': 'image/jpeg' }; APIWrapper.put(url, body, headers).then((res) => res.data); }); diff --git a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx index 6682fd32a..3bf9f1fcb 100644 --- a/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx +++ b/frontend/src/components/Forms/UserProfile/UserProfileImage.tsx @@ -31,7 +31,7 @@ const UserProfileImage: React.FC = () => { .then((res) => res.blob()) .then((blob) => { imageURL = window.URL.createObjectURL(blob); - ImagesAPI.uploadMemberImage(blob); + ImagesAPI.uploadMemberImage(blob, userInfo?.email || ''); setProfilePhoto(imageURL); }); } From ed5935fe16a6462b22ba10763a24b54838b6d0ff Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 11:08:18 -0400 Subject: [PATCH 31/49] setup auth rules for members --- backend/rbac.json | 12 +++++++ backend/src/API/memberAPI.ts | 59 ++++++++++++++++++++++++---------- frontend/src/API/MembersAPI.ts | 4 +-- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index 649751ae0..4a978765b 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -17,6 +17,18 @@ "read_and_write": ["lead"], "has_metadata": false, "has_owner": true + }, + "member": { + "read_only": [], + "read_and_write": ["lead"], + "has_metadata": false, + "has_owner": true + }, + "member-diff": { + "read_only": [], + "read_and_write": ["lead"], + "has_metadata": false, + "has_owner": false } } } diff --git a/backend/src/API/memberAPI.ts b/backend/src/API/memberAPI.ts index f71e113d8..36a7d5f17 100644 --- a/backend/src/API/memberAPI.ts +++ b/backend/src/API/memberAPI.ts @@ -145,20 +145,45 @@ memberRouter.get('/', async (req, res) => { res.status(200).json({ members }); }); -loginCheckedPost(memberRouter, '/', async (req, user) => ({ - member: await setMember(req.body, user) -})); -loginCheckedDelete(memberRouter, '/:email', async (req, user) => { - await deleteMember(req.params.email, user); - return {}; -}); -loginCheckedPut(memberRouter, '/', async (req, user) => ({ - member: await updateMember(req, req.body, user) -})); - -loginCheckedGet(memberDiffRouter, '/', async (_, user) => ({ - diffs: await getUserInformationDifference(user) -})); -loginCheckedPut(memberDiffRouter, '/', async (req, user) => ({ - member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) -})); +loginCheckedPost( + memberRouter, + '/:email', + async (req, user) => ({ + member: await setMember(req.body, user) + }), + 'member' +); +loginCheckedDelete( + memberRouter, + '/:email', + async (req, user) => { + await deleteMember(req.params.email, user); + return {}; + }, + 'member' +); +loginCheckedPut( + memberRouter, + '/:email', + async (req, user) => ({ + member: await updateMember(req, req.body, user) + }), + 'member' +); + +loginCheckedGet( + memberDiffRouter, + '/', + async (_, user) => ({ + diffs: await getUserInformationDifference(user) + }), + 'member-diff' +); +loginCheckedPut( + memberDiffRouter, + '/', + async (req, user) => ({ + member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) + }), + 'member-diff' +); diff --git a/frontend/src/API/MembersAPI.ts b/frontend/src/API/MembersAPI.ts index a28229ea2..9b448c1b4 100644 --- a/frontend/src/API/MembersAPI.ts +++ b/frontend/src/API/MembersAPI.ts @@ -14,7 +14,7 @@ export class MembersAPI { } public static setMember(member: Member): Promise { - return APIWrapper.post(`${backendURL}/member`, member).then((res) => res.data); + return APIWrapper.post(`${backendURL}/member/${member.email}`, member).then((res) => res.data); } public static deleteMember(memberEmail: string): Promise<{ status: number; error?: string }> { @@ -22,7 +22,7 @@ export class MembersAPI { } public static updateMember(member: Member): Promise { - return APIWrapper.put(`${backendURL}/member`, member).then((res) => res.data); + return APIWrapper.put(`${backendURL}/member/${member.email}`, member).then((res) => res.data); } public static hasIDOLAccess(email: string): Promise { From 0dd80d4d90d0b7e45e3b3cb36b5ff4d190714a2c Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 11:14:12 -0400 Subject: [PATCH 32/49] setup rbac for shoutotus --- backend/rbac.json | 6 ++++ backend/src/API/shoutoutAPI.ts | 50 ++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index 4a978765b..b5371c030 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -29,6 +29,12 @@ "read_and_write": ["lead"], "has_metadata": false, "has_owner": false + }, + "shoutout": { + "read_only": [], + "read_and_write": ["lead"], + "has_metadata": false, + "has_owner": true } } } diff --git a/backend/src/API/shoutoutAPI.ts b/backend/src/API/shoutoutAPI.ts index 53c91cdb0..b635255bd 100644 --- a/backend/src/API/shoutoutAPI.ts +++ b/backend/src/API/shoutoutAPI.ts @@ -68,24 +68,44 @@ export const deleteShoutout = async (uuid: string, user: IdolMember): Promise ({ - shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) -})); - -loginCheckedGet(shoutoutRouter, '/', async () => ({ - shoutouts: await getAllShoutouts() -})); +loginCheckedGet( + shoutoutRouter, + '/:email/:type', + async (req, user) => ({ + shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) + }), + 'shoutout' +); +loginCheckedGet( + shoutoutRouter, + '/', + async () => ({ + shoutouts: await getAllShoutouts() + }), + 'shoutout' +); +// No RBAC? loginCheckedPost(shoutoutRouter, '/', async (req, user) => ({ shoutout: await giveShoutout(req.body, user) })); -loginCheckedPut(shoutoutRouter, '/', async (req, user) => { - await hideShoutout(req.body.uuid, req.body.hide, user); - return {}; -}); +loginCheckedPut( + shoutoutRouter, + '/', + async (req, user) => { + await hideShoutout(req.body.uuid, req.body.hide, user); + return {}; + }, + 'shoutout' +); -loginCheckedDelete(shoutoutRouter, '/:uuid', async (req, user) => { - await deleteShoutout(req.params.uuid, user); - return {}; -}); +loginCheckedDelete( + shoutoutRouter, + '/:uuid', + async (req, user) => { + await deleteShoutout(req.params.uuid, user); + return {}; + }, + 'shoutout' +); From e9f6491dcc23c1f9291051833ac0e6988a38cf72 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 11:14:56 -0400 Subject: [PATCH 33/49] setup rbac for teams --- backend/rbac.json | 6 ++++++ backend/src/API/teamAPI.ts | 24 +++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index b5371c030..c9e4e860d 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -35,6 +35,12 @@ "read_and_write": ["lead"], "has_metadata": false, "has_owner": true + }, + "team": { + "read_only": [], + "read_and_write": ["lead"], + "has_metadata": false, + "has_owner": false } } } diff --git a/backend/src/API/teamAPI.ts b/backend/src/API/teamAPI.ts index 77bdaf5d7..735fa15ca 100644 --- a/backend/src/API/teamAPI.ts +++ b/backend/src/API/teamAPI.ts @@ -124,13 +124,23 @@ export const deleteTeam = async (teamBody: Team, member: IdolMember): Promise ({ teams: await allTeams() })); +loginCheckedGet(teamRouter, '/', async () => ({ teams: await allTeams() }), 'team'); -loginCheckedPut(teamRouter, '/', async (req, user) => ({ - team: await setTeam(req.body, user) -})); +loginCheckedPut( + teamRouter, + '/', + async (req, user) => ({ + team: await setTeam(req.body, user) + }), + 'team' +); // TODO: should eventually make this a delete request -loginCheckedPost(teamRouter, '/', async (req, user) => ({ - team: await deleteTeam(req.body, user) -})); +loginCheckedPost( + teamRouter, + '/', + async (req, user) => ({ + team: await deleteTeam(req.body, user) + }), + 'team' +); From b8bbf7f1997ca7b6f1f25df46186d9ceed6b3bd8 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 11:19:03 -0400 Subject: [PATCH 34/49] setup rbac for tec --- backend/rbac.json | 12 +++ backend/src/API/teamEventsAPI.ts | 125 ++++++++++++++++++++++--------- 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index c9e4e860d..be53d31e7 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -41,6 +41,18 @@ "read_and_write": ["lead"], "has_metadata": false, "has_owner": false + }, + "team-event": { + "read_only": ["lead"], + "read_and_write": ["ci_lead"], + "has_metadata": true, + "has_owner": false + }, + "team-event-attendance": { + "read_only": ["lead"], + "read_and_write": ["ci_lead"], + "has_metadata": false, + "has_owner": true } } } diff --git a/backend/src/API/teamEventsAPI.ts b/backend/src/API/teamEventsAPI.ts index ec72368bf..677121451 100644 --- a/backend/src/API/teamEventsAPI.ts +++ b/backend/src/API/teamEventsAPI.ts @@ -144,39 +144,92 @@ export const deleteTeamEventAttendance = async (uuid: string, user: IdolMember): }; export const teamEventRouter = Router(); - -loginCheckedPost(teamEventRouter, '/', async (req, user) => { - await createTeamEvent(req.body, user); - return {}; -}); -loginCheckedGet(teamEventRouter, '/:uuid', async (req, user) => ({ - event: await getTeamEvent(req.params.uuid, user) -})); -loginCheckedGet(teamEventRouter, '/', async (req, user) => ({ - events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() -})); -loginCheckedPut(teamEventRouter, '/', async (req, user) => ({ - event: await updateTeamEvent(req.body, user) -})); -loginCheckedDelete(teamEventRouter, '/:uuid', async (req, user) => { - await deleteTeamEvent(req.body, user); - return {}; -}); -loginCheckedDelete(teamEventRouter, '/', async (_, user) => { - await clearAllTeamEvents(user); - return {}; -}); -loginCheckedPost(teamEventRouter, '/attendance', async (req, user) => { - await requestTeamEventCredit(req.body.request, user); - return {}; -}); -loginCheckedGet(teamEventRouter, '/attendance/:email', async (_, user) => ({ - teamEventAttendance: await getTeamEventAttendanceByUser(user) -})); -loginCheckedPut(teamEventRouter, '/attendance', async (req, user) => ({ - teamEventAttendance: await updateTeamEventAttendance(req.body, user) -})); -loginCheckedDelete(teamEventRouter, '/attendance/:uuid', async (req, user) => { - await deleteTeamEventAttendance(req.params.uuid, user); - return {}; -}); +const teamEventAttendanceRouter = Router({ mergeParams: true }); + +teamEventRouter.use('/attendance', teamEventAttendanceRouter); + +loginCheckedPost( + teamEventRouter, + '/', + async (req, user) => { + await createTeamEvent(req.body, user); + return {}; + }, + 'team-event' +); +loginCheckedGet( + teamEventRouter, + '/:uuid', + async (req, user) => ({ + event: await getTeamEvent(req.params.uuid, user) + }), + 'team-event' +); +loginCheckedGet( + teamEventRouter, + '/', + async (req, user) => ({ + events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() + }), + 'team-event' +); +loginCheckedPut( + teamEventRouter, + '/', + async (req, user) => ({ + event: await updateTeamEvent(req.body, user) + }), + 'team-event' +); +loginCheckedDelete( + teamEventRouter, + '/:uuid', + async (req, user) => { + await deleteTeamEvent(req.body, user); + return {}; + }, + 'team-event' +); +loginCheckedDelete( + teamEventRouter, + '/', + async (_, user) => { + await clearAllTeamEvents(user); + return {}; + }, + 'team-event' +); +loginCheckedPost( + teamEventAttendanceRouter, + '/attendance', + async (req, user) => { + await requestTeamEventCredit(req.body.request, user); + return {}; + }, + 'team-event-attendance' +); +loginCheckedGet( + teamEventAttendanceRouter, + '/attendance/:email', + async (_, user) => ({ + teamEventAttendance: await getTeamEventAttendanceByUser(user) + }), + 'team-event-attendance' +); +loginCheckedPut( + teamEventAttendanceRouter, + '/attendance', + async (req, user) => ({ + teamEventAttendance: await updateTeamEventAttendance(req.body, user) + }), + 'team-event-attendance' +); +loginCheckedDelete( + teamEventAttendanceRouter, + '/attendance/:uuid', + async (req, user) => { + await deleteTeamEventAttendance(req.params.uuid, user); + return {}; + }, + 'team-event-attendance' +); From 32ec982fc1be58870ae35c0a32f0a7b87460a9a0 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:00:54 -0400 Subject: [PATCH 35/49] create separate files for routers --- backend/src/API/candidateDeciderAPI.ts | 33 ----- backend/src/API/devPortfolioAPI.ts | 86 ------------- backend/src/API/imageAPI.ts | 27 ---- backend/src/API/memberAPI.ts | 70 +---------- backend/src/API/shoutoutAPI.ts | 51 -------- backend/src/API/signInFormAPI.ts | 24 ---- backend/src/API/siteIntegrationAPI.ts | 14 --- backend/src/API/teamEventsAPI.ts | 98 --------------- backend/src/API/teamEventsImageAPI.ts | 24 +--- backend/src/api.ts | 21 ++-- backend/src/routers/candidateDeciderRouter.ts | 44 +++++++ backend/src/routers/devPortfolioRouter.ts | 100 +++++++++++++++ backend/src/routers/imageRouter.ts | 30 +++++ backend/src/routers/memberRouter.ts | 79 ++++++++++++ backend/src/routers/shoutoutRouter.ts | 59 +++++++++ backend/src/routers/signInRouter.ts | 35 ++++++ backend/src/routers/siteIntegrationRouter.ts | 22 ++++ backend/src/routers/teamEventRouter.ts | 116 ++++++++++++++++++ backend/src/routers/teamEventsImageRouter.ts | 22 ++++ backend/src/routers/teamRouter.ts | 0 20 files changed, 522 insertions(+), 433 deletions(-) create mode 100644 backend/src/routers/candidateDeciderRouter.ts create mode 100644 backend/src/routers/devPortfolioRouter.ts create mode 100644 backend/src/routers/imageRouter.ts create mode 100644 backend/src/routers/memberRouter.ts create mode 100644 backend/src/routers/shoutoutRouter.ts create mode 100644 backend/src/routers/signInRouter.ts create mode 100644 backend/src/routers/siteIntegrationRouter.ts create mode 100644 backend/src/routers/teamEventRouter.ts create mode 100644 backend/src/routers/teamEventsImageRouter.ts create mode 100644 backend/src/routers/teamRouter.ts diff --git a/backend/src/API/candidateDeciderAPI.ts b/backend/src/API/candidateDeciderAPI.ts index 16c9e58cf..29754da43 100644 --- a/backend/src/API/candidateDeciderAPI.ts +++ b/backend/src/API/candidateDeciderAPI.ts @@ -1,13 +1,6 @@ -import { Router } from 'express'; import CandidateDeciderDao from '../dao/CandidateDeciderDao'; import { NotFoundError, PermissionError } from '../utils/errors'; import PermissionsManager from '../utils/permissionsManager'; -import { - loginCheckedDelete, - loginCheckedGet, - loginCheckedPost, - loginCheckedPut -} from '../utils/auth'; const candidateDeciderDao = new CandidateDeciderDao(); @@ -149,29 +142,3 @@ export const updateCandidateDeciderComment = async ( }; candidateDeciderDao.updateInstance(updatedInstance); }; - -export const candidateDeciderRouter = Router(); - -loginCheckedGet(candidateDeciderRouter, '/', async (_, user) => ({ - instances: await getAllCandidateDeciderInstances(user) -})); -loginCheckedGet(candidateDeciderRouter, '/:uuid', async (req, user) => ({ - instance: await getCandidateDeciderInstance(req.params.uuid, user) -})); -loginCheckedPost(candidateDeciderRouter, '/', async (req, user) => ({ - instance: await createNewCandidateDeciderInstance(req.body, user) -})); -loginCheckedPut(candidateDeciderRouter, '/:uuid', async (req, user) => - toggleCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) -); -loginCheckedDelete(candidateDeciderRouter, '/:uuid', async (req, user) => - deleteCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) -); -loginCheckedPut(candidateDeciderRouter, '/:uuid/rating', (req, user) => - updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) -); -loginCheckedPost(candidateDeciderRouter, '/:uuid/comment', (req, user) => - updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( - () => ({}) - ) -); diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index a4a9cdde0..df15f14b7 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -1,16 +1,8 @@ -import { Router } from 'express'; import { DateTime } from 'luxon'; import DevPortfolioDao from '../dao/DevPortfolioDao'; import PermissionsManager from '../utils/permissionsManager'; import { PermissionError, BadRequestError, NotFoundError } from '../utils/errors'; import { validateSubmission, isWithinDates } from '../utils/githubUtil'; -import { - loginCheckedDelete, - loginCheckedGet, - loginCheckedPost, - loginCheckedPut -} from '../utils/auth'; -import DPSubmissionRequestLogDao from '../dao/DPSubmissionRequestLogDao'; const zonedTime = (timestamp: number, ianatz = 'America/New_York') => DateTime.fromMillis(timestamp, { zone: ianatz }); @@ -166,81 +158,3 @@ export const regradeSubmissions = async (uuid: string, user: IdolMember): Promis await devPortfolioDao.updateInstance(updatedDP); return updatedDP; }; - -export const devPortfolioRouter = Router(); -const devPortfolioSubmissionRouter = Router({ mergeParams: true }); -devPortfolioRouter.use('/:uuid/submission', devPortfolioSubmissionRouter); - -// /dev-portfolio -loginCheckedGet( - devPortfolioRouter, - '/', - async (req, user) => ({ - portfolios: !req.query.meta_only - ? await getAllDevPortfolios(user) - : await getAllDevPortfolioInfo() - }), - 'dev-portfolio' -); -loginCheckedGet( - devPortfolioRouter, - '/:uuid', - async (req, user) => ({ - portfolio: !req.query.meta_only - ? await getDevPortfolio(req.params.uuid, user) - : await getDevPortfolioInfo(req.params.uuid) - }), - 'dev-portfolio' -); - -loginCheckedPost( - devPortfolioRouter, - '/', - async (req, user) => ({ - portfolio: await createNewDevPortfolio(req.body, user) - }), - 'dev-portfolio' -); -loginCheckedDelete( - devPortfolioRouter, - '/:uuid', - async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})), - 'dev-portfolio' -); - -// devPortfolioSubmissionRouter: /dev-portfolio/:uuid/submission -loginCheckedPost( - devPortfolioSubmissionRouter, - '/', - async (req, user) => { - await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); - return { - submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) - }; - }, - 'dev-portfolio-submission' -); -loginCheckedPut( - devPortfolioSubmissionRouter, - '/regrade', - async (req, user) => ({ - portfolio: await regradeSubmissions(req.body.uuid, user) - }), - 'dev-portfolio-submission' -); -loginCheckedPut( - devPortfolioSubmissionRouter, - '/', - async (req, user) => ({ - portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) - }), - 'dev-portfolio-submission' -); -loginCheckedGet( - devPortfolioSubmissionRouter, - '/:email', - async (req, user) => ({ - submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) - }), - 'dev-portfolio-submission' -); diff --git a/backend/src/API/imageAPI.ts b/backend/src/API/imageAPI.ts index 4318dae94..d4e7587b9 100644 --- a/backend/src/API/imageAPI.ts +++ b/backend/src/API/imageAPI.ts @@ -1,8 +1,6 @@ -import { Router } from 'express'; import { bucket } from '../firebase'; import { getNetIDFromEmail, filterImagesResponse } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; -import { loginCheckedGet } from '../utils/auth'; export const allMemberImages = async (): Promise => { const files = await bucket.getFiles({ prefix: 'images/' }); @@ -46,28 +44,3 @@ export const getMemberImage = async (user: IdolMember): Promise => { }); return signedUrl[0]; }; - -export const memberImageRouter = Router(); - -loginCheckedGet( - memberImageRouter, - '/:email', - async (_, user) => ({ - url: await getMemberImage(user) - }), - 'profile-image' -); - -loginCheckedGet( - memberImageRouter, - '/:email/signed-url', - async (_, user) => ({ - url: await setMemberImage(user) - }), - 'profile-image' -); - -memberImageRouter.get('/', async (_, res) => { - const images = await allMemberImages(); - res.status(200).json({ images }); -}); diff --git a/backend/src/API/memberAPI.ts b/backend/src/API/memberAPI.ts index 36a7d5f17..66978c4a1 100644 --- a/backend/src/API/memberAPI.ts +++ b/backend/src/API/memberAPI.ts @@ -1,16 +1,10 @@ -import { Request, Router } from 'express'; +import { Request } from 'express'; import MembersDao from '../dao/MembersDao'; import PermissionsManager from '../utils/permissionsManager'; import { BadRequestError, PermissionError } from '../utils/errors'; import { bucket } from '../firebase'; import { getNetIDFromEmail, computeMembersDiff } from '../utils/memberUtil'; import { sendMemberUpdateNotifications } from './mailAPI'; -import { - loginCheckedDelete, - loginCheckedGet, - loginCheckedPost, - loginCheckedPut -} from '../utils/auth'; const membersDao = new MembersDao(); @@ -125,65 +119,3 @@ export const reviewUserInformationChange = async ( MembersDao.revertMemberInformationChanges(rejected) ]); }; - -export const memberRouter = Router(); -export const memberDiffRouter = Router(); - -memberRouter.get('/', async (req, res) => { - const type = req.query.type as string | undefined; - let members; - switch (type) { - case 'all-semesters': - members = await MembersDao.getMembersFromAllSemesters(); - break; - case 'approved': - members = await allApprovedMembers(); - break; - default: - members = await allMembers(); - } - res.status(200).json({ members }); -}); - -loginCheckedPost( - memberRouter, - '/:email', - async (req, user) => ({ - member: await setMember(req.body, user) - }), - 'member' -); -loginCheckedDelete( - memberRouter, - '/:email', - async (req, user) => { - await deleteMember(req.params.email, user); - return {}; - }, - 'member' -); -loginCheckedPut( - memberRouter, - '/:email', - async (req, user) => ({ - member: await updateMember(req, req.body, user) - }), - 'member' -); - -loginCheckedGet( - memberDiffRouter, - '/', - async (_, user) => ({ - diffs: await getUserInformationDifference(user) - }), - 'member-diff' -); -loginCheckedPut( - memberDiffRouter, - '/', - async (req, user) => ({ - member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) - }), - 'member-diff' -); diff --git a/backend/src/API/shoutoutAPI.ts b/backend/src/API/shoutoutAPI.ts index b635255bd..b748d50dd 100644 --- a/backend/src/API/shoutoutAPI.ts +++ b/backend/src/API/shoutoutAPI.ts @@ -1,13 +1,6 @@ -import { Router } from 'express'; import PermissionsManager from '../utils/permissionsManager'; import { NotFoundError, PermissionError } from '../utils/errors'; import ShoutoutsDao from '../dao/ShoutoutsDao'; -import { - loginCheckedGet, - loginCheckedPost, - loginCheckedPut, - loginCheckedDelete -} from '../utils/auth'; const shoutoutsDao = new ShoutoutsDao(); @@ -65,47 +58,3 @@ export const deleteShoutout = async (uuid: string, user: IdolMember): Promise ({ - shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) - }), - 'shoutout' -); - -loginCheckedGet( - shoutoutRouter, - '/', - async () => ({ - shoutouts: await getAllShoutouts() - }), - 'shoutout' -); -// No RBAC? -loginCheckedPost(shoutoutRouter, '/', async (req, user) => ({ - shoutout: await giveShoutout(req.body, user) -})); - -loginCheckedPut( - shoutoutRouter, - '/', - async (req, user) => { - await hideShoutout(req.body.uuid, req.body.hide, user); - return {}; - }, - 'shoutout' -); - -loginCheckedDelete( - shoutoutRouter, - '/:uuid', - async (req, user) => { - await deleteShoutout(req.params.uuid, user); - return {}; - }, - 'shoutout' -); diff --git a/backend/src/API/signInFormAPI.ts b/backend/src/API/signInFormAPI.ts index f1d1774a4..6965babc7 100644 --- a/backend/src/API/signInFormAPI.ts +++ b/backend/src/API/signInFormAPI.ts @@ -1,9 +1,7 @@ -import { Router } from 'express'; import SignInFormDao from '../dao/SignInFormDao'; import { PermissionError, BadRequestError, NotFoundError } from '../utils/errors'; import { signInFormCollection, memberCollection } from '../firebase'; import PermissionsManager from '../utils/permissionsManager'; -import { loginCheckedGet, loginCheckedPost } from '../utils/auth'; const checkIfDocExists = async (id: string): Promise => (await signInFormCollection.doc(id).get()).exists; @@ -89,25 +87,3 @@ export const getSignInPrompt = async (id: string): Promise = if (!signInForm) throw new NotFoundError(`Sign-in form with id ${id} does not exist!`); return signInForm.prompt; }; - -export const signInRouter = Router(); -loginCheckedPost(signInRouter, '/signInExists', async (req, _) => ({ - exists: await signInFormExists(req.body.id) -})); -loginCheckedPost(signInRouter, '/signInExpired', async (req, _) => ({ - expired: await signInFormExpired(req.body.id) -})); -loginCheckedPost(signInRouter, '/signInCreate', async (req, user) => - createSignInForm(req.body.id, req.body.expireAt, req.body.prompt, user) -); -loginCheckedPost(signInRouter, '/signInDelete', async (req, user) => { - await deleteSignInForm(req.body.id, user); - return {}; -}); -loginCheckedPost(signInRouter, '/signIn', async (req, user) => - signIn(req.body.id, req.body.response, user) -); -loginCheckedPost(signInRouter, '/signInAll', async (_, user) => allSignInForms(user)); -loginCheckedGet(signInRouter, '/signInPrompt/:id', async (req, _) => ({ - prompt: await getSignInPrompt(req.params.id) -})); diff --git a/backend/src/API/siteIntegrationAPI.ts b/backend/src/API/siteIntegrationAPI.ts index 780bcb44d..cdba7ff83 100644 --- a/backend/src/API/siteIntegrationAPI.ts +++ b/backend/src/API/siteIntegrationAPI.ts @@ -1,9 +1,7 @@ -import { Router } from 'express'; import { Octokit } from '@octokit/rest'; import { PRResponse } from '../types/GithubTypes'; import PermissionsManager from '../utils/permissionsManager'; import { PermissionError, BadRequestError } from '../utils/errors'; -import { loginCheckedGet, loginCheckedPost } from '../utils/auth'; require('dotenv').config(); @@ -110,15 +108,3 @@ const checkPermissions = async (user: IdolMember): Promise => { ); } }; - -export const siteIntegrationRouter = Router(); - -loginCheckedPost(siteIntegrationRouter, '/pullIDOLChanges', (_, user) => - requestIDOLPullDispatch(user) -); - -loginCheckedGet(siteIntegrationRouter, '/getIDOLChangesPR', (_, user) => getIDOLChangesPR(user)); - -loginCheckedPost(siteIntegrationRouter, '/acceptIDOLChanges', (_, user) => acceptIDOLChanges(user)); - -loginCheckedPost(siteIntegrationRouter, '/rejectIDOLChanges', (_, user) => rejectIDOLChanges(user)); diff --git a/backend/src/API/teamEventsAPI.ts b/backend/src/API/teamEventsAPI.ts index 677121451..f3075ea6e 100644 --- a/backend/src/API/teamEventsAPI.ts +++ b/backend/src/API/teamEventsAPI.ts @@ -1,14 +1,7 @@ -import { Router } from 'express'; import TeamEventAttendanceDao from '../dao/TeamEventAttendanceDao'; import TeamEventsDao from '../dao/TeamEventsDao'; import { PermissionError } from '../utils/errors'; import PermissionsManager from '../utils/permissionsManager'; -import { - loginCheckedDelete, - loginCheckedPost, - loginCheckedGet, - loginCheckedPut -} from '../utils/auth'; const teamEventAttendanceDao = new TeamEventAttendanceDao(); @@ -142,94 +135,3 @@ export const deleteTeamEventAttendance = async (uuid: string, user: IdolMember): } await teamEventAttendanceDao.deleteTeamEventAttendance(uuid); }; - -export const teamEventRouter = Router(); -const teamEventAttendanceRouter = Router({ mergeParams: true }); - -teamEventRouter.use('/attendance', teamEventAttendanceRouter); - -loginCheckedPost( - teamEventRouter, - '/', - async (req, user) => { - await createTeamEvent(req.body, user); - return {}; - }, - 'team-event' -); -loginCheckedGet( - teamEventRouter, - '/:uuid', - async (req, user) => ({ - event: await getTeamEvent(req.params.uuid, user) - }), - 'team-event' -); -loginCheckedGet( - teamEventRouter, - '/', - async (req, user) => ({ - events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() - }), - 'team-event' -); -loginCheckedPut( - teamEventRouter, - '/', - async (req, user) => ({ - event: await updateTeamEvent(req.body, user) - }), - 'team-event' -); -loginCheckedDelete( - teamEventRouter, - '/:uuid', - async (req, user) => { - await deleteTeamEvent(req.body, user); - return {}; - }, - 'team-event' -); -loginCheckedDelete( - teamEventRouter, - '/', - async (_, user) => { - await clearAllTeamEvents(user); - return {}; - }, - 'team-event' -); -loginCheckedPost( - teamEventAttendanceRouter, - '/attendance', - async (req, user) => { - await requestTeamEventCredit(req.body.request, user); - return {}; - }, - 'team-event-attendance' -); -loginCheckedGet( - teamEventAttendanceRouter, - '/attendance/:email', - async (_, user) => ({ - teamEventAttendance: await getTeamEventAttendanceByUser(user) - }), - 'team-event-attendance' -); -loginCheckedPut( - teamEventAttendanceRouter, - '/attendance', - async (req, user) => ({ - teamEventAttendance: await updateTeamEventAttendance(req.body, user) - }), - 'team-event-attendance' -); -loginCheckedDelete( - teamEventAttendanceRouter, - '/attendance/:uuid', - async (req, user) => { - await deleteTeamEventAttendance(req.params.uuid, user); - return {}; - }, - 'team-event-attendance' -); diff --git a/backend/src/API/teamEventsImageAPI.ts b/backend/src/API/teamEventsImageAPI.ts index 972aa99a7..c5ba0b57b 100644 --- a/backend/src/API/teamEventsImageAPI.ts +++ b/backend/src/API/teamEventsImageAPI.ts @@ -1,10 +1,7 @@ -import { Router } from 'express'; import { bucket } from '../firebase'; -// import { getNetIDFromEmail } from '../utils/memberUtil'; import { NotFoundError } from '../utils/errors'; -import { loginCheckedGet, loginCheckedDelete } from '../utils/auth'; -const setEventProofImage = async (name: string, user: IdolMember): Promise => { +export const setEventProofImage = async (name: string, user: IdolMember): Promise => { const file = bucket.file(`${name}.jpg`); const signedURL = await file.getSignedUrl({ action: 'write', @@ -14,7 +11,7 @@ const setEventProofImage = async (name: string, user: IdolMember): Promise => { +export const getEventProofImage = async (name: string, user: IdolMember): Promise => { const file = bucket.file(`${name}.jpg`); const fileExists = await file.exists().then((result) => result[0]); if (!fileExists) { @@ -56,7 +53,7 @@ const getEventProofImage = async (name: string, user: IdolMember): Promise => { +export const deleteEventProofImage = async (name: string, user: IdolMember): Promise => { const imageFile = bucket.file(`${name}.jpg`); await imageFile.delete(); }; @@ -70,18 +67,3 @@ const deleteEventProofImage = async (name: string, user: IdolMember): Promise ({ - url: await getEventProofImage(req.params.name, user) -})); -loginCheckedGet(eventProofImageRouter, '/:name(*)/signed-url', async (req, user) => ({ - url: await setEventProofImage(req.params.name, user) -})); -loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { - await deleteEventProofImage(req.params.name, user); - return {}; -}); - -export default eventProofImageRouter; diff --git a/backend/src/api.ts b/backend/src/api.ts index 1f0293372..a8f508320 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -4,17 +4,18 @@ import cors from 'cors'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import { env } from './firebase'; -import { siteIntegrationRouter } from './API/siteIntegrationAPI'; -import { memberRouter, memberDiffRouter, getMember } from './API/memberAPI'; -import { memberImageRouter } from './API/imageAPI'; -import { teamRouter } from './API/teamAPI'; -import { shoutoutRouter } from './API/shoutoutAPI'; -import { signInRouter } from './API/signInFormAPI'; -import { teamEventRouter } from './API/teamEventsAPI'; -import { candidateDeciderRouter } from './API/candidateDeciderAPI'; -import eventProofImageRouter from './API/teamEventsImageAPI'; -import { devPortfolioRouter } from './API/devPortfolioAPI'; import AdminsDao from './dao/AdminsDao'; +import { getMember } from './API/memberAPI'; +import { teamRouter } from './API/teamAPI'; +import candidateDeciderRouter from './routers/candidateDeciderRouter'; +import devPortfolioRouter from './routers/devPortfolioRouter'; +import memberImageRouter from './routers/imageRouter'; +import { memberRouter, memberDiffRouter } from './routers/memberRouter'; +import shoutoutRouter from './routers/shoutoutRouter'; +import signInRouter from './routers/signInRouter'; +import siteIntegrationRouter from './routers/siteIntegrationRouter'; +import teamEventRouter from './routers/teamEventRouter'; +import eventProofImageRouter from './routers/teamEventsImageRouter'; // Constants and configurations const app = express(); diff --git a/backend/src/routers/candidateDeciderRouter.ts b/backend/src/routers/candidateDeciderRouter.ts new file mode 100644 index 000000000..d9ccfcb49 --- /dev/null +++ b/backend/src/routers/candidateDeciderRouter.ts @@ -0,0 +1,44 @@ +import { Router } from 'express'; +import { + loginCheckedDelete, + loginCheckedGet, + loginCheckedPost, + loginCheckedPut +} from '../utils/auth'; +import { + createNewCandidateDeciderInstance, + deleteCandidateDeciderInstance, + getAllCandidateDeciderInstances, + getCandidateDeciderInstance, + toggleCandidateDeciderInstance, + updateCandidateDeciderComment, + updateCandidateDeciderRating +} from '../API/candidateDeciderAPI'; + +const candidateDeciderRouter = Router(); + +loginCheckedGet(candidateDeciderRouter, '/', async (_, user) => ({ + instances: await getAllCandidateDeciderInstances(user) +})); +loginCheckedGet(candidateDeciderRouter, '/:uuid', async (req, user) => ({ + instance: await getCandidateDeciderInstance(req.params.uuid, user) +})); +loginCheckedPost(candidateDeciderRouter, '/', async (req, user) => ({ + instance: await createNewCandidateDeciderInstance(req.body, user) +})); +loginCheckedPut(candidateDeciderRouter, '/:uuid', async (req, user) => + toggleCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) +); +loginCheckedDelete(candidateDeciderRouter, '/:uuid', async (req, user) => + deleteCandidateDeciderInstance(req.params.uuid, user).then(() => ({})) +); +loginCheckedPut(candidateDeciderRouter, '/:uuid/rating', (req, user) => + updateCandidateDeciderRating(user, req.params.uuid, req.body.id, req.body.rating).then(() => ({})) +); +loginCheckedPost(candidateDeciderRouter, '/:uuid/comment', (req, user) => + updateCandidateDeciderComment(user, req.params.uuid, req.body.id, req.body.comment).then( + () => ({}) + ) +); + +export default candidateDeciderRouter; diff --git a/backend/src/routers/devPortfolioRouter.ts b/backend/src/routers/devPortfolioRouter.ts new file mode 100644 index 000000000..a898399e7 --- /dev/null +++ b/backend/src/routers/devPortfolioRouter.ts @@ -0,0 +1,100 @@ +import { Router } from 'express'; +import { + loginCheckedDelete, + loginCheckedGet, + loginCheckedPost, + loginCheckedPut +} from '../utils/auth'; +import { + getAllDevPortfolios, + getAllDevPortfolioInfo, + getDevPortfolio, + getDevPortfolioInfo, + createNewDevPortfolio, + deleteDevPortfolio, + makeDevPortfolioSubmission, + regradeSubmissions, + updateSubmissions, + getUsersDevPortfolioSubmissions +} from '../API/devPortfolioAPI'; +import DPSubmissionRequestLogDao from '../dao/DPSubmissionRequestLogDao'; + +const devPortfolioRouter = Router(); +const devPortfolioSubmissionRouter = Router({ mergeParams: true }); +devPortfolioRouter.use('/:uuid/submission', devPortfolioSubmissionRouter); + +// /dev-portfolio +loginCheckedGet( + devPortfolioRouter, + '/', + async (req, user) => ({ + portfolios: !req.query.meta_only + ? await getAllDevPortfolios(user) + : await getAllDevPortfolioInfo() + }), + 'dev-portfolio' +); +loginCheckedGet( + devPortfolioRouter, + '/:uuid', + async (req, user) => ({ + portfolio: !req.query.meta_only + ? await getDevPortfolio(req.params.uuid, user) + : await getDevPortfolioInfo(req.params.uuid) + }), + 'dev-portfolio' +); + +loginCheckedPost( + devPortfolioRouter, + '/', + async (req, user) => ({ + portfolio: await createNewDevPortfolio(req.body, user) + }), + 'dev-portfolio' +); +loginCheckedDelete( + devPortfolioRouter, + '/:uuid', + async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})), + 'dev-portfolio' +); + +// devPortfolioSubmissionRouter: /dev-portfolio/:uuid/submission +loginCheckedPost( + devPortfolioSubmissionRouter, + '/', + async (req, user) => { + await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); + return { + submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) + }; + }, + 'dev-portfolio-submission' +); +loginCheckedPut( + devPortfolioSubmissionRouter, + '/regrade', + async (req, user) => ({ + portfolio: await regradeSubmissions(req.body.uuid, user) + }), + 'dev-portfolio-submission' +); +loginCheckedPut( + devPortfolioSubmissionRouter, + '/', + async (req, user) => ({ + portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) + }), + 'dev-portfolio-submission' +); +loginCheckedGet( + devPortfolioSubmissionRouter, + '/:email', + async (req, user) => ({ + submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) + }), + 'dev-portfolio-submission' +); + +export default devPortfolioRouter; diff --git a/backend/src/routers/imageRouter.ts b/backend/src/routers/imageRouter.ts new file mode 100644 index 000000000..32084e5fe --- /dev/null +++ b/backend/src/routers/imageRouter.ts @@ -0,0 +1,30 @@ +import { Router } from 'express'; +import { getMemberImage, setMemberImage, allMemberImages } from '../API/imageAPI'; +import { loginCheckedGet } from '../utils/auth'; + +const memberImageRouter = Router(); + +loginCheckedGet( + memberImageRouter, + '/:email', + async (_, user) => ({ + url: await getMemberImage(user) + }), + 'profile-image' +); + +loginCheckedGet( + memberImageRouter, + '/:email/signed-url', + async (_, user) => ({ + url: await setMemberImage(user) + }), + 'profile-image' +); + +memberImageRouter.get('/', async (_, res) => { + const images = await allMemberImages(); + res.status(200).json({ images }); +}); + +export default memberImageRouter; diff --git a/backend/src/routers/memberRouter.ts b/backend/src/routers/memberRouter.ts new file mode 100644 index 000000000..a25c3ad18 --- /dev/null +++ b/backend/src/routers/memberRouter.ts @@ -0,0 +1,79 @@ +import { Router } from 'express'; +import { + allApprovedMembers, + allMembers, + setMember, + deleteMember, + updateMember, + getUserInformationDifference, + reviewUserInformationChange +} from '../API/memberAPI'; +import MembersDao from '../dao/MembersDao'; +import { + loginCheckedPost, + loginCheckedDelete, + loginCheckedPut, + loginCheckedGet +} from '../utils/auth'; + +export const memberRouter = Router(); +export const memberDiffRouter = Router(); + +memberRouter.get('/', async (req, res) => { + const type = req.query.type as string | undefined; + let members; + switch (type) { + case 'all-semesters': + members = await MembersDao.getMembersFromAllSemesters(); + break; + case 'approved': + members = await allApprovedMembers(); + break; + default: + members = await allMembers(); + } + res.status(200).json({ members }); +}); + +loginCheckedPost( + memberRouter, + '/:email', + async (req, user) => ({ + member: await setMember(req.body, user) + }), + 'member' +); +loginCheckedDelete( + memberRouter, + '/:email', + async (req, user) => { + await deleteMember(req.params.email, user); + return {}; + }, + 'member' +); +loginCheckedPut( + memberRouter, + '/:email', + async (req, user) => ({ + member: await updateMember(req, req.body, user) + }), + 'member' +); + +loginCheckedGet( + memberDiffRouter, + '/', + async (_, user) => ({ + diffs: await getUserInformationDifference(user) + }), + 'member-diff' +); +loginCheckedPut( + memberDiffRouter, + '/', + async (req, user) => ({ + member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) + }), + 'member-diff' +); diff --git a/backend/src/routers/shoutoutRouter.ts b/backend/src/routers/shoutoutRouter.ts new file mode 100644 index 000000000..fd65ef562 --- /dev/null +++ b/backend/src/routers/shoutoutRouter.ts @@ -0,0 +1,59 @@ +import { Router } from 'express'; +import { + getShoutouts, + getAllShoutouts, + giveShoutout, + hideShoutout, + deleteShoutout +} from '../API/shoutoutAPI'; +import { + loginCheckedGet, + loginCheckedPost, + loginCheckedPut, + loginCheckedDelete +} from '../utils/auth'; + +const shoutoutRouter = Router(); + +loginCheckedGet( + shoutoutRouter, + '/:email/:type', + async (req, user) => ({ + shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) + }), + 'shoutout' +); + +loginCheckedGet( + shoutoutRouter, + '/', + async () => ({ + shoutouts: await getAllShoutouts() + }), + 'shoutout' +); +// No RBAC? +loginCheckedPost(shoutoutRouter, '/', async (req, user) => ({ + shoutout: await giveShoutout(req.body, user) +})); + +loginCheckedPut( + shoutoutRouter, + '/', + async (req, user) => { + await hideShoutout(req.body.uuid, req.body.hide, user); + return {}; + }, + 'shoutout' +); + +loginCheckedDelete( + shoutoutRouter, + '/:uuid', + async (req, user) => { + await deleteShoutout(req.params.uuid, user); + return {}; + }, + 'shoutout' +); +export default shoutoutRouter; diff --git a/backend/src/routers/signInRouter.ts b/backend/src/routers/signInRouter.ts new file mode 100644 index 000000000..d91fc186a --- /dev/null +++ b/backend/src/routers/signInRouter.ts @@ -0,0 +1,35 @@ +import { Router } from 'express'; +import { + signInFormExists, + signInFormExpired, + createSignInForm, + deleteSignInForm, + signIn, + allSignInForms, + getSignInPrompt +} from '../API/signInFormAPI'; +import { loginCheckedPost, loginCheckedGet } from '../utils/auth'; + +const signInRouter = Router(); +loginCheckedPost(signInRouter, '/signInExists', async (req, _) => ({ + exists: await signInFormExists(req.body.id) +})); +loginCheckedPost(signInRouter, '/signInExpired', async (req, _) => ({ + expired: await signInFormExpired(req.body.id) +})); +loginCheckedPost(signInRouter, '/signInCreate', async (req, user) => + createSignInForm(req.body.id, req.body.expireAt, req.body.prompt, user) +); +loginCheckedPost(signInRouter, '/signInDelete', async (req, user) => { + await deleteSignInForm(req.body.id, user); + return {}; +}); +loginCheckedPost(signInRouter, '/signIn', async (req, user) => + signIn(req.body.id, req.body.response, user) +); +loginCheckedPost(signInRouter, '/signInAll', async (_, user) => allSignInForms(user)); +loginCheckedGet(signInRouter, '/signInPrompt/:id', async (req, _) => ({ + prompt: await getSignInPrompt(req.params.id) +})); + +export default signInRouter; diff --git a/backend/src/routers/siteIntegrationRouter.ts b/backend/src/routers/siteIntegrationRouter.ts new file mode 100644 index 000000000..7db2d3b57 --- /dev/null +++ b/backend/src/routers/siteIntegrationRouter.ts @@ -0,0 +1,22 @@ +import { Router } from 'express'; +import { + requestIDOLPullDispatch, + getIDOLChangesPR, + acceptIDOLChanges, + rejectIDOLChanges +} from '../API/siteIntegrationAPI'; +import { loginCheckedPost, loginCheckedGet } from '../utils/auth'; + +const siteIntegrationRouter = Router(); + +loginCheckedPost(siteIntegrationRouter, '/pullIDOLChanges', (_, user) => + requestIDOLPullDispatch(user) +); + +loginCheckedGet(siteIntegrationRouter, '/getIDOLChangesPR', (_, user) => getIDOLChangesPR(user)); + +loginCheckedPost(siteIntegrationRouter, '/acceptIDOLChanges', (_, user) => acceptIDOLChanges(user)); + +loginCheckedPost(siteIntegrationRouter, '/rejectIDOLChanges', (_, user) => rejectIDOLChanges(user)); + +export default siteIntegrationRouter; diff --git a/backend/src/routers/teamEventRouter.ts b/backend/src/routers/teamEventRouter.ts new file mode 100644 index 000000000..a3d43ca2f --- /dev/null +++ b/backend/src/routers/teamEventRouter.ts @@ -0,0 +1,116 @@ +import { Router } from 'express'; +import { + createTeamEvent, + getTeamEvent, + getAllTeamEvents, + getAllTeamEventInfo, + updateTeamEvent, + deleteTeamEvent, + clearAllTeamEvents, + requestTeamEventCredit, + getTeamEventAttendanceByUser, + updateTeamEventAttendance, + deleteTeamEventAttendance +} from '../API/teamEventsAPI'; +import { + loginCheckedPost, + loginCheckedGet, + loginCheckedPut, + loginCheckedDelete +} from '../utils/auth'; + +const teamEventRouter = Router(); +const teamEventAttendanceRouter = Router({ mergeParams: true }); + +teamEventRouter.use('/attendance', teamEventAttendanceRouter); + +// /team-event +loginCheckedPost( + teamEventRouter, + '/', + async (req, user) => { + await createTeamEvent(req.body, user); + return {}; + }, + 'team-event' +); +loginCheckedGet( + teamEventRouter, + '/:uuid', + async (req, user) => ({ + event: await getTeamEvent(req.params.uuid, user) + }), + 'team-event' +); +loginCheckedGet( + teamEventRouter, + '/', + async (req, user) => ({ + events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() + }), + 'team-event' +); +loginCheckedPut( + teamEventRouter, + '/', + async (req, user) => ({ + event: await updateTeamEvent(req.body, user) + }), + 'team-event' +); +loginCheckedDelete( + teamEventRouter, + '/:uuid', + async (req, user) => { + await deleteTeamEvent(req.body, user); + return {}; + }, + 'team-event' +); +loginCheckedDelete( + teamEventRouter, + '/', + async (_, user) => { + await clearAllTeamEvents(user); + return {}; + }, + 'team-event' +); + +// /team-event/attendance +loginCheckedPost( + teamEventAttendanceRouter, + '/attendance', + async (req, user) => { + await requestTeamEventCredit(req.body.request, user); + return {}; + }, + 'team-event-attendance' +); +loginCheckedGet( + teamEventAttendanceRouter, + '/attendance/:email', + async (_, user) => ({ + teamEventAttendance: await getTeamEventAttendanceByUser(user) + }), + 'team-event-attendance' +); +loginCheckedPut( + teamEventAttendanceRouter, + '/attendance', + async (req, user) => ({ + teamEventAttendance: await updateTeamEventAttendance(req.body, user) + }), + 'team-event-attendance' +); +loginCheckedDelete( + teamEventAttendanceRouter, + '/attendance/:uuid', + async (req, user) => { + await deleteTeamEventAttendance(req.params.uuid, user); + return {}; + }, + 'team-event-attendance' +); + +export default teamEventRouter; diff --git a/backend/src/routers/teamEventsImageRouter.ts b/backend/src/routers/teamEventsImageRouter.ts new file mode 100644 index 000000000..f6c8e27ec --- /dev/null +++ b/backend/src/routers/teamEventsImageRouter.ts @@ -0,0 +1,22 @@ +import { Router } from 'express'; +import { loginCheckedGet, loginCheckedDelete } from '../utils/auth'; +import { + getEventProofImage, + setEventProofImage, + deleteEventProofImage +} from '../API/teamEventsImageAPI'; + +const eventProofImageRouter = Router(); + +loginCheckedGet(eventProofImageRouter, '/:name(*)', async (req, user) => ({ + url: await getEventProofImage(req.params.name, user) +})); +loginCheckedGet(eventProofImageRouter, '/:name(*)/signed-url', async (req, user) => ({ + url: await setEventProofImage(req.params.name, user) +})); +loginCheckedDelete(eventProofImageRouter, '/:name', async (req, user) => { + await deleteEventProofImage(req.params.name, user); + return {}; +}); + +export default eventProofImageRouter; diff --git a/backend/src/routers/teamRouter.ts b/backend/src/routers/teamRouter.ts new file mode 100644 index 000000000..e69de29bb From 0a132ab6b3f8faaf32b8432fa692396c25b6fb0b Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:07:50 -0400 Subject: [PATCH 36/49] remove redundant permissions check in api logic --- backend/src/API/devPortfolioAPI.ts | 44 ++------------------------- backend/src/API/memberAPI.ts | 39 ++---------------------- backend/src/API/shoutoutAPI.ts | 23 +------------- backend/src/API/siteIntegrationAPI.ts | 14 --------- backend/src/API/teamAPI.ts | 15 +-------- backend/src/API/teamEventsAPI.ts | 37 ---------------------- 6 files changed, 8 insertions(+), 164 deletions(-) diff --git a/backend/src/API/devPortfolioAPI.ts b/backend/src/API/devPortfolioAPI.ts index df15f14b7..4aed13e81 100644 --- a/backend/src/API/devPortfolioAPI.ts +++ b/backend/src/API/devPortfolioAPI.ts @@ -1,7 +1,6 @@ import { DateTime } from 'luxon'; import DevPortfolioDao from '../dao/DevPortfolioDao'; -import PermissionsManager from '../utils/permissionsManager'; -import { PermissionError, BadRequestError, NotFoundError } from '../utils/errors'; +import { BadRequestError, NotFoundError } from '../utils/errors'; import { validateSubmission, isWithinDates } from '../utils/githubUtil'; const zonedTime = (timestamp: number, ianatz = 'America/New_York') => @@ -9,14 +8,8 @@ const zonedTime = (timestamp: number, ianatz = 'America/New_York') => export const devPortfolioDao = new DevPortfolioDao(); -export const getAllDevPortfolios = async (user: IdolMember): Promise => { - const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); - if (!isLeadOrAdmin) - throw new PermissionError( - `User with email ${user.email} does not have permission to view dev portfolios!` - ); - return devPortfolioDao.getAllInstances(); -}; +export const getAllDevPortfolios = async (user: IdolMember): Promise => + devPortfolioDao.getAllInstances(); export const getAllDevPortfolioInfo = async (): Promise => devPortfolioDao.getAllDevPortfolioInfo(); @@ -30,11 +23,6 @@ export const getUsersDevPortfolioSubmissions = async ( ): Promise => devPortfolioDao.getUsersDevPortfolioSubmissions(uuid, user); export const getDevPortfolio = async (uuid: string, user: IdolMember): Promise => { - const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); - if (!isLeadOrAdmin) - throw new PermissionError( - `User with email ${user.email} does not have permission to view dev portfolios!` - ); const devPortfolio = await devPortfolioDao.getInstance(uuid); if (!devPortfolio) throw new NotFoundError(`Dev portfolio with uuid: ${uuid} does not exist!`); return devPortfolio; @@ -44,12 +32,6 @@ export const createNewDevPortfolio = async ( instance: DevPortfolio, user: IdolMember ): Promise => { - const canCreateDevPortfolio = await PermissionsManager.isLeadOrAdmin(user); - if (!canCreateDevPortfolio) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to create dev portfolio!` - ); - } if ( !instance.name || instance.name.length === 0 || @@ -77,12 +59,6 @@ export const createNewDevPortfolio = async ( }; export const deleteDevPortfolio = async (uuid: string, user: IdolMember): Promise => { - const canDeleteDevPortfolio = await PermissionsManager.isLeadOrAdmin(user); - if (!canDeleteDevPortfolio) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to delete dev portfolio!` - ); - } await devPortfolioDao.deleteInstance(uuid); }; @@ -116,14 +92,6 @@ export const updateSubmissions = async ( updatedSubmissions: DevPortfolioSubmission[], user: IdolMember ): Promise => { - const canChangeSubmission = await PermissionsManager.isLeadOrAdmin(user); - - if (!canChangeSubmission) { - throw new PermissionError( - `User with email ${user.email} does not have permission to update dev portfolio submissions` - ); - } - const devPortfolio = await devPortfolioDao.getInstance(uuid); if (!devPortfolio) { throw new BadRequestError(`Dev portfolio with uuid: ${uuid} does not exist`); @@ -138,12 +106,6 @@ export const updateSubmissions = async ( }; export const regradeSubmissions = async (uuid: string, user: IdolMember): Promise => { - const canRequestRegrade = await PermissionsManager.isLeadOrAdmin(user); - if (!canRequestRegrade) - throw new PermissionError( - `User with email ${user.email} does not have permission to regrade dev portfolio submissions` - ); - const devPortfolio = await devPortfolioDao.getInstance(uuid); if (!devPortfolio) { throw new BadRequestError(`Dev portfolio with uuid: ${uuid} does not exist`); diff --git a/backend/src/API/memberAPI.ts b/backend/src/API/memberAPI.ts index 66978c4a1..1e01d6a96 100644 --- a/backend/src/API/memberAPI.ts +++ b/backend/src/API/memberAPI.ts @@ -1,6 +1,5 @@ import { Request } from 'express'; import MembersDao from '../dao/MembersDao'; -import PermissionsManager from '../utils/permissionsManager'; import { BadRequestError, PermissionError } from '../utils/errors'; import { bucket } from '../firebase'; import { getNetIDFromEmail, computeMembersDiff } from '../utils/memberUtil'; @@ -14,12 +13,6 @@ export const allApprovedMembers = (): Promise => MembersDao.getAllMembers(true); export const setMember = async (body: IdolMember, user: IdolMember): Promise => { - const canEdit = await PermissionsManager.canEditMembers(user); - if (!canEdit) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to edit members!` - ); - } if (!body.email || body.email === '') { throw new BadRequestError("Couldn't edit member with undefined email!"); } @@ -34,21 +27,13 @@ export const updateMember = async ( body: IdolMember, user: IdolMember ): Promise => { - const canEdit = await PermissionsManager.canEditMembers(user); - if (!canEdit && user.email !== body.email) { - // members are able to edit their own information - throw new PermissionError( - `User with email: ${user.email} does not have permission to edit members!` - ); - } if (!body.email || body.email === '') { throw new BadRequestError("Couldn't edit member with undefined email!"); } if ( - !canEdit && - (body.role !== user.role || - body.firstName !== user.firstName || - body.lastName !== user.lastName) + body.role !== user.role || + body.firstName !== user.firstName || + body.lastName !== user.lastName ) { throw new PermissionError( `User with email: ${user.email} does not have permission to edit member name or roles!` @@ -62,12 +47,6 @@ export const updateMember = async ( }; export const deleteMember = async (email: string, user: IdolMember): Promise => { - const canEdit = await PermissionsManager.canEditMembers(user); - if (!canEdit) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to delete members!` - ); - } if (!email || email === '') { throw new BadRequestError("Couldn't delete member with undefined email!"); } @@ -90,12 +69,6 @@ export const deleteImage = async (email: string): Promise => { export const getUserInformationDifference = async ( user: IdolMember ): Promise => { - const canReview = await PermissionsManager.canReviewChanges(user); - if (!canReview) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to review members information diff!` - ); - } const [allApprovedMembersList, allLatestMembersList] = await Promise.all([ allApprovedMembers(), allMembers() @@ -108,12 +81,6 @@ export const reviewUserInformationChange = async ( rejected: readonly string[], user: IdolMember ): Promise => { - const canReview = await PermissionsManager.canReviewChanges(user); - if (!canReview) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to review members information diff!` - ); - } await Promise.all([ MembersDao.approveMemberInformationChanges(approved), MembersDao.revertMemberInformationChanges(rejected) diff --git a/backend/src/API/shoutoutAPI.ts b/backend/src/API/shoutoutAPI.ts index b748d50dd..d79a7318b 100644 --- a/backend/src/API/shoutoutAPI.ts +++ b/backend/src/API/shoutoutAPI.ts @@ -1,4 +1,3 @@ -import PermissionsManager from '../utils/permissionsManager'; import { NotFoundError, PermissionError } from '../utils/errors'; import ShoutoutsDao from '../dao/ShoutoutsDao'; @@ -19,27 +18,13 @@ export const getShoutouts = async ( memberEmail: string, type: 'given' | 'received', user: IdolMember -): Promise => { - const canEdit: boolean = await PermissionsManager.canGetShoutouts(user); - if (!canEdit && memberEmail !== user.email) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to get shoutouts!` - ); - } - return shoutoutsDao.getShoutouts(memberEmail, type); -}; +): Promise => shoutoutsDao.getShoutouts(memberEmail, type); export const hideShoutout = async ( uuid: string, hide: boolean, user: IdolMember ): Promise => { - const canEdit = await PermissionsManager.canHideShoutouts(user); - if (!canEdit) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to hide shoutouts!` - ); - } const shoutout = await shoutoutsDao.getShoutout(uuid); if (!shoutout) throw new NotFoundError(`Shoutout with uuid: ${uuid} does not exist!`); await shoutoutsDao.updateShoutout({ ...shoutout, hidden: hide }); @@ -50,11 +35,5 @@ export const deleteShoutout = async (uuid: string, user: IdolMember): Promise => { - await checkPermissions(user); const octokit = new Octokit({ auth: `token ${process.env.BOT_TOKEN}`, userAgent: 'cornell-dti/idol-backend' @@ -20,7 +18,6 @@ export const requestIDOLPullDispatch = async (user: IdolMember): Promise<{ updat }; export const getIDOLChangesPR = async (user: IdolMember): Promise<{ pr: PRResponse }> => { - await checkPermissions(user); const foundPR = await findBotPR(); return { pr: foundPR }; }; @@ -28,7 +25,6 @@ export const getIDOLChangesPR = async (user: IdolMember): Promise<{ pr: PRRespon export const acceptIDOLChanges = async ( user: IdolMember ): Promise<{ pr: PRResponse; merged: boolean }> => { - await checkPermissions(user); const octokit = new Octokit({ auth: `token ${process.env.BOT_TOKEN}`, userAgent: 'cornell-dti/idol-backend' @@ -62,7 +58,6 @@ export const acceptIDOLChanges = async ( export const rejectIDOLChanges = async ( user: IdolMember ): Promise<{ pr: PRResponse; closed: boolean }> => { - await checkPermissions(user); const foundPR = await findBotPR(); const octokit2 = new Octokit({ auth: `token ${process.env.BOT_2_TOKEN}`, @@ -99,12 +94,3 @@ const findBotPR = async (): Promise => { } return foundPR; }; - -const checkPermissions = async (user: IdolMember): Promise => { - const canEdit = await PermissionsManager.canDeploySite(user); - if (!canEdit) { - throw new PermissionError( - `User with email: ${user.email} does not have permission to trigger site deploys!` - ); - } -}; diff --git a/backend/src/API/teamAPI.ts b/backend/src/API/teamAPI.ts index 735fa15ca..b9c21a66e 100644 --- a/backend/src/API/teamAPI.ts +++ b/backend/src/API/teamAPI.ts @@ -1,8 +1,7 @@ import { Router } from 'express'; import { v4 as uuidv4 } from 'uuid'; -import PermissionsManager from '../utils/permissionsManager'; import { Team } from '../types/DataTypes'; -import { BadRequestError, PermissionError } from '../utils/errors'; +import { BadRequestError } from '../utils/errors'; import MembersDao from '../dao/MembersDao'; import { loginCheckedGet, loginCheckedPost, loginCheckedPut } from '../utils/auth'; @@ -95,12 +94,6 @@ const updateFormerMembers = async (team: Team, oldTeam: Team): Promise => }; export const setTeam = async (teamBody: Team, member: IdolMember): Promise => { - const canEdit = await PermissionsManager.canEditTeams(member); - if (!canEdit) { - throw new PermissionError( - `User with email: ${member.email} does not have permission to edit teams!` - ); - } if (teamBody.members.length > 0 && !teamBody.members[0].email) { throw new BadRequestError('Malformed members on POST!'); } @@ -112,12 +105,6 @@ export const deleteTeam = async (teamBody: Team, member: IdolMember): Promise => { - const canEditTeamEvents = await PermissionsManager.canEditTeamEvent(user); - if (!canEditTeamEvents) throw new PermissionError('does not have permissions'); const teamEvents = await TeamEventsDao.getAllTeamEvents(); return Promise.all( teamEvents.map(async (event) => ({ @@ -29,17 +26,11 @@ export const createTeamEvent = async ( teamEventInfo: TeamEventInfo, user: IdolMember ): Promise => { - const canCreateTeamEvent = await PermissionsManager.canEditTeamEvent(user); - if (!canCreateTeamEvent) - throw new PermissionError('does not have permissions to create team event'); await TeamEventsDao.createTeamEvent(teamEventInfo); return teamEventInfo; }; export const deleteTeamEvent = async (uuid: string, user: IdolMember): Promise => { - if (!PermissionsManager.canEditTeamEvent(user)) { - throw new PermissionError("You don't have permission to delete a team event!"); - } const teamEvent = await TeamEventsDao.getTeamEvent(uuid); // TODO: need to make a dao method for getting full team event if (!teamEvent) return; @@ -57,11 +48,6 @@ export const updateTeamEvent = async ( teamEventInfo: TeamEventInfo, user: IdolMember ): Promise => { - if (!PermissionsManager.canEditTeamEvent(user)) { - throw new PermissionError( - `User with email ${user.email} does not have permissions to update team events` - ); - } const updatedTeamEvent = await TeamEventsDao.updateTeamEvent(teamEventInfo); return updatedTeamEvent; }; @@ -80,12 +66,6 @@ export const requestTeamEventCredit = async ( }; export const getTeamEvent = async (uuid: string, user: IdolMember): Promise => { - const canEditTeamEvents = await PermissionsManager.canEditTeamEvent(user); - if (!canEditTeamEvents) - throw new PermissionError( - `User with email ${user.email} does not have permission to get full team event` - ); - const teamEvent = await TeamEventsDao.getTeamEvent(uuid); return { ...teamEvent, @@ -99,11 +79,6 @@ export const getTeamEvent = async (uuid: string, user: IdolMember): Promise => { - const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); - if (!isLeadOrAdmin) - throw new PermissionError( - `User with email ${user.email} does not have sufficient permissions to delete all team events.` - ); await TeamEventAttendanceDao.deleteAllTeamEventAttendance(); await TeamEventsDao.deleteAllTeamEvents(); }; @@ -116,22 +91,10 @@ export const updateTeamEventAttendance = async ( teamEventAttendance: TeamEventAttendance, user: IdolMember ): Promise => { - const canEditTeamEvent = await PermissionsManager.canEditTeamEvent(user); - if (!canEditTeamEvent) { - throw new PermissionError( - `User with email ${user.email} does not have permissions to update team events attendance` - ); - } await teamEventAttendanceDao.updateTeamEventAttendance(teamEventAttendance); return teamEventAttendance; }; export const deleteTeamEventAttendance = async (uuid: string, user: IdolMember): Promise => { - const isLeadOrAdmin = await PermissionsManager.isLeadOrAdmin(user); - if (!isLeadOrAdmin) { - throw new PermissionError( - `User with email ${user.email} does not have sufficient permissions to delete team events attendance` - ); - } await teamEventAttendanceDao.deleteTeamEventAttendance(uuid); }; From cd22b757166a429f1bce4d0755d54178b7f9935c Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:09:57 -0400 Subject: [PATCH 37/49] remove todo --- backend/src/API/teamEventsAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/API/teamEventsAPI.ts b/backend/src/API/teamEventsAPI.ts index dd1b36231..b09540b48 100644 --- a/backend/src/API/teamEventsAPI.ts +++ b/backend/src/API/teamEventsAPI.ts @@ -31,7 +31,7 @@ export const createTeamEvent = async ( }; export const deleteTeamEvent = async (uuid: string, user: IdolMember): Promise => { - const teamEvent = await TeamEventsDao.getTeamEvent(uuid); // TODO: need to make a dao method for getting full team event + const teamEvent = await TeamEventsDao.getTeamEvent(uuid); if (!teamEvent) return; const allAttendances = await teamEventAttendanceDao.getTeamEventAttendanceByEventId(uuid); From bd633307b14b6c2830b8d7b9fcf05e4cebd60432 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:16:15 -0400 Subject: [PATCH 38/49] create rbacconfig type --- backend/src/types/AuthTypes.d.ts | 34 ++++++++++++++++++++++++++++++++ backend/src/types/DataTypes.d.ts | 16 --------------- backend/src/utils/auth.ts | 14 ++++++++----- 3 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 backend/src/types/AuthTypes.d.ts diff --git a/backend/src/types/AuthTypes.d.ts b/backend/src/types/AuthTypes.d.ts new file mode 100644 index 000000000..9e57051c9 --- /dev/null +++ b/backend/src/types/AuthTypes.d.ts @@ -0,0 +1,34 @@ +type AuthLead = + | 'lead' + | 'dev_lead' + | 'ops_lead' + | 'lead' + | 'design_lead' + | 'business_lead' + | 'ci_lead' + | 'pm_lead'; + +export type AuthRole = + | undefined + | 'admin' + | 'dev' + | 'tpm' + | 'pm' + | 'designer' + | 'business' + | AuthLead; + +export interface AuthRoleDoc { + role: AuthRole; +} + +export interface RBACConfig { + resources: { + [resourceName: string]: { + read_only: AuthRole[]; + read_and_write: AuthRole[]; + has_metadata: boolean; + has_owner: boolean; + }; + }; +} diff --git a/backend/src/types/DataTypes.d.ts b/backend/src/types/DataTypes.d.ts index 17047ddff..a9bd66cab 100644 --- a/backend/src/types/DataTypes.d.ts +++ b/backend/src/types/DataTypes.d.ts @@ -92,19 +92,3 @@ export type DevPortfolioSubmissionRequestLog = { uuid: string; }; }; - -type AuthLead = - | 'lead' - | 'dev_lead' - | 'ops_lead' - | 'lead' - | 'design_lead' - | 'business_lead' - | 'ci_lead' - | 'pm_lead'; - -export type AuthRole = 'admin' | 'dev' | 'tpm' | 'pm' | 'designer' | 'business' | AuthLead; - -export interface AuthRoleDoc { - role: AuthRole; -} diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index cd30d04cf..c71c304ee 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -5,7 +5,7 @@ import PermissionsManager from './permissionsManager'; import { app as adminApp, env } from '../firebase'; import MembersDao from '../dao/MembersDao'; import rbacConfig from '../../rbac.json'; -import { AuthRole } from '../types/DataTypes'; +import { AuthRole, RBACConfig } from '../types/AuthTypes'; import AuthRoleDao from '../dao/AuthRoleDao'; const getUserEmailFromRequest = async (request: Request): Promise => { @@ -39,7 +39,7 @@ const isAuthorized = async ( req: Request, resource: string, user: IdolMember, - rbacConfig: any // TODO: create type for rbacconfig + rbacConfig: RBACConfig ): Promise => { const userRole = await getUserRole(user); const resourceRBACConfig = rbacConfig.resources[resource]; @@ -48,7 +48,11 @@ const isAuthorized = async ( if (req.method === 'GET') { if (resourceRBACConfig.has_metadata && req.query.meta_only) return true; - const canReadRoles = resourceRBACConfig.read_only.push(...resourceRBACConfig.read_and_write); + const canReadRoles: AuthRole[] = [ + ...resourceRBACConfig.read_only, + ...resourceRBACConfig.read_and_write + ]; + if (canReadRoles.includes(userRole)) return true; } @@ -57,7 +61,7 @@ const isAuthorized = async ( if (req.params.email === user.email) return true; } - if (canWriteRoles.includes('userRole')) return true; + if (canWriteRoles.includes(userRole)) return true; return false; }; @@ -83,7 +87,7 @@ const getAuthMiddleware = if ( resource && resource in Object.keys(rbacConfig.resources) && - !(await isAuthorized(req, resource, user, rbacConfig)) + !(await isAuthorized(req, resource, user, rbacConfig as RBACConfig)) ) { res.status(401).send({ error: `User with email ${user.email} does not have read and/or write access to the requested resource.` From b5873b3dda46ccd4fb7f5f9caafe0d66031714ae Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:23:14 -0400 Subject: [PATCH 39/49] change logic for handling lead types --- backend/src/dao/AuthRoleDao.ts | 6 +++--- backend/src/types/AuthTypes.d.ts | 7 ++++--- backend/src/utils/auth.ts | 19 ++++++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/backend/src/dao/AuthRoleDao.ts b/backend/src/dao/AuthRoleDao.ts index 275d31640..e1d84d3bd 100644 --- a/backend/src/dao/AuthRoleDao.ts +++ b/backend/src/dao/AuthRoleDao.ts @@ -1,6 +1,6 @@ import BaseDao from './BaseDao'; import { authRoleCollection } from '../firebase'; -import { AuthRoleDoc, AuthRole } from '../types/DataTypes'; +import { AuthRoleDoc } from '../types/AuthTypes'; export default class AuthRoleDao extends BaseDao { constructor() { @@ -11,8 +11,8 @@ export default class AuthRoleDao extends BaseDao { ); } - async getAuthRole(user: IdolMember): Promise { + async getAuthRole(user: IdolMember): Promise { const authRoleData = await this.getDocument(user.email); - return authRoleData?.role; + return authRoleData || undefined; } } diff --git a/backend/src/types/AuthTypes.d.ts b/backend/src/types/AuthTypes.d.ts index 9e57051c9..512140853 100644 --- a/backend/src/types/AuthTypes.d.ts +++ b/backend/src/types/AuthTypes.d.ts @@ -1,5 +1,4 @@ -type AuthLead = - | 'lead' +type AuthLeadTypes = | 'dev_lead' | 'ops_lead' | 'lead' @@ -16,10 +15,12 @@ export type AuthRole = | 'pm' | 'designer' | 'business' - | AuthLead; + | 'lead' + | AuthLeadTypes; export interface AuthRoleDoc { role: AuthRole; + leadType?: AuthLeadTypes; } export interface RBACConfig { diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index c71c304ee..144457b21 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -5,7 +5,7 @@ import PermissionsManager from './permissionsManager'; import { app as adminApp, env } from '../firebase'; import MembersDao from '../dao/MembersDao'; import rbacConfig from '../../rbac.json'; -import { AuthRole, RBACConfig } from '../types/AuthTypes'; +import { AuthRole, AuthRoleDoc, RBACConfig } from '../types/AuthTypes'; import AuthRoleDao from '../dao/AuthRoleDao'; const getUserEmailFromRequest = async (request: Request): Promise => { @@ -15,7 +15,7 @@ const getUserEmailFromRequest = async (request: Request): Promise => { +const getUserRole = async (user: IdolMember): Promise => { const authRoleDao = new AuthRoleDao(); return authRoleDao.getAuthRole(user); }; @@ -41,7 +41,8 @@ const isAuthorized = async ( user: IdolMember, rbacConfig: RBACConfig ): Promise => { - const userRole = await getUserRole(user); + const roleData = await getUserRole(user); + const userRole = roleData?.role; const resourceRBACConfig = rbacConfig.resources[resource]; if (userRole === 'admin') return true; @@ -53,7 +54,11 @@ const isAuthorized = async ( ...resourceRBACConfig.read_and_write ]; - if (canReadRoles.includes(userRole)) return true; + if ( + canReadRoles.includes(userRole) || + (userRole === 'lead' && canReadRoles.includes(roleData?.leadType)) + ) + return true; } const canWriteRoles = resourceRBACConfig.read_and_write; @@ -61,7 +66,11 @@ const isAuthorized = async ( if (req.params.email === user.email) return true; } - if (canWriteRoles.includes(userRole)) return true; + if ( + canWriteRoles.includes(userRole) || + (userRole === 'lead' && canWriteRoles.includes(roleData?.leadType)) + ) + return true; return false; }; From 44e298639b77a123d26750703f11b9a569ac3dad Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 22 May 2023 13:26:36 -0400 Subject: [PATCH 40/49] Fix firebase import --- backend/src/firebase.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/firebase.ts b/backend/src/firebase.ts index 6a6a5451f..9b37b5533 100644 --- a/backend/src/firebase.ts +++ b/backend/src/firebase.ts @@ -5,9 +5,9 @@ import { DBCandidateDeciderInstance, DBDevPortfolio, DevPortfolioSubmissionRequestLog, - DBTeamEventAttendance, - AuthRoleDoc + DBTeamEventAttendance } from './types/DataTypes'; +import { AuthRoleDoc } from './types/AuthTypes'; import { configureAccount } from './utils/firebase-utils'; require('dotenv').config(); From 676c8cca06b32baf4a27e06e88f2fb2d41186b73 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Mon, 29 May 2023 20:33:21 -0400 Subject: [PATCH 41/49] configure teamRouter --- backend/src/API/teamAPI.ts | 25 ------------------------- backend/src/api.ts | 2 +- backend/src/routers/teamRouter.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/backend/src/API/teamAPI.ts b/backend/src/API/teamAPI.ts index b9c21a66e..0505a5d45 100644 --- a/backend/src/API/teamAPI.ts +++ b/backend/src/API/teamAPI.ts @@ -1,9 +1,7 @@ -import { Router } from 'express'; import { v4 as uuidv4 } from 'uuid'; import { Team } from '../types/DataTypes'; import { BadRequestError } from '../utils/errors'; import MembersDao from '../dao/MembersDao'; -import { loginCheckedGet, loginCheckedPost, loginCheckedPut } from '../utils/auth'; const membersDao = new MembersDao(); @@ -108,26 +106,3 @@ export const deleteTeam = async (teamBody: Team, member: IdolMember): Promise ({ teams: await allTeams() }), 'team'); - -loginCheckedPut( - teamRouter, - '/', - async (req, user) => ({ - team: await setTeam(req.body, user) - }), - 'team' -); - -// TODO: should eventually make this a delete request -loginCheckedPost( - teamRouter, - '/', - async (req, user) => ({ - team: await deleteTeam(req.body, user) - }), - 'team' -); diff --git a/backend/src/api.ts b/backend/src/api.ts index a8f508320..c33d12a0f 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -6,7 +6,7 @@ import * as expressWinston from 'express-winston'; import { env } from './firebase'; import AdminsDao from './dao/AdminsDao'; import { getMember } from './API/memberAPI'; -import { teamRouter } from './API/teamAPI'; +import teamRouter from './routers/teamRouter'; import candidateDeciderRouter from './routers/candidateDeciderRouter'; import devPortfolioRouter from './routers/devPortfolioRouter'; import memberImageRouter from './routers/imageRouter'; diff --git a/backend/src/routers/teamRouter.ts b/backend/src/routers/teamRouter.ts index e69de29bb..9987418b0 100644 --- a/backend/src/routers/teamRouter.ts +++ b/backend/src/routers/teamRouter.ts @@ -0,0 +1,28 @@ +import { Router } from 'express'; +import { allTeams, setTeam, deleteTeam } from '../API/teamAPI'; +import { loginCheckedGet, loginCheckedPut, loginCheckedPost } from '../utils/auth'; + +const teamRouter = Router(); + +loginCheckedGet(teamRouter, '/', async () => ({ teams: await allTeams() }), 'team'); + +loginCheckedPut( + teamRouter, + '/', + async (req, user) => ({ + team: await setTeam(req.body, user) + }), + 'team' +); + +// TODO: should eventually make this a delete request +loginCheckedPost( + teamRouter, + '/', + async (req, user) => ({ + team: await deleteTeam(req.body, user) + }), + 'team' +); + +export default teamRouter; From 5977e722d1921c0f284fc71238a1e1a315af713a Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 21:53:41 -0500 Subject: [PATCH 42/49] make auth middleware more extensible --- backend/rbac.json | 54 ++++++---------- backend/src/types/AuthTypes.d.ts | 6 +- backend/src/utils/auth.ts | 105 ++++++++++++++++--------------- 3 files changed, 76 insertions(+), 89 deletions(-) diff --git a/backend/rbac.json b/backend/rbac.json index be53d31e7..89025538e 100644 --- a/backend/rbac.json +++ b/backend/rbac.json @@ -1,58 +1,40 @@ { "resources": { "dev-portfolio": { - "read_only": ["lead"], - "read_and_write": ["dev_lead"], - "has_metadata": true, - "has_owner": false + "read": ["lead", "admin"], + "write": ["dev_lead", "lead", "admin"] }, "dev-portfolio-submission": { - "read_only": ["lead"], - "read_and_write": ["dev_lead"], - "has_metadata": true, - "has_owner": false + "read": ["lead", "dev_lead", "admin"], + "write": ["dev_lead", "admin"] }, "profile-image": { - "read_only": [], - "read_and_write": ["lead"], - "has_metadata": false, - "has_owner": true + "read": ["lead", "admin"], + "write": ["lead", "admin"] }, "member": { - "read_only": [], - "read_and_write": ["lead"], - "has_metadata": false, - "has_owner": true + "read": ["lead", "admin"], + "write": ["lead", "admin"] }, "member-diff": { - "read_only": [], - "read_and_write": ["lead"], - "has_metadata": false, - "has_owner": false + "read": ["lead", "admin"], + "write": ["lead", "admin"] }, "shoutout": { - "read_only": [], - "read_and_write": ["lead"], - "has_metadata": false, - "has_owner": true + "read": ["lead", "admin"], + "write": ["lead", "admin"] }, "team": { - "read_only": [], - "read_and_write": ["lead"], - "has_metadata": false, - "has_owner": false + "read": ["lead", "admin"], + "write": ["lead", "admin"] }, "team-event": { - "read_only": ["lead"], - "read_and_write": ["ci_lead"], - "has_metadata": true, - "has_owner": false + "read": ["lead", "ci_lead", "admin"], + "write": ["ci_lead", "admin"] }, "team-event-attendance": { - "read_only": ["lead"], - "read_and_write": ["ci_lead"], - "has_metadata": false, - "has_owner": true + "read": ["lead", "ci_lead", "admin"], + "write": ["ci_lead", "admin"] } } } diff --git a/backend/src/types/AuthTypes.d.ts b/backend/src/types/AuthTypes.d.ts index 512140853..63d88aa9b 100644 --- a/backend/src/types/AuthTypes.d.ts +++ b/backend/src/types/AuthTypes.d.ts @@ -26,10 +26,8 @@ export interface AuthRoleDoc { export interface RBACConfig { resources: { [resourceName: string]: { - read_only: AuthRole[]; - read_and_write: AuthRole[]; - has_metadata: boolean; - has_owner: boolean; + read: AuthRole[]; + write: AuthRole[]; }; }; } diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 144457b21..0bdb4261d 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -5,7 +5,7 @@ import PermissionsManager from './permissionsManager'; import { app as adminApp, env } from '../firebase'; import MembersDao from '../dao/MembersDao'; import rbacConfig from '../../rbac.json'; -import { AuthRole, AuthRoleDoc, RBACConfig } from '../types/AuthTypes'; +import { AuthRoleDoc, RBACConfig } from '../types/AuthTypes'; import AuthRoleDao from '../dao/AuthRoleDao'; const getUserEmailFromRequest = async (request: Request): Promise => { @@ -35,48 +35,30 @@ const loginCheckedHandler = } }; -const isAuthorized = async ( - req: Request, - resource: string, +const hasRbacPerm = async ( user: IdolMember, - rbacConfig: RBACConfig + rbacConfig: RBACConfig, + resource?: string, + action?: string ): Promise => { - const roleData = await getUserRole(user); - const userRole = roleData?.role; - const resourceRBACConfig = rbacConfig.resources[resource]; + if (resource && action) { + const roleData = await getUserRole(user); + const userRole = roleData?.role; + const resourceRBACConfig = rbacConfig.resources[resource]; - if (userRole === 'admin') return true; - - if (req.method === 'GET') { - if (resourceRBACConfig.has_metadata && req.query.meta_only) return true; - const canReadRoles: AuthRole[] = [ - ...resourceRBACConfig.read_only, - ...resourceRBACConfig.read_and_write - ]; - - if ( - canReadRoles.includes(userRole) || - (userRole === 'lead' && canReadRoles.includes(roleData?.leadType)) - ) - return true; - } - - const canWriteRoles = resourceRBACConfig.read_and_write; - if (resourceRBACConfig.has_owner && req.params.email) { - if (req.params.email === user.email) return true; + if (resourceRBACConfig) { + return resourceRBACConfig[action].includes(userRole); + } } - - if ( - canWriteRoles.includes(userRole) || - (userRole === 'lead' && canWriteRoles.includes(roleData?.leadType)) - ) - return true; - - return false; + return true; }; const getAuthMiddleware = - (resource?: string): RequestHandler => + ( + resource?: string, + action?: string, + userHasAccess: (req: Request, user: IdolMember) => Promise = async () => true + ): RequestHandler => async (req: Request, res: Response, next: NextFunction) => { // authentication const userEmail = await getUserEmailFromRequest(req); @@ -93,11 +75,8 @@ const getAuthMiddleware = res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); } // RBAC - if ( - resource && - resource in Object.keys(rbacConfig.resources) && - !(await isAuthorized(req, resource, user, rbacConfig as RBACConfig)) - ) { + const userHasRbacPerm = await hasRbacPerm(user, rbacConfig as RBACConfig, resource, action); + if (!(userHasRbacPerm || (await userHasAccess(req, user)))) { res.status(401).send({ error: `User with email ${user.email} does not have read and/or write access to the requested resource.` }); @@ -110,26 +89,54 @@ export const loginCheckedGet = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, - resource?: string -): RequestHandler => router.get(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); + resource?: string, + action?: string, + userHasAccess?: (req: Request, user: IdolMember) => Promise +): RequestHandler => + router.get( + path, + getAuthMiddleware(resource, action, userHasAccess), + loginCheckedHandler(handler) + ); export const loginCheckedPost = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, - resource?: string -): RequestHandler => router.post(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); + resource?: string, + action?: string, + userHasAccess?: (req: Request, user: IdolMember) => Promise +): RequestHandler => + router.post( + path, + getAuthMiddleware(resource, action, userHasAccess), + loginCheckedHandler(handler) + ); export const loginCheckedDelete = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, - resource?: string -): RequestHandler => router.delete(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); + resource?: string, + action?: string, + userHasAccess?: (req: Request, user: IdolMember) => Promise +): RequestHandler => + router.delete( + path, + getAuthMiddleware(resource, action, userHasAccess), + loginCheckedHandler(handler) + ); export const loginCheckedPut = ( router: Router, path: string, handler: (req: Request, user: IdolMember) => Promise>, - resource?: string -): RequestHandler => router.put(path, getAuthMiddleware(resource), loginCheckedHandler(handler)); + resource?: string, + action?: string, + userHasAccess?: (req: Request, user: IdolMember) => Promise +): RequestHandler => + router.put( + path, + getAuthMiddleware(resource, action, userHasAccess), + loginCheckedHandler(handler) + ); From e4b4d230e541256d9324f249e6d5ac74170174cd Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:08:36 -0500 Subject: [PATCH 43/49] redo dev portfolio routes --- backend/src/routers/devPortfolioRouter.ts | 67 ++++++++++++++--------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/backend/src/routers/devPortfolioRouter.ts b/backend/src/routers/devPortfolioRouter.ts index a898399e7..8b9edb966 100644 --- a/backend/src/routers/devPortfolioRouter.ts +++ b/backend/src/routers/devPortfolioRouter.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request } from 'express'; import { loginCheckedDelete, loginCheckedGet, @@ -20,8 +20,12 @@ import { import DPSubmissionRequestLogDao from '../dao/DPSubmissionRequestLogDao'; const devPortfolioRouter = Router(); -const devPortfolioSubmissionRouter = Router({ mergeParams: true }); -devPortfolioRouter.use('/:uuid/submission', devPortfolioSubmissionRouter); + +const userCanAccessResource = async (req: Request, user: IdolMember): Promise => { + if (req.params.email === user.email) return true; + if (req.query.meta_only) return true; + return false; +}; // /dev-portfolio loginCheckedGet( @@ -32,7 +36,9 @@ loginCheckedGet( ? await getAllDevPortfolios(user) : await getAllDevPortfolioInfo() }), - 'dev-portfolio' + 'dev-portfolio', + 'write', + userCanAccessResource ); loginCheckedGet( devPortfolioRouter, @@ -42,7 +48,9 @@ loginCheckedGet( ? await getDevPortfolio(req.params.uuid, user) : await getDevPortfolioInfo(req.params.uuid) }), - 'dev-portfolio' + 'dev-portfolio', + 'read', + userCanAccessResource ); loginCheckedPost( @@ -51,50 +59,55 @@ loginCheckedPost( async (req, user) => ({ portfolio: await createNewDevPortfolio(req.body, user) }), - 'dev-portfolio' + 'dev-portfolio', + 'write', + userCanAccessResource ); loginCheckedDelete( devPortfolioRouter, '/:uuid', async (req, user) => deleteDevPortfolio(req.params.uuid, user).then(() => ({})), - 'dev-portfolio' + 'dev-portfolio', + 'write', + userCanAccessResource ); // devPortfolioSubmissionRouter: /dev-portfolio/:uuid/submission -loginCheckedPost( - devPortfolioSubmissionRouter, - '/', - async (req, user) => { - await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); - return { - submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) - }; - }, - 'dev-portfolio-submission' -); +loginCheckedPost(devPortfolioRouter, '/:uuid/submission', async (req, user) => { + await DPSubmissionRequestLogDao.logRequest(user.email, req.body.uuid, req.body.submission); + return { + submission: await makeDevPortfolioSubmission(req.body.uuid, req.body.submission) + }; +}); loginCheckedPut( - devPortfolioSubmissionRouter, - '/regrade', + devPortfolioRouter, + '/:uuid/submission/regrade', async (req, user) => ({ portfolio: await regradeSubmissions(req.body.uuid, user) }), - 'dev-portfolio-submission' + 'dev-portfolio-submission', + 'write', + async () => false ); loginCheckedPut( - devPortfolioSubmissionRouter, - '/', + devPortfolioRouter, + '/:uuid/submission', async (req, user) => ({ portfolio: await updateSubmissions(req.params.uuid, req.body.updatedSubmissions, user) }), - 'dev-portfolio-submission' + 'dev-portfolio-submission', + 'write', + async () => false ); loginCheckedGet( - devPortfolioSubmissionRouter, - '/:email', + devPortfolioRouter, + '/:uuid/submission/:email', async (req, user) => ({ submissions: await getUsersDevPortfolioSubmissions(req.params.uuid, user) }), - 'dev-portfolio-submission' + 'dev-portfolio-submission', + 'read', + userCanAccessResource ); export default devPortfolioRouter; From 34d5d51ca00e9837e0dc226b59a7c462cfa11778 Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:11:23 -0500 Subject: [PATCH 44/49] redo image routes --- backend/src/routers/imageRouter.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/routers/imageRouter.ts b/backend/src/routers/imageRouter.ts index 32084e5fe..9022b27e5 100644 --- a/backend/src/routers/imageRouter.ts +++ b/backend/src/routers/imageRouter.ts @@ -1,16 +1,21 @@ -import { Router } from 'express'; +import { Router, Request } from 'express'; import { getMemberImage, setMemberImage, allMemberImages } from '../API/imageAPI'; import { loginCheckedGet } from '../utils/auth'; const memberImageRouter = Router(); +const canAccessResource = async (req: Request, user: IdolMember): Promise => + req.params.email === user.email; + loginCheckedGet( memberImageRouter, '/:email', async (_, user) => ({ url: await getMemberImage(user) }), - 'profile-image' + 'profile-image', + 'read', + canAccessResource ); loginCheckedGet( @@ -19,7 +24,9 @@ loginCheckedGet( async (_, user) => ({ url: await setMemberImage(user) }), - 'profile-image' + 'profile-image', + 'write', + canAccessResource ); memberImageRouter.get('/', async (_, res) => { From 672ca780c5caca7f6fea5cfc61fbac3b4c00abed Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:13:39 -0500 Subject: [PATCH 45/49] redo member router --- backend/src/routers/memberRouter.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/backend/src/routers/memberRouter.ts b/backend/src/routers/memberRouter.ts index a25c3ad18..82be4ac83 100644 --- a/backend/src/routers/memberRouter.ts +++ b/backend/src/routers/memberRouter.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request } from 'express'; import { allApprovedMembers, allMembers, @@ -16,6 +16,9 @@ import { loginCheckedGet } from '../utils/auth'; +const canAccessResource = async (req: Request, user: IdolMember): Promise => + req.params.email === user.email; + export const memberRouter = Router(); export const memberDiffRouter = Router(); @@ -41,7 +44,9 @@ loginCheckedPost( async (req, user) => ({ member: await setMember(req.body, user) }), - 'member' + 'member', + 'write', + canAccessResource ); loginCheckedDelete( memberRouter, @@ -50,7 +55,9 @@ loginCheckedDelete( await deleteMember(req.params.email, user); return {}; }, - 'member' + 'member', + 'write', + async () => false ); loginCheckedPut( memberRouter, @@ -58,7 +65,9 @@ loginCheckedPut( async (req, user) => ({ member: await updateMember(req, req.body, user) }), - 'member' + 'member', + 'write', + canAccessResource ); loginCheckedGet( @@ -67,7 +76,9 @@ loginCheckedGet( async (_, user) => ({ diffs: await getUserInformationDifference(user) }), - 'member-diff' + 'member-diff', + 'read', + async () => false ); loginCheckedPut( memberDiffRouter, @@ -75,5 +86,7 @@ loginCheckedPut( async (req, user) => ({ member: await reviewUserInformationChange(req.body.approved, req.body.rejected, user) }), - 'member-diff' + 'member-diff', + 'write', + async () => false ); From baf5276da228e00b4b4f56be5facd0ba6a7c9332 Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:16:03 -0500 Subject: [PATCH 46/49] redo shoutouts router --- backend/src/routers/shoutoutRouter.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/src/routers/shoutoutRouter.ts b/backend/src/routers/shoutoutRouter.ts index fd65ef562..e0cfdda34 100644 --- a/backend/src/routers/shoutoutRouter.ts +++ b/backend/src/routers/shoutoutRouter.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request } from 'express'; import { getShoutouts, getAllShoutouts, @@ -15,13 +15,18 @@ import { const shoutoutRouter = Router(); +const canAccessResource = async (req: Request, user: IdolMember): Promise => + req.params.email === user.email; + loginCheckedGet( shoutoutRouter, '/:email/:type', async (req, user) => ({ shoutouts: await getShoutouts(req.params.email, req.params.type as 'given' | 'received', user) }), - 'shoutout' + 'shoutout', + 'read', + canAccessResource ); loginCheckedGet( @@ -30,7 +35,9 @@ loginCheckedGet( async () => ({ shoutouts: await getAllShoutouts() }), - 'shoutout' + 'shoutout', + 'read', + async () => false ); // No RBAC? loginCheckedPost(shoutoutRouter, '/', async (req, user) => ({ @@ -44,7 +51,9 @@ loginCheckedPut( await hideShoutout(req.body.uuid, req.body.hide, user); return {}; }, - 'shoutout' + 'shoutout', + 'write', + async () => false ); loginCheckedDelete( @@ -54,6 +63,8 @@ loginCheckedDelete( await deleteShoutout(req.params.uuid, user); return {}; }, - 'shoutout' + 'shoutout', + 'write', + async () => false ); export default shoutoutRouter; From 45a408e74efc3b29194e3dfc994bd066713321f3 Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:27:37 -0500 Subject: [PATCH 47/49] redo team event router --- backend/src/routers/teamEventRouter.ts | 46 +++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/backend/src/routers/teamEventRouter.ts b/backend/src/routers/teamEventRouter.ts index a3d43ca2f..b6d70e1e7 100644 --- a/backend/src/routers/teamEventRouter.ts +++ b/backend/src/routers/teamEventRouter.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request } from 'express'; import { createTeamEvent, getTeamEvent, @@ -20,9 +20,9 @@ import { } from '../utils/auth'; const teamEventRouter = Router(); -const teamEventAttendanceRouter = Router({ mergeParams: true }); -teamEventRouter.use('/attendance', teamEventAttendanceRouter); +const canAccessResource = async (req: Request, user: IdolMember): Promise => + req.query.meta_only === 'true' || req.params.email === user.email; // /team-event loginCheckedPost( @@ -32,7 +32,9 @@ loginCheckedPost( await createTeamEvent(req.body, user); return {}; }, - 'team-event' + 'team-event', + 'write', + async () => false ); loginCheckedGet( teamEventRouter, @@ -40,7 +42,9 @@ loginCheckedGet( async (req, user) => ({ event: await getTeamEvent(req.params.uuid, user) }), - 'team-event' + 'team-event', + 'read', + canAccessResource ); loginCheckedGet( teamEventRouter, @@ -48,7 +52,9 @@ loginCheckedGet( async (req, user) => ({ events: !req.query.meta_only ? await getAllTeamEvents(user) : await getAllTeamEventInfo() }), - 'team-event' + 'team-event', + 'read', + canAccessResource ); loginCheckedPut( teamEventRouter, @@ -56,7 +62,9 @@ loginCheckedPut( async (req, user) => ({ event: await updateTeamEvent(req.body, user) }), - 'team-event' + 'team-event', + 'write', + async () => false ); loginCheckedDelete( teamEventRouter, @@ -65,7 +73,9 @@ loginCheckedDelete( await deleteTeamEvent(req.body, user); return {}; }, - 'team-event' + 'team-event', + 'write', + async () => false ); loginCheckedDelete( teamEventRouter, @@ -74,12 +84,14 @@ loginCheckedDelete( await clearAllTeamEvents(user); return {}; }, - 'team-event' + 'team-event', + 'write', + async () => false ); // /team-event/attendance loginCheckedPost( - teamEventAttendanceRouter, + teamEventRouter, '/attendance', async (req, user) => { await requestTeamEventCredit(req.body.request, user); @@ -88,15 +100,17 @@ loginCheckedPost( 'team-event-attendance' ); loginCheckedGet( - teamEventAttendanceRouter, + teamEventRouter, '/attendance/:email', async (_, user) => ({ teamEventAttendance: await getTeamEventAttendanceByUser(user) }), - 'team-event-attendance' + 'team-event-attendance', + 'read', + canAccessResource ); loginCheckedPut( - teamEventAttendanceRouter, + teamEventRouter, '/attendance', async (req, user) => ({ teamEventAttendance: await updateTeamEventAttendance(req.body, user) @@ -104,13 +118,15 @@ loginCheckedPut( 'team-event-attendance' ); loginCheckedDelete( - teamEventAttendanceRouter, + teamEventRouter, '/attendance/:uuid', async (req, user) => { await deleteTeamEventAttendance(req.params.uuid, user); return {}; }, - 'team-event-attendance' + 'team-event-attendance', + 'write', + async () => false ); export default teamEventRouter; From ff58c2de4cacd1009a51379c3e47c161ab9626d8 Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:27:46 -0500 Subject: [PATCH 48/49] redo team router --- backend/src/routers/teamRouter.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/backend/src/routers/teamRouter.ts b/backend/src/routers/teamRouter.ts index 9987418b0..52f89f80d 100644 --- a/backend/src/routers/teamRouter.ts +++ b/backend/src/routers/teamRouter.ts @@ -4,7 +4,14 @@ import { loginCheckedGet, loginCheckedPut, loginCheckedPost } from '../utils/aut const teamRouter = Router(); -loginCheckedGet(teamRouter, '/', async () => ({ teams: await allTeams() }), 'team'); +loginCheckedGet( + teamRouter, + '/', + async () => ({ teams: await allTeams() }), + 'team', + 'read', + async () => false +); loginCheckedPut( teamRouter, @@ -12,7 +19,9 @@ loginCheckedPut( async (req, user) => ({ team: await setTeam(req.body, user) }), - 'team' + 'team', + 'write', + async () => false ); // TODO: should eventually make this a delete request @@ -22,7 +31,9 @@ loginCheckedPost( async (req, user) => ({ team: await deleteTeam(req.body, user) }), - 'team' + 'team', + 'write', + async () => false ); export default teamRouter; From 9cbe06ee03dfce9a4c5109f9c8068f3b32267e84 Mon Sep 17 00:00:00 2001 From: henry-li-06 Date: Thu, 25 Jan 2024 22:29:39 -0500 Subject: [PATCH 49/49] too lazy to fix candidate decider --- backend/src/routers/candidateDeciderRouter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/routers/candidateDeciderRouter.ts b/backend/src/routers/candidateDeciderRouter.ts index d9ccfcb49..d5f4e3802 100644 --- a/backend/src/routers/candidateDeciderRouter.ts +++ b/backend/src/routers/candidateDeciderRouter.ts @@ -17,6 +17,10 @@ import { const candidateDeciderRouter = Router(); +// I think the flexibility of `userHasAccess` param for the auth middleware +// make it so that the auth doesn't have to be handled by the API router handler +// anymore. I just don't wanna do it. + loginCheckedGet(candidateDeciderRouter, '/', async (_, user) => ({ instances: await getAllCandidateDeciderInstances(user) }));