mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #13875 from overleaf/msm-hotfix-4-0-4
[CE/SP] Hotfixes 3.5.9 / 4.0.4 GitOrigin-RevId: 89f5b3832b2a069a2ee31b2ddc5cde9a4bbb5464
This commit is contained in:
parent
38d1e749fb
commit
22e232242c
4 changed files with 798 additions and 0 deletions
10
server-ce/hotfix/3.5.9/Dockerfile
Normal file
10
server-ce/hotfix/3.5.9/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
FROM sharelatex/sharelatex:3.5.8
|
||||
|
||||
# Node update
|
||||
RUN curl -sSL https://deb.nodesource.com/setup_16.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Patch: fetch access tokens via endpoint
|
||||
COPY pr_13485.patch .
|
||||
RUN patch -p0 < pr_13485.patch
|
||||
RUN node genScript compile | bash
|
389
server-ce/hotfix/3.5.9/pr_13485.patch
Normal file
389
server-ce/hotfix/3.5.9/pr_13485.patch
Normal file
|
@ -0,0 +1,389 @@
|
|||
--- services/web/app/src/Features/Collaborators/CollaboratorsController.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsController.js
|
||||
@@ -11,6 +11,7 @@ const Errors = require('../Errors/Errors')
|
||||
const logger = require('@overleaf/logger')
|
||||
const { expressify } = require('../../util/promises')
|
||||
const { hasAdminAccess } = require('../Helpers/AdminAuthorizationHelper')
|
||||
+const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
|
||||
|
||||
module.exports = {
|
||||
removeUserFromProject: expressify(removeUserFromProject),
|
||||
@@ -18,6 +19,7 @@ module.exports = {
|
||||
getAllMembers: expressify(getAllMembers),
|
||||
setCollaboratorInfo: expressify(setCollaboratorInfo),
|
||||
transferOwnership: expressify(transferOwnership),
|
||||
+ getShareTokens: expressify(getShareTokens),
|
||||
}
|
||||
|
||||
async function removeUserFromProject(req, res, next) {
|
||||
@@ -114,3 +116,37 @@ async function _removeUserIdFromProject(projectId, userId) {
|
||||
)
|
||||
await TagsHandler.promises.removeProjectFromAllTags(userId, projectId)
|
||||
}
|
||||
+
|
||||
+async function getShareTokens(req, res) {
|
||||
+ const projectId = req.params.Project_id
|
||||
+ const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
+
|
||||
+ let tokens
|
||||
+ if (userId) {
|
||||
+ tokens = await CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
+ ObjectId(userId),
|
||||
+ ObjectId(projectId)
|
||||
+ )
|
||||
+ } else {
|
||||
+ // anonymous access, the token is already available in the session
|
||||
+ const readOnly = TokenAccessHandler.getRequestToken(req, projectId)
|
||||
+ tokens = { readOnly }
|
||||
+ }
|
||||
+ if (!tokens) {
|
||||
+ return res.sendStatus(403)
|
||||
+ }
|
||||
+
|
||||
+ if (tokens.readOnly || tokens.readAndWrite) {
|
||||
+ logger.info(
|
||||
+ {
|
||||
+ projectId,
|
||||
+ userId: userId || 'anonymous',
|
||||
+ ip: req.ip,
|
||||
+ tokens: Object.keys(tokens),
|
||||
+ },
|
||||
+ 'project tokens accessed'
|
||||
+ )
|
||||
+ }
|
||||
+
|
||||
+ res.json(tokens)
|
||||
+}
|
||||
--- services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
|
||||
@@ -25,6 +25,7 @@ module.exports = {
|
||||
getInvitedCollaboratorCount: callbackify(getInvitedCollaboratorCount),
|
||||
getProjectsUserIsMemberOf: callbackify(getProjectsUserIsMemberOf),
|
||||
isUserInvitedMemberOfProject: callbackify(isUserInvitedMemberOfProject),
|
||||
+ getPublicShareTokens: callbackify(getPublicShareTokens),
|
||||
userIsTokenMember: callbackify(userIsTokenMember),
|
||||
getAllInvitedMembers: callbackify(getAllInvitedMembers),
|
||||
promises: {
|
||||
@@ -37,6 +38,7 @@ module.exports = {
|
||||
getInvitedCollaboratorCount,
|
||||
getProjectsUserIsMemberOf,
|
||||
isUserInvitedMemberOfProject,
|
||||
+ getPublicShareTokens,
|
||||
userIsTokenMember,
|
||||
getAllInvitedMembers,
|
||||
},
|
||||
@@ -133,6 +135,40 @@ async function isUserInvitedMemberOfProject(userId, projectId) {
|
||||
return false
|
||||
}
|
||||
|
||||
+async function getPublicShareTokens(userId, projectId) {
|
||||
+ const memberInfo = await Project.findOne(
|
||||
+ {
|
||||
+ _id: projectId,
|
||||
+ },
|
||||
+ {
|
||||
+ isOwner: { $eq: ['$owner_ref', userId] },
|
||||
+ hasTokenReadOnlyAccess: {
|
||||
+ $and: [
|
||||
+ { $in: [userId, '$tokenAccessReadOnly_refs'] },
|
||||
+ { $eq: ['$publicAccesLevel', PublicAccessLevels.TOKEN_BASED] },
|
||||
+ ],
|
||||
+ },
|
||||
+ tokens: 1,
|
||||
+ }
|
||||
+ )
|
||||
+ .lean()
|
||||
+ .exec()
|
||||
+
|
||||
+ if (!memberInfo) {
|
||||
+ return null
|
||||
+ }
|
||||
+
|
||||
+ if (memberInfo.isOwner) {
|
||||
+ return memberInfo.tokens
|
||||
+ } else if (memberInfo.hasTokenReadOnlyAccess) {
|
||||
+ return {
|
||||
+ readOnly: memberInfo.tokens.readOnly,
|
||||
+ }
|
||||
+ } else {
|
||||
+ return {}
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
async function getProjectsUserIsMemberOf(userId, fields) {
|
||||
const limit = pLimit(2)
|
||||
const [readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly] =
|
||||
--- services/web/app/src/Features/Collaborators/CollaboratorsRouter.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsRouter.js
|
||||
@@ -22,6 +22,10 @@ const rateLimiters = {
|
||||
points: 200,
|
||||
duration: 60 * 10,
|
||||
}),
|
||||
+ getProjectTokens: new RateLimiter('get-project-tokens', {
|
||||
+ points: 200,
|
||||
+ duration: 60 * 10,
|
||||
+ }),
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -139,5 +143,12 @@ module.exports = {
|
||||
CollaboratorsInviteController.acceptInvite,
|
||||
AnalyticsRegistrationSourceMiddleware.clearSource()
|
||||
)
|
||||
+
|
||||
+ webRouter.get(
|
||||
+ '/project/:Project_id/tokens',
|
||||
+ RateLimiterMiddleware.rateLimit(rateLimiters.getProjectTokens),
|
||||
+ AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
+ CollaboratorsController.getShareTokens
|
||||
+ )
|
||||
},
|
||||
}
|
||||
--- services/web/app/src/Features/Editor/EditorController.js
|
||||
+++ services/web/app/src/Features/Editor/EditorController.js
|
||||
@@ -581,20 +581,7 @@ const EditorController = {
|
||||
{ newAccessLevel }
|
||||
)
|
||||
if (newAccessLevel === PublicAccessLevels.TOKEN_BASED) {
|
||||
- ProjectDetailsHandler.ensureTokensArePresent(
|
||||
- projectId,
|
||||
- function (err, tokens) {
|
||||
- if (err) {
|
||||
- return callback(err)
|
||||
- }
|
||||
- EditorRealTimeController.emitToRoom(
|
||||
- projectId,
|
||||
- 'project:tokens:changed',
|
||||
- { tokens }
|
||||
- )
|
||||
- callback()
|
||||
- }
|
||||
- )
|
||||
+ ProjectDetailsHandler.ensureTokensArePresent(projectId, callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
--- services/web/app/src/Features/Editor/EditorHttpController.js
|
||||
+++ services/web/app/src/Features/Editor/EditorHttpController.js
|
||||
@@ -67,8 +67,6 @@ async function joinProject(req, res, next) {
|
||||
if (!project) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
- // Hide access tokens if this is not the project owner
|
||||
- TokenAccessHandler.protectTokens(project, privilegeLevel)
|
||||
// Hide sensitive data if the user is restricted
|
||||
if (isRestrictedUser) {
|
||||
project.owner = { _id: project.owner._id }
|
||||
--- services/web/app/src/Features/Project/ProjectController.js
|
||||
+++ services/web/app/src/Features/Project/ProjectController.js
|
||||
@@ -343,7 +343,7 @@ const ProjectController = {
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
ProjectGetter.findAllUsersProjects(
|
||||
userId,
|
||||
- 'name lastUpdated publicAccesLevel archived trashed owner_ref tokens',
|
||||
+ 'name lastUpdated publicAccesLevel archived trashed owner_ref',
|
||||
(err, projects) => {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
@@ -1072,7 +1072,6 @@ const ProjectController = {
|
||||
// If a project is simultaneously trashed and archived, we will consider it archived but not trashed.
|
||||
const trashed = ProjectHelper.isTrashed(project, userId) && !archived
|
||||
|
||||
- TokenAccessHandler.protectTokens(project, accessLevel)
|
||||
const model = {
|
||||
id: project._id,
|
||||
name: project.name,
|
||||
--- services/web/app/src/Features/Project/ProjectDetailsHandler.js
|
||||
+++ services/web/app/src/Features/Project/ProjectDetailsHandler.js
|
||||
@@ -207,14 +207,13 @@ async function ensureTokensArePresent(projectId) {
|
||||
project.tokens.readOnly != null &&
|
||||
project.tokens.readAndWrite != null
|
||||
) {
|
||||
- return project.tokens
|
||||
+ return
|
||||
}
|
||||
await _generateTokens(project)
|
||||
await Project.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { tokens: project.tokens } }
|
||||
).exec()
|
||||
- return project.tokens
|
||||
}
|
||||
|
||||
async function clearTokens(projectId) {
|
||||
--- services/web/app/src/Features/Project/ProjectEditorHandler.js
|
||||
+++ services/web/app/src/Features/Project/ProjectEditorHandler.js
|
||||
@@ -49,7 +49,6 @@ module.exports = ProjectEditorHandler = {
|
||||
),
|
||||
members: [],
|
||||
invites,
|
||||
- tokens: project.tokens,
|
||||
imageName:
|
||||
project.imageName != null
|
||||
? Path.basename(project.imageName)
|
||||
--- services/web/app/src/Features/TokenAccess/TokenAccessHandler.js
|
||||
+++ services/web/app/src/Features/TokenAccess/TokenAccessHandler.js
|
||||
@@ -246,22 +246,6 @@ const TokenAccessHandler = {
|
||||
})
|
||||
},
|
||||
|
||||
- protectTokens(project, privilegeLevel) {
|
||||
- if (!project || !project.tokens) {
|
||||
- return
|
||||
- }
|
||||
- if (privilegeLevel === PrivilegeLevels.OWNER) {
|
||||
- return
|
||||
- }
|
||||
- if (privilegeLevel !== PrivilegeLevels.READ_AND_WRITE) {
|
||||
- project.tokens.readAndWrite = ''
|
||||
- project.tokens.readAndWritePrefix = ''
|
||||
- }
|
||||
- if (privilegeLevel !== PrivilegeLevels.READ_ONLY) {
|
||||
- project.tokens.readOnly = ''
|
||||
- }
|
||||
- },
|
||||
-
|
||||
getV1DocPublishedInfo(token, callback) {
|
||||
// default to allowing access
|
||||
if (!Settings.apis.v1 || !Settings.apis.v1.url) {
|
||||
@@ -304,7 +288,6 @@ TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
|
||||
'_projectFindOne',
|
||||
'grantSessionTokenAccess',
|
||||
'getRequestToken',
|
||||
- 'protectTokens',
|
||||
],
|
||||
multiResult: {
|
||||
validateTokenForAnonymousAccess: ['isValidReadAndWrite', 'isValidReadOnly'],
|
||||
--- services/web/frontend/js/features/share-project-modal/components/link-sharing.js
|
||||
+++ services/web/frontend/js/features/share-project-modal/components/link-sharing.js
|
||||
@@ -1,4 +1,4 @@
|
||||
-import { useCallback, useState } from 'react'
|
||||
+import { useCallback, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
@@ -10,6 +10,8 @@ import CopyLink from '../../../shared/components/copy-link'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { useUserContext } from '../../../shared/context/user-context'
|
||||
+import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
+import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
|
||||
export default function LinkSharing({ canAddCollaborators }) {
|
||||
const [inflight, setInflight] = useState(false)
|
||||
@@ -27,8 +29,7 @@ export default function LinkSharing({ canAddCollaborators }) {
|
||||
)
|
||||
.then(() => {
|
||||
// NOTE: not calling `updateProject` here as it receives data via
|
||||
- // project:publicAccessLevel:changed and project:tokens:changed
|
||||
- // over the websocket connection
|
||||
+ // project:publicAccessLevel:changed over the websocket connection
|
||||
// TODO: eventTracking.sendMB('project-make-token-based') when newPublicAccessLevel is 'tokenBased'
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -106,7 +107,17 @@ PrivateSharing.propTypes = {
|
||||
}
|
||||
|
||||
function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
||||
- const { tokens } = useProjectContext()
|
||||
+ const { _id: projectId } = useProjectContext()
|
||||
+
|
||||
+ const [tokens, setTokens] = useState(null)
|
||||
+
|
||||
+ const { signal } = useAbortController()
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ getJSON(`/project/${projectId}/tokens`, { signal })
|
||||
+ .then(data => setTokens(data))
|
||||
+ .catch(error => console.error(error))
|
||||
+ }, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
@@ -194,7 +205,17 @@ LegacySharing.propTypes = {
|
||||
}
|
||||
|
||||
export function ReadOnlyTokenLink() {
|
||||
- const { tokens } = useProjectContext()
|
||||
+ const { _id: projectId } = useProjectContext()
|
||||
+
|
||||
+ const [tokens, setTokens] = useState(null)
|
||||
+
|
||||
+ const { signal } = useAbortController()
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ getJSON(`/project/${projectId}/tokens`, { signal })
|
||||
+ .then(data => setTokens(data))
|
||||
+ .catch(error => console.error(error))
|
||||
+ }, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
--- services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js
|
||||
+++ services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js
|
||||
@@ -31,16 +31,6 @@ export default App.controller(
|
||||
})
|
||||
}
|
||||
|
||||
- /* tokens */
|
||||
-
|
||||
- ide.socket.on('project:tokens:changed', data => {
|
||||
- if (data.tokens != null) {
|
||||
- $scope.$applyAsync(() => {
|
||||
- $scope.project.tokens = data.tokens
|
||||
- })
|
||||
- }
|
||||
- })
|
||||
-
|
||||
ide.socket.on('project:membership:changed', data => {
|
||||
if (data.members) {
|
||||
listProjectMembers($scope.project._id)
|
||||
--- services/web/frontend/js/shared/context/mock/mock-ide.js
|
||||
+++ services/web/frontend/js/shared/context/mock/mock-ide.js
|
||||
@@ -27,10 +27,6 @@ export const getMockIde = () => {
|
||||
zotero: false,
|
||||
},
|
||||
publicAccessLevel: '',
|
||||
- tokens: {
|
||||
- readOnly: '',
|
||||
- readAndWrite: '',
|
||||
- },
|
||||
owner: {
|
||||
_id: '',
|
||||
email: '',
|
||||
--- services/web/frontend/js/shared/context/project-context.js
|
||||
+++ services/web/frontend/js/shared/context/project-context.js
|
||||
@@ -28,10 +28,6 @@ export const projectShape = {
|
||||
versioning: PropTypes.bool,
|
||||
}),
|
||||
publicAccessLevel: PropTypes.string,
|
||||
- tokens: PropTypes.shape({
|
||||
- readOnly: PropTypes.string,
|
||||
- readAndWrite: PropTypes.string,
|
||||
- }),
|
||||
owner: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
@@ -81,7 +77,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccesLevel: publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
} = project || projectFallback
|
||||
|
||||
@@ -94,7 +89,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
}
|
||||
}, [
|
||||
@@ -105,7 +99,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
])
|
10
server-ce/hotfix/4.0.4/Dockerfile
Normal file
10
server-ce/hotfix/4.0.4/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
FROM sharelatex/sharelatex:4.0.3
|
||||
|
||||
# Node update
|
||||
RUN curl -sSL https://deb.nodesource.com/setup_16.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Patch: fetch access tokens via endpoint
|
||||
COPY pr_13485.patch .
|
||||
RUN patch -p0 < pr_13485.patch
|
||||
RUN node genScript compile | bash
|
389
server-ce/hotfix/4.0.4/pr_13485.patch
Normal file
389
server-ce/hotfix/4.0.4/pr_13485.patch
Normal file
|
@ -0,0 +1,389 @@
|
|||
--- services/web/app/src/Features/Collaborators/CollaboratorsController.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsController.js
|
||||
@@ -11,6 +11,7 @@ const Errors = require('../Errors/Errors')
|
||||
const logger = require('@overleaf/logger')
|
||||
const { expressify } = require('../../util/promises')
|
||||
const { hasAdminAccess } = require('../Helpers/AdminAuthorizationHelper')
|
||||
+const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
|
||||
|
||||
module.exports = {
|
||||
removeUserFromProject: expressify(removeUserFromProject),
|
||||
@@ -18,6 +19,7 @@ module.exports = {
|
||||
getAllMembers: expressify(getAllMembers),
|
||||
setCollaboratorInfo: expressify(setCollaboratorInfo),
|
||||
transferOwnership: expressify(transferOwnership),
|
||||
+ getShareTokens: expressify(getShareTokens),
|
||||
}
|
||||
|
||||
async function removeUserFromProject(req, res, next) {
|
||||
@@ -114,3 +116,37 @@ async function _removeUserIdFromProject(projectId, userId) {
|
||||
)
|
||||
await TagsHandler.promises.removeProjectFromAllTags(userId, projectId)
|
||||
}
|
||||
+
|
||||
+async function getShareTokens(req, res) {
|
||||
+ const projectId = req.params.Project_id
|
||||
+ const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
+
|
||||
+ let tokens
|
||||
+ if (userId) {
|
||||
+ tokens = await CollaboratorsGetter.promises.getPublicShareTokens(
|
||||
+ ObjectId(userId),
|
||||
+ ObjectId(projectId)
|
||||
+ )
|
||||
+ } else {
|
||||
+ // anonymous access, the token is already available in the session
|
||||
+ const readOnly = TokenAccessHandler.getRequestToken(req, projectId)
|
||||
+ tokens = { readOnly }
|
||||
+ }
|
||||
+ if (!tokens) {
|
||||
+ return res.sendStatus(403)
|
||||
+ }
|
||||
+
|
||||
+ if (tokens.readOnly || tokens.readAndWrite) {
|
||||
+ logger.info(
|
||||
+ {
|
||||
+ projectId,
|
||||
+ userId: userId || 'anonymous',
|
||||
+ ip: req.ip,
|
||||
+ tokens: Object.keys(tokens),
|
||||
+ },
|
||||
+ 'project tokens accessed'
|
||||
+ )
|
||||
+ }
|
||||
+
|
||||
+ res.json(tokens)
|
||||
+}
|
||||
--- services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
|
||||
@@ -25,6 +25,7 @@ module.exports = {
|
||||
getInvitedCollaboratorCount: callbackify(getInvitedCollaboratorCount),
|
||||
getProjectsUserIsMemberOf: callbackify(getProjectsUserIsMemberOf),
|
||||
isUserInvitedMemberOfProject: callbackify(isUserInvitedMemberOfProject),
|
||||
+ getPublicShareTokens: callbackify(getPublicShareTokens),
|
||||
userIsTokenMember: callbackify(userIsTokenMember),
|
||||
getAllInvitedMembers: callbackify(getAllInvitedMembers),
|
||||
promises: {
|
||||
@@ -37,6 +38,7 @@ module.exports = {
|
||||
getInvitedCollaboratorCount,
|
||||
getProjectsUserIsMemberOf,
|
||||
isUserInvitedMemberOfProject,
|
||||
+ getPublicShareTokens,
|
||||
userIsTokenMember,
|
||||
getAllInvitedMembers,
|
||||
},
|
||||
@@ -133,6 +135,40 @@ async function isUserInvitedMemberOfProject(userId, projectId) {
|
||||
return false
|
||||
}
|
||||
|
||||
+async function getPublicShareTokens(userId, projectId) {
|
||||
+ const memberInfo = await Project.findOne(
|
||||
+ {
|
||||
+ _id: projectId,
|
||||
+ },
|
||||
+ {
|
||||
+ isOwner: { $eq: ['$owner_ref', userId] },
|
||||
+ hasTokenReadOnlyAccess: {
|
||||
+ $and: [
|
||||
+ { $in: [userId, '$tokenAccessReadOnly_refs'] },
|
||||
+ { $eq: ['$publicAccesLevel', PublicAccessLevels.TOKEN_BASED] },
|
||||
+ ],
|
||||
+ },
|
||||
+ tokens: 1,
|
||||
+ }
|
||||
+ )
|
||||
+ .lean()
|
||||
+ .exec()
|
||||
+
|
||||
+ if (!memberInfo) {
|
||||
+ return null
|
||||
+ }
|
||||
+
|
||||
+ if (memberInfo.isOwner) {
|
||||
+ return memberInfo.tokens
|
||||
+ } else if (memberInfo.hasTokenReadOnlyAccess) {
|
||||
+ return {
|
||||
+ readOnly: memberInfo.tokens.readOnly,
|
||||
+ }
|
||||
+ } else {
|
||||
+ return {}
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
async function getProjectsUserIsMemberOf(userId, fields) {
|
||||
const limit = pLimit(2)
|
||||
const [readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly] =
|
||||
--- services/web/app/src/Features/Collaborators/CollaboratorsRouter.js
|
||||
+++ services/web/app/src/Features/Collaborators/CollaboratorsRouter.js
|
||||
@@ -22,6 +22,10 @@ const rateLimiters = {
|
||||
points: 200,
|
||||
duration: 60 * 10,
|
||||
}),
|
||||
+ getProjectTokens: new RateLimiter('get-project-tokens', {
|
||||
+ points: 200,
|
||||
+ duration: 60 * 10,
|
||||
+ }),
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -139,5 +143,12 @@ module.exports = {
|
||||
CollaboratorsInviteController.acceptInvite,
|
||||
AnalyticsRegistrationSourceMiddleware.clearSource()
|
||||
)
|
||||
+
|
||||
+ webRouter.get(
|
||||
+ '/project/:Project_id/tokens',
|
||||
+ RateLimiterMiddleware.rateLimit(rateLimiters.getProjectTokens),
|
||||
+ AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
+ CollaboratorsController.getShareTokens
|
||||
+ )
|
||||
},
|
||||
}
|
||||
--- services/web/app/src/Features/Editor/EditorController.js
|
||||
+++ services/web/app/src/Features/Editor/EditorController.js
|
||||
@@ -581,20 +581,7 @@ const EditorController = {
|
||||
{ newAccessLevel }
|
||||
)
|
||||
if (newAccessLevel === PublicAccessLevels.TOKEN_BASED) {
|
||||
- ProjectDetailsHandler.ensureTokensArePresent(
|
||||
- projectId,
|
||||
- function (err, tokens) {
|
||||
- if (err) {
|
||||
- return callback(err)
|
||||
- }
|
||||
- EditorRealTimeController.emitToRoom(
|
||||
- projectId,
|
||||
- 'project:tokens:changed',
|
||||
- { tokens }
|
||||
- )
|
||||
- callback()
|
||||
- }
|
||||
- )
|
||||
+ ProjectDetailsHandler.ensureTokensArePresent(projectId, callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
--- services/web/app/src/Features/Editor/EditorHttpController.js
|
||||
+++ services/web/app/src/Features/Editor/EditorHttpController.js
|
||||
@@ -67,8 +67,6 @@ async function joinProject(req, res, next) {
|
||||
if (!project) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
- // Hide access tokens if this is not the project owner
|
||||
- TokenAccessHandler.protectTokens(project, privilegeLevel)
|
||||
// Hide sensitive data if the user is restricted
|
||||
if (isRestrictedUser) {
|
||||
project.owner = { _id: project.owner._id }
|
||||
--- services/web/app/src/Features/Project/ProjectController.js
|
||||
+++ services/web/app/src/Features/Project/ProjectController.js
|
||||
@@ -343,7 +343,7 @@ const ProjectController = {
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
ProjectGetter.findAllUsersProjects(
|
||||
userId,
|
||||
- 'name lastUpdated publicAccesLevel archived trashed owner_ref tokens',
|
||||
+ 'name lastUpdated publicAccesLevel archived trashed owner_ref',
|
||||
(err, projects) => {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
@@ -1072,7 +1072,6 @@ const ProjectController = {
|
||||
// If a project is simultaneously trashed and archived, we will consider it archived but not trashed.
|
||||
const trashed = ProjectHelper.isTrashed(project, userId) && !archived
|
||||
|
||||
- TokenAccessHandler.protectTokens(project, accessLevel)
|
||||
const model = {
|
||||
id: project._id,
|
||||
name: project.name,
|
||||
--- services/web/app/src/Features/Project/ProjectDetailsHandler.js
|
||||
+++ services/web/app/src/Features/Project/ProjectDetailsHandler.js
|
||||
@@ -207,14 +207,13 @@ async function ensureTokensArePresent(projectId) {
|
||||
project.tokens.readOnly != null &&
|
||||
project.tokens.readAndWrite != null
|
||||
) {
|
||||
- return project.tokens
|
||||
+ return
|
||||
}
|
||||
await _generateTokens(project)
|
||||
await Project.updateOne(
|
||||
{ _id: projectId },
|
||||
{ $set: { tokens: project.tokens } }
|
||||
).exec()
|
||||
- return project.tokens
|
||||
}
|
||||
|
||||
async function clearTokens(projectId) {
|
||||
--- services/web/app/src/Features/Project/ProjectEditorHandler.js
|
||||
+++ services/web/app/src/Features/Project/ProjectEditorHandler.js
|
||||
@@ -49,7 +49,6 @@ module.exports = ProjectEditorHandler = {
|
||||
),
|
||||
members: [],
|
||||
invites,
|
||||
- tokens: project.tokens,
|
||||
imageName:
|
||||
project.imageName != null
|
||||
? Path.basename(project.imageName)
|
||||
--- services/web/app/src/Features/TokenAccess/TokenAccessHandler.js
|
||||
+++ services/web/app/src/Features/TokenAccess/TokenAccessHandler.js
|
||||
@@ -246,22 +246,6 @@ const TokenAccessHandler = {
|
||||
})
|
||||
},
|
||||
|
||||
- protectTokens(project, privilegeLevel) {
|
||||
- if (!project || !project.tokens) {
|
||||
- return
|
||||
- }
|
||||
- if (privilegeLevel === PrivilegeLevels.OWNER) {
|
||||
- return
|
||||
- }
|
||||
- if (privilegeLevel !== PrivilegeLevels.READ_AND_WRITE) {
|
||||
- project.tokens.readAndWrite = ''
|
||||
- project.tokens.readAndWritePrefix = ''
|
||||
- }
|
||||
- if (privilegeLevel !== PrivilegeLevels.READ_ONLY) {
|
||||
- project.tokens.readOnly = ''
|
||||
- }
|
||||
- },
|
||||
-
|
||||
getV1DocPublishedInfo(token, callback) {
|
||||
// default to allowing access
|
||||
if (!Settings.apis.v1 || !Settings.apis.v1.url) {
|
||||
@@ -304,7 +288,6 @@ TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
|
||||
'_projectFindOne',
|
||||
'grantSessionTokenAccess',
|
||||
'getRequestToken',
|
||||
- 'protectTokens',
|
||||
],
|
||||
multiResult: {
|
||||
validateTokenForAnonymousAccess: ['isValidReadAndWrite', 'isValidReadOnly'],
|
||||
--- services/web/frontend/js/features/share-project-modal/components/link-sharing.js
|
||||
+++ services/web/frontend/js/features/share-project-modal/components/link-sharing.js
|
||||
@@ -1,4 +1,4 @@
|
||||
-import { useCallback, useState } from 'react'
|
||||
+import { useCallback, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Col, Row } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
@@ -10,6 +10,8 @@ import CopyLink from '../../../shared/components/copy-link'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { useUserContext } from '../../../shared/context/user-context'
|
||||
+import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
+import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
|
||||
export default function LinkSharing({ canAddCollaborators }) {
|
||||
const [inflight, setInflight] = useState(false)
|
||||
@@ -27,8 +29,7 @@ export default function LinkSharing({ canAddCollaborators }) {
|
||||
)
|
||||
.then(() => {
|
||||
// NOTE: not calling `updateProject` here as it receives data via
|
||||
- // project:publicAccessLevel:changed and project:tokens:changed
|
||||
- // over the websocket connection
|
||||
+ // project:publicAccessLevel:changed over the websocket connection
|
||||
// TODO: eventTracking.sendMB('project-make-token-based') when newPublicAccessLevel is 'tokenBased'
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -106,7 +107,17 @@ PrivateSharing.propTypes = {
|
||||
}
|
||||
|
||||
function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
||||
- const { tokens } = useProjectContext()
|
||||
+ const { _id: projectId } = useProjectContext()
|
||||
+
|
||||
+ const [tokens, setTokens] = useState(null)
|
||||
+
|
||||
+ const { signal } = useAbortController()
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ getJSON(`/project/${projectId}/tokens`, { signal })
|
||||
+ .then(data => setTokens(data))
|
||||
+ .catch(error => console.error(error))
|
||||
+ }, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
@@ -194,7 +205,17 @@ LegacySharing.propTypes = {
|
||||
}
|
||||
|
||||
export function ReadOnlyTokenLink() {
|
||||
- const { tokens } = useProjectContext()
|
||||
+ const { _id: projectId } = useProjectContext()
|
||||
+
|
||||
+ const [tokens, setTokens] = useState(null)
|
||||
+
|
||||
+ const { signal } = useAbortController()
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ getJSON(`/project/${projectId}/tokens`, { signal })
|
||||
+ .then(data => setTokens(data))
|
||||
+ .catch(error => console.error(error))
|
||||
+ }, [projectId, signal])
|
||||
|
||||
return (
|
||||
<Row className="public-access-level">
|
||||
--- services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js
|
||||
+++ services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js
|
||||
@@ -31,16 +31,6 @@ export default App.controller(
|
||||
})
|
||||
}
|
||||
|
||||
- /* tokens */
|
||||
-
|
||||
- ide.socket.on('project:tokens:changed', data => {
|
||||
- if (data.tokens != null) {
|
||||
- $scope.$applyAsync(() => {
|
||||
- $scope.project.tokens = data.tokens
|
||||
- })
|
||||
- }
|
||||
- })
|
||||
-
|
||||
ide.socket.on('project:membership:changed', data => {
|
||||
if (data.members) {
|
||||
listProjectMembers($scope.project._id)
|
||||
--- services/web/frontend/js/shared/context/mock/mock-ide.js
|
||||
+++ services/web/frontend/js/shared/context/mock/mock-ide.js
|
||||
@@ -27,10 +27,6 @@ export const getMockIde = () => {
|
||||
zotero: false,
|
||||
},
|
||||
publicAccessLevel: '',
|
||||
- tokens: {
|
||||
- readOnly: '',
|
||||
- readAndWrite: '',
|
||||
- },
|
||||
owner: {
|
||||
_id: '',
|
||||
email: '',
|
||||
--- services/web/frontend/js/shared/context/project-context.js
|
||||
+++ services/web/frontend/js/shared/context/project-context.js
|
||||
@@ -28,10 +28,6 @@ export const projectShape = {
|
||||
versioning: PropTypes.bool,
|
||||
}),
|
||||
publicAccessLevel: PropTypes.string,
|
||||
- tokens: PropTypes.shape({
|
||||
- readOnly: PropTypes.string,
|
||||
- readAndWrite: PropTypes.string,
|
||||
- }),
|
||||
owner: PropTypes.shape({
|
||||
_id: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
@@ -81,7 +77,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccesLevel: publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
} = project || projectFallback
|
||||
|
||||
@@ -94,7 +89,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
}
|
||||
}, [
|
||||
@@ -105,7 +99,6 @@ export function ProjectProvider({ children }) {
|
||||
invites,
|
||||
features,
|
||||
publicAccessLevel,
|
||||
- tokens,
|
||||
owner,
|
||||
])
|
Loading…
Reference in a new issue