From cad8d8a23be2496f26542c6c4230aa4b5a115ea3 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 22 May 2015 13:57:15 +0100 Subject: [PATCH 01/13] v1 basic invite works, not pretty or tested --- .../coffee/Features/Email/EmailBuilder.coffee | 22 ++++++++++ .../SubscriptionDomainAllocator.coffee | 32 +++++++++++++++ .../SubscriptionGroupController.coffee | 40 +++++++++++++++++++ .../Subscription/SubscriptionLocator.coffee | 5 ++- .../Subscription/SubscriptionRouter.coffee | 3 ++ services/web/app/coffee/models/User.coffee | 1 - .../app/views/subscriptions/group/invite.jade | 25 ++++++++++++ services/web/config/settings.defaults.coffee | 4 ++ services/web/public/coffee/main.coffee | 1 + ...roup-subscription-invite-controller.coffee | 14 +++++++ 10 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 services/web/app/views/subscriptions/group/invite.jade create mode 100644 services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee diff --git a/services/web/app/coffee/Features/Email/EmailBuilder.coffee b/services/web/app/coffee/Features/Email/EmailBuilder.coffee index 064b59f7a8..d3500c9276 100644 --- a/services/web/app/coffee/Features/Email/EmailBuilder.coffee +++ b/services/web/app/coffee/Features/Email/EmailBuilder.coffee @@ -88,6 +88,28 @@ templates.projectSharedWithYou =

#{settings.appName}

""" + +templates.completeJoinGroupAccount = + subject: _.template "Verify Email to join <%= group_name %> group" + layout: NotificationEmailLayout + type:"notification" + compiledTemplate: _.template """ +

Hi, please verify your email to join the <%= group_name %> and get your free premium account

+
+
+
+ + + Verify now + + +
+
+
+

Thank you

+

#{settings.appName}

