Browse Source

split site into 2 routers, webRouter and apiRouter

web router has things like sessions etc added onto it. Api router is minimal, doesn't include things like csrf
Henry Oswald 11 years ago
parent
commit
1cc0cbe8fc

+ 5 - 0
services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee

@@ -0,0 +1,5 @@
+AnalyticsController = require('./AnalyticsController')
+
+module.exports =
+	apply: (webRouter, apiRouter) ->
+		webRouter.post '/event/:event', AnalyticsController.recordEvent

+ 5 - 5
services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee

@@ -3,9 +3,9 @@ SecurityManager = require('../../managers/SecurityManager')
 AuthenticationController = require('../Authentication/AuthenticationController')
 
 module.exports =
-	apply: (app) ->
-		app.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject
-		app.get  '/project/:Project_id/collaborators', SecurityManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
+	apply: (webRouter, apiRouter) ->
+		webRouter.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject
+		apiRouter.get  '/project/:Project_id/collaborators', SecurityManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
 
-		app.post   '/project/:Project_id/users', SecurityManager.requestIsOwner, CollaboratorsController.addUserToProject
-		app.delete '/project/:Project_id/users/:user_id', SecurityManager.requestIsOwner, CollaboratorsController.removeUserFromProject
+		webRouter.post   '/project/:Project_id/users', SecurityManager.requestIsOwner, CollaboratorsController.addUserToProject
+		webRouter.delete '/project/:Project_id/users/:user_id', SecurityManager.requestIsOwner, CollaboratorsController.removeUserFromProject

+ 10 - 11
services/web/app/coffee/Features/Editor/EditorRouter.coffee

@@ -3,21 +3,20 @@ SecurityManager = require('../../managers/SecurityManager')
 AuthenticationController = require "../Authentication/AuthenticationController"
 
 module.exports =
-	apply: (app) ->
-		app.post   '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc
-		app.post   '/project/:Project_id/folder', SecurityManager.requestCanModifyProject, EditorHttpController.addFolder
+	apply: (webRouter, apiRouter) ->
+		webRouter.post   '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc
+		webRouter.post   '/project/:Project_id/folder', SecurityManager.requestCanModifyProject, EditorHttpController.addFolder
 
-		app.post   '/project/:Project_id/:entity_type/:entity_id/rename', SecurityManager.requestCanModifyProject, EditorHttpController.renameEntity
-		app.post   '/project/:Project_id/:entity_type/:entity_id/move', SecurityManager.requestCanModifyProject, EditorHttpController.moveEntity
+		webRouter.post   '/project/:Project_id/:entity_type/:entity_id/rename', SecurityManager.requestCanModifyProject, EditorHttpController.renameEntity
+		webRouter.post   '/project/:Project_id/:entity_type/:entity_id/move', SecurityManager.requestCanModifyProject, EditorHttpController.moveEntity
 
-		app.delete '/project/:Project_id/file/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFile
-		app.delete '/project/:Project_id/doc/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteDoc
-		app.delete '/project/:Project_id/folder/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFolder
+		webRouter.delete '/project/:Project_id/file/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFile
+		webRouter.delete '/project/:Project_id/doc/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteDoc
+		webRouter.delete '/project/:Project_id/folder/:entity_id', SecurityManager.requestCanModifyProject, EditorHttpController.deleteFolder
 
-		app.post   '/project/:Project_id/doc/:doc_id/restore', SecurityManager.requestCanModifyProject, EditorHttpController.restoreDoc
+		webRouter.post   '/project/:Project_id/doc/:doc_id/restore', SecurityManager.requestCanModifyProject, EditorHttpController.restoreDoc
 
 		# Called by the real-time API to load up the current project state.
 		# This is a post request because it's more than just a getting of data. We take actions
 		# whenever a user joins a project, like updating the deleted status.
