mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[CE/SP] Hotfix 6.2.2 (#35546)
* [CE/SP] Hotfix 6.2.2 GitOrigin-RevId: 5ee457a611fe7d6f73d8ce4bcf55378bd8c7fc69
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
FROM sharelatex/sharelatex:6.2.1
|
||||
|
||||
# Apply security updates to base image
|
||||
RUN apt update && apt install -y linux-libc-dev \
|
||||
&& unattended-upgrade --verbose --no-minimal-upgrade-steps \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# bump protobufjs to 7.6.3 and @protobufjs/* packages
|
||||
COPY pr_33656.patch .
|
||||
RUN patch -p1 < pr_33656.patch && rm pr_33656.patch
|
||||
|
||||
# bump form-data to 2.5.6
|
||||
COPY pr_34920.patch .
|
||||
RUN patch -p1 < pr_34920.patch && rm pr_34920.patch
|
||||
|
||||
# bump @grpc/grpc-js to 1.14.4
|
||||
COPY pr_34580.patch .
|
||||
RUN patch -p1 < pr_34580.patch && rm pr_34580.patch
|
||||
|
||||
# bump multer to 2.2.0
|
||||
COPY pr_34916.patch .
|
||||
RUN patch -p1 < pr_34916.patch && rm pr_34916.patch
|
||||
|
||||
# fix projectInviteEncryptorOptions assignment in settings.js
|
||||
COPY fix_invite_token_secret.patch .
|
||||
RUN patch -p1 < fix_invite_token_secret.patch && rm fix_invite_token_secret.patch
|
||||
|
||||
# gate group link sharing behind feature flag, remove debug logging
|
||||
COPY pr_34394.patch .
|
||||
RUN patch -p1 < pr_34394.patch && rm pr_34394.patch
|
||||
|
||||
# migrate email sender from @aws-sdk/client-ses to @aws-sdk/client-sesv2
|
||||
COPY pr_35531.patch .
|
||||
RUN patch -p1 < pr_35531.patch && rm pr_35531.patch
|
||||
|
||||
# add script for flushing/discarding stuck docs
|
||||
COPY pr_35603.patch .
|
||||
RUN patch -p1 < pr_35603.patch && rm pr_35603.patch
|
||||
|
||||
RUN yarn workspace @overleaf/web remove @aws-sdk/client-ses \
|
||||
&& yarn workspace @overleaf/web add @aws-sdk/client-sesv2@3.994.0 \
|
||||
&& yarn install --no-immutable \
|
||||
&& node genScript compile | bash \
|
||||
&& yarn workspaces focus --all --production
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/server-ce/config/settings.js b/server-ce/config/settings.js
|
||||
index 160a0b8cb01..3ecb620a339 100644
|
||||
--- a/server-ce/config/settings.js
|
||||
+++ b/server-ce/config/settings.js
|
||||
@@ -307,7 +307,7 @@ const settings = {
|
||||
|
||||
// This secret is used for encrypting sharing link tokens in the database
|
||||
if (process.env.OVERLEAF_INVITE_TOKEN_SECRET) {
|
||||
- module.exports.projectInviteEncryptorOptions = {
|
||||
+ settings.projectInviteEncryptorOptions = {
|
||||
cipherLabel: '2026.3-v3',
|
||||
cipherPasswords: {
|
||||
'2026.3-v3': process.env.OVERLEAF_INVITE_TOKEN_SECRET,
|
||||
@@ -0,0 +1,132 @@
|
||||
diff --git a/package.json b/package.json
|
||||
index fff7e1383f32..ac93e9ebfbc1 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -45,8 +45,8 @@
|
||||
"@cypress/request/qs": "6.14.1",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"mocha/serialize-javascript": "7.0.5",
|
||||
- "pprof/protobufjs": "7.5.5",
|
||||
- "@google-cloud/profiler/protobufjs": "7.5.5",
|
||||
+ "protobufjs": "7.6.3",
|
||||
+ "@protobufjs/utf8": "1.1.1",
|
||||
"mocha-multi-reporters": "patch:mocha-multi-reporters@npm%3A1.5.1#~/.yarn/patches/mocha-multi-reporters-npm-1.5.1-0a1088aed5.patch",
|
||||
"pdfjs-dist": "patch:pdfjs-dist@npm%3A5.1.91#~/.yarn/patches/pdfjs-dist-npm-5.1.91.patch",
|
||||
"referer-parser": "patch:referer-parser@npm%3A0.0.3#~/.yarn/patches/referer-parser-npm-0.0.3.patch",
|
||||
diff --git a/yarn.lock b/yarn.lock
|
||||
index f6e71513d7a3..03cadecb9882 100644
|
||||
--- a/yarn.lock
|
||||
+++ b/yarn.lock
|
||||
@@ -8098,27 +8098,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"@protobufjs/codegen@npm:^2.0.4":
|
||||
- version: 2.0.4
|
||||
- resolution: "@protobufjs/codegen@npm:2.0.4"
|
||||
- checksum: 10c0/26ae337c5659e41f091606d16465bbcc1df1f37cc1ed462438b1f67be0c1e28dfb2ca9f294f39100c52161aef82edf758c95d6d75650a1ddf31f7ddee1440b43
|
||||
+"@protobufjs/codegen@npm:^2.0.5":
|
||||
+ version: 2.0.5
|
||||
+ resolution: "@protobufjs/codegen@npm:2.0.5"
|
||||
+ checksum: 10c0/1b8a2ae56ee60a56e9d205cd4b6072a1503c5069b8ebb905710f974ff0098a0d0700641c137e0a8d98dedf14423156a106a9433695cbf52574810f55000fdcab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"@protobufjs/eventemitter@npm:^1.1.0":
|
||||
- version: 1.1.0
|
||||
- resolution: "@protobufjs/eventemitter@npm:1.1.0"
|
||||
- checksum: 10c0/1eb0a75180e5206d1033e4138212a8c7089a3d418c6dfa5a6ce42e593a4ae2e5892c4ef7421f38092badba4040ea6a45f0928869989411001d8c1018ea9a6e70
|
||||
+"@protobufjs/eventemitter@npm:^1.1.1":
|
||||
+ version: 1.1.1
|
||||
+ resolution: "@protobufjs/eventemitter@npm:1.1.1"
|
||||
+ checksum: 10c0/8e06193d4629c5e7c09d4f8c2ddba8fc4dfa739f0149f33a1d901568d35bb7b8b5277a4e8452baf3bdd0b302fd599cf255d193267aa93a0a4747e23cd073c4ac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"@protobufjs/fetch@npm:^1.1.0":
|
||||
- version: 1.1.0
|
||||
- resolution: "@protobufjs/fetch@npm:1.1.0"
|
||||
+"@protobufjs/fetch@npm:^1.1.1":
|
||||
+ version: 1.1.1
|
||||
+ resolution: "@protobufjs/fetch@npm:1.1.1"
|
||||
dependencies:
|
||||
"@protobufjs/aspromise": "npm:^1.1.1"
|
||||
- "@protobufjs/inquire": "npm:^1.1.0"
|
||||
- checksum: 10c0/cda6a3dc2d50a182c5865b160f72077aac197046600091dbb005dd0a66db9cce3c5eaed6d470ac8ed49d7bcbeef6ee5f0bc288db5ff9a70cbd003e5909065233
|
||||
+ checksum: 10c0/a497ff5433854e8577f0427983ea39b9113b49a8120f94515291d763327061d2c3013e60e24ea436d091dafae01a0f6eb1867e3b1616045d96a31d8b3c646ed4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8129,10 +8128,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"@protobufjs/inquire@npm:^1.1.0":
|
||||
- version: 1.1.0
|
||||
- resolution: "@protobufjs/inquire@npm:1.1.0"
|
||||
- checksum: 10c0/64372482efcba1fb4d166a2664a6395fa978b557803857c9c03500e0ac1013eb4b1aacc9ed851dd5fc22f81583670b4f4431bae186f3373fedcfde863ef5921a
|
||||
+"@protobufjs/inquire@npm:^1.1.2":
|
||||
+ version: 1.1.2
|
||||
+ resolution: "@protobufjs/inquire@npm:1.1.2"
|
||||
+ checksum: 10c0/af69597818a14cac6a00a74cd5b3fb85aa96658b9dbbae73e6d907cb154ebf470953a8c537b3e6095a43de565ec4e6c5b40227c72f4a7d762d34fbec7ac179e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8150,10 +8149,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"@protobufjs/utf8@npm:^1.1.0":
|
||||
- version: 1.1.0
|
||||
- resolution: "@protobufjs/utf8@npm:1.1.0"
|
||||
- checksum: 10c0/a3fe31fe3fa29aa3349e2e04ee13dc170cc6af7c23d92ad49e3eeaf79b9766264544d3da824dba93b7855bd6a2982fb40032ef40693da98a136d835752beb487
|
||||
+"@protobufjs/utf8@npm:1.1.1":
|
||||
+ version: 1.1.1
|
||||
+ resolution: "@protobufjs/utf8@npm:1.1.1"
|
||||
+ checksum: 10c0/641fc145f00626405e8984b6e90b9edcbcc072ffc82d0647ca3176e09c730b2d022f988e65f011a7a17e2e4d77cde7733643aa10d8ac2bfa30f134dbcad553fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -23208,7 +23207,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"long@npm:^5.0.0":
|
||||
+"long@npm:^5.0.0, long@npm:^5.3.2":
|
||||
version: 5.3.2
|
||||
resolution: "long@npm:5.3.2"
|
||||
checksum: 10c0/7130fe1cbce2dca06734b35b70d380ca3f70271c7f8852c922a7c62c86c4e35f0c39290565eca7133c625908d40e126ac57c02b1b1a4636b9457d77e1e60b981
|
||||
@@ -28120,23 +28119,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"protobufjs@npm:7.5.5, protobufjs@npm:^7.2.5, protobufjs@npm:^7.3.2, protobufjs@npm:^7.5.3":
|
||||
- version: 7.5.5
|
||||
- resolution: "protobufjs@npm:7.5.5"
|
||||
+"protobufjs@npm:7.6.3":
|
||||
+ version: 7.6.3
|
||||
+ resolution: "protobufjs@npm:7.6.3"
|
||||
dependencies:
|
||||
"@protobufjs/aspromise": "npm:^1.1.2"
|
||||
"@protobufjs/base64": "npm:^1.1.2"
|
||||
- "@protobufjs/codegen": "npm:^2.0.4"
|
||||
- "@protobufjs/eventemitter": "npm:^1.1.0"
|
||||
- "@protobufjs/fetch": "npm:^1.1.0"
|
||||
+ "@protobufjs/codegen": "npm:^2.0.5"
|
||||
+ "@protobufjs/eventemitter": "npm:^1.1.1"
|
||||
+ "@protobufjs/fetch": "npm:^1.1.1"
|
||||
"@protobufjs/float": "npm:^1.0.2"
|
||||
- "@protobufjs/inquire": "npm:^1.1.0"
|
||||
+ "@protobufjs/inquire": "npm:^1.1.2"
|
||||
"@protobufjs/path": "npm:^1.1.2"
|
||||
"@protobufjs/pool": "npm:^1.1.0"
|
||||
- "@protobufjs/utf8": "npm:^1.1.0"
|
||||
+ "@protobufjs/utf8": "npm:^1.1.1"
|
||||
"@types/node": "npm:>=13.7.0"
|
||||
- long: "npm:^5.0.0"
|
||||
- checksum: 10c0/3d48896a916761e3e60b52f80027eb4fba3f5a6e3f8461e04939db18812db2cb0db4c73d03e1134a960e99525ae1d236f076a0bc01273016f573b76f33ffbd47
|
||||
+ long: "npm:^5.3.2"
|
||||
+ checksum: 10c0/e1132b1ff82684280041550d720398568763ff40928def03e8bb409eaed6edd222e63372e3f52d7c19e5a8e9975333480e26950759086eb5a1f5b5c118da7bf8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs
|
||||
index 8a772a17412b..4b231e0614cc 100644
|
||||
--- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs
|
||||
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs
|
||||
@@ -43,7 +43,6 @@ async function encryptToken(token) {
|
||||
if (!accessTokenEncryptor) {
|
||||
throw new Error('Token encryption not configured, could not encrypt token')
|
||||
}
|
||||
- logger.debug({ settings: Settings.projectInviteEncryptorOptions })
|
||||
return accessTokenEncryptor.promises.encryptJson(token)
|
||||
}
|
||||
|
||||
@@ -51,7 +50,6 @@ async function decryptToken(encryptedToken) {
|
||||
if (!accessTokenEncryptor) {
|
||||
throw new Error('Token encryption not configured, could not encrypt token')
|
||||
}
|
||||
- logger.debug({ settings: Settings.projectInviteEncryptorOptions })
|
||||
return accessTokenEncryptor.promises.decryptToJson(encryptedToken)
|
||||
}
|
||||
|
||||
diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs
|
||||
index bce4a5699aea..6ace94a5a687 100644
|
||||
--- a/services/web/app/src/Features/Project/ProjectController.mjs
|
||||
+++ b/services/web/app/src/Features/Project/ProjectController.mjs
|
||||
@@ -495,6 +495,7 @@ const _ProjectController = {
|
||||
'editor-upgrade-button-relocation',
|
||||
'markdown-visual',
|
||||
'ai-disabled-collaborators',
|
||||
+ 'group-link-sharing',
|
||||
].filter(Boolean)
|
||||
|
||||
const getUserValues = async userId =>
|
||||
diff --git a/services/web/frontend/js/features/share-project-modal/components/project-access.tsx b/services/web/frontend/js/features/share-project-modal/components/project-access.tsx
|
||||
index 512b8f080a6d..847663bbf679 100644
|
||||
--- a/services/web/frontend/js/features/share-project-modal/components/project-access.tsx
|
||||
+++ b/services/web/frontend/js/features/share-project-modal/components/project-access.tsx
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '@/features/share-project-modal/components/share-project-modal'
|
||||
import { ExcludeStrict } from '@ol-types/utils'
|
||||
import getMeta from '@/utils/meta'
|
||||
+import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
|
||||
type ProjectAccessProps = {
|
||||
setIsInvitedPeopleScreen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
@@ -50,6 +51,7 @@ function ProjectAccess({
|
||||
)
|
||||
const { isProjectOwner } = useEditorContext()
|
||||
const { activeGroupSubscriptions } = getMeta('ol-user')
|
||||
+ const groupSharingEnabled = useFeatureFlag('group-link-sharing')
|
||||
|
||||
const {
|
||||
monitorRequest,
|
||||
@@ -235,7 +237,8 @@ function ProjectAccess({
|
||||
{t('only_invited_people')}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
- {activeGroupSubscriptions &&
|
||||
+ {groupSharingEnabled &&
|
||||
+ activeGroupSubscriptions &&
|
||||
activeGroupSubscriptions.map(subscription => (
|
||||
<DropdownListItem
|
||||
className="d-flex align-items-center"
|
||||
diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx
|
||||
index 4ce9b78d46c2..9e397a1bb31d 100644
|
||||
--- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx
|
||||
+++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.tsx
|
||||
@@ -7,7 +7,10 @@ import React, {
|
||||
} from 'react'
|
||||
import ShareProjectModalContent from './share-project-modal-content'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
-import { useSplitTestContext } from '@/shared/context/split-test-context'
|
||||
+import {
|
||||
+ useFeatureFlag,
|
||||
+ useSplitTestContext,
|
||||
+} from '@/shared/context/split-test-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import customLocalStorage from '@/infrastructure/local-storage'
|
||||
@@ -93,6 +96,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
const { publicAccessLevel } = project || {}
|
||||
|
||||
const { splitTestVariants } = useSplitTestContext()
|
||||
+ const isSharingUpdatesEnabled = useFeatureFlag('sharing-updates')
|
||||
|
||||
// show the new share modal if project owner
|
||||
// is over collaborator limit or has pending editors (once every 24 hours)
|
||||
@@ -147,6 +151,10 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
}, [show])
|
||||
|
||||
const handleShow = useCallback(async () => {
|
||||
+ if (!isSharingUpdatesEnabled || !isProjectOwner) {
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
if (publicAccessLevel === 'tokenBased') {
|
||||
setSharingLinkData(null)
|
||||
setProjectAccess('legacyLinkSharing')
|
||||
@@ -179,7 +187,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
'generic_something_went_wrong'
|
||||
)
|
||||
}
|
||||
- }, [publicAccessLevel, projectId])
|
||||
+ }, [publicAccessLevel, projectId, isSharingUpdatesEnabled, isProjectOwner])
|
||||
|
||||
// close the modal if not in flight
|
||||
const cancel = useCallback(() => {
|
||||
@@ -0,0 +1,20 @@
|
||||
diff --git a/yarn.lock b/yarn.lock
|
||||
index 8c85b5b32545..a5ce4cdf9bbb 100644
|
||||
--- a/yarn.lock
|
||||
+++ b/yarn.lock
|
||||
@@ -3983,12 +3983,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.1.8, @grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.14.3":
|
||||
- version: 1.14.3
|
||||
- resolution: "@grpc/grpc-js@npm:1.14.3"
|
||||
+ version: 1.14.4
|
||||
+ resolution: "@grpc/grpc-js@npm:1.14.4"
|
||||
dependencies:
|
||||
"@grpc/proto-loader": "npm:^0.8.0"
|
||||
"@js-sdsl/ordered-map": "npm:^4.4.2"
|
||||
- checksum: 10c0/f41f06a311b93cca8c472d56e21387e0f7b57bb2337a91d15ea4279bac8ec4fa0de6bd0d881201229ab800c0f0c55277911ecb850e057f20a828d0ddd623551d
|
||||
+ checksum: 10c0/0ff6395e8112ad30e8f99dbb684b997ebc3264e770b8e354f23effeedf181a380e0ecef8bca466cbbf3e9141968656144851de1da50f840a1efd9314c9812449
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
diff --git a/.yarn/patches/multer-npm-2.1.1.patch b/.yarn/patches/multer-npm-2.2.0-4ed181c78f.patch
|
||||
similarity index 77%
|
||||
rename from .yarn/patches/multer-npm-2.1.1.patch
|
||||
rename to .yarn/patches/multer-npm-2.2.0-4ed181c78f.patch
|
||||
index c74230a10835..10e08cf24327 100644
|
||||
--- a/.yarn/patches/multer-npm-2.1.1.patch
|
||||
+++ b/.yarn/patches/multer-npm-2.2.0-4ed181c78f.patch
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/lib/make-middleware.js b/lib/make-middleware.js
|
||||
-index ee50988..de77364 100644
|
||||
+index 2c15f248d4f4d221bd9e2b2e1092b8d5c46d2bdd..f2bceb4479c94e321d4882ca1fe09c3ad280cca7 100644
|
||||
--- a/lib/make-middleware.js
|
||||
+++ b/lib/make-middleware.js
|
||||
-@@ -164,7 +164,7 @@ function makeMiddleware (setup) {
|
||||
+@@ -174,7 +174,7 @@ function makeMiddleware (setup) {
|
||||
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
|
||||
|
||||
// don't attach to the files object, if there is no file
|
||||
diff --git a/package.json b/package.json
|
||||
index 2e77c8097eeb..7d7230d92ffd 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -71,7 +71,7 @@
|
||||
"body-parser@npm:1.20.4": "patch:body-parser@npm%3A1.20.4#~/.yarn/patches/body-parser-npm-1.20.4.patch",
|
||||
"cypress-multi-reporters": "patch:cypress-multi-reporters@npm%3A2.0.5#~/.yarn/patches/cypress-multi-reporters-npm-2.0.5.patch",
|
||||
"forwarded@npm:0.2.0": "patch:forwarded@npm%3A0.2.0#~/.yarn/patches/forwarded-npm-0.2.0.patch",
|
||||
- "multer@npm:2.1.1": "patch:multer@npm%3A2.1.1#~/.yarn/patches/multer-npm-2.1.1.patch",
|
||||
+ "multer@npm:2.2.0": "patch:multer@npm%3A2.2.0#~/.yarn/patches/multer-npm-2.2.0-4ed181c78f.patch",
|
||||
"node-fetch": "patch:node-fetch@npm%3A2.7.0#~/.yarn/patches/node-fetch-npm-2.7.0.patch",
|
||||
"passport-oauth2": "patch:passport-oauth2@npm%3A1.6.1#~/.yarn/patches/passport-oauth2-npm-1.6.1.patch",
|
||||
"send": "patch:send@npm%3A0.19.0#~/.yarn/patches/send-npm-0.19.0.patch",
|
||||
diff --git a/services/clsi/package.json b/services/clsi/package.json
|
||||
index e2bcc927ed6b..edb8d65bc96d 100644
|
||||
--- a/services/clsi/package.json
|
||||
+++ b/services/clsi/package.json
|
||||
@@ -32,6 +32,6 @@
|
||||
"express": "4.22.1",
|
||||
"lodash": "^4.18.1",
|
||||
- "multer": "2.1.1",
|
||||
+ "multer": "2.2.0",
|
||||
"overleaf-editor-core": "workspace:*",
|
||||
"p-limit": "^3.1.0",
|
||||
"send": "^0.19.0",
|
||||
diff --git a/services/web/package.json b/services/web/package.json
|
||||
index 1e3dd2acc41c..4ad8b3593c75 100644
|
||||
--- a/services/web/package.json
|
||||
+++ b/services/web/package.json
|
||||
@@ -154,7 +154,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"mongodb-legacy": "6.1.3",
|
||||
"mongoose": "8.22.1",
|
||||
- "multer": "2.1.1",
|
||||
+ "multer": "2.2.0",
|
||||
"nocache": "^2.1.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^8.0.5",
|
||||
diff --git a/yarn.lock b/yarn.lock
|
||||
index 371bfa1a9363..d60a273925b4 100644
|
||||
--- a/yarn.lock
|
||||
+++ b/yarn.lock
|
||||
@@ -6705,7 +6705,7 @@ __metadata:
|
||||
mocha: "npm:^11.1.0"
|
||||
mocha-junit-reporter: "npm:^2.2.1"
|
||||
mocha-multi-reporters: "npm:^1.5.1"
|
||||
- multer: "npm:2.1.1"
|
||||
+ multer: "npm:2.2.0"
|
||||
node-fetch: "npm:^2.7.0"
|
||||
nyc: "npm:^17.1.0"
|
||||
overleaf-editor-core: "workspace:*"
|
||||
@@ -7679,7 +7679,7 @@ __metadata:
|
||||
moment: "npm:^2.29.4"
|
||||
mongodb-legacy: "npm:6.1.3"
|
||||
mongoose: "npm:8.22.1"
|
||||
- multer: "npm:2.1.1"
|
||||
+ multer: "npm:2.2.0"
|
||||
nocache: "npm:^2.1.0"
|
||||
nock: "npm:^13.5.6"
|
||||
node-fetch: "npm:^2.7.0"
|
||||
@@ -25279,27 +25279,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"multer@npm:2.1.1":
|
||||
- version: 2.1.1
|
||||
- resolution: "multer@npm:2.1.1"
|
||||
+"multer@npm:2.2.0":
|
||||
+ version: 2.2.0
|
||||
+ resolution: "multer@npm:2.2.0"
|
||||
dependencies:
|
||||
append-field: "npm:^1.0.0"
|
||||
busboy: "npm:^1.6.0"
|
||||
concat-stream: "npm:^2.0.0"
|
||||
type-is: "npm:^1.6.18"
|
||||
- checksum: 10c0/2ec4e02833b20f403cfb879d4b64d2a9070d902b9deae7aef18a6faadb707d7665385456cf540aa8a6dadfe3d4c5fc8e0e7b0675b94e1077048b1125426deee6
|
||||
+ checksum: 10c0/7aa366d89042427347b6ab8e4a203405d7fe942e4a3095648a14526fcf3691d98ffc2da62abf85d8aca0a341f828168eb197b33cfdcfcce29d6ef593a5625591
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
-"multer@patch:multer@npm%3A2.1.1#~/.yarn/patches/multer-npm-2.1.1.patch":
|
||||
- version: 2.1.1
|
||||
- resolution: "multer@patch:multer@npm%3A2.1.1#~/.yarn/patches/multer-npm-2.1.1.patch::version=2.1.1&hash=0a6fed"
|
||||
+"multer@patch:multer@npm%3A2.2.0#~/.yarn/patches/multer-npm-2.2.0-4ed181c78f.patch":
|
||||
+ version: 2.2.0
|
||||
+ resolution: "multer@patch:multer@npm%3A2.2.0#~/.yarn/patches/multer-npm-2.2.0-4ed181c78f.patch::version=2.2.0&hash=cf9e1c"
|
||||
dependencies:
|
||||
append-field: "npm:^1.0.0"
|
||||
busboy: "npm:^1.6.0"
|
||||
concat-stream: "npm:^2.0.0"
|
||||
type-is: "npm:^1.6.18"
|
||||
- checksum: 10c0/647285a1c53531143d44d54ce923d3fca2037f368e581c9eecfa8b40aa5a8ef706fb1ed2a5635228002c38e31861d6209ab33a07d194c8768e7ae113b7a67976
|
||||
+ checksum: 10c0/9aa266ba976e6ee0d292d4b838d7e957086bcfb53fa170b62ca26d8737cc5eb2fc066fba0045895cbce6981a467adc0f801685b5528e8d1d09e00ca1544163dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -40,7 +40,8 @@
|
||||
"referer-parser/js-yaml": "^4.1.1",
|
||||
"sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch",
|
||||
"request/tough-cookie": "5.1.2",
|
||||
- "request/form-data": "2.5.5",
|
||||
+ "request/form-data": "2.5.6",
|
||||
+ "@types/request/form-data": "2.5.6",
|
||||
"request/qs": "6.14.1",
|
||||
"@cypress/request/qs": "6.14.1",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
@@ -0,0 +1,31 @@
|
||||
diff --git a/services/web/app/src/Features/Email/EmailSender.mjs b/services/web/app/src/Features/Email/EmailSender.mjs
|
||||
index 5eaef54202a..d5fbe797a46 100644
|
||||
--- a/services/web/app/src/Features/Email/EmailSender.mjs
|
||||
+++ b/services/web/app/src/Features/Email/EmailSender.mjs
|
||||
@@ -3,7 +3,7 @@ import logger from '@overleaf/logger'
|
||||
import metrics from '@overleaf/metrics'
|
||||
import Settings from '@overleaf/settings'
|
||||
import nodemailer from 'nodemailer'
|
||||
-import * as aws from '@aws-sdk/client-ses'
|
||||
+import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'
|
||||
import OError from '@overleaf/o-error'
|
||||
import { RateLimiter } from '../../infrastructure/RateLimiter.mjs'
|
||||
import _ from 'lodash'
|
||||
@@ -30,15 +30,14 @@ function getClient() {
|
||||
const emailParameters = EMAIL_SETTINGS.parameters
|
||||
if (emailParameters.AWSAccessKeyID || EMAIL_SETTINGS.driver === 'ses') {
|
||||
logger.debug('using aws ses for email')
|
||||
- const ses = new aws.SESClient({
|
||||
- apiVersion: '2010-12-01',
|
||||
+ const sesClient = new SESv2Client({
|
||||
region: emailParameters.region,
|
||||
credentials: {
|
||||
accessKeyId: emailParameters.AWSAccessKeyID,
|
||||
secretAccessKey: emailParameters.AWSSecretKey,
|
||||
},
|
||||
})
|
||||
- client = nodemailer.createTransport({ SES: { ses, aws } })
|
||||
+ client = nodemailer.createTransport({ SES: { sesClient, SendEmailCommand } })
|
||||
} else if (emailParameters.sendgridApiKey) {
|
||||
throw new OError(
|
||||
'sendgridApiKey configuration option is deprecated, use SMTP instead'
|
||||
@@ -0,0 +1,299 @@
|
||||
diff --git a/services/document-updater/app/js/ProjectFlusher.js b/services/document-updater/app/js/ProjectFlusher.js
|
||||
index d6b8c5b13304..fb6687417a64 100644
|
||||
--- a/services/document-updater/app/js/ProjectFlusher.js
|
||||
+++ b/services/document-updater/app/js/ProjectFlusher.js
|
||||
@@ -96,6 +96,7 @@ async function flushAllProjects(options) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
+ _getKeys,
|
||||
_extractIds,
|
||||
promises: {
|
||||
flushAllProjects,
|
||||
diff --git a/services/document-updater/scripts/flush_docs_with_pending_updates.js b/services/document-updater/scripts/flush_docs_with_pending_updates.js
|
||||
new file mode 100644
|
||||
index 000000000000..1be0f3c6acf1
|
||||
--- /dev/null
|
||||
+++ b/services/document-updater/scripts/flush_docs_with_pending_updates.js
|
||||
@@ -0,0 +1,281 @@
|
||||
+// Flush docs that were left with a stuck PendingUpdates queue in redis after
|
||||
+// their project was flushed (and possibly hard deleted since).
|
||||
+//
|
||||
+// For each doc with a PendingUpdates queue, the project id is looked up in
|
||||
+// db.docs, then the pending updates are processed and the doc is flushed and
|
||||
+// removed from redis. Docs whose project cannot be found (e.g. hard deleted
|
||||
+// projects) are logged and can be discarded with --discard-hard-deleted.
|
||||
+
|
||||
+const minimist = require('minimist')
|
||||
+const Settings = require('@overleaf/settings')
|
||||
+const OError = require('@overleaf/o-error')
|
||||
+const DocumentManager = require('../app/js/DocumentManager')
|
||||
+const HistoryManager = require('../app/js/HistoryManager')
|
||||
+const ProjectFlusher = require('../app/js/ProjectFlusher')
|
||||
+const { rclient } = require('../app/js/RedisManager')
|
||||
+const { db, ObjectId, mongoClient } = require('../app/js/mongodb')
|
||||
+
|
||||
+const keys = Settings.redis.documentupdater.key_schema
|
||||
+
|
||||
+const summary = {
|
||||
+ totalDocs: 0,
|
||||
+ flushedDocs: 0,
|
||||
+ missingDocs: 0,
|
||||
+ discardedDocs: 0,
|
||||
+ erroredDocs: 0,
|
||||
+}
|
||||
+
|
||||
+function printSummary() {
|
||||
+ console.log(
|
||||
+ `Processed ${summary.totalDocs} docs with pending updates: flushed=${summary.flushedDocs} missing=${summary.missingDocs} discarded=${summary.discardedDocs} errored=${summary.erroredDocs}`
|
||||
+ )
|
||||
+}
|
||||
+
|
||||
+function usage() {
|
||||
+ console.error(`
|
||||
+Usage: node scripts/flush_docs_with_pending_updates.js [options]
|
||||
+
|
||||
+Options:
|
||||
+ --estimated-docs Required. Estimated number of documents in db.docs,
|
||||
+ used as a safety check that the right database is
|
||||
+ consulted for the doc lookups. The script aborts
|
||||
+ unless the estimated collection count is within 20%
|
||||
+ of this value.
|
||||
+ --log-unflushed When the db.docs lookup failed for a doc, log its
|
||||
+ pending updates and, if the doc has unflushed
|
||||
+ changes, all its doc keys
|
||||
+ (default: true, disable via --log-unflushed=false)
|
||||
+ --discard-hard-deleted Discard the stuck queue and the other doc keys of
|
||||
+ docs whose db.docs lookup failed (default: false)
|
||||
+ --docId Process a single doc instead of scanning redis
|
||||
+ --projectId Override the project id for --docId, skipping the
|
||||
+ db.docs lookup
|
||||
+ --dry-run Only scan and log, without flushing, deleting or
|
||||
+ discarding anything (default: false)
|
||||
+ --limit Maximum number of PendingUpdates keys to scan
|
||||
+ (default: 100000)
|
||||
+ --help Show this help message
|
||||
+`)
|
||||
+}
|
||||
+
|
||||
+async function getDocIds(options) {
|
||||
+ if (options.docId) {
|
||||
+ return [options.docId]
|
||||
+ }
|
||||
+ const docKeys = await ProjectFlusher._getKeys(
|
||||
+ keys.pendingUpdates({ doc_id: '*' }),
|
||||
+ options.limit
|
||||
+ )
|
||||
+ return ProjectFlusher._extractIds(docKeys)
|
||||
+}
|
||||
+
|
||||
+async function processDoc(docId, options) {
|
||||
+ let projectId = options.projectId
|
||||
+ if (!projectId) {
|
||||
+ const doc = await db.docs.findOne(
|
||||
+ { _id: new ObjectId(docId) },
|
||||
+ { projection: { project_id: 1 } }
|
||||
+ )
|
||||
+ projectId = doc?.project_id?.toString()
|
||||
+ }
|
||||
+
|
||||
+ if (projectId) {
|
||||
+ await flushDoc(projectId, docId, options)
|
||||
+ } else {
|
||||
+ await handleMissingDoc(docId, options)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+async function flushDoc(projectId, docId, options) {
|
||||
+ const queueLength = await rclient.llen(keys.pendingUpdates({ doc_id: docId }))
|
||||
+ if (options.dryRun) {
|
||||
+ console.log(
|
||||
+ `Would flush doc ${docId} in project ${projectId} with ${queueLength} pending updates`
|
||||
+ )
|
||||
+ return
|
||||
+ }
|
||||
+ console.log(
|
||||
+ `Flushing doc ${docId} in project ${projectId} with ${queueLength} pending updates`
|
||||
+ )
|
||||
+ await DocumentManager.promises.flushAndDeleteDocWithLock(projectId, docId, {})
|
||||
+ await HistoryManager.promises.flushProjectChanges(projectId, {})
|
||||
+ const remaining = await rclient.llen(keys.pendingUpdates({ doc_id: docId }))
|
||||
+ if (remaining > 0) {
|
||||
+ console.error(
|
||||
+ `Doc ${docId} in project ${projectId} still has ${remaining} pending updates after flushing`
|
||||
+ )
|
||||
+ summary.erroredDocs++
|
||||
+ } else {
|
||||
+ summary.flushedDocs++
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+async function handleMissingDoc(docId, options) {
|
||||
+ summary.missingDocs++
|
||||
+ let message = `Doc ${docId} not found in db.docs`
|
||||
+
|
||||
+ if (options.logUnflushed) {
|
||||
+ // Log the pending updates: they are the reason this doc is processed and
|
||||
+ // would be lost when discarding it. These can exist independently of any
|
||||
+ // unflushed doc content.
|
||||
+ const pendingUpdates = await rclient.lrange(
|
||||
+ keys.pendingUpdates({ doc_id: docId }),
|
||||
+ 0,
|
||||
+ -1
|
||||
+ )
|
||||
+ message += `, pending updates: ${JSON.stringify(pendingUpdates)}`
|
||||
+
|
||||
+ // In addition, log the unflushed doc content if there is any.
|
||||
+ const unflushedTime = await rclient.get(
|
||||
+ keys.unflushedTime({ doc_id: docId })
|
||||
+ )
|
||||
+ if (unflushedTime) {
|
||||
+ const doc = {
|
||||
+ unflushedTime,
|
||||
+ projectId: await rclient.get(keys.projectKey({ doc_id: docId })),
|
||||
+ lines: await rclient.get(keys.docLines({ doc_id: docId })),
|
||||
+ version: await rclient.get(keys.docVersion({ doc_id: docId })),
|
||||
+ hash: await rclient.get(keys.docHash({ doc_id: docId })),
|
||||
+ ranges: await rclient.get(keys.ranges({ doc_id: docId })),
|
||||
+ pathname: await rclient.get(keys.pathname({ doc_id: docId })),
|
||||
+ projectHistoryId: await rclient.get(
|
||||
+ keys.projectHistoryId({ doc_id: docId })
|
||||
+ ),
|
||||
+ lastUpdatedAt: await rclient.get(keys.lastUpdatedAt({ doc_id: docId })),
|
||||
+ lastUpdatedBy: await rclient.get(keys.lastUpdatedBy({ doc_id: docId })),
|
||||
+ resolvedCommentIds: await rclient.smembers(
|
||||
+ keys.resolvedCommentIds({ doc_id: docId })
|
||||
+ ),
|
||||
+ }
|
||||
+ message += `, unflushed doc keys: ${JSON.stringify(doc)}`
|
||||
+ }
|
||||
+ }
|
||||
+ console.warn(message)
|
||||
+
|
||||
+ if (options.discardHardDeleted && !options.dryRun) {
|
||||
+ await discardDoc(docId)
|
||||
+ console.warn(`Discarded redis keys of doc ${docId}`)
|
||||
+ summary.discardedDocs++
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+async function discardDoc(docId) {
|
||||
+ // Purge the project-level entries first: in case the script crashes mid-way,
|
||||
+ // a re-run can still resolve the project id from the ProjectId doc key.
|
||||
+ const projectId = await rclient.get(keys.projectKey({ doc_id: docId }))
|
||||
+ if (projectId) {
|
||||
+ await rclient.srem(keys.docsInProject({ project_id: projectId }), docId)
|
||||
+ }
|
||||
+ await rclient.srem(keys.historyRangesSupport(), docId)
|
||||
+ await rclient.del(
|
||||
+ keys.pendingUpdates({ doc_id: docId }),
|
||||
+ keys.docLines({ doc_id: docId }),
|
||||
+ keys.projectKey({ doc_id: docId }),
|
||||
+ keys.docVersion({ doc_id: docId }),
|
||||
+ keys.docHash({ doc_id: docId }),
|
||||
+ keys.ranges({ doc_id: docId }),
|
||||
+ keys.pathname({ doc_id: docId }),
|
||||
+ keys.projectHistoryId({ doc_id: docId }),
|
||||
+ keys.unflushedTime({ doc_id: docId }),
|
||||
+ keys.lastUpdatedBy({ doc_id: docId }),
|
||||
+ keys.lastUpdatedAt({ doc_id: docId }),
|
||||
+ keys.resolvedCommentIds({ doc_id: docId }),
|
||||
+ keys.docOps({ doc_id: docId })
|
||||
+ )
|
||||
+}
|
||||
+
|
||||
+async function main() {
|
||||
+ const argv = minimist(process.argv.slice(2), {
|
||||
+ boolean: ['log-unflushed', 'discard-hard-deleted', 'dry-run', 'help'],
|
||||
+ string: ['docId', 'projectId'],
|
||||
+ default: { limit: 100_000, 'log-unflushed': true },
|
||||
+ })
|
||||
+
|
||||
+ if (argv.help) {
|
||||
+ usage()
|
||||
+ return 0
|
||||
+ }
|
||||
+
|
||||
+ const options = {
|
||||
+ estimatedDocs: argv['estimated-docs'],
|
||||
+ logUnflushed: argv['log-unflushed'],
|
||||
+ discardHardDeleted: argv['discard-hard-deleted'],
|
||||
+ docId: argv.docId,
|
||||
+ projectId: argv.projectId,
|
||||
+ dryRun: argv['dry-run'],
|
||||
+ limit: argv.limit,
|
||||
+ }
|
||||
+
|
||||
+ if (!Number.isInteger(options.estimatedDocs) || options.estimatedDocs <= 0) {
|
||||
+ console.error('--estimated-docs must be set to a positive integer')
|
||||
+ usage()
|
||||
+ return 1
|
||||
+ }
|
||||
+ if (options.docId && !ObjectId.isValid(options.docId)) {
|
||||
+ console.error('--docId is not a valid object id')
|
||||
+ usage()
|
||||
+ return 1
|
||||
+ }
|
||||
+ if (options.projectId && !options.docId) {
|
||||
+ console.error('--projectId can only be used together with --docId')
|
||||
+ usage()
|
||||
+ return 1
|
||||
+ }
|
||||
+ if (options.projectId && !ObjectId.isValid(options.projectId)) {
|
||||
+ console.error('--projectId is not a valid object id')
|
||||
+ usage()
|
||||
+ return 1
|
||||
+ }
|
||||
+
|
||||
+ const actualDocs = await db.docs.estimatedDocumentCount()
|
||||
+ if (
|
||||
+ actualDocs < 0.8 * options.estimatedDocs ||
|
||||
+ actualDocs > 1.2 * options.estimatedDocs
|
||||
+ ) {
|
||||
+ console.error(
|
||||
+ `Aborting: the estimated db.docs count of ${actualDocs} is not within 20% of --estimated-docs=${options.estimatedDocs}, is this the right database?`
|
||||
+ )
|
||||
+ return 1
|
||||
+ }
|
||||
+
|
||||
+ const docIds = await getDocIds(options)
|
||||
+ const limitReached = !options.docId && docIds.length >= options.limit
|
||||
+ for (const docId of docIds) {
|
||||
+ summary.totalDocs++
|
||||
+ try {
|
||||
+ await processDoc(docId, options)
|
||||
+ } catch (error) {
|
||||
+ console.error(`Error processing doc ${docId}`)
|
||||
+ console.error(OError.getFullStack(error))
|
||||
+ summary.erroredDocs++
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (limitReached) {
|
||||
+ console.error(
|
||||
+ `Hit the scan limit of ${options.limit} keys; there may be more docs with pending updates. Re-run to process the rest.`
|
||||
+ )
|
||||
+ return 2
|
||||
+ }
|
||||
+ if (options.dryRun) {
|
||||
+ return summary.totalDocs > 0 ? 2 : 0
|
||||
+ }
|
||||
+ if (summary.erroredDocs > 0 || summary.missingDocs > summary.discardedDocs) {
|
||||
+ return 2
|
||||
+ }
|
||||
+ return 0
|
||||
+}
|
||||
+
|
||||
+main()
|
||||
+ .then(code => {
|
||||
+ printSummary()
|
||||
+ rclient.quit()
|
||||
+ mongoClient.close()
|
||||
+ process.exit(code)
|
||||
+ })
|
||||
+ .catch(error => {
|
||||
+ printSummary()
|
||||
+ console.error(OError.getFullStack(error))
|
||||
+ console.error(OError.getFullInfo(error))
|
||||
+ process.exit(1)
|
||||
+ })
|
||||
Reference in New Issue
Block a user