mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-05 02:57:32 +00:00
Accept & revoke team invites
This commit is contained in:
parent
9aa95cb0d5
commit
11edfde153
10 changed files with 120 additions and 54 deletions
|
@ -19,7 +19,6 @@ module.exports =
|
|||
callback null, user.features.collaborators
|
||||
else
|
||||
callback null, Settings.defaultPlanCode.collaborators
|
||||
|
||||
|
||||
canAddXCollaborators: (project_id, x_collaborators, callback = (error, allowed)->) ->
|
||||
@allowedNumberOfCollaboratorsInProject project_id, (error, allowed_number) =>
|
||||
|
@ -56,6 +55,10 @@ module.exports =
|
|||
return callback(err) if err?
|
||||
callback err, subscriptions.length > 0, subscriptions
|
||||
|
||||
teamHasReachedMemberLimit: (subscription) ->
|
||||
currentTotal = (subscription.member_ids or []).length + (subscription.team_invites or []).length
|
||||
return currentTotal >= subscription.membersLimit
|
||||
|
||||
hasGroupMembersLimitReached: (user_id, callback = (err, limitReached, subscription)->)->
|
||||
SubscriptionLocator.getUsersSubscription user_id, (err, subscription)->
|
||||
if err?
|
||||
|
@ -68,5 +71,3 @@ module.exports =
|
|||
limitReached = currentTotal >= subscription.membersLimit
|
||||
logger.log user_id:user_id, limitReached:limitReached, currentTotal: currentTotal, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
|
||||
callback(err, limitReached, subscription)
|
||||
|
||||
getOwnerIdOfProject = (project_id, callback)->
|
||||
|
|
|
@ -52,11 +52,8 @@ module.exports = SubscriptionGroupHandler =
|
|||
for email in subscription.invited_emails or []
|
||||
users.push buildEmailInviteViewModel(email)
|
||||
|
||||
TeamInvitesHandler.getInvites subscription.id, (err, invites) ->
|
||||
return callback(err) if err?
|
||||
|
||||
for invite in invites or []
|
||||
users.push buildEmailInviteViewModel(invite.email)
|
||||
for teamInvite in subscription.teamInvites or []
|
||||
users.push buildEmailInviteViewModel(teamInvite.email)
|
||||
|
||||
jobs = _.map subscription.member_ids, (user_id)->
|
||||
return (cb)->
|
||||
|
|
|
@ -33,7 +33,7 @@ module.exports =
|
|||
TeamInvitesController.viewInvite
|
||||
webRouter.put '/subscription/invites/:token/', AuthenticationController.requireLogin(),
|
||||
TeamInvitesController.acceptInvite
|
||||
webRouter.delete '/subscription/invites/:token/', AuthenticationController.requireLogin(),
|
||||
webRouter.delete '/subscription/invites/:email/', AuthenticationController.requireLogin(),
|
||||
TeamInvitesController.revokeInvite
|
||||
|
||||
webRouter.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
settings = require "settings-sharelatex"
|
||||
logger = require("logger-sharelatex")
|
||||
TeamInvitesHandler = require('./TeamInvitesHandler')
|
||||
AuthenticationController = require("../Authentication/AuthenticationController")
|
||||
|
@ -5,10 +6,10 @@ ErrorController = require("../Errors/ErrorController")
|
|||
|
||||
module.exports =
|
||||
createInvite: (req, res, next) ->
|
||||
adminUserId = AuthenticationController.getLoggedInUserId(req)
|
||||
teamManagerId = AuthenticationController.getLoggedInUserId(req)
|
||||
email = req.body.email
|
||||
|
||||
TeamInvitesHandler.createInvite adminUserId, email, (err, invite) ->
|
||||
TeamInvitesHandler.createInvite teamManagerId, email, (err, invite) ->
|
||||
next(err) if err?
|
||||
inviteView = { user:
|
||||
{ email: invite.email, sentAt: invite.sentAt, holdingAccount: true }
|
||||
|
@ -31,8 +32,23 @@ module.exports =
|
|||
inviterName: inviterName
|
||||
inviteToken: invite.token
|
||||
hasPersonalSubscription: personalSubscription?
|
||||
appName: settings.appName
|
||||
|
||||
|
||||
acceptInvite: (req, res) ->
|
||||
acceptInvite: (req, res, next) ->
|
||||
token = req.params.token
|
||||
userId = AuthenticationController.getLoggedInUserId(req)
|
||||
|
||||
TeamInvitesHandler.acceptInvite token, userId, (err, results) ->
|
||||
next(err) if err?
|
||||
|
||||
res.sendStatus 204
|
||||
|
||||
revokeInvite: (req, res) ->
|
||||
email = req.params.email
|
||||
teamManagerId = AuthenticationController.getLoggedInUserId(req)
|
||||
|
||||
TeamInvitesHandler.revokeInvite teamManagerId, email, (err, results) ->
|
||||
next(err) if err?
|
||||
|
||||
res.sendStatus 204
|
||||
|
|
|
@ -2,10 +2,16 @@ logger = require("logger-sharelatex")
|
|||
crypto = require("crypto")
|
||||
|
||||
settings = require("settings-sharelatex")
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
|
||||
TeamInvite = require("../../models/TeamInvite").TeamInvite
|
||||
Subscription = require("../../models/Subscription").Subscription
|
||||
|
||||
UserLocator = require("../User/UserLocator")
|
||||
SubscriptionLocator = require("./SubscriptionLocator")
|
||||
TeamInvite = require("../../models/TeamInvite").TeamInvite
|
||||
SubscriptionUpdater = require("./SubscriptionUpdater")
|
||||
LimitationsManager = require("./LimitationsManager")
|
||||
|
||||
EmailHandler = require("../Email/EmailHandler")
|
||||
|
||||
module.exports = TeamInvitesHandler =
|
||||
|
@ -13,59 +19,96 @@ module.exports = TeamInvitesHandler =
|
|||
getInvites: (subscriptionId, callback) ->
|
||||
TeamInvite.find(subscriptionId: subscriptionId, callback)
|
||||
|
||||
createInvite: (adminUserId, email, callback) ->
|
||||
|
||||
UserLocator.findById adminUserId, (error, groupAdmin) ->
|
||||
SubscriptionLocator.getUsersSubscription adminUserId, (error, subscription) ->
|
||||
createInvite: (teamManagerId, email, callback) ->
|
||||
UserLocator.findById teamManagerId, (error, teamManager) ->
|
||||
SubscriptionLocator.getUsersSubscription teamManagerId, (error, subscription) ->
|
||||
return callback(error) if error?
|
||||
|
||||
inviterName = TeamInvitesHandler.inviterName(groupAdmin)
|
||||
if LimitationsManager.teamHasReachedMemberLimit(subscription)
|
||||
return callback(limitReached: true)
|
||||
|
||||
existingInvite = subscription.teamInvites.find (invite) -> invite.email == email
|
||||
|
||||
if existingInvite
|
||||
return callback(alreadyInvited: true)
|
||||
|
||||
inviterName = TeamInvitesHandler.inviterName(teamManager)
|
||||
token = crypto.randomBytes(32).toString("hex")
|
||||
|
||||
TeamInvite.create {
|
||||
subscriptionId: subscription.id,
|
||||
invite = {
|
||||
email: email,
|
||||
token: token,
|
||||
sentAt: new Date(),
|
||||
}, (error, invite) ->
|
||||
}
|
||||
|
||||
subscription.teamInvites.push(invite)
|
||||
|
||||
subscription.save (error) ->
|
||||
return callback(error) if error?
|
||||
|
||||
# TODO: use standard way to canonalise email addresses
|
||||
opts =
|
||||
to : email
|
||||
to: email.trim().toLowerCase()
|
||||
inviterName: inviterName
|
||||
acceptInviteUrl: "#{settings.siteUrl}/subscription/invites/#{token}/"
|
||||
EmailHandler.sendEmail "verifyEmailToJoinTeam", opts, (error) ->
|
||||
return callback(error, invite)
|
||||
|
||||
getInvite: (token, callback) ->
|
||||
TeamInvite.findOne(token: token, callback)
|
||||
acceptInvite: (token, userId, callback) ->
|
||||
TeamInvitesHandler.getInviteAndManager token, (err, invite, subscription, teamManager) ->
|
||||
return callback(err) if err?
|
||||
return callback(inviteNoLongerValid: true) unless invite? and teamManager?
|
||||
|
||||
acceptInvite: (userId, token, callback) ->
|
||||
SubscriptionUpdater.addUserToGroup teamManager, userId, (err) ->
|
||||
return callback(err) if err?
|
||||
|
||||
revokeInvite: (token, callback) ->
|
||||
TeamInvitesHandler.removeInviteFromTeam(subscription.id, invite.email, callback)
|
||||
|
||||
revokeInvite: (teamManagerId, email, callback) ->
|
||||
SubscriptionLocator.getUsersSubscription teamManagerId, (err, teamSubscription) ->
|
||||
return callback(err) if err?
|
||||
|
||||
TeamInvitesHandler.removeInviteFromTeam(teamSubscription.id, email, callback)
|
||||
|
||||
getInviteDetails: (token, userId, callback) ->
|
||||
TeamInvitesHandler.getInvite token, (err, invite) ->
|
||||
callback(err) if err?
|
||||
TeamInvitesHandler.getInviteAndManager token, (err, invite, teamSubscription, teamManager) ->
|
||||
return callback(err) if err?
|
||||
|
||||
SubscriptionLocator.getUsersSubscription userId, (err, personalSubscription) ->
|
||||
callback(err) if err?
|
||||
return callback(err) if err?
|
||||
|
||||
SubscriptionLocator.getSubscription invite.subscriptionId, (err, teamSubscription) ->
|
||||
callback(err) if err?
|
||||
return callback(null , {
|
||||
invite: invite,
|
||||
personalSubscription: personalSubscription,
|
||||
team: teamSubscription,
|
||||
inviterName: TeamInvitesHandler.inviterName(teamManager),
|
||||
teamManager: teamManager
|
||||
})
|
||||
|
||||
UserLocator.findById teamSubscription.admin_id, (err, teamAdmin) ->
|
||||
callback(err) if err?
|
||||
getInviteAndManager: (token, callback) ->
|
||||
TeamInvitesHandler.getInvite token, (err, invite, teamSubscription) ->
|
||||
return callback(err) if err?
|
||||
|
||||
callback(null , {
|
||||
invite: invite,
|
||||
personalSubscription: personalSubscription,
|
||||
team: teamSubscription,
|
||||
inviterName: TeamInvitesHandler.inviterName(teamAdmin),
|
||||
teamAdmin: teamAdmin
|
||||
})
|
||||
UserLocator.findById teamSubscription.admin_id, (err, teamManager) ->
|
||||
return callback(err, invite, teamSubscription, teamManager)
|
||||
|
||||
inviterName: (groupAdmin) ->
|
||||
if groupAdmin.first_name and groupAdmin.last_name
|
||||
"#{groupAdmin.first_name} #{groupAdmin.last_name} (#{groupAdmin.email})"
|
||||
getInvite: (token, callback) ->
|
||||
Subscription.findOne 'teamInvites.token': token, (err, subscription) ->
|
||||
return callback(err, subscription) if err?
|
||||
return callback(teamNotFound: true) unless subscription?
|
||||
|
||||
invite = subscription.teamInvites.find (i) -> i.token == token
|
||||
return callback(null, invite, subscription)
|
||||
|
||||
|
||||
removeInviteFromTeam: (subscriptionId, email, callback) ->
|
||||
searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
|
||||
updateOp = { $pull: { teamInvites: { email: email.trim().toLowerCase() } } }
|
||||
|
||||
Subscription.update(searchConditions, updateOp, callback)
|
||||
|
||||
inviterName: (teamManager) ->
|
||||
if teamManager.first_name and teamManager.last_name
|
||||
"#{teamManager.first_name} #{teamManager.last_name} (#{teamManager.email})"
|
||||
else
|
||||
groupAdmin.email
|
||||
teamManager.email
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mongoose = require 'mongoose'
|
||||
Settings = require 'settings-sharelatex'
|
||||
TeamInviteSchema = require('./TeamInvite').TeamInviteSchema
|
||||
|
||||
Schema = mongoose.Schema
|
||||
ObjectId = Schema.ObjectId
|
||||
|
@ -8,6 +9,7 @@ SubscriptionSchema = new Schema
|
|||
admin_id : {type:ObjectId, ref:'User', index: {unique: true, dropDups: true}}
|
||||
member_ids : [ type:ObjectId, ref:'User' ]
|
||||
invited_emails: [ String ]
|
||||
teamInvites : [ TeamInviteSchema ]
|
||||
recurlySubscription_id : String
|
||||
planCode : {type: String}
|
||||
groupPlan : {type: Boolean, default: false}
|
||||
|
|
|
@ -5,11 +5,9 @@ Schema = mongoose.Schema
|
|||
ObjectId = Schema.ObjectId
|
||||
|
||||
TeamInviteSchema = new Schema
|
||||
subscriptionId : { type: ObjectId, ref: 'Subscription', required: true }
|
||||
email : { type: String, required: true }
|
||||
token : { type: String, required: true }
|
||||
sentAt : { type: Date, required: true }
|
||||
|
||||
token : { type: String }
|
||||
sentAt : { type: Date }
|
||||
|
||||
mongoose.model 'TeamInvite', TeamInviteSchema
|
||||
exports.TeamInvite = mongoose.model 'TeamInvite'
|
||||
|
|
|
@ -20,7 +20,7 @@ block content
|
|||
.col-md-8.col-md-offset-2(ng-cloak)
|
||||
.card(ng-controller="TeamInviteController")
|
||||
.page-header
|
||||
h1.text-centered #{translate("invited_to_group", {inviterName: inviterName})}
|
||||
h1.text-centered #{translate("invited_to_group", {inviterName: inviterName, appName: appName})}
|
||||
|
||||
div(ng-show="view =='personalSubscription'").row.text-centered.message
|
||||
p #{translate("cancel_personal_subscription_first")}
|
||||
|
@ -31,10 +31,19 @@ block content
|
|||
a.btn.btn.btn-primary(ng-click="cancelPersonalSubscription()", ng-disabled="inflight") #{translate("cancel_your_subscription")}
|
||||
|
||||
div(ng-show="view =='teamInvite'").row.text-centered.message
|
||||
p #{translate("accept_invite_to_join_team", {inviterName: inviterName})}
|
||||
p #{translate("accept_invitation_gives_premium_account")}
|
||||
.row
|
||||
.col-md-12
|
||||
.text-center
|
||||
a.btn.btn-default(href="/project") #{translate("not_now")}
|
||||
span
|
||||
a.btn.btn.btn-primary(ng-click="joinTeam()", ng-disabled="inflight") #{translate("join_team")}
|
||||
a.btn.btn.btn-primary(ng-click="joinTeam()", ng-disabled="inflight") #{translate("accept_invitation")}
|
||||
|
||||
div(ng-show="view =='inviteAccepted'").row.text-centered.text-center
|
||||
.row
|
||||
.col-md-12 You have joined the team managed by John Smith
|
||||
.row
|
||||
.col-md-12
|
||||
.row
|
||||
.col-md-12
|
||||
a.btn.btn.btn-primary(href="/project") #{translate("done")}
|
||||
|
|
|
@ -35,7 +35,7 @@ define [
|
|||
for user in $scope.selectedUsers
|
||||
do (user) ->
|
||||
if user.holdingAccount and !user._id?
|
||||
url = "/subscription/group/email/#{encodeURIComponent(user.email)}"
|
||||
url = "/subscription/invites/#{encodeURIComponent(user.email)}"
|
||||
else
|
||||
url = "/subscription/group/user/#{user._id}"
|
||||
queuedHttp({
|
||||
|
|
|
@ -23,12 +23,12 @@ define [
|
|||
console.log "the request failed"
|
||||
|
||||
$scope.joinTeam = ->
|
||||
$scope.view = "requestSent"
|
||||
$scope.inflight = true
|
||||
request = $http.put "/subscription/invites/:token/", {_csrf:window.csrfToken}
|
||||
request = $http.put "/subscription/invites/#{window.inviteToken}/", {_csrf:window.csrfToken}
|
||||
request.then (response)->
|
||||
{ status } = response
|
||||
$scope.inflight = false
|
||||
$scope.view = "inviteAccepted"
|
||||
if status != 200 # assume request worked
|
||||
$scope.requestSent = false
|
||||
request.catch ()->
|
||||
|
|
Loading…
Reference in a new issue