-		app.post   '/project/:Project_id/join', AuthenticationController.httpAuth, EditorHttpController.joinProject
-		app.ignoreCsrf('post', '/project/:Project_id/join')
+		apiRouter.post '/project/:Project_id/join', AuthenticationController.httpAuth, EditorHttpController.joinProject

+ 5 - 5
services/web/app/coffee/Features/PasswordReset/PasswordResetRouter.coffee

@@ -2,13 +2,13 @@ PasswordResetController = require("./PasswordResetController")
 AuthenticationController = require('../Authentication/AuthenticationController')
 
 module.exports =
-	apply: (app) ->
+	apply: (webRouter, apiRouter) ->
 
-		app.get  '/user/password/reset', PasswordResetController.renderRequestResetForm
-		app.post  '/user/password/reset', PasswordResetController.requestReset
+		webRouter.get  '/user/password/reset', PasswordResetController.renderRequestResetForm
+		webRouter.post  '/user/password/reset', PasswordResetController.requestReset
 		AuthenticationController.addEndpointToLoginWhitelist '/user/password/reset'
 		
-		app.get '/user/password/set', PasswordResetController.renderSetPasswordForm
-		app.post '/user/password/set', PasswordResetController.setNewUserPassword
+		webRouter.get '/user/password/set', PasswordResetController.renderSetPasswordForm
+		webRouter.post '/user/password/set', PasswordResetController.setNewUserPassword
 		AuthenticationController.addEndpointToLoginWhitelist '/user/password/set'
 

+ 2 - 2
services/web/app/coffee/Features/RealTimeProxy/RealTimeProxyRouter.coffee

@@ -10,8 +10,8 @@ wsProxy = httpProxy.createProxyServer({
 })
 
 module.exports =
-	apply: (app) ->
-		app.all /\/socket\.io\/.*/, (req, res, next) ->
+	apply: (webRouter, apiRouter) ->
+		webRouter.all /\/socket\.io\/.*/, (req, res, next) ->
 			proxy.web req, res, next
 			
 		setTimeout () ->

+ 12 - 12
services/web/app/coffee/Features/StaticPages/StaticPagesRouter.coffee

@@ -3,18 +3,18 @@ UniversityController = require("./UniversityController")
 
 
 module.exports =
-	apply: (app) ->
-		app.get  '/', HomeController.index
-		app.get  '/home', HomeController.home
+	apply: (webRouter, apiRouter) ->
+		webRouter.get  '/', HomeController.index
+		webRouter.get  '/home', HomeController.home
 
-		app.get '/tos', HomeController.externalPage("tos", "Terms of Service")
-		app.get '/about', HomeController.externalPage("about", "About Us")
-		app.get '/security', HomeController.externalPage("security", "Security")
-		app.get '/privacy_policy', HomeController.externalPage("privacy", "Privacy Policy")
-		app.get '/planned_maintenance', HomeController.externalPage("planned_maintenance", "Planned Maintenance")
-		app.get '/style', HomeController.externalPage("style_guide", "Style Guide")
+		webRouter.get '/tos', HomeController.externalPage("tos", "Terms of Service")
+		webRouter.get '/about', HomeController.externalPage("about", "About Us")
+		webRouter.get '/security', HomeController.externalPage("security", "Security")
+		webRouter.get '/privacy_policy', HomeController.externalPage("privacy", "Privacy Policy")
+		webRouter.get '/planned_maintenance', HomeController.externalPage("planned_maintenance", "Planned Maintenance")
+		webRouter.get '/style', HomeController.externalPage("style_guide", "Style Guide")
 
-		app.get '/dropbox', HomeController.externalPage("dropbox", "Dropbox and ShareLaTeX")
+		webRouter.get '/dropbox', HomeController.externalPage("dropbox", "Dropbox and ShareLaTeX")
 
-		app.get '/university', UniversityController.getIndexPage
-		app.get '/university/*', UniversityController.getPage
+		webRouter.get '/university', UniversityController.getIndexPage
+		webRouter.get '/university/*', UniversityController.getPage

