Эх сурвалжийг харах

Merge branch 'master' into ja-review-panel

Paulo Reis 10 жил өмнө
parent
commit
ba5118d02a
100 өөрчлөгдсөн 631 нэмэгдсэн , 614 устгасан
  1. 45 40
      services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee
  2. 24 0
      services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee
  3. 33 0
      services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee
  4. 16 10
      services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
  5. 24 0
      services/web/app/coffee/Features/Blog/BlogHandler.coffee
  6. 1 1
      services/web/app/coffee/Features/Email/EmailSender.coffee
  7. 2 2
      services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee
  8. 2 0
      services/web/app/coffee/Features/Project/ProjectController.coffee
  9. 0 26
      services/web/app/coffee/Features/Security/SessionInvalidator.coffee
  10. 5 1
      services/web/app/coffee/Features/User/UserController.coffee
  11. 21 17
      services/web/app/coffee/Features/User/UserSessionsManager.coffee
  12. 21 0
      services/web/app/coffee/Features/User/UserSessionsRedis.coffee
  13. 1 1
      services/web/app/coffee/infrastructure/ExpressLocals.coffee
  14. 5 1
      services/web/app/coffee/infrastructure/Modules.coffee
  15. 1 1
      services/web/app/coffee/infrastructure/PackageVersions.coffee
  16. 11 5
      services/web/app/coffee/infrastructure/Server.coffee
  17. 4 0
      services/web/app/coffee/router.coffee
  18. 5 2
      services/web/app/views/general/500.jade
  19. 110 373
      services/web/app/views/project/editor/header.jade
  20. 5 1
      services/web/app/views/project/list.jade
  21. 6 0
      services/web/app/views/user/settings.jade
  22. 32 24
      services/web/config/settings.defaults.coffee
  23. 209 65
      services/web/npm-shrinkwrap.json
  24. 5 5
      services/web/package.json
  25. 0 17
      services/web/public/coffee/ide.coffee
  26. 1 1
      services/web/public/coffee/ide/editor/Document.coffee
  27. 12 9
      services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee
  28. 1 0
      services/web/public/coffee/main.coffee
  29. 20 0
      services/web/public/coffee/main/announcements.coffee
  30. 0 10
      services/web/public/coffee/main/event.coffee
  31. 1 1
      services/web/public/coffee/main/project-list/project-list.coffee
  32. 1 1
      services/web/public/js/ace-1.2.5/worker-latex.js
  33. 1 0
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/.gitattributes
  34. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-EUC-H.bcmap
  35. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-EUC-V.bcmap
  36. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-H.bcmap
  37. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-RKSJ-H.bcmap
  38. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-RKSJ-V.bcmap
  39. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-V.bcmap
  40. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78ms-RKSJ-H.bcmap
  41. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78ms-RKSJ-V.bcmap
  42. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/83pv-RKSJ-H.bcmap
  43. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90ms-RKSJ-H.bcmap
  44. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90ms-RKSJ-V.bcmap
  45. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90msp-RKSJ-H.bcmap
  46. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90msp-RKSJ-V.bcmap
  47. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90pv-RKSJ-H.bcmap
  48. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90pv-RKSJ-V.bcmap
  49. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-H.bcmap
  50. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-RKSJ-H.bcmap
  51. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-RKSJ-V.bcmap
  52. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-V.bcmap
  53. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-0.bcmap
  54. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-1.bcmap
  55. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-2.bcmap
  56. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-3.bcmap
  57. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-4.bcmap
  58. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-5.bcmap
  59. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-6.bcmap
  60. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-UCS2.bcmap
  61. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-0.bcmap
  62. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-1.bcmap
  63. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-2.bcmap
  64. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-3.bcmap
  65. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-4.bcmap
  66. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-5.bcmap
  67. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-UCS2.bcmap
  68. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-0.bcmap
  69. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-1.bcmap
  70. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-2.bcmap
  71. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-3.bcmap
  72. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-4.bcmap
  73. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-5.bcmap
  74. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-6.bcmap
  75. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-UCS2.bcmap
  76. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-0.bcmap
  77. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-1.bcmap
  78. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-2.bcmap
  79. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-UCS2.bcmap
  80. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5-H.bcmap
  81. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5-V.bcmap
  82. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5pc-H.bcmap
  83. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5pc-V.bcmap
  84. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS-EUC-H.bcmap
  85. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS-EUC-V.bcmap
  86. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS1-H.bcmap
  87. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS1-V.bcmap
  88. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS2-H.bcmap
  89. 3 0
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS2-V.bcmap
  90. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETHK-B5-H.bcmap
  91. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETHK-B5-V.bcmap
  92. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETen-B5-H.bcmap
  93. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETen-B5-V.bcmap
  94. 3 0
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETenms-B5-H.bcmap
  95. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETenms-B5-V.bcmap
  96. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/EUC-H.bcmap
  97. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/EUC-V.bcmap
  98. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-H.bcmap
  99. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-RKSJ-H.bcmap
  100. BIN
      services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-RKSJ-V.bcmap

+ 45 - 40
services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee

@@ -1,43 +1,48 @@
-Settings = require "settings-sharelatex"
+settings = require "settings-sharelatex"
 logger = require "logger-sharelatex"
 _ = require "underscore"
+request = require "request"
 
-if !Settings.analytics?.postgres?
-	module.exports =
-		recordEvent: (user_id, event, segmentation, callback = () ->) ->
-			logger.log {user_id, event, segmentation}, "no event tracking configured, logging event"
-			callback()
-else
-	Sequelize = require "sequelize"
-	options = _.extend {logging:false}, Settings.analytics.postgres
-
-	sequelize = new Sequelize(
-		Settings.analytics.postgres.database,
-		Settings.analytics.postgres.username,
-		Settings.analytics.postgres.password,
-		options
-	)
-	
-	Event = sequelize.define("Event", {
-		user_id: Sequelize.STRING,
-		event: Sequelize.STRING,
-		segmentation: Sequelize.JSONB
-	})
-
-	module.exports =
-		recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
-			if user_id? and typeof(user_id) != "string"
-				user_id = user_id.toString()
-			if user_id == Settings.smokeTest?.userId
-				# Don't record smoke tests analytics
-				return callback()
-			Event
-				.create({ user_id, event, segmentation })
-				.then(
-					(result) -> callback(),
-					(error) ->
-						logger.err {err: error, user_id, event, segmentation}, "error recording analytics event"
-						callback(error)
-				)
-			
-		sync: () -> sequelize.sync()
+
+makeRequest = (opts, callback)->
+	if settings.apis?.analytics?.url?
+		urlPath = opts.url
+		opts.url = "#{settings.apis.analytics.url}#{urlPath}"
+		request opts, callback
+	else
+		callback()
+
+
+
+module.exports =
+
+
+	recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
+		if user_id == settings.smokeTest?.userId
+			return callback()
+		opts =
+			body:
+				event:event
+				segmentation:segmentation
+			json:true
+			method:"POST"
+			timeout:1000
+			url: "/user/#{user_id}/event"
+		makeRequest opts, callback
+
+
+	getLastOccurance: (user_id, event, callback = (error) ->) ->
+		opts =
+			body:
+				event:event
+			json:true
+			method:"POST"
+			timeout:1000
+			url: "/user/#{user_id}/event/last_occurnace"
+		makeRequest opts, (err, response, body)->
+			if err? 
+				console.log response, opts
+				logger.err {user_id, err}, "error getting last occurance of event"
+				return callback err
+			else
+				return callback null, body

+ 24 - 0
services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee

@@ -0,0 +1,24 @@
+AnnouncementsHandler = require("./AnnouncementsHandler")
+AuthenticationController = require("../Authentication/AuthenticationController")
+logger = require("logger-sharelatex")
+
+
+module.exports =
+
+	getUndreadAnnouncements: (req, res, next)->
+		if !settings?.apis?.analytics?.url? or !settings.apis.blog.url?
+			return res.json []
+
+		user_id = AuthenticationController.getLoggedInUserId(req)
+		logger.log {user_id}, "getting unread announcements"
+		AnnouncementsHandler.getUnreadAnnouncements user_id, (err, announcements)->
+			if err?
+				logger.err {err, user_id}, "unable to get unread announcements"
+				next(err)
+			else
+				res.json announcements
+
+
+
+
+

+ 33 - 0
services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee

