User.js 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. const OError = require('@overleaf/o-error')
  2. const request = require('./request')
  3. const settings = require('@overleaf/settings')
  4. const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
  5. const UserModel = require('../../../../app/src/models/User').User
  6. const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
  7. const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager')
  8. const { promisifyClass } = require('@overleaf/promise-utils')
  9. const fs = require('fs')
  10. const Path = require('path')
  11. const { Cookie } = require('tough-cookie')
  12. const COOKIE_DOMAIN = settings.cookieDomain
  13. let count = settings.test.counterInit
  14. class User {
  15. constructor(options) {
  16. if (options == null) {
  17. options = {}
  18. }
  19. this.emails = [
  20. {
  21. email: options.email || `acceptance-test-${count}@example.com`,
  22. createdAt: new Date(),
  23. confirmedAt: options.confirmedAt,
  24. },
  25. ]
  26. this.email = this.emails[0].email
  27. this.password = `a-terrible-secret-${count}`
  28. count++
  29. this.jar = request.jar()
  30. this.request = request.defaults({
  31. jar: this.jar,
  32. })
  33. }
  34. getSession(options, callback) {
  35. if (typeof options === 'function') {
  36. callback = options
  37. options = {}
  38. }
  39. this.request.get(
  40. {
  41. url: '/dev/session',
  42. qs: options.set,
  43. },
  44. (err, response, body) => {
  45. if (err != null) {
  46. return callback(err)
  47. }
  48. if (response.statusCode !== 200) {
  49. return callback(
  50. new Error(
  51. `get session failed: status=${
  52. response.statusCode
  53. } body=${JSON.stringify(body)}`
  54. )
  55. )
  56. }
  57. const session = JSON.parse(response.body)
  58. callback(null, session)
  59. }
  60. )
  61. }
  62. setInSession(params, callback) {
  63. this.getCsrfToken(error => {
  64. if (error != null) {
  65. return callback(error)
  66. }
  67. this.request.post(
  68. {
  69. url: '/dev/set_in_session',
  70. json: params,
  71. },
  72. (err, response, body) => {
  73. if (err != null) {
  74. return callback(err)
  75. }
  76. if (response.statusCode !== 200) {
  77. return callback(
  78. new Error(
  79. `post set in session failed: status=${
  80. response.statusCode
  81. } body=${JSON.stringify(body)}`
  82. )
  83. )
  84. }
  85. callback(null)
  86. }
  87. )
  88. })
  89. }
  90. getSplitTestAssignment(splitTestName, query, callback) {
  91. if (!callback) {
  92. callback = query
  93. }
  94. const params = new URLSearchParams({
  95. splitTestName,
  96. ...query,
  97. }).toString()
  98. this.request.get(
  99. {
  100. url: `/dev/split_test/get_assignment?${params}`,
  101. },
  102. (err, response, body) => {
  103. if (err != null) {
  104. return callback(err)
  105. }
  106. if (response.statusCode !== 200) {
  107. return callback(
  108. new Error(
  109. `get split test assignment failed: status=${
  110. response.statusCode
  111. } body=${JSON.stringify(body)}`
  112. )
  113. )
  114. }
  115. const assignment = JSON.parse(response.body)
  116. callback(null, assignment)
  117. }
  118. )
  119. }
  120. doSessionMaintenance(callback) {
  121. this.request.post(
  122. {
  123. url: `/dev/split_test/session_maintenance`,
  124. },
  125. (err, response, body) => {
  126. if (err != null) {
  127. return callback(err)
  128. }
  129. if (response.statusCode !== 200) {
  130. return callback(
  131. new Error(
  132. `post session maintenance failed: status=${
  133. response.statusCode
  134. } body=${JSON.stringify(body)}`
  135. )
  136. )
  137. }
  138. callback(null)
  139. }
  140. )
  141. }
  142. optIntoBeta(callback) {
  143. this.request.post(
  144. {
  145. url: '/beta/opt-in',
  146. },
  147. (err, response, body) => {
  148. if (err != null) {
  149. return callback(err)
  150. }
  151. if (response.statusCode !== 302) {
  152. return callback(
  153. new Error(
  154. `post beta opt-in failed: status=${
  155. response.statusCode
  156. } body=${JSON.stringify(body)}`
  157. )
  158. )
  159. }
  160. callback(null)
  161. }
  162. )
  163. }
  164. optOutOfBeta(callback) {
  165. this.request.post(
  166. {
  167. url: '/beta/opt-out',
  168. },
  169. (err, response, body) => {
  170. if (err != null) {
  171. return callback(err)
  172. }
  173. if (response.statusCode !== 302) {
  174. return callback(
  175. new Error(
  176. `post beta opt-out failed: status=${
  177. response.statusCode
  178. } body=${JSON.stringify(body)}`
  179. )
  180. )
  181. }
  182. callback(null)
  183. }
  184. )
  185. }
  186. /* Return the session cookie, url decoded. Use the option {raw:true} to get the original undecoded value */
  187. sessionCookie(options) {
  188. const cookie = Cookie.parse(
  189. this.jar.getCookieString(
  190. // The cookie domain has a leading '.' but
  191. // the cookie jar stores it without.
  192. 'https://' + COOKIE_DOMAIN.replace(/^\./, '') + '/'
  193. )
  194. )
  195. if (cookie?.value && !options?.raw) {
  196. cookie.value = decodeURIComponent(cookie.value)
  197. }
  198. return cookie
  199. }
  200. getEmailConfirmationCode(callback) {
  201. this.getSession((err, session) => {
  202. if (err != null) {
  203. return callback(err)
  204. }
  205. const code = session.pendingUserRegistration?.confirmCode
  206. if (!code) {
  207. return callback(new Error('No confirmation code found in session'))
  208. }
  209. callback(null, code)
  210. })
  211. }
  212. resetCookies() {
  213. this.jar = request.jar()
  214. this.request = request.defaults({
  215. jar: this.jar,
  216. })
  217. }
  218. setExtraAttributes(user) {
  219. if ((user != null ? user._id : undefined) == null) {
  220. throw new Error('User does not exist')
  221. }
  222. this.id = user._id.toString()
  223. this._id = user._id.toString()
  224. this.first_name = user.first_name
  225. this.referal_id = user.referal_id
  226. this.enrollment = user.enrollment
  227. }
  228. get(callback) {
  229. db.users.findOne({ _id: new ObjectId(this._id) }, callback)
  230. }
  231. getAuditLog(callback) {
  232. this.get((error, user) => {
  233. if (error) return callback(error)
  234. if (!user) return callback(new Error('User not found'))
  235. db.userAuditLogEntries
  236. .find({ userId: new ObjectId(this._id) })
  237. .toArray((error, auditLog) => {
  238. if (error) return callback(error)
  239. callback(null, auditLog || [])
  240. })
  241. })
  242. }
  243. getAuditLogWithoutNoise(callback) {
  244. this.getAuditLog((error, auditLog) => {
  245. if (error) return callback(error)
  246. callback(
  247. null,
  248. auditLog.filter(entry => {
  249. return entry.operation !== 'login'
  250. })
  251. )
  252. })
  253. }
  254. mongoUpdate(updateOp, callback) {
  255. db.users.updateOne({ _id: new ObjectId(this._id) }, updateOp, callback)
  256. }
  257. register(callback) {
  258. this.registerWithQuery('', callback)
  259. }
  260. registerWithQuery(query, callback) {
  261. if (this._id != null) {
  262. return callback(new Error('User already registered'))
  263. }
  264. this.getCsrfToken(error => {
  265. if (error != null) {
  266. return callback(error)
  267. }
  268. this.request.post(
  269. {
  270. url: `/register${query}`,
  271. json: { email: this.email, password: this.password },
  272. },
  273. (error, response, body) => {
  274. if (error != null) {
  275. return callback(error)
  276. }
  277. if (response.statusCode !== 200) {
  278. return callback(
  279. new Error(
  280. `register failed: status=${
  281. response.statusCode
  282. } body=${JSON.stringify(body)}`
  283. )
  284. )
  285. }
  286. this.getEmailConfirmationCode((error, code) => {
  287. if (error != null) {
  288. return callback(error)
  289. }
  290. this.request.post(
  291. {
  292. url: '/registration/confirm-email',
  293. json: { code },
  294. },
  295. (error, response, body) => {
  296. if (error != null) {
  297. return callback(error)
  298. }
  299. if (response.statusCode !== 200) {
  300. return callback(
  301. new Error(
  302. `email confirmation failed: status=${
  303. response.statusCode
  304. } body=${JSON.stringify(body)}`
  305. )
  306. )
  307. }
  308. db.users.findOne({ email: this.email }, (error, user) => {
  309. if (error != null) {
  310. return callback(error)
  311. }
  312. this.setExtraAttributes(user)
  313. callback(null, user, response)
  314. })
  315. }
  316. )
  317. })
  318. }
  319. )
  320. })
  321. }
  322. login(callback) {
  323. this.loginWith(this.email, callback)
  324. }
  325. loginWith(email, callback) {
  326. this.ensureUserExists(error => {
  327. if (error != null) {
  328. return callback(error)
  329. }
  330. this.loginWithEmailPassword(email, this.password, callback)
  331. })
  332. }
  333. loginNoUpdate(callback) {
  334. this.loginWithEmailPassword(this.email, this.password, callback)
  335. }
  336. loginWithEmailPassword(email, password, callback) {
  337. this.getCsrfToken(error => {
  338. if (error != null) {
  339. return callback(error)
  340. }
  341. this.request.post(
  342. {
  343. url: settings.enableLegacyLogin ? '/login/legacy' : '/login',
  344. json: {
  345. email,
  346. password,
  347. 'g-recaptcha-response': 'valid',
  348. },
  349. },
  350. (error, response, body) => {
  351. if (error != null) {
  352. return callback(error)
  353. }
  354. if (response.statusCode !== 200) {
  355. return callback(
  356. new OError(
  357. `login failed: status=${
  358. response.statusCode
  359. } body=${JSON.stringify(body)}`,
  360. { response, body }
  361. )
  362. )
  363. }
  364. // get new csrf token, then return result of login
  365. this.getCsrfToken(err => {
  366. if (err) {
  367. return callback(OError.tag(err, 'after login'))
  368. }
  369. callback(null, response, body)
  370. })
  371. }
  372. )
  373. })
  374. }
  375. ensureUserExists(callback) {
  376. const filter = { email: this.email }
  377. const options = { upsert: true, new: true, setDefaultsOnInsert: true }
  378. AuthenticationManager.hashPassword(
  379. this.password,
  380. (error, hashedPassword) => {
  381. if (error != null) {
  382. return callback(error)
  383. }
  384. UserModel.findOneAndUpdate(
  385. filter,
  386. { $set: { hashedPassword, emails: this.emails } },
  387. options,
  388. (error, user) => {
  389. if (error != null) {
  390. return callback(error)
  391. }
  392. this.setExtraAttributes(user)
  393. callback(null, this.password)
  394. }
  395. )
  396. }
  397. )
  398. }
  399. setFeatures(features, callback) {
  400. const update = {}
  401. for (const key in features) {
  402. const value = features[key]
  403. update[`features.${key}`] = value
  404. }
  405. UserModel.updateOne({ _id: this.id }, update, callback)
  406. }
  407. setFeaturesOverride(featuresOverride, callback) {
  408. const update = { $push: { featuresOverrides: featuresOverride } }
  409. UserModel.updateOne({ _id: this.id }, update, callback)
  410. }
  411. setOverleafId(overleafId, callback) {
  412. UserModel.updateOne(
  413. { _id: this.id },
  414. { 'overleaf.id': overleafId },
  415. callback
  416. )
  417. }
  418. setEmails(emails, callback) {
  419. UserModel.updateOne({ _id: this.id }, { emails }, callback)
  420. }
  421. setSuspended(suspended, callback) {
  422. UserModel.updateOne({ _id: this.id }, { suspended }, callback)
  423. }
  424. logout(callback) {
  425. this.getCsrfToken(error => {
  426. if (error != null) {
  427. return callback(error)
  428. }
  429. this.request.post(
  430. {
  431. url: '/logout',
  432. },
  433. (error, response, body) => {
  434. if (error != null) {
  435. return callback(error)
  436. }
  437. if (response.statusCode >= 400) {
  438. return callback(
  439. new Error(
  440. `logout failed: status=${
  441. response.statusCode
  442. } body=${JSON.stringify(body)}`
  443. )
  444. )
  445. }
  446. db.users.findOne({ email: this.email }, (error, user) => {
  447. if (error != null) {
  448. return callback(error)
  449. }
  450. if (user == null) {
  451. return callback()
  452. }
  453. this.id = user._id.toString()
  454. this._id = user._id.toString()
  455. callback()
  456. })
  457. }
  458. )
  459. })
  460. }
  461. addEmail(email, callback) {
  462. this.emails.push({ email, createdAt: new Date() })
  463. UserUpdater.addEmailAddress(
  464. this.id,
  465. email,
  466. {},
  467. { initiatorId: this._id, ipAddress: '127:0:0:0' },
  468. callback
  469. )
  470. }
  471. confirmEmail(email, callback) {
  472. for (let idx = 0; idx < this.emails.length; idx++) {
  473. const emailData = this.emails[idx]
  474. if (emailData.email === email) {
  475. this.emails[idx].confirmedAt = new Date()
  476. }
  477. }
  478. UserUpdater.confirmEmail(this.id, email, callback)
  479. }
  480. ensureAdmin(callback) {
  481. this.mongoUpdate({ $set: { isAdmin: true } }, callback)
  482. }
  483. ensureStaffAccess(flag, callback) {
  484. const update = { $set: {} }
  485. update.$set[`staffAccess.${flag}`] = true
  486. this.mongoUpdate(update, callback)
  487. }
  488. upgradeFeatures(callback) {
  489. const features = {
  490. collaborators: -1, // Infinite
  491. versioning: true,
  492. dropbox: true,
  493. compileTimeout: 60,
  494. compileGroup: 'priority',
  495. templates: true,
  496. references: true,
  497. trackChanges: true,
  498. trackChangesVisible: true,
  499. }
  500. this.mongoUpdate({ $set: { features } }, callback)
  501. }
  502. downgradeFeatures(callback) {
  503. const features = {
  504. collaborators: 1,
  505. versioning: false,
  506. dropbox: false,
  507. compileTimeout: 60,
  508. compileGroup: 'standard',
  509. templates: false,
  510. references: false,
  511. trackChanges: false,
  512. trackChangesVisible: false,
  513. }
  514. this.mongoUpdate({ $set: { features } }, callback)
  515. }
  516. defaultFeatures(callback) {
  517. const features = settings.defaultFeatures
  518. this.mongoUpdate({ $set: { features } }, callback)
  519. }
  520. getFeatures(callback) {
  521. db.users.findOne(
  522. { _id: new ObjectId(this.id) },
  523. { projection: { features: 1 } },
  524. (error, user) => callback(error, user && user.features)
  525. )
  526. }
  527. fullDeleteUser(email, callback) {
  528. db.users.findOne({ email }, (error, user) => {
  529. if (error != null) {
  530. return callback(error)
  531. }
  532. if (user == null) {
  533. return callback()
  534. }
  535. const userId = user._id
  536. db.projects.deleteMany({ owner_ref: new ObjectId(userId) }, err => {
  537. if (err != null) {
  538. callback(err)
  539. }
  540. db.users.deleteOne({ _id: new ObjectId(userId) }, callback)
  541. })
  542. })
  543. }
  544. deleteUser(callback) {
  545. this.getCsrfToken(error => {
  546. if (error) {
  547. return callback(error)
  548. }
  549. this.request.post(
  550. {
  551. url: '/user/delete',
  552. json: { password: this.password },
  553. },
  554. (err, response, body) => {
  555. if (err) {
  556. return callback(err)
  557. }
  558. if (response.statusCode !== 200) {
  559. return callback(
  560. new Error(
  561. `user deletion failed: status=${
  562. response.statusCode
  563. } body=${JSON.stringify(body)}`
  564. )
  565. )
  566. }
  567. callback()
  568. }
  569. )
  570. })
  571. }
  572. getProject(projectId, callback) {
  573. db.projects.findOne({ _id: new ObjectId(projectId.toString()) }, callback)
  574. }
  575. saveProject(project, callback) {
  576. db.projects.updateOne({ _id: project._id }, { $set: project }, callback)
  577. }
  578. createProject(name, options, callback) {
  579. if (typeof options === 'function') {
  580. callback = options
  581. options = {}
  582. }
  583. this.request.post(
  584. {
  585. url: '/project/new',
  586. json: Object.assign({ projectName: name }, options),
  587. },
  588. (error, response, body) => {
  589. if (error != null) {
  590. return callback(error)
  591. }
  592. if (response.statusCode !== 200) {
  593. return callback(
  594. new Error(
  595. `project creation failed: status=${
  596. response.statusCode
  597. } body=${JSON.stringify(body)}`
  598. )
  599. )
  600. }
  601. if ((body != null ? body.project_id : undefined) == null) {
  602. error = new Error(
  603. JSON.stringify([
  604. 'SOMETHING WENT WRONG CREATING PROJECT',
  605. name,
  606. options,
  607. response.statusCode,
  608. response.headers.location,
  609. body,
  610. ])
  611. )
  612. callback(error)
  613. } else {
  614. callback(null, body.project_id)
  615. }
  616. }
  617. )
  618. }
  619. deleteProject(projectId, callback) {
  620. this.request.delete(
  621. {
  622. url: `/project/${projectId}`,
  623. },
  624. (error, response, body) => {
  625. if (error != null) {
  626. return callback(error)
  627. }
  628. if (response.statusCode !== 200) {
  629. return callback(
  630. new Error(
  631. `project deletion failed: status=${
  632. response.statusCode
  633. } body=${JSON.stringify(body)}`
  634. )
  635. )
  636. }
  637. callback(null)
  638. }
  639. )
  640. }
  641. undeleteProject(projectId, callback) {
  642. this.request.post(
  643. {
  644. url: `/admin/project/${projectId}/undelete`,
  645. },
  646. (error, response, body) => {
  647. if (error) {
  648. return callback(error)
  649. }
  650. if (response.statusCode !== 204) {
  651. return callback(
  652. new Error(
  653. `project un-deletion failed: status=${
  654. response.statusCode
  655. } body=${JSON.stringify(body)}`
  656. )
  657. )
  658. }
  659. callback(null)
  660. }
  661. )
  662. }
  663. deleteProjects(callback) {
  664. db.projects.deleteMany({ owner_ref: new ObjectId(this.id) }, callback)
  665. }
  666. openProject(projectId, callback) {
  667. this.request.get(
  668. {
  669. url: `/project/${projectId}`,
  670. },
  671. (error, response, body) => {
  672. if (error != null) {
  673. return callback(error)
  674. }
  675. if (response.statusCode !== 200) {
  676. return callback(
  677. new Error(
  678. `opening project failed: status=${
  679. response.statusCode
  680. } body=${JSON.stringify(body)}`
  681. )
  682. )
  683. }
  684. callback(null)
  685. }
  686. )
  687. }
  688. createDocInProject(projectId, parentFolderId, name, callback) {
  689. this.getCsrfToken(error => {
  690. if (error != null) {
  691. return callback(error)
  692. }
  693. this.request.post(
  694. {
  695. url: `/project/${projectId}/doc`,
  696. json: {
  697. name,
  698. parentFolderId,
  699. },
  700. },
  701. (error, response, body) => {
  702. if (error != null) {
  703. return callback(error)
  704. }
  705. if (response.statusCode !== 200) {
  706. return callback(
  707. new Error(
  708. `doc creation in project failed: status=${
  709. response.statusCode
  710. } body=${JSON.stringify(body)}`
  711. )
  712. )
  713. }
  714. callback(null, body._id)
  715. }
  716. )
  717. })
  718. }
  719. uploadFileInProject(projectId, folderId, file, name, contentType, callback) {
  720. const fileStream = fs.createReadStream(
  721. Path.resolve(Path.join(__dirname, '..', '..', 'files', file))
  722. )
  723. this.request.post(
  724. {
  725. uri: `project/${projectId}/upload`,
  726. qs: {
  727. folder_id: String(folderId),
  728. },
  729. formData: {
  730. name,
  731. qqfile: {
  732. value: fileStream,
  733. options: {
  734. filename: name,
  735. contentType,
  736. },
  737. },
  738. },
  739. },
  740. (error, response, body) => {
  741. if (error) {
  742. return callback(error)
  743. }
  744. if (response.statusCode !== 200) {
  745. return callback(
  746. new Error(
  747. `uploading file failed: status=${
  748. response.statusCode
  749. } body=${JSON.stringify(body)}`
  750. )
  751. )
  752. }
  753. callback(null, JSON.parse(body).entity_id)
  754. }
  755. )
  756. }
  757. uploadExampleFileInProject(projectId, folderId, name, callback) {
  758. this.uploadFileInProject(
  759. projectId,
  760. folderId,
  761. '1pixel.png',
  762. name,
  763. 'image/png',
  764. callback
  765. )
  766. }
  767. moveItemInProject(projectId, type, itemId, folderId, callback) {
  768. this.request.post(
  769. {
  770. uri: `project/${projectId}/${type}/${itemId}/move`,
  771. json: {
  772. folder_id: folderId,
  773. },
  774. },
  775. (error, response, body) => {
  776. if (error) {
  777. return callback(error)
  778. }
  779. if (response.statusCode !== 200) {
  780. return callback(
  781. new Error(
  782. `move item in project failed: type=${type} status=${
  783. response.statusCode
  784. } body=${JSON.stringify(body)}`
  785. )
  786. )
  787. }
  788. callback()
  789. }
  790. )
  791. }
  792. renameItemInProject(projectId, type, itemId, name, callback) {
  793. this.request.post(
  794. {
  795. uri: `project/${projectId}/${type}/${itemId}/rename`,
  796. json: {
  797. name,
  798. },
  799. },
  800. (error, response, body) => {
  801. if (error) {
  802. return callback(error)
  803. }
  804. if (response.statusCode !== 204) {
  805. return callback(
  806. new Error(
  807. `rename item in project failed: type=${type} status=${
  808. response.statusCode
  809. } body=${JSON.stringify(body)}`
  810. )
  811. )
  812. }
  813. callback()
  814. }
  815. )
  816. }
  817. deleteItemInProject(projectId, type, itemId, callback) {
  818. this.request.delete(
  819. `project/${projectId}/${type}/${itemId}`,
  820. (error, response, body) => {
  821. if (error) {
  822. return callback(error)
  823. }
  824. if (response.statusCode !== 204) {
  825. return callback(
  826. new Error(
  827. `delete item in project failed: type=${type} status=${
  828. response.statusCode
  829. } body=${JSON.stringify(body)}`
  830. )
  831. )
  832. }
  833. callback()
  834. }
  835. )
  836. }
  837. joinProject(projectId, callback) {
  838. this.request.post(
  839. {
  840. url: `/project/${projectId}/join`,
  841. auth: {
  842. user: settings.apis.web.user,
  843. pass: settings.apis.web.pass,
  844. sendImmediately: true,
  845. },
  846. json: { userId: this._id },
  847. jar: false,
  848. },
  849. (error, response, body) => {
  850. if (error) {
  851. return callback(error)
  852. }
  853. if (response.statusCode !== 200) {
  854. return callback(
  855. new Error(
  856. `join project failed: status=${
  857. response.statusCode
  858. } body=${JSON.stringify(body)}`
  859. )
  860. )
  861. }
  862. callback(null, body)
  863. }
  864. )
  865. }
  866. addUserToProject(projectId, user, privileges, callback) {
  867. let updateOp
  868. if (privileges === 'readAndWrite') {
  869. updateOp = { $addToSet: { collaberator_refs: user._id } }
  870. } else if (privileges === 'readOnly') {
  871. updateOp = { $addToSet: { readOnly_refs: user._id } }
  872. }
  873. db.projects.updateOne({ _id: new ObjectId(projectId) }, updateOp, callback)
  874. }
  875. makePublic(projectId, level, callback) {
  876. // A fudge, to get around the fact that `readOnly` and `readAndWrite` are now disallowed
  877. // via the API, but we still need to test the behaviour of projects with these values set.
  878. db.projects.updateOne(
  879. { _id: new ObjectId(projectId) },
  880. // NOTE: Yes, there is a typo in the db schema.
  881. { $set: { publicAccesLevel: level } },
  882. callback
  883. )
  884. }
  885. makePrivate(projectId, callback) {
  886. this.request.post(
  887. {
  888. url: `/project/${projectId}/settings/admin`,
  889. json: {
  890. publicAccessLevel: 'private',
  891. },
  892. },
  893. (error, response, body) => {
  894. if (error != null) {
  895. return callback(error)
  896. }
  897. if (response.statusCode !== 204) {
  898. return callback(
  899. new Error(
  900. `disable link sharing failed: status=${
  901. response.statusCode
  902. } body=${JSON.stringify(body)}`
  903. )
  904. )
  905. }
  906. callback(null)
  907. }
  908. )
  909. }
  910. makeTokenBased(projectId, callback) {
  911. this.request.post(
  912. {
  913. url: `/project/${projectId}/settings/admin`,
  914. json: {
  915. publicAccessLevel: 'tokenBased',
  916. },
  917. },
  918. (error, response, body) => {
  919. if (error != null) {
  920. return callback(error)
  921. }
  922. if (response.statusCode !== 204) {
  923. return callback(
  924. new Error(
  925. `enable link sharing failed: status=${
  926. response.statusCode
  927. } body=${JSON.stringify(body)}`
  928. )
  929. )
  930. }
  931. callback(null)
  932. }
  933. )
  934. }
  935. getCsrfToken(callback) {
  936. this.request.get(
  937. {
  938. url: '/dev/csrf',
  939. },
  940. (err, response, body) => {
  941. if (err != null) {
  942. return callback(err)
  943. }
  944. if (response.statusCode !== 200) {
  945. return callback(
  946. new Error(
  947. `get csrf token failed: status=${
  948. response.statusCode
  949. } body=${JSON.stringify(body)}`
  950. )
  951. )
  952. }
  953. this.csrfToken = body
  954. this.request = this.request.defaults({
  955. headers: {
  956. 'x-csrf-token': this.csrfToken,
  957. },
  958. })
  959. callback()
  960. }
  961. )
  962. }
  963. changePassword(newPassword, callback) {
  964. this.getCsrfToken(error => {
  965. if (error != null) {
  966. return callback(error)
  967. }
  968. this.request.post(
  969. {
  970. url: '/user/password/update',
  971. json: {
  972. currentPassword: this.password,
  973. newPassword1: newPassword,
  974. newPassword2: newPassword,
  975. },
  976. },
  977. (err, response, body) => {
  978. if (err) {
  979. return callback(err)
  980. }
  981. if (response.statusCode !== 200) {
  982. return callback(
  983. new Error(
  984. `change password failed: status=${
  985. response.statusCode
  986. } body=${JSON.stringify(body)}`
  987. )
  988. )
  989. }
  990. this.password = newPassword
  991. callback()
  992. }
  993. )
  994. })
  995. }
  996. reconfirmAccountRequest(userEmail, callback) {
  997. this.getCsrfToken(error => {
  998. if (error != null) {
  999. return callback(error)
  1000. }
  1001. this.request.post(
  1002. {
  1003. url: '/user/reconfirm',
  1004. json: {
  1005. email: userEmail,
  1006. },
  1007. },
  1008. (error, response) => {
  1009. callback(error, response)
  1010. }
  1011. )
  1012. })
  1013. }
  1014. getUserSettingsPage(callback) {
  1015. this.getCsrfToken(error => {
  1016. if (error != null) {
  1017. return callback(error)
  1018. }
  1019. this.request.get(
  1020. {
  1021. url: '/user/settings',
  1022. },
  1023. (error, response, body) => {
  1024. if (error != null) {
  1025. return callback(error)
  1026. }
  1027. if (response.statusCode !== 200) {
  1028. return callback(
  1029. new Error(
  1030. `open settings page failed: status=${
  1031. response.statusCode
  1032. } body=${JSON.stringify(body)}`
  1033. )
  1034. )
  1035. }
  1036. callback(null, response.statusCode)
  1037. }
  1038. )
  1039. })
  1040. }
  1041. updateSettings(newSettings, callback) {
  1042. this.getCsrfToken(error => {
  1043. if (error != null) {
  1044. return callback(error)
  1045. }
  1046. this.request.post(
  1047. {
  1048. url: '/user/settings',
  1049. json: newSettings,
  1050. },
  1051. (err, response, body) => {
  1052. if (err) return callback(err)
  1053. if (response.statusCode !== 200) {
  1054. return callback(
  1055. new Error(
  1056. `login failed: status=${
  1057. response.statusCode
  1058. } body=${JSON.stringify(body)}`
  1059. )
  1060. )
  1061. }
  1062. callback()
  1063. }
  1064. )
  1065. })
  1066. }
  1067. getProjectListPage(callback) {
  1068. this.request.get('/project', (error, response) => {
  1069. if (error != null) {
  1070. return callback(error)
  1071. }
  1072. callback(null, response.statusCode)
  1073. })
  1074. }
  1075. isLoggedIn(callback) {
  1076. this.request.get('/user/personal_info', (error, response, body) => {
  1077. if (error != null) {
  1078. return callback(error)
  1079. }
  1080. if (response.statusCode === 200) {
  1081. callback(null, true)
  1082. } else if (response.statusCode === 302) {
  1083. callback(null, false)
  1084. } else {
  1085. callback(
  1086. new Error(
  1087. `unexpected status code from /user/personal_info: status=${response.statusCode} body=${body}`
  1088. )
  1089. )
  1090. }
  1091. })
  1092. }
  1093. transferProjectOwnership(projectId, userId, callback) {
  1094. this.getCsrfToken(err => {
  1095. if (err != null) {
  1096. return callback(err)
  1097. }
  1098. this.request.post(
  1099. {
  1100. url: `/project/${projectId.toString()}/transfer-ownership`,
  1101. json: {
  1102. user_id: userId.toString(),
  1103. },
  1104. },
  1105. (err, response, body) => {
  1106. if (err != null) {
  1107. return callback(err)
  1108. }
  1109. if (response.statusCode !== 204) {
  1110. return callback(
  1111. new Error(
  1112. `transfer project ownership failed: status=${
  1113. response.statusCode
  1114. } body=${JSON.stringify(body)}`
  1115. )
  1116. )
  1117. }
  1118. callback()
  1119. }
  1120. )
  1121. })
  1122. }
  1123. setV1Id(v1Id, callback) {
  1124. UserModel.updateOne(
  1125. {
  1126. _id: this._id,
  1127. },
  1128. {
  1129. overleaf: {
  1130. id: v1Id,
  1131. },
  1132. },
  1133. callback
  1134. )
  1135. }
  1136. setCollaboratorInfo(projectId, userId, info, callback) {
  1137. this.getCsrfToken(err => {
  1138. if (err != null) {
  1139. return callback(err)
  1140. }
  1141. this.request.put(
  1142. {
  1143. url: `/project/${projectId.toString()}/users/${userId.toString()}`,
  1144. json: info,
  1145. },
  1146. (err, response, body) => {
  1147. if (err != null) {
  1148. return callback(err)
  1149. }
  1150. if (response.statusCode !== 204) {
  1151. return callback(
  1152. new Error(
  1153. `update collaborator access failed: status=${
  1154. response.statusCode
  1155. } body=${JSON.stringify(body)}`
  1156. )
  1157. )
  1158. }
  1159. callback()
  1160. }
  1161. )
  1162. })
  1163. }
  1164. }
  1165. User.promises = promisifyClass(User, {
  1166. without: ['setExtraAttributes', 'sessionCookie'],
  1167. })
  1168. User.promises.prototype.doRequest = async function (method, params) {
  1169. return new Promise((resolve, reject) => {
  1170. this.request[method.toLowerCase()](params, (err, response, body) => {
  1171. if (err) {
  1172. reject(err)
  1173. } else {
  1174. resolve({ response, body })
  1175. }
  1176. })
  1177. })
  1178. }
  1179. module.exports = User