+ 22 - 23
services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee

@@ -4,44 +4,43 @@ SubscriptionGroupController = require './SubscriptionGroupController'
 Settings = require "settings-sharelatex"
 
 module.exports =
-	apply: (app) ->
+	apply: (webRouter, apiRouter) ->
 		return unless Settings.enableSubscriptions
 
-		app.get  '/user/subscription/plans',      SubscriptionController.plansPage
+		webRouter.get  '/user/subscription/plans',      SubscriptionController.plansPage
 
-		app.get  '/user/subscription',            AuthenticationController.requireLogin(), SubscriptionController.userSubscriptionPage
+		webRouter.get  '/user/subscription',            AuthenticationController.requireLogin(), SubscriptionController.userSubscriptionPage
 
-		app.get  '/user/subscription/custom_account', AuthenticationController.requireLogin(), SubscriptionController.userCustomSubscriptionPage
+		webRouter.get  '/user/subscription/custom_account', AuthenticationController.requireLogin(), SubscriptionController.userCustomSubscriptionPage
 
 		
-		app.get  '/user/subscription/new',        AuthenticationController.requireLogin(), SubscriptionController.paymentPage 
-		app.get  '/user/subscription/billing-details/edit', AuthenticationController.requireLogin(), SubscriptionController.editBillingDetailsPage
+		webRouter.get  '/user/subscription/new',        AuthenticationController.requireLogin(), SubscriptionController.paymentPage 
+		webRouter.get  '/user/subscription/billing-details/edit', AuthenticationController.requireLogin(), SubscriptionController.editBillingDetailsPage
 
-		app.get  '/user/subscription/thank-you', AuthenticationController.requireLogin(), SubscriptionController.successful_subscription
+		webRouter.get  '/user/subscription/thank-you', AuthenticationController.requireLogin(), SubscriptionController.successful_subscription
 
 
-		app.get '/subscription/group',  AuthenticationController.requireLogin(), SubscriptionGroupController.renderSubscriptionGroupAdminPage
-		app.post '/subscription/group/user', AuthenticationController.requireLogin(),  SubscriptionGroupController.addUserToGroup
-		app.get '/subscription/group/export',  AuthenticationController.requireLogin(), SubscriptionGroupController.exportGroupCsv
-		app.delete '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup
+		webRouter.get '/subscription/group',  AuthenticationController.requireLogin(), SubscriptionGroupController.renderSubscriptionGroupAdminPage
+		webRouter.post '/subscription/group/user', AuthenticationController.requireLogin(),  SubscriptionGroupController.addUserToGroup
+		webRouter.get '/subscription/group/export',  AuthenticationController.requireLogin(), SubscriptionGroupController.exportGroupCsv
+		webRouter.delete '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup
 
-		app.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage
-		app.post '/user/subscription/:subscription_id/group/begin-join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup
-		app.get '/user/subscription/:subscription_id/group/complete-join', AuthenticationController.requireLogin(), SubscriptionGroupController.completeJoin
-		app.get '/user/subscription/:subscription_id/group/successful-join', AuthenticationController.requireLogin(), SubscriptionGroupController.renderSuccessfulJoinPage
+		webRouter.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage
+		webRouter.post '/user/subscription/:subscription_id/group/begin-join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup
+		webRouter.get '/user/subscription/:subscription_id/group/complete-join', AuthenticationController.requireLogin(), SubscriptionGroupController.completeJoin
+		webRouter.get '/user/subscription/:subscription_id/group/successful-join', AuthenticationController.requireLogin(), SubscriptionGroupController.renderSuccessfulJoinPage
 
 		#recurly callback
-		app.post '/user/subscription/callback',   SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback
-		app.ignoreCsrf("post", '/user/subscription/callback')
+		apiRouter.post '/user/subscription/callback',   SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback
 
 		#user changes their account state