@@ -0,0 +1,33 @@
+AnalyticsManager = require("../Analytics/AnalyticsManager")
+BlogHandler = require("../Blog/BlogHandler")
+async = require("async")
+_ = require("lodash")
+logger = require("logger-sharelatex")
+settings = require("settings-sharelatex")
+
+module.exports =
+
+	getUnreadAnnouncements : (user_id, callback = (err, announcements)->)->
+		async.parallel {
+			lastEvent: (cb)->
+				AnalyticsManager.getLastOccurance user_id, "announcement-alert-dismissed", cb
+			announcements: (cb)->
+				BlogHandler.getLatestAnnouncements cb
+		}, (err, results)->
+			if err?
+				logger.err err:err, user_id:user_id, "error getting unread announcements"
+				return callback(err)
+			
+			announcements = _.sortBy(results.announcements, "date").reverse()
+
+			lastSeenBlogId = results?.lastEvent?.segmentation?.blogPostId
+
+			announcementIndex = _.findIndex announcements, (announcement)-> 
+				announcement.id == lastSeenBlogId
+
+			if announcementIndex != -1
+				announcements = announcements.slice(0, announcementIndex)
+
+			logger.log announcementsLength:announcements?.length, user_id:user_id, "returning announcements"
+
+			callback null, announcements

+ 16 - 10
services/web/app/coffee/Features/Authentication/AuthenticationController.coffee

@@ -32,21 +32,27 @@ module.exports = AuthenticationController =
 
 	afterLoginSessionSetup: (req, user, callback=(err)->) ->
 		req.login user, (err) ->
+			if err?
+				logger.err {user_id: user._id, err}, "error from req.login"
+				return callback(err)
 			# Regenerate the session to get a new sessionID (cookie value) to
 			# protect against session fixation attacks
 			oldSession = req.session
-			req.session.destroy()
-			req.sessionStore.generate(req)
-			for key, value of oldSession
-				req.session[key] = value
-			# copy to the old `session.user` location, for backward-comptability
-			req.session.user = req.session.passport.user
-			req.session.save (err) ->
+			req.session.destroy (err) ->
 				if err?
-					logger.err {user_id: user._id}, "error saving regenerated session after login"
+					logger.err {user_id: user._id, err}, "error when trying to destroy old session"
 					return callback(err)
-				UserSessionsManager.trackSession(user, req.sessionID, () ->)
-				callback(null)
+				req.sessionStore.generate(req)
+				for key, value of oldSession
+					req.session[key] = value
+				# copy to the old `session.user` location, for backward-comptability
+				req.session.user = req.session.passport.user
+				req.session.save (err) ->
+					if err?
+						logger.err {user_id: user._id}, "error saving regenerated session after login"
+						return callback(err)
+					UserSessionsManager.trackSession(user, req.sessionID, () ->)
+					callback(null)
 
 	passportLogin: (req, res, next) ->
 		# This function is middleware which wraps the passport.authenticate middleware,

+ 24 - 0
services/web/app/coffee/Features/Blog/BlogHandler.coffee

@@ -0,0 +1,24 @@
+request = require "request"
+settings = require "settings-sharelatex"
+_ = require("underscore")
+logger = require "logger-sharelatex"
+
+module.exports = BlogHandler =
+
+	getLatestAnnouncements: (callback)->
+		blogUrl = "#{settings.apis.blog.url}/blog/latestannouncements.json"
+		opts = 
+			url:blogUrl
+			json:true
+			timeout:500
+		request.get opts, (err, res, announcements)->
+			if err?
+				return callback err
+			if res.statusCode != 200
+				return callback("blog announcement returned non 200")
+			logger.log announcementsLength: announcements?.length, "announcements returned"
+			announcements = _.map announcements, (announcement)->
+				announcement.url = "/blog#{announcement.url}"
+				announcement.date = new Date(announcement.date)
+				return announcement
+			callback(err, announcements)

+ 1 - 1
services/web/app/coffee/Features/Email/EmailSender.coffee

@@ -25,7 +25,7 @@ else if Settings?.email?.parameters?.sendgridApiKey?
 	logger.log "using sendgrid for email"
 	nm_client = nodemailer.createTransport(sgTransport({auth:{api_key:Settings?.email?.parameters?.sendgridApiKey}}))
 else if Settings?.email?.parameters?
-	smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth")
+	smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS")
 
 
 	logger.log "using smtp for email"

+ 2 - 2
services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee

@@ -64,7 +64,7 @@ Reporter = (res) ->
 			res.contentType("application/json")
 			if failures.length > 0
 				logger.err failures:failures, "health check failed"
-				res.send 500, JSON.stringify(results, null, 2)
+				res.status(500).send(JSON.stringify(results, null, 2))
 			else
-				res.send 200, JSON.stringify(results, null, 2)
+				res.status(200).send(JSON.stringify(results, null, 2))
 

+ 2 - 0
services/web/app/coffee/Features/Project/ProjectController.coffee

@@ -153,6 +153,8 @@ module.exports = ProjectController =
 					return next(err)
 				logger.log results:results, user_id:user_id, "rendering project list"
 				tags = results.tags[0]
+				
+
 				notifications = require("underscore").map results.notifications, (notification)->
 					notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
 					return notification

+ 0 - 26
services/web/app/coffee/Features/Security/SessionInvalidator.coffee

@@ -1,26 +0,0 @@
-Settings = require("settings-sharelatex")
-redis = require("redis-sharelatex")
-rclient = redis.createClient(Settings.redis.web)
-crypto = require("crypto")
-async = require("async")
-
-
-module.exports = 
-
-	_getEmailKey : (email)->
-		hash = crypto.createHash("md5").update(email).digest("hex")
-		return "e_sess:#{hash}"
-
-	tracksession:(sessionId, email, callback = ->)->
-		session_lookup_key = @_getEmailKey(email)
-		rclient.set session_lookup_key, sessionId, callback
-
-	invalidateSession:(email, callback = ->)->
-		session_lookup_key = @_getEmailKey(email)
-		rclient.get session_lookup_key, (err, sessionId)->
-			async.series [
-				(cb)-> rclient.del sessionId, cb
-				(cb)-> rclient.del session_lookup_key, cb
-			], callback
-
-

+ 5 - 1
services/web/app/coffee/Features/User/UserController.coffee

@@ -44,6 +44,7 @@ module.exports = UserController =
 
 	updateUserSettings : (req, res)->
 		user_id = AuthenticationController.getLoggedInUserId(req)
+		usingExternalAuth = settings.ldap? or settings.saml?
 		logger.log user_id: user_id, "updating account settings"
 		User.findById user_id, (err, user)->
 			if err? or !user?
@@ -74,12 +75,15 @@ module.exports = UserController =
 				user.ace.syntaxValidation = req.body.syntaxValidation
 			user.save (err)->
 				newEmail = req.body.email?.trim().toLowerCase()
-				if !newEmail? or newEmail == user.email
+				if !newEmail? or newEmail == user.email or usingExternalAuth
+					# end here, don't update email
 					AuthenticationController.setInSessionUser(req, {first_name: user.first_name, last_name: user.last_name})
 					return res.sendStatus 200
 				else if newEmail.indexOf("@") == -1
+					# email invalid
 					return res.sendStatus(400)
 				else
+					# update the user email
 					UserUpdater.changeEmailAddress user_id, newEmail, (err)->
 						if err?
 							logger.err err:err, user_id:user_id, newEmail:newEmail, "problem updaing users email address"

+ 21 - 17
services/web/app/coffee/Features/User/UserSessionsManager.coffee

@@ -3,14 +3,11 @@ redis = require('redis-sharelatex')
 logger = require("logger-sharelatex")
 Async = require('async')
 _ = require('underscore')
+UserSessionsRedis = require('./UserSessionsRedis')
 
-rclient = redis.createClient(Settings.redis.web)
+rclient = UserSessionsRedis.client()
 
 module.exports = UserSessionsManager =
-
-	_sessionSetKey: (user) ->
-		return "UserSessions:#{user._id}"
-
 	# mimic the key used by the express sessions
 	_sessionKey: (sessionId) ->
 		return "sess:#{sessionId}"
@@ -23,7 +20,7 @@ module.exports = UserSessionsManager =
 			logger.log {user_id: user._id}, "no sessionId to track, returning"
 			return callback(null)
 		logger.log {user_id: user._id, sessionId}, "onLogin handler"
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		value = UserSessionsManager._sessionKey sessionId
 		rclient.multi()
 			.sadd(sessionSetKey, value)
@@ -43,7 +40,7 @@ module.exports = UserSessionsManager =
 			logger.log {user_id: user._id}, "no sessionId to untrack, returning"
 			return callback(null)
 		logger.log {user_id: user._id, sessionId}, "onLogout handler"
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		value = UserSessionsManager._sessionKey sessionId
 		rclient.multi()
 			.srem(sessionSetKey, value)
@@ -57,7 +54,7 @@ module.exports = UserSessionsManager =
 
 	getAllUserSessions: (user, exclude, callback=(err, sessionKeys)->) ->
 		exclude = _.map(exclude, UserSessionsManager._sessionKey)
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		rclient.smembers sessionSetKey, (err, sessionKeys) ->
 			if err?
 				logger.err user_id: user._id, "error getting all session keys for user from redis"
