diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 7428bd4b40..5b6fe447f2 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -6,6 +6,7 @@ Metrics = require('../../infrastructure/Metrics') logger = require("logger-sharelatex") querystring = require('querystring') Url = require("url") +Settings = require "settings-sharelatex" module.exports = AuthenticationController = login: (req, res, next = (error) ->) -> @@ -84,6 +85,26 @@ module.exports = AuthenticationController = return doRequest + _globalLoginWhitelist: [] + addEndpointToLoginWhitelist: (endpoint) -> + AuthenticationController._globalLoginWhitelist.push endpoint + + requireGlobalLogin: (req, res, next) -> + if req.url in AuthenticationController._globalLoginWhitelist + return next() + + if req.headers['authorization']? + return AuthenticationController.httpAuth(req, res, next) + else if req.session.user? + return next() + else + return res.redirect "/login" + + httpAuth: require('express').basicAuth (user, pass)-> + isValid = Settings.httpAuthUsers[user] == pass + if !isValid + logger.err user:user, pass:pass, "invalid login details" + return isValid _redirectToLoginOrRegisterPage: (req, res)-> if req.query.zipUrl? or req.query.project_name? diff --git a/services/web/app/coffee/Features/Editor/EditorRouter.coffee b/services/web/app/coffee/Features/Editor/EditorRouter.coffee index a7295722ac..1902506948 100644 --- a/services/web/app/coffee/Features/Editor/EditorRouter.coffee +++ b/services/web/app/coffee/Features/Editor/EditorRouter.coffee @@ -1,8 +1,9 @@ EditorHttpController = require('./EditorHttpController') SecurityManager = require('../../managers/SecurityManager') +AuthenticationController = require "../Authentication/AuthenticationController" module.exports = - apply: (app, httpAuth) -> + apply: (app) -> app.post '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc app.post '/project/:Project_id/folder', SecurityManager.requestCanModifyProject, EditorHttpController.addFolder @@ -18,5 +19,5 @@ module.exports = # 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', httpAuth, EditorHttpController.joinProject + app.post '/project/:Project_id/join', AuthenticationController.httpAuth, EditorHttpController.joinProject app.ignoreCsrf('post', '/project/:Project_id/join') \ No newline at end of file diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 5e1ee1ba87..ae4121a456 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -39,25 +39,25 @@ RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter') logger = require("logger-sharelatex") _ = require("underscore") -httpAuth = require('express').basicAuth (user, pass)-> - isValid = Settings.httpAuthUsers[user] == pass - if !isValid - logger.err user:user, pass:pass, "invalid login details" - return isValid - module.exports = class Router constructor: (app)-> + if !Settings.allowPublicAccess + app.all '*', AuthenticationController.requireGlobalLogin + app.use(app.router) app.get '/login', UserPagesController.loginPage + AuthenticationController.addEndpointToLoginWhitelist '/login' + app.post '/login', AuthenticationController.login app.get '/logout', UserController.logout app.get '/restricted', SecurityManager.restricted # Left as a placeholder for implementing a public register page app.get '/register', UserPagesController.registerPage + AuthenticationController.addEndpointToLoginWhitelist '/register' - EditorRouter.apply(app, httpAuth) + EditorRouter.apply(app) CollaboratorsRouter.apply(app) SubscriptionRouter.apply(app) UploadsRouter.apply(app) @@ -82,7 +82,7 @@ module.exports = class Router 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', httpAuth, UserInfoController.getPersonalInfo + app.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 @@ -127,23 +127,23 @@ module.exports = class Router app.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags app.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate - app.get '/project/:project_id/details', httpAuth, ProjectApiController.getProjectDetails + app.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails - app.get '/internal/project/:Project_id/zip', httpAuth, ProjectDownloadsController.downloadProject - app.get '/internal/project/:project_id/compile/pdf', httpAuth, CompileController.compileAndDownloadPdf + app.get '/internal/project/:Project_id/zip', AuthenticationController.httpAuth, ProjectDownloadsController.downloadProject + app.get '/internal/project/:project_id/compile/pdf', AuthenticationController.httpAuth, CompileController.compileAndDownloadPdf - app.get '/project/:Project_id/doc/:doc_id', httpAuth, DocumentController.getDocument - app.post '/project/:Project_id/doc/:doc_id', httpAuth, DocumentController.setDocument + 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') - app.post '/user/:user_id/update/*', httpAuth, TpdsController.mergeUpdate - app.del '/user/:user_id/update/*', httpAuth, TpdsController.deleteUpdate + app.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate + app.del '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate app.ignoreCsrf('post', '/user/:user_id/update/*') app.ignoreCsrf('delete', '/user/:user_id/update/*') - app.post '/project/:project_id/contents/*', httpAuth, TpdsController.updateProjectContents - app.del '/project/:project_id/contents/*', httpAuth, TpdsController.deleteProjectContents + app.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents + app.del '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents app.ignoreCsrf('post', '/project/:project_id/contents/*') app.ignoreCsrf('delete', '/project/:project_id/contents/*') diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index bbbec571e3..22cf938d66 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -232,6 +232,10 @@ module.exports = # Cookie max age (in milliseconds). Set to false for a browser session. cookieSessionLength: 5 * 24 * 60 * 60 * 1000 # 5 days + + # Should we allow access to any page without logging in? This includes + # public projects, /learn, /templates, about pages, etc. + allowPublicAccess: false # Internal configs # ---------------- diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee index 41b4694d4e..37d36e4268 100644 --- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee @@ -19,6 +19,7 @@ describe "AuthenticationController", -> "../../infrastructure/Metrics": @Metrics = { inc: sinon.stub() } "../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() } "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "settings-sharelatex": {} @user = _id: ObjectId() email: @email = "USER@example.com" @@ -275,6 +276,46 @@ describe "AuthenticationController", -> .calledWith(@req, {allow_auth_token: true}) .should.equal true + describe "requireGlobalLogin", -> + beforeEach -> + @req.headers = {} + @AuthenticationController.httpAuth = sinon.stub() + + describe "with white listed url", -> + beforeEach -> + @AuthenticationController.addEndpointToLoginWhitelist "/login" + @req.url = "/login" + @AuthenticationController.requireGlobalLogin @req, @res, @next + + it "should call next() directly", -> + @next.called.should.equal true + + describe "with http auth", -> + beforeEach -> + @req.headers["authorization"] = "Mock Basic Auth" + @AuthenticationController.requireGlobalLogin @req, @res, @next + + it "should pass the request onto httpAuth", -> + @AuthenticationController.httpAuth + .calledWith(@req, @res, @next) + .should.equal true + + describe "with a user session", -> + beforeEach -> + @req.session = + user: {"mock": "user"} + @AuthenticationController.requireGlobalLogin @req, @res, @next + + it "should call next() directly", -> + @next.called.should.equal true + + describe "with no login credentials", -> + beforeEach -> + @AuthenticationController.requireGlobalLogin @req, @res, @next + + it "should redirect to the /login page", -> + @res.redirectedTo.should.equal "/login" + describe "_redirectToLoginOrRegisterPage", -> beforeEach -> @middleware = @AuthenticationController.requireLogin(@options = { load_from_db: false }) diff --git a/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee b/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee index 9c83844654..e955e084bb 100644 --- a/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee +++ b/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee @@ -7,6 +7,6 @@ class MockRequest query: {} i18n: translate:-> - + module.exports = MockRequest