| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- --- 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,
- ])
|