Bläddra i källkod

Remove public registration and require that a user be registered by an admin

James Allen 11 år sedan
förälder
incheckning
9b8cf7bcfa

+ 20 - 28
services/web/app/coffee/Features/Email/EmailBuilder.coffee

@@ -6,25 +6,19 @@ settings = require("settings-sharelatex")
 
 
 templates = {}
 templates = {}
 
 
-templates.welcome =	
-	subject:  _.template "Welcome to ShareLaTeX"
+templates.registered =	
+	subject:  _.template "Activate your #{settings.appName} Account"
 	layout: PersonalEmailLayout
 	layout: PersonalEmailLayout
-	type:"lifecycle"
-	compiledTemplate: _.template '''
-<p>Hi <%= first_name %>,</p>
-
-<p>Thanks for signing up to ShareLaTeX! If you ever get lost, you can log in again <a href="<%= siteUrl %>/login">here</a> with the email address "<%= to %>".</p>
+	type: "notification"
+	compiledTemplate: _.template """
+<p>Congratulations, you've just had an account created for you on #{settings.appName} with the email address "<%= to %>".</p>
 
 
-<p>If you're new to LaTeX, take a look at our <a href="<%= siteUrl %>/learn">Help Guides</a> and <a href="<%= siteUrl %>/templates">Templates</a>.</p>
+<p><a href="<%= setNewPasswordUrl %>">Click here to set your password and log in.</a></p>
 
 
-<p>
-Regards, <br>
-Henry <br>
-ShareLaTeX Co-founder
-</p>
+<p>Once you have reset your password you can <a href="#{settings.siteUrl}/login">log in here</a>.</p>
 
 
-<p>PS. We love talking to our users about ShareLaTeX. Reply to this email to get in touch us with us directly, whatever the reason. Questions, comments, problems, suggestions, all welcome!<p>
-'''
+<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
+"""
 
 
 templates.canceledSubscription = 
 templates.canceledSubscription = 
 	subject:  _.template "ShareLaTeX thoughts"
 	subject:  _.template "ShareLaTeX thoughts"
@@ -44,16 +38,16 @@ ShareLaTeX Co-founder
 '''
 '''
 
 
 templates.passwordResetRequested =	
 templates.passwordResetRequested =	
-	subject:  _.template "Password Reset - ShareLatex.com"
+	subject:  _.template "Password Reset - #{settings.appName}"
 	layout: NotificationEmailLayout
 	layout: NotificationEmailLayout
 	type:"notification"
 	type:"notification"
-	compiledTemplate: _.template '''
-<h1 class="h1">Password Reset</h1>
+	compiledTemplate: _.template """
+<h2>Password Reset</h2>
 <p>
 <p>
-We got a request to reset your ShareLaTeX password.
+We got a request to reset your #{settings.appName} password.
 <p>
 <p>
 <center>
 <center>
-	<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:12.5px;">
+	<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:24px;">
 		<div style="padding-right:10px;padding-left:10px">
 		<div style="padding-right:10px;padding-left:10px">
 			<a href="<%= setNewPasswordUrl %>" style="text-decoration:none" target="_blank">
 			<a href="<%= setNewPasswordUrl %>" style="text-decoration:none" target="_blank">
 				<span style= "font-size:16px;font-family:Arial;font-weight:bold;color:#fff;white-space:nowrap;display:block; text-align:center">
 				<span style= "font-size:16px;font-family:Arial;font-weight:bold;color:#fff;white-space:nowrap;display:block; text-align:center">
@@ -70,18 +64,17 @@ If you didn't request a password reset, let us know.
 
 
 </p>
 </p>
 <p>Thank you</p>
 <p>Thank you</p>
-<p> <a href="<%= siteUrl %>"> ShareLatex.com </a></p>
-'''
+<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
+"""
 
 
 templates.projectSharedWithYou = 
 templates.projectSharedWithYou = 
 	subject: _.template "<%= owner.email %> wants to share <%= project.name %> with you"
 	subject: _.template "<%= owner.email %> wants to share <%= project.name %> with you"
 	layout: NotificationEmailLayout
 	layout: NotificationEmailLayout
 	type:"notification"
 	type:"notification"
-	compiledTemplate: _.template '''
+	compiledTemplate: _.template """
 <p>Hi, <%= owner.email %> wants to share <a href="<%= project.url %>">'<%= project.name %>'</a> with you</p>
 <p>Hi, <%= owner.email %> wants to share <a href="<%= project.url %>">'<%= project.name %>'</a> with you</p>
-<p>&nbsp;</p>
 <center>
 <center>
-	<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:12.5px;">
+	<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:24px;">
 		<div style="padding-right:10px;padding-left:10px">
 		<div style="padding-right:10px;padding-left:10px">
 			<a href="<%= project.url %>" style="text-decoration:none" target="_blank">
 			<a href="<%= project.url %>" style="text-decoration:none" target="_blank">
 				<span style= "font-size:16px;font-family:Helvetica,Arial;font-weight:400;color:#fff;white-space:nowrap;display:block; text-align:center">
 				<span style= "font-size:16px;font-family:Helvetica,Arial;font-weight:400;color:#fff;white-space:nowrap;display:block; text-align:center">
@@ -92,9 +85,8 @@ templates.projectSharedWithYou =
 	</div>
 	</div>
 </center>
 </center>
 <p> Thank you</p>
 <p> Thank you</p>
-<p> <a href="<%= siteUrl %>"> ShareLatex.com </a></p>
-
-'''
+<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
+"""
 
 
 module.exports =
 module.exports =
 
 

+ 5 - 33
services/web/app/coffee/Features/Email/Layouts/NotificationEmailLayout.coffee

@@ -1,6 +1,7 @@
 _ = require("underscore")
 _ = require("underscore")
+settings = require "settings-sharelatex"
 
 
-module.exports = _.template '''
+module.exports = _.template """
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html>
 <html>
 	<head>
 	<head>
@@ -311,12 +312,8 @@ module.exports = _.template '''
 									<!-- // Begin Template Header \\ -->
 									<!-- // Begin Template Header \\ -->
 									<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateHeader">
 									<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateHeader">
 										<tr>
 										<tr>
-											<td class="headerContent" style="padding: 25px;border-bottom:#dadf90;background-color:#F6F6F6;text-align:left;">
-
-												<!-- // Begin Module: Standard Header Image \\ -->
-												<img src="https://www.sharelatex.com/img/logo.png" style="max-width:600px;" id="headerImage campaign-icon"  />
-												<!-- // End Module: Standard Header Image \\ -->
-
+											<td class="headerContent" style="padding: 25px;border-bottom:#dadf90;background-color:#F6F6F6;text-align:left;font-size:18px">
+												#{settings.appName}
 											</td>
 											</td>
 										</tr>
 										</tr>
 									</table>
 									</table>
@@ -346,31 +343,6 @@ module.exports = _.template '''
 									<!-- // End Template Body \\ -->
 									<!-- // End Template Body \\ -->
 								</td>
 								</td>
 							</tr>
 							</tr>
-							<tr>
-								<td align="center" valign="top">
-									<!-- // Begin Template Footer \\ -->
-									<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateFooter">
-										<tr>
-											<td valign="top" class="footerContent">
-
-												<!-- // Begin Module: Standard Footer \\ -->
-												<table border="0" cellpadding="25" cellspacing="0" width="100%">
-													<tr>
-														<td colspan="2" valign="middle" id="social">
-															<div>
-																&nbsp;<a href="http://twitter.com/#!/sharelatex">Follow on Twitter</a> | <a href="http://www.facebook.com/pages/ShareLaTeX/301671376556660">Friend on Facebook</a>
-															</div>
-														</td>
-													</tr>
-												</table>
-												<!-- // End Module: Standard Footer \\ -->
-
-											</td>
-										</tr>
-									</table>
-									<!-- // End Template Footer \\ -->
-								</td>
-							</tr>
 						</table>
 						</table>
 
 
 					</td>
 					</td>
@@ -380,4 +352,4 @@ module.exports = _.template '''
 	</body>
 	</body>
 </html>
 </html>
 
 
-'''
+"""

+ 7 - 2
services/web/app/coffee/Features/PasswordReset/PasswordResetTokenHandler.coffee

@@ -10,12 +10,17 @@ buildKey = (token)-> return "password_token:#{token}"
 
 
 module.exports =
 module.exports =
 
 
-	getNewToken: (user_id, callback)->
+	getNewToken: (user_id, 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 user_id:user_id, "generating token for password reset"
 		token = crypto.randomBytes(32).toString("hex")
 		token = crypto.randomBytes(32).toString("hex")
 		multi = rclient.multi()
 		multi = rclient.multi()
 		multi.set buildKey(token), user_id
 		multi.set buildKey(token), user_id
-		multi.expire buildKey(token), ONE_HOUR_IN_S
+		multi.expire buildKey(token), expiresIn
 		multi.exec (err)->
 		multi.exec (err)->
 			callback(err, token)
 			callback(err, token)
 
 

+ 4 - 1
services/web/app/coffee/Features/ServerAdmin/AdminController.coffee

@@ -39,10 +39,13 @@ module.exports = AdminController =
 
 
 		SystemMessageManager.getMessagesFromDB (error, systemMessages) ->
 		SystemMessageManager.getMessagesFromDB (error, systemMessages) ->
 			return next(error) if error?
 			return next(error) if error?
-			res.render 'admin',
+			res.render 'admin/index',
 				title: 'System Admin'
 				title: 'System Admin'
 				openSockets: openSockets
 				openSockets: openSockets
 				systemMessages: systemMessages
 				systemMessages: systemMessages
+	
+	registerNewUser: (req, res, next) ->
+		res.render 'admin/register'
 
 
 	dissconectAllUsers: (req, res)=>
 	dissconectAllUsers: (req, res)=>
 		logger.warn "disconecting everyone"
 		logger.warn "disconecting everyone"

+ 31 - 25
services/web/app/coffee/Features/User/UserController.coffee

@@ -12,6 +12,9 @@ ReferalAllocator = require("../Referal/ReferalAllocator")
 UserUpdater = require("./UserUpdater")
 UserUpdater = require("./UserUpdater")
 SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator")
 SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator")
 EmailHandler = require("../Email/EmailHandler")
 EmailHandler = require("../Email/EmailHandler")
+PasswordResetTokenHandler = require "../PasswordReset/PasswordResetTokenHandler"
+settings = require "settings-sharelatex"
+crypto = require "crypto"
 
 
 module.exports =
 module.exports =
 
 
@@ -81,34 +84,37 @@ module.exports =
 			res.redirect '/login'
 			res.redirect '/login'
 
 
 	register : (req, res, next = (error) ->)->
 	register : (req, res, next = (error) ->)->
-		logger.log email: req.body.email, "attempted register"
-		redir = Url.parse(req.body.redir or "/project").path
-		UserRegistrationHandler.registerNewUser req.body, (err, user)->
-			if err? and err?.message == "EmailAlreadyRegistered"
-				return AuthenticationController.login req, res
-			else if err?
-				next(err)
-			else
-				metrics.inc "user.register.success"
-				ReferalAllocator.allocate req.session.referal_id, user._id, req.session.referal_source, req.session.referal_medium
-				SubscriptionDomainAllocator.autoAllocate(user)
+		email = req.body.email
+		if !email? or email == ""
+			res.send 422 # Unprocessable Entity
+			return
+		logger.log {email}, "registering new user"
+		UserRegistrationHandler.registerNewUser {
+			email: email
+			password: crypto.randomBytes(32).toString("hex")
+		}, (err, user)->
+			if err? and err?.message != "EmailAlreadyRegistered"
+				return next(err)
+			
+			if err?.message == "EmailAlreadyRegistered"
+				logger.log {email}, "user already exists, resending welcome email"
+
+			# TODO: Make a long term token.
+			ONE_WEEK = 7 * 24 * 60 * 60 # seconds
+			PasswordResetTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
+				return next(err) if err?
+				
+				setNewPasswordUrl = "#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}"
 
 
-				EmailHandler.sendEmail "welcome", {
-					first_name:user.first_name
+				EmailHandler.sendEmail "registered", {
 					to: user.email
 					to: user.email
+					setNewPasswordUrl: setNewPasswordUrl
 				}, () ->
 				}, () ->
-
-				AuthenticationController.establishUserSession req, user, (error) ->
-					return callback(error) if error?
-					req.session.justRegistered = true
-					res.send
-						redir:redir
-						id:user._id.toString()
-						first_name: user.first_name
-						last_name: user.last_name
-						email: user.email
-						created: Date.now()
-
+					
+				res.json {
+					email: user.email
+					setNewPasswordUrl: setNewPasswordUrl
+				}
 
 
 	changePassword : (req, res, next = (error) ->)->
 	changePassword : (req, res, next = (error) ->)->
 		metrics.inc "user.password-change"
 		metrics.inc "user.password-change"

+ 1 - 1
services/web/app/coffee/Features/User/UserRegistrationHandler.coffee

@@ -45,7 +45,7 @@ module.exports =
 			if err?
 			if err?
 				return callback err
 				return callback err
 			if user?.holdingAccount == false
 			if user?.holdingAccount == false
-				return callback(new Error("EmailAlreadyRegistered"))
+				return callback(new Error("EmailAlreadyRegistered"), user)
 			self._createNewUserIfRequired user, userDetails, (err, user)->
 			self._createNewUserIfRequired user, userDetails, (err, user)->
 				if err?
 				if err?
 					return callback(err)
 					return callback(err)

+ 3 - 1
services/web/app/coffee/router.coffee

@@ -54,8 +54,8 @@ module.exports = class Router
 		app.get  '/logout', UserController.logout
 		app.get  '/logout', UserController.logout
 		app.get  '/restricted', SecurityManager.restricted
 		app.get  '/restricted', SecurityManager.restricted
 
 
+		# Left as a placeholder for implementing a public register page
 		app.get  '/register', UserPagesController.registerPage
 		app.get  '/register', UserPagesController.registerPage
-		app.post '/register', UserController.register
 
 
 		EditorRouter.apply(app, httpAuth)
 		EditorRouter.apply(app, httpAuth)
 		CollaboratorsRouter.apply(app)
 		CollaboratorsRouter.apply(app)
@@ -157,6 +157,8 @@ module.exports = class Router
 
 
 		#Admin Stuff
 		#Admin Stuff
 		app.get  '/admin', SecurityManager.requestIsAdmin, AdminController.index
 		app.get  '/admin', SecurityManager.requestIsAdmin, AdminController.index
+		app.get  '/admin/register', SecurityManager.requestIsAdmin, AdminController.registerNewUser
+		app.post '/admin/register', SecurityManager.requestIsAdmin, UserController.register
 		app.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
 		app.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
 		app.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
 		app.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
 		app.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
 		app.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription

+ 1 - 1
services/web/app/views/admin.jade → services/web/app/views/admin/index.jade

@@ -1,4 +1,4 @@
-extends layout
+extends ../layout
 
 
 block content
 block content
 	.content.content-alt
 	.content.content-alt

+ 40 - 0
services/web/app/views/admin/register.jade

@@ -0,0 +1,40 @@
+extends ../layout
+
+block content
+	.content.content-alt
+		.container
+			.row
+				.col-md-12
+					.card(ng-controller="RegisterUsersController")
+						.page-header
+							h1 Register New Users
+						form.form
+							.row
+								.col-md-4.col-xs-8
+									input.form-control(
+									name="email",
+									type="text",
+									placeholder="jane@example.com, joe@example.com",
+									ng-model="inputs.emails",
+									on-enter="registerUsers()"
+								)
+								.col-md-8.col-xs-4
+									button.btn.btn-primary(ng-click="registerUsers()") #{translate("register")}
+									
+						.row-spaced(ng-show="error").ng-cloak.text-danger
+							p Sorry, an error occured
+						
+						.row-spaced(ng-show="users.length > 0").ng-cloak.text-success
+							p We've sent out welcome emails to the registered users.
+							p You can also manually send them URLs below to allow them to reset their password and log in for the first time.
+							p (Password reset tokens will expire after one week and the user will need registering again).
+
+						hr(ng-show="users.length > 0").ng-cloak
+						table(ng-show="users.length > 0").table.table-striped.ng-cloak
+							tr
+								th #{translate("email")}
+								th Set Password Url
+							tr(ng-repeat="user in users")
+								td {{ user.email }}
+								td(style="word-break: break-all;") {{ user.setNewPasswordUrl }}
+							

+ 10 - 1
services/web/app/views/layout/navbar.jade

@@ -9,8 +9,17 @@ nav.navbar.navbar-default
 				a(href='/').navbar-brand
 				a(href='/').navbar-brand
 
 
 		.navbar-collapse.collapse(collapse="navCollapsed")
 		.navbar-collapse.collapse(collapse="navCollapsed")
-			
+
 			ul.nav.navbar-nav.navbar-right
 			ul.nav.navbar-nav.navbar-right
+				if (session && session.user && session.user.isAdmin)
+					li.dropdown(class="subdued")
+						a.dropdown-toggle(href)
+							| Admin
+							b.caret
+						ul.dropdown-menu
+							li
+								a(href="/admin/register") Register New Users
+
 				each item in nav.header
 				each item in nav.header
 					if ((item.only_when_logged_in && session && session.user) || (item.only_when_logged_out && (!session || !session.user)) || (!item.only_when_logged_out && !item.only_when_logged_in))
 					if ((item.only_when_logged_in && session && session.user) || (item.only_when_logged_out && (!session || !session.user)) || (!item.only_when_logged_out && !item.only_when_logged_in))
 						if item.dropdown
 						if item.dropdown

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

@@ -14,6 +14,7 @@ define [
 	"main/subscription-dashboard"
 	"main/subscription-dashboard"
 	"main/new-subscription"
 	"main/new-subscription"
 	"main/annual-upgrade"
 	"main/annual-upgrade"
+	"main/register-users"
 	"analytics/AbTestingManager"
 	"analytics/AbTestingManager"
 	"directives/asyncForm"
 	"directives/asyncForm"
 	"directives/stopPropagation"
 	"directives/stopPropagation"

+ 32 - 0
services/web/public/coffee/main/register-users.coffee

@@ -0,0 +1,32 @@
+define [
+	"base"
+], (App) ->
+	App.controller "RegisterUsersController", ($scope, queuedHttp) ->
+		$scope.users = []
+
+		$scope.inputs =
+			emails: ""
+
+		parseEmails = (emailsString)->
+			regexBySpaceOrComma = /[\s,]+/
+			emails = emailsString.split(regexBySpaceOrComma)
+			emails = _.map emails, (email)->
+				email = email.trim()
+			emails = _.select emails, (email)->
+				email.indexOf("@") != -1
+			return emails
+
+		$scope.registerUsers = () ->
+			emails = parseEmails($scope.inputs.emails)
+			$scope.error = false
+			for email in emails
+				queuedHttp
+					.post("/admin/register", {
+						email: email,
+						_csrf: window.csrfToken
+					})
+					.success (user) ->
+						$scope.users.push user
+						$scope.inputs.emails = ""
+					.error () ->
+						$scope.error = true

+ 0 - 14
services/web/test/UnitTests/coffee/Email/EmailBuilderTests.coffee

@@ -18,20 +18,6 @@ describe "Email Templator ", ->
 			"settings-sharelatex":@settings
 			"settings-sharelatex":@settings
 			"logger-sharelatex": log:->
 			"logger-sharelatex": log:->
 
 
-	describe "welcomeEmail", ->
-
-		beforeEach ->
-			@opts =
-				to:"bob@bob.com"
-				first_name:"bob"
-			@email = @EmailBuilder.buildEmail("welcome", @opts)
-
-		it "should insert the first_name into the template", ->
-			@email.html.indexOf(@opts.first_name).should.not.equal -1
-
-		it "should not have undefined in it", ->
-			@email.html.indexOf("undefined").should.equal -1
-
 	describe "projectSharedWithYou", ->
 	describe "projectSharedWithYou", ->
 		beforeEach ->
 		beforeEach ->
 			@opts =
 			@opts =

+ 6 - 0
services/web/test/UnitTests/coffee/PasswordReset/PasswordResetTokenHandlerTests.coffee

@@ -48,6 +48,12 @@ describe "PasswordResetTokenHandler", ->
 				err.should.exist
 				err.should.exist
 				done()
 				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) =>
+				@redisMulti.expire.calledWith("password_token:#{@stubbedToken.toString("hex")}", @ttl).should.equal true
+				done()
 
 
 	describe "getUserIdFromTokenAndExpire", ->
 	describe "getUserIdFromTokenAndExpire", ->
 
 

+ 52 - 67
services/web/test/UnitTests/coffee/User/UserControllerTests.coffee

@@ -42,6 +42,10 @@ describe "UserController", ->
 			changeEmailAddress:sinon.stub()
 			changeEmailAddress:sinon.stub()
 		@EmailHandler =
 		@EmailHandler =
 			sendEmail:sinon.stub().callsArgWith(2)
 			sendEmail:sinon.stub().callsArgWith(2)
+		@PasswordResetTokenHandler =
+			getNewToken: sinon.stub()
+		@settings =
+			siteUrl: "sharelatex.example.com"
 		@UserController = SandboxedModule.require modulePath, requires:
 		@UserController = SandboxedModule.require modulePath, requires:
 			"./UserLocator": @UserLocator
 			"./UserLocator": @UserLocator
 			"./UserDeleter": @UserDeleter
 			"./UserDeleter": @UserDeleter
@@ -54,6 +58,9 @@ describe "UserController", ->
 			"../Referal/ReferalAllocator":@ReferalAllocator
 			"../Referal/ReferalAllocator":@ReferalAllocator
 			"../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator
 			"../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator
 			"../Email/EmailHandler": @EmailHandler
 			"../Email/EmailHandler": @EmailHandler
+			"../PasswordReset/PasswordResetTokenHandler": @PasswordResetTokenHandler
+			"crypto": @crypto = {}
+			"settings-sharelatex": @settings
 			"logger-sharelatex": {log:->}
 			"logger-sharelatex": {log:->}
 
 
 
 
@@ -63,7 +70,9 @@ describe "UserController", ->
 				user :
 				user :
 					_id : @user_id
 					_id : @user_id
 			body:{}
 			body:{}
-		@res = {}
+		@res =
+			send: sinon.stub()
+			json: sinon.stub()
 		@next = sinon.stub()
 		@next = sinon.stub()
 	describe "deleteUser", ->
 	describe "deleteUser", ->
 
 
@@ -165,76 +174,52 @@ describe "UserController", ->
 
 
 
 
 	describe "register", ->
 	describe "register", ->
-
-		it "should ask the UserRegistrationHandler to register user", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@res.send = =>
-				@UserRegistrationHandler.registerNewUser.calledWith(@req.body).should.equal true
-				done()
-			@UserController.register @req, @res
-
-		it "should try and log the user in if there is an EmailAlreadyRegistered error", (done)->
-
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, new Error("EmailAlreadyRegistered"))
-			@AuthenticationController.login = (req, res)=>
-				assert.deepEqual req, @req
-				assert.deepEqual res, @res
-				done()
-			@UserController.register @req, @res
-
-		it "should put the user on the session and mark them as justRegistered", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@res.send = =>
-				@AuthenticationController.establishUserSession
-					.calledWith(@req, @user)
+		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")
+		
+		describe "with a new user", ->
+			beforeEach ->
+				@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
+				@UserController.register @req, @res
+			
+			it "should ask the UserRegistrationHandler to register user", ->
+				@UserRegistrationHandler.registerNewUser
+					.calledWith({
+						email: @req.body.email
+						password: @password
+					}).should.equal true
+					
+			it "should generate a new password reset token", ->
+				@PasswordResetTokenHandler.getNewToken
+					.calledWith(@user_id, expiresIn: 7 * 24 * 60 * 60)
 					.should.equal true
 					.should.equal true
-				assert.equal @req.session.justRegistered, true
-				done()
-			@UserController.register @req, @res
-
-		it "should redirect to project page", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@res.send = (opts)=>
-				opts.redir.should.equal "/project"
-				done()
-			@UserController.register @req, @res			
-
-
-		it "should redirect passed redir if it exists", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@req.body.redir = "/somewhere"
-			@res.send = (opts)=>
-				opts.redir.should.equal "/somewhere"
-				done()
-			@UserController.register @req, @res
 
 
-		it "should allocate the referals", (done)->
-			@req.session =
-				referal_id : "23123"
-				referal_source : "email"
-				referal_medium : "bob"
-				
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@req.body.redir = "/somewhere"
-			@res.send = (opts)=>
-				@ReferalAllocator.allocate.calledWith(@req.session.referal_id, @user._id, @req.session.referal_source, @req.session.referal_medium).should.equal true
-				done()
-			@UserController.register @req, @res			
+			it "should send a registered email", ->
+				@EmailHandler.sendEmail
+					.calledWith("registered", {
+						to: @user.email
+						setNewPasswordUrl: "#{@settings.siteUrl}/user/password/set?passwordResetToken=#{@token}"
+					})
+					.should.equal true
 			
 			
-		it "should auto allocate the subscription for that domain", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@res.send = (opts)=>
-				@SubscriptionDomainAllocator.autoAllocate.calledWith(@user).should.equal true
-				done()
-			@UserController.register @req, @res
-
-		it "should send a welcome email", (done)->
-			@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
-			@res.send = (opts)=>
-				@EmailHandler.sendEmail.calledWith("welcome").should.equal true
-				done()
-			@UserController.register @req, @res	
+			it "should return the user", ->
+				@res.json
+					.calledWith({
+						email: @user.email
+						setNewPasswordUrl: "#{@settings.siteUrl}/user/password/set?passwordResetToken=#{@token}"
+					})
+					.should.equal true
 
 
+		describe "with a user that already exists", ->
+			beforeEach ->
+				@UserRegistrationHandler.registerNewUser.callsArgWith(1, new Error("EmailAlreadyRegistered"), @user)
+				@UserController.register @req, @res
+				
+			it "should still generate a new password token and email", ->
+				@PasswordResetTokenHandler.getNewToken.called.should.equal true
+				@EmailHandler.sendEmail.called.should.equal true
 
 
 	describe "changePassword", ->
 	describe "changePassword", ->
 
 

+ 3 - 2
services/web/test/UnitTests/coffee/User/UserRegistrationHandlerTests.coffee

@@ -85,9 +85,10 @@ describe "UserRegistrationHandler", ->
 					done()
 					done()
 
 
 			it "should return email registered in the error if there is a non holdingAccount there", (done)->
 			it "should return email registered in the error if there is a non holdingAccount there", (done)->
-				@User.findOne.callsArgWith(1, null, {holdingAccount:false})
-				@handler.registerNewUser @passingRequest, (err)=>
+				@User.findOne.callsArgWith(1, null, @user = {holdingAccount:false})
+				@handler.registerNewUser @passingRequest, (err, user)=>
 					err.should.deep.equal new Error("EmailAlreadyRegistered")
 					err.should.deep.equal new Error("EmailAlreadyRegistered")
+					user.should.deep.equal @user
 					done()
 					done()
 
 
 		describe "validRequest", ->
 		describe "validRequest", ->