diff --git a/services/web/app/src/infrastructure/Server.js b/services/web/app/src/infrastructure/Server.js index 6463a79b54..9b1e622079 100644 --- a/services/web/app/src/infrastructure/Server.js +++ b/services/web/app/src/infrastructure/Server.js @@ -1,18 +1,3 @@ -/* eslint-disable - handle-callback-err, - max-len, - no-path-concat, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let staticCacheAge const Path = require('path') const express = require('express') const Settings = require('settings-sharelatex') @@ -34,14 +19,9 @@ const methodOverride = require('method-override') const cookieParser = require('cookie-parser') const bearerToken = require('express-bearer-token') -// Init the session store -const sessionStore = new RedisStore({ client: sessionsRedisClient }) - const passport = require('passport') const LocalStrategy = require('passport-local').Strategy -const Mongoose = require('./Mongoose') - const oneDayInMilliseconds = 86400000 const ReferalConnect = require('../Features/Referal/ReferalConnect') const RedirectManager = require('./RedirectManager') @@ -54,16 +34,17 @@ const HttpErrorController = require('../Features/Errors/HttpErrorController') const UserSessionsManager = require('../Features/User/UserSessionsManager') const AuthenticationController = require('../Features/Authentication/AuthenticationController') +const STATIC_CACHE_AGE = Settings.cacheStaticAssets + ? oneDayInMilliseconds * 365 + : 0 + +// Init the session store +const sessionStore = new RedisStore({ client: sessionsRedisClient }) + if (metrics.event_loop != null) { metrics.event_loop.monitor(logger) } -if (Settings.cacheStaticAssets) { - staticCacheAge = oneDayInMilliseconds * 365 -} else { - staticCacheAge = 0 -} - const app = express() const webRouter = express.Router() @@ -71,13 +52,34 @@ const privateApiRouter = express.Router() const publicApiRouter = express.Router() if (Settings.behindProxy) { - app.enable('trust proxy') + app.set('trust proxy', Settings.trustedProxyIps || true) + /** + * Handle the X-Original-Forwarded-For header. + * + * The nginx ingress sends us the contents of X-Forwarded-For it received in + * X-Original-Forwarded-For. Express expects all proxy IPs to be in a comma + * separated list in X-Forwarded-For. + */ + app.use((req, res, next) => { + if ( + req.headers['x-original-forwarded-for'] && + req.headers['x-forwarded-for'] + ) { + req.headers['x-forwarded-for'] = + req.headers['x-original-forwarded-for'] + + ', ' + + req.headers['x-forwarded-for'] + } + next() + }) } webRouter.use( - express.static(__dirname + '/../../../public', { maxAge: staticCacheAge }) + express.static(Path.join(__dirname, '/../../../public'), { + maxAge: STATIC_CACHE_AGE + }) ) -app.set('views', __dirname + '/../../views') +app.set('views', Path.join(__dirname, '/../../views')) app.set('view engine', 'pug') Modules.loadViewIncludes(app) @@ -131,7 +133,7 @@ passport.deserializeUser(AuthenticationController.deserializeUser) Modules.hooks.fire('passportSetup', passport, function(err) { if (err != null) { - return logger.err({ err }, 'error setting up passport in modules') + logger.err({ err }, 'error setting up passport in modules') } }) @@ -148,10 +150,12 @@ webRouter.use(function(req, res, next) { if (AuthenticationController.isUserLoggedIn(req)) { UserSessionsManager.touch( AuthenticationController.getSessionUser(req), - function(err) {} + err => { + logger.err({ err }, 'error extending user session') + } ) } - return next() + next() }) webRouter.use(ReferalConnect.use) @@ -165,26 +169,26 @@ if (app.get('env') === 'production') { app.use(function(req, res, next) { metrics.inc('http-request') crawlerLogger.log(req) - return next() + next() }) webRouter.use(function(req, res, next) { if (Settings.siteIsOpen) { - return next() + next() } else { res.status(503) - return res.render('general/closed', { title: 'maintenance' }) + res.render('general/closed', { title: 'maintenance' }) } }) webRouter.use(function(req, res, next) { if (Settings.editorIsOpen) { - return next() + next() } else if (req.url.indexOf('/admin') === 0) { - return next() + next() } else { res.status(503) - return res.render('general/closed', { title: 'maintenance' }) + res.render('general/closed', { title: 'maintenance' }) } }) @@ -193,7 +197,7 @@ webRouter.use(function(req, res, next) { const isLoggedIn = AuthenticationController.isUserLoggedIn(req) const isProjectPage = !!req.path.match('^/project/[a-f0-9]{24}$') - return helmet({ + helmet({ // note that more headers are added by default dnsPrefetchControl: false, referrerPolicy: { policy: 'origin-when-cross-origin' }, @@ -208,16 +212,21 @@ const profiler = require('v8-profiler-node8') privateApiRouter.get('/profile', function(req, res) { const time = parseInt(req.query.time || '1000') profiler.startProfiling('test') - return setTimeout(function() { + setTimeout(function() { const profile = profiler.stopProfiling('test') - return res.json(profile) + res.json(profile) }, time) }) -privateApiRouter.get('/heapdump', (req, res) => +privateApiRouter.get('/heapdump', (req, res, next) => require('heapdump').writeSnapshot( `/tmp/${Date.now()}.web.heapsnapshot`, - (err, filename) => res.send(filename) + (err, filename) => { + if (err != null) { + return next(err) + } + res.send(filename) + } ) ) @@ -250,8 +259,7 @@ if (enableWebRouter || notDefined(enableWebRouter)) { } metrics.injectMetricsRoute(webRouter) - -const router = new Router(webRouter, privateApiRouter, publicApiRouter) +Router.initialize(webRouter, privateApiRouter, publicApiRouter) module.exports = { app, diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js index 09808435a7..8d1e4cb63d 100644 --- a/services/web/app/src/router.js +++ b/services/web/app/src/router.js @@ -56,1000 +56,994 @@ const UserMembershipRouter = require('./Features/UserMembership/UserMembershipRo const logger = require('logger-sharelatex') const _ = require('underscore') -module.exports = class Router { - constructor(webRouter, privateApiRouter, publicApiRouter) { - if (!Settings.allowPublicAccess) { - webRouter.all('*', AuthenticationController.requireGlobalLogin) - } +module.exports = { initialize } - webRouter.get('/login', UserPagesController.loginPage) - AuthenticationController.addEndpointToLoginWhitelist('/login') +function initialize(webRouter, privateApiRouter, publicApiRouter) { + if (!Settings.allowPublicAccess) { + webRouter.all('*', AuthenticationController.requireGlobalLogin) + } - webRouter.post('/login', AuthenticationController.passportLogin) + webRouter.get('/login', UserPagesController.loginPage) + AuthenticationController.addEndpointToLoginWhitelist('/login') + webRouter.post('/login', AuthenticationController.passportLogin) + + webRouter.get( + '/read-only/one-time-login', + UserPagesController.oneTimeLoginPage + ) + AuthenticationController.addEndpointToLoginWhitelist( + '/read-only/one-time-login' + ) + + webRouter.get('/logout', UserPagesController.logoutPage) + webRouter.post('/logout', UserController.logout) + + webRouter.get('/restricted', AuthorizationMiddleware.restricted) + + if (Features.hasFeature('registration')) { + webRouter.get('/register', UserPagesController.registerPage) + AuthenticationController.addEndpointToLoginWhitelist('/register') + } + + EditorRouter.apply(webRouter, privateApiRouter) + CollaboratorsRouter.apply(webRouter, privateApiRouter) + SubscriptionRouter.apply(webRouter, privateApiRouter, publicApiRouter) + UploadsRouter.apply(webRouter, privateApiRouter) + PasswordResetRouter.apply(webRouter, privateApiRouter) + StaticPagesRouter.apply(webRouter, privateApiRouter) + RealTimeProxyRouter.apply(webRouter, privateApiRouter) + ContactRouter.apply(webRouter, privateApiRouter) + AnalyticsRouter.apply(webRouter, privateApiRouter, publicApiRouter) + LinkedFilesRouter.apply(webRouter, privateApiRouter, publicApiRouter) + TemplatesRouter.apply(webRouter) + UserMembershipRouter.apply(webRouter) + + Modules.applyRouter(webRouter, privateApiRouter, publicApiRouter) + + if (Settings.enableSubscriptions) { webRouter.get( - '/read-only/one-time-login', - UserPagesController.oneTimeLoginPage - ) - AuthenticationController.addEndpointToLoginWhitelist( - '/read-only/one-time-login' - ) - - webRouter.get('/logout', UserPagesController.logoutPage) - webRouter.post('/logout', UserController.logout) - - webRouter.get('/restricted', AuthorizationMiddleware.restricted) - - if (Features.hasFeature('registration')) { - webRouter.get('/register', UserPagesController.registerPage) - AuthenticationController.addEndpointToLoginWhitelist('/register') - } - - EditorRouter.apply(webRouter, privateApiRouter) - CollaboratorsRouter.apply(webRouter, privateApiRouter) - SubscriptionRouter.apply(webRouter, privateApiRouter, publicApiRouter) - UploadsRouter.apply(webRouter, privateApiRouter) - PasswordResetRouter.apply(webRouter, privateApiRouter) - StaticPagesRouter.apply(webRouter, privateApiRouter) - RealTimeProxyRouter.apply(webRouter, privateApiRouter) - ContactRouter.apply(webRouter, privateApiRouter) - AnalyticsRouter.apply(webRouter, privateApiRouter, publicApiRouter) - LinkedFilesRouter.apply(webRouter, privateApiRouter, publicApiRouter) - TemplatesRouter.apply(webRouter) - UserMembershipRouter.apply(webRouter) - - Modules.applyRouter(webRouter, privateApiRouter, publicApiRouter) - - if (Settings.enableSubscriptions) { - webRouter.get( - '/user/bonus', - AuthenticationController.requireLogin(), - ReferalController.bonus - ) - } - - if (Settings.overleaf == null) { - webRouter.get('/blog', BlogController.getIndexPage) - webRouter.get('/blog/*', BlogController.getPage) - } - - webRouter.get('/user/activate', UserPagesController.activateAccountPage) - AuthenticationController.addEndpointToLoginWhitelist('/user/activate') - - webRouter.get( - '/user/settings', + '/user/bonus', AuthenticationController.requireLogin(), - SudoModeMiddleware.protectPage, - UserPagesController.settingsPage + ReferalController.bonus ) + } + + if (Settings.overleaf == null) { + webRouter.get('/blog', BlogController.getIndexPage) + webRouter.get('/blog/*', BlogController.getPage) + } + + webRouter.get('/user/activate', UserPagesController.activateAccountPage) + AuthenticationController.addEndpointToLoginWhitelist('/user/activate') + + webRouter.get( + '/user/settings', + AuthenticationController.requireLogin(), + SudoModeMiddleware.protectPage, + UserPagesController.settingsPage + ) + webRouter.post( + '/user/settings', + AuthenticationController.requireLogin(), + UserController.updateUserSettings + ) + webRouter.post( + '/user/password/update', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'change-password', + maxRequests: 10, + timeInterval: 60 + }), + UserController.changePassword + ) + webRouter.get( + '/user/emails', + AuthenticationController.requireLogin(), + UserEmailsController.list + ) + webRouter.get('/user/emails/confirm', UserEmailsController.showConfirm) + webRouter.post( + '/user/emails/confirm', + RateLimiterMiddleware.rateLimit({ + endpointName: 'confirm-email', + maxRequests: 10, + timeInterval: 60 + }), + UserEmailsController.confirm + ) + webRouter.post( + '/user/emails/resend_confirmation', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'resend-confirmation', + maxRequests: 10, + timeInterval: 60 + }), + UserEmailsController.resendConfirmation + ) + + if (Features.hasFeature('affiliations')) { webRouter.post( - '/user/settings', - AuthenticationController.requireLogin(), - UserController.updateUserSettings - ) - webRouter.post( - '/user/password/update', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'change-password', - maxRequests: 10, - timeInterval: 60 - }), - UserController.changePassword - ) - webRouter.get( '/user/emails', AuthenticationController.requireLogin(), - UserEmailsController.list - ) - webRouter.get('/user/emails/confirm', UserEmailsController.showConfirm) - webRouter.post( - '/user/emails/confirm', RateLimiterMiddleware.rateLimit({ - endpointName: 'confirm-email', + endpointName: 'add-email', maxRequests: 10, timeInterval: 60 }), - UserEmailsController.confirm + UserEmailsController.add ) webRouter.post( - '/user/emails/resend_confirmation', + '/user/emails/delete', AuthenticationController.requireLogin(), RateLimiterMiddleware.rateLimit({ - endpointName: 'resend-confirmation', + endpointName: 'delete-email', maxRequests: 10, timeInterval: 60 }), - UserEmailsController.resendConfirmation + UserEmailsController.remove ) + webRouter.post( + '/user/emails/default', + AuthenticationController.requireLogin(), + UserEmailsController.setDefault + ) + webRouter.post( + '/user/emails/endorse', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'endorse-email', + maxRequests: 30, + timeInterval: 60 + }), + UserEmailsController.endorse + ) + } - if (Features.hasFeature('affiliations')) { - webRouter.post( - '/user/emails', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'add-email', - maxRequests: 10, - timeInterval: 60 - }), - UserEmailsController.add - ) - webRouter.post( - '/user/emails/delete', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'delete-email', - maxRequests: 10, - timeInterval: 60 - }), - UserEmailsController.remove - ) - webRouter.post( - '/user/emails/default', - AuthenticationController.requireLogin(), - UserEmailsController.setDefault - ) - webRouter.post( - '/user/emails/endorse', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'endorse-email', - maxRequests: 30, - timeInterval: 60 - }), - UserEmailsController.endorse - ) + webRouter.get( + '/user/sessions', + AuthenticationController.requireLogin(), + SudoModeMiddleware.protectPage, + UserPagesController.sessionsPage + ) + webRouter.post( + '/user/sessions/clear', + AuthenticationController.requireLogin(), + UserController.clearSessions + ) + + webRouter.delete( + '/user/newsletter/unsubscribe', + AuthenticationController.requireLogin(), + UserController.unsubscribe + ) + webRouter.post( + '/user/delete', + RateLimiterMiddleware.rateLimit({ + endpointName: 'delete-user', + maxRequests: 10, + timeInterval: 60 + }), + AuthenticationController.requireLogin(), + UserController.tryDeleteUser + ) + + webRouter.get( + '/user/personal_info', + AuthenticationController.requireLogin(), + UserInfoController.getLoggedInUsersPersonalInfo + ) + privateApiRouter.get( + '/user/:user_id/personal_info', + AuthenticationController.httpAuth, + UserInfoController.getPersonalInfo + ) + + webRouter.get( + '/user/reconfirm', + UserPagesController.renderReconfirmAccountPage + ) + // for /user/reconfirm POST, see password router + + webRouter.get( + '/user/projects', + AuthenticationController.requireLogin(), + ProjectController.userProjectsJson + ) + webRouter.get( + '/project/:Project_id/entities', + AuthenticationController.requireLogin(), + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectController.projectEntitiesJson + ) + + webRouter.get( + '/project', + AuthenticationController.requireLogin(), + ProjectController.projectListPage + ) + webRouter.post( + '/project/new', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'create-project', + maxRequests: 20, + timeInterval: 60 + }), + ProjectController.newProject + ) + + webRouter.get( + '/Project/:Project_id', + RateLimiterMiddleware.rateLimit({ + endpointName: 'open-project', + params: ['Project_id'], + maxRequests: 15, + timeInterval: 60 + }), + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectController.loadEditor + ) + webRouter.head( + '/Project/:Project_id/file/:File_id', + AuthorizationMiddleware.ensureUserCanReadProject, + FileStoreController.getFileHead + ) + webRouter.get( + '/Project/:Project_id/file/:File_id', + AuthorizationMiddleware.ensureUserCanReadProject, + FileStoreController.getFile + ) + webRouter.post( + '/project/:Project_id/settings', + AuthorizationMiddleware.ensureUserCanWriteProjectSettings, + ProjectController.updateProjectSettings + ) + webRouter.post( + '/project/:Project_id/settings/admin', + AuthorizationMiddleware.ensureUserCanAdminProject, + ProjectController.updateProjectAdminSettings + ) + + webRouter.post( + '/project/:Project_id/compile', + RateLimiterMiddleware.rateLimit({ + endpointName: 'compile-project-http', + params: ['Project_id'], + maxRequests: 800, + timeInterval: 60 * 60 + }), + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.compile + ) + + webRouter.post( + '/project/:Project_id/compile/stop', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.stopCompile + ) + + // LEGACY: Used by the web download buttons, adds filename header, TODO: remove at some future date + webRouter.get( + '/project/:Project_id/output/output.pdf', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.downloadPdf + ) + + // PDF Download button + webRouter.get( + /^\/download\/project\/([^/]*)\/output\/output\.pdf$/, + function(req, res, next) { + const params = { Project_id: req.params[0] } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.downloadPdf + ) + + // PDF Download button for specific build + webRouter.get( + /^\/download\/project\/([^/]*)\/build\/([0-9a-f-]+)\/output\/output\.pdf$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + build_id: req.params[1] + } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.downloadPdf + ) + + // Used by the pdf viewers + webRouter.get( + /^\/project\/([^/]*)\/output\/(.*)$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + file: req.params[1] + } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.getFileFromClsi + ) + // direct url access to output files for a specific build (query string not required) + webRouter.get( + /^\/project\/([^/]*)\/build\/([0-9a-f-]+)\/output\/(.*)$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + build_id: req.params[1], + file: req.params[2] + } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.getFileFromClsi + ) + + // direct url access to output files for user but no build, to retrieve files when build fails + webRouter.get( + /^\/project\/([^/]*)\/user\/([0-9a-f-]+)\/output\/(.*)$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + user_id: req.params[1], + file: req.params[2] + } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.getFileFromClsi + ) + + // direct url access to output files for a specific user and build (query string not required) + webRouter.get( + /^\/project\/([^/]*)\/user\/([0-9a-f]+)\/build\/([0-9a-f-]+)\/output\/(.*)$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + user_id: req.params[1], + build_id: req.params[2], + file: req.params[3] + } + req.params = params + next() + }, + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.getFileFromClsi + ) + + webRouter.delete( + '/project/:Project_id/output', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.deleteAuxFiles + ) + webRouter.get( + '/project/:Project_id/sync/code', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.proxySyncCode + ) + webRouter.get( + '/project/:Project_id/sync/pdf', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.proxySyncPdf + ) + webRouter.get( + '/project/:Project_id/wordcount', + AuthorizationMiddleware.ensureUserCanReadProject, + CompileController.wordCount + ) + + webRouter.delete( + '/Project/:Project_id', + AuthorizationMiddleware.ensureUserCanAdminProject, + ProjectController.deleteProject + ) + webRouter.post( + '/Project/:Project_id/restore', + AuthorizationMiddleware.ensureUserCanAdminProject, + ProjectController.restoreProject + ) + webRouter.post( + '/Project/:Project_id/clone', + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectController.cloneProject + ) + + webRouter.post( + '/project/:Project_id/rename', + AuthorizationMiddleware.ensureUserCanAdminProject, + ProjectController.renameProject + ) + + webRouter.get( + '/project/:Project_id/updates', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.selectHistoryApi, + HistoryController.proxyToHistoryApiAndInjectUserDetails + ) + webRouter.get( + '/project/:Project_id/doc/:doc_id/diff', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.selectHistoryApi, + HistoryController.proxyToHistoryApi + ) + webRouter.get( + '/project/:Project_id/diff', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.selectHistoryApi, + HistoryController.proxyToHistoryApiAndInjectUserDetails + ) + webRouter.get( + '/project/:Project_id/filetree/diff', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.selectHistoryApi, + HistoryController.proxyToHistoryApi + ) + webRouter.post( + '/project/:Project_id/doc/:doc_id/version/:version_id/restore', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + HistoryController.selectHistoryApi, + HistoryController.proxyToHistoryApi + ) + webRouter.post( + '/project/:project_id/doc/:doc_id/restore', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + HistoryController.restoreDocFromDeletedDoc + ) + webRouter.post( + '/project/:project_id/restore_file', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + HistoryController.restoreFileFromV2 + ) + webRouter.get( + '/project/:project_id/version/:version/zip', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.downloadZipOfVersion + ) + privateApiRouter.post( + '/project/:Project_id/history/resync', + AuthenticationController.httpAuth, + HistoryController.resyncProjectHistory + ) + + webRouter.get( + '/project/:Project_id/labels', + AuthorizationMiddleware.ensureUserCanReadProject, + HistoryController.selectHistoryApi, + HistoryController.ensureProjectHistoryEnabled, + HistoryController.getLabels + ) + webRouter.post( + '/project/:Project_id/labels', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + HistoryController.selectHistoryApi, + HistoryController.ensureProjectHistoryEnabled, + HistoryController.createLabel + ) + webRouter.delete( + '/project/:Project_id/labels/:label_id', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + HistoryController.selectHistoryApi, + HistoryController.ensureProjectHistoryEnabled, + HistoryController.deleteLabel + ) + + webRouter.post( + '/project/:project_id/export/:brand_variation_id', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + ExportsController.exportProject + ) + webRouter.get( + '/project/:project_id/export/:export_id', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + ExportsController.exportStatus + ) + webRouter.get( + '/project/:project_id/export/:export_id/:type', + AuthorizationMiddleware.ensureUserCanWriteProjectContent, + ExportsController.exportDownload + ) + + webRouter.get( + '/Project/:Project_id/download/zip', + AuthorizationMiddleware.ensureUserCanReadProject, + ProjectDownloadsController.downloadProject + ) + webRouter.get( + '/project/download/zip', + AuthorizationMiddleware.ensureUserCanReadMultipleProjects, + ProjectDownloadsController.downloadMultipleProjects + ) + + webRouter.get( + '/project/:project_id/metadata', + AuthorizationMiddleware.ensureUserCanReadProject, + AuthenticationController.requireLogin(), + MetaController.getMetadata + ) + webRouter.post( + '/project/:project_id/doc/:doc_id/metadata', + AuthorizationMiddleware.ensureUserCanReadProject, + AuthenticationController.requireLogin(), + MetaController.broadcastMetadataForDoc + ) + + privateApiRouter.post( + '/internal/expire-deleted-projects-after-duration', + AuthenticationController.httpAuth, + ProjectController.expireDeletedProjectsAfterDuration + ) + privateApiRouter.post( + '/internal/expire-deleted-users-after-duration', + AuthenticationController.httpAuth, + UserController.expireDeletedUsersAfterDuration + ) + privateApiRouter.post( + '/internal/project/:projectId/expire-deleted-project', + AuthenticationController.httpAuth, + ProjectController.expireDeletedProject + ) + privateApiRouter.post( + '/internal/users/:userId/expire', + AuthenticationController.httpAuth, + UserController.expireDeletedUser + ) + + webRouter.get( + '/tag', + AuthenticationController.requireLogin(), + TagsController.getAllTags + ) + webRouter.post( + '/tag', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'create-tag', + maxRequests: 30, + timeInterval: 60 + }), + TagsController.createTag + ) + webRouter.post( + '/tag/:tag_id/rename', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'rename-tag', + maxRequests: 30, + timeInterval: 60 + }), + TagsController.renameTag + ) + webRouter.delete( + '/tag/:tag_id', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'delete-tag', + maxRequests: 30, + timeInterval: 60 + }), + TagsController.deleteTag + ) + webRouter.post( + '/tag/:tag_id/project/:project_id', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'add-project-to-tag', + maxRequests: 30, + timeInterval: 60 + }), + TagsController.addProjectToTag + ) + webRouter.delete( + '/tag/:tag_id/project/:project_id', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'remove-project-from-tag', + maxRequests: 30, + timeInterval: 60 + }), + TagsController.removeProjectFromTag + ) + + webRouter.get( + '/notifications', + AuthenticationController.requireLogin(), + NotificationsController.getAllUnreadNotifications + ) + webRouter.delete( + '/notifications/:notification_id', + AuthenticationController.requireLogin(), + NotificationsController.markNotificationAsRead + ) + + webRouter.get( + '/announcements', + AuthenticationController.requireLogin(), + AnnouncementsController.getUndreadAnnouncements + ) + + // Deprecated in favour of /internal/project/:project_id but still used by versioning + privateApiRouter.get( + '/project/:project_id/details', + AuthenticationController.httpAuth, + ProjectApiController.getProjectDetails + ) + + // New 'stable' /internal API end points + privateApiRouter.get( + '/internal/project/:project_id', + AuthenticationController.httpAuth, + ProjectApiController.getProjectDetails + ) + privateApiRouter.get( + '/internal/project/:Project_id/zip', + AuthenticationController.httpAuth, + ProjectDownloadsController.downloadProject + ) + privateApiRouter.get( + '/internal/project/:project_id/compile/pdf', + AuthenticationController.httpAuth, + CompileController.compileAndDownloadPdf + ) + + privateApiRouter.post( + '/internal/deactivateOldProjects', + AuthenticationController.httpAuth, + InactiveProjectController.deactivateOldProjects + ) + privateApiRouter.post( + '/internal/project/:project_id/deactivate', + AuthenticationController.httpAuth, + InactiveProjectController.deactivateProject + ) + + webRouter.get( + /^\/internal\/project\/([^/]*)\/output\/(.*)$/, + function(req, res, next) { + const params = { + Project_id: req.params[0], + file: req.params[1] + } + req.params = params + next() + }, + AuthenticationController.httpAuth, + CompileController.getFileFromClsi + ) + + privateApiRouter.get( + '/project/:Project_id/doc/:doc_id', + AuthenticationController.httpAuth, + DocumentController.getDocument + ) + privateApiRouter.post( + '/project/:Project_id/doc/:doc_id', + AuthenticationController.httpAuth, + DocumentController.setDocument + ) + + privateApiRouter.post( + '/user/:user_id/update/*', + AuthenticationController.httpAuth, + TpdsController.mergeUpdate + ) + privateApiRouter.delete( + '/user/:user_id/update/*', + AuthenticationController.httpAuth, + TpdsController.deleteUpdate + ) + + privateApiRouter.post( + '/project/:project_id/contents/*', + AuthenticationController.httpAuth, + TpdsController.updateProjectContents + ) + privateApiRouter.delete( + '/project/:project_id/contents/*', + AuthenticationController.httpAuth, + TpdsController.deleteProjectContents + ) + + webRouter.post( + '/spelling/check', + AuthenticationController.requireLogin(), + SpellingController.proxyRequestToSpellingApi + ) + webRouter.post( + '/spelling/learn', + AuthenticationController.requireLogin(), + SpellingController.proxyRequestToSpellingApi + ) + + webRouter.get( + '/project/:project_id/messages', + AuthorizationMiddleware.ensureUserCanReadProject, + ChatController.getMessages + ) + webRouter.post( + '/project/:project_id/messages', + AuthorizationMiddleware.ensureUserCanReadProject, + RateLimiterMiddleware.rateLimit({ + endpointName: 'send-chat-message', + maxRequests: 100, + timeInterval: 60 + }), + ChatController.sendMessage + ) + + webRouter.post( + '/project/:Project_id/references/index', + AuthorizationMiddleware.ensureUserCanReadProject, + RateLimiterMiddleware.rateLimit({ + endpointName: 'index-project-references', + maxRequests: 30, + timeInterval: 60 + }), + ReferencesController.index + ) + webRouter.post( + '/project/:Project_id/references/indexAll', + AuthorizationMiddleware.ensureUserCanReadProject, + RateLimiterMiddleware.rateLimit({ + endpointName: 'index-all-project-references', + maxRequests: 30, + timeInterval: 60 + }), + ReferencesController.indexAll + ) + + // disable beta program while v2 is in beta + webRouter.get( + '/beta/participate', + AuthenticationController.requireLogin(), + BetaProgramController.optInPage + ) + webRouter.post( + '/beta/opt-in', + AuthenticationController.requireLogin(), + BetaProgramController.optIn + ) + webRouter.post( + '/beta/opt-out', + AuthenticationController.requireLogin(), + BetaProgramController.optOut + ) + webRouter.get( + '/confirm-password', + AuthenticationController.requireLogin(), + SudoModeController.sudoModePrompt + ) + webRouter.post( + '/confirm-password', + AuthenticationController.requireLogin(), + RateLimiterMiddleware.rateLimit({ + endpointName: 'confirm-password', + maxRequests: 10, + timeInterval: 60 + }), + SudoModeController.submitPassword + ) + + // New "api" endpoints. Started as a way for v1 to call over to v2 (for + // long-term features, as opposed to the nominally temporary ones in the + // overleaf-integration module), but may expand beyond that role. + publicApiRouter.post( + '/api/clsi/compile/:submission_id', + AuthenticationController.httpAuth, + CompileController.compileSubmission + ) + publicApiRouter.get( + /^\/api\/clsi\/compile\/([^/]*)\/build\/([0-9a-f-]+)\/output\/(.*)$/, + function(req, res, next) { + const params = { + submission_id: req.params[0], + build_id: req.params[1], + file: req.params[2] + } + req.params = params + next() + }, + AuthenticationController.httpAuth, + CompileController.getFileFromClsiWithoutUser + ) + publicApiRouter.post( + '/api/institutions/confirm_university_domain', + RateLimiterMiddleware.rateLimit({ + endpointName: 'confirm-university-domain', + maxRequests: 1, + timeInterval: 60 + }), + AuthenticationController.httpAuth, + InstitutionsController.confirmDomain + ) + + webRouter.get('/chrome', function(req, res, next) { + // Match v1 behaviour - this is used for a Chrome web app + if (AuthenticationController.isUserLoggedIn(req)) { + res.redirect('/project') + } else { + res.redirect('/register') } + }) - webRouter.get( - '/user/sessions', - AuthenticationController.requireLogin(), - SudoModeMiddleware.protectPage, - UserPagesController.sessionsPage - ) - webRouter.post( - '/user/sessions/clear', - AuthenticationController.requireLogin(), - UserController.clearSessions - ) + // Admin Stuff + webRouter.get( + '/admin', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.index + ) + webRouter.get( + '/admin/user', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + (req, res) => res.redirect('/admin/register') + ) // this gets removed by admin-panel addon + webRouter.get( + '/admin/register', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.registerNewUser + ) + webRouter.post( + '/admin/register', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + UserController.register + ) + webRouter.post( + '/admin/closeEditor', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.closeEditor + ) + webRouter.post( + '/admin/dissconectAllUsers', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.dissconectAllUsers + ) + webRouter.post( + '/admin/syncUserToSubscription', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.syncUserToSubscription + ) + webRouter.post( + '/admin/flushProjectToTpds', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.flushProjectToTpds + ) + webRouter.post( + '/admin/pollDropboxForUser', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.pollDropboxForUser + ) + webRouter.post( + '/admin/messages', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.createMessage + ) + webRouter.post( + '/admin/messages/clear', + AuthorizationMiddleware.ensureUserIsSiteAdmin, + AdminController.clearMessages + ) - webRouter.delete( - '/user/newsletter/unsubscribe', - AuthenticationController.requireLogin(), - UserController.unsubscribe - ) - webRouter.post( - '/user/delete', - RateLimiterMiddleware.rateLimit({ - endpointName: 'delete-user', - maxRequests: 10, - timeInterval: 60 - }), - AuthenticationController.requireLogin(), - UserController.tryDeleteUser - ) + privateApiRouter.post( + '/disconnectAllUsers', + AdminController.dissconectAllUsers + ) - webRouter.get( - '/user/personal_info', - AuthenticationController.requireLogin(), - UserInfoController.getLoggedInUsersPersonalInfo - ) - privateApiRouter.get( - '/user/:user_id/personal_info', - AuthenticationController.httpAuth, - UserInfoController.getPersonalInfo - ) + privateApiRouter.get('/perfTest', (req, res) => res.send('hello')) - webRouter.get( - '/user/reconfirm', - UserPagesController.renderReconfirmAccountPage - ) - // for /user/reconfirm POST, see password router + publicApiRouter.get('/status', (req, res) => + res.send('web sharelatex is alive (web)') + ) + privateApiRouter.get('/status', (req, res) => + res.send('web sharelatex is alive (api)') + ) - webRouter.get( - '/user/projects', - AuthenticationController.requireLogin(), - ProjectController.userProjectsJson - ) - webRouter.get( - '/project/:Project_id/entities', - AuthenticationController.requireLogin(), - AuthorizationMiddleware.ensureUserCanReadProject, - ProjectController.projectEntitiesJson - ) + webRouter.get('/dev/csrf', (req, res) => res.send(res.locals.csrfToken)) - webRouter.get( - '/project', - AuthenticationController.requireLogin(), - ProjectController.projectListPage - ) - webRouter.post( - '/project/new', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'create-project', - maxRequests: 20, - timeInterval: 60 - }), - ProjectController.newProject - ) + publicApiRouter.get('/health_check', HealthCheckController.check) + privateApiRouter.get('/health_check', HealthCheckController.check) - webRouter.get( - '/Project/:Project_id', - RateLimiterMiddleware.rateLimit({ - endpointName: 'open-project', - params: ['Project_id'], - maxRequests: 15, - timeInterval: 60 - }), - AuthorizationMiddleware.ensureUserCanReadProject, - ProjectController.loadEditor - ) - webRouter.head( - '/Project/:Project_id/file/:File_id', - AuthorizationMiddleware.ensureUserCanReadProject, - FileStoreController.getFileHead - ) - webRouter.get( - '/Project/:Project_id/file/:File_id', - AuthorizationMiddleware.ensureUserCanReadProject, - FileStoreController.getFile - ) - webRouter.post( - '/project/:Project_id/settings', - AuthorizationMiddleware.ensureUserCanWriteProjectSettings, - ProjectController.updateProjectSettings - ) - webRouter.post( - '/project/:Project_id/settings/admin', - AuthorizationMiddleware.ensureUserCanAdminProject, - ProjectController.updateProjectAdminSettings - ) + publicApiRouter.get('/health_check/redis', HealthCheckController.checkRedis) + privateApiRouter.get('/health_check/redis', HealthCheckController.checkRedis) - webRouter.post( - '/project/:Project_id/compile', - RateLimiterMiddleware.rateLimit({ - endpointName: 'compile-project-http', - params: ['Project_id'], - maxRequests: 800, - timeInterval: 60 * 60 - }), - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.compile - ) + publicApiRouter.get('/health_check/mongo', HealthCheckController.checkMongo) + privateApiRouter.get('/health_check/mongo', HealthCheckController.checkMongo) - webRouter.post( - '/project/:Project_id/compile/stop', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.stopCompile - ) - - // LEGACY: Used by the web download buttons, adds filename header, TODO: remove at some future date - webRouter.get( - '/project/:Project_id/output/output.pdf', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.downloadPdf - ) - - // PDF Download button - webRouter.get( - /^\/download\/project\/([^/]*)\/output\/output\.pdf$/, - function(req, res, next) { - const params = { Project_id: req.params[0] } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.downloadPdf - ) - - // PDF Download button for specific build - webRouter.get( - /^\/download\/project\/([^/]*)\/build\/([0-9a-f-]+)\/output\/output\.pdf$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - build_id: req.params[1] + webRouter.get( + '/status/compiler/:Project_id', + AuthorizationMiddleware.ensureUserCanReadProject, + function(req, res) { + const projectId = req.params.Project_id + const sendRes = _.once(function(statusCode, message) { + res.status(statusCode) + res.send(message) + ClsiCookieManager.clearServerId(projectId) + }) // force every compile to a new server + // set a timeout + var handler = setTimeout(function() { + sendRes(500, 'Compiler timed out') + handler = null + }, 10000) + // use a valid user id for testing + const testUserId = '123456789012345678901234' + // run the compile + CompileManager.compile(projectId, testUserId, {}, function( + error, + status + ) { + if (handler) { + clearTimeout(handler) } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.downloadPdf - ) - - // Used by the pdf viewers - webRouter.get( - /^\/project\/([^/]*)\/output\/(.*)$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - file: req.params[1] + if (error) { + sendRes(500, `Compiler returned error ${error.message}`) + } else if (status === 'success') { + sendRes(200, 'Compiler returned in less than 10 seconds') + } else { + sendRes(500, `Compiler returned failure ${status}`) } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.getFileFromClsi - ) - // direct url access to output files for a specific build (query string not required) - webRouter.get( - /^\/project\/([^/]*)\/build\/([0-9a-f-]+)\/output\/(.*)$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - build_id: req.params[1], - file: req.params[2] - } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.getFileFromClsi - ) + }) + } + ) - // direct url access to output files for user but no build, to retrieve files when build fails - webRouter.get( - /^\/project\/([^/]*)\/user\/([0-9a-f-]+)\/output\/(.*)$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - user_id: req.params[1], - file: req.params[2] - } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.getFileFromClsi - ) + webRouter.get('/no-cache', function(req, res, next) { + res.header('Cache-Control', 'max-age=0') + res.sendStatus(404) + }) - // direct url access to output files for a specific user and build (query string not required) - webRouter.get( - /^\/project\/([^/]*)\/user\/([0-9a-f]+)\/build\/([0-9a-f-]+)\/output\/(.*)$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - user_id: req.params[1], - build_id: req.params[2], - file: req.params[3] - } - req.params = params - next() - }, - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.getFileFromClsi - ) - - webRouter.delete( - '/project/:Project_id/output', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.deleteAuxFiles - ) - webRouter.get( - '/project/:Project_id/sync/code', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.proxySyncCode - ) - webRouter.get( - '/project/:Project_id/sync/pdf', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.proxySyncPdf - ) - webRouter.get( - '/project/:Project_id/wordcount', - AuthorizationMiddleware.ensureUserCanReadProject, - CompileController.wordCount - ) - - webRouter.delete( - '/Project/:Project_id', - AuthorizationMiddleware.ensureUserCanAdminProject, - ProjectController.deleteProject - ) - webRouter.post( - '/Project/:Project_id/restore', - AuthorizationMiddleware.ensureUserCanAdminProject, - ProjectController.restoreProject - ) - webRouter.post( - '/Project/:Project_id/clone', - AuthorizationMiddleware.ensureUserCanReadProject, - ProjectController.cloneProject - ) - - webRouter.post( - '/project/:Project_id/rename', - AuthorizationMiddleware.ensureUserCanAdminProject, - ProjectController.renameProject - ) - - webRouter.get( - '/project/:Project_id/updates', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.selectHistoryApi, - HistoryController.proxyToHistoryApiAndInjectUserDetails - ) - webRouter.get( - '/project/:Project_id/doc/:doc_id/diff', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.selectHistoryApi, - HistoryController.proxyToHistoryApi - ) - webRouter.get( - '/project/:Project_id/diff', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.selectHistoryApi, - HistoryController.proxyToHistoryApiAndInjectUserDetails - ) - webRouter.get( - '/project/:Project_id/filetree/diff', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.selectHistoryApi, - HistoryController.proxyToHistoryApi - ) - webRouter.post( - '/project/:Project_id/doc/:doc_id/version/:version_id/restore', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - HistoryController.selectHistoryApi, - HistoryController.proxyToHistoryApi - ) - webRouter.post( - '/project/:project_id/doc/:doc_id/restore', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - HistoryController.restoreDocFromDeletedDoc - ) - webRouter.post( - '/project/:project_id/restore_file', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - HistoryController.restoreFileFromV2 - ) - webRouter.get( - '/project/:project_id/version/:version/zip', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.downloadZipOfVersion - ) - privateApiRouter.post( - '/project/:Project_id/history/resync', - AuthenticationController.httpAuth, - HistoryController.resyncProjectHistory - ) - - webRouter.get( - '/project/:Project_id/labels', - AuthorizationMiddleware.ensureUserCanReadProject, - HistoryController.selectHistoryApi, - HistoryController.ensureProjectHistoryEnabled, - HistoryController.getLabels - ) - webRouter.post( - '/project/:Project_id/labels', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - HistoryController.selectHistoryApi, - HistoryController.ensureProjectHistoryEnabled, - HistoryController.createLabel - ) - webRouter.delete( - '/project/:Project_id/labels/:label_id', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - HistoryController.selectHistoryApi, - HistoryController.ensureProjectHistoryEnabled, - HistoryController.deleteLabel - ) - - webRouter.post( - '/project/:project_id/export/:brand_variation_id', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - ExportsController.exportProject - ) - webRouter.get( - '/project/:project_id/export/:export_id', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - ExportsController.exportStatus - ) - webRouter.get( - '/project/:project_id/export/:export_id/:type', - AuthorizationMiddleware.ensureUserCanWriteProjectContent, - ExportsController.exportDownload - ) - - webRouter.get( - '/Project/:Project_id/download/zip', - AuthorizationMiddleware.ensureUserCanReadProject, - ProjectDownloadsController.downloadProject - ) - webRouter.get( - '/project/download/zip', - AuthorizationMiddleware.ensureUserCanReadMultipleProjects, - ProjectDownloadsController.downloadMultipleProjects - ) - - webRouter.get( - '/project/:project_id/metadata', - AuthorizationMiddleware.ensureUserCanReadProject, - AuthenticationController.requireLogin(), - MetaController.getMetadata - ) - webRouter.post( - '/project/:project_id/doc/:doc_id/metadata', - AuthorizationMiddleware.ensureUserCanReadProject, - AuthenticationController.requireLogin(), - MetaController.broadcastMetadataForDoc - ) - - privateApiRouter.post( - '/internal/expire-deleted-projects-after-duration', - AuthenticationController.httpAuth, - ProjectController.expireDeletedProjectsAfterDuration - ) - privateApiRouter.post( - '/internal/expire-deleted-users-after-duration', - AuthenticationController.httpAuth, - UserController.expireDeletedUsersAfterDuration - ) - privateApiRouter.post( - '/internal/project/:projectId/expire-deleted-project', - AuthenticationController.httpAuth, - ProjectController.expireDeletedProject - ) - privateApiRouter.post( - '/internal/users/:userId/expire', - AuthenticationController.httpAuth, - UserController.expireDeletedUser - ) - - webRouter.get( - '/tag', - AuthenticationController.requireLogin(), - TagsController.getAllTags - ) - webRouter.post( - '/tag', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'create-tag', - maxRequests: 30, - timeInterval: 60 - }), - TagsController.createTag - ) - webRouter.post( - '/tag/:tag_id/rename', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'rename-tag', - maxRequests: 30, - timeInterval: 60 - }), - TagsController.renameTag - ) - webRouter.delete( - '/tag/:tag_id', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'delete-tag', - maxRequests: 30, - timeInterval: 60 - }), - TagsController.deleteTag - ) - webRouter.post( - '/tag/:tag_id/project/:project_id', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'add-project-to-tag', - maxRequests: 30, - timeInterval: 60 - }), - TagsController.addProjectToTag - ) - webRouter.delete( - '/tag/:tag_id/project/:project_id', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'remove-project-from-tag', - maxRequests: 30, - timeInterval: 60 - }), - TagsController.removeProjectFromTag - ) - - webRouter.get( - '/notifications', - AuthenticationController.requireLogin(), - NotificationsController.getAllUnreadNotifications - ) - webRouter.delete( - '/notifications/:notification_id', - AuthenticationController.requireLogin(), - NotificationsController.markNotificationAsRead - ) - - webRouter.get( - '/announcements', - AuthenticationController.requireLogin(), - AnnouncementsController.getUndreadAnnouncements - ) - - // Deprecated in favour of /internal/project/:project_id but still used by versioning - privateApiRouter.get( - '/project/:project_id/details', - AuthenticationController.httpAuth, - ProjectApiController.getProjectDetails - ) - - // New 'stable' /internal API end points - privateApiRouter.get( - '/internal/project/:project_id', - AuthenticationController.httpAuth, - ProjectApiController.getProjectDetails - ) - privateApiRouter.get( - '/internal/project/:Project_id/zip', - AuthenticationController.httpAuth, - ProjectDownloadsController.downloadProject - ) - privateApiRouter.get( - '/internal/project/:project_id/compile/pdf', - AuthenticationController.httpAuth, - CompileController.compileAndDownloadPdf - ) - - privateApiRouter.post( - '/internal/deactivateOldProjects', - AuthenticationController.httpAuth, - InactiveProjectController.deactivateOldProjects - ) - privateApiRouter.post( - '/internal/project/:project_id/deactivate', - AuthenticationController.httpAuth, - InactiveProjectController.deactivateProject - ) - - webRouter.get( - /^\/internal\/project\/([^/]*)\/output\/(.*)$/, - function(req, res, next) { - const params = { - Project_id: req.params[0], - file: req.params[1] - } - req.params = params - next() - }, - AuthenticationController.httpAuth, - CompileController.getFileFromClsi - ) - - privateApiRouter.get( - '/project/:Project_id/doc/:doc_id', - AuthenticationController.httpAuth, - DocumentController.getDocument - ) - privateApiRouter.post( - '/project/:Project_id/doc/:doc_id', - AuthenticationController.httpAuth, - DocumentController.setDocument - ) - - privateApiRouter.post( - '/user/:user_id/update/*', - AuthenticationController.httpAuth, - TpdsController.mergeUpdate - ) - privateApiRouter.delete( - '/user/:user_id/update/*', - AuthenticationController.httpAuth, - TpdsController.deleteUpdate - ) - - privateApiRouter.post( - '/project/:project_id/contents/*', - AuthenticationController.httpAuth, - TpdsController.updateProjectContents - ) - privateApiRouter.delete( - '/project/:project_id/contents/*', - AuthenticationController.httpAuth, - TpdsController.deleteProjectContents - ) - - webRouter.post( - '/spelling/check', - AuthenticationController.requireLogin(), - SpellingController.proxyRequestToSpellingApi - ) - webRouter.post( - '/spelling/learn', - AuthenticationController.requireLogin(), - SpellingController.proxyRequestToSpellingApi - ) - - webRouter.get( - '/project/:project_id/messages', - AuthorizationMiddleware.ensureUserCanReadProject, - ChatController.getMessages - ) - webRouter.post( - '/project/:project_id/messages', - AuthorizationMiddleware.ensureUserCanReadProject, - RateLimiterMiddleware.rateLimit({ - endpointName: 'send-chat-message', - maxRequests: 100, - timeInterval: 60 - }), - ChatController.sendMessage - ) - - webRouter.post( - '/project/:Project_id/references/index', - AuthorizationMiddleware.ensureUserCanReadProject, - RateLimiterMiddleware.rateLimit({ - endpointName: 'index-project-references', - maxRequests: 30, - timeInterval: 60 - }), - ReferencesController.index - ) - webRouter.post( - '/project/:Project_id/references/indexAll', - AuthorizationMiddleware.ensureUserCanReadProject, - RateLimiterMiddleware.rateLimit({ - endpointName: 'index-all-project-references', - maxRequests: 30, - timeInterval: 60 - }), - ReferencesController.indexAll - ) - - // disable beta program while v2 is in beta - webRouter.get( - '/beta/participate', - AuthenticationController.requireLogin(), - BetaProgramController.optInPage - ) - webRouter.post( - '/beta/opt-in', - AuthenticationController.requireLogin(), - BetaProgramController.optIn - ) - webRouter.post( - '/beta/opt-out', - AuthenticationController.requireLogin(), - BetaProgramController.optOut - ) - webRouter.get( - '/confirm-password', - AuthenticationController.requireLogin(), - SudoModeController.sudoModePrompt - ) - webRouter.post( - '/confirm-password', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit({ - endpointName: 'confirm-password', - maxRequests: 10, - timeInterval: 60 - }), - SudoModeController.submitPassword - ) - - // New "api" endpoints. Started as a way for v1 to call over to v2 (for - // long-term features, as opposed to the nominally temporary ones in the - // overleaf-integration module), but may expand beyond that role. - publicApiRouter.post( - '/api/clsi/compile/:submission_id', - AuthenticationController.httpAuth, - CompileController.compileSubmission - ) - publicApiRouter.get( - /^\/api\/clsi\/compile\/([^/]*)\/build\/([0-9a-f-]+)\/output\/(.*)$/, - function(req, res, next) { - const params = { - submission_id: req.params[0], - build_id: req.params[1], - file: req.params[2] - } - req.params = params - next() - }, - AuthenticationController.httpAuth, - CompileController.getFileFromClsiWithoutUser - ) - publicApiRouter.post( - '/api/institutions/confirm_university_domain', - RateLimiterMiddleware.rateLimit({ - endpointName: 'confirm-university-domain', - maxRequests: 1, - timeInterval: 60 - }), - AuthenticationController.httpAuth, - InstitutionsController.confirmDomain - ) - - webRouter.get('/chrome', function(req, res, next) { - // Match v1 behaviour - this is used for a Chrome web app - if (AuthenticationController.isUserLoggedIn(req)) { - res.redirect('/project') - } else { - res.redirect('/register') - } - }) - - // Admin Stuff - webRouter.get( - '/admin', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.index - ) - webRouter.get( - '/admin/user', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - (req, res) => res.redirect('/admin/register') - ) // this gets removed by admin-panel addon - webRouter.get( - '/admin/register', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.registerNewUser - ) - webRouter.post( - '/admin/register', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - UserController.register - ) - webRouter.post( - '/admin/closeEditor', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.closeEditor - ) - webRouter.post( - '/admin/dissconectAllUsers', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.dissconectAllUsers - ) - webRouter.post( - '/admin/syncUserToSubscription', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.syncUserToSubscription - ) - webRouter.post( - '/admin/flushProjectToTpds', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.flushProjectToTpds - ) - webRouter.post( - '/admin/pollDropboxForUser', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.pollDropboxForUser - ) - webRouter.post( - '/admin/messages', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.createMessage - ) - webRouter.post( - '/admin/messages/clear', - AuthorizationMiddleware.ensureUserIsSiteAdmin, - AdminController.clearMessages - ) - - privateApiRouter.post( - '/disconnectAllUsers', - AdminController.dissconectAllUsers - ) - - privateApiRouter.get('/perfTest', (req, res) => res.send('hello')) - - publicApiRouter.get('/status', (req, res) => - res.send('web sharelatex is alive (web)') - ) - privateApiRouter.get('/status', (req, res) => - res.send('web sharelatex is alive (api)') - ) - - webRouter.get('/dev/csrf', (req, res) => res.send(res.locals.csrfToken)) - - publicApiRouter.get('/health_check', HealthCheckController.check) - privateApiRouter.get('/health_check', HealthCheckController.check) - - publicApiRouter.get('/health_check/redis', HealthCheckController.checkRedis) - privateApiRouter.get( - '/health_check/redis', - HealthCheckController.checkRedis - ) - - publicApiRouter.get('/health_check/mongo', HealthCheckController.checkMongo) - privateApiRouter.get( - '/health_check/mongo', - HealthCheckController.checkMongo - ) - - webRouter.get( - '/status/compiler/:Project_id', - AuthorizationMiddleware.ensureUserCanReadProject, - function(req, res) { - const projectId = req.params.Project_id - const sendRes = _.once(function(statusCode, message) { - res.status(statusCode) - res.send(message) - ClsiCookieManager.clearServerId(projectId) - }) // force every compile to a new server - // set a timeout - var handler = setTimeout(function() { - sendRes(500, 'Compiler timed out') - handler = null - }, 10000) - // use a valid user id for testing - const testUserId = '123456789012345678901234' - // run the compile - CompileManager.compile(projectId, testUserId, {}, function( - error, - status - ) { - if (handler) { - clearTimeout(handler) - } - if (error) { - sendRes(500, `Compiler returned error ${error.message}`) - } else if (status === 'success') { - sendRes(200, 'Compiler returned in less than 10 seconds') - } else { - sendRes(500, `Compiler returned failure ${status}`) - } - }) - } - ) - - webRouter.get('/no-cache', function(req, res, next) { - res.header('Cache-Control', 'max-age=0') - res.sendStatus(404) - }) - - webRouter.get('/oops-express', (req, res, next) => - next(new Error('Test error')) - ) - webRouter.get('/oops-internal', function(req, res, next) { + webRouter.get('/oops-express', (req, res, next) => + next(new Error('Test error')) + ) + webRouter.get('/oops-internal', function(req, res, next) { + throw new Error('Test error') + }) + webRouter.get('/oops-mongo', (req, res, next) => + require('./models/Project').Project.findOne({}, function() { throw new Error('Test error') }) - webRouter.get('/oops-mongo', (req, res, next) => - require('./models/Project').Project.findOne({}, function() { - throw new Error('Test error') - }) + ) + + privateApiRouter.get('/opps-small', function(req, res, next) { + logger.err('test error occured') + res.send() + }) + + webRouter.post('/error/client', function(req, res, next) { + logger.warn( + { err: req.body.error, meta: req.body.meta }, + 'client side error' ) + metrics.inc('client-side-error') + res.sendStatus(204) + }) - privateApiRouter.get('/opps-small', function(req, res, next) { - logger.err('test error occured') - res.send() - }) + webRouter.get( + '/read/:read_only_token([a-z]+)', + RateLimiterMiddleware.rateLimit({ + endpointName: 'read-only-token', + maxRequests: 15, + timeInterval: 60 + }), + TokenAccessController.readOnlyToken + ) - webRouter.post('/error/client', function(req, res, next) { - logger.warn( - { err: req.body.error, meta: req.body.meta }, - 'client side error' - ) - metrics.inc('client-side-error') - res.sendStatus(204) - }) + webRouter.get( + '/:read_and_write_token([0-9]+[a-z]+)', + RateLimiterMiddleware.rateLimit({ + endpointName: 'read-and-write-token', + maxRequests: 15, + timeInterval: 60 + }), + TokenAccessController.readAndWriteToken + ) - webRouter.get( - '/read/:read_only_token([a-z]+)', - RateLimiterMiddleware.rateLimit({ - endpointName: 'read-only-token', - maxRequests: 15, - timeInterval: 60 - }), - TokenAccessController.readOnlyToken - ) - - webRouter.get( - '/:read_and_write_token([0-9]+[a-z]+)', - RateLimiterMiddleware.rateLimit({ - endpointName: 'read-and-write-token', - maxRequests: 15, - timeInterval: 60 - }), - TokenAccessController.readAndWriteToken - ) - - webRouter.get('*', ErrorController.notFound) - } + webRouter.get('*', ErrorController.notFound) }