mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[web] remove generic analytics service proxy (#36934)
GitOrigin-RevId: 19b3613b7cc84e73ad556067da1b3cd2f956e1c5
This commit is contained in:
@@ -71,6 +71,7 @@ const ignoreWords = {
|
|||||||
'resendManagedUserInvite',
|
'resendManagedUserInvite',
|
||||||
'salesContactForm',
|
'salesContactForm',
|
||||||
'showSupport',
|
'showSupport',
|
||||||
|
'uniExternalCollaboration',
|
||||||
]),
|
]),
|
||||||
other: new Set([
|
other: new Set([
|
||||||
'Project',
|
'Project',
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import metrics from '@overleaf/metrics'
|
import metrics from '@overleaf/metrics'
|
||||||
|
import Settings from '@overleaf/settings'
|
||||||
|
import { fetchJson } from '@overleaf/fetch-utils'
|
||||||
import AnalyticsManager from './AnalyticsManager.mjs'
|
import AnalyticsManager from './AnalyticsManager.mjs'
|
||||||
import SessionManager from '../Authentication/SessionManager.mjs'
|
import SessionManager from '../Authentication/SessionManager.mjs'
|
||||||
import GeoIpLookup from '../../infrastructure/GeoIpLookup.mjs'
|
import GeoIpLookup from '../../infrastructure/GeoIpLookup.mjs'
|
||||||
import Features from '../../infrastructure/Features.mjs'
|
import Features from '../../infrastructure/Features.mjs'
|
||||||
import { expressify } from '@overleaf/promise-utils'
|
import { expressify } from '@overleaf/promise-utils'
|
||||||
import AccountMappingHelper from './AccountMappingHelper.mjs'
|
import AccountMappingHelper from './AccountMappingHelper.mjs'
|
||||||
|
import Errors from '../Errors/Errors.js'
|
||||||
import { z, zz, parseReq } from '../../infrastructure/Validation.mjs'
|
import { z, zz, parseReq } from '../../infrastructure/Validation.mjs'
|
||||||
|
|
||||||
// The set of segmentation/event keys is caller-supplied and dynamic (every
|
// The set of segmentation/event keys is caller-supplied and dynamic (every
|
||||||
@@ -125,8 +128,28 @@ function recordEvent(req, res, next) {
|
|||||||
res.sendStatus(202)
|
res.sendStatus(202)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uniExternalCollaborationSchema = z.object({
|
||||||
|
query: z.object({
|
||||||
|
// sent by v1's Api::V2::InstitutionsController#external_collaboration_data
|
||||||
|
university_id: z.coerce.number().int().positive(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
async function uniExternalCollaboration(req, res) {
|
||||||
|
if (!Settings.apis.analytics) {
|
||||||
|
throw new Errors.ServiceNotConfiguredError(
|
||||||
|
'Analytics service not configured'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const { query } = parseReq(req, uniExternalCollaborationSchema)
|
||||||
|
const url = new URL('/uniExternalCollaboration', Settings.apis.analytics.url)
|
||||||
|
url.searchParams.set('university_id', query.university_id)
|
||||||
|
res.json(await fetchJson(url, { signal: AbortSignal.timeout(20_000) }))
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
registerSalesforceMapping: expressify(registerSalesforceMapping),
|
registerSalesforceMapping: expressify(registerSalesforceMapping),
|
||||||
|
uniExternalCollaboration: expressify(uniExternalCollaboration),
|
||||||
updateEditingSession: expressify(updateEditingSession),
|
updateEditingSession: expressify(updateEditingSession),
|
||||||
recordEvent,
|
recordEvent,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import settings from '@overleaf/settings'
|
|
||||||
import Errors from '../Errors/Errors.js'
|
|
||||||
import httpProxy from 'express-http-proxy'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
call(basePath) {
|
|
||||||
if (!settings.apis.analytics) {
|
|
||||||
return (req, res, next) =>
|
|
||||||
next(
|
|
||||||
new Errors.ServiceNotConfiguredError(
|
|
||||||
'Analytics service not configured'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpProxy(settings.apis.analytics.url, {
|
|
||||||
proxyReqPathResolver(req) {
|
|
||||||
// req.url is the part of the path that comes after the mount point in
|
|
||||||
// app.use()
|
|
||||||
return `${basePath}${req.url}`
|
|
||||||
},
|
|
||||||
proxyReqOptDecorator(proxyReqOpts, srcReq) {
|
|
||||||
proxyReqOpts.headers = {} // unset all headers
|
|
||||||
return proxyReqOpts
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import AuthenticationController from './../Authentication/AuthenticationController.mjs'
|
import AuthenticationController from './../Authentication/AuthenticationController.mjs'
|
||||||
import AnalyticsController from './AnalyticsController.mjs'
|
import AnalyticsController from './AnalyticsController.mjs'
|
||||||
import AnalyticsProxy from './AnalyticsProxy.mjs'
|
|
||||||
import { RateLimiter } from '../../infrastructure/RateLimiter.mjs'
|
import { RateLimiter } from '../../infrastructure/RateLimiter.mjs'
|
||||||
import RateLimiterMiddleware from '../Security/RateLimiterMiddleware.mjs'
|
import RateLimiterMiddleware from '../Security/RateLimiterMiddleware.mjs'
|
||||||
|
|
||||||
@@ -13,8 +12,8 @@ const rateLimiters = {
|
|||||||
points: 20,
|
points: 20,
|
||||||
duration: 60,
|
duration: 60,
|
||||||
}),
|
}),
|
||||||
uniExternalCollabProxy: new RateLimiter(
|
uniExternalCollaboration: new RateLimiter(
|
||||||
'analytics-uni-external-collab-proxy',
|
'analytics-uni-external-collaboration',
|
||||||
{ points: 20, duration: 60 }
|
{ points: 20, duration: 60 }
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -35,11 +34,11 @@ export default {
|
|||||||
AnalyticsController.updateEditingSession
|
AnalyticsController.updateEditingSession
|
||||||
)
|
)
|
||||||
|
|
||||||
publicApiRouter.use(
|
publicApiRouter.get(
|
||||||
'/analytics/uniExternalCollaboration',
|
'/analytics/uniExternalCollaboration',
|
||||||
AuthenticationController.requirePrivateApiAuth(),
|
AuthenticationController.requirePrivateApiAuth(),
|
||||||
RateLimiterMiddleware.rateLimit(rateLimiters.uniExternalCollabProxy),
|
RateLimiterMiddleware.rateLimit(rateLimiters.uniExternalCollaboration),
|
||||||
AnalyticsProxy.call('/uniExternalCollaboration')
|
AnalyticsController.uniExternalCollaboration
|
||||||
)
|
)
|
||||||
|
|
||||||
publicApiRouter.post(
|
publicApiRouter.post(
|
||||||
|
|||||||
@@ -136,7 +136,6 @@
|
|||||||
"eventsource-parser": "^1.1.2",
|
"eventsource-parser": "^1.1.2",
|
||||||
"express": "4.22.1",
|
"express": "4.22.1",
|
||||||
"express-bearer-token": "patch:express-bearer-token@npm%3A2.4.0#~/.yarn/patches/express-bearer-token-npm-2.4.0-731c8c5c99.patch",
|
"express-bearer-token": "patch:express-bearer-token@npm%3A2.4.0#~/.yarn/patches/express-bearer-token-npm-2.4.0-731c8c5c99.patch",
|
||||||
"express-http-proxy": "^1.6.0",
|
|
||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"file-type": "^21.3.4",
|
"file-type": "^21.3.4",
|
||||||
"focus-trap-react": "^11.0.4",
|
"focus-trap-react": "^11.0.4",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { expect } from 'chai'
|
|||||||
import UserHelper from './helpers/User.mjs'
|
import UserHelper from './helpers/User.mjs'
|
||||||
import request from './helpers/request.js'
|
import request from './helpers/request.js'
|
||||||
import Features from '../../../app/src/infrastructure/Features.mjs'
|
import Features from '../../../app/src/infrastructure/Features.mjs'
|
||||||
|
import MockAnalyticsApiClass from './mocks/MockAnalyticsApi.mjs'
|
||||||
import { expectValidationErrorRaw } from '@overleaf/validation-tools/testUtils.js'
|
import { expectValidationErrorRaw } from '@overleaf/validation-tools/testUtils.js'
|
||||||
|
|
||||||
const User = UserHelper.promises
|
const User = UserHelper.promises
|
||||||
@@ -19,6 +20,14 @@ function privateApiRequest(options) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let MockAnalyticsApi
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
if (Features.hasFeature('saas')) {
|
||||||
|
MockAnalyticsApi = MockAnalyticsApiClass.instance()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe('AnalyticsController', function () {
|
describe('AnalyticsController', function () {
|
||||||
let owner
|
let owner
|
||||||
|
|
||||||
@@ -88,6 +97,81 @@ describe('AnalyticsController', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('GET /analytics/uniExternalCollaboration', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
// both the analytics API URL and its mock are saas-only, see
|
||||||
|
// test/acceptance/config/settings.test.saas.js and Init.mjs
|
||||||
|
if (!Features.hasFeature('saas')) {
|
||||||
|
this.skip()
|
||||||
|
}
|
||||||
|
MockAnalyticsApi.reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the data from the analytics service', async function () {
|
||||||
|
const response = await privateApiRequest({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration?university_id=42',
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
expect(response.body).to.deep.equal([
|
||||||
|
{ university_id: 123, external_collaborations: 321 },
|
||||||
|
])
|
||||||
|
expect(
|
||||||
|
MockAnalyticsApi.getLastUniExternalCollaborationRequest().query
|
||||||
|
).to.deep.equal({ university_id: '42' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject a non-numeric university id', async function () {
|
||||||
|
const response = await privateApiRequest({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration?university_id=not-a-number',
|
||||||
|
})
|
||||||
|
expectValidationErrorRaw(response, 400, 'university_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject a non-positive university id', async function () {
|
||||||
|
const response = await privateApiRequest({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration?university_id=0',
|
||||||
|
})
|
||||||
|
expectValidationErrorRaw(response, 400, 'university_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reject a missing university id', async function () {
|
||||||
|
const response = await privateApiRequest({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration',
|
||||||
|
})
|
||||||
|
expectValidationErrorRaw(response, 400, 'university_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should require private API auth', async function () {
|
||||||
|
const response = await authedRequest.request({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration?university_id=42',
|
||||||
|
json: true,
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(401)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not route a sub-path to the analytics service', async function () {
|
||||||
|
const response = await privateApiRequest({
|
||||||
|
method: 'get',
|
||||||
|
url: '/analytics/uniExternalCollaboration/extra?university_id=42',
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not route other methods to the analytics service', async function () {
|
||||||
|
await privateApiRequest({
|
||||||
|
method: 'post',
|
||||||
|
url: '/analytics/uniExternalCollaboration?university_id=42',
|
||||||
|
})
|
||||||
|
expect(MockAnalyticsApi.getLastUniExternalCollaborationRequest()).to.be
|
||||||
|
.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('POST /analytics/register-v-1-salesforce-mapping', function () {
|
describe('POST /analytics/register-v-1-salesforce-mapping', function () {
|
||||||
it('should accept a well-formed mapping', async function () {
|
it('should accept a well-formed mapping', async function () {
|
||||||
if (!Features.hasFeature('analytics')) {
|
if (!Features.hasFeature('analytics')) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class MockAnalyticsApi extends AbstractMockApi {
|
|||||||
this.lastSplitTestAssignmentsRequest = null
|
this.lastSplitTestAssignmentsRequest = null
|
||||||
this.lastSplitTestCalcRequest = null
|
this.lastSplitTestCalcRequest = null
|
||||||
this.lastSplitTestEventsRequest = null
|
this.lastSplitTestEventsRequest = null
|
||||||
|
this.lastUniExternalCollaborationRequest = null
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastGraphRequest() {
|
getLastGraphRequest() {
|
||||||
@@ -26,6 +27,10 @@ class MockAnalyticsApi extends AbstractMockApi {
|
|||||||
return this.lastSplitTestEventsRequest
|
return this.lastSplitTestEventsRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLastUniExternalCollaborationRequest() {
|
||||||
|
return this.lastUniExternalCollaborationRequest
|
||||||
|
}
|
||||||
|
|
||||||
applyRoutes() {
|
applyRoutes() {
|
||||||
this.app.get('/graphs/:graph', (req, res) => {
|
this.app.get('/graphs/:graph', (req, res) => {
|
||||||
// case 3: recorded verbatim for later assertion by acceptance tests
|
// case 3: recorded verbatim for later assertion by acceptance tests
|
||||||
@@ -80,6 +85,14 @@ class MockAnalyticsApi extends AbstractMockApi {
|
|||||||
this.lastSplitTestEventsRequest = { query: getRawReqInput(req).query }
|
this.lastSplitTestEventsRequest = { query: getRawReqInput(req).query }
|
||||||
res.json({ 'editor-open': { source: ['ide-open'] } })
|
res.json({ 'editor-open': { source: ['ide-open'] } })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.app.get('/uniExternalCollaboration', (req, res) => {
|
||||||
|
// case 3: recorded verbatim for later assertion by acceptance tests
|
||||||
|
this.lastUniExternalCollaborationRequest = {
|
||||||
|
query: getRawReqInput(req).query,
|
||||||
|
}
|
||||||
|
res.json([{ university_id: 123, external_collaborations: 321 }])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8128,7 +8128,6 @@ __metadata:
|
|||||||
eventsource-parser: "npm:^1.1.2"
|
eventsource-parser: "npm:^1.1.2"
|
||||||
express: "npm:4.22.1"
|
express: "npm:4.22.1"
|
||||||
express-bearer-token: "patch:express-bearer-token@npm%3A2.4.0#~/.yarn/patches/express-bearer-token-npm-2.4.0-731c8c5c99.patch"
|
express-bearer-token: "patch:express-bearer-token@npm%3A2.4.0#~/.yarn/patches/express-bearer-token-npm-2.4.0-731c8c5c99.patch"
|
||||||
express-http-proxy: "npm:^1.6.0"
|
|
||||||
express-session: "npm:^1.17.1"
|
express-session: "npm:^1.17.1"
|
||||||
fake-indexeddb: "npm:^6.0.0"
|
fake-indexeddb: "npm:^6.0.0"
|
||||||
fast-fuzzy: "npm:^1.12.0"
|
fast-fuzzy: "npm:^1.12.0"
|
||||||
@@ -16502,7 +16501,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"debug@npm:^3.0.1, debug@npm:^3.1.0, debug@npm:^3.2.7":
|
"debug@npm:^3.1.0, debug@npm:^3.2.7":
|
||||||
version: 3.2.7
|
version: 3.2.7
|
||||||
resolution: "debug@npm:3.2.7"
|
resolution: "debug@npm:3.2.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17660,7 +17659,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"es6-promise@npm:^4.1.0, es6-promise@npm:^4.1.1, es6-promise@npm:^4.2.8":
|
"es6-promise@npm:^4.1.0, es6-promise@npm:^4.2.8":
|
||||||
version: 4.2.8
|
version: 4.2.8
|
||||||
resolution: "es6-promise@npm:4.2.8"
|
resolution: "es6-promise@npm:4.2.8"
|
||||||
checksum: 10c0/2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3
|
checksum: 10c0/2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3
|
||||||
@@ -18499,17 +18498,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"express-http-proxy@npm:^1.6.0":
|
|
||||||
version: 1.6.3
|
|
||||||
resolution: "express-http-proxy@npm:1.6.3"
|
|
||||||
dependencies:
|
|
||||||
debug: "npm:^3.0.1"
|
|
||||||
es6-promise: "npm:^4.1.1"
|
|
||||||
raw-body: "npm:^2.3.0"
|
|
||||||
checksum: 10c0/e27d85b923a812aa8d2ce90bc15cc8254bcd1691d43ea1c070760d4e3fe985d913e4e9f4523be649aff36a0c827b0d35222f5f5e62a5b03c8ffe58284aa91ef6
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"express-rate-limit@npm:^2.11.0":
|
"express-rate-limit@npm:^2.11.0":
|
||||||
version: 2.14.2
|
version: 2.14.2
|
||||||
resolution: "express-rate-limit@npm:2.14.2"
|
resolution: "express-rate-limit@npm:2.14.2"
|
||||||
@@ -28706,7 +28694,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"raw-body@npm:^2.3.0, raw-body@npm:~2.5.3":
|
"raw-body@npm:~2.5.3":
|
||||||
version: 2.5.3
|
version: 2.5.3
|
||||||
resolution: "raw-body@npm:2.5.3"
|
resolution: "raw-body@npm:2.5.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user