-		app.post '/user/subscription/create',     AuthenticationController.requireLogin(), SubscriptionController.createSubscription
-		app.post '/user/subscription/update',     AuthenticationController.requireLogin(), SubscriptionController.updateSubscription
-		app.post '/user/subscription/cancel',     AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
-		app.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
+		webRouter.post '/user/subscription/create',     AuthenticationController.requireLogin(), SubscriptionController.createSubscription
+		webRouter.post '/user/subscription/update',     AuthenticationController.requireLogin(), SubscriptionController.updateSubscription
+		webRouter.post '/user/subscription/cancel',     AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
+		webRouter.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
 
 
-		app.get "/user/subscription/upgrade-annual",  AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage
-		app.post "/user/subscription/upgrade-annual",  AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan
+		webRouter.get "/user/subscription/upgrade-annual",  AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage
+		webRouter.post "/user/subscription/upgrade-annual",  AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan
 
 

+ 3 - 3
services/web/app/coffee/Features/Uploads/UploadsRouter.coffee

@@ -3,11 +3,11 @@ AuthenticationController = require('../Authentication/AuthenticationController')
 ProjectUploadController = require "./ProjectUploadController"
 
 module.exports =
-	apply: (app) ->
-		app.post '/project/new/upload',
+	apply: (webRouter, apiRouter) ->
+		webRouter.post '/project/new/upload',
 			AuthenticationController.requireLogin(),
 			ProjectUploadController.uploadProject
-		app.post '/Project/:Project_id/upload',
+		webRouter.post '/Project/:Project_id/upload',
 			SecurityManager.requestCanModifyProject,
 			ProjectUploadController.uploadFile
 

+ 3 - 3
services/web/app/coffee/infrastructure/ExpressLocals.coffee

