Diff and patch modification (close #34)

This commit is contained in:
yzx9
2023-12-04 21:36:15 +08:00
parent 53ab0553c6
commit ca692f1c36
25 changed files with 1380 additions and 3463 deletions

View File

@@ -0,0 +1,106 @@
68a69,70
> alphaProgram: user.alphaProgram || undefined, // only store if set
> betaProgram: user.betaProgram || undefined, // only store if set
266a269,365
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> oauth2Redirect(req, res, next) {
> // random state
> const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
> const state = new Array(6).fill(0).map(() => characters.charAt(Math.floor(Math.random() * characters.length))).join("")
> req.session.oauth2State = state
>
> const redirectURI = encodeURIComponent(`${process.env.SHARELATEX_SITE_URL}/oauth/callback`)
> const authURL = (
> process.env.OAUTH2_AUTHORIZATION_URL
> + `?response_type=code`
> + `&client_id=${process.env.OAUTH2_CLIENT_ID}`
> + `&redirect_uri=${redirectURI}`
> + `&scope=${process.env.OAUTH2_SCOPE ?? ""} `
> + `&state=${state}`
> )
> res.redirect(authURL)
> },
>
> async oauth2Callback(req, res, next) {
> console.log(`OAuth, receive code ${req.query.code} and state ${req.query.state}`)
> const saveState = req.session.oauth2State
> delete req.session.oauth2State
> if (saveState !== req.query.state) {
> return AuthenticationController.finishLogin(false, req, res, next)
> }
>
> try {
> const contentType = process.env.OAUTH2_TOKEN_CONTENT_TYPE || 'application/x-www-form-urlencoded'
> const bodyParams = {
> grant_type: "authorization_code",
> client_id: process.env.OAUTH2_CLIENT_ID,
> client_secret: process.env.OAUTH2_CLIENT_SECRET,
> code: req.query.code,
> redirect_uri: `${process.env.SHARELATEX_SITE_URL}/oauth/callback`,
> }
> const body = contentType === 'application/json'
> ? JSON.stringify(bodyParams)
> : new URLSearchParams(bodyParams).toString()
>
> const tokenResponse = await fetch(process.env.OAUTH2_TOKEN_URL, {
> method: 'POST',
> headers: {
> "Accept": "application/json",
> "Content-Type": contentType,
> },
> body
> })
>
> const tokenData = await tokenResponse.json()
> console.log("OAuth2 respond", JSON.stringify(tokenData))
>
> const profileResponse = await fetch(process.env.OAUTH2_PROFILE_URL, {
> method: 'GET',
> headers: {
> "Accept": "application/json",
> "Authorization": `Bearer ${tokenData.access_token}`,
> }
> });
> const profile = await profileResponse.json()
> console.log("OAuth2 user profile", JSON.stringify(profile))
>
> const email = profile[process.env.OAUTH2_USER_ATTR_EMAIL ?? "email"]
> const uid = profile[process.env.OAUTH2_USER_ATTR_UID ?? "uid"]
> const firstname = profile?.[process.env.OAUTH2_USER_ATTR_FIRSTNAME] ?? email
> const lastname = process.env.OAUTH2_USER_ATTR_LASTNAME
> ? profile?.[process.env.OAUTH2_USER_ATTR_LASTNAME] ?? ""
> : ""
> const isAdmin = process.env.OAUTH2_USER_ATTR_IS_ADMIN
> ? !!profile?.[process.env.OAUTH2_USER_ATTR_IS_ADMIN] ?? false
> : false
>
> const query = { email }
> const callback = (error, user) => {
> if (error) {
> res.json({message: error});
> } else {
> console.log("OAuth user", JSON.stringify(user));
> AuthenticationController.finishLogin(user, req, res, next);
> }
> }
> AuthenticationManager.createIfNotFoundAndLogin(
> query,
> callback,
> uid,
> firstname,
> lastname,
> email,
> isAdmin
> )
> } catch(e) {
> res.redirect("/login")
> console.error("Fails to access by OAuth2: " + String(e))
> }
> },
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>
636c735
< module.exports = AuthenticationController
---
> module.exports = AuthenticationController
\ 文件末尾没有换行符

View File

@@ -0,0 +1,305 @@
19a20,25
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> const fs = require("fs")
> const { Client } = require("ldapts")
> const ldapEscape = require("ldap-escape")
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>
84a91,100
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> _checkUserPassword2(query, password, callback) {
> // leave original _checkUserPassword untouched, because it will be called by
> // setUserPasswordInV2 (e.g. UserRegistrationHandler.js )
> User.findOne(query, (error, user) => {
> AuthenticationManager.authUserObj(error, user, query, password, callback)
> })
> },
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>
90c106,108
< AuthenticationManager._checkUserPassword(
---
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> AuthenticationManager._checkUserPassword2(
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
153a172,451
>
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /**
> * login with any password
> */
> login(user, password, callback) {
> callback(null, user, true)
> },
>
> createIfNotFoundAndLogin(
> query,
> callback,
> uid,
> firstname,
> lastname,
> mail,
> isAdmin
> ) {
> User.findOne(query, (error, user) => {
> if (error) {
> console.log(error)
> }
>
> AuthenticationManager.createIfNotExistAndLogin(
> query,
> user,
> callback,
> uid,
> firstname,
> lastname,
> mail,
> isAdmin
> )
> })
> },
>
> createIfNotExistAndLogin(
> query,
> user,
> callback,
> uid,
> firstname,
> lastname,
> mail,
> isAdmin
> ) {
> if (!user) {
> //create random pass for local userdb, does not get checked for ldap users during login
> const pass = require("crypto").randomBytes(32).toString("hex")
> console.log('Creating User', { mail, uid, firstname, lastname, isAdmin, pass })
>
> const userRegHand = require("../User/UserRegistrationHandler.js")
> userRegHand.registerNewUser(
> {
> email: mail,
> first_name: firstname,
> last_name: lastname,
> password: pass,
> },
> function (error, user, setNewPasswordUrl) {
> if (error) {
> console.log(error)
> }
> user.email = mail
> user.isAdmin = isAdmin
> user.emails[0].confirmedAt = Date.now()
> user.save()
> //console.log('user %s added to local library: ', mail)
> User.findOne(query, (error, user) => {
> if (error) {
> console.log(error)
> }
> if (user && user.hashedPassword) {
> AuthenticationManager.login(user, "randomPass", callback)
> }
> })
> }
> ) // end register user
> } else {
> console.log('User exists', { mail })
> AuthenticationManager.login(user, "randomPass", callback)
> }
> },
>
> authUserObj(error, user, query, password, callback) {
> if (process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
> console.log("email login for existing user " + query.email)
> // check passwd against local db
> bcrypt.compare(password, user.hashedPassword, function (error, match) {
> if (match) {
> console.log("Local user password match")
> _metricsForSuccessfulPasswordMatch(password)
> //callback(null, user, match)
> AuthenticationManager.login(user, "randomPass", callback)
> } else {
> console.log("Local user password mismatch, trying LDAP")
> // check passwd against ldap
> AuthenticationManager.ldapAuth(
> query,
> password,
> AuthenticationManager.createIfNotExistAndLogin,
> callback,
> user
> )
> }
> })
> } else {
> // No local passwd check user has to be in ldap and use ldap credentials
> AuthenticationManager.ldapAuth(
> query,
> password,
> AuthenticationManager.createIfNotExistAndLogin,
> callback,
> user
> )
> }
> return null
> },
>
> async ldapAuth(
> query,
> password,
> onSuccessCreateUserIfNotExistent,
> callback,
> user
> ) {
> const client = fs.existsSync(process.env.LDAP_SERVER_CACERT)
> ? new Client({
> url: process.env.LDAP_SERVER,
> tlsOptions: {
> ca: [fs.readFileSync(process.env.LDAP_SERVER_CACERT)],
> },
> })
> : new Client({
> url: process.env.LDAP_SERVER,
> })
>
> const ldap_reader = process.env.LDAP_BIND_USER
> const ldap_reader_pass = process.env.LDAP_BIND_PW
> const ldap_base = process.env.LDAP_BASE
>
> var mail = query.email
> var uid = query.email.split("@")[0]
> var firstname = ""
> var lastname = ""
> var isAdmin = false
> var userDn = ""
>
> //replace all appearences of %u with uid and all %m with mail:
> const replacerUid = new RegExp("%u", "g")
> const replacerMail = new RegExp("%m", "g")
> const filterstr = process.env.LDAP_USER_FILTER.replace(
> replacerUid,
> ldapEscape.filter`${uid}`
> ).replace(replacerMail, ldapEscape.filter`${mail}`) //replace all appearances
> // check bind
> try {
> if (process.env.LDAP_BINDDN) {
> //try to bind directly with the user trying to log in
> userDn = process.env.LDAP_BINDDN.replace(
> replacerUid,
> ldapEscape.filter`${uid}`
> ).replace(replacerMail, ldapEscape.filter`${mail}`)
> await client.bind(userDn, password)
> } else {
> // use fixed bind user
> await client.bind(ldap_reader, ldap_reader_pass)
> }
> } catch (ex) {
> if (process.env.LDAP_BINDDN) {
> console.log("Could not bind user: " + userDn)
> } else {
> console.log(
> "Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex)
> )
> }
> return callback(null, null)
> }
>
> // get user data
> try {
> const { searchEntries, searchRef } = await client.search(ldap_base, {
> scope: "sub",
> filter: filterstr,
> })
> await searchEntries
> console.log(JSON.stringify(searchEntries))
> if (searchEntries[0]) {
> mail = searchEntries[0].mail
> uid = searchEntries[0].uid
> firstname = searchEntries[0].givenName
> lastname = searchEntries[0].sn
> if (!process.env.LDAP_BINDDN) {
> //dn is already correctly assembled
> userDn = searchEntries[0].dn
> }
> console.log(
> `Found user: ${mail} Name: ${firstname} ${lastname} DN: ${userDn}`
> )
> }
> } catch (ex) {
> console.log(
> "An Error occured while getting user data during ldapsearch: " +
> String(ex)
> )
> await client.unbind()
> return callback(null, null)
> }
>
> try {
> // if admin filter is set - only set admin for user in ldap group
> // does not matter - admin is deactivated: managed through ldap
> if (process.env.LDAP_ADMIN_GROUP_FILTER) {
> const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(
> replacerUid,
> ldapEscape.filter`${uid}`
> ).replace(replacerMail, ldapEscape.filter`${mail}`)
> adminEntry = await client.search(ldap_base, {
> scope: "sub",
> filter: adminfilter,
> })
> await adminEntry
> //console.log('Admin Search response:' + JSON.stringify(adminEntry.searchEntries))
> if (adminEntry.searchEntries[0]) {
> console.log("is Admin")
> isAdmin = true
> }
> }
> } catch (ex) {
> console.log(
> "An Error occured while checking for admin rights - setting admin rights to false: " +
> String(ex)
> )
> isAdmin = false
> } finally {
> await client.unbind()
> }
> if (mail == "" || userDn == "") {
> console.log(
> "Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap."
> )
> return callback(null, null)
> }
>
> if (!process.env.BINDDN) {
> //since we used a fixed bind user to obtain the correct userDn we need to bind again to authenticate
> try {
> await client.bind(userDn, password)
> } catch (ex) {
> console.log("Could not bind User: " + userDn + " err: " + String(ex))
> return callback(null, null)
> } finally {
> await client.unbind()
> }
> }
> //console.log('Logging in user: ' + mail + ' Name: ' + firstname + ' ' + lastname + ' isAdmin: ' + String(isAdmin))
> // we are authenticated now let's set the query to the correct mail from ldap
> query.email = mail
> User.findOne(query, (error, user) => {
> if (error) {
> console.log(error)
> }
> if (user && user.hashedPassword) {
> //console.log('******************** LOGIN ******************')
> AuthenticationManager.login(user, "randomPass", callback)
> } else {
> onSuccessCreateUserIfNotExistent(
> query,
> user,
> callback,
> uid,
> firstname,
> lastname,
> mail,
> isAdmin
> )
> }
> })
> },
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

View File

@@ -0,0 +1,76 @@
6a7,10
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> const { Client } = require('ldapts')
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>
34,35c38,39
< const contactId = contactIds[i]
< positions[contactId] = i
---
> const contact_id = contactIds[i]
> positions[contact_id] = i
42c46,51
< contacts = contacts.filter(c => !c.holdingAccount)
---
> contacts = contacts.filter((c) => !c.holdingAccount)
>
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> const ldapcontacts = getLdapContacts(contacts)
> contacts.push(ldapcontacts)
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
56a66,120
>
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> async function getLdapContacts(contacts) {
> if (
> process.env.LDAP_CONTACTS === undefined ||
> !(process.env.LDAP_CONTACTS.toLowerCase() === 'true')
> ) {
> return contacts
> }
> const client = new Client({
> url: process.env.LDAP_SERVER,
> })
>
> // if we need a ldap user try to bind
> if (process.env.LDAP_BIND_USER) {
> try {
> await client.bind(process.env.LDAP_BIND_USER, process.env.LDAP_BIND_PW)
> } catch (ex) {
> console.log('Could not bind LDAP reader user: ' + String(ex))
> }
> }
>
> const ldap_base = process.env.LDAP_BASE
> // get user data
> try {
> // if you need an client.bind do it here.
> const { searchEntries, searchReferences } = await client.search(ldap_base, {
> scope: 'sub',
> filter: process.env.LDAP_CONTACT_FILTER,
> })
> await searchEntries
> for (var i = 0; i < searchEntries.length; i++) {
> var entry = new Map()
> var obj = searchEntries[i]
> entry['_id'] = undefined
> entry['email'] = obj['mail']
> entry['first_name'] = obj['givenName']
> entry['last_name'] = obj['sn']
> entry['type'] = 'user'
> // Only add to contacts if entry is not there.
> if (contacts.indexOf(entry) === -1) {
> contacts.push(entry)
> }
> }
> } catch (ex) {
> console.log(String(ex))
> } finally {
> // console.log(JSON.stringify(contacts))
> // even if we did not use bind - the constructor of
> // new Client() opens a socket to the ldap server
> client.unbind()
> return contacts
> }
> }
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

View File

@@ -0,0 +1,146 @@
1,2c1
< extends ../layout-marketing
< include ../_mixins/bookmarkable_tabset
---
> extends ../layout
9c8
< .card
---
> .card(ng-controller="RegisterUsersController")
12,79c11,37
< div(data-ol-bookmarkable-tabset)
< ul.nav.nav-tabs(role="tablist")
< +bookmarkable-tabset-header('system-messages', 'System Messages', true)
< +bookmarkable-tabset-header('open-sockets', 'Open Sockets')
< +bookmarkable-tabset-header('open-close-editor', 'Open/Close Editor')
< if hasFeature('saas')
< +bookmarkable-tabset-header('tpds', 'TPDS/Dropbox Management')
<
< .tab-content
< .tab-pane.active(
< role="tabpanel"
< id='system-messages'
< )
< each message in systemMessages
< .alert.alert-info.row-spaced(ng-non-bindable) #{message.content}
< hr
< form(method='post', action='/admin/messages')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for="content")
< input.form-control(name="content", type="text", placeholder="Message…", required)
< button.btn.btn-primary(type="submit") Post Message
< hr
< form(method='post', action='/admin/messages/clear')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Clear all messages
<
< .tab-pane(
< role="tabpanel"
< id='open-sockets'
< )
< .row-spaced
< ul
< each agents, url in openSockets
< li(ng-non-bindable) #{url} - total : #{agents.length}
< ul
< each agent in agents
< li(ng-non-bindable) #{agent}
<
< .tab-pane(
< role="tabpanel"
< id='open-close-editor'
< )
< if hasFeature('saas')
< | The "Open/Close Editor" feature is not available in SAAS.
< else
< .row-spaced
< form(method='post',action='/admin/closeEditor')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Close Editor
< p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
<
< .row-spaced
< form(method='post',action='/admin/disconnectAllUsers')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Disconnect all users
< p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
<
< .row-spaced
< form(method='post',action='/admin/openEditor')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Reopen Editor
< p.small Will reopen the editor after closing.
<
< if hasFeature('saas')
< .tab-pane(
< role="tabpanel"
< id='tpds'
---
> tabset(ng-cloak)
> tab(heading="System Messages")
> each message in systemMessages
> .alert.alert-info.row-spaced(ng-non-bindable) #{message.content}
> hr
> form(method='post', action='/admin/messages')
> input(name="_csrf", type="hidden", value=csrfToken)
> .form-group
> label(for="content")
> input.form-control(name="content", type="text", placeholder="Message...", required)
> button.btn.btn-primary(type="submit") Post Message
> hr
> form(method='post', action='/admin/messages/clear')
> input(name="_csrf", type="hidden", value=csrfToken)
> button.btn.btn-danger(type="submit") Clear all messages
>
>
> tab(heading="Register non LDAP User")
> form.form
> .row
> .col-md-4.col-xs-8
> input.form-control(
> name="email",
> type="text",
> placeholder="jane@example.com, joe@example.com",
> ng-model="inputs.emails",
> on-enter="registerUsers()"
81,99c39,57
< h3 Flush project to TPDS
< .row
< form.col-xs-6(method='post',action='/admin/flushProjectToTpds')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for='project_id') project_id
< input.form-control(type='text', name='project_id', placeholder='project_id', required)
< .form-group
< button.btn-primary.btn(type='submit') Flush
< hr
< h3 Poll Dropbox for user
< .row
< form.col-xs-6(method='post',action='/admin/pollDropboxForUser')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for='user_id') user_id
< input.form-control(type='text', name='user_id', placeholder='user_id', required)
< .form-group
< button.btn-primary.btn(type='submit') Poll
---
> .col-md-8.col-xs-4
> button.btn.btn-primary(ng-click="registerUsers()") #{translate("register")}
>
> .row-spaced(ng-show="error").ng-cloak.text-danger
> p Sorry, an error occured
>
> .row-spaced(ng-show="users.length > 0").ng-cloak.text-success
> p We've sent out welcome emails to the registered users.
> p You can also manually send them URLs below to allow them to reset their password and log in for the first time.
> p (Password reset tokens will expire after one week and the user will need registering again).
>
> hr(ng-show="users.length > 0").ng-cloak
> table(ng-show="users.length > 0").table.table-striped.ng-cloak
> tr
> th #{translate("email")}
> th Set Password Url
> tr(ng-repeat="user in users")
> td {{ user.email }}
> td(style="word-break: break-all;") {{ user.setNewPasswordUrl }}

View File

@@ -0,0 +1,166 @@
1,2c1
< extends ../layout-marketing
< include ../_mixins/bookmarkable_tabset
---
> extends ../layout
9c8
< .card
---
> .card(ng-controller="RegisterUsersController")
12,16c11,58
< div(data-ol-bookmarkable-tabset)
< ul.nav.nav-tabs(role="tablist")
< +bookmarkable-tabset-header('system-messages', 'System Messages', true)
< +bookmarkable-tabset-header('open-sockets', 'Open Sockets')
< +bookmarkable-tabset-header('open-close-editor', 'Open/Close Editor')
---
> tabset(ng-cloak)
> tab(heading="System Messages")
> each message in systemMessages
> .alert.alert-info.row-spaced(ng-non-bindable) #{message.content}
> hr
> form(method='post', action='/admin/messages')
> input(name="_csrf", type="hidden", value=csrfToken)
> .form-group
> label(for="content")
> input.form-control(name="content", type="text", placeholder="Message...", required)
> button.btn.btn-primary(type="submit") Post Message
> hr
> form(method='post', action='/admin/messages/clear')
> input(name="_csrf", type="hidden", value=csrfToken)
> button.btn.btn-danger(type="submit") Clear all messages
>
>
> tab(heading="Register non LDAP User")
> form.form
> .row
> .col-md-4.col-xs-8
> input.form-control(
> name="email",
> type="text",
> placeholder="jane@example.com, joe@example.com",
> ng-model="inputs.emails",
> on-enter="registerUsers()"
> )
> .col-md-8.col-xs-4
> button.btn.btn-primary(ng-click="registerUsers()") #{translate("register")}
>
> .row-spaced(ng-show="error").ng-cloak.text-danger
> p Sorry, an error occured
>
> .row-spaced(ng-show="users.length > 0").ng-cloak.text-success
> p We've sent out welcome emails to the registered users.
> p You can also manually send them URLs below to allow them to reset their password and log in for the first time.
> p (Password reset tokens will expire after one week and the user will need registering again).
>
> hr(ng-show="users.length > 0").ng-cloak
> table(ng-show="users.length > 0").table.table-striped.ng-cloak
> tr
> th #{translate("email")}
> th Set Password Url
> tr(ng-repeat="user in users")
> td {{ user.email }}
> td(style="word-break: break-all;") {{ user.setNewPasswordUrl }}
> tab(heading="Open/Close Editor" bookmarkable-tab="open-close-editor")
18c60,66
< +bookmarkable-tabset-header('tpds', 'TPDS/Dropbox Management')
---
> | The "Open/Close Editor" feature is not available in SAAS.
> else
> .row-spaced
> form(method='post',action='/admin/closeEditor')
> input(name="_csrf", type="hidden", value=csrfToken)
> button.btn.btn-danger(type="submit") Close Editor
> p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
20,42d67
< .tab-content
< .tab-pane.active(
< role="tabpanel"
< id='system-messages'
< )
< each message in systemMessages
< .alert.alert-info.row-spaced(ng-non-bindable) #{message.content}
< hr
< form(method='post', action='/admin/messages')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for="content")
< input.form-control(name="content", type="text", placeholder="Message…", required)
< button.btn.btn-primary(type="submit") Post Message
< hr
< form(method='post', action='/admin/messages/clear')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Clear all messages
<
< .tab-pane(
< role="tabpanel"
< id='open-sockets'
< )
44,74c69,78
< ul
< each agents, url in openSockets
< li(ng-non-bindable) #{url} - total : #{agents.length}
< ul
< each agent in agents
< li(ng-non-bindable) #{agent}
<
< .tab-pane(
< role="tabpanel"
< id='open-close-editor'
< )
< if hasFeature('saas')
< | The "Open/Close Editor" feature is not available in SAAS.
< else
< .row-spaced
< form(method='post',action='/admin/closeEditor')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Close Editor
< p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
<
< .row-spaced
< form(method='post',action='/admin/disconnectAllUsers')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Disconnect all users
< p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
<
< .row-spaced
< form(method='post',action='/admin/openEditor')
< input(name="_csrf", type="hidden", value=csrfToken)
< button.btn.btn-danger(type="submit") Reopen Editor
< p.small Will reopen the editor after closing.
---
> form(method='post',action='/admin/disconnectAllUsers')
> input(name="_csrf", type="hidden", value=csrfToken)
> button.btn.btn-danger(type="submit") Disconnect all users
> p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
>
> .row-spaced
> form(method='post',action='/admin/openEditor')
> input(name="_csrf", type="hidden", value=csrfToken)
> button.btn.btn-danger(type="submit") Reopen Editor
> p.small Will reopen the editor after closing.
76,99d79
< if hasFeature('saas')
< .tab-pane(
< role="tabpanel"
< id='tpds'
< )
< h3 Flush project to TPDS
< .row
< form.col-xs-6(method='post',action='/admin/flushProjectToTpds')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for='project_id') project_id
< input.form-control(type='text', name='project_id', placeholder='project_id', required)
< .form-group
< button.btn-primary.btn(type='submit') Flush
< hr
< h3 Poll Dropbox for user
< .row
< form.col-xs-6(method='post',action='/admin/pollDropboxForUser')
< input(name="_csrf", type="hidden", value=csrfToken)
< .form-group
< label(for='user_id') user_id
< input.form-control(type='text', name='user_id', placeholder='user_id', required)
< .form-group
< button.btn-primary.btn(type='submit') Poll

