Files
overleaf-cep/services/web/app/src/Features/User/UserPagesController.mjs
Rebeka Dekany 69bc8a135b Bootstrap 3 cleanup from the IDE page - #2 (#24175)
* Remove skipLoadingStyleSheet

* Remove unused bootstrap-5 assignment from the Account settings page since it's archived

* Remove bsVersionIcon

* Remove bsVersion, bootstrapVersion and isBootstrap5 from elements on the IDE page

* Remove BS3Dropdown from the context menu

* Cleanup Bootstrap 3 related comment and type

GitOrigin-RevId: a67244eb78943ee84cc5f89bae164c0361e8fc13
2025-03-11 09:05:00 +00:00

353 lines
10 KiB
JavaScript

import UserGetter from './UserGetter.js'
import OError from '@overleaf/o-error'
import UserSessionsManager from './UserSessionsManager.js'
import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import AuthenticationController from '../Authentication/AuthenticationController.js'
import SessionManager from '../Authentication/SessionManager.js'
import NewsletterManager from '../Newsletter/NewsletterManager.js'
import SubscriptionLocator from '../Subscription/SubscriptionLocator.js'
import _ from 'lodash'
import { expressify } from '@overleaf/promise-utils'
import Features from '../../infrastructure/Features.js'
import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
import Modules from '../../infrastructure/Modules.js'
async function settingsPage(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session)
const reconfirmationRemoveEmail = req.query.remove
// SSO
const ssoError = req.session.ssoError
if (ssoError) {
delete req.session.ssoError
}
const ssoErrorMessage = req.session.ssoErrorMessage
if (ssoErrorMessage) {
delete req.session.ssoErrorMessage
}
const projectSyncSuccessMessage = req.session.projectSyncSuccessMessage
if (projectSyncSuccessMessage) {
delete req.session.projectSyncSuccessMessage
}
// Institution SSO
let institutionLinked = _.get(req.session, ['saml', 'linked'])
if (institutionLinked) {
// copy object if exists because _.get does not
institutionLinked = Object.assign(
{
hasEntitlement: _.get(req.session, ['saml', 'hasEntitlement']),
},
institutionLinked
)
}
const samlError = _.get(req.session, ['saml', 'error'])
const institutionEmailNonCanonical = _.get(req.session, [
'saml',
'emailNonCanonical',
])
const institutionRequestedEmail = _.get(req.session, [
'saml',
'requestedEmail',
])
const reconfirmedViaSAML = _.get(req.session, ['saml', 'reconfirmed'])
delete req.session.saml
let shouldAllowEditingDetails = true
if (Settings.ldap && Settings.ldap.updateUserDetailsOnLogin) {
shouldAllowEditingDetails = false
}
if (Settings.saml && Settings.saml.updateUserDetailsOnLogin) {
shouldAllowEditingDetails = false
}
const oauthProviders = Settings.oauthProviders || {}
const user = await UserGetter.promises.getUser(userId)
if (!user) {
// The user has just deleted their account.
return UserSessionsManager.removeSessionsFromRedis(
{ _id: userId },
null,
() => res.redirect('/')
)
}
let personalAccessTokens
try {
const results = await Modules.promises.hooks.fire(
'listPersonalAccessTokens',
user._id
)
personalAccessTokens = results?.[0] ?? []
} catch (error) {
const err = OError.tag(error, 'listPersonalAccessTokens hook failed')
logger.error({ err, userId }, err.message)
}
let currentManagedUserAdminEmail
try {
currentManagedUserAdminEmail =
await SubscriptionLocator.promises.getAdminEmail(req.managedBy)
} catch (err) {
logger.error({ err }, 'error getting subscription admin email')
}
let memberOfSSOEnabledGroups = []
try {
memberOfSSOEnabledGroups =
(
await Modules.promises.hooks.fire(
'getUserGroupsSSOEnrollmentStatus',
user._id,
{ teamName: 1 },
['email']
)
)?.[0] || []
memberOfSSOEnabledGroups = memberOfSSOEnabledGroups.map(group => {
return {
groupId: group._id.toString(),
linked: group.linked,
groupName: group.teamName,
adminEmail: group.admin_id?.email,
}
})
} catch (error) {
logger.error(
{ err: error },
'error fetching groups with Group SSO enabled the user may be member of'
)
}
// Get the users write-and-cite assignment to switch between translation strings
await SplitTestHandler.promises.getAssignment(req, res, 'write-and-cite')
// Get the users papers-integration assignment to show the linking widget
await SplitTestHandler.promises.getAssignment(req, res, 'papers-integration')
res.render('user/settings', {
title: 'account_settings',
user: {
id: user._id,
isAdmin: user.isAdmin,
email: user.email,
allowedFreeTrial: user.allowedFreeTrial,
first_name: user.first_name,
last_name: user.last_name,
alphaProgram: user.alphaProgram,
betaProgram: user.betaProgram,
labsProgram: user.labsProgram,
features: {
dropbox: user.features.dropbox,
github: user.features.github,
mendeley: user.features.mendeley,
zotero: user.features.zotero,
papers: user.features.papers,
references: user.features.references,
},
refProviders: {
mendeley: Boolean(user.refProviders?.mendeley),
zotero: Boolean(user.refProviders?.zotero),
papers: Boolean(user.refProviders?.papers),
},
writefull: {
enabled: Boolean(user.writefull?.enabled),
},
aiErrorAssistant: {
enabled: Boolean(user.aiErrorAssistant?.enabled),
},
},
hasPassword: !!user.hashedPassword,
shouldAllowEditingDetails,
oauthProviders: UserPagesController._translateProviderDescriptions(
oauthProviders,
req
),
institutionLinked,
samlError,
institutionEmailNonCanonical:
institutionEmailNonCanonical && institutionRequestedEmail
? institutionEmailNonCanonical
: undefined,
reconfirmedViaSAML,
reconfirmationRemoveEmail,
samlBeta: req.session.samlBeta,
ssoErrorMessage,
thirdPartyIds: UserPagesController._restructureThirdPartyIds(user),
projectSyncSuccessMessage,
personalAccessTokens,
emailAddressLimit: Settings.emailAddressLimit,
isManagedAccount: !!req.managedBy,
userRestrictions: Array.from(req.userRestrictions || []),
currentManagedUserAdminEmail,
gitBridgeEnabled: Settings.enableGitBridge,
isSaas: Features.hasFeature('saas'),
memberOfSSOEnabledGroups,
})
}
async function accountSuspended(req, res) {
if (SessionManager.isUserLoggedIn(req.session)) {
return res.redirect('/project')
}
res.render('user/accountSuspended', {
title: 'your_account_is_suspended',
})
}
async function reconfirmAccountPage(req, res) {
const pageData = {
reconfirm_email: req.session.reconfirm_email,
}
const { variant } = await SplitTestHandler.promises.getAssignment(
req,
res,
'auth-pages-bs5'
)
const template =
variant === 'enabled' ? 'user/reconfirm-bs5' : 'user/reconfirm'
res.render(template, pageData)
}
const UserPagesController = {
accountSuspended: expressify(accountSuspended),
registerPage(req, res) {
const sharedProjectData = req.session.sharedProjectData || {}
const newTemplateData = {}
if (req.session.templateData != null) {
newTemplateData.templateName = req.session.templateData.templateName
}
res.render('user/register', {
title: 'register',
sharedProjectData,
newTemplateData,
samlBeta: req.session.samlBeta,
})
},
loginPage(req, res) {
// if user is being sent to /login with explicit redirect (redir=/foo),
// such as being sent from the editor to /login, then set the redirect explicitly
if (
req.query.redir != null &&
AuthenticationController.getRedirectFromSession(req) == null
) {
AuthenticationController.setRedirectInSession(req, req.query.redir)
}
res.render('user/login', {
title: Settings.nav?.login_support_title || 'login',
login_support_text: Settings.nav?.login_support_text,
})
},
/**
* Landing page for users who may have received one-time login
* tokens from the read-only maintenance site.
*
* We tell them that Overleaf is back up and that they can login normally.
*/
oneTimeLoginPage(req, res, next) {
res.render('user/one_time_login')
},
renderReconfirmAccountPage: expressify(reconfirmAccountPage),
settingsPage: expressify(settingsPage),
sessionsPage(req, res, next) {
const user = SessionManager.getSessionUser(req.session)
logger.debug({ userId: user._id }, 'loading sessions page')
const currentSession = {
ip_address: user.ip_address,
session_created: user.session_created,
}
UserSessionsManager.getAllUserSessions(
user,
[req.sessionID],
(err, sessions) => {
if (err != null) {
OError.tag(err, 'error getting all user sessions', {
userId: user._id,
})
return next(err)
}
res.render('user/sessions', {
title: 'sessions',
currentSession,
sessions,
})
}
)
},
emailPreferencesPage(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
UserGetter.getUser(
userId,
{ _id: 1, email: 1, first_name: 1, last_name: 1 },
(err, user) => {
if (err != null) {
return next(err)
}
NewsletterManager.subscribed(user, (err, subscribed) => {
if (err != null) {
OError.tag(err, 'error getting newsletter subscription status')
return next(err)
}
res.render('user/email-preferences', {
title: 'newsletter_info_title',
subscribed,
})
})
}
)
},
async compromisedPasswordPage(req, res) {
// Populates splitTestVariants with a value for the split test name and allows
// Pug to read it
await SplitTestHandler.promises.getAssignment(
req,
res,
'bs5-misc-pages-platform'
)
res.render('user/compromised_password')
},
_restructureThirdPartyIds(user) {
// 3rd party identifiers are an array of objects
// this turn them into a single object, which
// makes data easier to use in template
if (
!user.thirdPartyIdentifiers ||
user.thirdPartyIdentifiers.length === 0
) {
return null
}
return user.thirdPartyIdentifiers.reduce((obj, identifier) => {
obj[identifier.providerId] = identifier.externalUserId
return obj
}, {})
},
_translateProviderDescriptions(providers, req) {
const result = {}
if (providers) {
for (const provider in providers) {
const data = providers[provider]
data.description = req.i18n.translate(
data.descriptionKey,
Object.assign({}, data.descriptionOptions)
)
result[provider] = data
}
}
return result
},
}
export default UserPagesController