UserPagesController.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. const UserGetter = require('./UserGetter')
  2. const OError = require('@overleaf/o-error')
  3. const UserSessionsManager = require('./UserSessionsManager')
  4. const logger = require('@overleaf/logger')
  5. const Settings = require('@overleaf/settings')
  6. const AuthenticationController = require('../Authentication/AuthenticationController')
  7. const SessionManager = require('../Authentication/SessionManager')
  8. const NewsletterManager = require('../Newsletter/NewsletterManager')
  9. const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
  10. const _ = require('lodash')
  11. const { expressify } = require('@overleaf/promise-utils')
  12. const Features = require('../../infrastructure/Features')
  13. const SplitTestHandler = require('../SplitTests/SplitTestHandler')
  14. const Modules = require('../../infrastructure/Modules')
  15. async function settingsPage(req, res) {
  16. const userId = SessionManager.getLoggedInUserId(req.session)
  17. const reconfirmationRemoveEmail = req.query.remove
  18. // SSO
  19. const ssoError = req.session.ssoError
  20. if (ssoError) {
  21. delete req.session.ssoError
  22. }
  23. const ssoErrorMessage = req.session.ssoErrorMessage
  24. if (ssoErrorMessage) {
  25. delete req.session.ssoErrorMessage
  26. }
  27. const projectSyncSuccessMessage = req.session.projectSyncSuccessMessage
  28. if (projectSyncSuccessMessage) {
  29. delete req.session.projectSyncSuccessMessage
  30. }
  31. // Institution SSO
  32. let institutionLinked = _.get(req.session, ['saml', 'linked'])
  33. if (institutionLinked) {
  34. // copy object if exists because _.get does not
  35. institutionLinked = Object.assign(
  36. {
  37. hasEntitlement: _.get(req.session, ['saml', 'hasEntitlement']),
  38. },
  39. institutionLinked
  40. )
  41. }
  42. const samlError = _.get(req.session, ['saml', 'error'])
  43. const institutionEmailNonCanonical = _.get(req.session, [
  44. 'saml',
  45. 'emailNonCanonical',
  46. ])
  47. const institutionRequestedEmail = _.get(req.session, [
  48. 'saml',
  49. 'requestedEmail',
  50. ])
  51. const reconfirmedViaSAML = _.get(req.session, ['saml', 'reconfirmed'])
  52. delete req.session.saml
  53. let shouldAllowEditingDetails = true
  54. if (Settings.ldap && Settings.ldap.updateUserDetailsOnLogin) {
  55. shouldAllowEditingDetails = false
  56. }
  57. if (Settings.saml && Settings.saml.updateUserDetailsOnLogin) {
  58. shouldAllowEditingDetails = false
  59. }
  60. const oauthProviders = Settings.oauthProviders || {}
  61. const user = await UserGetter.promises.getUser(userId)
  62. if (!user) {
  63. // The user has just deleted their account.
  64. return UserSessionsManager.revokeAllUserSessions({ _id: userId }, [], () =>
  65. res.redirect('/')
  66. )
  67. }
  68. const showPersonalAccessToken =
  69. !Features.hasFeature('saas') || req.query?.personal_access_token === 'true'
  70. // if not already enabled, use a split test to determine whether to offer personal access tokens
  71. let optionalPersonalAccessToken = false
  72. if (!showPersonalAccessToken) {
  73. const { variant } = await SplitTestHandler.promises.getAssignment(
  74. req,
  75. res,
  76. 'personal-access-token'
  77. )
  78. optionalPersonalAccessToken = variant === 'enabled' // `?personal-access-token=enabled`
  79. }
  80. // getAssignment sets res.locals, which will pass to the splitTest context
  81. await SplitTestHandler.promises.getAssignment(
  82. req,
  83. res,
  84. 'writefull-oauth-promotion'
  85. )
  86. let personalAccessTokens
  87. if (showPersonalAccessToken || optionalPersonalAccessToken) {
  88. try {
  89. // require this here because module may not be included in some versions
  90. const PersonalAccessTokenManager = require('../../../../modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager')
  91. personalAccessTokens = await PersonalAccessTokenManager.listTokens(
  92. user._id
  93. )
  94. } catch (error) {
  95. logger.error(OError.tag(error))
  96. }
  97. }
  98. let currentManagedUserAdminEmail
  99. try {
  100. currentManagedUserAdminEmail =
  101. await SubscriptionLocator.promises.getAdminEmail(req.managedBy)
  102. } catch (err) {
  103. logger.error({ err }, 'error getting subscription admin email')
  104. }
  105. const memberOfSSOEnabledGroups = []
  106. try {
  107. const memberOfGroups =
  108. await SubscriptionLocator.promises.getMemberSubscriptions(user._id)
  109. for (const group of memberOfGroups) {
  110. const hasSSOEnabled = (
  111. await Modules.promises.hooks.fire('hasGroupSSOEnabled', group)
  112. )?.[0]
  113. if (hasSSOEnabled) {
  114. const groupId = group._id.toString()
  115. memberOfSSOEnabledGroups.push({
  116. groupId,
  117. linked: user.enrollment?.sso?.some(
  118. sso => sso.groupId.toString() === groupId
  119. ),
  120. groupName: group.teamName,
  121. adminEmail: group.admin_id?.email,
  122. })
  123. }
  124. }
  125. } catch (error) {
  126. logger.error(
  127. { err: error },
  128. 'error fetching groups with Group SSO enabled the user may be member of'
  129. )
  130. }
  131. // Get the user's assignment for the Bootstrap 5 split test, which populates
  132. // splitTestVariants with a value for 'bootstrap-5' and allows Pug to read it
  133. await SplitTestHandler.promises.getAssignment(req, res, 'bootstrap-5')
  134. res.render('user/settings', {
  135. title: 'account_settings',
  136. user: {
  137. id: user._id,
  138. isAdmin: user.isAdmin,
  139. email: user.email,
  140. allowedFreeTrial: user.allowedFreeTrial,
  141. first_name: user.first_name,
  142. last_name: user.last_name,
  143. alphaProgram: user.alphaProgram,
  144. betaProgram: user.betaProgram,
  145. labsProgram: user.labsProgram,
  146. features: {
  147. dropbox: user.features.dropbox,
  148. github: user.features.github,
  149. mendeley: user.features.mendeley,
  150. zotero: user.features.zotero,
  151. references: user.features.references,
  152. },
  153. refProviders: {
  154. mendeley: Boolean(user.refProviders?.mendeley),
  155. zotero: Boolean(user.refProviders?.zotero),
  156. },
  157. writefull: {
  158. enabled: Boolean(user.writefull?.enabled),
  159. },
  160. },
  161. hasPassword: !!user.hashedPassword,
  162. shouldAllowEditingDetails,
  163. oauthProviders: UserPagesController._translateProviderDescriptions(
  164. oauthProviders,
  165. req
  166. ),
  167. institutionLinked,
  168. samlError,
  169. institutionEmailNonCanonical:
  170. institutionEmailNonCanonical && institutionRequestedEmail
  171. ? institutionEmailNonCanonical
  172. : undefined,
  173. reconfirmedViaSAML,
  174. reconfirmationRemoveEmail,
  175. samlBeta: req.session.samlBeta,
  176. ssoErrorMessage,
  177. thirdPartyIds: UserPagesController._restructureThirdPartyIds(user),
  178. projectSyncSuccessMessage,
  179. showPersonalAccessToken,
  180. optionalPersonalAccessToken,
  181. personalAccessTokens,
  182. emailAddressLimit: Settings.emailAddressLimit,
  183. isManagedAccount: !!req.managedBy,
  184. userRestrictions: Array.from(req.userRestrictions || []),
  185. currentManagedUserAdminEmail,
  186. gitBridgeEnabled: Settings.enableGitBridge,
  187. isSaas: Features.hasFeature('saas'),
  188. memberOfSSOEnabledGroups,
  189. })
  190. }
  191. async function accountSuspended(req, res) {
  192. if (SessionManager.isUserLoggedIn(req.session)) {
  193. return res.redirect('/project')
  194. }
  195. res.render('user/accountSuspended', {
  196. title: 'your_account_is_suspended',
  197. })
  198. }
  199. const UserPagesController = {
  200. accountSuspended: expressify(accountSuspended),
  201. registerPage(req, res) {
  202. const sharedProjectData = {
  203. project_name: req.query.project_name,
  204. user_first_name: req.query.user_first_name,
  205. }
  206. const newTemplateData = {}
  207. if (req.session.templateData != null) {
  208. newTemplateData.templateName = req.session.templateData.templateName
  209. }
  210. res.render('user/register', {
  211. title: 'register',
  212. sharedProjectData,
  213. newTemplateData,
  214. samlBeta: req.session.samlBeta,
  215. })
  216. },
  217. loginPage(req, res) {
  218. // if user is being sent to /login with explicit redirect (redir=/foo),
  219. // such as being sent from the editor to /login, then set the redirect explicitly
  220. if (
  221. req.query.redir != null &&
  222. AuthenticationController._getRedirectFromSession(req) == null
  223. ) {
  224. AuthenticationController.setRedirectInSession(req, req.query.redir)
  225. }
  226. res.render('user/login', {
  227. title: 'login',
  228. })
  229. },
  230. /**
  231. * Landing page for users who may have received one-time login
  232. * tokens from the read-only maintenance site.
  233. *
  234. * We tell them that Overleaf is back up and that they can login normally.
  235. */
  236. oneTimeLoginPage(req, res, next) {
  237. res.render('user/one_time_login')
  238. },
  239. renderReconfirmAccountPage(req, res) {
  240. const pageData = {
  241. reconfirm_email: req.session.reconfirm_email,
  242. }
  243. // when a user must reconfirm their account
  244. res.render('user/reconfirm', pageData)
  245. },
  246. settingsPage: expressify(settingsPage),
  247. sessionsPage(req, res, next) {
  248. const user = SessionManager.getSessionUser(req.session)
  249. logger.debug({ userId: user._id }, 'loading sessions page')
  250. const currentSession = {
  251. ip_address: user.ip_address,
  252. session_created: user.session_created,
  253. }
  254. UserSessionsManager.getAllUserSessions(
  255. user,
  256. [req.sessionID],
  257. (err, sessions) => {
  258. if (err != null) {
  259. OError.tag(err, 'error getting all user sessions', {
  260. userId: user._id,
  261. })
  262. return next(err)
  263. }
  264. res.render('user/sessions', {
  265. title: 'sessions',
  266. currentSession,
  267. sessions,
  268. })
  269. }
  270. )
  271. },
  272. emailPreferencesPage(req, res, next) {
  273. const userId = SessionManager.getLoggedInUserId(req.session)
  274. UserGetter.getUser(
  275. userId,
  276. { _id: 1, email: 1, first_name: 1, last_name: 1 },
  277. (err, user) => {
  278. if (err != null) {
  279. return next(err)
  280. }
  281. NewsletterManager.subscribed(user, (err, subscribed) => {
  282. if (err != null) {
  283. OError.tag(err, 'error getting newsletter subscription status')
  284. return next(err)
  285. }
  286. res.render('user/email-preferences', {
  287. title: 'newsletter_info_title',
  288. subscribed,
  289. })
  290. })
  291. }
  292. )
  293. },
  294. _restructureThirdPartyIds(user) {
  295. // 3rd party identifiers are an array of objects
  296. // this turn them into a single object, which
  297. // makes data easier to use in template
  298. if (
  299. !user.thirdPartyIdentifiers ||
  300. user.thirdPartyIdentifiers.length === 0
  301. ) {
  302. return null
  303. }
  304. return user.thirdPartyIdentifiers.reduce((obj, identifier) => {
  305. obj[identifier.providerId] = identifier.externalUserId
  306. return obj
  307. }, {})
  308. },
  309. _translateProviderDescriptions(providers, req) {
  310. const result = {}
  311. if (providers) {
  312. for (const provider in providers) {
  313. const data = providers[provider]
  314. data.description = req.i18n.translate(
  315. data.descriptionKey,
  316. Object.assign({}, data.descriptionOptions)
  317. )
  318. result[provider] = data
  319. }
  320. }
  321. return result
  322. },
  323. }
  324. module.exports = UserPagesController