diff --git a/api/jest.utils.ts b/api/jest.utils.ts index e562fd95282..0591fc98ccd 100644 --- a/api/jest.utils.ts +++ b/api/jest.utils.ts @@ -32,6 +32,8 @@ export const getCsrfToken = (setCookies: string[]): string | undefined => { return csrfToken; }; +export const ORIGIN = 'https://www.freecodecamp.org'; + export function superRequest( resource: string, config: { @@ -43,10 +45,7 @@ export function superRequest( const { method, setCookies } = config; const { sendCSRFToken = true } = options ?? {}; - const req = requests[method](resource).set( - 'Origin', - 'https://www.freecodecamp.org' - ); + const req = requests[method](resource).set('Origin', ORIGIN); if (setCookies) { void req.set('Cookie', setCookies); diff --git a/api/src/app.ts b/api/src/app.ts index 3675a9309b9..86d89b3b917 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -21,6 +21,7 @@ import cors from './plugins/cors'; import jwtAuthz from './plugins/fastify-jwt-authz'; import security from './plugins/security'; import sessionAuth from './plugins/session-auth'; +import redirectWithMessage from './plugins/redirect-with-message'; import { settingRoutes } from './routes/settings'; import { deprecatedEndpoints } from './routes/deprecated-endpoints'; import { auth0Routes, devLoginCallback } from './routes/auth'; @@ -42,6 +43,7 @@ import { import { userRoutes } from './routes/user'; import { donateRoutes } from './routes/donate'; import { statusRoute } from './routes/status'; +import { unsubscribeDeprecated } from './routes/deprecated-unsubscribe'; export type FastifyInstanceWithTypeProvider = FastifyInstance< RawServerDefault, @@ -172,6 +174,8 @@ export const build = async ( void fastify.register(userRoutes); void fastify.register(deprecatedEndpoints); void fastify.register(statusRoute); + void fastify.register(unsubscribeDeprecated); + void fastify.register(redirectWithMessage); return fastify; }; diff --git a/api/src/routes/deprecated-endpoints.test.ts b/api/src/routes/deprecated-endpoints.test.ts index a62195cc386..91970a387a2 100644 --- a/api/src/routes/deprecated-endpoints.test.ts +++ b/api/src/routes/deprecated-endpoints.test.ts @@ -1,23 +1,14 @@ import request from 'supertest'; -import { build } from '../app'; +import { setupServer } from '../../jest.utils'; import { endpoints } from './deprecated-endpoints'; describe('Deprecated endpoints', () => { - let fastify: undefined | Awaited>; - - beforeAll(async () => { - fastify = await build(); - await fastify.ready(); - }, 20000); - - afterAll(async () => { - await fastify?.close(); - }); + setupServer(); endpoints.forEach(([endpoint, method]) => { test(`${method} ${endpoint} returns 410 status code with "info" message`, async () => { - const response = await request(fastify?.server)[ + const response = await request(fastifyTestInstance.server)[ method.toLowerCase() as 'get' | 'post' ](endpoint); diff --git a/api/src/routes/deprecated-unsubscribe.test.ts b/api/src/routes/deprecated-unsubscribe.test.ts new file mode 100644 index 00000000000..85ab238e562 --- /dev/null +++ b/api/src/routes/deprecated-unsubscribe.test.ts @@ -0,0 +1,25 @@ +import { setupServer, superRequest } from '../../jest.utils'; + +import { unsubscribeEndpoints } from './deprecated-unsubscribe'; + +const urlEncodedMessage = + '?messages=info%5B0%5D%3DWe%2520are%2520no%2520longer%2520able%2520to%2520process%2520this%2520unsubscription%2520request.%2520Please%2520go%2520to%2520your%2520settings%2520to%2520update%2520your%2520email%2520preferences'; + +describe('Deprecated unsubscribeEndpoints', () => { + setupServer(); + + unsubscribeEndpoints.forEach(([endpoint, method]) => { + test(`${method} ${endpoint} redirects to referer with "info" message`, async () => { + const response = await superRequest(endpoint, { method }).set( + 'Referer', + 'https://www.freecodecamp.org/settings' + ); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(response.headers.location).toStrictEqual( + 'https://www.freecodecamp.org/settings' + urlEncodedMessage + ); + expect(response.status).toBe(302); + }); + }); +}); diff --git a/api/src/routes/deprecated-unsubscribe.ts b/api/src/routes/deprecated-unsubscribe.ts new file mode 100644 index 00000000000..a8e798e8409 --- /dev/null +++ b/api/src/routes/deprecated-unsubscribe.ts @@ -0,0 +1,34 @@ +import { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'; +import { HOME_LOCATION } from '../utils/env'; + +type Endpoint = [string, 'GET' | 'POST']; + +export const unsubscribeEndpoints: Endpoint[] = [ + ['/u/:email', 'GET'], + ['/unsubscribe/:email', 'GET'] +]; + +export const unsubscribeDeprecated: FastifyPluginCallbackTypebox = ( + fastify, + _options, + done +) => { + unsubscribeEndpoints.forEach(([endpoint, method]) => { + fastify.route({ + method, + url: endpoint, + handler: async (req, reply) => { + // TODO: port over getRedirectParams from api-server anduser that + const origin = req.headers.referer ?? HOME_LOCATION; + void reply.redirectWithMessage(origin, { + type: 'info', + content: + 'We are no longer able to process this unsubscription request. ' + + 'Please go to your settings to update your email preferences' + }); + } + }); + }); + + done(); +};