@@ -39,7 +39,7 @@ for path in [
 		logger.log filePath:filePath, "file does not exist for fingerprints"
 	
 
-module.exports = (app)->
+module.exports = (app, webRouter, apiRouter)->
 	app.use (req, res, next)->
 		res.locals.session = req.session
 		next()
@@ -94,8 +94,8 @@ module.exports = (app)->
 			return ""
 		next()
 
-	app.use (req, res, next) ->
-		res.locals.csrfToken = req.csrfToken()
+	webRouter.use (req, res, next) ->
+		res.locals.csrfToken = req?.csrfToken()
 		next()
 
 	app.use (req, res, next) ->

+ 2 - 2
services/web/app/coffee/infrastructure/Modules.coffee

@@ -13,9 +13,9 @@ module.exports = Modules =
 				loadedModule.name = moduleName
 				@modules.push loadedModule
 
-	applyRouter: (app) ->
+	applyRouter: (webRouter, apiRouter) ->
 		for module in @modules
-			module.router?.apply(app)
+			module.router?.apply(webRouter, apiRouter)
 			
 	viewIncludes: {}
 	loadViewIncludes: (app) ->

+ 34 - 31
services/web/app/coffee/infrastructure/Server.coffee

@@ -44,19 +44,31 @@ else
 
 app = express()
 
-ignoreCsrfRoutes = []
-app.ignoreCsrf = (method, route) ->
-	ignoreCsrfRoutes.push new express.Route(method, route)
-
+webRouter = express.Router()
+apiRouter = express.Router()
 
 if Settings.behindProxy
 	app.enable('trust proxy')
-app.use express.static(__dirname + '/../../../public', {maxAge: staticCacheAge })
+
+webRouter.use express.static(__dirname + '/../../../public', {maxAge: staticCacheAge })
 app.set 'views', __dirname + '/../../views'
 app.set 'view engine', 'jade'
 Modules.loadViewIncludes app
-app.use cookieParser(Settings.security.sessionSecret)
-app.use session
+
+
+
+app.use bodyParser.urlencoded({ extended: true })
+app.use bodyParser.json()
+app.use multer(dest: Settings.path.uploadFolder)
+app.use methodOverride()
+
+app.use metrics.http.monitor(logger)
+app.use RedirectManager
+app.use OldAssetProxy
+
+
+webRouter.use cookieParser(Settings.security.sessionSecret)
+webRouter.use session
 	resave: false
 	secret:Settings.security.sessionSecret
 	proxy: Settings.behindProxy
@@ -66,36 +78,23 @@ app.use session
 		secure: Settings.secureCookie
 	store: sessionStore
 	key: Settings.cookieName
-
-app.use bodyParser.urlencoded({ extended: true })
-app.use bodyParser.json()
-app.use multer(dest: Settings.path.uploadFolder)
-app.use translations.expressMiddlewear
-app.use translations.setLangBasedOnDomainMiddlewear
+webRouter.use csrfProtection
+webRouter.use translations.expressMiddlewear
+webRouter.use translations.setLangBasedOnDomainMiddlewear
 
 # Measure expiry from last request, not last login
-app.use (req, res, next) ->
+webRouter.use (req, res, next) ->
 	req.session.touch()
 	next()
 
-app.use (req, res, next) ->
-	for route in ignoreCsrfRoutes
-		if route.method == req.method?.toLowerCase() and route.match(req.path)
-			return next()
-	csrfProtection(req, res, next)
-	
-app.use ReferalConnect.use
-app.use methodOverride()
-
-expressLocals(app)
+webRouter.use ReferalConnect.use
+expressLocals(app, webRouter, apiRouter)
 
 if app.get('env') == 'production'
 	logger.info "Production Enviroment"
 	app.enable('view cache')
 
-app.use metrics.http.monitor(logger)
-app.use RedirectManager
-app.use OldAssetProxy
+
 
 app.use (req, res, next)->
 	metrics.inc "http-request"
@@ -109,12 +108,11 @@ app.use (req, res, next) ->
 	else
 		next()
 
-app.get "/status", (req, res)->
+apiRouter.get "/status", (req, res)->
 	res.send("web sharelatex is alive")
-	req.session.destroy()
 	
 profiler = require "v8-profiler"
-app.get "/profile", (req, res) ->
+apiRouter.get "/profile", (req, res) ->
 	time = parseInt(req.query.time || "1000")
 	profiler.startProfiling("test")
 	setTimeout () ->
@@ -125,7 +123,12 @@ app.get "/profile", (req, res) ->
 logger.info ("creating HTTP server").yellow
 server = require('http').createServer(app)
 
-router = new Router(app)
+# process api routes first, if nothing matched fall though and use
+# web middlewear + routes
+app.use(apiRouter)
+app.use(webRouter)
+
+router = new Router(webRouter, apiRouter)
 
 module.exports =
 	app: app

+ 101 - 96
services/web/app/coffee/router.coffee

@@ -40,65 +40,67 @@ logger = require("logger-sharelatex")
 _ = require("underscore")
 
 module.exports = class Router
-	constructor: (app)->
+	constructor: (webRouter, apiRouter)->
 		if !Settings.allowPublicAccess
-			app.all '*', AuthenticationController.requireGlobalLogin
+			webRouter.all '*', AuthenticationController.requireGlobalLogin
 
 		
-		app.get  '/login', UserPagesController.loginPage
+		webRouter.get  '/login', UserPagesController.loginPage
 		AuthenticationController.addEndpointToLoginWhitelist '/login'
 
-		app.post '/login', AuthenticationController.login
-		app.get  '/logout', UserController.logout
-		app.get  '/restricted', SecurityManager.restricted
+		webRouter.post '/login', AuthenticationController.login
+		webRouter.get  '/logout', UserController.logout
+		webRouter.get  '/restricted', SecurityManager.restricted
 
 		# Left as a placeholder for implementing a public register page
-		app.get  '/register', UserPagesController.registerPage
+		webRouter.get  '/register', UserPagesController.registerPage
 		AuthenticationController.addEndpointToLoginWhitelist '/register'
 
-		EditorRouter.apply(app)
-		CollaboratorsRouter.apply(app)
-		SubscriptionRouter.apply(app)
-		UploadsRouter.apply(app)
-		PasswordResetRouter.apply(app)
-		StaticPagesRouter.apply(app)
-		RealTimeProxyRouter.apply(app)
+
+		EditorRouter.apply(webRouter, apiRouter)
+		CollaboratorsRouter.apply(webRouter, apiRouter)
+		SubscriptionRouter.apply(webRouter, apiRouter)
+		UploadsRouter.apply(webRouter, apiRouter)
+		PasswordResetRouter.apply(webRouter, apiRouter)
+		StaticPagesRouter.apply(webRouter, apiRouter)
+		RealTimeProxyRouter.apply(webRouter, apiRouter)
+		AnalyticsRouter.apply(webRouter, apiRouter)
 		
-		Modules.applyRouter(app)
+		Modules.applyRouter(webRouter, apiRouter)
 
-		app.get '/blog', BlogController.getIndexPage
-		app.get '/blog/*', BlogController.getPage
 
 		if Settings.enableSubscriptions
-			app.get  '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus
-
-		app.get  '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage
-		app.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings
-		app.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword
+			webRouter.get  '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus
+		
+		webRouter.get '/blog', BlogController.getIndexPage
+		webRouter.get '/blog/*', BlogController.getPage
+		
+		webRouter.get  '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage
+		webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings
+		webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword
 
-		app.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
-		app.delete '/user', AuthenticationController.requireLogin(), UserController.deleteUser
+		webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
+		webRouter.delete '/user', AuthenticationController.requireLogin(), UserController.deleteUser
 
-		app.get  '/user/auth_token', AuthenticationController.requireLogin(), AuthenticationController.getAuthToken
-		app.get  '/user/personal_info', AuthenticationController.requireLogin(allow_auth_token: true), UserInfoController.getLoggedInUsersPersonalInfo
-		app.get  '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
+		webRouter.get  '/user/auth_token', AuthenticationController.requireLogin(), AuthenticationController.getAuthToken
+		webRouter.get  '/user/personal_info', AuthenticationController.requireLogin(allow_auth_token: true), UserInfoController.getLoggedInUsersPersonalInfo
+		apiRouter.get  '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
 
-		app.get  '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
-		app.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject
+		webRouter.get  '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
+		webRouter.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject
 
-		app.get  '/Project/:Project_id', RateLimiterMiddlewear.rateLimit({
+		webRouter.get  '/Project/:Project_id', RateLimiterMiddlewear.rateLimit({
 			endpointName: "open-project"
 			params: ["Project_id"]
 			maxRequests: 10
 			timeInterval: 60
 		}), SecurityManager.requestCanAccessProject, ProjectController.loadEditor
-		app.get  '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile
+		webRouter.get  '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile
+		webRouter.post '/project/:Project_id/settings', SecurityManager.requestCanModifyProject, ProjectController.updateProjectSettings
 
-		app.post '/project/:Project_id/settings', SecurityManager.requestCanModifyProject, ProjectController.updateProjectSettings
-
-		app.post '/project/:Project_id/compile', SecurityManager.requestCanAccessProject, CompileController.compile
-		app.get  '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf
-		app.get  /^\/project\/([^\/]*)\/output\/(.*)$/,
+		webRouter.post '/project/:Project_id/compile', SecurityManager.requestCanAccessProject, CompileController.compile
+		webRouter.get  '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf
+		webRouter.get  /^\/project\/([^\/]*)\/output\/(.*)$/,
 			((req, res, next) ->
 				params =
 					"Project_id": req.params[0]
@@ -106,78 +108,82 @@ module.exports = class Router
 				req.params = params
 				next()
 			), SecurityManager.requestCanAccessProject, CompileController.getFileFromClsi
-		app.delete "/project/:Project_id/output", SecurityManager.requestCanAccessProject, CompileController.deleteAuxFiles
-		app.get "/project/:Project_id/sync/code", SecurityManager.requestCanAccessProject, CompileController.proxySync
-		app.get "/project/:Project_id/sync/pdf", SecurityManager.requestCanAccessProject, CompileController.proxySync
-
-		app.delete '/Project/:Project_id', SecurityManager.requestIsOwner, ProjectController.deleteProject
-		app.post '/Project/:Project_id/restore', SecurityManager.requestIsOwner, ProjectController.restoreProject
-		app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject
+		webRouter.delete "/project/:Project_id/output", SecurityManager.requestCanAccessProject, CompileController.deleteAuxFiles
+		webRouter.get "/project/:Project_id/sync/code", SecurityManager.requestCanAccessProject, CompileController.proxySync
+		webRouter.get "/project/:Project_id/sync/pdf", SecurityManager.requestCanAccessProject, CompileController.proxySync
 
-		app.post '/project/:Project_id/rename', SecurityManager.requestIsOwner, ProjectController.renameProject
+		webRouter.delete '/Project/:Project_id', SecurityManager.requestIsOwner, ProjectController.deleteProject
+		webRouter.post '/Project/:Project_id/restore', SecurityManager.requestIsOwner, ProjectController.restoreProject
+		webRouter.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject
 
-		app.get  "/project/:Project_id/updates", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
-		app.get  "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
-		app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
+		webRouter.post '/project/:Project_id/rename', SecurityManager.requestIsOwner, ProjectController.renameProject
 
-		app.get  '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
-		app.get  '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
+		webRouter.get  "/project/:Project_id/updates", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
+		webRouter.get  "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
+		webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
 
-		app.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
-		app.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate
+		webRouter.get  '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
+		webRouter.get  '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
 
-		app.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
+		webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
+		webRouter.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate
 
-		app.get '/internal/project/:Project_id/zip', AuthenticationController.httpAuth, ProjectDownloadsController.downloadProject
-		app.get '/internal/project/:project_id/compile/pdf', AuthenticationController.httpAuth, CompileController.compileAndDownloadPdf
+		# Deprecated in favour of /internal/project/:project_id but still used by versioning
+		apiRouter.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
 
+		# New 'stable' /internal API end points
+		apiRouter.get  '/internal/project/:project_id',     AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
+		apiRouter.get  '/internal/project/:Project_id/zip', AuthenticationController.httpAuth, ProjectDownloadsController.downloadProject
+		
+		webRouter.get  /^\/internal\/project\/([^\/]*)\/output\/(.*)$/,
+			((req, res, next) ->
+				params =
+					"Project_id": req.params[0]
+					"file":       req.params[1]
+				req.params = params
+				next()
+			), AuthenticationController.httpAuth, CompileController.getFileFromClsi
 
-		app.get  '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.getDocument
-		app.post '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.setDocument
-		app.ignoreCsrf('post', '/project/:Project_id/doc/:doc_id')
+		apiRouter.get  '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.getDocument
+		apiRouter.post '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.setDocument
 
-		app.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate
-		app.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate
-		app.ignoreCsrf('post', '/user/:user_id/update/*')
-		app.ignoreCsrf('delete', '/user/:user_id/update/*')
+		apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate
+		apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate
 		
-		app.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents
-		app.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents
-		app.ignoreCsrf('post', '/project/:project_id/contents/*')
-		app.ignoreCsrf('delete', '/project/:project_id/contents/*')
+		apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents
+		apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents
 
-		app.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
-		app.post "/spelling/learn", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
+		webRouter.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
+		webRouter.post "/spelling/learn", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
 
-		app.get  "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages
-		app.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage
+		webRouter.get  "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages
+		webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage
 		
-		app.get  /learn(\/.*)?/, WikiController.getPage
+		webRouter.get  /learn(\/.*)?/, WikiController.getPage
 
 		#Admin Stuff
-		app.get  '/admin', SecurityManager.requestIsAdmin, AdminController.index
-		app.get  '/admin/register', SecurityManager.requestIsAdmin, AdminController.registerNewUser
-		app.post '/admin/register', SecurityManager.requestIsAdmin, UserController.register
-		app.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
-		app.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
-		app.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
-		app.post '/admin/flushProjectToTpds', SecurityManager.requestIsAdmin, AdminController.flushProjectToTpds
-		app.post '/admin/pollDropboxForUser', SecurityManager.requestIsAdmin, AdminController.pollDropboxForUser
-		app.post '/admin/messages', SecurityManager.requestIsAdmin, AdminController.createMessage
-		app.post '/admin/messages/clear', SecurityManager.requestIsAdmin, AdminController.clearMessages
-
-		app.get '/perfTest', (req,res)->
+		webRouter.get  '/admin', SecurityManager.requestIsAdmin, AdminController.index
+		webRouter.get  '/admin/register', SecurityManager.requestIsAdmin, AdminController.registerNewUser
+		webRouter.post '/admin/register', SecurityManager.requestIsAdmin, UserController.register
+		webRouter.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
+		webRouter.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
+		webRouter.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
+		webRouter.post '/admin/flushProjectToTpds', SecurityManager.requestIsAdmin, AdminController.flushProjectToTpds
+		webRouter.post '/admin/pollDropboxForUser', SecurityManager.requestIsAdmin, AdminController.pollDropboxForUser
+		webRouter.post '/admin/messages', SecurityManager.requestIsAdmin, AdminController.createMessage
+		webRouter.post '/admin/messages/clear', SecurityManager.requestIsAdmin, AdminController.clearMessages
+
+		apiRouter.get '/perfTest', (req,res)->
 			res.send("hello")
-			req.session.destroy()
 
-		app.get '/status', (req,res)->
+		apiRouter.get '/status', (req,res)->
 			res.send("websharelatex is up")
-			req.session.destroy()
+		
 
-		app.get '/health_check', HealthCheckController.check
-		app.get '/health_check/redis', HealthCheckController.checkRedis
+		webRouter.get '/health_check', HealthCheckController.check
+		webRouter.get '/health_check/redis', HealthCheckController.checkRedis
 
-		app.get "/status/compiler/:Project_id", SecurityManager.requestCanAccessProject, (req, res) ->
+		apiRouter.get "/status/compiler/:Project_id", SecurityManager.requestCanAccessProject, (req, res) ->
 			sendRes = _.once (statusCode, message)->
 				res.writeHead statusCode
 				res.end message
@@ -186,27 +192,26 @@ module.exports = class Router
 			setTimeout (() ->
 				sendRes 500, "Compiler timed out"
 			), 10000
-			req.session.destroy()
 
-		app.get "/ip", (req, res, next) ->
+		apiRouter.get "/ip", (req, res, next) ->
 			res.send({
 				ip: req.ip
 				ips: req.ips
 				headers: req.headers
 			})
 
-		app.get '/oops-express', (req, res, next) -> next(new Error("Test error"))
-		app.get '/oops-internal', (req, res, next) -> throw new Error("Test error")
-		app.get '/oops-mongo', (req, res, next) ->
+		apiRouter.get '/oops-express', (req, res, next) -> next(new Error("Test error"))
+		apiRouter.get '/oops-internal', (req, res, next) -> throw new Error("Test error")
+		apiRouter.get '/oops-mongo', (req, res, next) ->
 			require("./models/Project").Project.findOne {}, () ->
 				throw new Error("Test error")
 
-		app.get '/opps-small', (req, res, next)->
+		apiRouter.get '/opps-small', (req, res, next)->
 			logger.err "test error occured"
 			res.send()
 
-		app.post '/error/client', (req, res, next) ->
+		webRouter.post '/error/client', (req, res, next) ->
 			logger.error err: req.body.error, meta: req.body.meta, "client side error"
 			res.send(204)
 
-		app.get '*', ErrorController.notFound
+		webRouter.get '*', ErrorController.notFound