From f1cd0cfae331ebe60ae86a8bcbfa3e3acdf5d861 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Thu, 20 Jun 2024 07:54:52 +0200 Subject: [PATCH] fix(api): handle 4XX errors get-public-profile (#55205) Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> --- api-server/src/common/models/user.js | 115 --------------------- api-server/src/server/boot/randomAPIs.js | 121 +++++++++++++++++++++++ api-server/src/server/boot/user.js | 2 +- 3 files changed, 122 insertions(+), 116 deletions(-) diff --git a/api-server/src/common/models/user.js b/api-server/src/common/models/user.js index 4cb061fc27c..afac7fad3e4 100644 --- a/api-server/src/common/models/user.js +++ b/api-server/src/common/models/user.js @@ -22,11 +22,6 @@ import { setAccessTokenToResponse, removeCookies } from '../../server/utils/getSetAccessToken'; -import { - normaliseUserFields, - getProgress, - publicUserProps -} from '../../server/utils/publicUserProps'; import { saveUser, observeMethod } from '../../server/utils/rx.js'; import { getEmailSender } from '../../server/utils/url-utils'; import { @@ -744,116 +739,6 @@ export default function initializeUser(User) { ); }; - function prepUserForPublish(user, profileUI) { - const { - about, - calendar, - completedChallenges, - isDonating, - joinDate, - location, - name, - points, - portfolio, - username, - yearsTopContributor - } = user; - const { - isLocked = true, - showAbout = false, - showCerts = false, - showDonation = false, - showHeatMap = false, - showLocation = false, - showName = false, - showPoints = false, - showPortfolio = false, - showTimeLine = false - } = profileUI; - - if (isLocked) { - return { - isLocked, - profileUI, - username - }; - } - return { - ...user, - about: showAbout ? about : '', - calendar: showHeatMap ? calendar : {}, - completedChallenges: (function () { - if (showTimeLine) { - return showCerts - ? completedChallenges - : completedChallenges.filter( - ({ challengeType }) => challengeType !== 7 - ); - } else { - return []; - } - })(), - isDonating: showDonation ? isDonating : null, - joinDate: showAbout ? joinDate : '', - location: showLocation ? location : '', - name: showName ? name : '', - points: showPoints ? points : null, - portfolio: showPortfolio ? portfolio : [], - yearsTopContributor: yearsTopContributor - }; - } - - User.getPublicProfile = function getPublicProfile(username, cb) { - return User.findOne$({ where: { username } }) - .flatMap(user => { - if (!user) { - return Observable.of({}); - } - const { completedChallenges, progressTimestamps, profileUI } = user; - const allUser = { - ..._.pick(user, publicUserProps), - points: progressTimestamps.length, - completedChallenges, - ...getProgress(progressTimestamps), - ...normaliseUserFields(user), - joinDate: user.id.getTimestamp() - }; - - const publicUser = prepUserForPublish(allUser, profileUI); - - return Observable.of({ - entities: { - user: { - [user.username]: { - ...publicUser - } - } - }, - result: user.username - }); - }) - .subscribe(user => cb(null, user), cb); - }; - - User.remoteMethod('getPublicProfile', { - accepts: { - arg: 'username', - type: 'string', - required: true - }, - returns: [ - { - arg: 'user', - type: 'object', - root: true - } - ], - http: { - path: '/get-public-profile', - verb: 'GET' - } - }); - User.giveBrowniePoints = function giveBrowniePoints( receiver, giver, diff --git a/api-server/src/server/boot/randomAPIs.js b/api-server/src/server/boot/randomAPIs.js index c51452c4640..11e3c91241e 100644 --- a/api-server/src/server/boot/randomAPIs.js +++ b/api-server/src/server/boot/randomAPIs.js @@ -1,5 +1,11 @@ +import { pick } from 'lodash'; import { getRedirectParams } from '../utils/redirection'; import { deprecatedEndpoint } from '../utils/disabled-endpoints'; +import { + getProgress, + normaliseUserFields, + publicUserProps +} from '../utils/publicUserProps'; module.exports = function (app) { const router = app.loopback.Router(); @@ -10,6 +16,7 @@ module.exports = function (app) { router.get('/unsubscribe/:email', unsubscribeDeprecated); router.get('/ue/:unsubscribeId', unsubscribeById); router.get('/resubscribe/:unsubscribeId', resubscribe); + router.get('/api/users/get-public-profile', blockUserAgent, getPublicProfile); app.use(router); @@ -105,4 +112,118 @@ module.exports = function (app) { .catch(next); }); } + + const blockedUserAgentParts = ['python', 'google-apps-script', 'curl']; + + function blockUserAgent(req, res, next) { + const userAgent = req.headers['user-agent']; + + if ( + !userAgent || + blockedUserAgentParts.some(ua => userAgent.toLowerCase().includes(ua)) + ) { + return res + .status(400) + .send( + 'This endpoint is no longer available outside of the freeCodeCamp ecosystem' + ); + } + + return next(); + } + + async function getPublicProfile(req, res) { + const { username } = req.query; + if (!username) { + return res.status(400).json({ error: 'No username provided' }); + } + + const user = await User.findOne({ where: { username } }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const { completedChallenges, progressTimestamps, profileUI } = user; + const allUser = { + ...pick(user, publicUserProps), + points: progressTimestamps.length, + completedChallenges, + ...getProgress(progressTimestamps), + ...normaliseUserFields(user), + joinDate: user.id.getTimestamp() + }; + + const publicUser = prepUserForPublish(allUser, profileUI); + + return res.json({ + entities: { + user: { + [user.username]: { + ...publicUser + } + } + }, + result: user.username + }); + } + + function prepUserForPublish(user, profileUI) { + const { + about, + calendar, + completedChallenges, + isDonating, + joinDate, + location, + name, + points, + portfolio, + username, + yearsTopContributor + } = user; + const { + isLocked = true, + showAbout = false, + showCerts = false, + showDonation = false, + showHeatMap = false, + showLocation = false, + showName = false, + showPoints = false, + showPortfolio = false, + showTimeLine = false + } = profileUI; + + if (isLocked) { + return { + isLocked, + profileUI, + username + }; + } + return { + ...user, + about: showAbout ? about : '', + calendar: showHeatMap ? calendar : {}, + completedChallenges: (function () { + if (showTimeLine) { + return showCerts + ? completedChallenges + : completedChallenges.filter( + ({ challengeType }) => challengeType !== 7 + ); + } else { + return []; + } + })(), + isDonating: showDonation ? isDonating : null, + joinDate: showAbout ? joinDate : '', + location: showLocation ? location : '', + name: showName ? name : '', + points: showPoints ? points : null, + portfolio: showPortfolio ? portfolio : [], + yearsTopContributor: yearsTopContributor + }; + } }; diff --git a/api-server/src/server/boot/user.js b/api-server/src/server/boot/user.js index aaa4a362161..70288aae553 100644 --- a/api-server/src/server/boot/user.js +++ b/api-server/src/server/boot/user.js @@ -43,7 +43,6 @@ function bootUser(app) { const deleteMsUsername = createDeleteMsUsername(app); const postSubmitSurvey = createPostSubmitSurvey(app); const deleteUserSurveys = createDeleteUserSurveys(app); - api.get('/account', sendNonUserToHome, deprecatedEndpoint); api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial); api.get('/user/get-session-user', getSessionUser); @@ -532,4 +531,5 @@ function createPostReportUserProfile(app) { ); }; } + export default bootUser;