mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-15 05:06:30 +00:00
Merge pull request #1741 from overleaf/jel-manage-linked-accounts
Manage linked accounts via v2 GitOrigin-RevId: 09a7af361e1fc3c30bd01f7876fc17d651f60f06
This commit is contained in:
parent
004272619e
commit
e03b5ae691
6 changed files with 154 additions and 46 deletions
|
@ -71,6 +71,8 @@ module.exports = UserPagesController =
|
|||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
logger.log user: user_id, "loading settings page"
|
||||
shouldAllowEditingDetails = !(Settings?.ldap?.updateUserDetailsOnLogin) and !(Settings?.saml?.updateUserDetailsOnLogin)
|
||||
oauthProviders = Settings.oauthProviders || {}
|
||||
|
||||
UserGetter.getUser user_id, (err, user)->
|
||||
return next(err) if err?
|
||||
|
||||
|
@ -83,7 +85,10 @@ module.exports = UserPagesController =
|
|||
hasPassword: passwordPresent,
|
||||
shouldAllowEditingDetails: shouldAllowEditingDetails
|
||||
languages: Settings.languages,
|
||||
accountSettingsTabActive: true
|
||||
accountSettingsTabActive: true,
|
||||
oauthProviders: UserPagesController._translateProviderDescriptions(oauthProviders, req),
|
||||
thirdPartyIds: UserPagesController._restructureThirdPartyIds(user),
|
||||
previewOauth: req.query.prvw?
|
||||
|
||||
sessionsPage: (req, res, next) ->
|
||||
user = AuthenticationController.getSessionUser(req)
|
||||
|
@ -110,4 +115,21 @@ module.exports = UserPagesController =
|
|||
else if body?.has_password
|
||||
return callback(err, true)
|
||||
return callback(err, false)
|
||||
|
||||
|
||||
_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
|
||||
return null if !user.thirdPartyIdentifiers || user.thirdPartyIdentifiers.length == 0
|
||||
user.thirdPartyIdentifiers.reduce (obj, identifier) ->
|
||||
obj[identifier.providerId] = identifier.externalUserId
|
||||
obj
|
||||
, {}
|
||||
|
||||
_translateProviderDescriptions: (providers, req) ->
|
||||
result = {}
|
||||
if providers
|
||||
for provider, data of providers
|
||||
data.description = req.i18n.translate(data.descriptionKey, data.descriptionOptions)
|
||||
result[provider] = data
|
||||
return result
|
|
@ -168,14 +168,14 @@ block content
|
|||
div
|
||||
a(id="sessions-link", href="/user/sessions") #{translate("manage_sessions")}
|
||||
|
||||
if settings.overleaf && !hasFeature('oauth')
|
||||
if settings.overleaf && !hasFeature('oauth') && !previewOauth
|
||||
hr
|
||||
p
|
||||
| To manage your account's connection to Google, Twitter, ORCID and IEEE, please
|
||||
|
|
||||
a(href="/sign_in_to_v1?return_to=/users/edit#linked-accounts") click here
|
||||
| .
|
||||
else if hasFeature('oauth')
|
||||
else if hasFeature('oauth') || previewOauth
|
||||
hr
|
||||
include settings/user-oauth
|
||||
|
||||
|
|
|
@ -1,35 +1,63 @@
|
|||
form.row(
|
||||
block scripts
|
||||
script(type='text/javascript').
|
||||
window.oauthFallback = #{oauthFallback}
|
||||
window.oauthProviders = !{StringHelper.stringifyJsonForScript(oauthProviders)}
|
||||
window.thirdPartyIds = !{StringHelper.stringifyJsonForScript(thirdPartyIds)}
|
||||
|
||||
mixin providerList()
|
||||
ul.list-like-table
|
||||
li(ng-repeat="(key, provider) in providers")
|
||||
.row(ng-if="key != 'collabratec' || (key == 'collabratec' && v2ThirdPartyIds[key])")
|
||||
.col-xs-12.col-sm-8.col-md-10
|
||||
h4 {{provider.name}}
|
||||
p.small(ng-bind-html="provider.description")
|
||||
.col-xs-2.col-sm-4.col-md-2.text-right
|
||||
//- Unlink
|
||||
button.btn.btn-default(
|
||||
ng-click="unlink(key)"
|
||||
ng-disabled="providers[key].ui.isProcessing"
|
||||
ng-if="thirdPartyIds[key] || (key == 'collabratec' && v2ThirdPartyIds[key])"
|
||||
)
|
||||
span(ng-if="!providers[key].ui.isProcessing") #{translate("unlink")}
|
||||
span(ng-if="providers[key].ui.isProcessing") #{translate("processing")}
|
||||
//- Link
|
||||
a.btn.btn-primary(
|
||||
href="{{provider.linkPath}}?intent=link"
|
||||
ng-if="!thirdPartyIds[key] && !provider.hideWhenNotLinked"
|
||||
) #{translate("link")}
|
||||
//- unlink error
|
||||
.row(
|
||||
ng-if="providers[key].ui.hasError"
|
||||
)
|
||||
.col-sm-12
|
||||
//- to do: fix CSS so that we don't need inline styling
|
||||
.alert.alert-danger(
|
||||
ng-if="providers[key].ui.hasError"
|
||||
style="display: block; margin-bottom: 10px;"
|
||||
)
|
||||
i.fa.fa-fw.fa-exclamation-triangle(aria-hidden="true")
|
||||
| {{providers[key].ui.errorMessage}}
|
||||
.row(
|
||||
ng-controller="UserOauthController"
|
||||
name="oauthForm"
|
||||
ng-cloak
|
||||
)
|
||||
.col-xs-12
|
||||
h3.text-capitalize #{translate("linked_accounts")}
|
||||
h3.text-capitalize#linked-accounts #{translate("linked_accounts")}
|
||||
p.small #{translate("linked_accounts_explained", {appName:'{{settings.appName}}'})}
|
||||
|
||||
div.text-center(ng-if="ui.isLoadingProviders")
|
||||
i.fa.fa-fw.fa-spin.fa-refresh(aria-hidden="true")
|
||||
| #{translate("loading")}...
|
||||
if settings.oauthFallback
|
||||
div.text-center(ng-if="ui.isLoadingV1Ids")
|
||||
i.fa.fa-fw.fa-spin.fa-refresh(aria-hidden="true")
|
||||
| #{translate("loading")}...
|
||||
|
||||
ul(
|
||||
class="list-like-table"
|
||||
ng-if="ui.isLoadingProviders == false && ui.hasError == false"
|
||||
)
|
||||
li(ng-repeat="provider in providers")
|
||||
.row
|
||||
.col-xs-12.col-sm-8.col-md-10
|
||||
h4 {{provider.name}}
|
||||
p.small(ng-if="provider.key != 'orcid'")
|
||||
| #{translate("login_with_service", {service:'{{provider.name}}'})}
|
||||
p.small(ng-if="provider.key == 'orcid'")
|
||||
| !{translate('oauth_orcid_description', {link: '/blog/434'})}
|
||||
.col-xs-2.col-sm-4.col-md-2.text-right
|
||||
button.btn.btn-default(ng-if="userProviders[provider.key]") #{translate("unlink")}
|
||||
button.btn.btn-primary(ng-if="!userProviders[provider.key]") #{translate("link")}
|
||||
div(ng-if="ui.isLoadingV1Ids == false && ui.hasError == false")
|
||||
+providerList()
|
||||
|
||||
.alert.alert-danger(
|
||||
ng-if="ui.hasError"
|
||||
)
|
||||
i.fa.fa-fw.fa-exclamation-triangle(aria-hidden="true")
|
||||
span(ng-if="!ui.errorMessage") #{translate("error_performing_request")}
|
||||
span(ng-if="ui.errorMessage") {{ui.errorMessage}}
|
||||
.alert.alert-danger(
|
||||
ng-if="ui.hasError"
|
||||
)
|
||||
i.fa.fa-fw.fa-exclamation-triangle(aria-hidden="true")
|
||||
span(ng-if="!ui.errorMessage") #{translate("error_performing_request")}
|
||||
span(ng-if="ui.errorMessage") {{ui.errorMessage}}
|
||||
else
|
||||
+providerList()
|
|
@ -1,18 +1,14 @@
|
|||
define(['base'], App =>
|
||||
App.controller('UserOauthController', [
|
||||
'$http',
|
||||
'$scope',
|
||||
'$q',
|
||||
'_',
|
||||
'UserOauthDataService',
|
||||
function($scope, $q, _, UserOauthDataService) {
|
||||
$scope.providers = [
|
||||
{ key: 'google', name: 'Google' },
|
||||
{ key: 'orcid', name: 'Orcid' },
|
||||
{ key: 'twitter', name: 'Twitter' }
|
||||
]
|
||||
function($http, $scope, $q, _, UserOauthDataService) {
|
||||
const _monitorRequest = function(promise) {
|
||||
$scope.ui.hasError = false
|
||||
$scope.ui.isLoadingProviders = true
|
||||
$scope.ui.isLoadingV1Ids = true
|
||||
promise
|
||||
.catch(response => {
|
||||
$scope.ui.hasError = true
|
||||
|
@ -22,7 +18,7 @@ define(['base'], App =>
|
|||
: 'error'
|
||||
})
|
||||
.finally(() => {
|
||||
$scope.ui.isLoadingProviders = false
|
||||
$scope.ui.isLoadingV1Ids = false
|
||||
})
|
||||
return promise
|
||||
}
|
||||
|
@ -30,19 +26,65 @@ define(['base'], App =>
|
|||
$scope.ui = {
|
||||
hasError: false,
|
||||
errorMessage: '',
|
||||
isLoadingProviders: false
|
||||
isLoadingV1Ids: false
|
||||
}
|
||||
$scope.userProviders = {}
|
||||
$scope.providers = window.oauthProviders
|
||||
$scope.thirdPartyIds = window.thirdPartyIds
|
||||
// $scope.v2ThirdPartyIds can be removed post user-c11n
|
||||
// until v1 is authoritative we will use v1 SSO data for providers
|
||||
// except collabratec, which will only write to v2.
|
||||
// post user-c11n, oauthFallback setting will be removed and
|
||||
// we will only use data from v2
|
||||
$scope.v2ThirdPartyIds = window.thirdPartyIds
|
||||
}
|
||||
const _getUserV1OauthProviders = () => {
|
||||
$scope.ui.isLoadingProviders = true
|
||||
$scope.ui.isLoadingV1Ids = true
|
||||
return _monitorRequest(UserOauthDataService.getUserOauthV1()).then(
|
||||
userProviders => {
|
||||
$scope.userProviders = userProviders
|
||||
thirdPartyIds => {
|
||||
$scope.thirdPartyIds = thirdPartyIds
|
||||
}
|
||||
)
|
||||
}
|
||||
const _unlinkError = (providerId, err) => {
|
||||
$scope.providers[providerId].ui.hasError = true
|
||||
$scope.providers[providerId].ui.errorMessage =
|
||||
err && err.data && err.data.message ? err.data.message : 'error'
|
||||
}
|
||||
|
||||
$scope.unlink = providerId => {
|
||||
if (window.ExposedSettings.isOverleaf) {
|
||||
// UI
|
||||
$scope.providers[providerId].ui = {
|
||||
hasError: false,
|
||||
isProcessing: true
|
||||
}
|
||||
// Data for update
|
||||
const data = {
|
||||
link: false,
|
||||
providerId
|
||||
}
|
||||
$http
|
||||
.post('/user/oauth-unlink', data)
|
||||
.catch(error => {
|
||||
$scope.providers[providerId].ui.isProcessing = false
|
||||
_unlinkError(providerId, error)
|
||||
})
|
||||
.then(response => {
|
||||
$scope.providers[providerId].ui.isProcessing = false
|
||||
if (response.status === 200) {
|
||||
$scope.thirdPartyIds[providerId] = null
|
||||
// v2thirdPartyIds below can be removed post user c11n
|
||||
$scope.v2ThirdPartyIds[providerId] = null
|
||||
} else {
|
||||
_unlinkError(providerId, response)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_reset()
|
||||
return _getUserV1OauthProviders()
|
||||
if (window.oauthFallback) {
|
||||
_getUserV1OauthProviders()
|
||||
}
|
||||
}
|
||||
]))
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
border-radius: @border-radius-base;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: @padding-sm;
|
||||
padding: 0 @padding-sm;
|
||||
li {
|
||||
border-top: 1px solid @hr-border;
|
||||
div {
|
||||
|
@ -13,6 +13,7 @@
|
|||
}
|
||||
.row {
|
||||
display: table;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
&:first-child {
|
||||
|
|
|
@ -21,6 +21,12 @@ describe "UserPagesController", ->
|
|||
_id: @user_id = "kwjewkl"
|
||||
features:{}
|
||||
email: "joe@example.com"
|
||||
thirdPartyIdentifiers: [
|
||||
{
|
||||
"providerId": "google",
|
||||
"externalUserId": "testId"
|
||||
}
|
||||
]
|
||||
|
||||
@UserGetter = getUser: sinon.stub()
|
||||
@UserSessionsManager =
|
||||
|
@ -161,6 +167,15 @@ describe "UserPagesController", ->
|
|||
done()
|
||||
@UserPagesController.settingsPage @req, @res
|
||||
|
||||
it "should restructure thirdPartyIdentifiers data for template use", (done)->
|
||||
expectedResult = {
|
||||
google: "testId"
|
||||
}
|
||||
@res.render = (page, opts)=>
|
||||
expect(opts.thirdPartyIds).to.include expectedResult
|
||||
done()
|
||||
@UserPagesController.settingsPage @req, @res
|
||||
|
||||
describe 'when ldap.updateUserDetailsOnLogin is true', ->
|
||||
|
||||
beforeEach ->
|
||||
|
|
Loading…
Add table
Reference in a new issue