Lay foundation for truly dynamic external logins
Moves all the external login services into a set of classes that share as much code as possible. These services are then registered on both the client and server, allowing us in the followup change to dynamically register new handlers
This commit is contained in:
parent
4755d08677
commit
19f7acf575
26 changed files with 686 additions and 472 deletions
|
@ -1,14 +1,13 @@
|
|||
<span class="external-login-button-element">
|
||||
<a ng-class="isLink ? '' : 'btn btn-primary btn-block'"
|
||||
ng-if="providerInfo.enabled" ng-click="startSignin()" style="margin-bottom: 10px"
|
||||
ng-click="startSignin()" style="margin-bottom: 10px"
|
||||
ng-disabled="signingIn">
|
||||
<img ng-src="{{ providerInfo.icon().url }}" ng-if="providerInfo.icon().url">
|
||||
<i class="fa" ng-class="providerInfo.icon().icon" ng-if="providerInfo.icon().icon"></i>
|
||||
<span class="icon-image-view" value="{{ provider.icon }}"></span>
|
||||
<span class="login-text" ng-if="action != 'attach'" style="vertical-align: middle">
|
||||
<span class="prefix">Sign in with </span><span class="suffix">{{ providerInfo.title() }}</span>
|
||||
<span class="prefix">Sign in with </span><span class="suffix">{{ provider.title }}</span>
|
||||
</span>
|
||||
<span class="login-text" ng-if="action == 'attach'" style="vertical-align: middle">
|
||||
Attach to {{ providerInfo.title() }}
|
||||
Attach to {{ provider.title }}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -14,27 +14,24 @@
|
|||
|
||||
<tr class="external-auth-provider" ng-repeat="provider in EXTERNAL_LOGINS">
|
||||
<td class="external-auth-provider-title">
|
||||
<img ng-src="{{ provider.icon().url }}" ng-if="provider.icon().url">
|
||||
<i class="fa" ng-class="provider.icon().icon" ng-if="provider.icon().icon"></i>
|
||||
{{ provider.title() }}
|
||||
<span class="icon-image-view" value="{{ provider.icon }}"></span>
|
||||
{{ provider.title }}
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="externalLoginInfo[provider.id]">
|
||||
Attached to {{ provider.title() }} account
|
||||
<b ng-if="provider.hasUserInfo">
|
||||
<a ng-href="{{ provider.getUserInfo(externalLoginInfo[provider.id]).endpoint }}" ng-safenewtab>
|
||||
{{ provider.getUserInfo(externalLoginInfo[provider.id]).username }}
|
||||
</a>
|
||||
Attached to {{ provider.title }} account
|
||||
<b ng-if="externalLoginInfo[provider.id].metadata.service_username">
|
||||
{{ externalLoginInfo[provider.id].metadata.service_username }}
|
||||
</b>
|
||||
</span>
|
||||
|
||||
<span class="empty" ng-if="!externalLoginInfo[provider.id]">
|
||||
Not attached to {{ provider.title() }}
|
||||
Not attached to {{ provider.title }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="external-login-button" provider="{{ provider.id }}" action="attach" is-link="true"
|
||||
<span class="external-login-button" provider="provider" action="attach" is-link="true"
|
||||
ng-if="!externalLoginInfo[provider.id]"></span>
|
||||
<a ng-if="externalLoginInfo[provider.id] && Features.DIRECT_LOGIN"
|
||||
ng-click="detachExternalLogin(provider.id)">Detach Account</a>
|
||||
|
|
4
static/directives/icon-image-view.html
Normal file
4
static/directives/icon-image-view.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<span class="icon-image-view-element">
|
||||
<img ng-src="{{ value }}" ng-if="value.indexOf('http') == 0">
|
||||
<i class="fa" ng-class="value" ng-if="value.indexOf('http') < 0"></i>
|
||||
</span>
|
|
@ -6,7 +6,7 @@
|
|||
</h4>
|
||||
|
||||
<div class="external-logins" quay-show="EXTERNAL_LOGINS.length" ng-class="EXTERNAL_LOGINS.length > 2 ? 'smaller': 'larger'">
|
||||
<div class="external-login-button" provider="{{ provider.id }}" redirect-url="redirectUrl"
|
||||
<div class="external-login-button" provider="provider" redirect-url="redirectUrl"
|
||||
sign-in-started="markStarted()" ng-repeat="provider in EXTERNAL_LOGINS" is-link="true"></div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -12,12 +12,11 @@ angular.module('quay').directive('externalLoginButton', function () {
|
|||
'signInStarted': '&signInStarted',
|
||||
'redirectUrl': '=redirectUrl',
|
||||
'isLink': '=isLink',
|
||||
'provider': '@provider',
|
||||
'provider': '=provider',
|
||||
'action': '@action'
|
||||
},
|
||||
controller: function($scope, $timeout, $interval, ApiService, KeyService, CookieService, ExternalLoginService) {
|
||||
$scope.signingIn = false;
|
||||
$scope.providerInfo = ExternalLoginService.getProvider($scope.provider);
|
||||
|
||||
$scope.startSignin = function() {
|
||||
$scope.signInStarted({'service': $scope.provider});
|
||||
|
|
|
@ -34,11 +34,11 @@ angular.module('quay').directive('externalLoginsManager', function () {
|
|||
}
|
||||
});
|
||||
|
||||
$scope.detachExternalLogin = function(kind) {
|
||||
$scope.detachExternalLogin = function(service_id) {
|
||||
if (!Features.DIRECT_LOGIN) { return; }
|
||||
|
||||
var params = {
|
||||
'servicename': kind
|
||||
'service_id': service_id
|
||||
};
|
||||
|
||||
ApiService.detachExternalLogin(null, params).then(function() {
|
||||
|
|
18
static/js/directives/ui/icon-image-view.js
Normal file
18
static/js/directives/ui/icon-image-view.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* An element which displays either an icon or an image, depending on the value.
|
||||
*/
|
||||
angular.module('quay').directive('iconImageView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/icon-image-view.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'value': '@value'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,19 +1,18 @@
|
|||
/**
|
||||
* Service which exposes the supported external logins.
|
||||
*/
|
||||
angular.module('quay').factory('ExternalLoginService', ['KeyService', 'Features', 'Config',
|
||||
function(KeyService, Features, Config) {
|
||||
angular.module('quay').factory('ExternalLoginService', ['Features', 'Config',
|
||||
function(Features, Config) {
|
||||
var externalLoginService = {};
|
||||
|
||||
externalLoginService.getLoginUrl = function(service, action) {
|
||||
var serviceInfo = externalLoginService.getProvider(service);
|
||||
if (!serviceInfo) { return ''; }
|
||||
externalLoginService.EXTERNAL_LOGINS = window.__external_login;
|
||||
|
||||
var loginUrl = KeyService.getConfiguration(serviceInfo.key, 'AUTHORIZE_ENDPOINT');
|
||||
var clientId = KeyService.getConfiguration(serviceInfo.key, 'CLIENT_ID');
|
||||
externalLoginService.getLoginUrl = function(loginService, action) {
|
||||
var loginUrl = loginService['config']['AUTHORIZE_ENDPOINT'];
|
||||
var clientId = loginService['config']['CLIENT_ID'];
|
||||
|
||||
var scope = serviceInfo.scopes();
|
||||
var redirectUri = Config.getUrl('/oauth2/' + service + '/callback');
|
||||
var scope = loginService.scopes.join(' ');
|
||||
var redirectUri = Config.getUrl('/oauth2/' + loginService['id'] + '/callback');
|
||||
|
||||
if (action == 'attach') {
|
||||
redirectUri += '/attach';
|
||||
|
@ -24,96 +23,6 @@ angular.module('quay').factory('ExternalLoginService', ['KeyService', 'Features'
|
|||
return url;
|
||||
};
|
||||
|
||||
var DEX = {
|
||||
id: 'dex',
|
||||
key: 'DEX_LOGIN_CONFIG',
|
||||
|
||||
title: function() {
|
||||
return KeyService.getConfiguration('DEX_LOGIN_CONFIG', 'OIDC_TITLE');
|
||||
},
|
||||
|
||||
icon: function() {
|
||||
return {'url': KeyService.getConfiguration('DEX_LOGIN_CONFIG', 'OIDC_LOGO') };
|
||||
},
|
||||
|
||||
scopes: function() {
|
||||
return 'openid email profile'
|
||||
},
|
||||
|
||||
enabled: Features.DEX_LOGIN
|
||||
};
|
||||
|
||||
var GITHUB = {
|
||||
id: 'github',
|
||||
key: 'GITHUB_LOGIN_CONFIG',
|
||||
|
||||
title: function() {
|
||||
return KeyService.isEnterprise('github') ? 'GitHub Enterprise' : 'GitHub';
|
||||
},
|
||||
|
||||
icon: function() {
|
||||
return {'icon': 'fa-github'};
|
||||
},
|
||||
|
||||
hasUserInfo: true,
|
||||
getUserInfo: function(service_info) {
|
||||
username = service_info['metadata']['service_username'];
|
||||
return {
|
||||
'username': username,
|
||||
'endpoint': KeyService['githubEndpoint'] + username
|
||||
}
|
||||
},
|
||||
|
||||
scopes: function() {
|
||||
var scopes = 'user:email';
|
||||
if (KeyService.getConfiguration('GITHUB_LOGIN_CONFIG', 'ORG_RESTRICT')) {
|
||||
scopes += ' read:org';
|
||||
}
|
||||
|
||||
return scopes;
|
||||
},
|
||||
|
||||
enabled: Features.GITHUB_LOGIN
|
||||
};
|
||||
|
||||
var GOOGLE = {
|
||||
id: 'google',
|
||||
key: 'GOOGLE_LOGIN_CONFIG',
|
||||
|
||||
title: function() {
|
||||
return 'Google';
|
||||
},
|
||||
|
||||
icon: function() {
|
||||
return {'icon': 'fa-google'};
|
||||
},
|
||||
|
||||
scopes: function() {
|
||||
return 'openid email';
|
||||
},
|
||||
|
||||
enabled: Features.GOOGLE_LOGIN
|
||||
};
|
||||
|
||||
externalLoginService.ALL_EXTERNAL_LOGINS = [
|
||||
DEX, GITHUB, GOOGLE
|
||||
];
|
||||
|
||||
externalLoginService.EXTERNAL_LOGINS = externalLoginService.ALL_EXTERNAL_LOGINS.filter(function(el) {
|
||||
return el.enabled;
|
||||
});
|
||||
|
||||
externalLoginService.getProvider = function(providerId) {
|
||||
for (var i = 0; i < externalLoginService.EXTERNAL_LOGINS.length; ++i) {
|
||||
var current = externalLoginService.EXTERNAL_LOGINS[i];
|
||||
if (current.id == providerId) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
externalLoginService.hasSingleSignin = function() {
|
||||
return externalLoginService.EXTERNAL_LOGINS.length == 1 && !Features.DIRECT_LOGIN;
|
||||
};
|
||||
|
@ -122,7 +31,7 @@ angular.module('quay').factory('ExternalLoginService', ['KeyService', 'Features'
|
|||
// If there is a single external login service and direct login is disabled,
|
||||
// then redirect to the external login directly.
|
||||
if (externalLoginService.hasSingleSignin()) {
|
||||
return externalLoginService.getLoginUrl(externalLoginService.EXTERNAL_LOGINS[0].id);
|
||||
return externalLoginService.getLoginUrl(externalLoginService.EXTERNAL_LOGINS[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
Reference in a new issue