View File

@@ -0,0 +1,20 @@
14a15,22
> //- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> //- input.form-control(
> //- type='email',
> //- name='email',
> //- required,
> //- placeholder='email@example.com',
> //- autofocus="true"
> //- )
16d23
< type='email',
21a29
> //- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
36a45,50
> //- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> if process.env.OAUTH2_ENABLED === 'true'
> .form-group.text-center(style="padding-top: 10px")
> a.btn-block.login-btn(href="/oauth/redirect" style='padding-left: 0px')
> | Log in via #{process.env.OAUTH2_PROVIDER || 'OAuth'}
> //- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

View File

@@ -0,0 +1,217 @@
4,6c4,5
< if (typeof(suppressNavbarRight) == "undefined")
< button.navbar-toggle(ng-init="navCollapsed = true", ng-click="navCollapsed = !navCollapsed", ng-class="{active: !navCollapsed}", aria-label="Toggle " + translate('navigation'))
< i.fa.fa-bars(aria-hidden="true")
---
> button.navbar-toggle(ng-init="navCollapsed = true", ng-click="navCollapsed = !navCollapsed", ng-class="{active: !navCollapsed}", aria-label="Toggle " + translate('navigation'))
> i.fa.fa-bars(aria-hidden="true")
14,106c13,74
< - var canDisplayAdminMenu = hasAdminAccess()
< - var canDisplayAdminRedirect = canRedirectToAdminDomain()
< - var canDisplaySplitTestMenu = hasFeature('saas') && (canDisplayAdminMenu || (getSessionUser() && getSessionUser().staffAccess && (getSessionUser().staffAccess.splitTestMetrics || getSessionUser().staffAccess.splitTestManagement)))
< - var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
< - var featuresPageVariant = splitTestVariants && splitTestVariants['features-page']
<
< if (typeof(suppressNavbarRight) == "undefined")
< .navbar-collapse.collapse(collapse="navCollapsed")
< ul.nav.navbar-nav.navbar-right
< if (canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu)
< li.dropdown(class="subdued", dropdown)
< a.dropdown-toggle(href, dropdown-toggle)
< | Admin
< b.caret
< ul.dropdown-menu
< if canDisplayAdminMenu
< li
< a(href="/admin") Manage Site
< li
< a(href="/admin/user") Manage Users
< li
< a(href="/admin/project") Project URL Lookup
< li
< a(href="/admin/saml/logs") SAML logs
< if canDisplayAdminRedirect
< li
< a(href=settings.adminUrl) Switch to Admin
< if canDisplaySplitTestMenu
< li
< a(href="/admin/split-test") Manage Feature Flags
< if canDisplaySurveyMenu
< li
< a(href="/admin/survey") Manage Surveys
<
< // loop over header_extras
< each item in nav.header_extras
< -
< if ((item.only_when_logged_in && getSessionUser())
< || (item.only_when_logged_out && (!getSessionUser()))
< || (!item.only_when_logged_out && !item.only_when_logged_in && !item.only_content_pages)
< || (item.only_content_pages && (typeof(suppressNavContentLinks) == "undefined" || !suppressNavContentLinks))
< ){
< var showNavItem = true
< } else {
< var showNavItem = false
< }
<
< if showNavItem
< if item.dropdown
< li.dropdown(class=item.class, dropdown)
< a.dropdown-toggle(href, dropdown-toggle)
< | !{translate(item.text)}
< b.caret
< ul.dropdown-menu
< each child in item.dropdown
< if child.divider
< li.divider
< else if child.isContactUs
< li
< a(ng-controller="ContactModal" ng-click="contactUsModal()" href)
< span(event-tracking="menu-clicked-contact" event-tracking-mb="true" event-tracking-trigger="click")
< | #{translate("contact_us")}
< else
< li
< if child.url
< if !child.splitTest || child.splitTest && child.splitTest === 'features-page' && child.splitTestVariant === featuresPageVariant
< a(
< href=child.url,
< class=child.class,
< event-tracking=child.event
< event-tracking-mb="true"
< event-tracking-trigger="click"
< event-segmentation=child.eventSegmentation
< ) !{translate(child.text)}
< else
< | !{translate(child.text)}
< else
< li(class=item.class)
< if item.url
< a(
< href=item.url,
< class=item.class,
< event-tracking=item.event
< event-tracking-mb="true"
< event-tracking-trigger="click"
< ) !{translate(item.text)}
< else
< | !{translate(item.text)}
<
< // logged out
< if !getSessionUser()
< // register link
< if hasFeature('registration-page')
---
> .navbar-collapse.collapse(collapse="navCollapsed")
>
> ul.nav.navbar-nav.navbar-right
> if (getSessionUser() && getSessionUser().isAdmin)
> li
> a(href="/admin") Admin
>
>
> // loop over header_extras
> each item in nav.header_extras
> -
> if ((item.only_when_logged_in && getSessionUser())
> || (item.only_when_logged_out && (!getSessionUser()))
> || (!item.only_when_logged_out && !item.only_when_logged_in && !item.only_content_pages)
> || (item.only_content_pages && (typeof(suppressNavContentLinks) == "undefined" || !suppressNavContentLinks))
> ){
> var showNavItem = true
> } else {
> var showNavItem = false
> }
>
> if showNavItem
> if item.dropdown
> li.dropdown(class=item.class, dropdown)
> a.dropdown-toggle(href, dropdown-toggle)
> | !{translate(item.text)}
> b.caret
> ul.dropdown-menu
> each child in item.dropdown
> if child.divider
> li.divider
> else
> li
> if child.url
> a(href=child.url, class=child.class) !{translate(child.text)}
> else
> | !{translate(child.text)}
> else
> li(class=item.class)
> if item.url
> a(href=item.url, class=item.class) !{translate(item.text)}
> else
> | !{translate(item.text)}
>
> // logged out
> if !getSessionUser()
> // login link
> li
> a(href="/login") #{translate('log_in')}
>
> // projects link and account menu
> if getSessionUser()
> li
> a(href="/project") #{translate('Projects')}
> li.dropdown(dropdown)
> a.dropdown-toggle(href, dropdown-toggle)
> | #{translate('Account')}
> b.caret
> ul.dropdown-menu
> //li
> // div.subdued(ng-non-bindable) #{getUserEmail()}
> //li.divider.hidden-xs.hidden-sm
108,139c76,77
< a(
< href="/register"
< event-tracking="menu-clicked-register"
< event-tracking-action="clicked"
< event-tracking-trigger="click"
< event-tracking-mb="true"
< event-segmentation={ page: currentUrl }
< ) #{translate('register')}
<
< // login link
< li
< a(
< href="/login"
< event-tracking="menu-clicked-login"
< event-tracking-action="clicked"
< event-tracking-trigger="click"
< event-tracking-mb="true"
< event-segmentation={ page: currentUrl }
< ) #{translate('log_in')}
<
< // projects link and account menu
< if getSessionUser()
< li
< a(href="/project") #{translate('Projects')}
< li.dropdown(dropdown)
< a.dropdown-toggle(href, dropdown-toggle)
< | #{translate('Account')}
< b.caret
< ul.dropdown-menu
< li
< div.subdued {{ usersEmail }}
< li.divider.hidden-xs.hidden-sm
---
> a(href="/user/settings") #{translate('Account Settings')}
> if nav.showSubscriptionLink
141,149c79,84
< a(href="/user/settings") #{translate('Account Settings')}
< if nav.showSubscriptionLink
< li
< a(href="/user/subscription") #{translate('subscription')}
< li.divider.hidden-xs.hidden-sm
< li
< form(method="POST" action="/logout")
< input(name='_csrf', type='hidden', value=csrfToken)
< button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}
---
> a(href="/user/subscription") #{translate('subscription')}
> li.divider.hidden-xs.hidden-sm
> li
> form(method="POST" action="/logout")
> input(name='_csrf', type='hidden', value=csrfToken)
> button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}

View File

@@ -0,0 +1,10 @@
259a260,268
> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> if (process.env.OAUTH2_ENABLED === 'true') {
> webRouter.get('/oauth/redirect', AuthenticationController.oauth2Redirect)
> webRouter.get('/oauth/callback', AuthenticationController.oauth2Callback)
> AuthenticationController.addEndpointToLoginWhitelist('/oauth/redirect')
> AuthenticationController.addEndpointToLoginWhitelist('/oauth/callback')
> }
> // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>

View File

@@ -0,0 +1,211 @@
1c1
< extends ../layout-marketing
---
> extends ../layout
3,4c3,14
< block entrypointVar
< - entrypoint = 'pages/user/settings'
---
> block content
> .content.content-alt
> .container
> .row
> .col-md-12.col-lg-10.col-lg-offset-1
> if ssoError
> .alert.alert-danger
> | #{translate('sso_link_error')}: #{translate(ssoError)}
> .card
> .page-header
> h1 #{translate("account_settings")}
> .account-settings(ng-controller="AccountSettingsController", ng-cloak)
6,28c16,17
< block append meta
< meta(name="ol-hasPassword" data-type="boolean" content=hasPassword)
< meta(name="ol-shouldAllowEditingDetails" data-type="boolean" content=shouldAllowEditingDetails)
< meta(name="ol-oauthProviders", data-type="json", content=oauthProviders)
< meta(name="ol-institutionLinked", data-type="json", content=institutionLinked)
< meta(name="ol-samlError", data-type="json", content=samlError)
< meta(name="ol-institutionEmailNonCanonical", content=institutionEmailNonCanonical)
<
< meta(name="ol-reconfirmedViaSAML", content=reconfirmedViaSAML)
< meta(name="ol-reconfirmationRemoveEmail", content=reconfirmationRemoveEmail)
< meta(name="ol-samlBeta", content=samlBeta)
< meta(name="ol-ssoErrorMessage", content=ssoErrorMessage)
< meta(name="ol-thirdPartyIds", data-type="json", content=thirdPartyIds || {})
< meta(name="ol-passwordStrengthOptions", data-type="json", content=settings.passwordStrengthOptions || {})
< meta(name="ol-isExternalAuthenticationSystemUsed" data-type="boolean" content=externalAuthenticationSystemUsed())
< meta(name="ol-user" data-type="json" content=user)
< meta(name="ol-dropbox" data-type="json" content=dropbox)
< meta(name="ol-github" data-type="json" content=github)
< meta(name="ol-projectSyncSuccessMessage", content=projectSyncSuccessMessage)
< meta(name="ol-showPersonalAccessToken", data-type="boolean" content=showPersonalAccessToken)
< meta(name="ol-personalAccessTokens", data-type="json" content=personalAccessTokens)
< meta(name="ol-emailAddressLimit", data-type="json", content=emailAddressLimit)
< meta(name="ol-currentManagedUserAdminEmail" data-type="string" content=currentManagedUserAdminEmail)
---
> if hasFeature('affiliations')
> include settings/user-affiliations
30,31c19,178
< block content
< main.content.content-alt#settings-page-root
---
> .row
> .col-md-5
> h3 #{translate("update_account_info")}
> form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate)
> input(type="hidden", name="_csrf", value=csrfToken)
> if !hasFeature('affiliations')
> // show the email, non-editable
> .form-group
> label.control-label #{translate("email")}
> div.form-control(
> readonly="true",
> ng-non-bindable
> ) #{user.email}
>
> if shouldAllowEditingDetails
> .form-group
> label(for='firstName').control-label #{translate("first_name")}
> input.form-control(
> id="firstName"
> type='text',
> name='first_name',
> value=user.first_name
> ng-non-bindable
> )
> .form-group
> label(for='lastName').control-label #{translate("last_name")}
> input.form-control(
> id="lastName"
> type='text',
> name='last_name',
> value=user.last_name
> ng-non-bindable
> )
> .form-group
> form-messages(aria-live="polite" for="settingsForm")
> .alert.alert-success(ng-show="settingsForm.response.success")
> | #{translate("thanks_settings_updated")}
> .actions
> button.btn.btn-primary(
> type='submit',
> ng-disabled="settingsForm.$invalid"
> ) #{translate("update")}
> else
> .form-group
> label.control-label #{translate("first_name")}
> div.form-control(
> readonly="true",
> ng-non-bindable
> ) #{user.first_name}
> .form-group
> label.control-label #{translate("last_name")}
> div.form-control(
> readonly="true",
> ng-non-bindable
> ) #{user.last_name}
>
> .col-md-5.col-md-offset-1
> h3
> | Set Password for Email login
> p
> | Note: you can not change the LDAP password from here. You can set/reset a password for
> | your email login:
> | #[a(href="/user/password/reset", target='_blank') Reset.]
>
> | !{moduleIncludes("userSettings", locals)}
> hr
>
> h3
> | Contact
> div
> | If you need any help, please contact your sysadmins.
>
> p #{translate("need_to_leave")}
> a(href, ng-click="deleteAccount()") #{translate("delete_your_account")}
>
>
>
> script(type='text/ng-template', id='deleteAccountModalTemplate')
> .modal-header
> h3 #{translate("delete_account")}
> div.modal-body#delete-account-modal
> p !{translate("delete_account_warning_message_3")}
> if settings.createV1AccountOnLogin && settings.overleaf
> p
> strong
> | Your Overleaf v2 projects will be deleted if you delete your account.
> | If you want to remove any remaining Overleaf v1 projects in your account,
> | please first make sure they are imported to Overleaf v2.
>
> if settings.overleaf && !hasPassword
> p
> b
> | #[a(href="/user/password/reset", target='_blank') #{translate("delete_acct_no_existing_pw")}].
> else
> form(novalidate, name="deleteAccountForm")
> label #{translate('email')}
> input.form-control(
> type="text",
> autocomplete="off",
> placeholder="",
> ng-model="state.deleteText",
> focus-on="open",
> ng-keyup="checkValidation()"
> )
>
> label #{translate('password')}
> input.form-control(
> type="password",
> autocomplete="off",
> placeholder="",
> ng-model="state.password",
> ng-keyup="checkValidation()"
> )
>
> div.confirmation-checkbox-wrapper
> input(
> type="checkbox"
> ng-model="state.confirmV1Purge"
> ng-change="checkValidation()"
> ).pull-left
> label(style="display: inline") &nbsp;I have left, purged or imported my projects on Overleaf v1 (if any) &nbsp;
>
> div.confirmation-checkbox-wrapper
> input(
> type="checkbox"
> ng-model="state.confirmSharelatexDelete"
> ng-change="checkValidation()"
> ).pull-left
> label(style="display: inline") &nbsp;I understand this will delete all projects in my Overleaf v2 account (and ShareLaTeX account, if any) with email address #[em {{ userDefaultEmail }}]
>
> div(ng-if="state.error")
> div.alert.alert-danger(ng-switch="state.error.code")
> span(ng-switch-when="InvalidCredentialsError")
> | #{translate('email_or_password_wrong_try_again')}
> span(ng-switch-when="SubscriptionAdminDeletionError")
> | #{translate('subscription_admins_cannot_be_deleted')}
> span(ng-switch-when="UserDeletionError")
> | #{translate('user_deletion_error')}
> span(ng-switch-default)
> | #{translate('generic_something_went_wrong')}
> if settings.createV1AccountOnLogin && settings.overleaf
> div(ng-if="state.error && state.error.code == 'InvalidCredentialsError'")
> div.alert.alert-info
> | If you can't remember your password, or if you are using Single-Sign-On with another provider
> | to sign in (such as Twitter or Google), please
> | #[a(href="/user/password/reset", target='_blank') reset your password],
> | and try again.
> .modal-footer
> button.btn.btn-default(
> ng-click="cancel()"
> ) #{translate("cancel")}
> button.btn.btn-danger(
> ng-disabled="!state.isValid || state.inflight"
> ng-click="delete()"
> )
> span(ng-hide="state.inflight") #{translate("delete")}
> span(ng-show="state.inflight") #{translate("deleting")}...
>
> script(type='text/javascript').
> window.passwordStrengthOptions = !{StringHelper.stringifyJsonForScript(settings.passwordStrengthOptions || {})}