diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
index 8a2c33536a..d301ddabbc 100644
--- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
+++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
@@ -11,6 +11,7 @@ UserHandler = require("../User/UserHandler")
UserSessionsManager = require("../User/UserSessionsManager")
Analytics = require "../Analytics/AnalyticsManager"
passport = require 'passport'
+NotificationsBuilder = require("../Notifications/NotificationsBuilder")
module.exports = AuthenticationController =
@@ -43,7 +44,7 @@ module.exports = AuthenticationController =
return callback(err)
req.sessionStore.generate(req)
for key, value of oldSession
- req.session[key] = value
+ req.session[key] = value unless key == '__tmp'
# copy to the old `session.user` location, for backward-comptability
req.session.user = req.session.passport.user
req.session.save (err) ->
@@ -112,6 +113,7 @@ module.exports = AuthenticationController =
UserHandler.setupLoginData(user, ()->)
LoginRateLimiter.recordSuccessfulLogin(user.email)
AuthenticationController._recordSuccessfulLogin(user._id)
+ AuthenticationController.ipMatchCheck(req, user)
Analytics.recordEvent(user._id, "user-logged-in", {ip:req.ip})
Analytics.identifyUser(user._id, req.sessionID)
logger.log email: user.email, user_id: user._id.toString(), "successful log in"
@@ -119,6 +121,13 @@ module.exports = AuthenticationController =
# capture the request ip for use when creating the session
user._login_req_ip = req.ip
+ ipMatchCheck: (req, user) ->
+ if req.ip != user.lastLoginIp
+ NotificationsBuilder.ipMatcherAffiliation(user._id, req.ip).create()
+ UserUpdater.updateUser user._id.toString(), {
+ $set: { "lastLoginIp": req.ip }
+ }
+
setInSessionUser: (req, props) ->
for key, value of props
if req?.session?.passport?.user?
diff --git a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
index 8578f5b3f4..743b9ac190 100644
--- a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -41,21 +41,19 @@ module.exports = EditorHttpController =
return callback(new Error("not found")) if !project?
CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels project_id, (error, members) ->
return callback(error) if error?
- UserGetter.getUser user_id, { isAdmin: true }, (error, user) ->
+ token = TokenAccessHandler.getRequestToken(req, project_id)
+ AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) ->
return callback(error) if error?
- token = TokenAccessHandler.getRequestToken(req, project_id)
- AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) ->
+ if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
+ logger.log {project_id, user_id, privilegeLevel}, "not an acceptable privilege level, returning null"
+ return callback null, null, false
+ CollaboratorsInviteHandler.getAllInvites project_id, (error, invites) ->
return callback(error) if error?
- if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
- logger.log {project_id, user_id, privilegeLevel}, "not an acceptable privilege level, returning null"
- return callback null, null, false
- CollaboratorsInviteHandler.getAllInvites project_id, (error, invites) ->
- return callback(error) if error?
- logger.log {project_id, user_id, memberCount: members.length, inviteCount: invites.length, privilegeLevel}, "returning project model view"
- callback(null,
- ProjectEditorHandler.buildProjectModelView(project, members, invites),
- privilegeLevel
- )
+ logger.log {project_id, user_id, memberCount: members.length, inviteCount: invites.length, privilegeLevel}, "returning project model view"
+ callback(null,
+ ProjectEditorHandler.buildProjectModelView(project, members, invites),
+ privilegeLevel
+ )
_nameIsAcceptableLength: (name)->
return name? and name.length < 150 and name.length != 0
diff --git a/services/web/app/coffee/Features/Email/EmailBuilder.coffee b/services/web/app/coffee/Features/Email/EmailBuilder.coffee
index e2e8311286..3326f8522b 100644
--- a/services/web/app/coffee/Features/Email/EmailBuilder.coffee
+++ b/services/web/app/coffee/Features/Email/EmailBuilder.coffee
@@ -40,6 +40,23 @@ The #{settings.appName} Team - #{settings.siteUrl}
templates = {}
+templates.accountMergeToOverleafAddress = CTAEmailTemplate({
+ subject: () -> "Confirm Account Merge - #{settings.appName}"
+ title: () -> "Confirm Account Merge"
+ message: () ->
+ """
+ To merge your ShareLaTeX and Overleaf accounts, click the button below.
+ If you think you have received this message in error,
+ please contact us at https://www.overleaf.com/contact
+ """
+ ctaText: () -> "Confirm Account Merge"
+ ctaURL: (opts) -> opts.tokenLinkUrl
+ secondaryMessage: (opts) ->
+ "If the button does not appear, open this link in your browser: #{opts.tokenLinkUrl}"
+})
+
+templates.accountMergeToSharelatexAddress = templates.accountMergeToOverleafAddress
+
templates.registered = CTAEmailTemplate({
subject: () -> "Activate your #{settings.appName} Account"
message: (opts) -> """
diff --git a/services/web/app/coffee/Features/Errors/Errors.coffee b/services/web/app/coffee/Features/Errors/Errors.coffee
index f33f3e523e..94aeaa2a90 100644
--- a/services/web/app/coffee/Features/Errors/Errors.coffee
+++ b/services/web/app/coffee/Features/Errors/Errors.coffee
@@ -31,56 +31,56 @@ UnsupportedFileTypeError = (message) ->
error.name = "UnsupportedFileTypeError"
error.__proto__ = UnsupportedFileTypeError.prototype
return error
-UnsupportedFileTypeError.prototype.__proto___ = Error.prototype
+UnsupportedFileTypeError.prototype.__proto__ = Error.prototype
UnsupportedBrandError = (message) ->
error = new Error(message)
error.name = "UnsupportedBrandError"
error.__proto__ = UnsupportedBrandError.prototype
return error
-UnsupportedBrandError.prototype.__proto___ = Error.prototype
+UnsupportedBrandError.prototype.__proto__ = Error.prototype
UnsupportedExportRecordsError = (message) ->
error = new Error(message)
error.name = "UnsupportedExportRecordsError"
error.__proto__ = UnsupportedExportRecordsError.prototype
return error
-UnsupportedExportRecordsError.prototype.__proto___ = Error.prototype
+UnsupportedExportRecordsError.prototype.__proto__ = Error.prototype
V1HistoryNotSyncedError = (message) ->
error = new Error(message)
error.name = "V1HistoryNotSyncedError"
error.__proto__ = V1HistoryNotSyncedError.prototype
return error
-V1HistoryNotSyncedError.prototype.__proto___ = Error.prototype
+V1HistoryNotSyncedError.prototype.__proto__ = Error.prototype
ProjectHistoryDisabledError = (message) ->
error = new Error(message)
error.name = "ProjectHistoryDisabledError"
error.__proto__ = ProjectHistoryDisabledError.prototype
return error
-ProjectHistoryDisabledError.prototype.__proto___ = Error.prototype
+ProjectHistoryDisabledError.prototype.__proto__ = Error.prototype
V1ConnectionError = (message) ->
error = new Error(message)
error.name = "V1ConnectionError"
error.__proto__ = V1ConnectionError.prototype
return error
-V1ConnectionError.prototype.__proto___ = Error.prototype
+V1ConnectionError.prototype.__proto__ = Error.prototype
UnconfirmedEmailError = (message) ->
error = new Error(message)
error.name = "UnconfirmedEmailError"
error.__proto__ = UnconfirmedEmailError.prototype
return error
-UnconfirmedEmailError.prototype.__proto___ = Error.prototype
+UnconfirmedEmailError.prototype.__proto__ = Error.prototype
EmailExistsError = (message) ->
error = new Error(message)
error.name = "EmailExistsError"
error.__proto__ = EmailExistsError.prototype
return error
-EmailExistsError.prototype.__proto___ = Error.prototype
+EmailExistsError.prototype.__proto__ = Error.prototype
module.exports = Errors =
NotFoundError: NotFoundError
diff --git a/services/web/app/coffee/Features/Exports/ExportsController.coffee b/services/web/app/coffee/Features/Exports/ExportsController.coffee
index e724951d2d..ea82cf6a04 100644
--- a/services/web/app/coffee/Features/Exports/ExportsController.coffee
+++ b/services/web/app/coffee/Features/Exports/ExportsController.coffee
@@ -13,13 +13,19 @@ module.exports =
user_id: user_id
}
- if req.body && req.body.firstName && req.body.lastName
- export_params.first_name = req.body.firstName.trim()
- export_params.last_name = req.body.lastName.trim()
+ if req.body
+ export_params.first_name = req.body.firstName.trim() if req.body.firstName
+ export_params.last_name = req.body.lastName.trim() if req.body.lastName
+ # additional parameters for gallery exports
+ export_params.title = req.body.title.trim() if req.body.title
+ export_params.description = req.body.description.trim() if req.body.description
+ export_params.author = req.body.author.trim() if req.body.author
+ export_params.license = req.body.license.trim() if req.body.license
+ export_params.show_source = req.body.show_source if req.body.show_source
ExportsHandler.exportProject export_params, (err, export_data) ->
- return next(err) if err?
- logger.log
+ return err if err?
+ logger.log
user_id:user_id
project_id: project_id
brand_variation_id:brand_variation_id
@@ -30,11 +36,13 @@ module.exports =
exportStatus: (req, res) ->
{export_id} = req.params
ExportsHandler.fetchExport export_id, (err, export_json) ->
- return next(err) if err?
+ return err if err?
parsed_export = JSON.parse(export_json)
json = {
status_summary: parsed_export.status_summary,
- status_detail: parsed_export.status_detail
+ status_detail: parsed_export.status_detail,
+ partner_submission_id: parsed_export.partner_submission_id,
+ token: parsed_export.token
}
res.send export_json: json
diff --git a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee
index 234a334241..885f063c8b 100644
--- a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee
+++ b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee
@@ -20,9 +20,7 @@ module.exports = ExportsHandler = self =
callback null, export_data
_buildExport: (export_params, callback=(err, export_data) ->) ->
- project_id = export_params.project_id
- user_id = export_params.user_id
- brand_variation_id = export_params.brand_variation_id
+ {project_id, user_id, brand_variation_id, title, description, author, license, show_source} = export_params
jobs =
project: (cb) ->
ProjectGetter.getProject project_id, cb
@@ -60,6 +58,11 @@ module.exports = ExportsHandler = self =
metadata:
compiler: project.compiler
imageName: project.imageName
+ title: title
+ description: description
+ author: author
+ license: license
+ show_source: show_source
user:
id: user_id
firstName: user.first_name
diff --git a/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee b/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee
index 941f4d4d4d..c0280450ab 100644
--- a/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee
+++ b/services/web/app/coffee/Features/Notifications/NotificationsBuilder.coffee
@@ -1,5 +1,7 @@
logger = require("logger-sharelatex")
NotificationsHandler = require("./NotificationsHandler")
+request = require "request"
+settings = require "settings-sharelatex"
module.exports =
@@ -29,3 +31,29 @@ module.exports =
NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback
read: (callback=()->) ->
NotificationsHandler.markAsReadByKeyOnly @key, callback
+
+ ipMatcherAffiliation: (userId, ip) ->
+ key: "ip-matched-affiliation-#{ip}"
+ create: (callback=()->) ->
+ return null unless settings?.apis?.v1?.url # service is not configured
+ _key = @key
+ request {
+ method: 'GET'
+ url: "#{settings.apis.v1.url}/api/v2/users/#{userId}/ip_matcher"
+ auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
+ body: { ip: ip }
+ json: true
+ timeout: 20 * 1000
+ }, (error, response, body) ->
+ return error if error?
+ return null unless response.statusCode == 200
+
+ messageOpts =
+ university_id: body.id
+ university_name: body.name
+ content: body.enrolment_ad_html
+ logger.log user_id:userId, key:_key, "creating notification key for user"
+ NotificationsHandler.createNotification userId, _key, "notification_ip_matched_affiliation", messageOpts, null, false, callback
+
+ read: (callback = ->)->
+ NotificationsHandler.markAsReadWithKey userId, @key, callback
diff --git a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
index 5a6ca47c2e..a0f6ae5c12 100644
--- a/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
+++ b/services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
@@ -29,12 +29,15 @@ module.exports =
unreadNotifications = []
callback(null, unreadNotifications)
- createNotification: (user_id, key, templateKey, messageOpts, expiryDateTime, callback)->
+ createNotification: (user_id, key, templateKey, messageOpts, expiryDateTime, forceCreate, callback)->
+ if !callback
+ callback = forceCreate
+ forceCreate = true
payload = {
key:key
messageOpts:messageOpts
templateKey:templateKey
- forceCreate: true
+ forceCreate:forceCreate
}
if expiryDateTime?
payload.expires = expiryDateTime
diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee
index 24dca35e96..59c0647c19 100644
--- a/services/web/app/coffee/Features/Project/ProjectController.coffee
+++ b/services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -26,6 +26,8 @@ TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
Modules = require '../../infrastructure/Modules'
ProjectEntityHandler = require './ProjectEntityHandler'
+UserGetter = require("../User/UserGetter")
+NotificationsBuilder = require("../Notifications/NotificationsBuilder")
crypto = require 'crypto'
{ V1ConnectionError } = require '../Errors/Errors'
Features = require('../../infrastructure/Features')
@@ -209,6 +211,11 @@ module.exports = ProjectController =
user = results.user
warnings = ProjectController._buildWarningsList results.v1Projects
+ # in v2 add notifications for matching university IPs
+ if Settings.overleaf?
+ UserGetter.getUser user_id, { 'lastLoginIp': 1 }, (error, user) ->
+ if req.ip != user.lastLoginIp
+ NotificationsBuilder.ipMatcherAffiliation(user._id, req.ip).create()
ProjectController._injectProjectOwners projects, (error, projects) ->
return next(error) if error?
diff --git a/services/web/app/coffee/Features/StaticPages/HomeController.coffee b/services/web/app/coffee/Features/StaticPages/HomeController.coffee
index 9a8f5ced41..a6c0bef978 100755
--- a/services/web/app/coffee/Features/StaticPages/HomeController.coffee
+++ b/services/web/app/coffee/Features/StaticPages/HomeController.coffee
@@ -9,7 +9,8 @@ fs = require "fs"
ErrorController = require "../Errors/ErrorController"
AuthenticationController = require('../Authentication/AuthenticationController')
-homepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home.pug")
+slHomepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home/sl.pug")
+v2HomepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home/v2.pug")
module.exports = HomeController =
index : (req,res)->
@@ -21,11 +22,13 @@ module.exports = HomeController =
else
HomeController.home(req, res)
- home: (req, res)->
- if Features.hasFeature('homepage') and homepageExists
- res.render 'external/home'
+ home: (req, res, next)->
+ if Features.hasFeature('homepage') and !Settings.overleaf and slHomepageExists
+ res.render 'external/home/sl'
+ else if Features.hasFeature('homepage') and Settings.overleaf and v2HomepageExists
+ res.render 'external/home/v2'
else
- res.redirect "/login"
+ res.redirect '/login'
externalPage: (page, title) ->
return (req, res, next = (error) ->) ->
diff --git a/services/web/app/coffee/Features/Subscription/planFeatures.coffee b/services/web/app/coffee/Features/Subscription/planFeatures.coffee
index 8c9c276955..db5710341e 100644
--- a/services/web/app/coffee/Features/Subscription/planFeatures.coffee
+++ b/services/web/app/coffee/Features/Subscription/planFeatures.coffee
@@ -33,7 +33,7 @@ module.exports =
student: true
}
{
- feature: 'hundreds_templates'
+ feature: 'thousands_templates'
value: 'bool'
info: 'hundreds_templates_info'
plans: {
diff --git a/services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee b/services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee
index b6b65cc7a7..08aa4663f1 100644
--- a/services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee
+++ b/services/web/app/coffee/Features/TokenAccess/TokenAccessController.coffee
@@ -3,6 +3,7 @@ AuthenticationController = require '../Authentication/AuthenticationController'
TokenAccessHandler = require './TokenAccessHandler'
Errors = require '../Errors/Errors'
logger = require 'logger-sharelatex'
+settings = require 'settings-sharelatex'
module.exports = TokenAccessController =
@@ -11,11 +12,16 @@ module.exports = TokenAccessController =
return ProjectController.loadEditor(req, res, next)
_tryHigherAccess: (token, userId, req, res, next) ->
- TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project) ->
+ TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project, projectExists) ->
if err?
logger.err {err, token, userId},
"[TokenAccess] error finding project with higher access"
return next(err)
+ if !projectExists and settings.overleaf
+ logger.log {token, userId},
+ "[TokenAccess] no project found for this token"
+ # Project does not exist, but may be unimported - try it on v1
+ return res.redirect(settings.overleaf.host + req.url)
if !project?
logger.log {token, userId},
"[TokenAccess] no project with higher access found for this user and token"
diff --git a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee
index 9eb792e55b..ed7f51f0d7 100644
--- a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee
+++ b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee
@@ -22,7 +22,7 @@ module.exports = TokenAccessHandler =
'publicAccesLevel': PublicAccessLevels.TOKEN_BASED
}, {_id: 1, publicAccesLevel: 1, owner_ref: 1}, callback
- findProjectWithHigherAccess: (token, userId, callback=(err, project)->) ->
+ findProjectWithHigherAccess: (token, userId, callback=(err, project, projectExists)->) ->
Project.findOne {
$or: [
{'tokens.readAndWrite': token},
@@ -32,12 +32,16 @@ module.exports = TokenAccessHandler =
if err?
return callback(err)
if !project?
- return callback(null, null)
+ return callback(null, null, false) # Project doesn't exist, so we handle differently
projectId = project._id
CollaboratorsHandler.isUserInvitedMemberOfProject userId, projectId, (err, isMember) ->
if err?
return callback(err)
- callback(null, if isMember == true then project else null)
+ callback(
+ null,
+ if isMember == true then project else null,
+ true # Project does exist, but user doesn't have access
+ )
addReadOnlyUserToProject: (userId, projectId, callback=(err)->) ->
userId = ObjectId(userId.toString())
diff --git a/services/web/app/coffee/Features/User/UserGetter.coffee b/services/web/app/coffee/Features/User/UserGetter.coffee
index f3aa4ad4d6..4291cda6b7 100644
--- a/services/web/app/coffee/Features/User/UserGetter.coffee
+++ b/services/web/app/coffee/Features/User/UserGetter.coffee
@@ -8,6 +8,7 @@ Errors = require("../Errors/Errors")
module.exports = UserGetter =
getUser: (query, projection, callback = (error, user) ->) ->
+ return callback(new Error("no query provided")) unless query?
if query?.email?
return callback(new Error("Don't use getUser to find user by email"), null)
if arguments.length == 2
@@ -34,7 +35,7 @@ module.exports = UserGetter =
getUserAffiliations userId, (error, affiliationsData) ->
return callback error if error?
- callback null, decorateFullEmails(user.email, user.emails, affiliationsData)
+ callback null, decorateFullEmails(user.email, user.emails or [], affiliationsData)
getUserByMainEmail: (email, projection, callback = (error, user) ->) ->
email = email.trim()
diff --git a/services/web/app/coffee/infrastructure/RedirectManager.coffee b/services/web/app/coffee/infrastructure/RedirectManager.coffee
index 0c9141926d..fe64f31c05 100644
--- a/services/web/app/coffee/infrastructure/RedirectManager.coffee
+++ b/services/web/app/coffee/infrastructure/RedirectManager.coffee
@@ -1,20 +1,33 @@
settings = require("settings-sharelatex")
logger = require("logger-sharelatex")
-module.exports = (req, res, next)->
-
- requestedUrl = req.url
+module.exports = RedirectManager =
+ apply: (webRouter) ->
+ for redirectUrl, target of settings.redirects
+ for method in (target.methods or ['get'])
+ webRouter[method] redirectUrl, RedirectManager.createRedirect(target)
- redirectUrl = settings.redirects[requestedUrl]
-
- #remove starting slash
- if !redirectUrl? and requestedUrl[requestedUrl.length-1] == "/"
- requestedUrl = requestedUrl.substring(0, requestedUrl.length - 1)
- redirectUrl = settings.redirects[requestedUrl]
-
- if redirectUrl?
- logger.log redirectUrl:redirectUrl, reqUrl:req.url, "redirecting to new path"
- res.redirect 301, "#{redirectUrl}"
- else
- next()
+ createRedirect: (target) ->
+ (req, res, next) ->
+ code = 302
+ if typeof target is 'string'
+ url = target
+ else
+ if req.method == "POST"
+ code = 307
+ if typeof target.url == "function"
+ url = target.url(req.params)
+ if !url
+ return next()
+ else
+ url = target.url
+ if target.baseUrl?
+ url = "#{target.baseUrl}#{url}"
+ res.redirect code, url + getQueryString(req)
+# Naively get the query params string. Stringifying the req.query object may
+# have differences between Express and Rails, so safer to just pass the raw
+# string
+getQueryString = (req) ->
+ qs = req.url.match(/\?.*$/)
+ if qs? then qs[0] else ""
diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee
index bf9d6652f4..dd70fb1a42 100644
--- a/services/web/app/coffee/infrastructure/Server.coffee
+++ b/services/web/app/coffee/infrastructure/Server.coffee
@@ -73,7 +73,7 @@ app.use multer(dest: Settings.path.uploadFolder)
app.use methodOverride()
app.use metrics.http.monitor(logger)
-app.use RedirectManager
+RedirectManager.apply(webRouter)
ProxyManager.apply(publicApiRouter)
diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee
index 95bedebbf4..23b59375f4 100644
--- a/services/web/app/coffee/models/User.coffee
+++ b/services/web/app/coffee/models/User.coffee
@@ -22,6 +22,7 @@ UserSchema = new Schema
confirmed : {type : Boolean, default : false}
signUpDate : {type : Date, default: () -> new Date() }
lastLoggedIn : {type : Date}
+ lastLoginIp : {type : String, default : ''}
loginCount : {type : Number, default: 0}
holdingAccount : {type : Boolean, default: false}
ace : {
diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee
index b196c56870..ac0889d0c4 100644
--- a/services/web/app/coffee/router.coffee
+++ b/services/web/app/coffee/router.coffee
@@ -68,6 +68,7 @@ module.exports = class Router
webRouter.get '/logout', UserController.logout
webRouter.get '/restricted', AuthorizationMiddlewear.restricted
+
if Features.hasFeature('registration')
webRouter.get '/register', UserPagesController.registerPage
AuthenticationController.addEndpointToLoginWhitelist '/register'
@@ -329,6 +330,21 @@ module.exports = class Router
AuthenticationController.httpAuth,
CompileController.getFileFromClsiWithoutUser
+ webRouter.get '/teams', (req, res, next) ->
+ # Match v1 behaviour - if the user is signed in, show their teams list
+ # Otherwise show some information about teams
+ if AuthenticationController.isUserLoggedIn(req)
+ res.redirect('/user/subscription')
+ else
+ res.redirect("#{settings.v1Api.host}/teams")
+
+ webRouter.get '/chrome', (req, res, next) ->
+ # Match v1 behaviour - this is used for a Chrome web app
+ if AuthenticationController.isUserLoggedIn(req)
+ res.redirect('/project')
+ else
+ res.redirect('/register')
+
#Admin Stuff
webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index
webRouter.get '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
diff --git a/services/web/app/views/_mixins/links.pug b/services/web/app/views/_mixins/links.pug
index d73c4772ce..765c3fcf53 100644
--- a/services/web/app/views/_mixins/links.pug
+++ b/services/web/app/views/_mixins/links.pug
@@ -18,8 +18,7 @@ mixin linkAdvisors(linkText, linkClass, track)
| #{linkText ? linkText : 'advisor programme'}
mixin linkBenefits(linkText, linkClass)
- //- To Do: verify path
- a(href="/benefits" class=linkClass ? linkClass : '')
+ a(href="/for/authors" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'benefits'}
mixin linkBlog(linkText, linkClass, slug)
@@ -36,8 +35,7 @@ mixin linkDash(linkText, linkClass)
| #{linkText ? linkText : 'project dashboard'}
mixin linkEducation(linkText, linkClass)
- //- To Do: verify path
- a(href="/plans" class=linkClass ? linkClass : '')
+ a(href="/for/edu" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'teaching toolkit'}
mixin linkEmail(linkText, linkClass, email)
@@ -66,8 +64,7 @@ mixin linkInvite(linkText, linkClass, track)
| #{linkText ? linkText : 'invite your friends'}
mixin linkPlansAndPricing(linkText, linkClass)
- //- To Do: verify path
- a(href="/plans" class=linkClass ? linkClass : '')
+ a(href="/user/subscription/plans" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'plans and pricing'}
mixin linkPrintNewTab(linkText, linkClass, icon, track)
@@ -121,6 +118,5 @@ mixin linkTweet(linkText, linkClass, tweetText, track)
) #{linkText ? linkText : 'tweet'}
mixin linkUniversities(linkText, linkClass)
- //- To Do: verify path
- a(href="/universities" class=linkClass ? linkClass : '')
+ a(href="/for/universities" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'universities'}
diff --git a/services/web/app/views/general/500.pug b/services/web/app/views/general/500.pug
index 73c926b6de..9bce0868e0 100644
--- a/services/web/app/views/general/500.pug
+++ b/services/web/app/views/general/500.pug
@@ -2,7 +2,7 @@ doctype html
html.full-height(itemscope, itemtype='http://schema.org/Product')
head
title Something went wrong
- link(rel="icon", href="/favicon.ico")
+ link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico")
if buildCssPath
link(rel="stylesheet", href=buildCssPath("/" + settings.brandPrefix + "style.css"))
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
diff --git a/services/web/app/views/layout/navbar.pug b/services/web/app/views/layout/navbar.pug
index 0b162b27d2..2fcdb66faf 100644
--- a/services/web/app/views/layout/navbar.pug
+++ b/services/web/app/views/layout/navbar.pug
@@ -27,8 +27,18 @@ nav.navbar.navbar-default.navbar-main
// loop over header_extras
each item in nav.header_extras
+ -
+ if ((item.only_when_logged_in && getSessionUser())
+ || (item.only_when_logged_out && (!getSessionUser()))
+ || (!item.only_when_logged_out && !item.only_when_logged_in && !item.only_content_pages)
+ || (item.only_content_pages && (typeof(suppressNavContentLinks) == "undefined" || !suppressNavContentLinks))
+ ){
+ var showNavItem = true
+ } else {
+ var showNavItem = false
+ }
- if ((item.only_when_logged_in && getSessionUser()) || (item.only_when_logged_out && (!getSessionUser())) || (!item.only_when_logged_out && !item.only_when_logged_in))
+ if showNavItem
if item.dropdown
li.dropdown(class=item.class, dropdown)
a.dropdown-toggle(href, dropdown-toggle)
diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug
index 835192f298..a7f03f2e3c 100644
--- a/services/web/app/views/project/editor/header.pug
+++ b/services/web/app/views/project/editor/header.pug
@@ -103,12 +103,8 @@ header.toolbar.toolbar-header.toolbar-with-labels(
a.btn.btn-full-height(
href
- ng-class="{ 'btn-full-height-disabled' : !permissions.admin }"
ng-click="openShareProjectModal(permissions.admin);"
ng-controller="ShareController"
- tooltip-enable="!permissions.admin"
- tooltip="Only the project owner can use the Share menu at the moment, but we're working on making it accessible to collaborators, too."
- tooltip-placement="bottom"
)
i.fa.fa-fw.fa-group
p.toolbar-label #{translate("share")}
diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug
index d2aa1cc173..17da2df67a 100644
--- a/services/web/app/views/project/editor/share.pug
+++ b/services/web/app/views/project/editor/share.pug
@@ -8,9 +8,8 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
h3 #{translate("share_project")}
.modal-body.modal-body-share
.container-fluid
-
//- Private (with token-access available)
- .row.public-access-level(ng-show="project.publicAccesLevel == 'private'")
+ .row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'private'")
.col-xs-12.text-center
| #{translate('link_sharing_is_off')}
|
@@ -28,7 +27,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
)
//- Token-based access
- .row.public-access-level(ng-show="project.publicAccesLevel == 'tokenBased'")
+ .row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'tokenBased'")
.col-xs-12.text-center
strong
| #{translate('link_sharing_is_on')}.
@@ -57,7 +56,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
pre.access-token(ng-hide="readOnlyTokenLink") #{translate('loading')}...
//- legacy public-access
- .row.public-access-level(ng-show="project.publicAccesLevel == 'readAndWrite' || project.publicAccesLevel == 'readOnly'")
+ .row.public-access-level(ng-show="isAdmin && (project.publicAccesLevel == 'readAndWrite' || project.publicAccesLevel == 'readOnly')")
.col-xs-12.text-center
strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")}
strong(ng-if="project.publicAccesLevel == 'readOnly'") #{translate("this_project_is_public_read_only")}
@@ -77,7 +76,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
.col-xs-3.text-left
span(ng-show="member.privileges == 'readAndWrite'") #{translate("can_edit")}
span(ng-show="member.privileges == 'readOnly'") #{translate("read_only")}
- .col-xs-1
+ .col-xs-1(ng-show="isAdmin")
a(
href
tooltip=translate('remove_collaborator')
@@ -102,7 +101,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
ng-click="revokeInvite(invite)"
)
i.fa.fa-times
- .row.invite-controls
+ .row.invite-controls(ng-show="isAdmin")
form(ng-show="canAddCollaborators")
.small #{translate("share_with_your_collabs")}
.form-group
@@ -177,8 +176,9 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
) #{translate("start_free_trial")}
p.small(ng-show="startedFreeTrial")
- | #{translate("refresh_page_after_starting_free_trial")}.
-
+ | #{translate("refresh_page_after_starting_free_trial")}
+ .row.public-access-level.public-access-level--notice(ng-show="!isAdmin")
+ .col-xs-12.text-center #{translate("to_add_more_collaborators")}
.modal-footer.modal-footer-share
.modal-footer-left
i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
diff --git a/services/web/app/views/project/list.pug b/services/web/app/views/project/list.pug
index b1d903ca49..0fec00cb55 100644
--- a/services/web/app/views/project/list.pug
+++ b/services/web/app/views/project/list.pug
@@ -1,5 +1,8 @@
extends ../layout
+block vars
+ - var suppressNavContentLinks = true
+
block content
//- We need to do .replace(/\//g, '\\/') do that '' -> '<\/script>'
//- and doesn't prematurely end the script tag.
@@ -98,7 +101,7 @@ block content
| To tag or rename your v1 projects, please go back to Overleaf v1.
div(ng-show="visible")
a.project-list-sidebar-v1-link(
- href=settings.overleaf.host + "/dash?prefer-v1-dash=1"
+ href='/sign_in_to_v1?return_to=/dash%3Fprefer-v1-dash%3D1'
) Go back to v1
if userIsFromSL(user)
div(ng-show="visible")
diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug
index 2c1f30bbbb..50d7f8d2ef 100644
--- a/services/web/app/views/project/list/modals.pug
+++ b/services/web/app/views/project/list/modals.pug
@@ -356,7 +356,7 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
form-messages(for="v1ImportForm")
if settings.overleaf && settings.overleaf.host
a.btn.btn-primary.v1-import-btn(
- ng-href=settings.overleaf.host + "/{{project.id}}"
+ ng-href='/sign_in_to_v1?return_to=/{{project.id}}'
ng-class="{disabled: v1ImportForm.inflight || v1ImportForm.response.success}"
) No thanks, open in v1
input.btn.btn-primary.v1-import-btn(
diff --git a/services/web/app/views/project/list/notifications.pug b/services/web/app/views/project/list/notifications.pug
index 55798d6a2b..04ba3827dc 100644
--- a/services/web/app/views/project/list/notifications.pug
+++ b/services/web/app/views/project/list/notifications.pug
@@ -60,6 +60,21 @@ span(ng-controller="NotificationsController").userNotifications
button(ng-click="dismiss(notification)").close.pull-right
span(aria-hidden="true") ×
span.sr-only #{translate("close")}
+ .alert.alert-info(ng-switch-when="notification_ip_matched_affiliation")
+ div.notification_inner
+ .notification_body
+ | It looks like you're at
+ strong {{ notification.messageOpts.university_name }}!
+ | Did you know that {{notification.messageOpts.university_name}} is providing
+ strong free Overleaf Professional accounts
+ | to everyone at {{notification.messageOpts.university_name}}?
+ | Add an institutional email address to claim your account.
+ a.pull-right.btn.btn-sm.btn-info(href="/user/settings")
+ | Add Affiliation
+ span().notification_close
+ button(ng-click="dismiss(notification)").close.pull-right
+ span(aria-hidden="true") ×
+ span.sr-only #{translate("close")}
.alert.alert-info(ng-switch-default)
div.notification_inner
span(ng-bind-html="notification.html").notification_body
diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug
index b2c8f11315..067f870294 100644
--- a/services/web/app/views/project/list/side-bar.pug
+++ b/services/web/app/views/project/list/side-bar.pug
@@ -115,19 +115,14 @@
span(ng-controller="LeftHandMenuPromoController", ng-cloak)
.row-spaced#userProfileInformation(ng-if="hasProjects")
- div(ng-controller="UserProfileController")
- hr(ng-show="percentComplete < 100")
- .text-centered.user-profile(ng-show="percentComplete < 100")
- .progress
- .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}")
-
- p.small #{translate("profile_complete_percentage", {percentval:"{{percentComplete}}"})}
+ div(ng-show="userEmails.length > 0 && userAffiliations.length == 0", ng-cloak)
+ hr
+ .text-centered.user-profile
+ p Are you affiliated with an institution?
- button#completeUserProfileInformation.btn.btn-info(
- ng-hide="formVisable",
- ng-click="openUserProfileModal()"
- ) #{translate("complete")}
-
+ a.btn.btn-info(
+ href="/user/settings"
+ ) Add Affiliation
.row-spaced(ng-if="hasProjects && userHasNoSubscription", ng-cloak).text-centered
hr
diff --git a/services/web/app/views/project/list/v1-item.pug b/services/web/app/views/project/list/v1-item.pug
index 5a8e37bca0..66c609ef2e 100644
--- a/services/web/app/views/project/list/v1-item.pug
+++ b/services/web/app/views/project/list/v1-item.pug
@@ -13,7 +13,7 @@
ng-show="project.accessLevel == 'owner'"
) {{project.name}}
a.projectName(
- href=settings.overleaf.host + "/{{project.id}}"
+ href='/sign_in_to_v1?return_to=/{{project.id}}'
target="_blank"
ng-hide="project.accessLevel == 'owner'"
) {{project.name}}
diff --git a/services/web/app/views/subscriptions/_modal_group_inquiry.pug b/services/web/app/views/subscriptions/_modal_group_inquiry.pug
index 5e54e31b7a..cc9de0d7df 100644
--- a/services/web/app/views/subscriptions/_modal_group_inquiry.pug
+++ b/services/web/app/views/subscriptions/_modal_group_inquiry.pug
@@ -2,7 +2,7 @@ script(type="text/ng-template", id="groupPlanModalTemplate")
.modal-header
h3 #{translate("group_plan_enquiry")}
.modal-body
- form.text-left.form(ng-controller="UniverstiesContactController", ng-submit="contactUs()")
+ form.text-left.form(ng-controller="GroupPlanContactController", ng-submit="contactUs()")
span(ng-show="sent == false && error == false")
.form-group
label#title9(for='Field9')
diff --git a/services/web/app/views/subscriptions/dashboard.pug b/services/web/app/views/subscriptions/dashboard.pug
index 36a79820de..3cd98ae184 100644
--- a/services/web/app/views/subscriptions/dashboard.pug
+++ b/services/web/app/views/subscriptions/dashboard.pug
@@ -122,7 +122,7 @@ block content
p
| You are subscribed to Overleaf through Overleaf v1
p
- a.btn.btn-primary(href=settings.overleaf.host+"/users/edit#status") Manage v1 Subscription
+ a.btn.btn-primary(href='/sign_in_to_v1?return_to=/users/edit%23status') Manage v1 Subscription
hr
if settings.overleaf && v1Subscriptions && v1Subscriptions.teams && v1Subscriptions.teams.length > 0
@@ -130,7 +130,7 @@ block content
p
| You are a member of the Overleaf v1 team: #{team.name}
p
- a.btn.btn-primary(href=settings.overleaf.host+"/teams") Manage v1 Team Membership
+ a.btn.btn-primary(href="/sign_in_to_v1?return_to=/teams") Manage v1 Team Membership
hr
.card(ng-if="view == 'cancelation'")
diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug
index b66d82e044..b7168de1cf 100644
--- a/services/web/app/views/user/settings.pug
+++ b/services/web/app/views/user/settings.pug
@@ -197,7 +197,7 @@ block content
if settings.createV1AccountOnLogin && settings.overleaf
p
strong
- | This will also delete your user account on #[a(href=settings.overleaf.host target="_blank") Overleaf v1].
+ | This will also delete your user account on #[a(href='/sign_in_to_v1?return_to=/dash%3Fprefer-v1-dash%3D1' target="_blank") Overleaf v1].
| If you want to remove your projects from Overleaf v1, you must do this before you
| delete your account by going to your My Projects page in Overleaf v1, moving your
| projects to the Trash, and then from there either ‘leaving’ or ‘purging’ them, as appropriate.
@@ -248,7 +248,7 @@ block content
div.alert.alert-info
| If you can't remember your password, or if you are using Single-Sign-On with another provider
| to sign in (such as Twitter or Google), please
- | #[a(href=settings.overleaf.host+'/users/password/new', target='_blank') reset your password],
+ | #[a(href='/sign_in_to_v1?return_to=/users/password/new', target='_blank') reset your password],
| and try again.
.modal-footer
button.btn.btn-default(
diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee
index 6572b01875..9097c4e433 100644
--- a/services/web/config/settings.defaults.coffee
+++ b/services/web/config/settings.defaults.coffee
@@ -491,7 +491,7 @@ module.exports = settings =
modules:
sanitize:
options:
- allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe', 'img', 'figure', 'figcaption' ]
+ allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe', 'img', 'figure', 'figcaption', 'source', 'video' ]
allowedAttributes:
'a': [ 'href', 'name', 'target', 'class' ]
'div': [ 'class', 'id', 'style' ]
@@ -504,4 +504,6 @@ module.exports = settings =
'figure': [ 'class', 'id', 'style']
'figcaption': [ 'class', 'id', 'style']
'iframe': [ 'allowfullscreen', 'frameborder', 'height', 'src', 'width' ]
- 'img': [ 'alt', 'class', 'src', 'style' ]
\ No newline at end of file
+ 'img': [ 'alt', 'class', 'src', 'style' ]
+ 'source': [ 'src', 'type' ]
+ 'video': [ 'alt', 'class', 'controls', 'height', 'width' ]
\ No newline at end of file
diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json
index dde80a9a3c..8fe259f82c 100644
--- a/services/web/npm-shrinkwrap.json
+++ b/services/web/npm-shrinkwrap.json
@@ -1545,6 +1545,11 @@
}
}
},
+ "chownr": {
+ "version": "1.1.1",
+ "from": "chownr@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz"
+ },
"ci-info": {
"version": "1.1.3",
"from": "ci-info@>=1.0.0 <2.0.0",
@@ -3482,6 +3487,12 @@
"resolved": "https://registry.npmjs.org/flatiron/-/flatiron-0.4.3.tgz",
"dev": true,
"dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "from": "minimist@>=0.0.1 <0.1.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "dev": true
+ },
"optimist": {
"version": "0.6.0",
"from": "optimist@0.6.0",
@@ -3603,6 +3614,11 @@
"from": "fs-extra@>=4.0.2 <5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz"
},
+ "fs-minipass": {
+ "version": "1.2.5",
+ "from": "fs-minipass@>=1.2.5 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz"
+ },
"fs.realpath": {
"version": "1.0.0",
"from": "fs.realpath@>=1.0.0 <2.0.0",
@@ -6696,6 +6712,38 @@
"from": "lynx@0.1.1",
"resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz"
},
+ "mailchimp-api-v3": {
+ "version": "1.12.0",
+ "from": "mailchimp-api-v3@>=1.12.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/mailchimp-api-v3/-/mailchimp-api-v3-1.12.0.tgz",
+ "dependencies": {
+ "bluebird": {
+ "version": "3.5.2",
+ "from": "bluebird@>=3.4.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz"
+ },
+ "lodash": {
+ "version": "4.17.11",
+ "from": "lodash@>=4.17.10 <5.0.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz"
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "from": "safe-buffer@>=5.1.2 <6.0.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
+ },
+ "tar": {
+ "version": "4.4.6",
+ "from": "tar@>=4.0.2 <5.0.0",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz"
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "from": "yallist@>=3.0.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz"
+ }
+ }
+ },
"mailcomposer": {
"version": "3.3.2",
"from": "mailcomposer@3.3.2",
@@ -6981,9 +7029,31 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz"
},
"minimist": {
- "version": "0.0.8",
- "from": "minimist@0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+ "version": "1.2.0",
+ "from": "minimist@1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
+ },
+ "minipass": {
+ "version": "2.3.4",
+ "from": "minipass@>=2.3.3 <3.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz",
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "from": "safe-buffer@^5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "from": "yallist@^3.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz"
+ }
+ }
+ },
+ "minizlib": {
+ "version": "1.1.0",
+ "from": "minizlib@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz"
},
"mixin-deep": {
"version": "1.3.1",
@@ -7002,7 +7072,14 @@
"mkdirp": {
"version": "0.5.1",
"from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "from": "minimist@0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+ }
+ }
},
"mocha": {
"version": "5.0.1",
@@ -7312,6 +7389,12 @@
"resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz",
"dev": true
},
+ "minimist": {
+ "version": "0.0.10",
+ "from": "minimist@>=0.0.1 <0.1.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "dev": true
+ },
"optimist": {
"version": "0.6.0",
"from": "optimist@0.6.0",
@@ -8145,7 +8228,14 @@
"optimist": {
"version": "0.6.1",
"from": "optimist@0.6.1",
- "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz"
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "from": "minimist@>=0.0.1 <0.1.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
+ }
+ }
},
"optionator": {
"version": "0.8.2",
@@ -8318,6 +8408,11 @@
"from": "passport@>=0.3.2 <0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz"
},
+ "passport-google-oauth20": {
+ "version": "1.0.0",
+ "from": "passport-google-oauth20@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-1.0.0.tgz"
+ },
"passport-ldapauth": {
"version": "0.6.0",
"from": "passport-ldapauth@>=0.6.0 <0.7.0",
@@ -8328,6 +8423,11 @@
"from": "passport-local@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz"
},
+ "passport-oauth1": {
+ "version": "1.1.0",
+ "from": "passport-oauth1@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.1.0.tgz"
+ },
"passport-oauth2": {
"version": "1.4.0",
"from": "passport-oauth2@>=1.4.0 <2.0.0",
@@ -8338,6 +8438,11 @@
"from": "passport-oauth2-refresh@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-1.0.0.tgz"
},
+ "passport-orcid": {
+ "version": "0.0.3",
+ "from": "https://registry.npmjs.org/passport-orcid/-/passport-orcid-0.0.3.tgz",
+ "resolved": "https://registry.npmjs.org/passport-orcid/-/passport-orcid-0.0.3.tgz"
+ },
"passport-saml": {
"version": "0.15.0",
"from": "passport-saml@>=0.15.0 <0.16.0",
@@ -8372,6 +8477,11 @@
"from": "passport-strategy@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz"
},
+ "passport-twitter": {
+ "version": "1.0.4",
+ "from": "https://registry.npmjs.org/passport-twitter/-/passport-twitter-1.0.4.tgz",
+ "resolved": "https://registry.npmjs.org/passport-twitter/-/passport-twitter-1.0.4.tgz"
+ },
"path-browserify": {
"version": "0.0.0",
"from": "path-browserify@0.0.0",
@@ -12502,6 +12612,11 @@
"from": "xtend@>=4.0.1 <5.0.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
},
+ "xtraverse": {
+ "version": "0.1.0",
+ "from": "xtraverse@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/xtraverse/-/xtraverse-0.1.0.tgz"
+ },
"y18n": {
"version": "3.2.1",
"from": "y18n@>=3.2.1 <4.0.0",
diff --git a/services/web/package.json b/services/web/package.json
index 8b8879375c..0bacc0d2d1 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -63,6 +63,7 @@
"marked": "^0.3.5",
"method-override": "^2.3.3",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1",
+ "minimist": "1.2.0",
"mocha": "^5.0.1",
"mongojs": "2.4.0",
"mongoose": "4.11.4",
@@ -75,11 +76,14 @@
"nvd3": "^1.8.6",
"optimist": "0.6.1",
"passport": "^0.3.2",
+ "passport-google-oauth20": "^1.0.0",
"passport-ldapauth": "^0.6.0",
"passport-local": "^1.0.0",
"passport-oauth2": "^1.4.0",
"passport-oauth2-refresh": "^1.0.0",
+ "passport-orcid": "0.0.3",
"passport-saml": "^0.15.0",
+ "passport-twitter": "^1.0.4",
"pug": "^2.0.0-beta6",
"react": "^15.4.2",
"react-dom": "^15.4.2",
@@ -99,8 +103,7 @@
"v8-profiler": "^5.2.3",
"valid-url": "^1.0.9",
"xml2js": "0.2.0",
- "yauzl": "^2.8.0",
- "minimist": "1.2.0"
+ "yauzl": "^2.8.0"
},
"devDependencies": {
"autoprefixer": "^6.6.1",
diff --git a/services/web/public/apple-touch-icon.png b/services/web/public/apple-touch-icon.png
new file mode 100644
index 0000000000..f0eb3aba59
Binary files /dev/null and b/services/web/public/apple-touch-icon.png differ
diff --git a/services/web/public/atlassian-domain-verification.html b/services/web/public/atlassian-domain-verification.html
new file mode 100644
index 0000000000..dd3728eb0f
--- /dev/null
+++ b/services/web/public/atlassian-domain-verification.html
@@ -0,0 +1,15 @@
+
+
https://example.com/atlassian-domain-verification.html
+6YyZjsahYa6Y5nyQDaNakzRPBJ6wVMQwN+2CDx-2wQtG/97OvOjc3gh4NCQ6Sgkv
+
+
+
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
index e3ab0b29be..015a4e65ee 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -516,6 +516,7 @@ define [
scope.$on '$destroy', () ->
if scope.sharejsDoc?
+ scope.$broadcast('changeEditor')
tearDownSpellCheck()
tearDownCursorPosition()
detachFromAce(scope.sharejsDoc)
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
index bb957f8cf6..764fcb1c98 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
@@ -3,12 +3,12 @@ define [], () ->
constructor: (@$scope, @adapter, @localStorage) ->
@$scope.$on 'editorInit', @jumpToPositionInNewDoc
- @$scope.$on 'beforeChangeDocument', () =>
- @storeCursorPosition()
- @storeFirstVisibleLine()
+ @$scope.$on 'beforeChangeDocument', @storePositionAndLine
@$scope.$on 'afterChangeDocument', @jumpToPositionInNewDoc
+ @$scope.$on 'changeEditor', @storePositionAndLine
+
@$scope.$on "#{@$scope.name}:gotoLine", (e, line, column) =>
if line?
setTimeout () =>
@@ -24,6 +24,10 @@ define [], () ->
@$scope.$on "#{@$scope.name}:clearSelection", (e) =>
@adapter.clearSelection()
+ storePositionAndLine: () =>
+ @storeCursorPosition()
+ @storeFirstVisibleLine()
+
jumpToPositionInNewDoc: () =>
@doc_id = @$scope.sharejsDoc?.doc_id
setTimeout () =>
diff --git a/services/web/public/coffee/ide/share/controllers/ShareController.coffee b/services/web/public/coffee/ide/share/controllers/ShareController.coffee
index e729b92f08..3f71a63fdc 100644
--- a/services/web/public/coffee/ide/share/controllers/ShareController.coffee
+++ b/services/web/public/coffee/ide/share/controllers/ShareController.coffee
@@ -4,9 +4,7 @@ define [
App.controller "ShareController", ["$scope", "$modal", "ide", "projectInvites", "projectMembers", "event_tracking",
($scope, $modal, ide, projectInvites, projectMembers, event_tracking) ->
$scope.openShareProjectModal = (isAdmin) ->
- if !isAdmin
- return
-
+ $scope.isAdmin = isAdmin;
event_tracking.sendMBOnce "ide-open-share-modal-once"
$modal.open(
diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee
index 39c1de445e..fc62c6bc38 100644
--- a/services/web/public/coffee/main.coffee
+++ b/services/web/public/coffee/main.coffee
@@ -24,7 +24,6 @@ define [
"main/affiliations/controllers/UserAffiliationsController"
"main/affiliations/factories/UserAffiliationsDataService"
"main/keys"
- "main/account-merge-checker"
"main/cms/index"
"analytics/AbTestingManager"
"directives/asyncForm"
diff --git a/services/web/public/coffee/main/account-merge-checker.coffee b/services/web/public/coffee/main/account-merge-checker.coffee
deleted file mode 100644
index caeb3011ca..0000000000
--- a/services/web/public/coffee/main/account-merge-checker.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-define [
- "base"
-], (App) ->
- App.controller "AccountMergeCheckerController", ($scope) ->
- $scope.hasOlAccount = null
diff --git a/services/web/public/coffee/main/contact-us.coffee b/services/web/public/coffee/main/contact-us.coffee
index 6d3e441c49..70627faba5 100644
--- a/services/web/public/coffee/main/contact-us.coffee
+++ b/services/web/public/coffee/main/contact-us.coffee
@@ -2,7 +2,7 @@ define [
"base"
"libs/platform"
], (App, platform) ->
- App.controller 'UniverstiesContactController', ($scope, $modal, $http) ->
+ App.controller 'GroupPlanContactController', ($scope, $modal, $http) ->
$scope.form = {}
$scope.sent = false
@@ -20,7 +20,7 @@ define [
email: $scope.form.email
labels: "#{$scope.form.source} accounts"
message: "Please contact me with more details"
- subject: "#{$scope.form.name} - General Enquiry - #{$scope.form.position} - #{$scope.form.university}"
+ subject: "#{$scope.form.name} - Group Enquiry - #{$scope.form.position} - #{$scope.form.university}"
inbox: "accounts"
request = $http.post "/support", data
diff --git a/services/web/public/coffee/main/project-list/left-hand-menu-promo-controller.coffee b/services/web/public/coffee/main/project-list/left-hand-menu-promo-controller.coffee
index 9fdb3e9ca2..5606842a70 100644
--- a/services/web/public/coffee/main/project-list/left-hand-menu-promo-controller.coffee
+++ b/services/web/public/coffee/main/project-list/left-hand-menu-promo-controller.coffee
@@ -2,8 +2,18 @@ define [
"base"
], (App) ->
- App.controller 'LeftHandMenuPromoController', ($scope) ->
+ App.controller 'LeftHandMenuPromoController', ($scope, UserAffiliationsDataService) ->
$scope.hasProjects = window.data.projects.length > 0
$scope.userHasNoSubscription = window.userHasNoSubscription
+ _userHasNoAffiliation = () ->
+ $scope.userEmails = []
+ $scope.userAffiliations = []
+ UserAffiliationsDataService.getUserEmails().then (emails) ->
+ $scope.userEmails = emails
+ for email in emails
+ if email.affiliation
+ $scope.userAffiliations.push email.affiliation
+
+ _userHasNoAffiliation()
diff --git a/services/web/public/google4f15e48c48709a75.html b/services/web/public/google4f15e48c48709a75.html
new file mode 100644
index 0000000000..8f23e59aff
--- /dev/null
+++ b/services/web/public/google4f15e48c48709a75.html
@@ -0,0 +1 @@
+google-site-verification: google4f15e48c48709a75.html
\ No newline at end of file
diff --git a/services/web/public/googleef256f97939bd9b7.html b/services/web/public/googleef256f97939bd9b7.html
new file mode 100644
index 0000000000..4ccb11541c
--- /dev/null
+++ b/services/web/public/googleef256f97939bd9b7.html
@@ -0,0 +1 @@
+google-site-verification: googleef256f97939bd9b7.html
\ No newline at end of file
diff --git a/services/web/public/img/advocates/friend.jpeg b/services/web/public/img/advocates/friend.jpeg
new file mode 100644
index 0000000000..30f3a1b4eb
Binary files /dev/null and b/services/web/public/img/advocates/friend.jpeg differ
diff --git a/services/web/public/img/grid.png b/services/web/public/img/grid.png
new file mode 100644
index 0000000000..6353ecfeb9
Binary files /dev/null and b/services/web/public/img/grid.png differ
diff --git a/services/web/public/img/homepage.png b/services/web/public/img/homepage.png
new file mode 100644
index 0000000000..052206d0a7
Binary files /dev/null and b/services/web/public/img/homepage.png differ
diff --git a/services/web/public/img/homepage@2x.png b/services/web/public/img/homepage@2x.png
new file mode 100644
index 0000000000..87f4687271
Binary files /dev/null and b/services/web/public/img/homepage@2x.png differ
diff --git a/services/web/public/img/other-brands/logo_google.svg b/services/web/public/img/other-brands/logo_google.svg
new file mode 100644
index 0000000000..728b6fa185
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_google_alt.svg b/services/web/public/img/other-brands/logo_google_alt.svg
new file mode 100644
index 0000000000..3c7a7228ae
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_google_alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_ieee.svg b/services/web/public/img/other-brands/logo_ieee.svg
new file mode 100644
index 0000000000..29fc90e1a7
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_ieee.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_orcid.svg b/services/web/public/img/other-brands/logo_orcid.svg
new file mode 100644
index 0000000000..98107f2e5e
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_orcid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_orcid_alt.svg b/services/web/public/img/other-brands/logo_orcid_alt.svg
new file mode 100644
index 0000000000..82affd04de
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_orcid_alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_sharelatex.svg b/services/web/public/img/other-brands/logo_sharelatex.svg
new file mode 100644
index 0000000000..e59e2db80d
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_sharelatex.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/other-brands/logo_twitter.svg b/services/web/public/img/other-brands/logo_twitter.svg
new file mode 100644
index 0000000000..6a00726c0c
--- /dev/null
+++ b/services/web/public/img/other-brands/logo_twitter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/services/web/public/img/overleaf-partner/overleaf-greygreen-410.png b/services/web/public/img/overleaf-partner/overleaf-greygreen-410.png
new file mode 100644
index 0000000000..1b09f4d16f
Binary files /dev/null and b/services/web/public/img/overleaf-partner/overleaf-greygreen-410.png differ
diff --git a/services/web/public/img/overleaf-partner/overleaf-white-410.png b/services/web/public/img/overleaf-partner/overleaf-white-410.png
new file mode 100644
index 0000000000..6a23d10c15
Binary files /dev/null and b/services/web/public/img/overleaf-partner/overleaf-white-410.png differ
diff --git a/services/web/public/static/brochures/Overleaf-Information-v8.pdf b/services/web/public/static/brochures/Overleaf-Information-v8.pdf
new file mode 100644
index 0000000000..d9f89df67d
Binary files /dev/null and b/services/web/public/static/brochures/Overleaf-Information-v8.pdf differ
diff --git a/services/web/public/static/brochures/Overleaf-Institutional-Solutions-v1.pdf b/services/web/public/static/brochures/Overleaf-Institutional-Solutions-v1.pdf
new file mode 100644
index 0000000000..63e5f6ea17
--- /dev/null
+++ b/services/web/public/static/brochures/Overleaf-Institutional-Solutions-v1.pdf
@@ -0,0 +1,3465 @@
+%PDF-1.5
%
+1 0 obj
<>/OCGs[5 0 R 6 0 R 7 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+