From be00d5f3bb078648ba60dcd6baa8bd728b6e6adf Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 8 Nov 2023 23:07:57 +0400 Subject: [PATCH] feat: mobile login enpoint (#51829) Co-authored-by: Mrugesh Mohapatra Co-authored-by: Oliver Eyton-Williams --- api/package.json | 4 ++- api/src/app.ts | 11 ++++---- api/src/middleware/index.ts | 23 ----------------- api/src/routes/auth.ts | 51 +++++++++++++++++++++++++++++++++++-- pnpm-lock.yaml | 38 +++++++++++++-------------- 5 files changed, 75 insertions(+), 52 deletions(-) delete mode 100644 api/src/middleware/index.ts diff --git a/api/package.json b/api/package.json index ea9072a9f4a..5178a588a49 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ "@aws-sdk/client-ses": "3.441.0", "@fastify/cookie": "9.1.0", "@fastify/csrf-protection": "6.4.1", - "@fastify/middie": "8.3", + "@fastify/express": "^2.3.0", "@fastify/session": "10.5.0", "@fastify/swagger": "8.12.0", "@fastify/swagger-ui": "1.10.1", @@ -19,6 +19,7 @@ "connect-mongo": "4.6.0", "date-fns": "2.30.0", "dotenv": "16.3.1", + "express-rate-limit": "^6.7.0", "fast-uri": "2.3.0", "fastify": "4.24.3", "fastify-auth0-verify": "1.2.1", @@ -33,6 +34,7 @@ "nodemon": "2.0.22", "pino-pretty": "10.2.3", "query-string": "7.1.3", + "rate-limit-mongo": "^2.3.2", "stripe": "8.222.0" }, "description": "The freeCodeCamp.org open-source codebase and curriculum", diff --git a/api/src/app.ts b/api/src/app.ts index cb27c5a8582..da1fde7780d 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,6 +1,6 @@ import fastifyCookie from '@fastify/cookie'; import fastifyCsrfProtection from '@fastify/csrf-protection'; -import middie from '@fastify/middie'; +import express from '@fastify/express'; import fastifySession from '@fastify/session'; import fastifySwagger from '@fastify/swagger'; import fastifySwaggerUI from '@fastify/swagger-ui'; @@ -21,7 +21,6 @@ import Fastify, { import fastifyAuth0 from 'fastify-auth0-verify'; import prismaPlugin from './db/prisma'; -import { testMiddleware } from './middleware'; import cors from './plugins/cors'; import jwtAuthz from './plugins/fastify-jwt-authz'; import { NodemailerProvider } from './plugins/mail-providers/nodemailer'; @@ -33,7 +32,8 @@ import sessionAuth from './plugins/session-auth'; import { auth0Routes, devLoginCallback, - devLegacyAuthRoutes + devLegacyAuthRoutes, + mobileAuth0Routes } from './routes/auth'; import { challengeRoutes } from './routes/challenge'; import { deprecatedEndpoints } from './routes/deprecated-endpoints'; @@ -106,7 +106,7 @@ export const build = async ( return { hello: 'world' }; }); // NOTE: Awaited to ensure `.use` is registered on `fastify` - await fastify.register(middie); + await fastify.register(express); if (SENTRY_DSN) { await fastify.register(fastifySentry, { dsn: SENTRY_DSN }); } @@ -202,11 +202,10 @@ export const build = async ( void fastify.register(jwtAuthz); void fastify.register(sessionAuth); - void fastify.use('/test', testMiddleware); - void fastify.register(prismaPlugin); void fastify.register(auth0Routes, { prefix: '/auth' }); + void fastify.register(mobileAuth0Routes); if (FCC_ENABLE_DEV_LOGIN_MODE) { void fastify.register(devLoginCallback, { prefix: '/auth' }); void fastify.register(devLegacyAuthRoutes); diff --git a/api/src/middleware/index.ts b/api/src/middleware/index.ts deleted file mode 100644 index bb874fd3fc8..00000000000 --- a/api/src/middleware/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { NextFunction, NextHandleFunction } from '@fastify/middie'; - -type MiddieRequest = Parameters[0]; -type MiddieResponse = Parameters[1]; - -/** - * Test middleware used to log request and response data? - * - * @param req The request payload. - * @param res The response to be sent back to the request. - * @param next Callback function to indicate that the middleware logic is complete. - */ -export function testMiddleware( - req: MiddieRequest, - res: MiddieResponse, - next: NextFunction -): void { - console.log('Test middleware running'); - console.log(req.headers); - console.log(req.query); - res.setHeader('X-Test-Header', 'test'); - next(); -} diff --git a/api/src/routes/auth.ts b/api/src/routes/auth.ts index 06f8e94c925..57f26ed9712 100644 --- a/api/src/routes/auth.ts +++ b/api/src/routes/auth.ts @@ -4,8 +4,12 @@ import { FastifyRequest } from 'fastify'; +import rateLimit from 'express-rate-limit'; +// @ts-expect-error - no types +import MongoStoreRL from 'rate-limit-mongo'; + import { createUserInput } from '../utils/create-user'; -import { AUTH0_DOMAIN, HOME_LOCATION } from '../utils/env'; +import { AUTH0_DOMAIN, HOME_LOCATION, MONGOHQ_URL } from '../utils/env'; declare module 'fastify' { interface Session { @@ -85,7 +89,50 @@ export const devLoginCallback: FastifyPluginCallback = ( export const auth0Routes: FastifyPluginCallback = (fastify, _options, done) => { fastify.addHook('onRequest', fastify.authenticate); - fastify.get('/callback', async req => { + fastify.get('/auth0/callback', async req => { + const email = await getEmailFromAuth0(req); + + const { id } = await findOrCreateUser(fastify, email); + req.session.user = { id }; + await req.session.save(); + }); + + done(); +}; + +/** + * Route handler for Mobile authentication. + * + * @param fastify The Fastify instance. + * @param _options Options passed to the plugin via `fastify.register(plugin, options)`. + * @param done Callback to signal that the logic has completed. + */ +export const mobileAuth0Routes: FastifyPluginCallback = ( + fastify, + _options, + done +) => { + // Rate limit for mobile login + // 10 requests per 15 minute windows + void fastify.use( + rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, + standardHeaders: true, + legacyHeaders: false, + keyGenerator: req => { + return (req.headers['x-forwarded-for'] as string) || 'localhost'; + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + store: new MongoStoreRL({ + collectionName: 'UserRateLimit', + uri: MONGOHQ_URL, + expireTimeMs: 15 * 60 * 1000 + }) + }) + ); + + fastify.get('/mobile-login', async req => { const email = await getEmailFromAuth0(req); const { id } = await findOrCreateUser(fastify, email); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5ab77f2e90..5b568cb983d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,9 +177,9 @@ importers: '@fastify/csrf-protection': specifier: 6.4.1 version: 6.4.1 - '@fastify/middie': - specifier: '8.3' - version: 8.3.0 + '@fastify/express': + specifier: ^2.3.0 + version: 2.3.0 '@fastify/session': specifier: 10.5.0 version: 10.5.0 @@ -213,6 +213,9 @@ importers: dotenv: specifier: 16.3.1 version: 16.3.1 + express-rate-limit: + specifier: ^6.7.0 + version: 6.7.0(express@4.18.2) fast-uri: specifier: 2.3.0 version: 2.3.0 @@ -255,6 +258,9 @@ importers: query-string: specifier: 7.1.3 version: 7.1.3 + rate-limit-mongo: + specifier: ^2.3.2 + version: 2.3.2 stripe: specifier: 8.222.0 version: 8.222.0 @@ -6646,14 +6652,19 @@ packages: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} dev: false - /@fastify/error@3.3.0: - resolution: {integrity: sha512-dj7vjIn1Ar8sVXj2yAXiMNCJDmS9MQ9XMlIecX2dIzzhjSHCyKo4DdXjXMs7wKW2kj6yvVRSpuQjOZ3YLrh56w==} - dev: false - /@fastify/error@3.4.1: resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} dev: false + /@fastify/express@2.3.0: + resolution: {integrity: sha512-jvvjlPPCfJsSHfF6tQDyARJ3+c3xXiqcxVZu6bi3xMWCWB3fl07vrjFDeaqnwqKhLZ9+m6cog5dw7gIMKEsTnQ==} + dependencies: + express: 4.18.2 + fastify-plugin: 4.5.1 + transitivePeerDependencies: + - supports-color + dev: false + /@fastify/fast-json-stringify-compiler@4.3.0: resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} dependencies: @@ -6680,15 +6691,6 @@ packages: steed: 1.1.3 dev: false - /@fastify/middie@8.3.0: - resolution: {integrity: sha512-h+zBxCzMlkEkh4fM7pZaSGzqS7P9M0Z6rXnWPdUEPfe7x1BCj++wEk/pQ5jpyYY4pF8AknFqb77n7uwh8HdxEA==} - dependencies: - '@fastify/error': 3.3.0 - fastify-plugin: 4.5.1 - path-to-regexp: 6.2.1 - reusify: 1.0.4 - dev: false - /@fastify/send@2.1.0: resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} dependencies: @@ -25800,10 +25802,6 @@ packages: resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} dev: true - /path-to-regexp@6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: false - /path-type@1.1.0: resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} engines: {node: '>=0.10.0'}