@@ -66,7 +63,8 @@ module.exports = UserSessionsManager =
 			if sessionKeys.length == 0
 				logger.log {user_id: user._id}, "no other sessions found, returning"
 				return callback(null, [])
-			rclient.mget sessionKeys, (err, sessions) ->
+
+			Async.mapSeries sessionKeys, ((k, cb) -> rclient.get(k, cb)), (err, sessions) ->
 				if err?
 					logger.err {user_id: user._id}, "error getting all sessions for user from redis"
 					return callback(err)
@@ -92,7 +90,7 @@ module.exports = UserSessionsManager =
 			logger.log {}, "no user to revoke sessions for, returning"
 			return callback(null)
 		logger.log {user_id: user._id}, "revoking all existing sessions for user"
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		rclient.smembers sessionSetKey, (err, sessionKeys) ->
 			if err?
 				logger.err {err, user_id: user._id, sessionSetKey}, "error getting contents of UserSessions set"
@@ -102,12 +100,18 @@ module.exports = UserSessionsManager =
 				logger.log {user_id: user._id}, "no sessions in UserSessions set to delete, returning"
 				return callback(null)
 			logger.log {user_id: user._id, count: keysToDelete.length}, "deleting sessions for user"
-			rclient.multi()
-				.del(keysToDelete)
-				.srem(sessionSetKey, keysToDelete)
-				.exec (err, result) ->
+
+			deletions = keysToDelete.map (k) ->
+				(cb) ->
+					rclient.del k, cb
+
+			Async.series deletions, (err, _result) ->
+				if err?
+					logger.err {err, user_id: user._id, sessionSetKey}, "errror revoking all sessions for user"
+					return callback(err)
+				rclient.srem sessionSetKey, keysToDelete, (err) ->
 					if err?
-						logger.err {err, user_id: user._id, sessionSetKey}, "error revoking all sessions for user"
+						logger.err {err, user_id: user._id, sessionSetKey}, "error removing session set for user"
 						return callback(err)
 					callback(null)
 
@@ -115,7 +119,7 @@ module.exports = UserSessionsManager =
 		if !user
 			logger.log {}, "no user to touch sessions for, returning"
 			return callback(null)
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		rclient.expire sessionSetKey, "#{Settings.cookieSessionLength}", (err, response) ->
 			if err?
 				logger.err {err, user_id: user._id}, "error while updating ttl on UserSessions set"
@@ -127,7 +131,7 @@ module.exports = UserSessionsManager =
 			logger.log {}, "no user, returning"
 			return callback(null)
 		logger.log {user_id: user._id}, "checking sessions for user"
-		sessionSetKey = UserSessionsManager._sessionSetKey(user)
+		sessionSetKey = UserSessionsRedis.sessionSetKey(user)
 		rclient.smembers sessionSetKey, (err, sessionKeys) ->
 			if err?
 				logger.err {err, user_id: user._id, sessionSetKey}, "error getting contents of UserSessions set"

+ 21 - 0
services/web/app/coffee/Features/User/UserSessionsRedis.coffee

@@ -0,0 +1,21 @@
+Settings = require 'settings-sharelatex'
+redis = require 'redis-sharelatex'
+ioredis = require 'ioredis'
+logger = require 'logger-sharelatex'
+
+redisSessionsSettings = Settings.redis.websessions or Settings.redis.web
+
+module.exports = Redis =
+	client: () ->
+		if redisSessionsSettings?.cluster?
+			logger.log {}, "using redis cluster for web sessions"
+			rclient = new ioredis.Cluster(redisSessionsSettings.cluster)
+		else
+			rclient = redis.createClient(redisSessionsSettings)
+		return rclient
+
+	sessionSetKey: (user) ->
+		if redisSessionsSettings?.cluster?
+			return "UserSessions:{#{user._id}}"
+		else
+			return "UserSessions:#{user._id}"

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

@@ -210,7 +210,7 @@ module.exports = (app, webRouter, apiRouter)->
 
 	webRouter.use (req, res, next)->
 		res.locals.externalAuthenticationSystemUsed = ->
-			Settings.ldap?
+			Settings.ldap? or Settings.saml?
 		next()
 
 	webRouter.use (req, res, next)->

+ 5 - 1
services/web/app/coffee/infrastructure/Modules.coffee

@@ -18,6 +18,10 @@ module.exports = Modules =
 	applyRouter: (webRouter, apiRouter) ->
 		for module in @modules
 			module.router?.apply(webRouter, apiRouter)
+
+	applyNonCsrfRouter: (webRouter, apiRouter) ->
+		for module in @modules
+			module.nonCsrfRouter?.apply(webRouter, apiRouter)
 			
 	viewIncludes: {}
 	loadViewIncludes: (app) ->
@@ -58,4 +62,4 @@ module.exports = Modules =
 				return callback(error) if error?
 				return callback null, results
 		
-Modules.loadModules()
+Modules.loadModules()

+ 1 - 1
services/web/app/coffee/infrastructure/PackageVersions.coffee

@@ -1,5 +1,5 @@
 version = {
-	"pdfjs": "1.6.210p1"
+	"pdfjs": "1.6.210p2"
 	"moment": "2.9.0"
 	"ace": "1.2.5"
 }

+ 11 - 5
services/web/app/coffee/infrastructure/Server.coffee

@@ -8,7 +8,9 @@ expressLocals = require('./ExpressLocals')
 Router = require('../router')
 metrics.inc("startup")
 redis = require("redis-sharelatex")
-rclient = redis.createClient(Settings.redis.web)
+UserSessionsRedis = require('../Features/User/UserSessionsRedis')
+
+sessionsRedisClient = UserSessionsRedis.client()
 
 session = require("express-session")
 RedisStore = require('connect-redis')(session)
@@ -19,7 +21,8 @@ csrf = require('csurf')
 csrfProtection = csrf()
 cookieParser = require('cookie-parser')
 
-sessionStore = new RedisStore(client:rclient)
+# Init the session store
+sessionStore = new RedisStore(client:sessionsRedisClient)
 
 passport = require('passport')
 LocalStrategy = require('passport-local').Strategy
@@ -87,9 +90,6 @@ webRouter.use session
 		secure: Settings.secureCookie
 	store: sessionStore
 	key: Settings.cookieName
-webRouter.use csrfProtection
-webRouter.use translations.expressMiddlewear
-webRouter.use translations.setLangBasedOnDomainMiddlewear
 
 # passport
 webRouter.use passport.initialize()
@@ -110,6 +110,12 @@ Modules.hooks.fire 'passportSetup', passport, (err) ->
 	if err?
 		logger.err {err}, "error setting up passport in modules"
 
+Modules.applyNonCsrfRouter(webRouter, apiRouter)
+
+webRouter.use csrfProtection
+webRouter.use translations.expressMiddlewear
+webRouter.use translations.setLangBasedOnDomainMiddlewear
+
 # Measure expiry from last request, not last login
 webRouter.use (req, res, next) ->
 	req.session.touch()

+ 4 - 0
services/web/app/coffee/router.coffee

@@ -39,6 +39,7 @@ ReferencesController = require('./Features/References/ReferencesController')
 AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear')
 BetaProgramController = require('./Features/BetaProgram/BetaProgramController')
 AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
+AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
 
 logger = require("logger-sharelatex")
 _ = require("underscore")
@@ -187,6 +188,9 @@ module.exports = class Router
 		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
 		apiRouter.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
 

+ 5 - 2
services/web/app/views/general/500.jade

@@ -3,7 +3,8 @@ html(itemscope, itemtype='http://schema.org/Product')
 	head
 		title Something went wrong
 		link(rel="icon", href="/favicon.ico")
-		link(rel='stylesheet', href=buildCssPath('/style.css'))
+		if buildCssPath
+			link(rel='stylesheet', href=buildCssPath('/style.css'))
 		link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
 	body
 		.content
@@ -12,7 +13,9 @@ html(itemscope, itemtype='http://schema.org/Product')
 					.col-md-8.col-md-offset-2.text-center
 						.page-header
 							h2 Oh dear, something went wrong.
-						p: img(src=buildImgPath("lion-sad-128.png"), alt="Sad Lion")
+						if buildImgPath
+							p
+								img(src=buildImgPath("lion-sad-128.png"), alt="Sad Lion")
 						p
 							| Something went wrong with your request, sorry. Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
 						p

+ 110 - 373
services/web/app/views/project/editor/header.jade

@@ -1,388 +1,125 @@
-div(ng-if="!shouldABTestHeaderLabels")
-	header.toolbar.toolbar-header(
-		ng-cloak,
-		ng-hide="state.loading"
-	)
+header.toolbar.toolbar-header.toolbar-with-labels(
+	ng-cloak,
+	ng-hide="state.loading"
+)
+	.toolbar-left
 		a.btn.btn-full-height(
 			href,
-			ng-click="ui.leftMenuShown = true"
-			tooltip='#{translate("menu")}',
-			tooltip-placement="bottom",
-			tooltip-append-to-body="true",
+			ng-click="ui.leftMenuShown = true;",
 		)
 			i.fa.fa-fw.fa-bars
