Merge pull request from GHSA-cc3r-grh4-27gj
* feat: restrict update-user-flag endpoint * feat(api): add dedicated-endpoints for user Co-authored-by: Naomi Carrigan <nhcarrigan@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
committed by
GitHub
parent
bb0f9c8036
commit
9ea7018740
@ -1,5 +1,6 @@
|
|||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { check } from 'express-validator';
|
import { check } from 'express-validator';
|
||||||
|
import _ from 'lodash';
|
||||||
import isURL from 'validator/lib/isURL';
|
import isURL from 'validator/lib/isURL';
|
||||||
|
|
||||||
import { isValidUsername } from '../../../../utils/validate';
|
import { isValidUsername } from '../../../../utils/validate';
|
||||||
@ -24,8 +25,8 @@ export default function settingsController(app) {
|
|||||||
createValidatorErrorHandler(alertTypes.danger),
|
createValidatorErrorHandler(alertTypes.danger),
|
||||||
updateMyCurrentChallenge
|
updateMyCurrentChallenge
|
||||||
);
|
);
|
||||||
api.post('/update-my-portfolio', ifNoUser401, updateMyPortfolio);
|
api.put('/update-my-portfolio', ifNoUser401, updateMyPortfolio);
|
||||||
api.post('/update-my-theme', deprecatedEndpoint);
|
api.put('/update-my-theme', ifNoUser401, updateMyTheme);
|
||||||
api.put('/update-my-about', ifNoUser401, updateMyAbout);
|
api.put('/update-my-about', ifNoUser401, updateMyAbout);
|
||||||
api.put(
|
api.put(
|
||||||
'/update-my-email',
|
'/update-my-email',
|
||||||
@ -37,6 +38,10 @@ export default function settingsController(app) {
|
|||||||
api.put('/update-my-profileui', ifNoUser401, updateMyProfileUI);
|
api.put('/update-my-profileui', ifNoUser401, updateMyProfileUI);
|
||||||
api.put('/update-my-username', ifNoUser401, updateMyUsername);
|
api.put('/update-my-username', ifNoUser401, updateMyUsername);
|
||||||
api.put('/update-user-flag', ifNoUser401, updateUserFlag);
|
api.put('/update-user-flag', ifNoUser401, updateUserFlag);
|
||||||
|
api.put('/update-my-socials', ifNoUser401, updateMySocials);
|
||||||
|
api.put('/update-my-sound', ifNoUser401, updateMySound);
|
||||||
|
api.put('/update-my-honesty', ifNoUser401, updateMyHonesty);
|
||||||
|
api.put('/update-my-quincy-email', ifNoUser401, updateMyQuincyEmail);
|
||||||
|
|
||||||
app.use(api);
|
app.use(api);
|
||||||
}
|
}
|
||||||
@ -97,18 +102,16 @@ function updateMyCurrentChallenge(req, res, next) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMyPortfolio(req, res, next) {
|
function updateMyPortfolio(...args) {
|
||||||
const {
|
const portfolioKeys = ['id', 'title', 'description', 'url', 'image'];
|
||||||
user,
|
const buildUpdate = body => {
|
||||||
body: { portfolio }
|
const portfolio = body?.portfolio?.map(elem => _.pick(elem, portfolioKeys));
|
||||||
} = req;
|
return { portfolio };
|
||||||
// if we only have one key, it should be the id
|
};
|
||||||
// user cannot send only one key to this route
|
const validate = ({ portfolio }) => portfolio?.every(isPortfolioElement);
|
||||||
// other than to remove a portfolio item
|
const isPortfolioElement = elem =>
|
||||||
const requestDelete = Object.keys(portfolio).length === 1;
|
Object.values(elem).every(val => typeof val == 'string');
|
||||||
return user
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
.updateMyPortfolio(portfolio, requestDelete)
|
|
||||||
.subscribe(message => res.json({ message }), next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMyProfileUI(req, res, next) {
|
function updateMyProfileUI(req, res, next) {
|
||||||
@ -204,7 +207,80 @@ const updatePrivacyTerms = (req, res, next) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateMySocials(...args) {
|
||||||
|
const buildUpdate = body =>
|
||||||
|
_.pick(body, ['githubProfile', 'linkedin', 'twitter', 'website']);
|
||||||
|
const validate = update =>
|
||||||
|
Object.values(update).every(x => typeof x === 'string');
|
||||||
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMyTheme(...args) {
|
||||||
|
const buildUpdate = body => _.pick(body, 'theme');
|
||||||
|
const validate = ({ theme }) => theme == 'default' || theme == 'night';
|
||||||
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMySound(...args) {
|
||||||
|
const buildUpdate = body => _.pick(body, 'sound');
|
||||||
|
const validate = ({ sound }) => typeof sound === 'boolean';
|
||||||
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMyHonesty(...args) {
|
||||||
|
const buildUpdate = body => _.pick(body, 'isHonest');
|
||||||
|
const validate = ({ isHonest }) => isHonest === true;
|
||||||
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMyQuincyEmail(...args) {
|
||||||
|
const buildUpdate = body => _.pick(body, 'sendQuincyEmail');
|
||||||
|
const validate = ({ sendQuincyEmail }) =>
|
||||||
|
typeof sendQuincyEmail === 'boolean';
|
||||||
|
createUpdateUserProperties(buildUpdate, validate)(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUpdateUserProperties(buildUpdate, validate) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
const { user, body } = req;
|
||||||
|
const update = buildUpdate(body);
|
||||||
|
if (validate(update)) {
|
||||||
|
user.updateAttributes(update, createStandardHandler(req, res, next));
|
||||||
|
} else {
|
||||||
|
handleInvalidUpdate(res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInvalidUpdate(res) {
|
||||||
|
res.status(403).json({
|
||||||
|
type: 'danger',
|
||||||
|
message: 'flash.wrong-updating'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateUserFlag(req, res, next) {
|
function updateUserFlag(req, res, next) {
|
||||||
const { user, body: update } = req;
|
const { user, body: update } = req;
|
||||||
return user.updateAttributes(update, createStandardHandler(req, res, next));
|
const allowedKeys = [
|
||||||
|
'theme',
|
||||||
|
'sound',
|
||||||
|
'isHonest',
|
||||||
|
'portfolio',
|
||||||
|
'sendQuincyEmail',
|
||||||
|
'isGithub',
|
||||||
|
'isLinkedIn',
|
||||||
|
'isTwitter',
|
||||||
|
'isWebsite',
|
||||||
|
'githubProfile',
|
||||||
|
'linkedin',
|
||||||
|
'twitter',
|
||||||
|
'website'
|
||||||
|
];
|
||||||
|
if (Object.keys(update).every(key => allowedKeys.includes(key))) {
|
||||||
|
return user.updateAttributes(update, createStandardHandler(req, res, next));
|
||||||
|
}
|
||||||
|
return res.status(403).json({
|
||||||
|
type: 'danger',
|
||||||
|
message: 'flash.invalid-update-flag'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -536,7 +536,8 @@
|
|||||||
"code-save-error": "An error occurred trying to save your code.",
|
"code-save-error": "An error occurred trying to save your code.",
|
||||||
"code-save-less": "Slow Down! Your code was not saved. Try again in a few seconds.",
|
"code-save-less": "Slow Down! Your code was not saved. Try again in a few seconds.",
|
||||||
"challenge-save-too-big": "Sorry, you cannot save your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
|
"challenge-save-too-big": "Sorry, you cannot save your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
|
||||||
"challenge-submit-too-big": "Sorry, you cannot submit your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org"
|
"challenge-submit-too-big": "Sorry, you cannot submit your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
|
||||||
|
"invalid-update-flag": "You are attempting to access forbidden resources. Please request assistance on https://forum.freecodecamp.org if this is a valid request."
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"max-characters": "There is a maximum limit of 288 characters, you have {{charsLeft}} left",
|
"max-characters": "There is a maximum limit of 288 characters, you have {{charsLeft}} left",
|
||||||
|
@ -114,8 +114,12 @@ let blocklist = [
|
|||||||
'twitch',
|
'twitch',
|
||||||
'unsubscribe',
|
'unsubscribe',
|
||||||
'unsubscribed',
|
'unsubscribed',
|
||||||
|
'update-my-honesty',
|
||||||
'update-my-portfolio',
|
'update-my-portfolio',
|
||||||
'update-my-profile-ui',
|
'update-my-profile-ui',
|
||||||
|
'update-my-quincy-email',
|
||||||
|
'update-my-socials',
|
||||||
|
'update-my-sound',
|
||||||
'update-my-theme',
|
'update-my-theme',
|
||||||
'update-my-username',
|
'update-my-username',
|
||||||
'user',
|
'user',
|
||||||
|
Reference in New Issue
Block a user