mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-09 01:01:04 -04:00
fix: email authenication link messages (#54152)
This commit is contained in:
committed by
GitHub
parent
38e4f80dae
commit
0f0a2682f0
@@ -6,7 +6,7 @@ import {
|
||||
} from '../../jest.utils';
|
||||
import { createUserInput } from '../utils/create-user';
|
||||
|
||||
import { isPictureWithProtocol } from './settings';
|
||||
import { isPictureWithProtocol, getWaitMessage } from './settings';
|
||||
|
||||
const baseProfileUI = {
|
||||
isLocked: false,
|
||||
@@ -766,3 +766,49 @@ Please wait 5 minutes to resend an authentication link.`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWaitMessage', () => {
|
||||
const sec = 1000;
|
||||
const min = 60 * 1000;
|
||||
it.each([
|
||||
{
|
||||
sentAt: new Date(0),
|
||||
now: new Date(0),
|
||||
expected: 'Please wait 5 minutes to resend an authentication link.'
|
||||
},
|
||||
{
|
||||
sentAt: new Date(0),
|
||||
now: new Date(59 * sec),
|
||||
expected: 'Please wait 5 minutes to resend an authentication link.'
|
||||
},
|
||||
{
|
||||
sentAt: new Date(0),
|
||||
now: new Date(4 * min),
|
||||
expected: 'Please wait 1 minute to resend an authentication link.'
|
||||
},
|
||||
{
|
||||
sentAt: new Date(0),
|
||||
now: new Date(4 * min + 59 * sec),
|
||||
expected: 'Please wait 1 minute to resend an authentication link.'
|
||||
},
|
||||
{
|
||||
sentAt: new Date(0),
|
||||
now: new Date(5 * min),
|
||||
expected: null
|
||||
}
|
||||
])(
|
||||
`returns "$expected" when sentAt is $sentAt and now is $now`,
|
||||
({ sentAt, now, expected }) => {
|
||||
expect(getWaitMessage({ sentAt, now })).toEqual(expected);
|
||||
}
|
||||
);
|
||||
|
||||
it('returns null when sentAt is null', () => {
|
||||
expect(getWaitMessage({ sentAt: null, now: new Date(0) })).toBeNull();
|
||||
});
|
||||
it('uses the current time when now is not provided', () => {
|
||||
expect(getWaitMessage({ sentAt: new Date() })).toEqual(
|
||||
'Please wait 5 minutes to resend an authentication link.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,37 +13,38 @@ import type {
|
||||
RouteGenericInterface
|
||||
} from 'fastify';
|
||||
import { ResolveFastifyReplyType } from 'fastify/types/type-provider';
|
||||
import { getMinutes, isBefore, sub } from 'date-fns';
|
||||
import { differenceInMinutes } from 'date-fns';
|
||||
import { isProfane } from 'no-profanity';
|
||||
|
||||
import { blocklistedUsernames } from '../../../shared/config/constants';
|
||||
import { isValidUsername } from '../../../shared/utils/validate';
|
||||
import { schemas } from '../schemas';
|
||||
|
||||
// TODO: move getWaitMessage and getWaitPeriod to own module and add tests
|
||||
function getWaitMessage(lastEmailSentAt: Date | null) {
|
||||
const minutesLeft = getWaitPeriod(lastEmailSentAt);
|
||||
if (minutesLeft <= 0) {
|
||||
return null;
|
||||
}
|
||||
type WaitMesssageArgs = {
|
||||
sentAt: Date | null;
|
||||
now?: Date;
|
||||
};
|
||||
|
||||
const timeToWait = minutesLeft
|
||||
? `${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}`
|
||||
: 'a few seconds';
|
||||
/**
|
||||
* Get a message to display to the user about how long they need to wait before
|
||||
* they can request an authentication link.
|
||||
*
|
||||
* @param param The parameters.
|
||||
* @param param.sentAt The date the last email was sent at.
|
||||
* @param param.now The current date.
|
||||
* @returns The message to display to the user.
|
||||
*/
|
||||
export function getWaitMessage({ sentAt, now = new Date() }: WaitMesssageArgs) {
|
||||
const minutesLeft = getWaitPeriod({ sentAt, now });
|
||||
if (minutesLeft <= 0) return null;
|
||||
|
||||
const timeToWait = `${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}`;
|
||||
return `Please wait ${timeToWait} to resend an authentication link.`;
|
||||
}
|
||||
|
||||
function getWaitPeriod(lastEmailSentAt: Date | null) {
|
||||
if (!lastEmailSentAt) return 0;
|
||||
|
||||
const now = new Date();
|
||||
const fiveMinutesAgo = sub(now, { minutes: 5 });
|
||||
const isWaitPeriodOver = isBefore(lastEmailSentAt, fiveMinutesAgo);
|
||||
|
||||
return isWaitPeriodOver
|
||||
? 0
|
||||
: 5 - (getMinutes(now) - getMinutes(lastEmailSentAt));
|
||||
function getWaitPeriod({ sentAt, now }: Required<WaitMesssageArgs>) {
|
||||
if (sentAt == null) return 0;
|
||||
return 5 - differenceInMinutes(now, sentAt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +192,9 @@ You can update a new email address instead.`
|
||||
|
||||
const isResendUpdateToSameEmail =
|
||||
newEmail === user.newEmail?.toLowerCase();
|
||||
const isLinkSentWithinLimitTTL = getWaitMessage(user.emailVerifyTTL);
|
||||
const isLinkSentWithinLimitTTL = getWaitMessage({
|
||||
sentAt: user.emailVerifyTTL
|
||||
});
|
||||
|
||||
if (isResendUpdateToSameEmail && isLinkSentWithinLimitTTL) {
|
||||
void reply.code(429);
|
||||
@@ -228,7 +231,9 @@ ${isLinkSentWithinLimitTTL}`
|
||||
// we need emailVeriftyTTL given that the main thing we want is to
|
||||
// restrict the rate of attempts and the emailAuthLinkTTL already does
|
||||
// that.
|
||||
const tooManyRequestsMessage = getWaitMessage(user.emailAuthLinkTTL);
|
||||
const tooManyRequestsMessage = getWaitMessage({
|
||||
sentAt: user.emailAuthLinkTTL
|
||||
});
|
||||
|
||||
if (tooManyRequestsMessage) {
|
||||
void reply.code(429);
|
||||
|
||||
Reference in New Issue
Block a user