mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-31 18:01:36 -04:00
fix(api): reject social urls with invalid domains (#55595)
This commit is contained in:
committed by
GitHub
parent
cda00d4770
commit
ee37d563f7
@@ -11,7 +11,11 @@ import {
|
||||
import { formatMessage } from '../plugins/redirect-with-message';
|
||||
import { createUserInput } from '../utils/create-user';
|
||||
import { API_LOCATION, HOME_LOCATION } from '../utils/env';
|
||||
import { isPictureWithProtocol, getWaitMessage } from './settings';
|
||||
import {
|
||||
isPictureWithProtocol,
|
||||
getWaitMessage,
|
||||
validateSocialUrl
|
||||
} from './settings';
|
||||
|
||||
const baseProfileUI = {
|
||||
isLocked: false,
|
||||
@@ -827,12 +831,24 @@ Happy coding!
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
test('PUT returns 400 status code with invalid socials setting', async () => {
|
||||
test('PUT rejects non-url values', async () => {
|
||||
const response = await superPut('/update-my-socials').send({
|
||||
website: 'invalid',
|
||||
twitter: '',
|
||||
linkedin: '',
|
||||
githubProfile: 'invalid'
|
||||
githubProfile: ''
|
||||
});
|
||||
|
||||
expect(response.body).toEqual(updateErrorResponse);
|
||||
expect(response.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT only accepts urls to certain domains', async () => {
|
||||
const response = await superPut('/update-my-socials').send({
|
||||
website: '',
|
||||
twitter: '',
|
||||
linkedin: '',
|
||||
githubProfile: 'https://x.com/should-be-github'
|
||||
});
|
||||
|
||||
expect(response.body).toEqual(updateErrorResponse);
|
||||
@@ -1122,3 +1138,29 @@ describe('getWaitMessage', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateSocialUrl', () => {
|
||||
it.each(['githubProfile', 'linkedin', 'twitter'] as const)(
|
||||
'accepts empty strings for %s',
|
||||
social => {
|
||||
expect(validateSocialUrl('', social)).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['githubProfile', 'https://something.com/user'],
|
||||
['linkedin', 'https://www.x.com/in/username'],
|
||||
['twitter', 'https://www.toomanyexes.com/username']
|
||||
] as const)('rejects invalid urls for %s', (social, url) => {
|
||||
expect(validateSocialUrl(url, social)).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['githubProfile', 'https://something.github.com/user'],
|
||||
['linkedin', 'https://www.linkedin.com/in/username'],
|
||||
['twitter', 'https://twitter.com/username'],
|
||||
['twitter', 'https://x.com/username']
|
||||
] as const)('accepts valid urls for %s', (social, url) => {
|
||||
expect(validateSocialUrl(url, social)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,6 +69,35 @@ export const isPictureWithProtocol = (picture?: string): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
const ALLOWED_DOMAINS_MAP = {
|
||||
githubProfile: ['github.com'],
|
||||
linkedin: ['linkedin.com'],
|
||||
twitter: ['twitter.com', 'x.com']
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a social URL.
|
||||
*
|
||||
* @param socialUrl The URL to check.
|
||||
* @param key The key of the allowed socials and domains.
|
||||
* @returns Whether the URL is valid.
|
||||
*/
|
||||
export const validateSocialUrl = (
|
||||
socialUrl: string,
|
||||
key: keyof typeof ALLOWED_DOMAINS_MAP
|
||||
): boolean => {
|
||||
if (!socialUrl) return true;
|
||||
|
||||
try {
|
||||
const url = new URL(socialUrl);
|
||||
const domains = ALLOWED_DOMAINS_MAP[key];
|
||||
const domainAndTld = url.hostname.split('.').slice(-2).join('.');
|
||||
return domains.includes(domainAndTld);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin for all endpoints related to user settings.
|
||||
*
|
||||
@@ -328,6 +357,18 @@ ${isLinkSentWithinLimitTTL}`
|
||||
errorHandler: updateErrorHandler
|
||||
},
|
||||
async (req, reply) => {
|
||||
const valid = (['twitter', 'githubProfile', 'linkedin'] as const).every(
|
||||
key => validateSocialUrl(req.body[key], key)
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
void reply.code(400);
|
||||
return reply.send({
|
||||
message: 'flash.wrong-updating',
|
||||
type: 'danger'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await fastify.prisma.user.update({
|
||||
where: { id: req.user?.id },
|
||||
|
||||
Reference in New Issue
Block a user