mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
aee83bc0cf
Update audit log when institution SSO is linked/unlinked GitOrigin-RevId: 264ffbed025dccb4dd202f86afe545c3bf0c1f76
743 lines
17 KiB
JavaScript
743 lines
17 KiB
JavaScript
const request = require('./request')
|
|
const settings = require('settings-sharelatex')
|
|
const { db, ObjectId } = require('../../../../app/src/infrastructure/mongojs')
|
|
const UserModel = require('../../../../app/src/models/User').User
|
|
const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
|
|
const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager')
|
|
const { promisify } = require('util')
|
|
|
|
let count = 0
|
|
|
|
class User {
|
|
constructor(options) {
|
|
if (options == null) {
|
|
options = {}
|
|
}
|
|
this.emails = [
|
|
{
|
|
email: options.email || `acceptance-test-${count}@example.com`,
|
|
createdAt: new Date()
|
|
}
|
|
]
|
|
this.email = this.emails[0].email
|
|
this.password = `acceptance-test-${count}-password`
|
|
count++
|
|
this.jar = request.jar()
|
|
this.request = request.defaults({
|
|
jar: this.jar
|
|
})
|
|
}
|
|
|
|
setExtraAttributes(user) {
|
|
if ((user != null ? user._id : undefined) == null) {
|
|
throw new Error('User does not exist')
|
|
}
|
|
this.id = user._id.toString()
|
|
this._id = user._id.toString()
|
|
this.first_name = user.first_name
|
|
this.referal_id = user.referal_id
|
|
}
|
|
|
|
get(callback) {
|
|
db.users.findOne({ _id: ObjectId(this._id) }, callback)
|
|
}
|
|
|
|
mongoUpdate(updateOp, callback) {
|
|
db.users.update({ _id: ObjectId(this._id) }, updateOp, callback)
|
|
}
|
|
|
|
register(callback) {
|
|
this.registerWithQuery('', callback)
|
|
}
|
|
|
|
registerWithQuery(query, callback) {
|
|
if (this._id != null) {
|
|
return callback(new Error('User already registered'))
|
|
}
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: `/register${query}`,
|
|
json: { email: this.email, password: this.password }
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
db.users.findOne({ email: this.email }, (error, user) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.setExtraAttributes(user)
|
|
callback(null, user)
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
login(callback) {
|
|
this.loginWith(this.email, callback)
|
|
}
|
|
|
|
loginWith(email, callback) {
|
|
this.ensureUserExists(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: settings.enableLegacyLogin ? '/login/legacy' : '/login',
|
|
json: { email, password: this.password }
|
|
},
|
|
callback
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
ensureUserExists(callback) {
|
|
const filter = { email: this.email }
|
|
const options = { upsert: true, new: true, setDefaultsOnInsert: true }
|
|
UserModel.findOneAndUpdate(filter, {}, options, (error, user) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
AuthenticationManager.setUserPasswordInV2(
|
|
user._id,
|
|
this.password,
|
|
error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
UserUpdater.updateUser(
|
|
user._id,
|
|
{ $set: { emails: this.emails } },
|
|
error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.setExtraAttributes(user)
|
|
callback(null, this.password)
|
|
}
|
|
)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
setFeatures(features, callback) {
|
|
const update = {}
|
|
for (let key in features) {
|
|
const value = features[key]
|
|
update[`features.${key}`] = value
|
|
}
|
|
UserModel.update({ _id: this.id }, update, callback)
|
|
}
|
|
|
|
setFeaturesOverride(featuresOverride, callback) {
|
|
const update = { $push: { featuresOverrides: featuresOverride } }
|
|
UserModel.update({ _id: this.id }, update, callback)
|
|
}
|
|
|
|
setOverleafId(overleafId, callback) {
|
|
UserModel.update({ _id: this.id }, { 'overleaf.id': overleafId }, callback)
|
|
}
|
|
|
|
logout(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: '/logout',
|
|
json: {
|
|
email: this.email,
|
|
password: this.password
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
db.users.findOne({ email: this.email }, (error, user) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
if (user == null) {
|
|
return callback()
|
|
}
|
|
this.id = user._id.toString()
|
|
this._id = user._id.toString()
|
|
callback()
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
addEmail(email, callback) {
|
|
this.emails.push({ email, createdAt: new Date() })
|
|
UserUpdater.addEmailAddress(
|
|
this.id,
|
|
email,
|
|
{},
|
|
{ initiatorId: this._id, ipAddress: '127:0:0:0' },
|
|
callback
|
|
)
|
|
}
|
|
|
|
confirmEmail(email, callback) {
|
|
for (let idx = 0; idx < this.emails.length; idx++) {
|
|
const emailData = this.emails[idx]
|
|
if (emailData.email === email) {
|
|
this.emails[idx].confirmedAt = new Date()
|
|
}
|
|
}
|
|
UserUpdater.confirmEmail(this.id, email, callback)
|
|
}
|
|
|
|
ensureAdmin(callback) {
|
|
db.users.update(
|
|
{ _id: ObjectId(this.id) },
|
|
{ $set: { isAdmin: true } },
|
|
callback
|
|
)
|
|
}
|
|
|
|
ensureStaffAccess(flag, callback) {
|
|
const update = { $set: {} }
|
|
update.$set[`staffAccess.${flag}`] = true
|
|
db.users.update({ _id: ObjectId(this.id) }, update, callback)
|
|
}
|
|
|
|
upgradeFeatures(callback) {
|
|
const features = {
|
|
collaborators: -1, // Infinite
|
|
versioning: true,
|
|
dropbox: true,
|
|
compileTimeout: 60,
|
|
compileGroup: 'priority',
|
|
templates: true,
|
|
references: true,
|
|
trackChanges: true,
|
|
trackChangesVisible: true
|
|
}
|
|
db.users.update(
|
|
{ _id: ObjectId(this.id) },
|
|
{ $set: { features } },
|
|
callback
|
|
)
|
|
}
|
|
|
|
downgradeFeatures(callback) {
|
|
const features = {
|
|
collaborators: 1,
|
|
versioning: false,
|
|
dropbox: false,
|
|
compileTimeout: 60,
|
|
compileGroup: 'standard',
|
|
templates: false,
|
|
references: false,
|
|
trackChanges: false,
|
|
trackChangesVisible: false
|
|
}
|
|
db.users.update(
|
|
{ _id: ObjectId(this.id) },
|
|
{ $set: { features } },
|
|
callback
|
|
)
|
|
}
|
|
|
|
defaultFeatures(callback) {
|
|
const features = settings.defaultFeatures
|
|
db.users.update(
|
|
{ _id: ObjectId(this.id) },
|
|
{ $set: { features } },
|
|
callback
|
|
)
|
|
}
|
|
|
|
getFeatures(callback) {
|
|
db.users.findOne(
|
|
{ _id: ObjectId(this.id) },
|
|
{ features: 1 },
|
|
(error, user) => callback(error, user && user.features)
|
|
)
|
|
}
|
|
|
|
fullDeleteUser(email, callback) {
|
|
db.users.findOne({ email }, (error, user) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
if (user == null) {
|
|
return callback()
|
|
}
|
|
const userId = user._id
|
|
db.projects.remove(
|
|
{ owner_ref: ObjectId(userId) },
|
|
{ multi: true },
|
|
err => {
|
|
if (err != null) {
|
|
callback(err)
|
|
}
|
|
db.users.remove({ _id: ObjectId(userId) }, callback)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
deleteUser(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: '/user/delete',
|
|
json: { password: this.password }
|
|
},
|
|
(err, res) => {
|
|
if (err) {
|
|
return callback(err)
|
|
}
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
return callback(
|
|
new Error('Error received from API: ' + res.statusCode)
|
|
)
|
|
}
|
|
|
|
callback()
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
getProject(projectId, callback) {
|
|
db.projects.findOne({ _id: ObjectId(projectId.toString()) }, callback)
|
|
}
|
|
|
|
saveProject(project, callback) {
|
|
db.projects.update({ _id: project._id }, project, callback)
|
|
}
|
|
|
|
createProject(name, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
this.request.post(
|
|
{
|
|
url: '/project/new',
|
|
json: Object.assign({ projectName: name }, options)
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
if ((body != null ? body.project_id : undefined) == null) {
|
|
error = new Error(
|
|
JSON.stringify([
|
|
'SOMETHING WENT WRONG CREATING PROJECT',
|
|
name,
|
|
options,
|
|
response.statusCode,
|
|
response.headers['location'],
|
|
body
|
|
])
|
|
)
|
|
callback(error)
|
|
} else {
|
|
callback(null, body.project_id)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
deleteProject(projectId, callback) {
|
|
this.request.delete(
|
|
{
|
|
url: `/project/${projectId}`
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null)
|
|
}
|
|
)
|
|
}
|
|
|
|
deleteProjects(callback) {
|
|
db.projects.remove({ owner_ref: ObjectId(this.id) }, { multi: true }, err =>
|
|
callback(err)
|
|
)
|
|
}
|
|
|
|
openProject(projectId, callback) {
|
|
this.request.get(
|
|
{
|
|
url: `/project/${projectId}`
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
if (response.statusCode !== 200) {
|
|
const err = new Error(
|
|
`Non-success response when opening project: ${response.statusCode}`
|
|
)
|
|
return callback(err)
|
|
}
|
|
callback(null)
|
|
}
|
|
)
|
|
}
|
|
|
|
createDocInProject(projectId, parentFolderId, name, callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: `/project/${projectId}/doc`,
|
|
json: {
|
|
name,
|
|
parentFolderId
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null, body._id)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
addUserToProject(projectId, user, privileges, callback) {
|
|
let updateOp
|
|
if (privileges === 'readAndWrite') {
|
|
updateOp = { $addToSet: { collaberator_refs: user._id } }
|
|
} else if (privileges === 'readOnly') {
|
|
updateOp = { $addToSet: { readOnly_refs: user._id } }
|
|
}
|
|
db.projects.update({ _id: db.ObjectId(projectId) }, updateOp, err =>
|
|
callback(err)
|
|
)
|
|
}
|
|
|
|
makePublic(projectId, level, callback) {
|
|
this.request.post(
|
|
{
|
|
url: `/project/${projectId}/settings/admin`,
|
|
json: {
|
|
publicAccessLevel: level
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null)
|
|
}
|
|
)
|
|
}
|
|
|
|
makePrivate(projectId, callback) {
|
|
this.request.post(
|
|
{
|
|
url: `/project/${projectId}/settings/admin`,
|
|
json: {
|
|
publicAccessLevel: 'private'
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null)
|
|
}
|
|
)
|
|
}
|
|
|
|
makeTokenBased(projectId, callback) {
|
|
this.request.post(
|
|
{
|
|
url: `/project/${projectId}/settings/admin`,
|
|
json: {
|
|
publicAccessLevel: 'tokenBased'
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null)
|
|
}
|
|
)
|
|
}
|
|
|
|
getCsrfToken(callback) {
|
|
this.request.get(
|
|
{
|
|
url: '/dev/csrf'
|
|
},
|
|
(err, response, body) => {
|
|
if (err != null) {
|
|
return callback(err)
|
|
}
|
|
this.csrfToken = body
|
|
this.request = this.request.defaults({
|
|
headers: {
|
|
'x-csrf-token': this.csrfToken
|
|
}
|
|
})
|
|
callback()
|
|
}
|
|
)
|
|
}
|
|
|
|
changePassword(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: '/user/password/update',
|
|
json: {
|
|
currentPassword: this.password,
|
|
newPassword1: this.password,
|
|
newPassword2: this.password
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
db.users.findOne({ email: this.email }, (error, user) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback()
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
reconfirmAccountRequest(userEmail, callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: '/user/reconfirm',
|
|
json: {
|
|
email: userEmail
|
|
}
|
|
},
|
|
(error, response, body) => {
|
|
callback(error, response)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
getUserSettingsPage(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.get(
|
|
{
|
|
url: '/user/settings'
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null, response.statusCode)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
activateSudoMode(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
uri: '/confirm-password',
|
|
json: {
|
|
password: this.password
|
|
}
|
|
},
|
|
callback
|
|
)
|
|
})
|
|
}
|
|
|
|
updateSettings(newSettings, callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: '/user/settings',
|
|
json: newSettings
|
|
},
|
|
callback
|
|
)
|
|
})
|
|
}
|
|
|
|
getProjectListPage(callback) {
|
|
this.getCsrfToken(error => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
this.request.get(
|
|
{
|
|
url: '/project'
|
|
},
|
|
(error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
callback(null, response.statusCode)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
isLoggedIn(callback) {
|
|
this.request.get('/user/personal_info', (error, response, body) => {
|
|
if (error != null) {
|
|
return callback(error)
|
|
}
|
|
if (response.statusCode === 200) {
|
|
callback(null, true)
|
|
} else if (response.statusCode === 302) {
|
|
callback(null, false)
|
|
} else {
|
|
callback(
|
|
new Error(
|
|
`unexpected status code from /user/personal_info: ${
|
|
response.statusCode
|
|
}`
|
|
)
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
transferProjectOwnership(projectId, userId, callback) {
|
|
this.getCsrfToken(err => {
|
|
if (err != null) {
|
|
return callback(err)
|
|
}
|
|
this.request.post(
|
|
{
|
|
url: `/project/${projectId.toString()}/transfer-ownership`,
|
|
json: {
|
|
user_id: userId.toString()
|
|
}
|
|
},
|
|
(err, response) => {
|
|
if (err != null) {
|
|
return callback(err)
|
|
}
|
|
if (response.statusCode !== 204) {
|
|
return callback(
|
|
new Error(`Unexpected status code: ${response.statusCode}`)
|
|
)
|
|
}
|
|
callback()
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
setV1Id(v1Id, callback) {
|
|
UserModel.update(
|
|
{
|
|
_id: this._id
|
|
},
|
|
{
|
|
overleaf: {
|
|
id: v1Id
|
|
}
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
|
|
setCollaboratorInfo(projectId, userId, info, callback) {
|
|
this.getCsrfToken(err => {
|
|
if (err != null) {
|
|
return callback(err)
|
|
}
|
|
this.request.put(
|
|
{
|
|
url: `/project/${projectId.toString()}/users/${userId.toString()}`,
|
|
json: info
|
|
},
|
|
(err, response) => {
|
|
if (err != null) {
|
|
return callback(err)
|
|
}
|
|
if (response.statusCode !== 204) {
|
|
return callback(
|
|
new Error(`Unexpected status code: ${response.statusCode}`)
|
|
)
|
|
}
|
|
callback()
|
|
}
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
User.promises = class extends User {
|
|
doRequest(method, params) {
|
|
return new Promise((resolve, reject) => {
|
|
this.request[method.toLowerCase()](params, (err, response, body) => {
|
|
if (err) {
|
|
reject(err)
|
|
} else {
|
|
resolve({ response, body })
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// promisify User class methods - works for methods with 0-1 output parameters,
|
|
// otherwise we will need to implement the method manually instead
|
|
const nonPromiseMethods = ['constructor', 'setExtraAttributes']
|
|
Object.getOwnPropertyNames(User.prototype).forEach(methodName => {
|
|
const method = User.prototype[methodName]
|
|
if (typeof method === 'function' && !nonPromiseMethods.includes(methodName)) {
|
|
User.promises.prototype[methodName] = promisify(method)
|
|
}
|
|
})
|
|
|
|
module.exports = User
|