diff --git a/packages/api/package.json b/packages/api/package.json index 0631570ed..70fa43d9e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -53,6 +53,7 @@ "dot-case": "^3.0.4", "dotenv": "^8.2.0", "express": "^4.17.1", + "express-http-context": "^1.2.4", "express-rate-limit": "^6.3.0", "firebase-admin": "^10.0.2", "googleapis": "^105.0.0", diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 62dbc8d40..2efb4e765 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -46,6 +46,7 @@ import rateLimit from 'express-rate-limit' import { webhooksServiceRouter } from './routers/svc/webhooks' import { integrationsServiceRouter } from './routers/svc/integrations' import { textToSpeechRouter } from './routers/text_to_speech' +import * as httpContext from 'express-http-context' const PORT = process.env.PORT || 4000 @@ -104,6 +105,16 @@ export const createApp = (): { app.use('/api/', apiLimiter) } + // set client info in the request context + app.use(httpContext.middleware) + app.use('/api/', (req, res, next) => { + const client = req.header('X-OmnivoreClient') + if (client) { + httpContext.set('client', client) + } + next() + }) + // respond healthy to auto-scaler. app.get('/_ah/health', (req, res) => res.sendStatus(200)) diff --git a/packages/api/src/services/popular_reads.ts b/packages/api/src/services/popular_reads.ts index 67748516b..db2933c3d 100644 --- a/packages/api/src/services/popular_reads.ts +++ b/packages/api/src/services/popular_reads.ts @@ -5,6 +5,7 @@ import { PageType } from '../generated/graphql' import { generateSlug, stringToHash } from '../utils/helpers' import { readFileSync } from 'fs' import path from 'path' +import * as httpContext from 'express-http-context' type PopularRead = { url: string @@ -113,8 +114,7 @@ const addPopularReads = async ( } export const addPopularReadsForNewUser = async ( - userId: string, - client = 'web' + userId: string ): Promise => { const defaultReads = [ 'omnivore_get_started', @@ -122,6 +122,9 @@ export const addPopularReadsForNewUser = async ( 'omnivore_organize', ] + // get client from request context + const client = httpContext.get('client') as string | undefined + switch (client) { case 'web': defaultReads.push('omnivore_web') diff --git a/packages/api/test/routers/auth.test.ts b/packages/api/test/routers/auth.test.ts index aedee397e..09ee056ae 100644 --- a/packages/api/test/routers/auth.test.ts +++ b/packages/api/test/routers/auth.test.ts @@ -16,6 +16,7 @@ import sinonChai from 'sinon-chai' import chai, { expect } from 'chai' import { searchPages } from '../../src/elastic/pages' import { createPendingUserToken } from '../../src/routers/auth/jwt_helpers' +import { AuthProvider } from '../../src/routers/auth/auth_types' chai.use(sinonChai) @@ -554,10 +555,12 @@ describe('auth router', () => { bio: string, name: string, username: string, - pendingUserAuth: string + pendingUserAuth: string, + client: string ): supertest.Test => { return request .post(`${route}/create-account`) + .set('X-OmnivoreClient', client) .set('Cookie', [`pendingUserAuth=${pendingUserAuth}`]) .send({ name, @@ -572,6 +575,7 @@ describe('auth router', () => { let sourceUserId = 'test_source_user_id' let email = 'test_user@omnivore.app' let bio = 'test_bio' + let provider: AuthProvider = 'EMAIL' afterEach(async () => { await deleteTestUser(username) @@ -581,7 +585,7 @@ describe('auth router', () => { const pendingUserToken = await createPendingUserToken({ sourceUserId, email, - provider: 'EMAIL', + provider, name, username, }) @@ -589,7 +593,8 @@ describe('auth router', () => { bio, name, username, - pendingUserToken! + pendingUserToken!, + 'web' ).expect(200) const user = await getRepository(User).findOneBy({ name }) const [popularReads, count] = (await searchPages({}, user?.id!)) || [ @@ -604,7 +609,7 @@ describe('auth router', () => { const pendingUserToken = await createPendingUserToken({ sourceUserId, email, - provider: 'APPLE', + provider, name, username, }) @@ -612,7 +617,8 @@ describe('auth router', () => { bio, name, username, - pendingUserToken! + pendingUserToken!, + 'ios' ).expect(200) const user = await getRepository(User).findOneBy({ name }) const [popularReads, count] = (await searchPages({}, user?.id!)) || [ diff --git a/yarn.lock b/yarn.lock index ebf984f5a..272f859a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7557,6 +7557,13 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg== +"@types/cls-hooked@^4.2.1": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/cls-hooked/-/cls-hooked-4.3.3.tgz#c09e2f8dc62198522eaa18a5b6b873053154bd00" + integrity sha512-gNstDTb/ty5h6gJd6YpSPgsLX9LmRpaKJqGFp7MRlYxhwp4vXXKlJ9+bt1TZ9KbVNXE+Mbxy2AYXcpY21DDtJw== + dependencies: + "@types/node" "*" + "@types/color-convert@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22" @@ -7680,6 +7687,16 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/express@^4.16.0": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/fined@*": version "1.1.3" resolved "https://registry.yarnpkg.com/@types/fined/-/fined-1.1.3.tgz#83f03e8f0a8d3673dfcafb18fce3571f6250e1bc" @@ -9559,6 +9576,13 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + async-retry@^1.2.1, async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" @@ -11011,6 +11035,15 @@ cloudevents@^6.0.0: util "^0.12.4" uuid "^8.3.2" +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -12732,6 +12765,13 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emitter-listener@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + emittery@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" @@ -13473,6 +13513,15 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +express-http-context@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/express-http-context/-/express-http-context-1.2.4.tgz#49769d0e260836278996e728d9a3e7f3735f0531" + integrity sha512-jPpBbF1MWWdRcUU1rxsX0CPnA8ueEj8xgWvpRGHoXWGI4l5KqhPY4Bq+Gt6s2IhqHQQ0g0wIvJ3jFfbUuJJycQ== + dependencies: + "@types/cls-hooked" "^4.2.1" + "@types/express" "^4.16.0" + cls-hooked "^4.2.2" + express-rate-limit@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.3.0.tgz#253387ce4d36c9c2cc77c7c676068deb36cc0821" @@ -22804,7 +22853,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shimmer@^1.2.1: +shimmer@^1.2.0, shimmer@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== @@ -23251,6 +23300,11 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"