+""" + module.exports = templates: templates diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee index 5b89abf2ed..90401b0de5 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee @@ -6,8 +6,36 @@ _s = require("underscore.string") module.exports = SubscriptionDomainAllocator = + + getLicenceUserCanJoin: (user, callback)-> + licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + if licence? + callback null, licence + else + callback() + + attemptToJoinGroup: (user, callback)-> + licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + if licence? and user.emailVerified + SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback + else + callback "user not verified" + + rejectInvitationToGroup: (user, subscription, callback)-> + removeUserFromGroup(subscription.admin_id, user._id, callback) + + + getDomainLicencePage: (user)-> + licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + if licence?.verifyEmail + return "/user/subscription/#{licence.subscription_id}/group/invited" + else + return undefined + + autoAllocate: (user, callback = ->)-> licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + # if licence? SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback else @@ -21,4 +49,8 @@ module.exports = SubscriptionDomainAllocator = return licence + findDomainLicenceBySubscriptionId: (subscription_id)-> + licence = _.find settings.domainLicences, (licence)-> + licence?.subscription_id == subscription_id + return licence diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index 674c5affbe..fc8885563f 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -2,6 +2,11 @@ SubscriptionGroupHandler = require("./SubscriptionGroupHandler") logger = require("logger-sharelatex") SubscriptionLocator = require("./SubscriptionLocator") +settings = require("settings-sharelatex") +PasswordResetTokenHandler = require("../PasswordReset/PasswordResetTokenHandler") +EmailHandler = require("../Email/EmailHandler") +SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") + module.exports = addUserToGroup: (req, res)-> @@ -32,3 +37,38 @@ module.exports = title: 'group_admin' users: users subscription: subscription + + renderGroupInvitePage: (req, res)-> + subscription_id = req.params.subscription_id + licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + + res.render "subscriptions/group/invite", + title: "Group Invitation" + subscription_id:subscription_id + licenceName:licence.name + + beginJoinGroup: (req, res)-> + subscription_id = req.params.subscription_id + user_id = req.session.user._id + licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + if !licence? + res.send 500 + PasswordResetTokenHandler.getNewToken subscription_id, (err, token)-> + opts = + to : req.session.user.email + group_name: licence.name + completeJoinUrl: "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/complete_join?token=#{token}" + EmailHandler.sendEmail "completeJoinGroupAccount", opts, -> + res.send 200 + + completeJoin: (req, res)-> + subscription_id = req.params.subscription_id + PasswordResetTokenHandler.getUserIdFromTokenAndExpire req.query.token, (err, token_subscription_id)-> + console.log token_subscription_id + if subscription_id != token_subscription_id + return res.send 403 + SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> + SubscriptionGroupHandler.addUserToGroup subscription.admin_id, req.user.email, (err, user)-> + res.send "joined" + + diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee index a4521bd8f4..19e701443b 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee @@ -14,4 +14,7 @@ module.exports = getMemberSubscriptions: (user_id, callback) -> logger.log user_id: user_id, "getting users group subscriptions" - Subscription.find(member_ids: user_id).populate("admin_id").exec callback \ No newline at end of file + Subscription.find(member_ids: user_id).populate("admin_id").exec callback + + getSubscription: (subscription_id, callback)-> + Subscription.findOne _id:subscription_id, callback diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index 963ff3dfcf..8a6732e3b0 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -24,6 +24,9 @@ module.exports = app.post '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.addUserToGroup app.del '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup + app.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage + app.post '/user/subscription/:subscription_id/group/begin_join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup + app.get '/user/subscription/:subscription_id/group/complete_join', AuthenticationController.requireLogin(), SubscriptionGroupController.completeJoin #recurly callback app.post '/user/subscription/callback', SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index 783389cb6a..af83318b49 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -53,7 +53,6 @@ UserSchema = new Schema # has this set to true, despite never having had a free trial hadFreeTrial: {type: Boolean, default: false} - UserSchema.statics.getAllIds = (callback)-> this.find {}, ["first_name"], callback diff --git a/services/web/app/views/subscriptions/group/invite.jade b/services/web/app/views/subscriptions/group/invite.jade new file mode 100644 index 0000000000..06089e3d4b --- /dev/null +++ b/services/web/app/views/subscriptions/group/invite.jade @@ -0,0 +1,25 @@ +extends ../../layout + +block scripts + script(type='text/javascript'). + window.subscription_id = '#{subscription_id}' + +block content + .content.content-alt + .container + .row + .col-md-8.col-md-offset-2(ng-cloak) + .card(ng-controller="GroupSubscriptionInviteController") + .page-header + h1 You are invited to Join #{licenceName} + span(ng-show="!requestSent") + div You can claim a free premium account provided by #{licenceName} by verifying your email + + a.btn.btn-default(href="/project") Skip +   + a.btn.btn.btn-primary(ng-click="joinGroup()") Verify email and join Group + + span(ng-show="requestSent") + div Check your email to complete joinging the group + + a.btn(href="/project") Continue to projects diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 6d923bea61..6876d48f75 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -349,6 +349,10 @@ module.exports = reloadModuleViewsOnEachRequest: true + domainLicences: [ + {"domains":["gmail.com"], "adminUser_id":"555208e6ce4812e7d2319daa", "subscription_id":"555c6d1eb844bfb3964e0629", "name":"HENRYS AMAZING LICENCE"} + ] + # ShareLaTeX Server Pro options (https://www.sharelatex.com/university/onsite.html) # ---------- diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index 614c29f44c..88e33694e7 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -15,6 +15,7 @@ define [ "main/new-subscription" "main/annual-upgrade" "main/register-users" + "main/subscription/group-subscription-invite-controller" "analytics/AbTestingManager" "directives/asyncForm" "directives/stopPropagation" diff --git a/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee new file mode 100644 index 0000000000..f123985a70 --- /dev/null +++ b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee @@ -0,0 +1,14 @@ +define [ + "base" +], (App) -> + App.controller "GroupSubscriptionInviteController", ($scope, $http) -> + + $scope.requestSent = false + + $scope.joinGroup = -> + console.log "joingin group" + request = $http.post "/user/subscription/#{subscription_id}/group/begin_join", {_csrf:window.csrfToken} + request.success (data, status)-> + $scope.requestSent = true + request.error (data, status)-> + console.log "the request failed" \ No newline at end of file From 841231dbf89b1a56e99b9857ce8e71f80a4fcd8f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 26 May 2015 15:24:09 +0100 Subject: [PATCH 02/13] make PasswordResetTokenHandler generic so it can be used for invites --- services/web/Gruntfile.coffee | 4 ++-- .../PasswordReset/PasswordResetHandler.coffee | 6 ++--- .../OneTimeTokenHandler.coffee} | 8 +++---- .../SubscriptionGroupController.coffee | 8 +++---- .../Features/User/UserController.coffee | 4 ++-- .../PasswordResetHandlerTests.coffee | 16 ++++++------- .../OneTimeTokenHandlerTests.coffee} | 24 +++++++++---------- .../coffee/User/UserControllerTests.coffee | 10 ++++---- 8 files changed, 40 insertions(+), 40 deletions(-) rename services/web/app/coffee/Features/{PasswordReset/PasswordResetTokenHandler.coffee => Security/OneTimeTokenHandler.coffee} (79%) rename services/web/test/UnitTests/coffee/{PasswordReset/PasswordResetTokenHandlerTests.coffee => Security/OneTimeTokenHandlerTests.coffee} (67%) diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee index f1b78c50a6..4efe88299c 100644 --- a/services/web/Gruntfile.coffee +++ b/services/web/Gruntfile.coffee @@ -299,7 +299,7 @@ module.exports = (grunt) -> settings = require "settings-sharelatex" UserRegistrationHandler = require "./app/js/Features/User/UserRegistrationHandler" - PasswordResetTokenHandler = require "./app/js/Features/PasswordReset/PasswordResetTokenHandler" + OneTimeTokenHandler = require "./app/js/Features/Security/OneTimeTokenHandler" UserRegistrationHandler.registerNewUser { email: email password: require("crypto").randomBytes(32).toString("hex") @@ -310,7 +310,7 @@ module.exports = (grunt) -> user.save (error) -> throw error if error? ONE_WEEK = 7 * 24 * 60 * 60 # seconds - PasswordResetTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)-> + OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)-> return next(err) if err? console.log "" diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee index ec5e6a8de5..6a43dec3e6 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee @@ -1,7 +1,7 @@ settings = require("settings-sharelatex") async = require("async") UserGetter = require("../User/UserGetter") -PasswordResetTokenHandler = require("./PasswordResetTokenHandler") +OneTimeTokenHandler = require("./OneTimeTokenHandler") EmailHandler = require("../Email/EmailHandler") AuthenticationManager = require("../Authentication/AuthenticationManager") logger = require("logger-sharelatex") @@ -14,7 +14,7 @@ module.exports = if !user? or user.holdingAccount logger.err email:email, "user could not be found for password reset" return callback(null, false) - PasswordResetTokenHandler.getNewToken user._id, (err, token)-> + OneTimeTokenHandler.getNewToken user._id, (err, token)-> if err then return callback(err) emailOptions = to : email @@ -24,7 +24,7 @@ module.exports = callback null, true setNewUserPassword: (token, password, callback = (error, found) ->)-> - PasswordResetTokenHandler.getUserIdFromTokenAndExpire token, (err, user_id)-> + OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, user_id)-> if err then return callback(err) if !user_id? return callback null, false diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetTokenHandler.coffee b/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee similarity index 79% rename from services/web/app/coffee/Features/PasswordReset/PasswordResetTokenHandler.coffee rename to services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee index 1fc5680ec2..66581a02b3 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetTokenHandler.coffee +++ b/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee @@ -10,21 +10,21 @@ buildKey = (token)-> return "password_token:#{token}" module.exports = - getNewToken: (user_id, options = {}, callback)-> + getNewToken: (value, options = {}, callback)-> # options is optional if typeof options == "function" callback = options options = {} expiresIn = options.expiresIn or ONE_HOUR_IN_S - logger.log user_id:user_id, "generating token for password reset" + logger.log value:value, "generating token for password reset" token = crypto.randomBytes(32).toString("hex") multi = rclient.multi() - multi.set buildKey(token), user_id + multi.set buildKey(token), value multi.expire buildKey(token), expiresIn multi.exec (err)-> callback(err, token) - getUserIdFromTokenAndExpire: (token, callback)-> + getValueFromTokenAndExpire: (token, callback)-> logger.log token:token, "getting user id from password token" multi = rclient.multi() multi.get buildKey(token) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index fc8885563f..07b229b00b 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -3,7 +3,7 @@ logger = require("logger-sharelatex") SubscriptionLocator = require("./SubscriptionLocator") settings = require("settings-sharelatex") -PasswordResetTokenHandler = require("../PasswordReset/PasswordResetTokenHandler") +OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") EmailHandler = require("../Email/EmailHandler") SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") @@ -53,7 +53,7 @@ module.exports = licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) if !licence? res.send 500 - PasswordResetTokenHandler.getNewToken subscription_id, (err, token)-> + OneTimeTokenHandler.getNewToken subscription_id, (err, token)-> opts = to : req.session.user.email group_name: licence.name @@ -63,9 +63,9 @@ module.exports = completeJoin: (req, res)-> subscription_id = req.params.subscription_id - PasswordResetTokenHandler.getUserIdFromTokenAndExpire req.query.token, (err, token_subscription_id)-> + OneTimeTokenHandler.getValueFromTokenAndExpire req.query.token, (err, token_subscription_id)-> console.log token_subscription_id - if subscription_id != token_subscription_id + if err? or subscription_id != token_subscription_id return res.send 403 SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> SubscriptionGroupHandler.addUserToGroup subscription.admin_id, req.user.email, (err, user)-> diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee index 53ce50eb1b..1efe77e44c 100644 --- a/services/web/app/coffee/Features/User/UserController.coffee +++ b/services/web/app/coffee/Features/User/UserController.coffee @@ -10,7 +10,7 @@ AuthenticationManager = require("../Authentication/AuthenticationManager") UserUpdater = require("./UserUpdater") SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator") EmailHandler = require("../Email/EmailHandler") -PasswordResetTokenHandler = require "../PasswordReset/PasswordResetTokenHandler" +OneTimeTokenHandler = require "../Security/OneTimeTokenHandler" settings = require "settings-sharelatex" crypto = require "crypto" @@ -98,7 +98,7 @@ module.exports = logger.log {email}, "user already exists, resending welcome email" ONE_WEEK = 7 * 24 * 60 * 60 # seconds - PasswordResetTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)-> + OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)-> return next(err) if err? setNewPasswordUrl = "#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}&email=#{encodeURIComponent(email)}" diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee index 65eb81bdcb..67a4688c58 100644 --- a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee @@ -12,9 +12,9 @@ describe "PasswordResetHandler", -> @settings = siteUrl: "www.sharelatex.com" - @PasswordResetTokenHandler = + @OneTimeTokenHandler = getNewToken:sinon.stub() - getUserIdFromTokenAndExpire:sinon.stub() + getValueFromTokenAndExpire:sinon.stub() @UserGetter = getUser:sinon.stub() @EmailHandler = @@ -23,7 +23,7 @@ describe "PasswordResetHandler", -> setUserPassword:sinon.stub() @PasswordResetHandler = SandboxedModule.require modulePath, requires: "../User/UserGetter": @UserGetter - "./PasswordResetTokenHandler": @PasswordResetTokenHandler + "./OneTimeTokenHandler": @OneTimeTokenHandler "../Email/EmailHandler":@EmailHandler "../Authentication/AuthenticationManager":@AuthenticationManager "settings-sharelatex": @settings @@ -41,7 +41,7 @@ describe "PasswordResetHandler", -> it "should check the user exists", (done)-> @UserGetter.getUser.callsArgWith(1) - @PasswordResetTokenHandler.getNewToken.callsArgWith(1) + @OneTimeTokenHandler.getNewToken.callsArgWith(1) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> exists.should.equal false done() @@ -50,7 +50,7 @@ describe "PasswordResetHandler", -> it "should send the email with the token", (done)-> @UserGetter.getUser.callsArgWith(1, null, @user) - @PasswordResetTokenHandler.getNewToken.callsArgWith(1, null, @token) + @OneTimeTokenHandler.getNewToken.callsArgWith(1, null, @token) @EmailHandler.sendEmail.callsArgWith(2) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> @EmailHandler.sendEmail.called.should.equal true @@ -63,7 +63,7 @@ describe "PasswordResetHandler", -> it "should return exists = false for a holdingAccount", (done) -> @user.holdingAccount = true @UserGetter.getUser.callsArgWith(1, null, @user) - @PasswordResetTokenHandler.getNewToken.callsArgWith(1) + @OneTimeTokenHandler.getNewToken.callsArgWith(1) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> exists.should.equal false done() @@ -71,14 +71,14 @@ describe "PasswordResetHandler", -> describe "setNewUserPassword", -> it "should return false if no user id can be found", (done)-> - @PasswordResetTokenHandler.getUserIdFromTokenAndExpire.callsArgWith(1) + @OneTimeTokenHandler.getValueFromTokenAndExpire.callsArgWith(1) @PasswordResetHandler.setNewUserPassword @token, @password, (err, found) => found.should.equal false @AuthenticationManager.setUserPassword.called.should.equal false done() it "should set the user password", (done)-> - @PasswordResetTokenHandler.getUserIdFromTokenAndExpire.callsArgWith(1, null, @user_id) + @OneTimeTokenHandler.getValueFromTokenAndExpire.callsArgWith(1, null, @user_id) @AuthenticationManager.setUserPassword.callsArgWith(2) @PasswordResetHandler.setNewUserPassword @token, @password, (err, found) => found.should.equal true diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetTokenHandlerTests.coffee b/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee similarity index 67% rename from services/web/test/UnitTests/coffee/PasswordReset/PasswordResetTokenHandlerTests.coffee rename to services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee index 7de011740c..a6305db27c 100644 --- a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetTokenHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee @@ -3,13 +3,13 @@ SandboxedModule = require('sandboxed-module') assert = require('assert') path = require('path') sinon = require('sinon') -modulePath = path.join __dirname, "../../../../app/js/Features/PasswordReset/PasswordResetTokenHandler" +modulePath = path.join __dirname, "../../../../app/js/Features/Security/OneTimeTokenHandler" expect = require("chai").expect -describe "PasswordResetTokenHandler", -> +describe "OneTimeTokenHandler", -> beforeEach -> - @user_id = "user id here" + @value = "user id here" @stubbedToken = require("crypto").randomBytes(32) @settings = @@ -22,7 +22,7 @@ describe "PasswordResetTokenHandler", -> expire:sinon.stub() exec:sinon.stub() self = @ - @PasswordResetTokenHandler = SandboxedModule.require modulePath, requires: + @OneTimeTokenHandler = SandboxedModule.require modulePath, requires: "redis-sharelatex" : createClient: => auth:-> @@ -37,30 +37,30 @@ describe "PasswordResetTokenHandler", -> it "should set a new token into redis with a ttl", (done)-> @redisMulti.exec.callsArgWith(0) - @PasswordResetTokenHandler.getNewToken @user_id, (err, token) => - @redisMulti.set.calledWith("password_token:#{@stubbedToken.toString("hex")}", @user_id).should.equal true + @OneTimeTokenHandler.getNewToken @value, (err, token) => + @redisMulti.set.calledWith("password_token:#{@stubbedToken.toString("hex")}", @value).should.equal true @redisMulti.expire.calledWith("password_token:#{@stubbedToken.toString("hex")}", 60 * 60).should.equal true done() it "should return if there was an error", (done)-> @redisMulti.exec.callsArgWith(0, "error") - @PasswordResetTokenHandler.getNewToken @user_id, (err, token)=> + @OneTimeTokenHandler.getNewToken @value, (err, token)=> err.should.exist done() it "should allow the expiry time to be overridden", (done) -> @redisMulti.exec.callsArgWith(0) @ttl = 42 - @PasswordResetTokenHandler.getNewToken @user_id, {expiresIn: @ttl}, (err, token) => + @OneTimeTokenHandler.getNewToken @value, {expiresIn: @ttl}, (err, token) => @redisMulti.expire.calledWith("password_token:#{@stubbedToken.toString("hex")}", @ttl).should.equal true done() - describe "getUserIdFromTokenAndExpire", -> + describe "getValueFromTokenAndExpire", -> it "should get and delete the token", (done)-> - @redisMulti.exec.callsArgWith(0, null, [@user_id]) - @PasswordResetTokenHandler.getUserIdFromTokenAndExpire @stubbedToken, (err, user_id)=> - user_id.should.equal @user_id + @redisMulti.exec.callsArgWith(0, null, [@value]) + @OneTimeTokenHandler.getValueFromTokenAndExpire @stubbedToken, (err, value)=> + value.should.equal @value @redisMulti.get.calledWith("password_token:#{@stubbedToken}").should.equal true @redisMulti.del.calledWith("password_token:#{@stubbedToken}").should.equal true done() diff --git a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee index 3aad4b9c85..cb94bc8618 100644 --- a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee @@ -42,7 +42,7 @@ describe "UserController", -> changeEmailAddress:sinon.stub() @EmailHandler = sendEmail:sinon.stub().callsArgWith(2) - @PasswordResetTokenHandler = + @OneTimeTokenHandler = getNewToken: sinon.stub() @settings = siteUrl: "sharelatex.example.com" @@ -58,7 +58,7 @@ describe "UserController", -> "../Referal/ReferalAllocator":@ReferalAllocator "../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator "../Email/EmailHandler": @EmailHandler - "../PasswordReset/PasswordResetTokenHandler": @PasswordResetTokenHandler + "../Security/OneTimeTokenHandler": @OneTimeTokenHandler "crypto": @crypto = {} "settings-sharelatex": @settings "logger-sharelatex": {log:->} @@ -177,7 +177,7 @@ describe "UserController", -> beforeEach -> @req.body.email = @user.email = "email@example.com" @crypto.randomBytes = sinon.stub().returns({toString: () => @password = "mock-password"}) - @PasswordResetTokenHandler.getNewToken.callsArgWith(2, null, @token = "mock-token") + @OneTimeTokenHandler.getNewToken.callsArgWith(2, null, @token = "mock-token") describe "with a new user", -> beforeEach -> @@ -192,7 +192,7 @@ describe "UserController", -> }).should.equal true it "should generate a new password reset token", -> - @PasswordResetTokenHandler.getNewToken + @OneTimeTokenHandler.getNewToken .calledWith(@user_id, expiresIn: 7 * 24 * 60 * 60) .should.equal true @@ -218,7 +218,7 @@ describe "UserController", -> @UserController.register @req, @res it "should still generate a new password token and email", -> - @PasswordResetTokenHandler.getNewToken.called.should.equal true + @OneTimeTokenHandler.getNewToken.called.should.equal true @EmailHandler.sendEmail.called.should.equal true describe "changePassword", -> From 94b26e71a4b555b79c828368fc4281971f00e3c5 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 26 May 2015 15:25:24 +0100 Subject: [PATCH 03/13] ignore test script --- services/web/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/.gitignore b/services/web/.gitignore index be7f96666b..f8a1fc0842 100644 --- a/services/web/.gitignore +++ b/services/web/.gitignore @@ -44,6 +44,7 @@ cookies.txt requestQueueWorker.js TpdsWorker.js BackgroundJobsWorker.js +UserAndProjectPopulator.coffee public/js/history/versiondetail.js !public/js/libs/ From 481bd67fbd10bb1493332121c235d7c1fb1a9dcc Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 26 May 2015 15:25:46 +0100 Subject: [PATCH 04/13] changed paths to use hyphens and add succesfull join page --- .../SubscriptionGroupController.coffee | 10 ++++++++-- .../Subscription/SubscriptionRouter.coffee | 5 +++-- .../subscriptions/group/successful_join.jade | 19 +++++++++++++++++++ services/web/config/settings.defaults.coffee | 2 +- ...roup-subscription-invite-controller.coffee | 2 +- 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 services/web/app/views/subscriptions/group/successful_join.jade diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index 07b229b00b..b9f3d29343 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -57,7 +57,7 @@ module.exports = opts = to : req.session.user.email group_name: licence.name - completeJoinUrl: "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/complete_join?token=#{token}" + completeJoinUrl: "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/complete-join?token=#{token}" EmailHandler.sendEmail "completeJoinGroupAccount", opts, -> res.send 200 @@ -69,6 +69,12 @@ module.exports = return res.send 403 SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> SubscriptionGroupHandler.addUserToGroup subscription.admin_id, req.user.email, (err, user)-> - res.send "joined" + res.redir "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/successful-join" + renderSuccessfulJoinPage: (req, res)-> + subscription_id = req.params.subscription_id + licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + res.render "subscriptions/group/successful_join", + title: "Sucessfully joined group" + licenceName:licence.name diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index 8a6732e3b0..d5cc2ee3bc 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -25,8 +25,9 @@ module.exports = app.del '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup app.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage - app.post '/user/subscription/:subscription_id/group/begin_join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup - app.get '/user/subscription/:subscription_id/group/complete_join', AuthenticationController.requireLogin(), SubscriptionGroupController.completeJoin + app.post '/user/subscription/:subscription_id/group/begin-join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup + app.get '/user/subscription/:subscription_id/group/complete-join', AuthenticationController.requireLogin(), SubscriptionGroupController.completeJoin + app.get '/user/subscription/:subscription_id/group/successful-join', AuthenticationController.requireLogin(), SubscriptionGroupController.renderSuccessfulJoinPage #recurly callback app.post '/user/subscription/callback', SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback diff --git a/services/web/app/views/subscriptions/group/successful_join.jade b/services/web/app/views/subscriptions/group/successful_join.jade new file mode 100644 index 0000000000..b2b0f26e79 --- /dev/null +++ b/services/web/app/views/subscriptions/group/successful_join.jade @@ -0,0 +1,19 @@ +extends ../../layout + +block scripts + script(type='text/javascript'). + window.subscription_id = '#{subscription_id}' + +block content + .content.content-alt + .container + .row + .col-md-8.col-md-offset-2(ng-cloak) + .card + .page-header + h1 You have joined #{licenceName} + span(ng-show="!requestSent") + div You have claimed your premium account provided by #{licenceName}. + + a.btn.btn-success(href="/project") Done + diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 6876d48f75..c9b1f17841 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -350,7 +350,7 @@ module.exports = reloadModuleViewsOnEachRequest: true domainLicences: [ - {"domains":["gmail.com"], "adminUser_id":"555208e6ce4812e7d2319daa", "subscription_id":"555c6d1eb844bfb3964e0629", "name":"HENRYS AMAZING LICENCE"} + {"domains":["gmail.com"], "adminUser_id":"555208e6ce4812e7d2319daa", "subscription_id":"555c6d1eb844bfb3964e0629", "name":"HENRYS AMAZING LICENCE", verifyEmail:true} ] # ShareLaTeX Server Pro options (https://www.sharelatex.com/university/onsite.html) diff --git a/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee index f123985a70..4a31a5e0b1 100644 --- a/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee +++ b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee @@ -7,7 +7,7 @@ define [ $scope.joinGroup = -> console.log "joingin group" - request = $http.post "/user/subscription/#{subscription_id}/group/begin_join", {_csrf:window.csrfToken} + request = $http.post "/user/subscription/#{subscription_id}/group/begin-join", {_csrf:window.csrfToken} request.success (data, status)-> $scope.requestSent = true request.error (data, status)-> From 1d21bddcf59a446a292b2eb8d95c6d2447508002 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 15:06:36 +0100 Subject: [PATCH 05/13] fix Onetime token handler path --- .../coffee/Features/PasswordReset/PasswordResetHandler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee index 6a43dec3e6..f4bad9c593 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee @@ -1,7 +1,7 @@ settings = require("settings-sharelatex") async = require("async") UserGetter = require("../User/UserGetter") -OneTimeTokenHandler = require("./OneTimeTokenHandler") +OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") EmailHandler = require("../Email/EmailHandler") AuthenticationManager = require("../Authentication/AuthenticationManager") logger = require("logger-sharelatex") From 79fa49a43d1b79bf8793d1c13bea1f57f044de4f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 15:07:46 +0100 Subject: [PATCH 06/13] if a user is elelable to be part of a group subscription and they go to /user/subscription it should redirect them to the group subscription invite --- .../SubscriptionController.coffee | 8 +++-- .../SubscriptionControllerTests.coffee | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index adaf8e6e65..7971110eb7 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -8,7 +8,7 @@ RecurlyWrapper = require './RecurlyWrapper' Settings = require 'settings-sharelatex' logger = require('logger-sharelatex') GeoIpLookup = require("../../infrastructure/GeoIpLookup") - +SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") module.exports = SubscriptionController = @@ -84,9 +84,13 @@ module.exports = SubscriptionController = SecurityManager.getCurrentUser req, (error, user) => return next(error) if error? LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)-> + groupLicenceInviteUrl = SubscriptionDomainAllocator.getDomainLicencePage(user) if subscription?.customAccount logger.log user: user, "redirecting to plans" - res.redirect "/user/subscription/custom_account" + res.redirect "/user/subscription/custom_account" + else if groupLicenceInviteUrl? and !hasSubOrIsGroupMember + logger.log user:user, "redirecting to group subscription invite page" + res.redirect groupLicenceInviteUrl else if !hasSubOrIsGroupMember logger.log user: user, "redirecting to plans" res.redirect "/user/subscription/plans" diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee index 624f55ef0d..71fd9f51a5 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee @@ -58,7 +58,8 @@ describe "SubscriptionController sanboxed", -> gaExperiments:{} @GeoIpLookup = getCurrencyCode:sinon.stub() - + @SubscriptionDomainAllocator = + getDomainLicencePage:sinon.stub() @SubscriptionController = SandboxedModule.require modulePath, requires: '../../managers/SecurityManager': @SecurityManager './SubscriptionHandler': @SubscriptionHandler @@ -69,6 +70,7 @@ describe "SubscriptionController sanboxed", -> './RecurlyWrapper': @RecurlyWrapper "logger-sharelatex": log:-> "settings-sharelatex": @settings + "./SubscriptionDomainAllocator":@SubscriptionDomainAllocator @res = new MockResponse() @@ -206,6 +208,35 @@ describe "SubscriptionController sanboxed", -> @res.redirected.should.equal true @res.redirectedTo.should.equal "/user/subscription/plans" + describe "with a potential domain licence", -> + beforeEach () -> + @groupUrl = "/go/over-here" + @SubscriptionDomainAllocator.getDomainLicencePage.returns(@groupUrl) + + describe "without an existing subscription", -> + beforeEach (done)-> + @res.callback = done + @LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, false) + @SubscriptionController.userSubscriptionPage @req, @res + + it "should redirect to the group invite url", -> + @res.redirected.should.equal true + @res.redirectedTo.should.equal @groupUrl + + describe "with an existing subscription", -> + beforeEach (done)-> + @res.callback = done + @LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, true) + @SubscriptionController.userSubscriptionPage @req, @res + + + it "should render the dashboard", -> + @res.renderedTemplate.should.equal "subscriptions/dashboard" + + + + + describe "with a user with a paid subscription", -> beforeEach (done) -> @res.callback = done From 72e528e9d16a550e08bd9e6849571fb81fb3004f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 15:50:28 +0100 Subject: [PATCH 07/13] if you are alread in the group show the custom group page --- .../SubscriptionGroupController.coffee | 16 ++++++++++------ .../SubscriptionControllerTests.coffee | 4 ---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index b9f3d29343..3659f91c8f 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -6,6 +6,7 @@ settings = require("settings-sharelatex") OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") EmailHandler = require("../Email/EmailHandler") SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") +_ = require("underscore") module.exports = @@ -41,11 +42,14 @@ module.exports = renderGroupInvitePage: (req, res)-> subscription_id = req.params.subscription_id licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) - - res.render "subscriptions/group/invite", - title: "Group Invitation" - subscription_id:subscription_id - licenceName:licence.name + SubscriptionGroupHandler.getPopulatedListOfMembers licence.adminUser_id, (err, users)-> + userInSubscription = _.find users, (user)-> user._id == req.session.user._id + if userInSubscription? + return res.redirect("/user/subscription/custom_account") + res.render "subscriptions/group/invite", + title: "Group Invitation" + subscription_id:subscription_id + licenceName:licence.name beginJoinGroup: (req, res)-> subscription_id = req.params.subscription_id @@ -69,7 +73,7 @@ module.exports = return res.send 403 SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> SubscriptionGroupHandler.addUserToGroup subscription.admin_id, req.user.email, (err, user)-> - res.redir "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/successful-join" + res.redirect "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/successful-join" renderSuccessfulJoinPage: (req, res)-> subscription_id = req.params.subscription_id diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee index 71fd9f51a5..18488c0fe7 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee @@ -233,10 +233,6 @@ describe "SubscriptionController sanboxed", -> it "should render the dashboard", -> @res.renderedTemplate.should.equal "subscriptions/dashboard" - - - - describe "with a user with a paid subscription", -> beforeEach (done) -> @res.callback = done From f27c072ae1f4a64b280081cdf328ca7acfdc581f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 16:33:47 +0100 Subject: [PATCH 08/13] pull logic checking if user is already part of a group out of controller into handler --- .../SubscriptionGroupController.coffee | 15 ++++++------- .../SubscriptionGroupHandler.coffee | 11 +++++++++- .../Subscription/SubscriptionLocator.coffee | 3 +++ .../SubscriptionGroupHandlerTests.coffee | 21 ++++++++++++++++++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index 3659f91c8f..2cafcb2a24 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -41,15 +41,16 @@ module.exports = renderGroupInvitePage: (req, res)-> subscription_id = req.params.subscription_id + user_id = req.session.user._id licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) - SubscriptionGroupHandler.getPopulatedListOfMembers licence.adminUser_id, (err, users)-> - userInSubscription = _.find users, (user)-> user._id == req.session.user._id - if userInSubscription? + SubscriptionGroupHandler.isUserPartOfGroup user_id, licence.subscription_id, (err, partOfGroup)-> + if partOfGroup return res.redirect("/user/subscription/custom_account") - res.render "subscriptions/group/invite", - title: "Group Invitation" - subscription_id:subscription_id - licenceName:licence.name + else + res.render "subscriptions/group/invite", + title: "Group Invitation" + subscription_id:subscription_id + licenceName:licence.name beginJoinGroup: (req, res)-> subscription_id = req.params.subscription_id diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee index a4907f9544..bc05bd36dd 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee @@ -5,7 +5,7 @@ SubscriptionUpdater = require("./SubscriptionUpdater") SubscriptionLocator = require("./SubscriptionLocator") UserLocator = require("../User/UserLocator") LimitationsManager = require("./LimitationsManager") - +logger = require("logger-sharelatex") module.exports = @@ -37,6 +37,15 @@ module.exports = async.series jobs, (err)-> callback(err, users) + isUserPartOfGroup: (user_id, subscription_id, callback=(err, partOfGroup)->)-> + SubscriptionLocator.getSubscriptionByMemberIdAndId user_id, subscription_id, (err, subscription)-> + if subscription? + partOfGroup = true + else + partOfGroup = false + logger.log user_id:user_id, subscription_id:subscription_id, partOfGroup:partOfGroup, "checking if user is part of a group" + callback(err, partOfGroup) + buildUserViewModel = (user)-> u = diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee index 19e701443b..52dbf6b835 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee @@ -18,3 +18,6 @@ module.exports = getSubscription: (subscription_id, callback)-> Subscription.findOne _id:subscription_id, callback + + getSubscriptionByMemberIdAndId: (user_id, subscription_id, callback)-> + Subscription.findOne member_ids: user_id, _id:subscription_id, callback diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee index b9cff7ef72..ddc51a154a 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee @@ -10,10 +10,12 @@ describe "Subscription Group Handler", -> beforeEach -> @adminUser_id = "12321" @newEmail = "bob@smith.com" - @user = {_id:"3121321", email:@newEmail} + @user_id = "3121321" + @user = {_id:@user_id, email:@newEmail} @SubscriptionLocator = getUsersSubscription: sinon.stub() + getSubscriptionByMemberIdAndId: sinon.stub() @UserCreator = getUserOrCreateHoldingAccount: sinon.stub().callsArgWith(1, null, @user) @@ -100,3 +102,20 @@ describe "Subscription Group Handler", -> assert.deepEqual users[2], {_id:@subscription.member_ids[2]} done() + describe "isUserPartOfGroup", -> + beforeEach -> + @subscription_id = "123ed13123" + + it "should return true when user is part of subscription", (done)-> + @SubscriptionLocator.getSubscriptionByMemberIdAndId.callsArgWith(2, null, {_id:@subscription_id}) + @Handler.isUserPartOfGroup @user_id, @subscription_id, (err, partOfGroup)-> + partOfGroup.should.equal true + done() + + it "should return false when no subscription is found", (done)-> + @SubscriptionLocator.getSubscriptionByMemberIdAndId.callsArgWith(2, null) + @Handler.isUserPartOfGroup @user_id, @subscription_id, (err, partOfGroup)-> + partOfGroup.should.equal false + done() + + From 4773d6d22f15a249c33d605f793e739bbb67428f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 20:50:16 +0100 Subject: [PATCH 09/13] added tests around new endpoints for joining groups --- .../SubscriptionGroupController.coffee | 34 +++--- .../SubscriptionGroupHandler.coffee | 22 +++- .../PasswordResetHandlerTests.coffee | 2 +- .../SubscriptionGroupControllerTests.coffee | 110 +++++++++++++++++- .../SubscriptionGroupHandlerTests.coffee | 55 +++++++++ 5 files changed, 204 insertions(+), 19 deletions(-) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index 2cafcb2a24..e60e4e4600 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -2,9 +2,9 @@ SubscriptionGroupHandler = require("./SubscriptionGroupHandler") logger = require("logger-sharelatex") SubscriptionLocator = require("./SubscriptionLocator") +ErrorsController = require("../Errors/ErrorController") settings = require("settings-sharelatex") -OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") -EmailHandler = require("../Email/EmailHandler") + SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") _ = require("underscore") @@ -43,6 +43,8 @@ module.exports = subscription_id = req.params.subscription_id user_id = req.session.user._id licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + if !licence? + return ErrorsController.notFound(req, res) SubscriptionGroupHandler.isUserPartOfGroup user_id, licence.subscription_id, (err, partOfGroup)-> if partOfGroup return res.redirect("/user/subscription/custom_account") @@ -57,28 +59,28 @@ module.exports = user_id = req.session.user._id licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) if !licence? - res.send 500 - OneTimeTokenHandler.getNewToken subscription_id, (err, token)-> - opts = - to : req.session.user.email - group_name: licence.name - completeJoinUrl: "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/complete-join?token=#{token}" - EmailHandler.sendEmail "completeJoinGroupAccount", opts, -> + return ErrorsController.notFound(req, res) + SubscriptionGroupHandler.sendVerificationEmail subscription_id, licence.name, req.session.user.email, (err)-> + if err? + res.send 500 + else res.send 200 completeJoin: (req, res)-> subscription_id = req.params.subscription_id - OneTimeTokenHandler.getValueFromTokenAndExpire req.query.token, (err, token_subscription_id)-> - console.log token_subscription_id - if err? or subscription_id != token_subscription_id - return res.send 403 - SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> - SubscriptionGroupHandler.addUserToGroup subscription.admin_id, req.user.email, (err, user)-> - res.redirect "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/successful-join" + if !SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id)? + return ErrorsController.notFound(req, res) + SubscriptionGroupHandler.processGroupVerification req.session.user.email, subscription_id, req.query.token, (err)-> + if err? + res.send 500 + else + res.redirect "/user/subscription/#{subscription_id}/group/successful-join" renderSuccessfulJoinPage: (req, res)-> subscription_id = req.params.subscription_id licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + if !SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id)? + return ErrorsController.notFound(req, res) res.render "subscriptions/group/successful_join", title: "Sucessfully joined group" licenceName:licence.name diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee index bc05bd36dd..c816b5271c 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee @@ -6,8 +6,11 @@ SubscriptionLocator = require("./SubscriptionLocator") UserLocator = require("../User/UserLocator") LimitationsManager = require("./LimitationsManager") logger = require("logger-sharelatex") +OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") +EmailHandler = require("../Email/EmailHandler") +settings = require("settings-sharelatex") -module.exports = +module.exports = SubscriptionGroupHandler = addUserToGroup: (adminUser_id, newEmail, callback)-> UserCreator.getUserOrCreateHoldingAccount newEmail, (err, user)-> @@ -47,6 +50,23 @@ module.exports = callback(err, partOfGroup) + sendVerificationEmail: (subscription_id, licenceName, email, callback)-> + OneTimeTokenHandler.getNewToken subscription_id, (err, token)-> + opts = + to : email + group_name: licenceName + completeJoinUrl: "#{settings.siteUrl}/user/subscription/#{subscription_id}/group/complete-join?token=#{token}" + EmailHandler.sendEmail "completeJoinGroupAccount", opts, callback + + processGroupVerification: (userEmail, subscription_id, token, callback)-> + OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, token_subscription_id)-> + if err? or subscription_id != token_subscription_id + logger.err userEmail:userEmail, token:token, "token value not found for processing group verification" + return callback("token not found") + SubscriptionLocator.getSubscription subscription_id, (err, subscription)-> + SubscriptionGroupHandler.addUserToGroup subscription.admin_id, userEmail, callback + + buildUserViewModel = (user)-> u = email: user.email diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee index 67a4688c58..47fa4f2836 100644 --- a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetHandlerTests.coffee @@ -23,7 +23,7 @@ describe "PasswordResetHandler", -> setUserPassword:sinon.stub() @PasswordResetHandler = SandboxedModule.require modulePath, requires: "../User/UserGetter": @UserGetter - "./OneTimeTokenHandler": @OneTimeTokenHandler + "../Security/OneTimeTokenHandler": @OneTimeTokenHandler "../Email/EmailHandler":@EmailHandler "../Authentication/AuthenticationManager":@AuthenticationManager "settings-sharelatex": @settings diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee index 28279a1296..828657a1e1 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee @@ -13,16 +13,42 @@ describe "Subscription Group Controller", -> @GroupHandler = addUserToGroup: sinon.stub().callsArgWith(2, null, @user) removeUserFromGroup: sinon.stub().callsArgWith(2) + isUserPartOfGroup: sinon.stub() + sendVerificationEmail:sinon.stub() + processGroupVerification:sinon.stub() + @SubscriptionLocator = getUsersSubscription: sinon.stub().callsArgWith(1, null, @subscription) + @SubscriptionDomainAllocator = + findDomainLicenceBySubscriptionId:sinon.stub() + + @OneTimeTokenHandler = + getValueFromTokenAndExpire:sinon.stub() + + + @ErrorsController = + notFound:sinon.stub() + @Controller = SandboxedModule.require modulePath, requires: "./SubscriptionGroupHandler":@GroupHandler "logger-sharelatex": log:-> "./SubscriptionLocator": @SubscriptionLocator + "./SubscriptionDomainAllocator":@SubscriptionDomainAllocator + "../Errors/ErrorController":@ErrorsController + @adminUserId = "123jlkj" + @subscription_id = "123434325412" + @user_email = "bob@gmail.com" @req = session: - user: _id: @adminUserId + user: + _id: @adminUserId + email:@user_email + params: + subscription_id:@subscription_id + query:{} + + @token = "super-secret-token" describe "addUserToGroup", -> @@ -59,3 +85,85 @@ describe "Subscription Group Controller", -> path.should.equal("/") done() @Controller.renderSubscriptionGroupAdminPage @req, res + + + describe "renderGroupInvitePage", -> + describe "with a valid licence", -> + beforeEach -> + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({subscription_id:@subscription_id, adminUser_id:@adminUserId}) + + it "should render subscriptions/group/invite if not part of group", (done)-> + @GroupHandler.isUserPartOfGroup.callsArgWith(2, null, false) + res = + render : (pageName)=> + pageName.should.equal "subscriptions/group/invite" + done() + @Controller.renderGroupInvitePage @req, res + + it "should redirect to custom page if is already part of group", (done)-> + @GroupHandler.isUserPartOfGroup.callsArgWith(2, null, true) + res = + redirect : (location)=> + location.should.equal "/user/subscription/custom_account" + done() + @Controller.renderGroupInvitePage @req, res + + describe "without a valid licence", -> + beforeEach -> + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + + it "should send a 500", (done)-> + @Controller.renderGroupInvitePage @req, {} + @ErrorsController.notFound.called.should.equal true + done() + + + + describe "beginJoinGroup", -> + describe "with a valid licence", -> + beforeEach -> + @licenceName = "get amazing licence" + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) + @GroupHandler.sendVerificationEmail.callsArgWith(3) + + it "should ask the SubscriptionGroupHandler to send the verification email", (done)-> + res = + send : (statusCode)=> + statusCode.should.equal 200 + @GroupHandler.sendVerificationEmail.calledWith(@subscription_id, @licenceName, @user_email).should.equal true + done() + @Controller.beginJoinGroup @req, res + + describe "without a valid licence", -> + beforeEach -> + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + + it "should send a 500", (done)-> + @Controller.beginJoinGroup @req, {} + @ErrorsController.notFound.called.should.equal true + done() + + + describe "completeJoin", -> + describe "with a valid licence", -> + beforeEach -> + @GroupHandler.processGroupVerification.callsArgWith(3) + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) + + it "should redirect to the success page upon processGroupVerification", (done)-> + @req.query.token = @token + res = + redirect : (location)=> + @GroupHandler.processGroupVerification.calledWith(@user_email, @subscription_id, @token).should.equal true + location.should.equal "/user/subscription/#{@subscription_id}/group/successful-join" + done() + @Controller.completeJoin @req, res + + describe "without a valid licence", -> + beforeEach -> + @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + + it "should send a 500", (done)-> + @Controller.completeJoin @req, {} + @ErrorsController.notFound.called.should.equal true + done() \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee index ddc51a154a..03af764442 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupHandlerTests.coffee @@ -16,6 +16,7 @@ describe "Subscription Group Handler", -> @SubscriptionLocator = getUsersSubscription: sinon.stub() getSubscriptionByMemberIdAndId: sinon.stub() + getSubscription: sinon.stub() @UserCreator = getUserOrCreateHoldingAccount: sinon.stub().callsArgWith(1, null, @user) @@ -30,6 +31,16 @@ describe "Subscription Group Handler", -> @LimitationsManager = hasGroupMembersLimitReached: sinon.stub() + @OneTimeTokenHandler = + getValueFromTokenAndExpire:sinon.stub() + getNewToken:sinon.stub() + + @EmailHandler = + sendEmail:sinon.stub() + + @settings = + siteUrl:"http://www.sharelatex.com" + @Handler = SandboxedModule.require modulePath, requires: "logger-sharelatex": log:-> "../User/UserCreator": @UserCreator @@ -37,6 +48,13 @@ describe "Subscription Group Handler", -> "./SubscriptionLocator": @SubscriptionLocator "../User/UserLocator": @UserLocator "./LimitationsManager": @LimitationsManager + "../Security/OneTimeTokenHandler":@OneTimeTokenHandler + "../Email/EmailHandler":@EmailHandler + "settings-sharelatex":@settings + "logger-sharelatex": + err:-> + log:-> + describe "addUserToGroup", -> it "should find or create the user", (done)-> @@ -119,3 +137,40 @@ describe "Subscription Group Handler", -> done() + describe "sendVerificationEmail", -> + beforeEach -> + @token = "secret token" + @subscription_id = "123ed13123" + @licenceName = "great licnece" + @email = "bob@smith.com" + @OneTimeTokenHandler.getNewToken.callsArgWith(1, null, @token) + @EmailHandler.sendEmail.callsArgWith(2) + + it "should put a one time token into the email", (done)-> + @Handler.sendVerificationEmail @subscription_id, @licenceName, @email, (err)=> + emailOpts = @EmailHandler.sendEmail.args[0][1] + emailOpts.completeJoinUrl.should.equal "#{@settings.siteUrl}/user/subscription/#{@subscription_id}/group/complete-join?token=#{@token}" + emailOpts.to.should.equal @email + emailOpts.group_name.should.equal @licenceName + done() + + describe "processGroupVerification", -> + beforeEach -> + @token = "31dDAd2Da" + @subscription_id = "31DSd1123D" + @admin_id = "eDSda1ew" + @OneTimeTokenHandler.getValueFromTokenAndExpire.callsArgWith(1, null, @subscription_id) + @SubscriptionLocator.getSubscription.callsArgWith(1, null, {admin_id:@admin_id}) + @Handler.addUserToGroup = sinon.stub().callsArgWith(2) + + it "should addUserToGroup", (done)-> + @Handler.processGroupVerification @email, @subscription_id, @token, (err)=> + @Handler.addUserToGroup.calledWith(@admin_id, @email).should.equal true + done() + + + + + + + From 22b94e924687e4ead0096638094cb6b2b767e3ca Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 20:57:54 +0100 Subject: [PATCH 10/13] renamed SubscriptionDomainAllocator -> SubscriptionDomainHandler --- .../SubscriptionController.coffee | 4 ++-- ...offee => SubscriptionDomainHandler.coffee} | 10 ++++----- .../SubscriptionGroupController.coffee | 12 +++++----- .../Features/User/UserController.coffee | 2 +- .../SubscriptionControllerTests.coffee | 6 ++--- ... => SubscriptionDomainHandlerTests.coffee} | 22 +++++++++---------- .../SubscriptionGroupControllerTests.coffee | 16 +++++++------- .../coffee/User/UserControllerTests.coffee | 4 ++-- 8 files changed, 38 insertions(+), 38 deletions(-) rename services/web/app/coffee/Features/Subscription/{SubscriptionDomainAllocator.coffee => SubscriptionDomainHandler.coffee} (79%) rename services/web/test/UnitTests/coffee/Subscription/{SubscriptionDomainAllocatorTests.coffee => SubscriptionDomainHandlerTests.coffee} (66%) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 7971110eb7..ea03458093 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -8,7 +8,7 @@ RecurlyWrapper = require './RecurlyWrapper' Settings = require 'settings-sharelatex' logger = require('logger-sharelatex') GeoIpLookup = require("../../infrastructure/GeoIpLookup") -SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") +SubscriptionDomainHandler = require("./SubscriptionDomainHandler") module.exports = SubscriptionController = @@ -84,7 +84,7 @@ module.exports = SubscriptionController = SecurityManager.getCurrentUser req, (error, user) => return next(error) if error? LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)-> - groupLicenceInviteUrl = SubscriptionDomainAllocator.getDomainLicencePage(user) + groupLicenceInviteUrl = SubscriptionDomainHandler.getDomainLicencePage(user) if subscription?.customAccount logger.log user: user, "redirecting to plans" res.redirect "/user/subscription/custom_account" diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee similarity index 79% rename from services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee rename to services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee index 90401b0de5..b57c3492f6 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionDomainAllocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionDomainHandler.coffee @@ -4,18 +4,18 @@ settings = require("settings-sharelatex") SubscriptionGroupHandler = require("./SubscriptionGroupHandler") _s = require("underscore.string") -module.exports = SubscriptionDomainAllocator = +module.exports = SubscriptionDomainHandler = getLicenceUserCanJoin: (user, callback)-> - licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + licence = SubscriptionDomainHandler._findDomainLicence(user.email) if licence? callback null, licence else callback() attemptToJoinGroup: (user, callback)-> - licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + licence = SubscriptionDomainHandler._findDomainLicence(user.email) if licence? and user.emailVerified SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback else @@ -26,7 +26,7 @@ module.exports = SubscriptionDomainAllocator = getDomainLicencePage: (user)-> - licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + licence = SubscriptionDomainHandler._findDomainLicence(user.email) if licence?.verifyEmail return "/user/subscription/#{licence.subscription_id}/group/invited" else @@ -34,7 +34,7 @@ module.exports = SubscriptionDomainAllocator = autoAllocate: (user, callback = ->)-> - licence = SubscriptionDomainAllocator._findDomainLicence(user.email) + licence = SubscriptionDomainHandler._findDomainLicence(user.email) # if licence? SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index e60e4e4600..31d8077788 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -5,7 +5,7 @@ SubscriptionLocator = require("./SubscriptionLocator") ErrorsController = require("../Errors/ErrorController") settings = require("settings-sharelatex") -SubscriptionDomainAllocator = require("./SubscriptionDomainAllocator") +SubscriptionDomainHandler = require("./SubscriptionDomainHandler") _ = require("underscore") module.exports = @@ -42,7 +42,7 @@ module.exports = renderGroupInvitePage: (req, res)-> subscription_id = req.params.subscription_id user_id = req.session.user._id - licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + licence = SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id) if !licence? return ErrorsController.notFound(req, res) SubscriptionGroupHandler.isUserPartOfGroup user_id, licence.subscription_id, (err, partOfGroup)-> @@ -57,7 +57,7 @@ module.exports = beginJoinGroup: (req, res)-> subscription_id = req.params.subscription_id user_id = req.session.user._id - licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) + licence = SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id) if !licence? return ErrorsController.notFound(req, res) SubscriptionGroupHandler.sendVerificationEmail subscription_id, licence.name, req.session.user.email, (err)-> @@ -68,7 +68,7 @@ module.exports = completeJoin: (req, res)-> subscription_id = req.params.subscription_id - if !SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id)? + if !SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id)? return ErrorsController.notFound(req, res) SubscriptionGroupHandler.processGroupVerification req.session.user.email, subscription_id, req.query.token, (err)-> if err? @@ -78,8 +78,8 @@ module.exports = renderSuccessfulJoinPage: (req, res)-> subscription_id = req.params.subscription_id - licence = SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id) - if !SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId(subscription_id)? + licence = SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id) + if !SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id)? return ErrorsController.notFound(req, res) res.render "subscriptions/group/successful_join", title: "Sucessfully joined group" diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee index 1efe77e44c..a374939545 100644 --- a/services/web/app/coffee/Features/User/UserController.coffee +++ b/services/web/app/coffee/Features/User/UserController.coffee @@ -8,7 +8,7 @@ metrics = require("../../infrastructure/Metrics") Url = require("url") AuthenticationManager = require("../Authentication/AuthenticationManager") UserUpdater = require("./UserUpdater") -SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator") +SubscriptionDomainHandler = require("../Subscription/SubscriptionDomainHandler") EmailHandler = require("../Email/EmailHandler") OneTimeTokenHandler = require "../Security/OneTimeTokenHandler" settings = require "settings-sharelatex" diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee index 18488c0fe7..56e31c5f26 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee @@ -58,7 +58,7 @@ describe "SubscriptionController sanboxed", -> gaExperiments:{} @GeoIpLookup = getCurrencyCode:sinon.stub() - @SubscriptionDomainAllocator = + @SubscriptionDomainHandler = getDomainLicencePage:sinon.stub() @SubscriptionController = SandboxedModule.require modulePath, requires: '../../managers/SecurityManager': @SecurityManager @@ -70,7 +70,7 @@ describe "SubscriptionController sanboxed", -> './RecurlyWrapper': @RecurlyWrapper "logger-sharelatex": log:-> "settings-sharelatex": @settings - "./SubscriptionDomainAllocator":@SubscriptionDomainAllocator + "./SubscriptionDomainHandler":@SubscriptionDomainHandler @res = new MockResponse() @@ -211,7 +211,7 @@ describe "SubscriptionController sanboxed", -> describe "with a potential domain licence", -> beforeEach () -> @groupUrl = "/go/over-here" - @SubscriptionDomainAllocator.getDomainLicencePage.returns(@groupUrl) + @SubscriptionDomainHandler.getDomainLicencePage.returns(@groupUrl) describe "without an existing subscription", -> beforeEach (done)-> diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainAllocatorTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee similarity index 66% rename from services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainAllocatorTests.coffee rename to services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee index 7c48b889fb..21a44da1ca 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainAllocatorTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee @@ -3,10 +3,10 @@ SandboxedModule = require('sandboxed-module') assert = require('assert') path = require('path') sinon = require('sinon') -modulePath = path.join __dirname, "../../../../app/js/Features/Subscription/SubscriptionDomainAllocator" +modulePath = path.join __dirname, "../../../../app/js/Features/Subscription/SubscriptionDomainHandler" expect = require("chai").expect -describe "SubscriptionDomainAllocator", -> +describe "SubscriptionDomainHandler", -> beforeEach -> @@ -18,7 +18,7 @@ describe "SubscriptionDomainAllocator", -> ] @SubscriptionGroupHandler = addUserToGroup: sinon.stub().callsArg(2) - @SubscriptionDomainAllocator = SandboxedModule.require modulePath, requires: + @SubscriptionDomainHandler = SandboxedModule.require modulePath, requires: "settings-sharelatex":@settings "logger-sharelatex": log:-> "./SubscriptionGroupHandler": @SubscriptionGroupHandler @@ -28,33 +28,33 @@ describe "SubscriptionDomainAllocator", -> describe "_findDomainLicence", -> it "should find the domain", (done)-> - licence = @SubscriptionDomainAllocator._findDomainLicence "bob@uni.edu" + licence = @SubscriptionDomainHandler._findDomainLicence "bob@uni.edu" licence.adminUser_id.should.equal @adminUser_id done() it "should find one of the other emails in the domain list", (done)-> - licence = @SubscriptionDomainAllocator._findDomainLicence "sally@student.uni.edu" + licence = @SubscriptionDomainHandler._findDomainLicence "sally@student.uni.edu" licence.adminUser_id.should.equal @adminUser_id done() it "should return undefined if no licence matches", (done)-> - licence = @SubscriptionDomainAllocator._findDomainLicence "bob@other.edu" + licence = @SubscriptionDomainHandler._findDomainLicence "bob@other.edu" expect(licence).to.not.exist done(licence) describe "autoAllocate", -> beforeEach -> @email = "bob@somewhere.com" - @SubscriptionDomainAllocator._findDomainLicence = sinon.stub() + @SubscriptionDomainHandler._findDomainLicence = sinon.stub() it "should call the SubscriptionGroupHandler if there is licence", (done)-> - @SubscriptionDomainAllocator._findDomainLicence.returns(@settings.domainLicences[1]) - @SubscriptionDomainAllocator.autoAllocate {email:@email}, (err)=> + @SubscriptionDomainHandler._findDomainLicence.returns(@settings.domainLicences[1]) + @SubscriptionDomainHandler.autoAllocate {email:@email}, (err)=> @SubscriptionGroupHandler.addUserToGroup.calledWith(@adminUser_id, @email).should.equal true done() it "should not call the SubscriptionGroupHandler if there is no licence", (done)-> - @SubscriptionDomainAllocator._findDomainLicence.returns() - @SubscriptionDomainAllocator.autoAllocate {email:@email}, (err)=> + @SubscriptionDomainHandler._findDomainLicence.returns() + @SubscriptionDomainHandler.autoAllocate {email:@email}, (err)=> @SubscriptionGroupHandler.addUserToGroup.called.should.equal false done() \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee index 828657a1e1..8dd5d93e8c 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionGroupControllerTests.coffee @@ -19,7 +19,7 @@ describe "Subscription Group Controller", -> @SubscriptionLocator = getUsersSubscription: sinon.stub().callsArgWith(1, null, @subscription) - @SubscriptionDomainAllocator = + @SubscriptionDomainHandler = findDomainLicenceBySubscriptionId:sinon.stub() @OneTimeTokenHandler = @@ -33,7 +33,7 @@ describe "Subscription Group Controller", -> "./SubscriptionGroupHandler":@GroupHandler "logger-sharelatex": log:-> "./SubscriptionLocator": @SubscriptionLocator - "./SubscriptionDomainAllocator":@SubscriptionDomainAllocator + "./SubscriptionDomainHandler":@SubscriptionDomainHandler "../Errors/ErrorController":@ErrorsController @adminUserId = "123jlkj" @@ -90,7 +90,7 @@ describe "Subscription Group Controller", -> describe "renderGroupInvitePage", -> describe "with a valid licence", -> beforeEach -> - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({subscription_id:@subscription_id, adminUser_id:@adminUserId}) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns({subscription_id:@subscription_id, adminUser_id:@adminUserId}) it "should render subscriptions/group/invite if not part of group", (done)-> @GroupHandler.isUserPartOfGroup.callsArgWith(2, null, false) @@ -110,7 +110,7 @@ describe "Subscription Group Controller", -> describe "without a valid licence", -> beforeEach -> - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns(undefined) it "should send a 500", (done)-> @Controller.renderGroupInvitePage @req, {} @@ -123,7 +123,7 @@ describe "Subscription Group Controller", -> describe "with a valid licence", -> beforeEach -> @licenceName = "get amazing licence" - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) @GroupHandler.sendVerificationEmail.callsArgWith(3) it "should ask the SubscriptionGroupHandler to send the verification email", (done)-> @@ -136,7 +136,7 @@ describe "Subscription Group Controller", -> describe "without a valid licence", -> beforeEach -> - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns(undefined) it "should send a 500", (done)-> @Controller.beginJoinGroup @req, {} @@ -148,7 +148,7 @@ describe "Subscription Group Controller", -> describe "with a valid licence", -> beforeEach -> @GroupHandler.processGroupVerification.callsArgWith(3) - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns({name:@licenceName}) it "should redirect to the success page upon processGroupVerification", (done)-> @req.query.token = @token @@ -161,7 +161,7 @@ describe "Subscription Group Controller", -> describe "without a valid licence", -> beforeEach -> - @SubscriptionDomainAllocator.findDomainLicenceBySubscriptionId.returns(undefined) + @SubscriptionDomainHandler.findDomainLicenceBySubscriptionId.returns(undefined) it "should send a 500", (done)-> @Controller.completeJoin @req, {} diff --git a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee index cb94bc8618..3bc8a3ca46 100644 --- a/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/User/UserControllerTests.coffee @@ -36,7 +36,7 @@ describe "UserController", -> setUserPassword: sinon.stub() @ReferalAllocator = allocate:sinon.stub() - @SubscriptionDomainAllocator = + @SubscriptionDomainHandler = autoAllocate:sinon.stub() @UserUpdater = changeEmailAddress:sinon.stub() @@ -56,7 +56,7 @@ describe "UserController", -> "../Authentication/AuthenticationController": @AuthenticationController "../Authentication/AuthenticationManager": @AuthenticationManager "../Referal/ReferalAllocator":@ReferalAllocator - "../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator + "../Subscription/SubscriptionDomainHandler":@SubscriptionDomainHandler "../Email/EmailHandler": @EmailHandler "../Security/OneTimeTokenHandler": @OneTimeTokenHandler "crypto": @crypto = {} From b657c5207e0c3c70a2859de6675936401c6bd881 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 21:45:29 +0100 Subject: [PATCH 11/13] cleaned up the views a bit --- .../app/views/subscriptions/group/invite.jade | 23 +++++++++++-------- .../subscriptions/group/successful_join.jade | 11 +++++---- ...roup-subscription-invite-controller.coffee | 5 ++-- .../SubscriptionDomainHandlerTests.coffee | 2 -- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/services/web/app/views/subscriptions/group/invite.jade b/services/web/app/views/subscriptions/group/invite.jade index 06089e3d4b..dbbd4c7f0d 100644 --- a/services/web/app/views/subscriptions/group/invite.jade +++ b/services/web/app/views/subscriptions/group/invite.jade @@ -11,15 +11,18 @@ block content .col-md-8.col-md-offset-2(ng-cloak) .card(ng-controller="GroupSubscriptionInviteController") .page-header - h1 You are invited to Join #{licenceName} - span(ng-show="!requestSent") - div You can claim a free premium account provided by #{licenceName} by verifying your email + h1.text-centered You are invited to Join #{licenceName} + div(ng-show="!requestSent").row.text-centered - a.btn.btn-default(href="/project") Skip -   - a.btn.btn.btn-primary(ng-click="joinGroup()") Verify email and join Group + .col-md-12 You can claim a free premium account provided by #{licenceName} by verifying your email + .col-md-12   + .col-md-12 + span.pull-right + a.btn.btn-default(href="/project") Skip +   + a.btn.btn.btn-primary(ng-click="joinGroup()") Verify email and join Group - span(ng-show="requestSent") - div Check your email to complete joinging the group - - a.btn(href="/project") Continue to projects + span(ng-show="requestSent").row.text-centered + .col-md-8 Check your email to complete joinging the group + .col-md-4.pull-right + a.btn.btn.btn-primary(href="/project") Continue to projects diff --git a/services/web/app/views/subscriptions/group/successful_join.jade b/services/web/app/views/subscriptions/group/successful_join.jade index b2b0f26e79..6810e3a8f8 100644 --- a/services/web/app/views/subscriptions/group/successful_join.jade +++ b/services/web/app/views/subscriptions/group/successful_join.jade @@ -10,10 +10,11 @@ block content .row .col-md-8.col-md-offset-2(ng-cloak) .card - .page-header + .page-header.row.text-centered h1 You have joined #{licenceName} - span(ng-show="!requestSent") - div You have claimed your premium account provided by #{licenceName}. - - a.btn.btn-success(href="/project") Done + div(ng-show="!requestSent").row.text-centered + .span-md-12 + div You have claimed your premium account provided by #{licenceName}. + .span-md-12 + a.btn.btn-success(href="/project") Done diff --git a/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee index 4a31a5e0b1..ccfae4a3cf 100644 --- a/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee +++ b/services/web/public/coffee/main/subscription/group-subscription-invite-controller.coffee @@ -6,9 +6,10 @@ define [ $scope.requestSent = false $scope.joinGroup = -> - console.log "joingin group" + $scope.requestSent = true request = $http.post "/user/subscription/#{subscription_id}/group/begin-join", {_csrf:window.csrfToken} request.success (data, status)-> - $scope.requestSent = true + if status != 200 # assume request worked + $scope.requestSent = false request.error (data, status)-> console.log "the request failed" \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee index 21a44da1ca..82c94d352c 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionDomainHandlerTests.coffee @@ -23,8 +23,6 @@ describe "SubscriptionDomainHandler", -> "logger-sharelatex": log:-> "./SubscriptionGroupHandler": @SubscriptionGroupHandler - - describe "_findDomainLicence", -> it "should find the domain", (done)-> From ca28d8ee701538966936871abd9f61631ffed204 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 May 2015 22:15:46 +0100 Subject: [PATCH 12/13] changed views to use translations --- .../web/app/views/subscriptions/group/invite.jade | 13 +++++++------ .../views/subscriptions/group/successful_join.jade | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/services/web/app/views/subscriptions/group/invite.jade b/services/web/app/views/subscriptions/group/invite.jade index dbbd4c7f0d..41f1c09b5a 100644 --- a/services/web/app/views/subscriptions/group/invite.jade +++ b/services/web/app/views/subscriptions/group/invite.jade @@ -11,18 +11,19 @@ block content .col-md-8.col-md-offset-2(ng-cloak) .card(ng-controller="GroupSubscriptionInviteController") .page-header - h1.text-centered You are invited to Join #{licenceName} + h1.text-centered #{translate("you_are_invited_to_group", {groupName:licenceName})} div(ng-show="!requestSent").row.text-centered - .col-md-12 You can claim a free premium account provided by #{licenceName} by verifying your email + .col-md-12 #{translate("you_can_claim_premium_account", {groupName:licenceName})} .col-md-12   .col-md-12 span.pull-right - a.btn.btn-default(href="/project") Skip + a.btn.btn-default(href="/project") #{translate("not_now")}   - a.btn.btn.btn-primary(ng-click="joinGroup()") Verify email and join Group + a.btn.btn.btn-primary(ng-click="joinGroup()") #{translate("verify_email_join_group")} + span(ng-show="requestSent").row.text-centered - .col-md-8 Check your email to complete joinging the group + .col-md-8 #{translate("check_email_to_complete_group")} .col-md-4.pull-right - a.btn.btn.btn-primary(href="/project") Continue to projects + a.btn.btn.btn-primary(href="/project") #{translate("done")} diff --git a/services/web/app/views/subscriptions/group/successful_join.jade b/services/web/app/views/subscriptions/group/successful_join.jade index 6810e3a8f8..0900c875e3 100644 --- a/services/web/app/views/subscriptions/group/successful_join.jade +++ b/services/web/app/views/subscriptions/group/successful_join.jade @@ -11,10 +11,11 @@ block content .col-md-8.col-md-offset-2(ng-cloak) .card .page-header.row.text-centered - h1 You have joined #{licenceName} + h1 #{translate("you_have_joined", {groupName:licenceName})} div(ng-show="!requestSent").row.text-centered + .span-md-12 #{translate("claim_premium_account", {groupName:licenceName})} + div .span-md-12 - div You have claimed your premium account provided by #{licenceName}. - .span-md-12 - a.btn.btn-success(href="/project") Done + a.btn.btn-success(href="/project") #{translate("Done")} + From 702fdc14803df804eb017f2c93a16cff94edf4d7 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 29 May 2015 12:09:28 +0100 Subject: [PATCH 13/13] improved views for domain licence invites --- .../app/views/subscriptions/group/invite.jade | 30 +++++++++++-------- .../subscriptions/group/successful_join.jade | 12 +++++--- services/web/config/settings.defaults.coffee | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/services/web/app/views/subscriptions/group/invite.jade b/services/web/app/views/subscriptions/group/invite.jade index 41f1c09b5a..db560abd3b 100644 --- a/services/web/app/views/subscriptions/group/invite.jade +++ b/services/web/app/views/subscriptions/group/invite.jade @@ -13,17 +13,23 @@ block content .page-header h1.text-centered #{translate("you_are_invited_to_group", {groupName:licenceName})} div(ng-show="!requestSent").row.text-centered - - .col-md-12 #{translate("you_can_claim_premium_account", {groupName:licenceName})} - .col-md-12   - .col-md-12 - span.pull-right - a.btn.btn-default(href="/project") #{translate("not_now")} -   - a.btn.btn.btn-primary(ng-click="joinGroup()") #{translate("verify_email_join_group")} + .row + .col-md-12 #{translate("group_provides_you_with_premium_account", {groupName:licenceName})} + .row + .col-md-12   + .row + .col-md-12 + .text-center + a.btn.btn-default(href="/project") #{translate("not_now")} +   + a.btn.btn.btn-primary(ng-click="joinGroup()") #{translate("verify_email_address")} - span(ng-show="requestSent").row.text-centered - .col-md-8 #{translate("check_email_to_complete_group")} - .col-md-4.pull-right - a.btn.btn.btn-primary(href="/project") #{translate("done")} + span(ng-show="requestSent").row.text-centered.text-center + .row + .col-md-12 #{translate("check_email_to_complete_the_upgrade")} + .row + .col-md-12   + .row + .col-md-12 + a.btn.btn.btn-primary(href="/project") #{translate("done")} diff --git a/services/web/app/views/subscriptions/group/successful_join.jade b/services/web/app/views/subscriptions/group/successful_join.jade index 0900c875e3..6de282aa36 100644 --- a/services/web/app/views/subscriptions/group/successful_join.jade +++ b/services/web/app/views/subscriptions/group/successful_join.jade @@ -13,9 +13,13 @@ block content .page-header.row.text-centered h1 #{translate("you_have_joined", {groupName:licenceName})} div(ng-show="!requestSent").row.text-centered - .span-md-12 #{translate("claim_premium_account", {groupName:licenceName})} - div - .span-md-12 - a.btn.btn-success(href="/project") #{translate("Done")} + .row + .span-md-12 #{translate("claim_premium_account", {groupName:licenceName})} + div + .row + .col-md-12   + .row + .span-md-12 + a.btn.btn-success(href="/project") #{translate("Done")} diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index c9b1f17841..ba32e7c4d0 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -350,7 +350,7 @@ module.exports = reloadModuleViewsOnEachRequest: true domainLicences: [ - {"domains":["gmail.com"], "adminUser_id":"555208e6ce4812e7d2319daa", "subscription_id":"555c6d1eb844bfb3964e0629", "name":"HENRYS AMAZING LICENCE", verifyEmail:true} + ] # ShareLaTeX Server Pro options (https://www.sharelatex.com/university/onsite.html)