+			p.toolbar-label #{translate("menu")}
 		a(
 			href="/project"
-			tooltip="#{translate('back_to_projects')}",
-			tooltip-placement="bottom",
-			tooltip-append-to-body="true"
 		)
 			i.fa.fa-fw.fa-level-up
-		
-		span(ng-controller="PdfViewToggleController")
-			a(
-				href,
-				ng-show="ui.pdfLayout == 'flat' && fileTreeClosed",
-				tooltip="PDF",
-				tooltip-placement="bottom",
-				tooltip-append-to-body="true",
-				ng-click="togglePdfView()",
-				ng-class="{ 'active': ui.view == 'pdf' }"
-			)
-				i.fa.fa-file-pdf-o
-
-		.toolbar-center.project-name(ng-controller="ProjectNameController")
-			span.name(
-				ng-dblclick="!permissions.admin || startRenaming()",
-				ng-show="!state.renaming"
-			) {{ project.name }}
-
-			input.form-control(
-				type="text"
-				ng-model="inputs.name",
-				ng-show="state.renaming",
-				on-enter="finishRenaming()",
-				ng-blur="finishRenaming()",
-				select-name-when="state.renaming"
-			)
-
-			a.rename(
-				ng-if="permissions.admin",
-				href='#',
-				tooltip-placement="bottom",
-				tooltip="#{translate('rename')}",
-				tooltip-append-to-body="true",
-				ng-click="startRenaming()",
-				ng-show="!state.renaming"
-			)
-				i.fa.fa-pencil
-
-		.toolbar-right
-			span.online-users(
-				ng-show="onlineUsersArray.length > 0"
-				ng-controller="OnlineUsersController"
-			)
-				span(ng-if="onlineUsersArray.length < 4")
-					span.online-user(
-						ng-repeat="user in onlineUsersArray",
-						ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }",
-						popover="{{ user.name }}"
-						popover-placement="bottom"
-						popover-append-to-body="true"
-						popover-trigger="mouseenter"
-						ng-click="gotoUser(user)"
-					) {{ user.name.slice(0,1) }}
-				
-				span.dropdown(dropdown, ng-if="onlineUsersArray.length >= 4")
-					span.online-user.online-user-multi(
-						dropdown-toggle,
-						tooltip="#{translate('connected_users')}",
-						tooltip-placement="left"
-					)
-						strong {{ onlineUsersArray.length }}
-						i.fa.fa-fw.fa-user
-					ul.dropdown-menu.pull-right
-						li.dropdown-header #{translate('connected_users')}
-						li(ng-repeat="user in onlineUsersArray")
-							a(href, ng-click="gotoUser(user)")
-								span.online-user(
-									ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }"
-								) {{ user.name.slice(0,1) }}
-								| {{ user.name }}
-
-			a.btn.btn-full-height(
-				href,
-				ng-if="trackChangesFeatureFlag",
-				ng-class="{ active: ui.reviewPanelOpen }"
-				tooltip="#{translate('review')}",
-				tooltip-placement="bottom",
-				ng-click="toggleReviewPanel()"
-			)
-				| Review
-			a.btn.btn-full-height(
-				href,
-				ng-if="permissions.admin",
-				tooltip="#{translate('share')}",
-				tooltip-placement="bottom",
-				ng-click="openShareProjectModal()",
-				ng-controller="ShareController",
-			)
-				i.fa.fa-fw.fa-group
-			a.btn.btn-full-height(
-				href,
-				ng-click="toggleHistory()",
-				ng-class="{ active: (ui.view == 'history') }"
-				tooltip="#{translate('recent_changes')}",
-				tooltip-placement="bottom",
-			)
-				i.fa.fa-fw.fa-history
-			a.btn.btn-full-height(
-				href,
-				tooltip="#{translate('chat')}",
-				tooltip-placement="bottom",
-				ng-class="{ active: ui.chatOpen }",
-				ng-click="toggleChat()",
-				ng-controller="ChatButtonController",
-				ng-show="!anonymous",
-			)
-				i.fa.fa-fw.fa-comment(
-					ng-class="{ 'bounce': unreadMessages > 0 }"
-				)
-				span.label.label-info(
-					ng-show="unreadMessages > 0"
-				) {{ unreadMessages }}
 
-div(ng-if="shouldABTestHeaderLabels")
-	div(sixpack-switch="editor-header")
-		header.toolbar.toolbar-header(
-			ng-cloak,
-			ng-hide="state.loading"
-			sixpack-default
+	span(ng-controller="PdfViewToggleController")
+		a(
+			href,
+			ng-show="ui.pdfLayout == 'flat' && fileTreeClosed",
+			tooltip="PDF",
+			tooltip-placement="bottom",
+			tooltip-append-to-body="true",
+			ng-click="togglePdfView()",
+			ng-class="{ 'active': ui.view == 'pdf' }"
+		)
+			i.fa.fa-file-pdf-o
+
+	.toolbar-center.project-name(ng-controller="ProjectNameController")
+		span.name(
+			ng-dblclick="!permissions.admin || startRenaming()",
+			ng-show="!state.renaming"
+		) {{ project.name }}
+
+		input.form-control(
+			type="text"
+			ng-model="inputs.name",
+			ng-show="state.renaming",
+			on-enter="finishRenaming()",
+			ng-blur="finishRenaming()",
+			select-name-when="state.renaming"
 		)
-			a.btn.btn-full-height(
-				href,
-				ng-click="ui.leftMenuShown = true; trackABTestConversion('menu');"
-				tooltip='#{translate("menu")}',
-				tooltip-placement="bottom",
-				tooltip-append-to-body="true",
-				sixpack-convert="editor-header"
-			)
-				i.fa.fa-fw.fa-bars
-			a(
-				href="/project"
-				tooltip="#{translate('back_to_projects')}",
-				tooltip-placement="bottom",
-				tooltip-append-to-body="true"
-			)
-				i.fa.fa-fw.fa-level-up
-			
-			span(ng-controller="PdfViewToggleController")
-				a(
-					href,
-					ng-show="ui.pdfLayout == 'flat' && fileTreeClosed",
-					tooltip="PDF",
-					tooltip-placement="bottom",
-					tooltip-append-to-body="true",
-					ng-click="togglePdfView()",
-					ng-class="{ 'active': ui.view == 'pdf' }"
-				)
-					i.fa.fa-file-pdf-o
-
-			.toolbar-center.project-name(ng-controller="ProjectNameController")
-				span.name(
-					ng-dblclick="!permissions.admin || startRenaming()",
-					ng-show="!state.renaming"
-				) {{ project.name }}
-
-				input.form-control(
-					type="text"
-					ng-model="inputs.name",
-					ng-show="state.renaming",
-					on-enter="finishRenaming()",
-					ng-blur="finishRenaming()",
-					select-name-when="state.renaming"
-				)
-
-				a.rename(
-					ng-if="permissions.admin",
-					href='#',
-					tooltip-placement="bottom",
-					tooltip="#{translate('rename')}",
-					tooltip-append-to-body="true",
-					ng-click="startRenaming()",
-					ng-show="!state.renaming"
-				)
-					i.fa.fa-pencil
 
-			.toolbar-right
-				span.online-users(
-					ng-show="onlineUsersArray.length > 0"
-					ng-controller="OnlineUsersController"
-				)
-					span(ng-if="onlineUsersArray.length < 4")
-						span.online-user(
-							ng-repeat="user in onlineUsersArray",
-							ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }",
-							popover="{{ user.name }}"
-							popover-placement="bottom"
-							popover-append-to-body="true"
-							popover-trigger="mouseenter"
-							ng-click="gotoUser(user)"
-						) {{ user.name.slice(0,1) }}
-					
-					span.dropdown(dropdown, ng-if="onlineUsersArray.length >= 4")
-						span.online-user.online-user-multi(
-							dropdown-toggle,
-							tooltip="#{translate('connected_users')}",
-							tooltip-placement="left"
-						)
-							strong {{ onlineUsersArray.length }}
-							i.fa.fa-fw.fa-user
-						ul.dropdown-menu.pull-right
-							li.dropdown-header #{translate('connected_users')}
-							li(ng-repeat="user in onlineUsersArray")
-								a(href, ng-click="gotoUser(user)")
-									span.online-user(
-										ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }"
-									) {{ user.name.slice(0,1) }}
-									| {{ user.name }}
+		a.rename(
+			ng-if="permissions.admin",
+			href='#',
+			tooltip-placement="bottom",
+			tooltip="#{translate('rename')}",
+			tooltip-append-to-body="true",
+			ng-click="startRenaming()",
+			ng-show="!state.renaming"
+		)
+			i.fa.fa-pencil
 
