Files
overleaf-cep/services/web/test/acceptance/src/helpers/User.mjs
T
Jakob Ackermann b928e865b7 [web] make user feature changes in tests more reliable (#23557)
* [web] tests: rename User.upgradeFeatures to User.upgradeSomeFeatures

The upgrades features do not match with any plan.

* [web] make user feature changes in tests more reliable

* [web] fix schema for featuresOverride in tests

* [web] log in after upgrading features

GitOrigin-RevId: ac5b19c798712926c118b221c553540b413802cd
2025-02-13 09:06:21 +00:00

1324 lines
33 KiB
JavaScript

import OError from '@overleaf/o-error'
import request from './request.js'
import settings from '@overleaf/settings'
import { db, ObjectId } from '../../../../app/src/infrastructure/mongodb.js'
import { User as UserModel } from '../../../../app/src/models/User.js'
import UserUpdater from '../../../../app/src/Features/User/UserUpdater.js'
import AuthenticationManager from '../../../../app/src/Features/Authentication/AuthenticationManager.js'
import { promisifyClass } from '@overleaf/promise-utils'
import fs from 'node:fs'
import Path from 'node:path'
import { fileURLToPath } from 'node:url'
import { Cookie } from 'tough-cookie'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const COOKIE_DOMAIN = settings.cookieDomain
// The cookie domain has a leading '.' but the cookie jar stores it without.
const DEFAULT_COOKIE_URL = `https://${COOKIE_DOMAIN.replace(/^\./, '')}/`
let count = settings.test.counterInit
class User {
constructor(options) {
if (options == null) {
options = {}
}
this.emails = [
{
email: options.email || `acceptance-test-${count}@example.com`,
createdAt: new Date(),
confirmedAt: options.confirmedAt,
},
]
this.email = this.emails[0].email
this.password = `a-terrible-secret-${count}`
count++
this.jar = request.jar()
this.request = request.defaults({
jar: this.jar,
})
}
getSession(options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}
this.request.get(
{
url: '/dev/session',
qs: options.set,
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`get session failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
const session = JSON.parse(response.body)
callback(null, session)
}
)
}
setInSession(params, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/dev/set_in_session',
json: params,
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`post set in session failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
})
}
getSplitTestAssignment(splitTestName, query, callback) {
if (!callback) {
callback = query
}
const params = new URLSearchParams({
splitTestName,
...query,
}).toString()
this.request.get(
{
url: `/dev/split_test/get_assignment?${params}`,
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`get split test assignment failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
const assignment = JSON.parse(response.body)
callback(null, assignment)
}
)
}
doSessionMaintenance(callback) {
this.request.post(
{
url: `/dev/split_test/session_maintenance`,
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`post session maintenance failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
optIntoBeta(callback) {
this.request.post(
{
url: '/beta/opt-in',
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 302) {
return callback(
new Error(
`post beta opt-in failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
optOutOfBeta(callback) {
this.request.post(
{
url: '/beta/opt-out',
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 302) {
return callback(
new Error(
`post beta opt-out failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
/* Return the session cookie, url decoded. Use the option {raw:true} to get the original undecoded value */
sessionCookie(options) {
const cookie = Cookie.parse(this.jar.getCookieString(DEFAULT_COOKIE_URL))
if (cookie?.value && !options?.raw) {
cookie.value = decodeURIComponent(cookie.value)
}
return cookie
}
/* Set the session cookie from a string and store it in the cookie jar, so that it will be used
for subsequent requests. */
setSessionCookie(cookie) {
const sessionCookie = request.cookie(
`${settings.cookieName}=${cookie}; Domain=${COOKIE_DOMAIN}; Max-age=3600; Path=/; SameSite=Lax`
)
this.jar.setCookie(sessionCookie, DEFAULT_COOKIE_URL)
}
getEmailConfirmationCode(callback) {
this.getSession((err, session) => {
if (err != null) {
return callback(err)
}
const code = session.pendingUserRegistration?.confirmCode
if (!code) {
return callback(new Error('No confirmation code found in session'))
}
callback(null, code)
})
}
resetCookies() {
this.jar = request.jar()
this.request = request.defaults({
jar: this.jar,
})
}
setExtraAttributes(user) {
if (!user?._id) {
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
this.enrollment = user.enrollment
}
get(callback) {
db.users.findOne({ _id: new ObjectId(this._id) }, callback)
}
getAuditLog(callback) {
this.get((error, user) => {
if (error) return callback(error)
if (!user) return callback(new Error('User not found'))
db.userAuditLogEntries
.find({ userId: new ObjectId(this._id) })
.toArray((error, auditLog) => {
if (error) return callback(error)
callback(null, auditLog || [])
})
})
}
getAuditLogWithoutNoise(callback) {
this.getAuditLog((error, auditLog) => {
if (error) return callback(error)
callback(
null,
auditLog.filter(entry => {
return entry.operation !== 'login'
})
)
})
}
mongoUpdate(updateOp, callback) {
db.users.updateOne({ _id: new 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)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`register failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
this.getEmailConfirmationCode((error, code) => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/registration/confirm-email',
json: { code },
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`email confirmation failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
db.users.findOne({ email: this.email }, (error, user) => {
if (error != null) {
return callback(error)
}
this.setExtraAttributes(user)
callback(null, user, response)
})
}
)
})
}
)
})
}
login(callback) {
this.loginWith(this.email, callback)
}
loginWith(email, callback) {
this.ensureUserExists(error => {
if (error != null) {
return callback(error)
}
this.loginWithEmailPassword(email, this.password, callback)
})
}
loginNoUpdate(callback) {
this.loginWithEmailPassword(this.email, this.password, callback)
}
loginWithEmailPassword(email, password, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: settings.enableLegacyLogin ? '/login/legacy' : '/login',
json: {
email,
password,
'g-recaptcha-response': 'valid',
},
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new OError(
`login failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`,
{ response, body }
)
)
}
// get new csrf token, then return result of login
this.getCsrfToken(err => {
if (err) {
return callback(OError.tag(err, 'after login'))
}
callback(null, response, body)
})
}
)
})
}
ensureUserExists(callback) {
if (this._id) return callback() // already exists
const filter = { email: this.email }
const options = { upsert: true, new: true, setDefaultsOnInsert: true }
AuthenticationManager.hashPassword(
this.password,
(error, hashedPassword) => {
if (error != null) {
return callback(error)
}
UserModel.findOneAndUpdate(
filter,
{ $set: { hashedPassword, emails: this.emails } },
options
)
.then(user => {
this.setExtraAttributes(user)
callback(null, this.password)
})
.catch(callback)
}
)
}
// Update and persist feature upgrade. Downgrades will be flaky!
upgradeFeatures(features, callback) {
this.setFeatures(features, err => {
if (err) return callback(err)
// Persist the feature update, otherwise the next feature refresh will reset them.
this.setFeaturesOverride(
{
createdAt: new Date(),
note: 'Some note',
features,
},
callback
)
})
}
// Low-level. Temporary feature change. A feature refresh might reset them, e.g. "some time" after logging in.
setFeatures(features, callback) {
const update = {}
for (const key in features) {
const value = features[key]
update[`features.${key}`] = value
}
UserModel.updateOne({ _id: this.id }, update)
.then((...args) => callback(null, ...args))
.catch(callback)
}
// Low-level. Permanent feature change. Feature overrides are not applied right away. A feature refresh might populate them "some time" after login/changing subscriptions.
setFeaturesOverride(featuresOverride, callback) {
if (!featuresOverride?.features) {
throw new Error('bad featuresOverride schema')
}
const update = { $push: { featuresOverrides: featuresOverride } }
UserModel.updateOne({ _id: this.id }, update)
.then((...args) => callback(null, ...args))
.catch(callback)
}
setOverleafId(overleafId, callback) {
UserModel.updateOne({ _id: this.id }, { 'overleaf.id': overleafId })
.then((...args) => callback(null, ...args))
.catch(callback)
}
setEmails(emails, callback) {
UserModel.updateOne({ _id: this.id }, { emails })
.then((...args) => callback(null, ...args))
.catch(callback)
}
setSuspended(suspended, callback) {
UserModel.updateOne({ _id: this.id }, { suspended })
.then((...args) => callback(null, ...args))
.catch(callback)
}
logout(callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/logout',
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode >= 400) {
return callback(
new Error(
`logout failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
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) {
this.mongoUpdate({ $set: { isAdmin: true } }, callback)
}
ensureStaffAccess(flag, callback) {
const update = { $set: {} }
update.$set[`staffAccess.${flag}`] = true
this.mongoUpdate(update, callback)
}
upgradeSomeFeatures(callback) {
const features = {
collaborators: -1, // Infinite
versioning: true,
dropbox: true,
compileTimeout: 60,
compileGroup: 'priority',
references: true,
trackChanges: true,
trackChangesVisible: true,
}
this.mongoUpdate({ $set: { features } }, callback)
}
downgradeFeatures(callback) {
const features = {
collaborators: 1,
versioning: false,
dropbox: false,
compileTimeout: 60,
compileGroup: 'standard',
references: false,
trackChanges: false,
trackChangesVisible: false,
}
this.mongoUpdate({ $set: { features } }, callback)
}
defaultFeatures(callback) {
const features = settings.defaultFeatures
this.mongoUpdate({ $set: { features } }, callback)
}
getFeatures(callback) {
db.users.findOne(
{ _id: new ObjectId(this.id) },
{ projection: { 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.deleteMany({ owner_ref: new ObjectId(userId) }, err => {
if (err != null) {
callback(err)
}
db.users.deleteOne({ _id: new ObjectId(userId) }, callback)
})
})
}
deleteUser(callback) {
this.getCsrfToken(error => {
if (error) {
return callback(error)
}
this.request.post(
{
url: '/user/delete',
json: { password: this.password },
},
(err, response, body) => {
if (err) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`user deletion failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
})
}
getProject(projectId, callback) {
db.projects.findOne({ _id: new ObjectId(projectId.toString()) }, callback)
}
saveProject(project, callback) {
db.projects.updateOne({ _id: project._id }, { $set: 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 (response.statusCode !== 200) {
return callback(
new Error(
`project creation failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
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)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`project deletion failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
undeleteProject(projectId, callback) {
this.request.post(
{
url: `/admin/project/${projectId}/undelete`,
},
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`project un-deletion failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
deleteProjects(callback) {
db.projects.deleteMany({ owner_ref: new ObjectId(this.id) }, callback)
}
openProject(projectId, callback) {
this.request.get(
{
url: `/project/${projectId}`,
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`opening project failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
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)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`doc creation in project failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null, body._id)
}
)
})
}
uploadFileInProject(projectId, folderId, file, name, contentType, callback) {
this.uploadFileInProjectFull(
projectId,
folderId,
file,
name,
contentType,
(err, body) => callback(err, body?.entity_id)
)
}
uploadFileInProjectFull(
projectId,
folderId,
file,
name,
contentType,
callback
) {
const fileStream = fs.createReadStream(
Path.resolve(Path.join(__dirname, '..', '..', 'files', file))
)
this.request.post(
{
uri: `project/${projectId}/upload`,
qs: {
folder_id: String(folderId),
},
formData: {
name,
qqfile: {
value: fileStream,
options: {
filename: name,
contentType,
},
},
},
},
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`uploading file failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null, JSON.parse(body))
}
)
}
uploadExampleFileInProject(projectId, folderId, name, callback) {
this.uploadFileInProject(
projectId,
folderId,
'1pixel.png',
name,
'image/png',
callback
)
}
moveItemInProject(projectId, type, itemId, folderId, callback) {
this.request.post(
{
uri: `project/${projectId}/${type}/${itemId}/move`,
json: {
folder_id: folderId,
},
},
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`move item in project failed: type=${type} status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
}
renameItemInProject(projectId, type, itemId, name, callback) {
this.request.post(
{
uri: `project/${projectId}/${type}/${itemId}/rename`,
json: {
name,
},
},
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`rename item in project failed: type=${type} status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
}
deleteItemInProject(projectId, type, itemId, callback) {
this.request.delete(
`project/${projectId}/${type}/${itemId}`,
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`delete item in project failed: type=${type} status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
}
joinProject(projectId, callback) {
this.request.post(
{
url: `/project/${projectId}/join`,
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true,
},
json: { userId: this._id },
jar: false,
},
(error, response, body) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`join project failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null, body)
}
)
}
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 } }
} else if (privileges === 'pendingEditor') {
updateOp = {
$addToSet: { readOnly_refs: user._id, pendingEditor_refs: user._id },
}
} else if (privileges === 'review') {
updateOp = {
$addToSet: { reviewer_refs: user._id },
}
}
db.projects.updateOne({ _id: new ObjectId(projectId) }, updateOp, callback)
}
makePublic(projectId, level, callback) {
// A fudge, to get around the fact that `readOnly` and `readAndWrite` are now disallowed
// via the API, but we still need to test the behaviour of projects with these values set.
db.projects.updateOne(
{ _id: new ObjectId(projectId) },
// NOTE: Yes, there is a typo in the db schema.
{ $set: { publicAccesLevel: level } },
callback
)
}
makePrivate(projectId, callback) {
this.request.post(
{
url: `/project/${projectId}/settings/admin`,
json: {
publicAccessLevel: 'private',
},
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`disable link sharing failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
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)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`enable link sharing failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null)
}
)
}
getCsrfToken(callback) {
this.request.get(
{
url: '/dev/csrf',
},
(err, response, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`get csrf token failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
this.csrfToken = body
this.request = this.request.defaults({
headers: {
'x-csrf-token': this.csrfToken,
},
})
callback()
}
)
}
changePassword(newPassword, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/password/update',
json: {
currentPassword: this.password,
newPassword1: newPassword,
newPassword2: newPassword,
},
},
(err, response, body) => {
if (err) {
return callback(err)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`change password failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
this.password = newPassword
callback()
}
)
})
}
reconfirmAccountRequest(userEmail, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/reconfirm',
json: {
email: userEmail,
},
},
(error, response) => {
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)
}
if (response.statusCode !== 200) {
return callback(
new Error(
`open settings page failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback(null, response.statusCode)
}
)
})
}
updateSettings(newSettings, callback) {
this.getCsrfToken(error => {
if (error != null) {
return callback(error)
}
this.request.post(
{
url: '/user/settings',
json: newSettings,
},
(err, response, body) => {
if (err) return callback(err)
if (response.statusCode !== 200) {
return callback(
new Error(
`update settings failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
})
}
getProjectListPage(callback) {
this.request.get('/project', (error, response) => {
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: status=${response.statusCode} body=${body}`
)
)
}
})
}
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, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`transfer project ownership failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
})
}
setV1Id(v1Id, callback) {
UserModel.updateOne(
{
_id: this._id,
},
{
overleaf: {
id: v1Id,
},
}
)
.then((...args) => callback(null, ...args))
.catch(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, body) => {
if (err != null) {
return callback(err)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`update collaborator access failed: status=${
response.statusCode
} body=${JSON.stringify(body)}`
)
)
}
callback()
}
)
})
}
}
User.promises = promisifyClass(User, {
without: ['setExtraAttributes', 'sessionCookie', 'setSessionCookie'],
})
User.promises.prototype.doRequest = async function (method, params) {
return new Promise((resolve, reject) => {
this.request[method.toLowerCase()](params, (err, response, body) => {
if (err) {
reject(err)
} else {
resolve({ response, body })
}
})
})
}
export default User