From d8537e4913eda1fd055424d0293a2607b6b0a089 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 22 Oct 2024 16:21:17 -0400 Subject: [PATCH 01/28] SCRUM-116 added get history and delete history routes --- backend/routes/dataRoutes.js | 44 +++++++++++++++++++++++++++++++++ backend/schemas/studyHistory.js | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/backend/routes/dataRoutes.js b/backend/routes/dataRoutes.js index 43f8b48a..40a82f66 100644 --- a/backend/routes/dataRoutes.js +++ b/backend/routes/dataRoutes.js @@ -3,6 +3,7 @@ const Classroom = require('../schemas/classroom.js'); const Schedule = require('../schemas/schedule.js'); const Rating = require('../schemas/rating.js'); const User = require('../schemas/user.js'); +const History = require('../schemas/studyHistory.js'); const Report = require('../schemas/report.js'); const { verifyToken, verifyTokenOptional } = require('../middlewares/verifyToken'); const { sortByAvailability } = require('../helpers.js'); @@ -261,5 +262,48 @@ router.get('/get-recommendation', verifyTokenOptional, async (req, res) => { } }); +router.get("/get-history", verifyToken, async (req,res) => { + const userId = req.user.userId; + try{ + //takes in user id, returns all study history objects associated with user + const getHistory = await History.find({ user_id : userId }); + + if(getHistory){ + console.log(`GET: /get-history`); + return res.status(200).json({success: true, message: 'History grabbed', data: getHistory}); + } else { + return res.status(404).json({ success: false, message: 'Could not get history' }); + } + + } catch(error){ + console.log(`GET: /get-history failed`, error); + return res.status(500).json({ success: false, message: 'Error finding user', error: error.message }); + } +}); + + +router.delete("/delete-history",verifyToken, async (req,res)=>{ + // takes in study history id and deletes object + const histId = req.body.histId; + try{ + const deleteHist = await History.deleteOne({ _id: histId}); + //check if successful, if success, return success status, if not, return 404 + //if deleted acount =0 then return 404 + + if (deleteHist.deletedCount!==0){ + console.log(`DELETE: /delete-history`); + return res.status(200).json({success: true, message: 'History sucessfully deleted', data: deleteHist}); + } else { + return res.status(404).json({ success: false, message: 'Could not delete history' }); + } + + + }catch(error){ + console.log(`DELETE: /delete-history failed`, error); + return res.status(500).json({ success: false, message: 'Error finding user', error: error.message }); + } +}); + + module.exports = router; diff --git a/backend/schemas/studyHistory.js b/backend/schemas/studyHistory.js index eda6bd7b..ba7ec065 100644 --- a/backend/schemas/studyHistory.js +++ b/backend/schemas/studyHistory.js @@ -10,7 +10,7 @@ const studyHistory = new Schema({ user_id: { type: Schema.Types.ObjectId, required: true, - ref: 'User' + ref: 'User' }, start_time: { type: Date, From 9096f602eadd0705ea1ef3bed2ab396fd09fcc2f Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Fri, 25 Oct 2024 17:23:25 -0400 Subject: [PATCH 02/28] SCRUM-125 Club, Members, Followers Schema Created --- backend/schemas/club.js | Bin 0 -> 1452 bytes backend/schemas/clubFollowers.js | Bin 0 -> 876 bytes backend/schemas/clubMember.js | 25 +++++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 backend/schemas/club.js create mode 100644 backend/schemas/clubFollowers.js create mode 100644 backend/schemas/clubMember.js diff --git a/backend/schemas/club.js b/backend/schemas/club.js new file mode 100644 index 0000000000000000000000000000000000000000..987685f2958637938ff8d95221352104cfefc763 GIT binary patch literal 1452 zcmc&!O;5r=5PfG8|3h!Z#Nb`S!N|qL#6-GYRhY1;12 z&c~a#GxPHuA;Sy>N(7ji^@QJyl9-YX-<+!tERdL03*nAlh%;Q_6en=KJ#zO-+bM=? zPfh=AxH`3mrk4mQW50NsLN%l3CI7C67QPIMfO0+7c7QptXBabwly(FDg=)g}Wy8kW zmZ)f@bHylWpD^YG=UxP>?Q=9TRpgEF#utA_m+;fR_A5i8D|@e*?v|BKE=J~uUy zY>*WuN1tz-rOVwlGs?+z#moa-@CneTBqo;&-o}t?ortZNyMj`!YN@JFoWwkrW`$>u zPa9Dh+Zy%x-+yD`drY6gP%@W3>sns6s6yfjP1UGwBA(^k`|<64x=byp8RMOPD^}Xn z>~x>1o@4Iwp4I+FtBANPXFb}>Q$b0WsKtiP9*8gFNq{^4BXd$$j*I9JOX46k-R@fK z%PV8Qsq6A1Z{F?9HhSxRD$s_T;VnkY#EpFjt?jA5Eyf02h$oYiZp`()GCN6Do0+AZ zUUzogkP}TEO~Bo$C5h@Mg{Gm^RlV4Lc5_!1S$V;=ot27Ndr}R?M!FLF~NeU$oVCj`0c;$udwP>39L)+9OOBLy>t7kWf>~8b{BqVIk z&YU?jbG|+cD)uzAB<$SzQQt~PDP*Cy*69_)vpZ#^qqvkQbIv)#XS>zklX7?5_e?Q= zjvv!8#4HsWH*YeImd^W=8 z%USj}4LLVrdQnuNnO%!-7StNJ!l`vtTj@8-{5xagYNuTGiqnAm6lEH>rTQQBwdxiB z-q-2JN7eQ)GA6_hnrMp22=(_plgHz%M%dtTtE{VGRHKw9 RO5Wt0O8afq;exT&Ip6wjg}(p* literal 0 HcmV?d00001 diff --git a/backend/schemas/clubMember.js b/backend/schemas/clubMember.js new file mode 100644 index 00000000..d67003eb --- /dev/null +++ b/backend/schemas/clubMember.js @@ -0,0 +1,25 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const memberSchema = new Schema({ + club_id: { + type: Schema.Types.ObjectId, + required: true, + ref: 'Club' + }, + user_id: { + type: Schema.Types.ObjectId, + required: true, + ref: 'User' + }, + status: { + //Ranking in club + type: Number, + required:true, + } +}); + + +const ClubMember = mongoose.model('Ratings', memberSchema, 'ratings'); + +module.exports = ClubMember; \ No newline at end of file From da8417d0586f5658434d636c75e60fe82ce9efc0 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 4 Nov 2024 16:09:38 -0500 Subject: [PATCH 03/28] Get/Create/Edit/Delete Clubs --- backend/routes/clubRoutes.js | Bin 1898 -> 13012 bytes backend/schemas/clubFollowers.js | Bin 876 -> 878 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 4d59b5af5d50b7587fd6db1cecb33f37f3a6dc12..69ce2942a9933eefcf0281a906001caee6425a46 100644 GIT binary patch literal 13012 zcmeI3ZEqXJ7RTo`65nBo5J@Di0|?%L0C7WjkboizNC=_gByO5ZleBRRl#;Iw{C{(N za%OjD*QO1jh%3vrvz|F~9{%TXx4-{(kj~O+nx~7@q*?koJxJ&Joa^;={`N2}(sBN_ zl{PyoHq&-`D?Oi{OC^C&B~FNzggRT_`A>&HytINeQ;(wExtgZ@5E zFAx55OT1r*SLB(d`&xIYH|HZ!TaHPZq@8r4)rWfB*Y7`Rk4e5WaB^MRlYHKh{yme< zW`ieo*VUU4hj{>XdgSHUb~}L z_+q0SXX4;UJBS8Tz2~An(@A(CCz>t{NBcUd$-Z`?HP9rl{R#W(fm)=B)fOg zo1%B9--i8}U%>YxopF%1vUfhzdh^n4dG`f20he6GU(gSH zG`e=CZ^!z($?~1&NI=Z9=rPY!k?2CFJzAd9Xf>}g);W4VmMjrzCZb9-nTjqrE>~H+ zbXstZwNAAk{o~b5+7*YUV|d{0>uYCo?li|QtRK54*=A6_CHgaI@Uf7K*5A~slBdh7 z7+ju(&9|=BZFjWb93erIKGxdc-)URq2m2R#V^3^{WzMti;kP`|X}Xlem6`Ns_S#C{ zrw`Mfasm9;F!iE%^s}+ZrFM9Ra@ziBC^F9{W1t=Iz7PfTta&~M1T4e_+=FvaGR-)G zi;coocXNzD--c@Eq||m@)5MXPq$GbBqMb|sEVX>9bFg!q*Vin*`+9ms??%W#&-e6= zt8E!-+Hc#)^@I(ldXsCmmbAB*F?vjY#3hrguEEVhgMG<1#c}A&wQ+gnpLPZP9c1qC z^DSxdT(2{|yQnoiex$Y733Q>A(`DrFU2|PUW60Zx4u}A=jrQ<~zaA~uK*1ugqo$_Q zCqpzgp6WFCJQSAEU@2QWmbiA-vELiQ#w*g!YW%OZ-<~vL+IIg&L&Ra%lY0XCL#I>K zC~aBrk&tZCL1`ZYhJZi@IxpYtO!PoD1izMx4#X|lmI^g8()DE`B5<5wUPt`>Niv2e z41q)(vM%cz=?lD}HOs`Vt$lGzY$%asnW>aiarmC06iNnod*hl#<4Nlq`dWwnl5&OeP#WdG1cH=_Cu|Kt>Eo_@Z#u6VO6 zS*$v84TI-l`RX~u`R(QszfOfgWpi7!t*$;!zeu(^`rMljv-IHYSXsfl(RMFuz6Q34 zm&eK^_q4KR@h&c{M{xN+TjI;`3GVE7uaw8pHdGh682D z3DX|ghKP!``J@}Oe9gbd`kOpJPk;@Ngeo)k6XHhX^nAJp*cTCk2Ww`UDUHJfr8 zUR3AkS+p=!=HF>e%i->Q=5?Oe{VH7q7N}XlH69V)JA=BHy}W{o_x)BM(%CT&qQiwQ zJ~xzchIg(yvyQ#G9Cc*XPp}G&`o}x)x(xkwP5M=@CVi`#5}EN}eEED?V{R(r-qp+I zC0zYZwP(#fOs{6jtV|5<j&{ZD(b_-ZgnU<4gpu!R^vY=Gm7@DXcP`QPA1VYpi3#u+?fw+tbLs2$ z)f}L#?xLO_@BCNgrq|Z)k77R1?yRv$IUOjuq!MmV8t!J)g1r9h!1Z)kbjo!*uHOfj z7H`tC)DN#?+ofP zZG=BK0{fkw%jXuDs8;ZdI*~MI$}*tYe3m@bq!%>j$Kq~j)BjLEw60dJr#Ie6--zdh zxU+ki%tvfiJk?by3MNg~$C86=g5YtW>`PrE>uM6L?JeVp(V?e`{+t>tt~>QE)&kYT zQ-+%7#B}0qNzR>Io22b7dz{*x5a%JP@jhZ4IX+hj{^GDMwIKYv}SkOLRV&Y=j(WJ-uoWtzxcF;)2thc86`7)pLuc5gBscM1U-ALdoPg{ zXLPYs@4Nc*6t_@d#k=jG?cY46{C|E@i@~lXuUhJMvds0BtZWl-EbRS|+oHr{+~F!$ zIgV^X>i2f*Cpy*oT$XsxiaK7}RYFZNr zsa@A(X&H}|{W(z=Cu1vZ%JS#vw>_h_w~m_NtL<@hd<>n^?*xo)?RrE5yQNVaO8 zT(w?pm%cv2!pJY!O zlve`HJp1PUM$LaSQx~uoae#Bdu0=%b#l3>^skLsrGmpNWJNk}8sJSMLlQJglN}JR# z(Y*~%Kk&UWwsk8mt2)Z+5X!^%!P`~J*yLj!tUqgW4~sir%j!%6?AZ`>L)?l=+b0~| zI}$M?;n}El`g-D^+xg!)V<{tnY1r=axK`?Y^H6n29psTF?B%pdKUmM7-pkp%q#E|J zsYJgLa3Ahx^Y;~xKgrK&?&WS_N3kaOjW~D{S-m{DU$2yRU7jwJKfN~oITNb*gNVUd~4C*?LdfSYCVDVCtg&ol)lH%RkL_Fxs5{76`c z*OMWIftP`cK>-9O%d=`S7EkV#R<74z&}6U%%jh%cGn6yrGZZmoGo%7lD*)MfK(d4( zgCUcl7|2Ql@n+a delta 10 ScmaFI_J(c3p^ZmYF#!M`rUhC6 From 3d563ba60766b3e00f369428d6dbd86c162e05a7 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 4 Nov 2024 16:13:37 -0500 Subject: [PATCH 04/28] SCRUM-125 Get/Create/Edit/Delete Clubs --- backend/routes/clubRoutes.js | Bin 13012 -> 13058 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 69ce2942a9933eefcf0281a906001caee6425a46..5c0f3a4fd38abba7854866fe53b98f1398c575fe 100644 GIT binary patch delta 78 zcmcbT+LX58AG0Yh0~don5IQq>Ft`FqZw3VhPX;$I83H838T=W7fTF<+x?r9ogAarE W=C91zf|E-WL?%hFZ0->Ckp%#C>JUNz delta 25 hcmZolyOO%$AM<7#mL$Q+*VqLnUt*WoyiL?c766h*3M2pk From 57bfc3e8f17160ffc2d5b71cc3ba28e07db78cac Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Wed, 6 Nov 2024 09:47:39 -0500 Subject: [PATCH 05/28] SCRUM-125 Get/Create/Edit/Delete Clubs fixed errors --- backend/app.js | 3 ++- backend/routes/clubRoutes.js | Bin 13058 -> 7166 bytes backend/schemas/club.js | Bin 1452 -> 755 bytes backend/schemas/clubFollowers.js | Bin 878 -> 420 bytes backend/schemas/clubMember.js | 2 +- 5 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app.js b/backend/app.js index 07279e5f..75a7ca47 100644 --- a/backend/app.js +++ b/backend/app.js @@ -64,6 +64,7 @@ const analyticsRoutes = require('./routes/analytics.js'); const classroomChangeRoutes = require('./routes/classroomChangeRoutes.js'); const ratingRoutes = require('./routes/ratingRoutes.js'); const searchRoutes = require('./routes/searchRoutes.js'); +const clubRoutes = require('./routes/clubRoutes.js'); app.use(authRoutes); app.use(dataRoutes); @@ -73,7 +74,7 @@ app.use(analyticsRoutes); app.use(classroomChangeRoutes); app.use(ratingRoutes); app.use(searchRoutes); - +app.use(clubRoutes); //deprecated, should lowk invest in this // app.get('/update-database', (req, res) => { // const pythonProcess = spawn('python3', ['courseScraper.py']); diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 5c0f3a4fd38abba7854866fe53b98f1398c575fe..25402f37f06b496cad870e9cbbed9a8e14279a54 100644 GIT binary patch literal 7166 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 Rm;mwou;;G$-xNdXe*kGg0RR91 diff --git a/backend/schemas/club.js b/backend/schemas/club.js index 987685f2958637938ff8d95221352104cfefc763..fcac4f62e3a5041ad4945c74f756e75279d09b71 100644 GIT binary patch literal 755 zcmb7C!A`?4488X&e9Mrc?%t^f1Q#SEBsNYHnzGdG0!dSxRHH)scammfY=gska-UzG z{cKCyIuFLSjkOM;1j3JA5l+|vV>h!*j;y)dpb>#{NaYEcWqX3u{W?J8$OaExbL7Uz*CPSyi3_pq9a=?jVonDbD;%SidE<&sm?3g zp0{;{)YxNZor*fHfHQ+|OC$yk(CB=mxY*MDYkoK1zb=(R7F zY%jz(vvsW`5=*bcs7947;=3MJ2GNJ-GYRhY1;12 z&c~a#GxPHuA;Sy>N(7ji^@QJyl9-YX-<+!tERdL03*nAlh%;Q_6en=KJ#zO-+bM=? zPfh=AxH`3mrk4mQW50NsLN%l3CI7C67QPIMfO0+7c7QptXBabwly(FDg=)g}Wy8kW zmZ)f@bHylWpD^YG=UxP>?Q=9TRpgEF#utA_m+;fR_A5i8D|@e*?v|BKE=J~uUy zY>*WuN1tz-rOVwlGs?+z#moa-@CneTBqo;&-o}t?ortZNyMj`!YN@JFoWwkrW`$>u zPa9Dh+Zy%x-+yD`drY6gP%@W3>sns6s6yfjP1UGwBA(^k`|<64x=byp8RMOPD^}Xn z>~x>1o@4Iwp4I+FtBANPXFb}>Q$b0WsKtiP9*8gFNq{^4BXd$$j*I9JOX46k-R@fK z%PV8Qsq6A1Z{F?9HhSxRD$s_T;VnkY#EpFjt?jA5Eyf02h$oYiZp`()GCN6Do0+AZ zUUzogkP}TEO~Bo$C5h@Mg{Gm^RlV4Lc5_!1S$V;=ot27Ndr}R?0;kRAZy%EZK#3w3Rz42mff`x7*VyF1X9Z~(jNy!ra3^MvY* zrLF4jN*SPAs^R4t4JnHn+Z061)JZ&`y&)4@+$#kMK&_-y1Ly_pV4%K8zl7ou(v{by z%Be3W$$oms1I5o+7=A3DAnc*a<~I`*euWRMcCgm#oU$FdvY8!y%NfxBA9F$1Wz2}N q#J%@nZ?n#(RTaoIiJ2rBjaQ#Y#$s)zhzase0*806v_+@IwD1lCrHb^`)o(Y6>~8b{BqVIk z&YU?jbG|+cRobgjt3*3@e&kz$lpqVJ>48J)g8_>l@eSEdiACjqxl0fW(l?&xBz3y|=Bh->w(1AI2Z z=gYP1ZyM#gf$4>)f~j4@Hw$VFT;bfhsx2KDGXKulxZ0^Md&OzMeTp)T+f)6I`damh zfA8z`<0R{Y*b7&Y7aF!?9Xj@vMb^xDt4tHc9-1)4WQ6+rp5gL1s{tE)Zk2U43^htT SQR)rnRP48 Date: Wed, 6 Nov 2024 17:12:31 -0500 Subject: [PATCH 06/28] SCRUM-125 Added Profanity Blockers --- backend/routes/clubRoutes.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 25402f37..46bc541d 100644 --- a/backend/routes/clubRoutes.js +++ b/backend/routes/clubRoutes.js @@ -7,11 +7,9 @@ const Club = require('../schemas/club.js'); const Follower = require('../schemas/clubFollowers.js'); const Member = require('../schemas/clubMember.js'); const { cloudiot_v1 } = require('googleapis'); -const { clean } = require('../services/profanityFilterService'); +const { clean, isProfane } = require('../services/profanityFilterService'); -//CHECK IF IT WORKS- WALK - //Route to get a specific club by name router.get("/get-club/:id", verifyToken, async(req,res)=>{ //May eventually change login permissions @@ -40,10 +38,9 @@ try{ }); - - -router.post("/create-club", verifyToken, async(req,res)=>{ // Create a new club and store it into database +router.post("/create-club", verifyToken, async(req,res)=>{ + try { const{clubId, club_profile_image, club_description, positions, weekly_meeting } = req.body; @@ -51,26 +48,28 @@ router.post("/create-club", verifyToken, async(req,res)=>{ const userId = req.user.userId; //if club name exist fail to create - const clubExist = await Club.findOne({club_name: req.body.club_name }); - + //Check to verify if the club already exists if (clubExist){ return res.status(400).json({ success: false, message: 'Club name already exists'}); } - // Sanitize club name and description verify this right - const cleanClubName = clean(req.body.club_name); - if (!cleanClubName) { + //Check if Bad Words + if (isProfane(req.body.club_name)) { return res.status(400).json({ success: false, message: 'Club name contains inappropriate language' }); } - const cleanClubDescription = clean(req.body.club_description); + // Sanitize club name and description verify this right + const cleanClubName = clean(req.body.club_name); - if (!cleanClubDescription) { - return res.status(400).json({ success: false, message: 'Contains inappropriate language' }); + //Check if Bad Words + if (isProfane(req.body.club_description)) { + return res.status(400).json({ success: false, message: 'Description contains inappropriate language' }); } + const cleanClubDescription = clean(req.body.club_description); + const newClub = new Club({ club_name: cleanClubName, @@ -110,6 +109,7 @@ router.post("/edit-club", verifyToken, async (req, res) => { const club = await Club.findById(clubId); // Check if the club exists + console.log if (!club) { return res.status(404).json({ success: false, message: "Club not found" }); } @@ -121,8 +121,10 @@ router.post("/edit-club", verifyToken, async (req, res) => { // If club_name is provided, clean it and check for inappropriate language if (club_name) { + const cleanClubName = clean(club_name); - if (!cleanClubName) { + + if (isProfane(club_name)) { return res.status(400).json({ success: false, message: "Club name contains inappropriate language" }); } @@ -139,7 +141,8 @@ router.post("/edit-club", verifyToken, async (req, res) => { // If club_description is provided, clean it if (club_description) { const cleanClubDescription = clean(club_description); - if (!cleanClubDescription) { + + if (isProfane(club_description)) { return res.status(400).json({ success: false, message: "Description contains inappropriate language" }); } club.club_description = cleanClubDescription; From 2994c6fd210a3d66a5a4c48e84097d2f57143f40 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Fri, 15 Nov 2024 16:42:35 -0500 Subject: [PATCH 07/28] SCRUM-125 Added Scrum Scalability --- backend/routes/clubRoutes.js | 68 +++++++++++++++--------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 46bc541d..d12ced24 100644 --- a/backend/routes/clubRoutes.js +++ b/backend/routes/clubRoutes.js @@ -95,12 +95,13 @@ router.post("/create-club", verifyToken, async(req,res)=>{ }); - +//Adjust Scalabilty router.post("/edit-club", verifyToken, async (req, res) => { - try { - const { clubId, club_profile_image, club_description, positions, weekly_meeting, club_name } = req.body; + const allowedFields=['club_profile_image', 'club_description', 'positions', 'weekly_meeting', 'club_name']; const userId = req.user?.userId; - + + try { + const {clubId, ...updateData}=req.body; // Validate that the essential fields are present if (!clubId) { return res.status(400).json({ success: false, message: "Club ID is required" }); @@ -109,7 +110,6 @@ router.post("/edit-club", verifyToken, async (req, res) => { const club = await Club.findById(clubId); // Check if the club exists - console.log if (!club) { return res.status(404).json({ success: false, message: "Club not found" }); } @@ -119,45 +119,33 @@ router.post("/edit-club", verifyToken, async (req, res) => { return res.status(400).json({ success: false, message: "You are not authorized to edit this club" }); } - // If club_name is provided, clean it and check for inappropriate language - if (club_name) { - - const cleanClubName = clean(club_name); - - if (isProfane(club_name)) { - return res.status(400).json({ success: false, message: "Club name contains inappropriate language" }); + // Loop through the allowed fields list to clean and update them as needed + for (const field of allowedFields) { + if (updateData[field] !== undefined) { + let value = updateData[field]; + + // Check for inappropriate language in each field + if (['club_name', 'club_description'].includes(field)) { + if (isProfane(value)) { + value = clean(value); + return res.status(400).json({ success: false, message: `${field.replace('_', ' ')} contains inappropriate language` }); + } + value = clean(value); } - // Check if a club with the cleaned name already exists, excluding the current club - const clubExist = await Club.findOne({ club_name: cleanClubName }); - if (clubExist && clubExist._id.toString() !== clubId) { - return res.status(400).json({ success: false, message: "Club name already taken" }); + // For unique fields, perform a duplicate check + if (field === 'club_name') { + const cleanClubName = value; + const clubExist = await Club.findOne({ club_name: cleanClubName }); + if (clubExist && clubExist._id.toString() !== clubId) { + return res.status(400).json({ success: false, message: "Club name already taken" }); + } + club[field] = cleanClubName; + } else { + club[field] = value; } - - // Update the club name with the clean version - club.club_name = cleanClubName; - } - - // If club_description is provided, clean it - if (club_description) { - const cleanClubDescription = clean(club_description); - - if (isProfane(club_description)) { - return res.status(400).json({ success: false, message: "Description contains inappropriate language" }); - } - club.club_description = cleanClubDescription; - } - - // Update other fields only if they are provided - if (club_profile_image) { - club.club_profile_image = club_profile_image; - } - if (positions) { - club.positions = positions; - } - if (weekly_meeting) { - club.weekly_meeting = weekly_meeting; } + } // Save the updated club await club.save(); From 5f90a65c103b197afe291485ead928fd91ead7f5 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 9 Dec 2024 20:32:54 -0500 Subject: [PATCH 08/28] SCRUM-158 Added Input Validator Middleware --- backend/middlewares/monitoring.js | 4 +++- backend/middlewares/validate.js | 14 +++++++++++++- backend/routes/apiRoutes.js | 9 +++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/backend/middlewares/monitoring.js b/backend/middlewares/monitoring.js index f4e94d17..2bb48809 100644 --- a/backend/middlewares/monitoring.js +++ b/backend/middlewares/monitoring.js @@ -1 +1,3 @@ -//Monitor and Logger \ No newline at end of file +//Monitor and Logger +const express = require('express'); +const morgan = require('morgan'); \ No newline at end of file diff --git a/backend/middlewares/validate.js b/backend/middlewares/validate.js index 1820b1bb..c8d19db2 100644 --- a/backend/middlewares/validate.js +++ b/backend/middlewares/validate.js @@ -1,6 +1,8 @@ //Input Validator const Joi = require('joi'); +//SCHEMA - MAY NEED TO ADD OR ADJUST THIS + const validateRequest = (schema, property = "body") => { return (req, res, next) => { const { error } = schema.validate(req[property], { abortEarly: false }); // Validate and collect all errors @@ -15,4 +17,14 @@ const validateRequest = (schema, property = "body") => { }; }; - module.exports = validateRequest; \ No newline at end of file + //Catches Malformed or missing data early + const createApiKeySchema =Joi.object({ + author_key: Joi.string().trim().required(), + api_key: Joi.string().trim().required(), + owner: Joi.string().trim().required(), + createdAt: Joi.date().optional(), + usageCount: Joi.number().integer().min(0).optional(), + }); + + + module.exports = {validateRequest, createApiKeySchema}; \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index fd224595..3ff02afe 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,7 +1,8 @@ const express = require('express'); const router = express.Router(); -const Api = require('../models/api'); -const User = require('../models/user'); // Assuming a User schema exists -const limiter = require('../middleware/rateLimit'); -const authenticate = require('../middleware/authenticate'); // Middleware for auth +const Api = require('../models/api.js'); +const User = require('../models/user.js'); +const limiter = require('../middleware/rateLimit.js'); // Rate limiting middleware +const apiKeyMiddleware = require('../middleware/apiKeyMiddleware.js'); // API key validation +const validateInput = require('../middleware/validate.js'); // Input validation middleware const crypto = require('crypto'); // For generating API keys \ No newline at end of file From 6f3daa21a9c23e62575f6dbc94de9564e5c864a0 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 10 Dec 2024 11:49:37 -0500 Subject: [PATCH 09/28] SCRUM-158 Added Monitor and Logging Middleware --- backend/middlewares/monitoring.js | 38 +++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/backend/middlewares/monitoring.js b/backend/middlewares/monitoring.js index 2bb48809..fcccd9c2 100644 --- a/backend/middlewares/monitoring.js +++ b/backend/middlewares/monitoring.js @@ -1,3 +1,37 @@ //Monitor and Logger -const express = require('express'); -const morgan = require('morgan'); \ No newline at end of file +const API = require('../models/api.js'); +const morgan = require('morgan'); + +//Logging HTTP Requests +//CAN CHANGE FORMATTING LATER +const logRequests=morgan('combined') + +//Monitoring and Tracking The API-KEY usage +const monitorUsage = async (req, res, next)=>{ + const {authorization_key, api_key} = req.headers; + + //If authorization/api missing - continue + if (!authorization_key || !api_key){ + return next(); + } + try{ + const apiEntry = await API.findOne({authorization_key, api_key}); + if (apiEntry){ + //If Key valid, count usage + apiEntry.usageCount +=1; + await apiEntry.save(); + + //Can delete later + console.log(`API Key used: ${api_key}, Usage Count: ${apiEntry.usageCount}`); + } + + next(); + } catch(error){ + //Change this error + console.error ('Error verifying or updating API key usage:', error); + //Continue + return next(); + } +}; + +module.exports = {logRequests, monitorUsage}; \ No newline at end of file From 05ea4381280ac09df1574156ef087d47655550dc Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 21 Jan 2025 16:28:36 -0500 Subject: [PATCH 10/28] SCRUM 158- API Modifications --- backend/middlewares/apiKeyMiddleware.js | 30 +++++++++- backend/middlewares/monitoring.js | 6 +- backend/middlewares/validate.js | 4 +- backend/routes/apiRoutes.js | 74 ++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index d55c9ac1..1282deea 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1 +1,29 @@ -//Stores API handling of where can be accessed \ No newline at end of file +//Stores API handling of where can be accessed +//KEY NOTES THAT IM RETURNING TO +//apiKeyMiddleWare -> apiRoutes +const API = require('../schemas/api.js'); +const validate = require('./validate.js'); +const monitoring = require('./monitoring.js'); +const rateLimit = require('./rateLimit.js'); + +// Middleware for validating API keys +const apiKeyMiddleware = async (req, res, next) => { + const { authorization_key, api_key } = req.headers; + + if (!authorization_key || !api_key) { + return res.status(401).json({ error: 'Missing authorization or API key.' }); + } + + try { + const apiEntry = await API.findOne({ authorization_key, api_key }); + if (!apiEntry) { + return res.status(401).json({ error: 'Invalid authorization or API key.' }); + } + next(); + + } catch (error) { + console.error('Error verifying API key:', error); + return res.status(500).json({ error: 'Internal server error.' }); + } +}; + diff --git a/backend/middlewares/monitoring.js b/backend/middlewares/monitoring.js index fcccd9c2..65018b21 100644 --- a/backend/middlewares/monitoring.js +++ b/backend/middlewares/monitoring.js @@ -1,5 +1,5 @@ //Monitor and Logger -const API = require('../models/api.js'); +const API = require('./schemas/api.js'); const morgan = require('morgan'); //Logging HTTP Requests @@ -27,11 +27,11 @@ const monitorUsage = async (req, res, next)=>{ next(); } catch(error){ - //Change this error + //Change this error messsage console.error ('Error verifying or updating API key usage:', error); //Continue return next(); } }; - +//Maybe could improve with new code module.exports = {logRequests, monitorUsage}; \ No newline at end of file diff --git a/backend/middlewares/validate.js b/backend/middlewares/validate.js index c8d19db2..535f956f 100644 --- a/backend/middlewares/validate.js +++ b/backend/middlewares/validate.js @@ -1,7 +1,8 @@ //Input Validator const Joi = require('joi'); -//SCHEMA - MAY NEED TO ADD OR ADJUST THIS +//SCHEMA - MAY NEED TO ADD OR ADJUST THIS, +//Convert to a rest api format so that my comman lines are easier to run const validateRequest = (schema, property = "body") => { return (req, res, next) => { @@ -17,6 +18,7 @@ const validateRequest = (schema, property = "body") => { }; }; + //Might remove this since mongoDB //Catches Malformed or missing data early const createApiKeySchema =Joi.object({ author_key: Joi.string().trim().required(), diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 3ff02afe..fc2a8d05 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,3 +1,4 @@ +// apiRoutes.js const express = require('express'); const router = express.Router(); const Api = require('../models/api.js'); @@ -5,4 +6,75 @@ const User = require('../models/user.js'); const limiter = require('../middleware/rateLimit.js'); // Rate limiting middleware const apiKeyMiddleware = require('../middleware/apiKeyMiddleware.js'); // API key validation const validateInput = require('../middleware/validate.js'); // Input validation middleware -const crypto = require('crypto'); // For generating API keys \ No newline at end of file +const crypto = require('crypto'); // For generating API keys + +// Generate a new API key +router.post('/generate', validateInput, async (req, res) => { + const { userId } = req.body; + + try { + const user = await User.findById(userId); + if (!user) { + return res.status(404).json({ error: 'User not found.' }); + } + + const apiKey = crypto.randomBytes(32).toString('hex'); + const newApi = new Api({ + authorization_key: crypto.randomBytes(16).toString('hex'), + api_key: apiKey, + owner: userId + }); + + await newApi.save(); + + res.status(201).json({ message: 'API key generated successfully.', apiKey: newApi }); + } catch (error) { + console.error('Error generating API key:', error); + res.status(500).json({ error: 'Internal server error.' }); + } +}); + +// Validate API key middleware +router.use('/protected', apiKeyMiddleware); + +// Get API key details +router.get('/details', apiKeyMiddleware, async (req, res) => { + const { authorization_key, api_key } = req.headers; + + try { + const apiEntry = await Api.findOne({ authorization_key, api_key }); + if (!apiEntry) { + return res.status(404).json({ error: 'API key not found.' }); + } + + res.status(200).json(apiEntry); + } catch (error) { + console.error('Error retrieving API key details:', error); + res.status(500).json({ error: 'Internal server error.' }); + } +}); + +// Delete an API key +router.delete('/delete', apiKeyMiddleware, async (req, res) => { + const { authorization_key, api_key } = req.headers; + + try { + const deletedApi = await Api.findOneAndDelete({ authorization_key, api_key }); + if (!deletedApi) { + return res.status(404).json({ error: 'API key not found.' }); + } + + res.status(200).json({ message: 'API key deleted successfully.' }); + } catch (error) { + console.error('Error deleting API key:', error); + res.status(500).json({ error: 'Internal server error.' }); + } +}); + +// Apply rate limiter +router.use(limiter); + +module.exports = router; + + +//DEBUG and Check if works \ No newline at end of file From 5fb3bc5f5707d26aa7c31acb015da0643ef8d6ea Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 21 Jan 2025 16:38:14 -0500 Subject: [PATCH 11/28] Trying to fix file changes --- package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 8af9d9fe..a5b7d122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "Study-Compass", "dependencies": { "bcrypt": "^5.1.1", "dotenv": "^16.3.1", From 708d2eb093941675a4f4f7f45bc56e65e50828a7 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 21 Jan 2025 16:44:37 -0500 Subject: [PATCH 12/28] Fixed App.js merge conflict --- backend/app.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/app.js b/backend/app.js index 8fc37ec4..0657f545 100644 --- a/backend/app.js +++ b/backend/app.js @@ -66,12 +66,9 @@ const analyticsRoutes = require('./routes/analytics.js'); const classroomChangeRoutes = require('./routes/classroomChangeRoutes.js'); const ratingRoutes = require('./routes/ratingRoutes.js'); const searchRoutes = require('./routes/searchRoutes.js'); -<<<<<<< HEAD const clubRoutes = require('./routes/clubRoutes.js'); -======= const eventRoutes = require('./routes/eventRoutes.js'); const oieRoutes = require('./routes/oie-routes.js'); ->>>>>>> 1c297de7e3f664954ded9815a98a269f53686810 app.use(authRoutes); app.use(dataRoutes); @@ -83,9 +80,7 @@ app.use(eventRoutes); app.use(classroomChangeRoutes); app.use(ratingRoutes); app.use(searchRoutes); -<<<<<<< HEAD app.use(clubRoutes); -======= app.use(eventRoutes); app.use(oieRoutes); @@ -100,7 +95,6 @@ if (process.env.NODE_ENV === 'production') { }); } ->>>>>>> 1c297de7e3f664954ded9815a98a269f53686810 //deprecated, should lowk invest in this // app.get('/update-database', (req, res) => { // const pythonProcess = spawn('python3', ['courseScraper.py']); From 81c93c699a41e8a0174fe582c353f89c0bf386c6 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Fri, 24 Jan 2025 16:50:13 -0500 Subject: [PATCH 13/28] Added Routes to App.js --- backend/app.js | 3 ++- backend/routes/apiRoutes.js | 42 +++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/backend/app.js b/backend/app.js index 0657f545..3c96ace7 100644 --- a/backend/app.js +++ b/backend/app.js @@ -69,6 +69,7 @@ const searchRoutes = require('./routes/searchRoutes.js'); const clubRoutes = require('./routes/clubRoutes.js'); const eventRoutes = require('./routes/eventRoutes.js'); const oieRoutes = require('./routes/oie-routes.js'); +const apiRoutes = require('./routes/apiRoutes.js'); app.use(authRoutes); app.use(dataRoutes); @@ -83,7 +84,7 @@ app.use(searchRoutes); app.use(clubRoutes); app.use(eventRoutes); app.use(oieRoutes); - +app.use(apiRoutes);//ADDED PK // Serve static files from the React app in production if (process.env.NODE_ENV === 'production') { diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index fc2a8d05..ca5bffe2 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,39 +1,52 @@ -// apiRoutes.js const express = require('express'); const router = express.Router(); const Api = require('../models/api.js'); -const User = require('../models/user.js'); +const User = require('../models/user.js'); const limiter = require('../middleware/rateLimit.js'); // Rate limiting middleware const apiKeyMiddleware = require('../middleware/apiKeyMiddleware.js'); // API key validation const validateInput = require('../middleware/validate.js'); // Input validation middleware const crypto = require('crypto'); // For generating API keys +const mongoose = require('mongoose'); + +// Generate a new API key validateInput, +router.post('/create_api', validateInput, async (req, res) => { + // DEBUG: Check if `req.user` exists and contains `userId` + if (!req.user || !req.user.userId) { + console.error('User ID is missing in the request.'); + return res.status(400).json({ error: 'User ID is required to generate an API key.' }); + } -// Generate a new API key -router.post('/generate', validateInput, async (req, res) => { - const { userId } = req.body; + const userId = req.user.userId; try { + // DEBUG: Verify if the user exists in the database const user = await User.findById(userId); if (!user) { + console.error(`User with ID ${userId} not found.`); return res.status(404).json({ error: 'User not found.' }); } + // Generate API key and authorization key const apiKey = crypto.randomBytes(32).toString('hex'); + const authorizationKey = crypto.randomBytes(16).toString('hex'); + + // Create and save the new API key entry const newApi = new Api({ - authorization_key: crypto.randomBytes(16).toString('hex'), + authorization_key: authorizationKey, api_key: apiKey, owner: userId }); await newApi.save(); + console.log('POST: /generate_api successful. API key generated:', newApi); res.status(201).json({ message: 'API key generated successfully.', apiKey: newApi }); } catch (error) { - console.error('Error generating API key:', error); + console.error('POST: /generate_api failed. Error:', error); res.status(500).json({ error: 'Internal server error.' }); } }); - +/* // Validate API key middleware router.use('/protected', apiKeyMiddleware); @@ -44,12 +57,14 @@ router.get('/details', apiKeyMiddleware, async (req, res) => { try { const apiEntry = await Api.findOne({ authorization_key, api_key }); if (!apiEntry) { + console.error('API key not found:', { authorization_key, api_key }); return res.status(404).json({ error: 'API key not found.' }); } + console.log('GET: /details successful. API key details:', apiEntry); res.status(200).json(apiEntry); } catch (error) { - console.error('Error retrieving API key details:', error); + console.error('GET: /details failed. Error:', error); res.status(500).json({ error: 'Internal server error.' }); } }); @@ -61,20 +76,21 @@ router.delete('/delete', apiKeyMiddleware, async (req, res) => { try { const deletedApi = await Api.findOneAndDelete({ authorization_key, api_key }); if (!deletedApi) { + console.error('API key not found for deletion:', { authorization_key, api_key }); return res.status(404).json({ error: 'API key not found.' }); } + console.log('DELETE: /delete successful. Deleted API key:', deletedApi); res.status(200).json({ message: 'API key deleted successfully.' }); } catch (error) { - console.error('Error deleting API key:', error); + console.error('DELETE: /delete failed. Error:', error); res.status(500).json({ error: 'Internal server error.' }); } }); // Apply rate limiter router.use(limiter); - -module.exports = router; +*/ -//DEBUG and Check if works \ No newline at end of file +module.exports = router; From 21960f365ce9d297002cbb5b9441b9a03297e8ed Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 28 Jan 2025 17:19:43 -0500 Subject: [PATCH 14/28] SCRUM 158- Fixed API Generation --- backend/app.js | 2 +- backend/middlewares/apiKeyMiddleware.js | 5 +- backend/middlewares/monitoring.js | 2 +- backend/package-lock.json | 72 +++++++++++++++++++++++++ backend/package.json | 2 + backend/routes/apiRoutes.js | 49 +++++++++-------- 6 files changed, 106 insertions(+), 26 deletions(-) diff --git a/backend/app.js b/backend/app.js index 3c96ace7..51dec161 100644 --- a/backend/app.js +++ b/backend/app.js @@ -69,7 +69,7 @@ const searchRoutes = require('./routes/searchRoutes.js'); const clubRoutes = require('./routes/clubRoutes.js'); const eventRoutes = require('./routes/eventRoutes.js'); const oieRoutes = require('./routes/oie-routes.js'); -const apiRoutes = require('./routes/apiRoutes.js'); +const apiRoutes = require('./routes/apiRoutes.js'); //Added Pk ERROR app.use(authRoutes); app.use(dataRoutes); diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 1282deea..9262ba2e 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -8,6 +8,7 @@ const rateLimit = require('./rateLimit.js'); // Middleware for validating API keys const apiKeyMiddleware = async (req, res, next) => { + console.log("THIS SHIT HIT") const { authorization_key, api_key } = req.headers; if (!authorization_key || !api_key) { @@ -22,8 +23,10 @@ const apiKeyMiddleware = async (req, res, next) => { next(); } catch (error) { - console.error('Error verifying API key:', error); + console.log('Error verifying API key:', error); return res.status(500).json({ error: 'Internal server error.' }); } }; + +module.exports = { apiKeyMiddleware }; \ No newline at end of file diff --git a/backend/middlewares/monitoring.js b/backend/middlewares/monitoring.js index 65018b21..9bc7a7ce 100644 --- a/backend/middlewares/monitoring.js +++ b/backend/middlewares/monitoring.js @@ -1,5 +1,5 @@ //Monitor and Logger -const API = require('./schemas/api.js'); +const API = require('../schemas/api.js'); const morgan = require('morgan'); //Logging HTTP Requests diff --git a/backend/package-lock.json b/backend/package-lock.json index 9bb27617..03518696 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,11 +18,13 @@ "date-fns": "^4.1.0", "dotenv": "^16.3.2", "express": "^4.18.2", + "express-rate-limit": "^7.5.0", "express-sslify": "^1.2.0", "google-auth-library": "^9.4.2", "googleapis": "^131.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", + "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", "node-cron": "^3.0.3", "nodemon": "^3.1.7", @@ -305,6 +307,24 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -893,6 +913,21 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express-sslify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/express-sslify/-/express-sslify-1.2.0.tgz", @@ -1932,6 +1967,34 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -2185,6 +2248,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/backend/package.json b/backend/package.json index 07f614d6..9ac2296b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,11 +9,13 @@ "date-fns": "^4.1.0", "dotenv": "^16.3.2", "express": "^4.18.2", + "express-rate-limit": "^7.5.0", "express-sslify": "^1.2.0", "google-auth-library": "^9.4.2", "googleapis": "^131.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", + "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", "node-cron": "^3.0.3", "nodemon": "^3.1.7", diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index ca5bffe2..1028e06a 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,30 +1,29 @@ const express = require('express'); const router = express.Router(); -const Api = require('../models/api.js'); -const User = require('../models/user.js'); -const limiter = require('../middleware/rateLimit.js'); // Rate limiting middleware -const apiKeyMiddleware = require('../middleware/apiKeyMiddleware.js'); // API key validation -const validateInput = require('../middleware/validate.js'); // Input validation middleware +const Api = require('../schemas/api.js'); +const User = require('../schemas/user.js'); +const limiter = require('../middlewares/rateLimit.js'); // Rate limiting middleware +const {apiKeyMiddleware} = require('../middlewares/apiKeyMiddleware.js'); // API key validation +const {validateInput} = require('../middlewares/validate.js'); // Input validation middleware const crypto = require('crypto'); // For generating API keys const mongoose = require('mongoose'); +const {verifyToken}= require('../middlewares/verifyToken'); -// Generate a new API key validateInput, -router.post('/create_api', validateInput, async (req, res) => { - // DEBUG: Check if `req.user` exists and contains `userId` - if (!req.user || !req.user.userId) { - console.error('User ID is missing in the request.'); - return res.status(400).json({ error: 'User ID is required to generate an API key.' }); - } - const userId = req.user.userId; +//Need to make the Api Usable and give access +//CURRENT ERRRORS +/** + * validate Input needs to be called like a middle ware { } go back through validate and change sutff up + * need my rate limited + * api Key Middle ware needs to be added with protected routes + */ +// Generate a new API key +router.post('/create_api', verifyToken, async (req, res) => { // Validate input needs to go in here try { - // DEBUG: Verify if the user exists in the database - const user = await User.findById(userId); - if (!user) { - console.error(`User with ID ${userId} not found.`); - return res.status(404).json({ error: 'User not found.' }); - } + const userId = req.user.userId; + + const user = await User.findById(userId); // Generate API key and authorization key const apiKey = crypto.randomBytes(32).toString('hex'); @@ -39,19 +38,21 @@ router.post('/create_api', validateInput, async (req, res) => { await newApi.save(); - console.log('POST: /generate_api successful. API key generated:', newApi); + console.log('POST: /create_api successful. API key generated:', newApi); res.status(201).json({ message: 'API key generated successfully.', apiKey: newApi }); } catch (error) { - console.error('POST: /generate_api failed. Error:', error); + console.error('POST: /create_api failed. Error:', error); res.status(500).json({ error: 'Internal server error.' }); } }); -/* + // Validate API key middleware router.use('/protected', apiKeyMiddleware); // Get API key details -router.get('/details', apiKeyMiddleware, async (req, res) => { + +//Get details LOOK at MIDDLEWARE Debugging guide +router.get('/protected/details', apiKeyMiddleware, async (req, res) => { const { authorization_key, api_key } = req.headers; try { @@ -69,6 +70,8 @@ router.get('/details', apiKeyMiddleware, async (req, res) => { } }); + +/* // Delete an API key router.delete('/delete', apiKeyMiddleware, async (req, res) => { const { authorization_key, api_key } = req.headers; From 657255f715c28699781b6c7c8cea97e0744a1f43 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 10 Feb 2025 10:50:57 -0500 Subject: [PATCH 15/28] SCRUM 158- Added Tangible routing for middleware --- backend/middlewares/apiKeyMiddleware.js | 7 ++--- backend/middlewares/rateLimit.js | 3 +-- backend/routes/apiRoutes.js | 35 ++++++++++++++++--------- backend/schemas/api.js | 6 ----- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index ece0c083..a77af234 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1,17 +1,18 @@ //Stores API handling of where can be accessed +//Will need to store the apiKey in this function to then check in any route for this key and if route exist then user can access const API = require('../schemas/api.js'); const monitoring = require('./monitoring.js'); // Middleware for validating API keys to make sure it exist, also validate that authorization is accepted const apiKeyMiddleware = async (req, res, next) => { - const { authorization_key, api_key } = req.body; + const { owner, api_key } = req.body; - if (!authorization_key || !api_key) { + if (!owner || !api_key) { return res.status(401).json({ error: 'Missing authorization or API key.' }); } try { - const apiEntry = await API.findOne({ authorization_key, api_key }); + const apiEntry = await API.findOne({ owner, api_key }); if (!apiEntry) { console.log("Cant verify authorization or API"); return res.status(401).json({ error: 'Invalid authorization or API key.' }); diff --git a/backend/middlewares/rateLimit.js b/backend/middlewares/rateLimit.js index 45fd3ed9..9a01c72c 100644 --- a/backend/middlewares/rateLimit.js +++ b/backend/middlewares/rateLimit.js @@ -1,4 +1,4 @@ -//Rate Limiter + //Rate Limiter const rateLimit=require('express-rate-limit'); @@ -11,5 +11,4 @@ const limiter=rateLimit({ message: 'Too many requests, please try again later.', }); - module.exports = limiter; \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 5b6d335b..50f25d66 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -10,18 +10,21 @@ const {verifyToken}= require('../middlewares/verifyToken'); // Generate a new API key -router.post('/create_api', verifyToken, limiter, async (req, res) => { // Limit to how many keys can be created by +router.post('/create_api', verifyToken, async (req, res) => { try { const userId = req.user.userId; const user = await User.findById(userId); - // Generate API key and authorization key + // Generate API key and verify user does not have any pre-existing one + const existingApi = await Api.findOne({ owner: userId }); + if (existingApi) { + console.log ('POST: /create_api failed. User already has an API key') + return res.status(400).json({ success: false, message: 'User already has an API key.' }); + } const apiKey = crypto.randomBytes(32).toString('hex'); - const authorizationKey = crypto.randomBytes(16).toString('hex');// Replace with userID // Create and save the new API key entry const newApi = new Api({ - authorization_key: authorizationKey, api_key: apiKey, owner: userId, }); @@ -39,13 +42,24 @@ router.post('/create_api', verifyToken, limiter, async (req, res) => { // Limit //WORKING RIGHT HERE // Validate API key middleware- Make sure API exists, safe and clean, //Should check and make sure middleware is present and linked to the account is what apiKeyMiddleware does -router.use('/protected', apiKeyMiddleware); // Make sure is nor redudndant -router.get('/protected/details', apiKeyMiddleware, limiter, async (req, res) => { + + +//router.use('/protected', apiKeyMiddleware); // ANY LINE THAT CONTAINS THE /protected will apply apiKeyMiddleware too +//I should also store apiKeyin th + +//Simplistic then detailed dont stress about key things +//One type of api key that has access to all routes that we can access +//If we want to grant access to a route, we want to grant access using the apiKeyMiddleware +//apiKeyMiddleware will guarantee and verify the existence of the key and then continue to grab items + +router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //See middleware debugging guid //Should allow access to any route,(will this be specified?) - const { authorization_key, api_key } = req.body; // ALL I need is thhe api_key userId + //const {owner, api_key } = req.body; try { - const apiEntry = await Api.findOneAndUpdate({ authorization_key, api_key }, { $inc: { usageCount: 1 } }, { new: true }); + //const apiEntry = await Api.findOneAndUpdate({api_key, owner }, { $inc: { usageCount: 1 } }, { new: true }); + //TEST THE NEW CODE AS OF 2/10/25 changes were made int apiKeyMiddleware for effectivness + const apiEntry = req.apiKeyData; if (!apiEntry) { console.log('API key not found', error); @@ -61,9 +75,8 @@ router.get('/protected/details', apiKeyMiddleware, limiter, async (req, res) => } }); - /* -// Delete an API key, userId could also be assumed// addeded +// Delete an API key, Verify user is the owner router.delete('/delete', apiKeyMiddleware, async (req, res) => { const { authorization_key, api_key } = req.headers; @@ -82,8 +95,6 @@ router.delete('/delete', apiKeyMiddleware, async (req, res) => { } }); -// Apply rate limiter -router.use(limiter); */ diff --git a/backend/schemas/api.js b/backend/schemas/api.js index bd187a40..60ea9a8e 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -2,12 +2,6 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const apiSchema = new Schema({ - authorization_key:{ - type: String, - required: true, - unique: true, // Ensure each authorization key is unique - trim: true, // Removes unnecessary whitespace - }, api_key:{ type: String, required: true, From 8b700112ee3cce0b48056cce8e36d479892238ff Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 10 Feb 2025 12:42:07 -0500 Subject: [PATCH 16/28] SCRUM 158- Removed Monitoring.js from middleware --- backend/middlewares/apiKeyMiddleware.js | 28 +++++++++++++------ backend/middlewares/monitoring.js | 37 ------------------------- backend/routes/apiRoutes.js | 5 ++-- 3 files changed, 23 insertions(+), 47 deletions(-) delete mode 100644 backend/middlewares/monitoring.js diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index a77af234..4da3492a 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -3,20 +3,32 @@ const API = require('../schemas/api.js'); const monitoring = require('./monitoring.js'); +//For routes http://localhost:5000/api/get-org-by-name/{club name} using the api key + // Middleware for validating API keys to make sure it exist, also validate that authorization is accepted const apiKeyMiddleware = async (req, res, next) => { - const { owner, api_key } = req.body; + //const { owner, api_key } = req.body; + const userId = req.user.userId - if (!owner || !api_key) { - return res.status(401).json({ error: 'Missing authorization or API key.' }); - } + //With these changes i made you shouldnt need owner box or api key it should just find the owner based on verify token, make sure i dont need this + + //if (!owner || !api_key) { + // return res.status(401).json({ error: 'Missing API key.' }); + //} try { - const apiEntry = await API.findOne({ owner, api_key }); - if (!apiEntry) { - console.log("Cant verify authorization or API"); - return res.status(401).json({ error: 'Invalid authorization or API key.' }); + const apiKeyData = await Api.findOne({ owner: userId }); + if (!apiKeyData) { + console.log("API key does not exist for user"); + return res.status(401).json({ error: 'API key does not exist for user' }); } + + // Increment the usage count + apiKeyData.usageCount = (apiKeyData.usageCount || 0) + 1; + await apiKeyData.save(); + console.log(`API Key Used. New usage count: ${apiKeyData.usageCount}`); + + req.apiKeyData = apiKeyData // Saves the apiKey into the middleware, to be used cross next(); } catch (error) { diff --git a/backend/middlewares/monitoring.js b/backend/middlewares/monitoring.js deleted file mode 100644 index 9bc7a7ce..00000000 --- a/backend/middlewares/monitoring.js +++ /dev/null @@ -1,37 +0,0 @@ -//Monitor and Logger -const API = require('../schemas/api.js'); -const morgan = require('morgan'); - -//Logging HTTP Requests -//CAN CHANGE FORMATTING LATER -const logRequests=morgan('combined') - -//Monitoring and Tracking The API-KEY usage -const monitorUsage = async (req, res, next)=>{ - const {authorization_key, api_key} = req.headers; - - //If authorization/api missing - continue - if (!authorization_key || !api_key){ - return next(); - } - try{ - const apiEntry = await API.findOne({authorization_key, api_key}); - if (apiEntry){ - //If Key valid, count usage - apiEntry.usageCount +=1; - await apiEntry.save(); - - //Can delete later - console.log(`API Key used: ${api_key}, Usage Count: ${apiEntry.usageCount}`); - } - - next(); - } catch(error){ - //Change this error messsage - console.error ('Error verifying or updating API key usage:', error); - //Continue - return next(); - } -}; -//Maybe could improve with new code -module.exports = {logRequests, monitorUsage}; \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 50f25d66..af960806 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -75,9 +75,10 @@ router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //Se } }); -/* +/* START WORKING ON THE DELETE TOOL TUESDAY AND THEN GO BACK FOR MANUVERABILITY +I can already see there is a few things that can be taken out // Delete an API key, Verify user is the owner -router.delete('/delete', apiKeyMiddleware, async (req, res) => { +router.delete('/delete', verifyToken, apiKeyMiddleware, async (req, res) => { const { authorization_key, api_key } = req.headers; try { From 836c44ab41de39530cadf01d31c04ed4771333c2 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 10 Feb 2025 12:55:32 -0500 Subject: [PATCH 17/28] SCRUM 158- Added Api Delete Rout --- backend/middlewares/apiKeyMiddleware.js | 5 ++--- backend/routes/apiRoutes.js | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 4da3492a..f081bfab 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1,9 +1,8 @@ //Stores API handling of where can be accessed //Will need to store the apiKey in this function to then check in any route for this key and if route exist then user can access const API = require('../schemas/api.js'); -const monitoring = require('./monitoring.js'); -//For routes http://localhost:5000/api/get-org-by-name/{club name} using the api key +//For routes http://localhost:5000/api/get-org-by-name/{club name} using the api key example route // Middleware for validating API keys to make sure it exist, also validate that authorization is accepted const apiKeyMiddleware = async (req, res, next) => { @@ -28,7 +27,7 @@ const apiKeyMiddleware = async (req, res, next) => { await apiKeyData.save(); console.log(`API Key Used. New usage count: ${apiKeyData.usageCount}`); - req.apiKeyData = apiKeyData // Saves the apiKey into the middleware, to be used cross + req.apiKeyData = apiKeyData // Saves the apiKey into the middleware, to be used across platforms next(); } catch (error) { diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index af960806..61290db1 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -13,7 +13,6 @@ const {verifyToken}= require('../middlewares/verifyToken'); router.post('/create_api', verifyToken, async (req, res) => { try { const userId = req.user.userId; - const user = await User.findById(userId); // Generate API key and verify user does not have any pre-existing one const existingApi = await Api.findOne({ owner: userId }); From 8247484293808b225af8888044a557bec79b1a03 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 10 Feb 2025 17:03:56 -0500 Subject: [PATCH 18/28] SCRUM 158- Implemented Delete, Fixed Bugs, Added to ApiKeyMiddleware --- backend/middlewares/apiKeyMiddleware.js | 42 ++++++++++++------------- backend/routes/apiRoutes.js | 33 ++++++------------- 2 files changed, 30 insertions(+), 45 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index f081bfab..56a85823 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1,38 +1,38 @@ -//Stores API handling of where can be accessed -//Will need to store the apiKey in this function to then check in any route for this key and if route exist then user can access const API = require('../schemas/api.js'); - +const apiKeyRateLimiter = require('../middlewares/rateLimit.js'); //For routes http://localhost:5000/api/get-org-by-name/{club name} using the api key example route - // Middleware for validating API keys to make sure it exist, also validate that authorization is accepted -const apiKeyMiddleware = async (req, res, next) => { - //const { owner, api_key } = req.body; - const userId = req.user.userId - - //With these changes i made you shouldnt need owner box or api key it should just find the owner based on verify token, make sure i dont need this - - //if (!owner || !api_key) { - // return res.status(401).json({ error: 'Missing API key.' }); - //} +const apiKeyMiddleware = async (req, res, next) => { + + const userId = req.user.userId; try { - const apiKeyData = await Api.findOne({ owner: userId }); + + const apiKeyData = await API.findOne({ owner: userId }); + console.log(apiKeyData); if (!apiKeyData) { console.log("API key does not exist for user"); return res.status(401).json({ error: 'API key does not exist for user' }); } - // Increment the usage count - apiKeyData.usageCount = (apiKeyData.usageCount || 0) + 1; - await apiKeyData.save(); - console.log(`API Key Used. New usage count: ${apiKeyData.usageCount}`); + // Apply the rate limiter + apiKeyRateLimiter(req, res, async (error) => { + // Stop execution if rate limit is exceeded + if (error) return; + + // Increment API key usage count + apiKeyData.usageCount = (apiKeyData.usageCount || 0) + 1; + await apiKeyData.save(); + console.log(`API Key Used. New usage count: ${apiKeyData.usageCount}`); - req.apiKeyData = apiKeyData // Saves the apiKey into the middleware, to be used across platforms - next(); + // Store the API key data in the request object + req.apiKeyData = apiKeyData; + next(); + }); } catch (error) { console.log('Error verifying API key:', error); - return res.status(500).json({ error: 'Internal server error.' }); + return res.status(500).json({ error: 'Could not verify API key' }); } }; diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 61290db1..b30d480e 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -8,7 +8,6 @@ const crypto = require('crypto'); // For generating API keys const mongoose = require('mongoose'); const {verifyToken}= require('../middlewares/verifyToken'); - // Generate a new API key router.post('/create_api', verifyToken, async (req, res) => { try { @@ -38,52 +37,39 @@ router.post('/create_api', verifyToken, async (req, res) => { } }); -//WORKING RIGHT HERE -// Validate API key middleware- Make sure API exists, safe and clean, -//Should check and make sure middleware is present and linked to the account is what apiKeyMiddleware does - - - //router.use('/protected', apiKeyMiddleware); // ANY LINE THAT CONTAINS THE /protected will apply apiKeyMiddleware too -//I should also store apiKeyin th //Simplistic then detailed dont stress about key things -//One type of api key that has access to all routes that we can access //If we want to grant access to a route, we want to grant access using the apiKeyMiddleware //apiKeyMiddleware will guarantee and verify the existence of the key and then continue to grab items - -router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //See middleware debugging guid + +router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //Should allow access to any route,(will this be specified?) - //const {owner, api_key } = req.body; try { - //const apiEntry = await Api.findOneAndUpdate({api_key, owner }, { $inc: { usageCount: 1 } }, { new: true }); - //TEST THE NEW CODE AS OF 2/10/25 changes were made int apiKeyMiddleware for effectivness const apiEntry = req.apiKeyData; if (!apiEntry) { console.log('API key not found', error); return res.status(404).json({ error: 'API key not found.' }); } + console.log('GET: /details successful. API key details:', apiEntry); res.status(200).json(apiEntry); } catch (error) { console.log('GET: /details failed. Error', error); - res.status(500).json({ error: 'Internal server error.' }); + res.status(500).json({ success: false, message: 'Unable to retrieve details.' }); } }); -/* START WORKING ON THE DELETE TOOL TUESDAY AND THEN GO BACK FOR MANUVERABILITY -I can already see there is a few things that can be taken out // Delete an API key, Verify user is the owner -router.delete('/delete', verifyToken, apiKeyMiddleware, async (req, res) => { - const { authorization_key, api_key } = req.headers; - +router.delete('/delete-api', verifyToken, apiKeyMiddleware, async (req, res) => { + const userId = req.user.userId; try { - const deletedApi = await Api.findOneAndDelete({ authorization_key, api_key }); + const deletedApi = await Api.findOneAndDelete({ owner: userId }); if (!deletedApi) { - console.error('API key not found for deletion:', { authorization_key, api_key }); + console.error('API key not found for deletion'); return res.status(404).json({ error: 'API key not found.' }); } @@ -91,11 +77,10 @@ router.delete('/delete', verifyToken, apiKeyMiddleware, async (req, res) => { res.status(200).json({ message: 'API key deleted successfully.' }); } catch (error) { console.error('DELETE: /delete failed. Error:', error); - res.status(500).json({ error: 'Internal server error.' }); + res.status(500).json({success:false, message : 'Error deleting API.' }); } }); -*/ module.exports = router; From f807707e1784679c8d3accc5110a94e8560dd214 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Mon, 10 Feb 2025 17:24:04 -0500 Subject: [PATCH 19/28] SCRUM 158- Cleanup to code --- backend/middlewares/apiKeyMiddleware.js | 3 ++- backend/middlewares/rateLimit.js | 2 +- backend/routes/apiRoutes.js | 2 +- backend/schemas/api.js | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 56a85823..6b4396f9 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -15,9 +15,10 @@ const apiKeyMiddleware = async (req, res, next) => { return res.status(401).json({ error: 'API key does not exist for user' }); } + //Add a status for rate limit here, better status more usages + // Apply the rate limiter apiKeyRateLimiter(req, res, async (error) => { - // Stop execution if rate limit is exceeded if (error) return; // Increment API key usage count diff --git a/backend/middlewares/rateLimit.js b/backend/middlewares/rateLimit.js index 9a01c72c..a028365d 100644 --- a/backend/middlewares/rateLimit.js +++ b/backend/middlewares/rateLimit.js @@ -7,7 +7,7 @@ const limiter=rateLimit({ // 15 minutes windowMs: 15 * 60 * 1000, // Limit each IP to 100 requests per windowMs - max: 100, + max: 100, //This would be maxRequests message: 'Too many requests, please try again later.', }); diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index b30d480e..fdce15a2 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -43,7 +43,7 @@ router.post('/create_api', verifyToken, async (req, res) => { //If we want to grant access to a route, we want to grant access using the apiKeyMiddleware //apiKeyMiddleware will guarantee and verify the existence of the key and then continue to grab items -router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { +router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //name to be changed //Should allow access to any route,(will this be specified?) try { const apiEntry = req.apiKeyData; diff --git a/backend/schemas/api.js b/backend/schemas/api.js index 60ea9a8e..b28bee15 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -5,7 +5,7 @@ const apiSchema = new Schema({ api_key:{ type: String, required: true, - unique: true, // Ensure each API key is unique + unique: true, trim: true, }, owner: { @@ -15,7 +15,7 @@ const apiSchema = new Schema({ }, createdAt: { type: Date, - default: Date.now, // Automatically record when the key was created + default: Date.now, }, usageCount: { type: Number, From ece2a1c573c598c6d76f872da5dfdfbd0757f04b Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Wed, 12 Feb 2025 15:36:03 -0500 Subject: [PATCH 20/28] SCRUM 158- Updating Misc --- backend/middlewares/apiKeyMiddleware.js | 8 ++++++-- backend/routes/apiRoutes.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 6b4396f9..34588ea0 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -9,13 +9,14 @@ const apiKeyMiddleware = async (req, res, next) => { try { const apiKeyData = await API.findOne({ owner: userId }); + console.log(apiKeyData); if (!apiKeyData) { console.log("API key does not exist for user"); return res.status(401).json({ error: 'API key does not exist for user' }); } - //Add a status for rate limit here, better status more usages + //Add a status for rate limit here, better status more usages , see how wants // Apply the rate limiter apiKeyRateLimiter(req, res, async (error) => { @@ -38,4 +39,7 @@ const apiKeyMiddleware = async (req, res, next) => { }; -module.exports = { apiKeyMiddleware }; \ No newline at end of file +module.exports = { apiKeyMiddleware }; + + +//New task to do is to now apply apiKeyMiddleware into different branches to see if will return me a status \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index fdce15a2..a12f0a97 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -68,6 +68,7 @@ router.delete('/delete-api', verifyToken, apiKeyMiddleware, async (req, res) => const userId = req.user.userId; try { const deletedApi = await Api.findOneAndDelete({ owner: userId }); + if (!deletedApi) { console.error('API key not found for deletion'); return res.status(404).json({ error: 'API key not found.' }); From 291791983badbf9e9c1b2b463b873b9b2c34f539 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Fri, 21 Feb 2025 10:42:59 -0500 Subject: [PATCH 21/28] SCRUM 158- Update api.js --- backend/middlewares/apiKeyMiddleware.js | 4 +-- backend/routes/apiRoutes.js | 34 ++++++++++++++++++++++++- backend/schemas/api.js | 6 +++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 34588ea0..1bf98c4d 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -5,10 +5,10 @@ const apiKeyRateLimiter = require('../middlewares/rateLimit.js'); const apiKeyMiddleware = async (req, res, next) => { - const userId = req.user.userId; + const userId = req.user.userId; // Verify apiId try { - const apiKeyData = await API.findOne({ owner: userId }); + const apiKeyData = await API.findOne({ owner: userId }); //change owner to find apiKey console.log(apiKeyData); if (!apiKeyData) { diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index a12f0a97..0b93d443 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -43,7 +43,39 @@ router.post('/create_api', verifyToken, async (req, res) => { //If we want to grant access to a route, we want to grant access using the apiKeyMiddleware //apiKeyMiddleware will guarantee and verify the existence of the key and then continue to grab items -router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //name to be changed +//Api key adds other servers to use our routes, functionallity isnt to have to make a verify token +//Instead of checking user we should be checking the api key, trust any request with a valid api key, take out verifyToken, + + +//Verify the api key, have a special header for api keys, when header is present should check validity of the api key, then pass them through rate limiters +//this is the only one that takes outside servers that not our front, prolly only given to none verify token routes, change user validity to api valdity + +//associating a header : with an api key +/* +Tasks to do: +change user id to strictly api idea +find a way to test the request coming from a different server (new api with a new routing system +): create a new repositiory, a light weight server to use fetch or axios to call routes from a different server (dont know the logistics) +Set up one route/action + +calling a different api to another server + +have the new header that signifys an api request, if the api request to see if valid, if so let them through the next process +maybe add more validity +Unauthorized organization vs authorized prompt for rate (header in the schema), hard code how much rate each gets + +Look through middleware debugger for anything + +- change validity from user, to strictly api key validation- make sure exists and cant be looped around +- rate limiting authority verification | associate the api in the create function with a tag based on their user id, would need to be assigned that tag from front end, but have it in schema +//so thunder clients say "Authorization:" unauthorized_org , "apikey" +-api caller program + +*/ + + + +router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //This is a place holder //Should allow access to any route,(will this be specified?) try { const apiEntry = req.apiKeyData; diff --git a/backend/schemas/api.js b/backend/schemas/api.js index b28bee15..b150f75a 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -20,6 +20,12 @@ const apiSchema = new Schema({ usageCount: { type: Number, default: 0, + }, + apiAuthorization: { + type: String, + required: true, + default:['Unauthorized', 'Authorized'], + } }); const API = mongoose.model("api", apiSchema); From 34ace2e60e54f5b88f825a32c2f6b7eea11c30cb Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Fri, 21 Feb 2025 11:08:43 -0500 Subject: [PATCH 22/28] SCRUM 158- Added Dynamic rate limiting --- backend/middlewares/apiKeyMiddleware.js | 30 +++++++++++++++++++++---- backend/middlewares/rateLimit.js | 4 +++- backend/schemas/api.js | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 1bf98c4d..565044e2 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -5,10 +5,11 @@ const apiKeyRateLimiter = require('../middlewares/rateLimit.js'); const apiKeyMiddleware = async (req, res, next) => { - const userId = req.user.userId; // Verify apiId + //User attached Api Key + const apiKey = req.headers['x-api-key']; // Verify apiId CHANGED try { - - const apiKeyData = await API.findOne({ owner: userId }); //change owner to find apiKey + //Verifies the Existence + const apiKeyData = await API.findOne({apiKey}); //Check if works console.log(apiKeyData); if (!apiKeyData) { @@ -16,9 +17,30 @@ const apiKeyMiddleware = async (req, res, next) => { return res.status(401).json({ error: 'API key does not exist for user' }); } - //Add a status for rate limit here, better status more usages , see how wants + //Add a status for rate limit here, better status more usages + const clearance = apiKeyData.Authorization ||"default"; //NEED TO ATTACH A TAG WITH KEY, test both cases, front end will handle who gets the tag + let maxRequests; + + //Basic Framework if we want to change the different types + switch (clearance){ + case "Unauthorized": + maxRequests = 100; + console.log(apiKeyData, "Unauthorized User Key: Limited ", maxRequests, " requests.\nCurrent Usage: ", apiKeyData.usageCount); + break; + case "Authorized": + maxRequests = 500; + console.log(apiKeyData, "Authorized User Key: Limited ", maxRequests, " requests.\nCurrent Usage: ", apiKeyData.usageCount); + break; + + default: //Default with no tag is still unauthorized + maxRequests = 100; + console.log(apiKeyData, "Unauthorized User Key: Limited ", maxRequests, " requests.\nCurrent Usage: ", apiKeyData.usageCount); + break; + } + // Apply the rate limiter + const apiKeyRateLimiter = limiter(maxRequests); apiKeyRateLimiter(req, res, async (error) => { if (error) return; diff --git a/backend/middlewares/rateLimit.js b/backend/middlewares/rateLimit.js index a028365d..cb5c4a07 100644 --- a/backend/middlewares/rateLimit.js +++ b/backend/middlewares/rateLimit.js @@ -2,7 +2,8 @@ const rateLimit=require('express-rate-limit'); -const limiter=rateLimit({ +const limiter=(maxRequests)=>{ + return rateLimit({ //Adjust Levels of access Different levels of rate keys and different routes will have different levels, adjust this later // 15 minutes windowMs: 15 * 60 * 1000, @@ -10,5 +11,6 @@ const limiter=rateLimit({ max: 100, //This would be maxRequests message: 'Too many requests, please try again later.', }); +}; module.exports = limiter; \ No newline at end of file diff --git a/backend/schemas/api.js b/backend/schemas/api.js index b150f75a..e3c0e944 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -21,7 +21,7 @@ const apiSchema = new Schema({ type: Number, default: 0, }, - apiAuthorization: { + Authorization: { type: String, required: true, default:['Unauthorized', 'Authorized'], From 53315410e0e0a39eafe138f83a7575441fb39f3b Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 18 Mar 2025 16:25:40 -0400 Subject: [PATCH 23/28] SCRUM 158- Updated Routing --- backend/app.js | 2 ++ backend/middlewares/apiKeyMiddleware.js | 10 ++++------ backend/routes/apiRoutes.js | 22 ++++++++++++++++++---- backend/schemas/api.js | 14 ++++++++------ backend/services/getModelService.js | 4 +++- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/backend/app.js b/backend/app.js index c2fb564c..52646191 100644 --- a/backend/app.js +++ b/backend/app.js @@ -66,6 +66,8 @@ app.use(async (req, res, next) => { res.status(500).send('Database connection error'); } }); +// berkeley.study-compass.com + const upload = multer({ storage: multer.memoryStorage(), diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 565044e2..34ee39b7 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -6,10 +6,11 @@ const apiKeyRateLimiter = require('../middlewares/rateLimit.js'); const apiKeyMiddleware = async (req, res, next) => { //User attached Api Key - const apiKey = req.headers['x-api-key']; // Verify apiId CHANGED + const apiKey = req.headers['x-api-key']; // Verify apiId CHANGED see if it works HAVE TO CHANGE try { //Verifies the Existence const apiKeyData = await API.findOne({apiKey}); //Check if works + console.log(apiKeyData); if (!apiKeyData) { @@ -18,7 +19,7 @@ const apiKeyMiddleware = async (req, res, next) => { } //Add a status for rate limit here, better status more usages - const clearance = apiKeyData.Authorization ||"default"; //NEED TO ATTACH A TAG WITH KEY, test both cases, front end will handle who gets the tag + const clearance = apiKeyData.Authorization || "default"; // NEED TO ATTACH A TAG WITH KEY, test both cases, front end will handle who gets the tag let maxRequests; //Basic Framework if we want to change the different types @@ -32,7 +33,7 @@ const apiKeyMiddleware = async (req, res, next) => { console.log(apiKeyData, "Authorized User Key: Limited ", maxRequests, " requests.\nCurrent Usage: ", apiKeyData.usageCount); break; - default: //Default with no tag is still unauthorized + default: //Default with no tag is still unauthorized, see if this work maxRequests = 100; console.log(apiKeyData, "Unauthorized User Key: Limited ", maxRequests, " requests.\nCurrent Usage: ", apiKeyData.usageCount); break; @@ -62,6 +63,3 @@ const apiKeyMiddleware = async (req, res, next) => { module.exports = { apiKeyMiddleware }; - - -//New task to do is to now apply apiKeyMiddleware into different branches to see if will return me a status \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 0b93d443..5edd5140 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,17 +1,21 @@ const express = require('express'); const router = express.Router(); -const Api = require('../schemas/api.js'); -const User = require('../schemas/user.js'); const limiter = require('../middlewares/rateLimit.js'); // Rate limiting middleware const {apiKeyMiddleware} = require('../middlewares/apiKeyMiddleware.js'); // API key validation const crypto = require('crypto'); // For generating API keys const mongoose = require('mongoose'); const {verifyToken}= require('../middlewares/verifyToken'); +const getModels = require('../services/getModelService.js'); // Generate a new API key router.post('/create_api', verifyToken, async (req, res) => { try { + const { Api } = getModels(req, 'Api'); + //Add a set authorization here, just a tag to connect to api key front end will handle assigning + //Authorized or Unauthorized try both + const userId = req.user.userId; + const user_status = req.headers["Authorization"];// see if this works // Generate API key and verify user does not have any pre-existing one const existingApi = await Api.findOne({ owner: userId }); @@ -25,6 +29,7 @@ router.post('/create_api', verifyToken, async (req, res) => { const newApi = new Api({ api_key: apiKey, owner: userId, + Authorization: user_status // see if this works }); await newApi.save(); @@ -71,14 +76,22 @@ Look through middleware debugger for anything //so thunder clients say "Authorization:" unauthorized_org , "apikey" -api caller program + + + +3/11/25 +Changed interface and teach new syntax +Every route where i call a database route +small change or medium change */ -router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //This is a place holder +router.get('/details', apiKeyMiddleware, async (req, res) => { //This is a place holder //Should allow access to any route,(will this be specified?) + const apiKey = req.headers['x-api-key'];// see if this call works try { - const apiEntry = req.apiKeyData; + const apiEntry = req.apiKey; if (!apiEntry) { console.log('API key not found', error); @@ -99,6 +112,7 @@ router.get('/details', verifyToken, apiKeyMiddleware, async (req, res) => { //Th router.delete('/delete-api', verifyToken, apiKeyMiddleware, async (req, res) => { const userId = req.user.userId; try { + const { Api } = getModels(req, 'Api'); const deletedApi = await Api.findOneAndDelete({ owner: userId }); if (!deletedApi) { diff --git a/backend/schemas/api.js b/backend/schemas/api.js index e3c0e944..2d55e264 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -22,12 +22,14 @@ const apiSchema = new Schema({ default: 0, }, Authorization: { - type: String, - required: true, - default:['Unauthorized', 'Authorized'], + type: String, + enum: ["Unauthorized", "Authorized"], + default: "default" + } - } }); -const API = mongoose.model("api", apiSchema); +// const API = mongoose.model("api", apiSchema); + +// module.exports = API;//NO LONGER SUPPORTED -module.exports = API; \ No newline at end of file +module.exports = apiSchema; \ No newline at end of file diff --git a/backend/services/getModelService.js b/backend/services/getModelService.js index d33bd3d2..627c0170 100644 --- a/backend/services/getModelService.js +++ b/backend/services/getModelService.js @@ -18,6 +18,7 @@ const searchSchema = require('../schemas/search'); const studyHistorySchema = require('../schemas/studyHistory'); const userSchema = require('../schemas/user'); const visitSchema = require('../schemas/visit'); +const apiSchema = require('../schemas/api'); const getModels = (req, ...names) => { const models = { @@ -40,7 +41,8 @@ const getModels = (req, ...names) => { Search: req.db.model('Search', searchSchema, 'searches'), StudyHistory: req.db.model('StudyHistory', studyHistorySchema, 'studyHistories'), User: req.db.model('User', userSchema, 'users'), - Visit: req.db.model('Visit', visitSchema, 'visits') + Visit: req.db.model('Visit', visitSchema, 'visits'), + Api: req.db.model('Api', apiSchema, 'api') }; return names.reduce((acc, name) => { From c344c2861946636b1f26ab57b955b1ac29e58428 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 18 Mar 2025 17:16:06 -0400 Subject: [PATCH 24/28] SCRUM 158- Fixed all errors and Updated Authorization --- backend/middlewares/apiKeyMiddleware.js | 6 ++++-- backend/middlewares/rateLimit.js | 2 +- backend/routes/apiRoutes.js | 24 ++++++++++-------------- backend/schemas/api.js | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 34ee39b7..81e345e5 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1,15 +1,17 @@ const API = require('../schemas/api.js'); -const apiKeyRateLimiter = require('../middlewares/rateLimit.js'); +const limiter = require('../middlewares/rateLimit.js'); +const getModels = require('../services/getModelService.js'); //For routes http://localhost:5000/api/get-org-by-name/{club name} using the api key example route // Middleware for validating API keys to make sure it exist, also validate that authorization is accepted const apiKeyMiddleware = async (req, res, next) => { //User attached Api Key + const { Api } = getModels(req, 'Api') const apiKey = req.headers['x-api-key']; // Verify apiId CHANGED see if it works HAVE TO CHANGE try { //Verifies the Existence - const apiKeyData = await API.findOne({apiKey}); //Check if works + const apiKeyData = await Api.findOne({apiKey}); //Check if works console.log(apiKeyData); diff --git a/backend/middlewares/rateLimit.js b/backend/middlewares/rateLimit.js index cb5c4a07..39aaf2b1 100644 --- a/backend/middlewares/rateLimit.js +++ b/backend/middlewares/rateLimit.js @@ -8,7 +8,7 @@ const limiter=(maxRequests)=>{ // 15 minutes windowMs: 15 * 60 * 1000, // Limit each IP to 100 requests per windowMs - max: 100, //This would be maxRequests + max: maxRequests, message: 'Too many requests, please try again later.', }); }; diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 5edd5140..1acfca99 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -87,24 +87,20 @@ small change or medium change -router.get('/details', apiKeyMiddleware, async (req, res) => { //This is a place holder - //Should allow access to any route,(will this be specified?) - const apiKey = req.headers['x-api-key'];// see if this call works - try { - const apiEntry = req.apiKey; +router.get('/details', apiKeyMiddleware, async (req, res, next) => { + const apiKey = req.headers['x-api-key']; - if (!apiEntry) { - console.log('API key not found', error); - return res.status(404).json({ error: 'API key not found.' }); + try { + const apiKeyData = req.apiKeyData; // Assuming you set this in apiKeyMiddleware + if (!apiKeyData) { + return res.status(404).json({ error: 'API key not found' }); } - console.log('GET: /details successful. API key details:', apiEntry); - res.status(200).json(apiEntry); + console.log('GET: /details successful. API key details:', apiKeyData); + return res.status(200).json(apiKeyData); } catch (error) { - - - console.log('GET: /details failed. Error', error); - res.status(500).json({ success: false, message: 'Unable to retrieve details.' }); + console.error('GET: /details failed. Error:', error); // Correctly reference 'error' + return next(error); // Pass the error to the next middleware or global error handler } }); diff --git a/backend/schemas/api.js b/backend/schemas/api.js index 2d55e264..704581e7 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -24,7 +24,7 @@ const apiSchema = new Schema({ Authorization: { type: String, enum: ["Unauthorized", "Authorized"], - default: "default" + } }); From afdd85026da64e11f71a1d5818bcc475e7e6c779 Mon Sep 17 00:00:00 2001 From: Phoenix Keck Date: Tue, 18 Mar 2025 17:17:35 -0400 Subject: [PATCH 25/28] SCRUM-158 Removed comments --- backend/routes/apiRoutes.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 1acfca99..97dcb600 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -58,7 +58,6 @@ router.post('/create_api', verifyToken, async (req, res) => { //associating a header : with an api key /* Tasks to do: -change user id to strictly api idea find a way to test the request coming from a different server (new api with a new routing system ): create a new repositiory, a light weight server to use fetch or axios to call routes from a different server (dont know the logistics) Set up one route/action @@ -91,7 +90,7 @@ router.get('/details', apiKeyMiddleware, async (req, res, next) => { const apiKey = req.headers['x-api-key']; try { - const apiKeyData = req.apiKeyData; // Assuming you set this in apiKeyMiddleware + const apiKeyData = req.apiKeyData; if (!apiKeyData) { return res.status(404).json({ error: 'API key not found' }); } @@ -99,8 +98,8 @@ router.get('/details', apiKeyMiddleware, async (req, res, next) => { console.log('GET: /details successful. API key details:', apiKeyData); return res.status(200).json(apiKeyData); } catch (error) { - console.error('GET: /details failed. Error:', error); // Correctly reference 'error' - return next(error); // Pass the error to the next middleware or global error handler + console.error('GET: /details failed. Error:', error); + return next(error); } }); From b6b4c5823606b75caef89c4234158af02a51f037 Mon Sep 17 00:00:00 2001 From: AZ0228 <53315675+AZ0228@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:51:39 -0700 Subject: [PATCH 26/28] SCRUM-158 adding temporary api access --- backend/package-lock.json | 30 ------------------------------ backend/routes/apiRoutes.js | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 03518696..106156a9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1149,36 +1149,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "optional": true, - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 1acfca99..3a8786ae 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -6,6 +6,7 @@ const crypto = require('crypto'); // For generating API keys const mongoose = require('mongoose'); const {verifyToken}= require('../middlewares/verifyToken'); const getModels = require('../services/getModelService.js'); +require('dotenv').config(); // Generate a new API key router.post('/create_api', verifyToken, async (req, res) => { @@ -124,6 +125,29 @@ router.delete('/delete-api', verifyToken, apiKeyMiddleware, async (req, res) => } }); +//temporary logic just to test connection +function checkApiKey(req, res, next) { + const apiKey = req.headers['x-api-key']; + if (apiKey === process.env.EDUREKA_API) { + return next(); + } else { + return res.status(403).json({ error: 'Forbidden: Invalid API key' }); + } +} + +router.get('/api/events', limiter(100), checkApiKey, async (req, res) => { + + try { + const { Event } = getModels(req, 'Event'); + const events = await Event.find({}); + + console.log('GET: /api/events successful. Events:', events); + res.status(200).json(events); + } catch (error) { + console.error('GET: /api/events failed. Error:', error); + res.status(500).json({ error: 'Unable to retrieve events' }); + } +}); module.exports = router; From 6d640eb69336917f1e583051dd0f0d4c440aee15 Mon Sep 17 00:00:00 2001 From: AZ0228 <53315675+AZ0228@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:58:24 -0700 Subject: [PATCH 27/28] SCRUM-158 new testing route --- backend/routes/apiRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 3a8786ae..f0809d26 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -149,5 +149,11 @@ router.get('/api/events', limiter(100), checkApiKey, async (req, res) => { } }); +//simple post request, returns what it gets +router.post('/api/test', limiter(100), checkApiKey, async (req, res) => { + console.log('POST: /api/test successful. Request body:', req.body); + res.status(200).json(req.body); +}); + module.exports = router; From cf55844c484a163805dcaaeed8a013915c2794ca Mon Sep 17 00:00:00 2001 From: AZ0228 <53315675+AZ0228@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:53:26 -0700 Subject: [PATCH 28/28] SCRUM-158 refactoring api, real world considerations --- .DS_Store | Bin 10244 -> 10244 bytes backend/app.js | 29 +++- backend/middlewares/apiCors.js | 31 ++++ backend/middlewares/apiKeyMiddleware.js | 71 +++----- backend/middlewares/rateLimit.js | 75 +++++++-- backend/routes/apiRoutes.js | 127 +++++++++++++- backend/schemas/api.js | 82 +++++++-- test-api-client.html | 211 ++++++++++++++++++++++++ 8 files changed, 540 insertions(+), 86 deletions(-) create mode 100644 backend/middlewares/apiCors.js create mode 100644 test-api-client.html diff --git a/.DS_Store b/.DS_Store index e6a5342f13322402d135cd6582500028b0adb5d9..74b83a3f1dde73ddc90fa19478b22e45ef6c3fa5 100644 GIT binary patch delta 45 vcmZn(XbG6$&nUAoU^hRb%w`^eOxDR~MFlsn6OH7W*r2hQT_F!9P{#xSUbhb` delta 110 zcmZn(XbG6$&nUYwU^hRb>}DQ;OjcDEh7^WUhFpdM&z$_^q@4UD1_lNJAm#_*|4^{G QNHmvgGrK|_@#?df07!Kt#sB~S diff --git a/backend/app.js b/backend/app.js index f3de981a..dc302b55 100644 --- a/backend/app.js +++ b/backend/app.js @@ -28,17 +28,36 @@ const io = new Server(server, { } }); +//cors configuration if (process.env.NODE_ENV === 'production') { app.use(enforce.HTTPS({ trustProtoHeader: true })); const corsOptions = { origin: [ 'https://www.study-compass.com', 'https://studycompass.com', - `http://${process.env.EDUREKA_IP}:${process.env.EDUREKA_PORT}` + // `http://${process.env.EDUREKA_IP}:${process.env.EDUREKA_PORT}` ], optionsSuccessStatus: 200 // for legacy browser support }; - app.use(cors(corsOptions)); + + //apply CORS policy to all routes EXCEPT /api routes + app.use((req, res, next) => { + if (req.path.startsWith('/api')) { + return next(); + } + return cors(corsOptions)(req, res, next); + }); +} else { + //in development, use a more permissive CORS policy for non-API routes + app.use((req, res, next) => { + if (req.path.startsWith('/api')) { + return next(); + } + return cors({ + origin: true, //allow all origins in development + credentials: true + })(req, res, next); + }); } // Other middleware @@ -93,20 +112,18 @@ const apiRoutes = require('./routes/apiRoutes.js'); //Added Pk ERROR const orgRoutes = require('./routes/orgRoutes.js'); const workflowRoutes = require('./routes/workflowRoutes.js'); +// Mount routes app.use(authRoutes); app.use(dataRoutes); app.use(friendRoutes); app.use(userRoutes); app.use(analyticsRoutes); -app.use(eventRoutes); - app.use(classroomChangeRoutes); app.use(ratingRoutes); app.use(searchRoutes); - app.use(eventRoutes); app.use(oieRoutes); -app.use(apiRoutes);//ADDED PK +app.use('/api', apiRoutes); // API routes with their own CORS configuration app.use(orgRoutes); app.use(workflowRoutes); diff --git a/backend/middlewares/apiCors.js b/backend/middlewares/apiCors.js new file mode 100644 index 00000000..05345204 --- /dev/null +++ b/backend/middlewares/apiCors.js @@ -0,0 +1,31 @@ +const cors = require('cors'); + +/** + * CORS middleware specifically for API routes + * This allows cross-origin requests to API endpoints while maintaining security through API keys + */ +const apiCors = cors({ + origin: '*', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], + + allowedHeaders: [ + 'Content-Type', + 'Authorization', + 'X-Requested-With', + 'Accept', + 'Origin', + 'x-api-key' + ], + + //set credentials to false since we're using '*' for origin + credentials: false, + + //set how long the results of a preflight request can be cached + maxAge: 86400, // 24 hours + + //enable preflight requests + preflightContinue: false, + optionsSuccessStatus: 204 +}); + +module.exports = apiCors; \ No newline at end of file diff --git a/backend/middlewares/apiKeyMiddleware.js b/backend/middlewares/apiKeyMiddleware.js index 3a933135..077f7faa 100644 --- a/backend/middlewares/apiKeyMiddleware.js +++ b/backend/middlewares/apiKeyMiddleware.js @@ -1,67 +1,42 @@ -const API = require('../schemas/api.js'); -const limiter = require('../middlewares/rateLimit.js'); +const mongoose = require('mongoose'); const getModels = require('../services/getModelService.js'); -// Middleware for validating API keys and enforcing rate limits const apiKeyMiddleware = async (req, res, next) => { - const { Api } = getModels(req, 'Api'); - const apiKey = req.headers['x-api-key']; - try { - console.log("Received API Key"); - const apiKeyData = await Api.findOne({ api_key: apiKey }); + const apiKey = req.headers['x-api-key']; - if (!apiKeyData) { - console.log("API key does not exist for user"); - return res.status(401).json({ error: 'API key does not exist for user' }); + if (!apiKey) { + return res.status(401).json({ error: 'API key is required' }); } - // Determine clearance level and rate limits - const clearance = apiKeyData.Authorization || "default"; - let maxRequests; + const { Api } = getModels(req, 'Api'); + const apiKeyData = await Api.findOne({ api_key: apiKey }); - switch (clearance) { - case "Unauthorized": - maxRequests = 100; - break; - case "Authorized": - maxRequests = 500; - break; - default: - maxRequests = 100; - break; + if (!apiKeyData) { + return res.status(401).json({ error: 'Invalid API key' }); } - console.log(`Clearance: ${clearance} | Max Requests: ${maxRequests} | Current Usage: ${apiKeyData.usageCount}`); - - // BLOCK EXCESSIVE REQUESTS BEFORE PROCESSING - if (apiKeyData.usageCount >= maxRequests) { - console.log(`Exceded limit (${apiKeyData.usageCount}/${maxRequests}). Blocking request.`); - return res.status(429).json({ error: 'Rate limit exceeded. Please try again later.' }); + //check if API key is expired + if (apiKeyData.expiresAt && new Date() > apiKeyData.expiresAt) { + return res.status(401).json({ error: 'API key has expired' }); } - // Increment usage count and save - apiKeyData.usageCount = (apiKeyData.usageCount || 0) + 1; - await apiKeyData.save(); - - console.log(`API Key Used. New usage count: ${apiKeyData.usageCount}`); - - // Attach API key data to the request object for use in later routes - req.apiKeyData = apiKeyData; - - // Apply Rate Limiter Middleware - const apiKeyRateLimiter = limiter(maxRequests); - apiKeyRateLimiter(req, res, (error) => { - if (error) { - return res.status(429).json({ error: 'Rate limit exceeded. Please try again later.' }); + //check IP whitelist if configured + if (apiKeyData.allowedIPs && apiKeyData.allowedIPs.length > 0) { + const clientIP = req.ip; + if (!apiKeyData.allowedIPs.includes(clientIP)) { + return res.status(403).json({ error: 'IP not whitelisted' }); } - next(); - }); + } + //attach API key data to request for use in other middleware + req.apiKeyData = apiKeyData; + next(); } catch (error) { - console.error('Error verifying API key:', error); - return res.status(500).json({ error: 'Could not verify API key' }); + console.error('API Key Middleware Error:', error); + res.status(500).json({ error: 'Internal server error' }); } }; module.exports = { apiKeyMiddleware }; + diff --git a/backend/middlewares/rateLimit.js b/backend/middlewares/rateLimit.js index 4ae293f9..07c60e0f 100644 --- a/backend/middlewares/rateLimit.js +++ b/backend/middlewares/rateLimit.js @@ -1,18 +1,69 @@ //Rate Limiter -const rateLimit=require('express-rate-limit'); +const rateLimit = require('express-rate-limit'); + +const coolDown = 15 * 60 * 1000; // 15 minutes + +const limiter = (options = {}) => { + const { + maxRequests = 200, + windowMs = coolDown, // 15 minutes + message = 'Too many requests, please try again later.', + keyGenerator = (req) => { + //use API key if available, otherwise fall back to IP + return req.apiKeyData ? req.apiKeyData.api_key : req.ip; + }, + skip = (req) => false, + handler = (req, res) => { + res.status(429).json({ + error: message, + requestId: req.requestId, + retryAfter: Math.ceil(windowMs / 1000) + }); + }, + //debug option to help troubleshoot rate limiting + debug = process.env.NODE_ENV === 'development' + } = options; -const limiter=(maxRequests)=>{ return rateLimit({ -//Adjust Levels of access Different levels of rate keys and different routes will have different levels, adjust this later - // 15 minutes - windowMs: 15 * 60 * 1000, - // Limit each IP to 100 requests per windowMs - max: maxRequests, - message: 'Too many requests, please try again later.', - standardHeaders: true, - legacyHeaders:false -}); + windowMs, + max: maxRequests, + message, + standardHeaders: true, + legacyHeaders: false, + keyGenerator, + skip, + handler, + //custom headers + headers: true, + skipFailedRequests: false, + skipSuccessfulRequests: false, + statusCode: 429, + skip: (req) => { + //skip rate limiting for certain paths or conditions + //examples below + // if (req.path.startsWith('/health')) return true; + // if (req.path.startsWith('/metrics')) return true; + return false; + }, + debug + }); }; -module.exports = limiter; \ No newline at end of file +module.exports = { + limiter, + //strict rate limiter for sensitive endpoints + strictLimiter: (maxRequests = 50) => limiter({ + maxRequests, + windowMs: coolDown, // 15 minutes + message: 'Rate limit exceeded. Please try again in a minute.' + }), + //burst rate limiter for high-traffic endpoints + burstLimiter: (maxRequests = 1000) => limiter({ + maxRequests, + windowMs: coolDown, // 15 minutes + message: 'Too many requests in a short time. Please try again in a minute.' + }), + //default rate limiter + defaultLimiter: limiter +}; \ No newline at end of file diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index 6c7b9e1e..cde0e364 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -1,13 +1,17 @@ const express = require('express'); const router = express.Router(); -const limiter = require('../middlewares/rateLimit.js'); // Rate limiting middleware +const { limiter, strictLimiter, burstLimiter, defaultLimiter } = require('../middlewares/rateLimit.js'); // Rate limiting middleware const {apiKeyMiddleware} = require('../middlewares/apiKeyMiddleware.js'); // API key validation +const apiCors = require('../middlewares/apiCors.js'); // API-specific CORS middleware const crypto = require('crypto'); // For generating API keys const mongoose = require('mongoose'); const {verifyToken}= require('../middlewares/verifyToken'); const getModels = require('../services/getModelService.js'); require('dotenv').config(); +// Apply CORS to all API routes +router.use(apiCors); + // Generate a new API key router.post('/create_api', verifyToken, async (req, res) => { try { @@ -30,7 +34,11 @@ router.post('/create_api', verifyToken, async (req, res) => { const newApi = new Api({ api_key: apiKey, owner: userId, - Authorization: "Unauthorized" // see if this works + Authorization: "Unauthorized", // see if this works + description: req.body.description || 'API key created via dashboard', + scopes: req.body.scopes || ['read'], + expiresAt: req.body.expiresAt ? new Date(req.body.expiresAt) : null, + allowedIPs: req.body.allowedIPs || [] }); await newApi.save(); @@ -82,7 +90,7 @@ small change or medium change -router.get('/details', apiKeyMiddleware, async (req, res, next) => { +router.get('/details', burstLimiter(), apiKeyMiddleware, async (req, res, next) => { const apiKey = req.headers['x-api-key']; try { @@ -129,7 +137,8 @@ function checkApiKey(req, res, next) { } } -router.get('/api/events', limiter(100), checkApiKey, async (req, res) => { +// Use different rate limiters for different endpoints +router.get('/events', defaultLimiter(), apiKeyMiddleware, async (req, res) => { try { const { Event } = getModels(req, 'Event'); @@ -144,10 +153,118 @@ router.get('/api/events', limiter(100), checkApiKey, async (req, res) => { }); //simple post request, returns what it gets -router.post('/api/test', limiter(100), checkApiKey, async (req, res) => { +router.post('/test', burstLimiter(), apiKeyMiddleware, async (req, res) => { console.log('POST: /api/test successful. Request body:', req.body); res.status(200).json(req.body); }); +router.post('/sensitive', strictLimiter(), checkApiKey, async (req, res) => { + try { + // Process sensitive data + console.log('POST: /api/sensitive successful. Processing sensitive data'); + res.status(200).json({ + success: true, + message: 'Sensitive operation completed successfully', + data: req.body + }); + } catch (error) { + console.error('POST: /api/sensitive failed. Error:', error); + res.status(500).json({ error: 'Unable to process sensitive operation' }); + } +}); + +router.post('/rotate-api-key', verifyToken, apiKeyMiddleware, async (req, res) => { + try { + const userId = req.user.userId; + const { Api } = getModels(req, 'Api'); + + //find existing API key + const existingApi = await Api.findOne({ owner: userId }); + if (!existingApi) { + return res.status(404).json({ error: 'API key not found.' }); + } + + //generate new API key + const newApiKey = crypto.randomBytes(32).toString('hex'); + + //update the API key while preserving other properties + existingApi.api_key = newApiKey; + existingApi.usageCount = 0; //reset usage count + existingApi.dailyUsageCount = 0; //reset daily usage count + existingApi.lastUsageReset = new Date(); //reset last usage reset time + + await existingApi.save(); + + console.log('POST: /rotate-api-key successful. API key rotated'); + res.status(200).json({ + success: true, + message: 'API key rotated successfully', + apiKey: existingApi + }); + } catch (error) { + console.error('POST: /rotate-api-key failed. Error:', error); + res.status(500).json({ success: false, message: 'Error rotating API key' }); + } +}); + +//public API endpoint that doesn't require authentication +//testing purposes +// router.get('/public', defaultLimiter(), (req, res) => { +// res.status(200).json({ +// success: true, +// message: 'Public API endpoint accessed successfully', +// timestamp: new Date().toISOString() +// }); +// }); + +//route to reset rate limits for testing purposes, only available in development mode +router.post('/reset-rate-limit', async (req, res) => { + //only allow in development mode + if (process.env.NODE_ENV !== 'development') { + return res.status(403).json({ + success: false, + message: 'Rate limit reset is only available in development mode' + }); + } + + try { + const { Api } = getModels(req, 'Api'); + const apiKey = req.headers['x-api-key']; + + if (!apiKey) { + return res.status(400).json({ + success: false, + message: 'API key is required' + }); + } + + const apiKeyData = await Api.findOne({ api_key: apiKey }); + if (!apiKeyData) { + return res.status(404).json({ + success: false, + message: 'API key not found' + }); + } + + apiKeyData.usageCount = 0; + apiKeyData.dailyUsageCount = 0; + apiKeyData.lastUsageReset = new Date(); + + await apiKeyData.save(); + + console.log('POST: /reset-rate-limit successful. Reset rate limit for API key:', apiKey); + res.status(200).json({ + success: true, + message: 'Rate limit reset successfully', + apiKey: apiKeyData + }); + } catch (error) { + console.error('POST: /reset-rate-limit failed. Error:', error); + res.status(500).json({ + success: false, + message: 'Error resetting rate limit' + }); + } +}); module.exports = router; diff --git a/backend/schemas/api.js b/backend/schemas/api.js index 704581e7..f03da5a7 100644 --- a/backend/schemas/api.js +++ b/backend/schemas/api.js @@ -2,34 +2,86 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const apiSchema = new Schema({ - api_key:{ + api_key: { type: String, required: true, unique: true, trim: true, }, owner: { - type: String, - required: true, - ref: 'User' - }, + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true + }, createdAt: { type: Date, default: Date.now, }, - usageCount: { + expiresAt: { + type: Date, + default: null, + }, + lastUsed: { + type: Date, + default: null, + }, + dailyUsageCount: { type: Number, - default: 0, + default: 0, + }, + lastUsageReset: { + type: Date, + default: Date.now, }, Authorization: { - type: String, - enum: ["Unauthorized", "Authorized"], - - } - + type: String, + enum: ["Unauthorized", "Authorized"], + default: "Unauthorized" + }, + allowedIPs: [{ + type: String, + trim: true + }], + description: { + type: String, + trim: true, + maxLength: 500 + }, + isActive: { + type: Boolean, + default: true + }, + scopes: [{ + type: String, + enum: ['read', 'write', 'admin'] + }], + metadata: { + type: Map, + of: Schema.Types.Mixed, + default: {} + } }); -// const API = mongoose.model("api", apiSchema); -// module.exports = API;//NO LONGER SUPPORTED +// Index for faster queries +apiSchema.index({ api_key: 1 }); +apiSchema.index({ owner: 1 }); +apiSchema.index({ expiresAt: 1 }); +apiSchema.index({ isActive: 1 }); + +// Pre-save middleware to reset daily usage count if needed +apiSchema.pre('save', function(next) { + const now = new Date(); + const lastReset = this.lastUsageReset || new Date(0); + + // Reset daily usage if it's a new day + if (lastReset.getDate() !== now.getDate() || + lastReset.getMonth() !== now.getMonth() || + lastReset.getFullYear() !== now.getFullYear()) { + this.dailyUsageCount = 0; + this.lastUsageReset = now; + } + + next(); +}); -module.exports = apiSchema; \ No newline at end of file +module.exports = apiSchema \ No newline at end of file diff --git a/test-api-client.html b/test-api-client.html new file mode 100644 index 00000000..b9a5c82a --- /dev/null +++ b/test-api-client.html @@ -0,0 +1,211 @@ + + + + + + API Test Client + + + +
+

API Test Client

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + + + \ No newline at end of file