From b7c133d58e2d835157596500e4c0aa697cbea294 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 28 Jun 2023 17:00:11 +0800 Subject: [PATCH] fix: email sending and recommendation * use token in the custom header as the key in rate limiter --- packages/api/src/routers/page_router.ts | 25 +++++++++++++------------ packages/api/src/routers/user_router.ts | 7 ++----- packages/api/src/server.ts | 17 +++++------------ packages/api/src/utils/auth.ts | 23 +++++++++++++++++------ packages/api/src/utils/createTask.ts | 4 ++-- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/api/src/routers/page_router.ts b/packages/api/src/routers/page_router.ts index 50bca8865..d3072874b 100644 --- a/packages/api/src/routers/page_router.ts +++ b/packages/api/src/routers/page_router.ts @@ -2,19 +2,24 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import cors from 'cors' import express from 'express' +import * as jwt from 'jsonwebtoken' +import { kx } from '../datalayer/knex_config' +import { createPubSubClient } from '../datalayer/pubsub' +import { createPage, getPageByParam, updatePage } from '../elastic/pages' +import { addRecommendation } from '../elastic/recommendation' +import { Recommendation } from '../elastic/types' +import { env } from '../env' import { ArticleSavingRequestStatus, PageType, UploadFileStatus, } from '../generated/graphql' -import cors from 'cors' -import { env } from '../env' -import { buildLogger } from '../utils/logger' -import * as jwt from 'jsonwebtoken' -import { corsConfig } from '../utils/corsConfig' +import { Claims } from '../resolvers/types' import { initModels } from '../server' -import { kx } from '../datalayer/knex_config' +import { getTokenByRequest } from '../utils/auth' +import { corsConfig } from '../utils/corsConfig' import { fileNameForFilePath, generateSlug, @@ -22,15 +27,11 @@ import { titleForFilePath, validateUuid, } from '../utils/helpers' +import { buildLogger } from '../utils/logger' import { generateUploadFilePathName, generateUploadSignedUrl, } from '../utils/uploads' -import { Claims } from '../resolvers/types' -import { createPage, getPageByParam, updatePage } from '../elastic/pages' -import { createPubSubClient } from '../datalayer/pubsub' -import { Recommendation } from '../elastic/types' -import { addRecommendation } from '../elastic/recommendation' const logger = buildLogger('app.dispatch') @@ -157,7 +158,7 @@ export function pageRouter() { '/recommend', cors(corsConfig), async (req, res) => { - const token = req?.cookies?.auth || req?.headers?.authorization + const token = getTokenByRequest(req) if (!token || !jwt.verify(token, env.server.jwtSecret)) { return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) } diff --git a/packages/api/src/routers/user_router.ts b/packages/api/src/routers/user_router.ts index f9bae9350..ebe954c3a 100644 --- a/packages/api/src/routers/user_router.ts +++ b/packages/api/src/routers/user_router.ts @@ -5,7 +5,7 @@ import express from 'express' import { User } from '../entity/user' import { getRepository } from '../entity/utils' import { env } from '../env' -import { getClaimsByToken } from '../utils/auth' +import { getClaimsByToken, getTokenByRequest } from '../utils/auth' import { corsConfig } from '../utils/corsConfig' import { buildLogger } from '../utils/logger' import { sendEmail } from '../utils/sendEmail' @@ -17,10 +17,7 @@ export function userRouter() { router.post('/email', cors(corsConfig), async (req, res) => { logger.info('email to-user router') - const token = - req.header('Omnivore-Authorization') || - req.cookies?.auth || - req.headers?.authorization + const token = getTokenByRequest(req) let claims try { diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 5837ae153..94565eeeb 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -48,7 +48,7 @@ import { webhooksServiceRouter } from './routers/svc/webhooks' import { textToSpeechRouter } from './routers/text_to_speech' import { userRouter } from './routers/user_router' import { sentryConfig } from './sentry' -import { getClaimsByToken } from './utils/auth' +import { getClaimsByToken, getTokenByRequest } from './utils/auth' import { corsConfig } from './utils/corsConfig' import { buildLogger, buildLoggerTransport } from './utils/logger' @@ -102,19 +102,12 @@ export const createApp = (): { windowMs: 60 * 1000, // 1 minute max: async (req) => { // 100 RPM for an authenticated request, 5 for a non-authenticated request - const token = await getClaimsByToken( - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - req.header('authorization') ?? req.cookies['auth'] - ) - return token ? 100 : 5 + const token = getTokenByRequest(req) + const claims = await getClaimsByToken(token) + return claims ? 100 : 5 }, keyGenerator: (req) => { - return ( - req.header('authorization') || - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - (req.cookies['auth'] as string) || - req.ip - ) + return getTokenByRequest(req) || req.ip }, // skip preflight requests and test requests skip: (req) => req.method === 'OPTIONS' || env.dev.isLocal, diff --git a/packages/api/src/utils/auth.ts b/packages/api/src/utils/auth.ts index 8885d279e..45ed00b39 100644 --- a/packages/api/src/utils/auth.ts +++ b/packages/api/src/utils/auth.ts @@ -1,13 +1,15 @@ import * as bcrypt from 'bcryptjs' -import { v4 as uuidv4 } from 'uuid' -import { Claims, ClaimsToSet } from '../resolvers/types' -import { getRepository } from '../entity/utils' -import { ApiKey } from '../entity/api_key' import crypto from 'crypto' -import * as jwt from 'jsonwebtoken' -import { env } from '../env' import express from 'express' +import * as jwt from 'jsonwebtoken' import { promisify } from 'util' +import { v4 as uuidv4 } from 'uuid' +import { ApiKey } from '../entity/api_key' +import { getRepository } from '../entity/utils' +import { env } from '../env' +import { Claims, ClaimsToSet } from '../resolvers/types' + +export const OmnivoreAuthorizationHeader = 'Omnivore-Authorization' const signToken = promisify(jwt.sign) @@ -112,3 +114,12 @@ export const setAuthInCookie = async ( expires: new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000), }) } + +export const getTokenByRequest = (req: express.Request): string | undefined => { + return ( + req.header(OmnivoreAuthorizationHeader) || + req.headers.authorization || + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (req.cookies?.auth as string) + ) +} diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 939eacc5b..03752913e 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -12,7 +12,7 @@ import { CreateLabelInput, } from '../generated/graphql' import { signFeatureToken } from '../services/features' -import { generateVerificationToken } from './auth' +import { generateVerificationToken, OmnivoreAuthorizationHeader } from './auth' import { CreateTaskError } from './errors' import { buildLogger } from './logger' import View = google.cloud.tasks.v2.Task.View @@ -432,7 +432,7 @@ export const enqueueRecommendation = async ( } const headers = { - Cookie: `auth=${authToken}`, + [OmnivoreAuthorizationHeader]: authToken, } // If there is no Google Cloud Project Id exposed, it means that we are in local environment if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {