Merge branch 'private_registration'

This commit is contained in:
James Allen 2015-03-20 10:34:17 +00:00
commit ff55e4c5ed
21 changed files with 270 additions and 224 deletions

View file

@ -290,3 +290,37 @@ module.exports = (grunt) ->
grunt.registerTask 'default', 'run' grunt.registerTask 'default', 'run'
grunt.registerTask 'version', "Write the version number into sentry.jade", ['git-rev-parse', 'sed'] grunt.registerTask 'version', "Write the version number into sentry.jade", ['git-rev-parse', 'sed']
grunt.registerTask 'create-admin-user', "Create a user with the given email address and make them an admin. Update in place if the user already exists", () ->
done = @async()
email = grunt.option("email")
if !email?
console.error "Usage: grunt create-admin-user --email joe@example.com"
process.exit(1)
settings = require "settings-sharelatex"
UserRegistrationHandler = require "./app/js/Features/User/UserRegistrationHandler"
PasswordResetTokenHandler = require "./app/js/Features/PasswordReset/PasswordResetTokenHandler"
UserRegistrationHandler.registerNewUser {
email: email
password: require("crypto").randomBytes(32).toString("hex")
}, (error, user) ->
if error? and error?.message != "EmailAlreadyRegistered"
throw error
user.isAdmin = true
user.save (error) ->
throw error if error?
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
PasswordResetTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
return next(err) if err?
console.log ""
console.log """
Successfully created #{email} as an admin user.
Please visit the following URL to set a password for #{email} and log in:
#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}
"""
done()

View file

@ -18,7 +18,7 @@ Server.app.use (error, req, res, next) ->
logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear" logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear"
res.statusCode = error.status or 500 res.statusCode = error.status or 500
if res.statusCode == 500 if res.statusCode == 500
res.end("Oops, something went wrong with your request, sorry. If this continues, please contact us at support@sharelatex.com") res.end("Oops, something went wrong with your request, sorry. If this continues, please contact us at #{Settings.adminEmail}")
else else
res.end() res.end()

View file

@ -6,25 +6,19 @@ settings = require("settings-sharelatex")
templates = {} templates = {}
templates.welcome = templates.registered =
subject: _.template "Welcome to ShareLaTeX" subject: _.template "Activate your #{settings.appName} Account"
layout: PersonalEmailLayout layout: PersonalEmailLayout
type:"lifecycle" type: "notification"
compiledTemplate: _.template ''' compiledTemplate: _.template """
<p>Hi <%= first_name %>,</p> <p>Congratulations, you've just had an account created for you on #{settings.appName} with the email address "<%= to %>".</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> <p><a href="<%= setNewPasswordUrl %>">Click here to set your password and log in.</a></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>Once you have reset your password you can <a href="#{settings.siteUrl}/login">log in here</a>.</p>
<p> <p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
Regards, <br> """
Henry <br>
ShareLaTeX Co-founder
</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>
'''
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 ''' compiledTemplate: _.template """
<h1 class="h1">Password Reset</h1> <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,11 +85,11 @@ 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 =
templates: templates
buildEmail: (templateName, opts)-> buildEmail: (templateName, opts)->
template = templates[templateName] template = templates[templateName]

View file

@ -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;"> <td class="headerContent" style="padding: 25px;border-bottom:#dadf90;background-color:#F6F6F6;text-align:left;font-size:18px">
#{settings.appName}
<!-- // 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> </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>
''' """

View file

@ -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)

View file

@ -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"

View file

@ -6,11 +6,13 @@ UserRegistrationHandler = require("./UserRegistrationHandler")
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
metrics = require("../../infrastructure/Metrics") metrics = require("../../infrastructure/Metrics")
Url = require("url") Url = require("url")
AuthenticationController = require("../Authentication/AuthenticationController")
AuthenticationManager = require("../Authentication/AuthenticationManager") AuthenticationManager = require("../Authentication/AuthenticationManager")
ReferalAllocator = require("../Referal/ReferalAllocator")
UserUpdater = require("./UserUpdater") UserUpdater = require("./UserUpdater")
SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator") SubscriptionDomainAllocator = require("../Subscription/SubscriptionDomainAllocator")
EmailHandler = require("../Email/EmailHandler")
PasswordResetTokenHandler = require "../PasswordReset/PasswordResetTokenHandler"
settings = require "settings-sharelatex"
crypto = require "crypto"
module.exports = module.exports =
@ -80,28 +82,36 @@ 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" email = req.body.email
redir = Url.parse(req.body.redir or "/project").path if !email? or email == ""
UserRegistrationHandler.registerNewUser req.body, (err, user)-> res.send 422 # Unprocessable Entity
if err == "EmailAlreadyRegisterd" return
return AuthenticationController.login req, res logger.log {email}, "registering new user"
else if err? UserRegistrationHandler.registerNewUser {
next(err) email: email
else password: crypto.randomBytes(32).toString("hex")
metrics.inc "user.register.success" }, (err, user)->
ReferalAllocator.allocate req.session.referal_id, user._id, req.session.referal_source, req.session.referal_medium if err? and err?.message != "EmailAlreadyRegistered"
SubscriptionDomainAllocator.autoAllocate(user) return next(err)
AuthenticationController.establishUserSession req, user, (error) ->
return callback(error) if error? if err?.message == "EmailAlreadyRegistered"
req.session.justRegistered = true logger.log {email}, "user already exists, resending welcome email"
res.send
redir:redir
id:user._id.toString()
first_name: user.first_name
last_name: user.last_name
email: user.email
created: Date.now()
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 "registered", {
to: user.email
setNewPasswordUrl: setNewPasswordUrl
}, () ->
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"

View file

@ -4,7 +4,6 @@ UserCreator = require("./UserCreator")
AuthenticationManager = require("../Authentication/AuthenticationManager") AuthenticationManager = require("../Authentication/AuthenticationManager")
NewsLetterManager = require("../Newsletter/NewsletterManager") NewsLetterManager = require("../Newsletter/NewsletterManager")
async = require("async") async = require("async")
EmailHandler = require("../Email/EmailHandler")
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
module.exports = module.exports =
@ -40,13 +39,13 @@ module.exports =
self = @ self = @
requestIsValid = @_registrationRequestIsValid userDetails requestIsValid = @_registrationRequestIsValid userDetails
if !requestIsValid if !requestIsValid
return callback("request is not valid") return callback(new Error("request is not valid"))
userDetails.email = userDetails.email?.trim()?.toLowerCase() userDetails.email = userDetails.email?.trim()?.toLowerCase()
User.findOne email:userDetails.email, (err, user)-> User.findOne email:userDetails.email, (err, user)->
if err? if err?
return callback err return callback err
if user?.holdingAccount == false if user?.holdingAccount == false
return callback("EmailAlreadyRegisterd") 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)
@ -56,11 +55,6 @@ module.exports =
(cb)-> (cb)->
NewsLetterManager.subscribe user, -> NewsLetterManager.subscribe user, ->
cb() #this can be slow, just fire it off cb() #this can be slow, just fire it off
(cb)->
emailOpts =
first_name:user.first_name
to: user.email
EmailHandler.sendEmail "welcome", emailOpts, cb
], (err)-> ], (err)->
logger.log user: user, "registered" logger.log user: user, "registered"
callback(err, user) callback(err, user)

View file

@ -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

View file

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

View file

@ -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 }}

View file

@ -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

View file

@ -16,43 +16,11 @@ block content
a(href="/login") #{translate("login_here")} a(href="/login") #{translate("login_here")}
.row .row
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4 .col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3
.card .card
.page-header .page-header
h1 #{translate("register")} h1 #{translate("register")}
form(async-form="register", name="registerForm", action="/register", method="POST", ng-cloak) p
input(name='_csrf', type='hidden', value=csrfToken) | Please contact
input(name='redir', type='hidden', value=redir) strong #{settings.adminEmail}
form-messages(for="registerForm") | to create an account.
.form-group
label(for='email') #{translate("email")}
input.form-control(
type='email',
name='email',
placeholder="email@example.com"
required,
ng-model="email",
ng-init="email = #{JSON.stringify(new_email)}",
ng-model-options="{ updateOn: 'blur' }",
focus="true"
)
span.small.text-primary(ng-show="registerForm.email.$invalid && registerForm.email.$dirty")
| #{translate("must_be_email_address")}
.form-group
label(for='password') #{translate("password")}
input.form-control(
type='password',
name='password',
placeholder="********",
required,
ng-model="password"
)
span.small.text-primary(ng-show="registerForm.password.$invalid && registerForm.password.$dirty")
| #{translate("required")}
.actions
button.btn-primary.btn(
type='submit'
ng-disabled="registerForm.inflight"
)
span(ng-show="!registerForm.inflight") #{translate("register")}
span(ng-show="registerForm.inflight") #{translate("registering")}...

View file

@ -266,6 +266,7 @@ module.exports =
# projectId: "" # projectId: ""
appName: "ShareLaTeX (Community Edition)" appName: "ShareLaTeX (Community Edition)"
adminEmail: "placeholder@example.com"
nav: nav:
title: "ShareLaTeX Community Edition" title: "ShareLaTeX Community Edition"

View file

@ -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"

View file

@ -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

View file

@ -13,25 +13,11 @@ describe "Email Templator ", ->
beforeEach -> beforeEach ->
@settings = {} @settings = appName: "testApp"
@EmailBuilder = SandboxedModule.require modulePath, requires: @EmailBuilder = SandboxedModule.require modulePath, requires:
"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 =

View file

@ -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", ->

View file

@ -40,6 +40,12 @@ describe "UserController", ->
autoAllocate:sinon.stub() autoAllocate:sinon.stub()
@UserUpdater = @UserUpdater =
changeEmailAddress:sinon.stub() changeEmailAddress:sinon.stub()
@EmailHandler =
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
@ -51,6 +57,10 @@ describe "UserController", ->
"../Authentication/AuthenticationManager": @AuthenticationManager "../Authentication/AuthenticationManager": @AuthenticationManager
"../Referal/ReferalAllocator":@ReferalAllocator "../Referal/ReferalAllocator":@ReferalAllocator
"../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator "../Subscription/SubscriptionDomainAllocator":@SubscriptionDomainAllocator
"../Email/EmailHandler": @EmailHandler
"../PasswordReset/PasswordResetTokenHandler": @PasswordResetTokenHandler
"crypto": @crypto = {}
"settings-sharelatex": @settings
"logger-sharelatex": {log:->} "logger-sharelatex": {log:->}
@ -60,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", ->
@ -162,69 +174,52 @@ describe "UserController", ->
describe "register", -> describe "register", ->
beforeEach ->
it "should ask the UserRegistrationHandler to register user", (done)-> @req.body.email = @user.email = "email@example.com"
@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user) @crypto.randomBytes = sinon.stub().returns({toString: () => @password = "mock-password"})
@res.send = => @PasswordResetTokenHandler.getNewToken.callsArgWith(2, null, @token = "mock-token")
@UserRegistrationHandler.registerNewUser.calledWith(@req.body).should.equal true
done() describe "with a new user", ->
@UserController.register @req, @res beforeEach ->
@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
it "should try and log the user in if there is an EmailAlreadyRegisterd error", (done)-> @UserController.register @req, @res
@UserRegistrationHandler.registerNewUser.callsArgWith(1, "EmailAlreadyRegisterd")
@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)
.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 auto allocate the subscription for that domain", (done)-> it "should ask the UserRegistrationHandler to register user", ->
@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user) @UserRegistrationHandler.registerNewUser
@res.send = (opts)=> .calledWith({
@SubscriptionDomainAllocator.autoAllocate.calledWith(@user).should.equal true email: @req.body.email
done() password: @password
@UserController.register @req, @res }).should.equal true
it "should generate a new password reset token", ->
@PasswordResetTokenHandler.getNewToken
.calledWith(@user_id, expiresIn: 7 * 24 * 60 * 60)
.should.equal true
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 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", ->

View file

@ -28,6 +28,7 @@ describe "UserDeleter", ->
"../Newsletter/NewsletterManager": @NewsletterManager "../Newsletter/NewsletterManager": @NewsletterManager
"../Subscription/SubscriptionHandler": @SubscriptionHandler "../Subscription/SubscriptionHandler": @SubscriptionHandler
"../Project/ProjectDeleter": @ProjectDeleter "../Project/ProjectDeleter": @ProjectDeleter
"logger-sharelatex": @logger = { log: sinon.stub() }
describe "deleteUser", -> describe "deleteUser", ->

View file

@ -20,14 +20,12 @@ describe "UserRegistrationHandler", ->
setUserPassword: sinon.stub().callsArgWith(2) setUserPassword: sinon.stub().callsArgWith(2)
@NewsLetterManager = @NewsLetterManager =
subscribe: sinon.stub().callsArgWith(1) subscribe: sinon.stub().callsArgWith(1)
@EmailHandler =
sendEmail:sinon.stub().callsArgWith(2)
@handler = SandboxedModule.require modulePath, requires: @handler = SandboxedModule.require modulePath, requires:
"../../models/User": {User:@User} "../../models/User": {User:@User}
"./UserCreator": @UserCreator "./UserCreator": @UserCreator
"../Authentication/AuthenticationManager":@AuthenticationManager "../Authentication/AuthenticationManager":@AuthenticationManager
"../Newsletter/NewsletterManager":@NewsLetterManager "../Newsletter/NewsletterManager":@NewsLetterManager
"../Email/EmailHandler": @EmailHandler "logger-sharelatex": @logger = { log: sinon.stub() }
@passingRequest = {email:"something@email.com", password:"123"} @passingRequest = {email:"something@email.com", password:"123"}
@ -87,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}) @User.findOne.callsArgWith(1, null, @user = {holdingAccount:false})
@handler.registerNewUser @passingRequest, (err)=> @handler.registerNewUser @passingRequest, (err, user)=>
err.should.equal "EmailAlreadyRegisterd" err.should.deep.equal new Error("EmailAlreadyRegistered")
user.should.deep.equal @user
done() done()
describe "validRequest", -> describe "validRequest", ->
@ -125,11 +124,6 @@ describe "UserRegistrationHandler", ->
@NewsLetterManager.subscribe.calledWith(@user).should.equal true @NewsLetterManager.subscribe.calledWith(@user).should.equal true
done() done()
it "should send a welcome email", (done)->
@handler.registerNewUser @passingRequest, (err)=>
@EmailHandler.sendEmail.calledWith("welcome").should.equal true
done()
it "should call the ReferalAllocator", (done)-> it "should call the ReferalAllocator", (done)->
done() done()