+	.toolbar-right
+		span.online-users(
+			ng-show="onlineUsersArray.length > 0"
+			ng-controller="OnlineUsersController"
+		)
+			span(ng-if="onlineUsersArray.length < 4")
+				span.online-user(
+					ng-repeat="user in onlineUsersArray",
+					ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }",
+					popover="{{ user.name }}"
+					popover-placement="bottom"
+					popover-append-to-body="true"
+					popover-trigger="mouseenter"
+					ng-click="gotoUser(user)"
+				) {{ user.name.slice(0,1) }}
 			
-				a.btn.btn-full-height(
-					href,
-					ng-if="permissions.admin",
-					tooltip="#{translate('share')}",
-					tooltip-placement="bottom",
-					ng-click="openShareProjectModal(); trackABTestConversion('share');",
-					ng-controller="ShareController",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-group
-				a.btn.btn-full-height(
-					href,
-					ng-click="toggleHistory(); trackABTestConversion('history');",
-					ng-class="{ active: (ui.view == 'history') }"
-					tooltip="#{translate('recent_changes')}",
-					tooltip-placement="bottom",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-history
-				a.btn.btn-full-height(
-					href,
-					tooltip="#{translate('chat')}",
-					tooltip-placement="bottom",
-					ng-class="{ active: ui.chatOpen }",
-					ng-click="toggleChat(); trackABTestConversion('chat');",
-					ng-controller="ChatButtonController",
-					ng-show="!anonymous",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-comment(
-						ng-class="{ 'bounce': unreadMessages > 0 }"
-					)
-					span.label.label-info(
-						ng-show="unreadMessages > 0"
-					) {{ unreadMessages }}
+			span.dropdown(dropdown, ng-if="onlineUsersArray.length >= 4")
+				span.online-user.online-user-multi(
+					dropdown-toggle,
+					tooltip="#{translate('connected_users')}",
+					tooltip-placement="left"
+				)
+					strong {{ onlineUsersArray.length }}
+					i.fa.fa-fw.fa-user
+				ul.dropdown-menu.pull-right
+					li.dropdown-header #{translate('connected_users')}
+					li(ng-repeat="user in onlineUsersArray")
+						a(href, ng-click="gotoUser(user)")
+							span.online-user(
+								ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }"
+							) {{ user.name.slice(0,1) }}
+							| {{ user.name }}
 
-		header.toolbar.toolbar-header.toolbar-with-labels(
-			ng-cloak,
-			ng-hide="state.loading"
-			sixpack-when="labels"
+		a.btn.btn-full-height(
+			href,
+			ng-if="trackChangesFeatureFlag",
+			ng-class="{ active: ui.reviewPanelOpen }"
+			tooltip="#{translate('review')}",
+			tooltip-placement="bottom",
+			ng-click="toggleReviewPanel()"
 		)
-			.toolbar-left
-				a.btn.btn-full-height(
-					href,
-					ng-click="ui.leftMenuShown = true; trackABTestConversion('menu');",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-bars
-					p.toolbar-label #{translate("menu")}
-				a(
-					href="/project"
-				)
-					i.fa.fa-fw.fa-level-up
-
-			span(ng-controller="PdfViewToggleController")
-				a(
-					href,
-					ng-show="ui.pdfLayout == 'flat' && fileTreeClosed",
-					tooltip="PDF",
-					tooltip-placement="bottom",
-					tooltip-append-to-body="true",
-					ng-click="togglePdfView()",
-					ng-class="{ 'active': ui.view == 'pdf' }"
-				)
-					i.fa.fa-file-pdf-o
-
-			.toolbar-center.project-name(ng-controller="ProjectNameController")
-				span.name(
-					ng-dblclick="!permissions.admin || startRenaming()",
-					ng-show="!state.renaming"
-				) {{ project.name }}
-
-				input.form-control(
-					type="text"
-					ng-model="inputs.name",
-					ng-show="state.renaming",
-					on-enter="finishRenaming()",
-					ng-blur="finishRenaming()",
-					select-name-when="state.renaming"
-				)
-
-				a.rename(
-					ng-if="permissions.admin",
-					href='#',
-					tooltip-placement="bottom",
-					tooltip="#{translate('rename')}",
-					tooltip-append-to-body="true",
-					ng-click="startRenaming()",
-					ng-show="!state.renaming"
-				)
-					i.fa.fa-pencil
-
-			.toolbar-right
-				span.online-users(
-					ng-show="onlineUsersArray.length > 0"
-					ng-controller="OnlineUsersController"
-				)
-					span(ng-if="onlineUsersArray.length < 4")
-						span.online-user(
-							ng-repeat="user in onlineUsersArray",
-							ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }",
-							popover="{{ user.name }}"
-							popover-placement="bottom"
-							popover-append-to-body="true"
-							popover-trigger="mouseenter"
-							ng-click="gotoUser(user)"
-						) {{ user.name.slice(0,1) }}
-					
-					span.dropdown(dropdown, ng-if="onlineUsersArray.length >= 4")
-						span.online-user.online-user-multi(
-							dropdown-toggle,
-							tooltip="#{translate('connected_users')}",
-							tooltip-placement="left"
-						)
-							strong {{ onlineUsersArray.length }}
-							i.fa.fa-fw.fa-user
-						ul.dropdown-menu.pull-right
-							li.dropdown-header #{translate('connected_users')}
-							li(ng-repeat="user in onlineUsersArray")
-								a(href, ng-click="gotoUser(user)")
-									span.online-user(
-										ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 70%, 50%)' }"
-									) {{ user.name.slice(0,1) }}
-									| {{ user.name }}
-
-			
-				a.btn.btn-full-height(
-					href,
-					ng-if="permissions.admin",
-					ng-click="openShareProjectModal(); trackABTestConversion('share');",
-					ng-controller="ShareController",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-group
-					p.toolbar-label #{translate("share")}
-				a.btn.btn-full-height(
-					href,
-					ng-click="toggleHistory(); trackABTestConversion('history');",
-					ng-class="{ active: (ui.view == 'history') }",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-history
-					p.toolbar-label #{translate("history")}
-				a.btn.btn-full-height(
-					href,
-					ng-class="{ active: ui.chatOpen }",
-					ng-click="toggleChat(); trackABTestConversion('chat');",
-					ng-controller="ChatButtonController",
-					ng-show="!anonymous",
-					sixpack-convert="editor-header"
-				)
-					i.fa.fa-fw.fa-comment(
-						ng-class="{ 'bounce': unreadMessages > 0 }"
-					)
-					span.label.label-info(
-						ng-show="unreadMessages > 0"
-					) {{ unreadMessages }}
-					p.toolbar-label #{translate("chat")}
+			| Review
+		a.btn.btn-full-height(
+			href,
+			ng-if="permissions.admin",
+			ng-click="openShareProjectModal();",
+			ng-controller="ShareController",
+		)
+			i.fa.fa-fw.fa-group
+			p.toolbar-label #{translate("share")}
+		a.btn.btn-full-height(
+			href,
+			ng-click="toggleHistory();",
+			ng-class="{ active: (ui.view == 'history') }",
+		)
+			i.fa.fa-fw.fa-history
+			p.toolbar-label #{translate("history")}
+		a.btn.btn-full-height(
+			href,
+			ng-class="{ active: ui.chatOpen }",
+			ng-click="toggleChat();",
+			ng-controller="ChatButtonController",
+			ng-show="!anonymous",
+		)
+			i.fa.fa-fw.fa-comment(
+				ng-class="{ 'bounce': unreadMessages > 0 }"
+			)
+			span.label.label-info(
+				ng-show="unreadMessages > 0"
+			) {{ unreadMessages }}
+			p.toolbar-label #{translate("chat")}

+ 5 - 1
services/web/app/views/project/list.jade

@@ -21,7 +21,11 @@ block content
 
 	.content.content-alt(ng-controller="ProjectPageController")
 		.container
-				
+			
+			//- div(ng-controller="AnnouncementsController", ng-cloak)
+			//- 	.alert.alert-success(ng-show="dataRecived")
+			//- 		a(href, ng-click="openLink()") {{title}} and {{totalAnnouncements}} others
+
 			.row(ng-cloak)
 				span(ng-if="projects.length > 0")
 					aside.col-md-2.col-xs-3

+ 6 - 0
services/web/app/views/user/settings.jade

@@ -33,6 +33,12 @@ block content
 													)
 													span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty")
 														|  #{translate("must_be_email_address")}
+											else
+												// show the email, non-editable
+												.form-group
+													label.control-label #{translate("email")}
+													div.form-control(readonly="true") #{user.email}
+
 											.form-group
 												label(for='firstName').control-label #{translate("first_name")}
 												input.form-control(

+ 32 - 24
services/web/config/settings.defaults.coffee

@@ -38,6 +38,16 @@ module.exports = settings =
 			port: "6379"
 			password: ""
 
+		# websessions:
+		# 	cluster: [
+		# 		{host: 'localhost', port: 7000}
+		# 		{host: 'localhost', port: 7001}
+		# 		{host: 'localhost', port: 7002}
+		# 		{host: 'localhost', port: 7003}
+		# 		{host: 'localhost', port: 7004}
+		# 		{host: 'localhost', port: 7005}
+		# 	]
+
 		api:
 			host: "localhost"
 			port: "6379"
@@ -107,8 +117,8 @@ module.exports = settings =
 		# references:
 		# 	url: "http://localhost:3040"
 		notifications:
-			url: "http://localhost:3042" 
-			
+			url: "http://localhost:3042"
+
 	templates:
 		user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
 		showSocialButtons: false
@@ -131,13 +141,13 @@ module.exports = settings =
 
 	# this is only used if cookies are used for clsi backend
 	#clsiCookieKey: "clsiserver"
-	
+
 	# Same, but with http auth credentials.
 	httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
 
 
 	maxEntitiesPerProject: 2000
-	
+
 	# Security
 	# --------
 	security:
@@ -166,12 +176,12 @@ module.exports = settings =
 		price: 0
 		features: defaultFeatures
 	}]
-	
+
 	enableSubscriptions:false
 
 	# i18n
 	# ------
-	# 
+	#
 	i18n:
 		subdomainLang:
 			www: {lngCode:"en", url: siteUrl}
@@ -180,7 +190,7 @@ module.exports = settings =
 	# Spelling languages
 	# ------------------
 	#
-	# You must have the corresponding aspell package installed to 
+	# You must have the corresponding aspell package installed to
 	# be able to use a language.
 	languages: [
 		{name: "English", code: "en"},
@@ -228,7 +238,7 @@ module.exports = settings =
 	# analytics:
 	# 	ga:
 	# 		token: ""
-	# 
+	#
 	# ShareLaTeX's help desk is provided by tenderapp.com
 	# tenderUrl: ""
 	#
@@ -262,10 +272,10 @@ module.exports = settings =
 	# then set this to true to allow it to correctly detect the forwarded IP
 	# address and http/https protocol information.
 	behindProxy: false
-	
+
 	# 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: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
@@ -273,10 +283,10 @@ module.exports = settings =
 	# Use a single compile directory for all users in a project
 	# (otherwise each user has their own directory)
 	# disablePerUserCompiles: true
-	
+
 	# Maximum size of text documents in the real-time editing system.
 	max_doc_length: 2 * 1024 * 1024 # 2mb
-	
+
 	# Internal configs
 	# ----------------
 	path:
@@ -285,11 +295,11 @@ module.exports = settings =
 		# them to disk here).
 		dumpFolder: Path.resolve __dirname + "/../data/dumpFolder"
 		uploadFolder: Path.resolve __dirname + "/../data/uploads"
-	
+
 	# Automatic Snapshots
 	# -------------------
 	automaticSnapshots:
-		# How long should we wait after the user last edited to 
+		# How long should we wait after the user last edited to
 		# take a snapshot?
 		waitTimeAfterLastEdit: 5 * minutes
 		# Even if edits are still taking place, this is maximum
@@ -305,13 +315,13 @@ module.exports = settings =
 	# 	user: ""
 	# 	password: ""
 	# 	projectId: ""
-	
+
 	appName: "ShareLaTeX (Community Edition)"
 	adminEmail: "placeholder@example.com"
 
 	nav:
 		title: "ShareLaTeX Community Edition"
-		
+
 		left_footer: [{
 			text: "Powered by <a href='https://www.sharelatex.com'>ShareLaTeX</a> © 2016"
 		}]
@@ -377,11 +387,11 @@ module.exports = settings =
 		"/templates/index": "/templates/"
 
 	proxyUrls: {}
-	
+
 	reloadModuleViewsOnEachRequest: true
 
 	domainLicences: [
-		
+
 	]
 
 	sixpack:
@@ -390,12 +400,12 @@ module.exports = settings =
 	# ----------
 
 
-	
+
 	# LDAP
 	# ----------
 	# Settings below use a working LDAP test server kindly provided by forumsys.com
 	# When testing with forumsys.com use username = einstein and password = password
-	
+
 	# ldap :
 	# 	host: 'ldap://ldap.forumsys.com'
 	# 	dn: 'uid=:userKey,dc=example,dc=com'
@@ -406,13 +416,13 @@ module.exports = settings =
 	# 	placeholder: 'email@example.com'
 	# 	emailAtt: 'mail'
 	# 	anonymous: false
-	#	adminDN: 'cn=read-only-admin,dc=example,dc=com'	
+	#	adminDN: 'cn=read-only-admin,dc=example,dc=com'
 	#	adminPW: 'password'
 	#	starttls: true
 	#	tlsOptions:
 	#		rejectUnauthorized: false
 	#		ca: ['/etc/ldap/ca_certs.pem']
-	
+
 	#templateLinks: [{
 	#	name : "CV projects",
 	#	url : "/templates/cv"
@@ -420,5 +430,3 @@ module.exports = settings =
 	#	name : "all projects",
 	#	url: "/templates/all"
 	#}]
-
-

+ 209 - 65
services/web/npm-shrinkwrap.json

@@ -274,12 +274,12 @@
       "dependencies": {
         "bytes": {
           "version": "2.4.0",
-          "from": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz",
+          "from": "bytes@2.4.0",
           "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz"
         },
         "content-type": {
           "version": "1.0.2",
-          "from": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
+          "from": "content-type@~1.0.1",
           "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
         },
         "debug": {
@@ -328,7 +328,7 @@
         },
         "on-finished": {
           "version": "2.3.0",
-          "from": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+          "from": "on-finished@~2.3.0",
           "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
           "dependencies": {
             "ee-first": {
@@ -345,7 +345,7 @@
         },
         "raw-body": {
           "version": "2.1.7",
-          "from": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
+          "from": "raw-body@~2.1.5",
           "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
           "dependencies": {
             "unpipe": {
@@ -357,7 +357,7 @@
         },
         "type-is": {
           "version": "1.6.13",
-          "from": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
+          "from": "type-is@~1.6.10",
           "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
           "dependencies": {
             "media-typer": {
@@ -387,26 +387,42 @@
       "resolved": "https://registry.npmjs.org/bufferedstream/-/bufferedstream-1.6.0.tgz"
     },
     "connect-redis": {
-      "version": "2.3.0",
-      "from": "https://registry.npmjs.org/connect-redis/-/connect-redis-2.3.0.tgz",
-      "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-2.3.0.tgz",
+      "version": "3.1.0",
+      "from": "connect-redis@3.1.0",
+      "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-3.1.0.tgz",
       "dependencies": {
         "debug": {
-          "version": "1.0.4",
-          "from": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
+          "version": "2.3.0",
+          "from": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
           "dependencies": {
             "ms": {
-              "version": "0.6.2",
-              "from": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz",
-              "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
+              "version": "0.7.2",
+              "from": "ms@0.7.2"
             }
           }
         },
         "redis": {
-          "version": "0.12.1",
-          "from": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz",
-          "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
+          "version": "2.6.3",
+          "from": "https://registry.npmjs.org/redis/-/redis-2.6.3.tgz",
+          "resolved": "https://registry.npmjs.org/redis/-/redis-2.6.3.tgz",
+          "dependencies": {
+            "double-ended-queue": {
+              "version": "2.1.0-0",
+              "from": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
+              "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz"
+            },
+            "redis-commands": {
+              "version": "1.3.0",
+              "from": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz",
+              "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz"
+            },
+            "redis-parser": {
+              "version": "2.1.1",
+              "from": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.1.1.tgz"
+            }
+          }
         }
       }
     },
@@ -470,7 +486,7 @@
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "from": "json-stringify-safe@~5.0.0",
           "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
         }
       }
@@ -504,7 +520,7 @@
       "dependencies": {
         "cookie": {
           "version": "0.3.1",
-          "from": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+          "from": "cookie@0.3.1",
           "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
         },
         "cookie-signature": {
@@ -616,7 +632,7 @@
         },
         "content-type": {
           "version": "1.0.2",
-          "from": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
+          "from": "content-type@~1.0.1",
           "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
         },
         "cookie": {
@@ -631,7 +647,7 @@
         },
         "debug": {
           "version": "2.2.0",
-          "from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "from": "debug@~2.2.0",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
           "dependencies": {
             "ms": {
@@ -685,7 +701,7 @@
         },
         "on-finished": {
           "version": "2.3.0",
-          "from": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+          "from": "on-finished@~2.3.0",
           "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
           "dependencies": {
             "ee-first": {
@@ -829,7 +845,7 @@
         },
         "type-is": {
           "version": "1.6.13",
-          "from": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
+          "from": "type-is@~1.6.10",
           "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
           "dependencies": {
             "media-typer": {
@@ -864,68 +880,66 @@
       }
     },
     "express-session": {
-      "version": "1.11.3",
-      "from": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz",
-      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz",
+      "version": "1.14.2",
+      "from": "express-session@1.14.2",
+      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.14.2.tgz",
       "dependencies": {
         "cookie": {
-          "version": "0.1.3",
-          "from": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz",
-          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz"
+          "version": "0.3.1",
+          "from": "cookie@0.3.1",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
         },
         "cookie-signature": {
           "version": "1.0.6",
-          "from": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
-          "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
+          "from": "cookie-signature@1.0.6"
         },
         "crc": {
-          "version": "3.3.0",
-          "from": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz",
-          "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz"
+          "version": "3.4.1",
+          "from": "crc@3.4.1",
+          "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.1.tgz"
         },
         "debug": {
           "version": "2.2.0",
-          "from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "from": "debug@2.2.0",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
           "dependencies": {
             "ms": {
               "version": "0.7.1",
-              "from": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+              "from": "ms@0.7.1",
               "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
             }
           }
         },
         "depd": {
-          "version": "1.0.1",
-          "from": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz",
-          "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz"
+          "version": "1.1.0",
+          "from": "depd@1.1.0"
         },
         "on-headers": {
           "version": "1.0.1",
-          "from": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
-          "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
+          "from": "on-headers@1.0.1"
         },
         "parseurl": {
           "version": "1.3.1",
-          "from": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
-          "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz"
+          "from": "parseurl@1.3.1"
         },
         "uid-safe": {
-          "version": "2.0.0",
-          "from": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz",
-          "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz",
+          "version": "2.1.3",
+          "from": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.3.tgz",
+          "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.3.tgz",
           "dependencies": {
             "base64-url": {
-              "version": "1.2.1",
-              "from": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz",
-              "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz"
+              "version": "1.3.3",
+              "from": "base64-url@1.3.3"
+            },
+            "random-bytes": {
+              "version": "1.0.0",
+              "from": "random-bytes@1.0.0"
             }
           }
         },
         "utils-merge": {
           "version": "1.0.0",
-          "from": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
-          "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
+          "from": "utils-merge@1.0.0"
         }
       }
     },
@@ -1166,6 +1180,53 @@
         }
       }
     },
+    "ioredis": {
+      "version": "2.4.0",
+      "from": "https://registry.npmjs.org/ioredis/-/ioredis-2.4.0.tgz",
+      "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-2.4.0.tgz",
+      "dependencies": {
+        "bluebird": {
+          "version": "3.4.6",
+          "from": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz"
+        },
+        "cluster-key-slot": {
+          "version": "1.0.8",
+          "from": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz",
+          "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz"
+        },
+        "debug": {
+          "version": "2.3.0",
+          "from": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz",
+          "dependencies": {
+            "ms": {
+              "version": "0.7.2",
+              "from": "ms@0.7.2"
+            }
+          }
+        },
+        "double-ended-queue": {
+          "version": "2.1.0-0",
+          "from": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
+          "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz"
+        },
+        "flexbuffer": {
+          "version": "0.0.6",
+          "from": "flexbuffer@0.0.6"
+        },
+        "redis-commands": {
+          "version": "1.3.0",
+          "from": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.0.tgz"
+        },
+        "redis-parser": {
+          "version": "1.3.0",
+          "from": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz"
+        }
+      }
+    },
     "jade": {
       "version": "1.3.1",
       "from": "https://registry.npmjs.org/jade/-/jade-1.3.1.tgz",
@@ -1602,7 +1663,7 @@
     },
     "logger-sharelatex": {
       "version": "1.3.1",
-      "from": "git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
+      "from": "logger-sharelatex@git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
       "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#bf413ec621a000cf0e08c939de38d5e24541a08c",
       "dependencies": {
         "bunyan": {
@@ -1783,7 +1844,7 @@
       "dependencies": {
         "debug": {
           "version": "2.2.0",
-          "from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "from": "debug@2.2.0",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
           "dependencies": {
             "ms": {
@@ -1812,7 +1873,7 @@
     },
     "metrics-sharelatex": {
       "version": "1.3.0",
-      "from": "git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
+      "from": "metrics-sharelatex@git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
       "resolved": "git+https://github.com/sharelatex/metrics-sharelatex.git#080c4aeb696edcd5d6d86f202f2c528f0661d7a6",
       "dependencies": {
         "coffee-script": {
@@ -2535,32 +2596,32 @@
     },
     "passport-ldapauth": {
       "version": "0.6.0",
-      "from": "passport-ldapauth@*",
+      "from": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-0.6.0.tgz",
       "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-0.6.0.tgz",
       "dependencies": {
         "passport-strategy": {
           "version": "1.0.0",
-          "from": "passport-strategy@>=1.0.0 <2.0.0",
+          "from": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
           "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz"
         },
         "ldapauth-fork": {
           "version": "2.5.3",
-          "from": "ldapauth-fork@>=2.5.0 <2.6.0",
+          "from": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-2.5.3.tgz",
           "resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-2.5.3.tgz",
           "dependencies": {
             "bcryptjs": {
               "version": "2.3.0",
-              "from": "bcryptjs@2.3.0",
+              "from": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.3.0.tgz",
               "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.3.0.tgz"
             },
             "lru-cache": {
               "version": "3.2.0",
-              "from": "lru-cache@3.2.0",
+              "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
               "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
               "dependencies": {
                 "pseudomap": {
                   "version": "1.0.2",
-                  "from": "pseudomap@>=1.0.1 <2.0.0",
+                  "from": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
                   "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
                 }
               }
@@ -2581,6 +2642,89 @@
         }
       }
     },
+    "passport-saml": {
+      "version": "0.15.0",
+      "from": "passport-saml@",
+      "resolved": "https://registry.npmjs.org/passport-saml/-/passport-saml-0.15.0.tgz",
+      "dependencies": {
+        "passport-strategy": {
+          "version": "1.0.0",
+          "from": "passport-strategy@*"
+        },
+        "q": {
+          "version": "1.1.2",
+          "from": "q@1.1.x"
+        },
+        "xml2js": {
+          "version": "0.4.17",
+          "from": "xml2js@0.4.x",
+          "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz",
+          "dependencies": {
+            "sax": {
+              "version": "1.2.1",
+              "from": "sax@>=0.6.0"
+            },
+            "xmlbuilder": {
+              "version": "4.2.1",
+              "from": "xmlbuilder@^4.1.0",
+              "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz"
+            }
+          }
+        },
+        "xml-crypto": {
+          "version": "0.8.4",
+          "from": "xml-crypto@0.8.x",
+          "dependencies": {
+            "xmldom": {
+              "version": "0.1.19",
+              "from": "xmldom@=0.1.19"
+            },
+            "xpath.js": {
+              "version": "1.0.6",
+              "from": "xpath.js@>=0.0.3"
+            }
+          }
+        },
+        "xmldom": {
+          "version": "0.1.22",
+          "from": "xmldom@0.1.x"
+        },
+        "xmlbuilder": {
+          "version": "2.5.2",
+          "from": "xmlbuilder@2.5.x",
+          "dependencies": {
+            "lodash": {
+              "version": "3.2.0",
+              "from": "lodash@~3.2.0"
+            }
+          }
+        },
+        "xml-encryption": {
+          "version": "0.7.4",
+          "from": "xml-encryption@~0.7",
+          "dependencies": {
+            "ejs": {
+              "version": "0.8.8",
+              "from": "ejs@~0.8.3"
+            },
+            "async": {
+              "version": "0.2.10",
+              "from": "async@~0.2.7"
+            },
+            "xpath": {
+              "version": "0.0.5",
+              "from": "xpath@0.0.5",
+              "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.5.tgz"
+            },
+            "node-forge": {
+              "version": "0.2.24",
+              "from": "node-forge@0.2.24",
+              "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.2.24.tgz"
+            }
+          }
+        }
+      }
+    },
     "pg": {
       "version": "6.0.3",
       "from": "https://registry.npmjs.org/pg/-/pg-6.0.3.tgz",
@@ -3430,7 +3574,7 @@
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "from": "json-stringify-safe@~5.0.0",
           "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
         },
         "mime-types": {
@@ -3462,7 +3606,7 @@
         },
         "stringstream": {
           "version": "0.0.5",
-          "from": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "from": "stringstream@~0.0.4",
           "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
         },
         "tough-cookie": {
@@ -3472,7 +3616,7 @@
         },
         "tunnel-agent": {
           "version": "0.4.3",
-          "from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+          "from": "tunnel-agent@~0.4.0",
           "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
         }
       }
@@ -3558,7 +3702,7 @@
         },
         "depd": {
           "version": "1.1.0",
-          "from": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
+          "from": "depd@~1.1.0",
           "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
         },
         "dottie": {
@@ -3656,7 +3800,7 @@
     },
     "settings-sharelatex": {
       "version": "1.0.0",
-      "from": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
+      "from": "settings-sharelatex@git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
       "resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559",
       "dependencies": {
         "coffee-script": {

+ 5 - 5
services/web/package.json

@@ -17,17 +17,18 @@
     "bcrypt": "0.8.3",
     "body-parser": "^1.13.1",
     "bufferedstream": "1.6.0",
-    "connect-redis": "2.3.0",
+    "connect-redis": "^3.1.0",
     "contentful": "^3.3.14",
     "cookie": "^0.2.3",
     "cookie-parser": "1.3.5",
     "csurf": "^1.8.3",
     "dateformat": "1.0.4-1.2.3",
     "express": "4.13.0",
-    "express-session": "1.11.3",
+    "express-session": "^1.14.2",
     "grunt": "^0.4.5",
     "heapdump": "^0.3.7",
     "http-proxy": "^1.8.1",
+    "ioredis": "^2.4.0",
     "jade": "~1.3.1",
     "ldapjs": "^1.0.0",
     "lodash": "^4.13.1",
@@ -49,8 +50,6 @@
     "passport": "^0.3.2",
     "passport-ldapauth": "^0.6.0",
     "passport-local": "^1.0.0",
-    "pg": "^6.0.3",
-    "pg-hstore": "^2.3.2",
     "redback": "0.4.0",
     "redis": "0.10.1",
     "redis-sharelatex": "0.0.9",
@@ -64,7 +63,8 @@
     "temp": "^0.8.3",
     "underscore": "1.6.0",
     "v8-profiler": "^5.2.3",
-    "xml2js": "0.2.0"
+    "xml2js": "0.2.0",
+    "passport-saml": "^0.15.0"
   },
   "devDependencies": {
     "bunyan": "0.22.1",

+ 0 - 17
services/web/public/coffee/ide.coffee

@@ -91,23 +91,6 @@ define [
 			if value?
 				localStorage "ui.reviewPanelOpen.#{window.project_id}", value
 
-		# Only run the header AB test for newly registered users.
-		_abTestStartDate = new Date(Date.UTC(2016, 8, 28))
-		_userSignUpDate = new Date(window.user.signUpDate)
-
-		$scope.shouldABTestHeaderLabels = _userSignUpDate > _abTestStartDate
-		$scope.headerLabelsABVariant = ""
-
-		if ($scope.shouldABTestHeaderLabels)
-			sixpack.participate "editor-header", [ "default", "labels"], (chosenVariation) ->
-				$scope.headerLabelsABVariant = chosenVariation
-
-		$scope.trackABTestConversion = (headerItem) ->
-			event_tracking.sendMB "header-ab-conversion", {
-				headerItem: headerItem,
-				variant: $scope.headerLabelsABVariant
-			}
-
 		# Tracking code.
 		$scope.$watch "ui.view", (newView, oldView) ->
 			if newView? and newView != "editor" and newView != "pdf"

+ 1 - 1
services/web/public/coffee/ide/editor/Document.coffee

@@ -310,7 +310,7 @@ define [
 
 		_onError: (error, meta = {}) ->
 			meta.doc_id = @doc_id
-			console.error "ShareJS error", error, meta
+			sl_console.log "ShareJS error", error, meta
 			ga?('send', 'event', 'error', "shareJsError", "#{error.message} - #{@ide.socket.socket.transport.name}" )
 			@doc?.clearInflightAndPendingOps()
 			@trigger "error", error, meta

+ 12 - 9
services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee

@@ -207,16 +207,19 @@ define [
 			return doc.split("\n")
 
 		_aceDeltaSetsToSimpleDeltaSets: (aceDeltaSets, docLines) ->
+			simpleDeltaSets = []
 			for deltaSet in aceDeltaSets
-				simpleDeltas = []
-				for delta in deltaSet.deltas
-					simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
-					docLines = @_applyAceDeltasToDocLines([delta], docLines)
-				{
-					deltas: simpleDeltas
-					group: deltaSet.group
-				}
-			
+				if deltaSet.group == "doc" # ignore fold changes
+					simpleDeltas = []
+					for delta in deltaSet.deltas
+						simpleDeltas.push @_aceDeltaToSimpleDelta(delta, docLines)
+						docLines = @_applyAceDeltasToDocLines([delta], docLines)
+					simpleDeltaSets.push {
+						deltas: simpleDeltas
+						group: deltaSet.group
+					}
+			return simpleDeltaSets
+
 		_simpleDeltaSetsToAceDeltaSets: (simpleDeltaSets, docLines) ->
 			for deltaSet in simpleDeltaSets
 				aceDeltas = []

+ 1 - 0
services/web/public/coffee/main.coffee

@@ -14,6 +14,7 @@ define [
 	"main/subscription-dashboard"
 	"main/new-subscription"
 	"main/annual-upgrade"
+	"main/announcements"
 	"main/register-users"
 	"main/subscription/group-subscription-invite-controller"
 	"main/contact-us"

+ 20 - 0
services/web/public/coffee/main/announcements.coffee

@@ -0,0 +1,20 @@
+define [
+	"base"
+], (App) ->
+	App.controller "AnnouncementsController", ($scope, $http, event_tracking, $window) ->
+
+		$scope.dataRecived = false
+		announcement = null
+		$http.get("/announcements").success (announcements) ->
+			if announcements?[0]?
+				announcement = announcements[0]
+				$scope.title = announcement.title
+				$scope.totalAnnouncements =  announcements.length
+				$scope.dataRecived = true
+
+		dismissannouncement = ->
+			event_tracking.sendMB "announcement-alert-dismissed", {blogPostId:announcement.id}
+
+		$scope.openLink = ->
+			dismissannouncement()
+			$window.location.href = announcement.url

+ 0 - 10
services/web/public/coffee/main/event.coffee

@@ -53,16 +53,6 @@ define [
 					@sendMB key, segmentation
 		}
 
-	# App.directive "countlyTrack", () ->
-	# 	return {
-	# 		restrict: "A"
-	# 		scope: false,
-	# 		link: (scope, el, attrs) ->
-	# 			eventKey  = attrs.countlyTrack
-	# 			if (eventKey?)
-	# 				el.on "click", () ->
-	# 					console.log eventKey
-	# 	}
 
 	#header
 	$('.navbar a').on "click", (e)->

+ 1 - 1
services/web/public/coffee/main/project-list/project-list.coffee

@@ -99,7 +99,7 @@ define [
 				visible = true
 				# Only show if it matches any search text
 				if $scope.searchText.value? and $scope.searchText.value != ""
-					if !project.name.toLowerCase().match($scope.searchText.value.toLowerCase())
+					if project.name.toLowerCase().indexOf($scope.searchText.value.toLowerCase()) == -1
 						visible = false
 				# Only show if it matches the selected tag
 				if $scope.filter == "tag" and selectedTag? and project.id not in selectedTag.project_ids

+ 1 - 1
services/web/public/js/ace-1.2.5/worker-latex.js

@@ -1813,7 +1813,7 @@ var CheckEnvironments = function (Environments, ErrorReporter) {
     var verbatimRanges = [];
     for (var i = 0, len = Environments.length; i < len; i++) {
         var name = Environments[i].name ;
-        if (name && name.match(/^(verbatim|boxedverbatim|lstlisting)$/)) {
+        if (name && name.match(/^(verbatim|boxedverbatim|lstlisting|minted)$/)) {
             Environments[i].verbatim = true;
         }
     }

+ 1 - 0
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/.gitattributes

@@ -0,0 +1 @@
+* binary

BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-EUC-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-EUC-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78ms-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/78ms-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/83pv-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90ms-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90ms-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90msp-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90msp-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90pv-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/90pv-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-RKSJ-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Add-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-0.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-1.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-3.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-4.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-5.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-6.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-CNS1-UCS2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-0.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-1.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-3.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-4.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-5.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-GB1-UCS2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-0.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-1.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-3.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-4.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-5.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-6.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Japan1-UCS2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-0.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-1.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Adobe-Korea1-UCS2.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5pc-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/B5pc-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS-EUC-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS-EUC-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS1-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS1-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS2-H.bcmap


+ 3 - 0
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/CNS2-V.bcmap

@@ -0,0 +1,3 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSEáCNS2-H

BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETHK-B5-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETHK-B5-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETen-B5-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETen-B5-V.bcmap


+ 3 - 0
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETenms-B5-H.bcmap

@@ -0,0 +1,3 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSEá	ETen-B5-H` ^

BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/ETenms-B5-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/EUC-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/EUC-V.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-RKSJ-H.bcmap


BIN
services/web/public/js/libs/pdfjs-1.6.210p2/bcmaps/Ext-RKSJ-V.bcmap


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно