Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub OAuth #19

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ Options :
* *projects* : an array of Github repository's names (optional, default get all repositories),
* *org* : an array of Github organizations,
* *apiUrl* : url of your Github API (optional, default is `https://api.github.com`),
* *token* : authorization token for API calls (optional, it can increase API rate limit).
* *descendingOrder* : allow to change ordering of pull requests (optional, default is `true`).
* *token* : authorization token for API calls (optional, it can allow access to more repos and increase API rate limit) NB: if a token is set, OAuth will be ignored for the team
* *oauthAppClientId* : clientId of the OAuth app the team depends on (optional)
* **githubOAuth** : OAuth config (optional)
* *gatekeeperBaseUrl* : url to [Gatekeeper](https://github.com/M6Web/gatekeeper) (see OAuth section)
* *apps* : list of the apps you use to auth
* *url* : base url of GitHub (should be https://github.com or the base url of your GitHub enterprise)
* *clientId* : clientId of the app

## Run the server

Expand All @@ -48,6 +54,16 @@ $ gulp serve:dist

It will automatically open the dashboard in your browser.

## OAuth

To use GTR with GitHub OAuth you must :
* Register a new application on GitHub (in Settings > Applications)
* Install [Gatekeeper](https://github.com/M6Web/gatekeeper) and launch it
* Set your "gatekeeperBaseUrl" and type in your app data (clientId and GitHub URL) in config/config.json (example in config.json.dist)
* Don't forget to link the OAuth app to the teams thanks to the oauthAppClientId property

Then, you should see the Auth button in the upper-right corner of the app !

## Use

Use directly the page path in order to select a team.
Expand Down Expand Up @@ -80,7 +96,7 @@ $ cp Vagrantfile.dist Vagrantfile

```shell
$ vagrant up
$ vagrant provision # because of npm issue on the first vagrant up
$ vagrant provision # because of npm issue on the first vagrant up
$ vagrant ssh
$ cd /vagrant
```
Expand Down
8 changes: 6 additions & 2 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->

<ng-view></ng-view>
<div ui-view=""></div>

<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/json3/lib/json3.js"></script>
<script src="bower_components/lodash/lodash.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="bower_components/uri.js/src/URI.js"></script>
<script src="bower_components/angular-uri/angular-uri.js"></script>
<!-- endbower -->
<!-- endbuild -->

<!-- build:js({app,.tmp}) scripts/main.js -->
<script src="scripts/config.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/services/pullFetcher.js"></script>
<script src="scripts/services/authManager.js"></script>
<script src="scripts/controllers/auth.js"></script>
<script src="scripts/controllers/main.js"></script>

<!-- inject:partials -->
Expand Down
28 changes: 18 additions & 10 deletions app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@

angular
.module('gtrApp', [
'ngRoute',
'ui.router',
'angular-uri',
'gtrApp.config'
]).config(function ($routeProvider, config) {
$routeProvider
.when('/:team', {
]).config(function ($stateProvider, $urlRouterProvider, config) {
$stateProvider

.state('auth', {
url: '/auth',
controller: 'AuthCtrl',
reloadOnSearch: false
})

.state('main', {
url: '/:team',
templateUrl: 'views/main.html',
controller: 'MainCtrl',
resolve: {team: function($q, $route, config) {
resolve: {team: function($q, $stateParams, config) {
var defer = $q.defer();
if (config.teams[$route.current.params.team]) {
defer.resolve($route.current.params.team);
if (config.teams[$stateParams.team]) {
defer.resolve($stateParams.team);
} else {
defer.reject('Team does not exist');
}

return defer.promise;
}}
})
.otherwise({
redirectTo: '/' + Object.keys(config.teams)[0]
});

$urlRouterProvider.otherwise('/' + Object.keys(config.teams)[0]);
});
18 changes: 18 additions & 0 deletions app/scripts/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

angular.module('gtrApp')
.controller('AuthCtrl', function ($window, URI, authManager) {
var authFailed = function(err) {
$window.alert('Authentication failed' + err.error ? ' : ' + err.error : '');
$window.location.href = '/';
};
var searchArr = URI($window.location.href).search(true);
if (searchArr.code && searchArr.state) {
authManager.getAccessToken(searchArr.code, searchArr.state)
.then(function() {
$window.location.href = '/';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il y a pas une méthode plus sympa pour changer de route dans Angular ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est pas tellement pour changer de route mais surtout pour enlever le ?code=... que github met dans l'url

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, compris 👯

}, authFailed);
} else {
authFailed();
}
});
16 changes: 15 additions & 1 deletion app/scripts/controllers/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
'use strict';

angular.module('gtrApp')
.controller('MainCtrl', function ($scope, $location, $interval, PullFetcher, config, team) {
.controller('MainCtrl', function ($scope, $location, $interval, PullFetcher, authManager, config, team) {
var oauthEnabled = !angular.isUndefined(config.githubOAuth);
$scope.oauthEnabled = oauthEnabled;
if (oauthEnabled) {
authManager.authenticateTeams();
$scope.loginUrls = authManager.getLoginUrls();
$scope.logoutClientIds = authManager.getLogoutClientIds();
}

$scope.pulls = PullFetcher.pulls;
$scope.teams = config.teams;
$scope.team = team;
Expand Down Expand Up @@ -65,6 +73,12 @@ angular.module('gtrApp')
return array;
};

$scope.logout = function(clientId) {
authManager.logout(clientId);
$scope.loginUrls = authManager.getLoginUrls();
$scope.logoutClientIds = authManager.getLogoutClientIds();
};

$scope.$watch('team', function (team) {
$location.path(team);
});
Expand Down
115 changes: 115 additions & 0 deletions app/scripts/services/authManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* global _ */

'use strict';

angular.module('gtrApp')
.factory('authManager', function ($http, $q, $state, URI, config) {
return {

getAccessTokens: function() {
if (!localStorage.githubOAuthAccessTokens) {
return {};
}

return JSON.parse(localStorage.githubOAuthAccessTokens);
},

setAccessTokens: function(accessTokens) {
localStorage.githubOAuthAccessTokens = JSON.stringify(accessTokens);
},

getLoginUrls: function() {
var accessTokens = this.getAccessTokens();
var loginUrls = [];
_.forEach(config.githubOAuth.apps, function(appConfig) {
if (!accessTokens[appConfig.clientId]) {
var loginUrl = appConfig.url + '/login/oauth/authorize';
var loginParams = {
client_id: appConfig.clientId,
redirect_uri: $state.href('auth', null, {absolute: true}),
scope: 'repo,read:org',
state: appConfig.clientId,
};

loginUrls.push({
githubHostname: URI(appConfig.url).hostname(),
loginUrl: URI(loginUrl).addSearch(loginParams).toString(),
});
}
});

return loginUrls;
},

getLogoutClientIds: function() {
var accessTokens = this.getAccessTokens();
var logoutClientIds = [];

_.forEach(accessTokens, function(accessToken, clientId) {
var appUrl = _.findWhere(config.githubOAuth.apps, {clientId:clientId});
logoutClientIds.push({
clientId:clientId,
githubHostname:URI(appUrl.url).hostname()
});
});

return logoutClientIds;
},

getAccessToken: function(code, clientId) {
if(angular.isUndefined(code) || angular.isUndefined(clientId)) {
return false;
}

var authManager = this;
var deferred = $q.defer();

var gatekeeperUrl = config.githubOAuth.gatekeeperBaseUrl + '/authenticate/' + clientId + '/' + code;
$http.get(gatekeeperUrl)
.success(function(data) {
if (data.token) {
var accessTokens = authManager.getAccessTokens();
if (!accessTokens) {
accessTokens = {};
}
accessTokens[clientId] = data.token;
authManager.setAccessTokens(accessTokens);

deferred.resolve(data.token);
} else {
deferred.reject('No token found');
}
})
.error(function(reason) {
deferred.reject(reason);
});

return deferred.promise;
},

authenticateTeams: function() {
var accessTokens = this.getAccessTokens();
if (accessTokens.length) {
// Add OAuth token on each team if found
_.forEach(config.teams, function(team) {
if (!team.token && team.oauthAppClientId && accessTokens[team.oauthAppClientId]) {
team.token = accessTokens[team.oauthAppClientId];
}
});
}
},

logout: function(clientId) {
if(angular.isUndefined(clientId)) {
return false;
}

var accessTokens = this.getAccessTokens();
if (accessTokens) {
delete accessTokens[clientId];
this.setAccessTokens(accessTokens);
}
},

};
});
81 changes: 80 additions & 1 deletion app/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ body {
a {
text-decoration: none;
color: inherit;
cursor: pointer;
}

.header-top {
Expand All @@ -25,7 +26,7 @@ a {
.header-top select {
float: right;
margin-right: 6px;
}
}

.header-users {
background-color: #3cc0bf;
Expand Down Expand Up @@ -87,3 +88,81 @@ ul.pulls .repo {
line-height: 0;
clear: both;
}

.popover-content {
padding: 9px 14px;
}

* {
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.popover-container {
position: relative;
}

.popover.bottom {
margin-top: 26px;
}
.popover {
border-color: #eee;
border-bottom: 2px solid #e4eaec;
border-radius: 3px;
}
.progress, .progress .progress-bar, .popover {
box-shadow: 0 0 0 #000;
}
.popover {
position: absolute;
top: 0;
left: -155px;
z-index: 9;
max-width: 276px;
padding: 1px;
text-align: left;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #e1e1e1;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
white-space: normal;
}

.popover.bottom>.arrow {
right: 0;
margin-left: -22px;
border-top-width: 0;
border-bottom-color: #fff;
top: -11px;
}
.popover>.arrow {
border-width: 11px;
}
.popover>.arrow, .popover>.arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}

.popover li {
border-bottom: 1px solid #ccc;
}
.popover li:last-child {
border-bottom: 0;
}

.popover .login-li {
color: #373;
}
.popover .logout-li {
color: #a44;
}

ul {
list-style: none;
padding: 0;
margin: 0;
}
12 changes: 12 additions & 0 deletions app/views/main.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
<header class="header-top">
<a href="http://github.com/m6web/GithubTeamReviewer">Github Team Reviewer</a> by <a href="http://tech.m6web.fr">M6Web</a>
<div ng-show="oauthEnabled" class="pull-right popover-container">
<a class="btn btn-link" ng-click="authPopover = !authPopover">Auth</a>
<div class="popover bottom" ng-show="authPopover">
<div class="arrow"></div>
<div class="popover-content">
<ul>
<li ng-repeat="login in loginUrls" class="login-li"><a href="{{ login.loginUrl }}">Authenticate to {{ login.githubHostname }}</a></li>
<li ng-repeat="logoutData in logoutClientIds" class="logout-li"><a ng-click="logout(logoutData.clientId)">Logout from {{ logoutData.githubHostname }}</a></li>
</ul>
</div>
</div>
</div>
<select ng-model="team" ng-options="teamName as teamName for (teamName, team) in teams"></select>
</header>

Expand Down
Loading