Skip to content

Commit

Permalink
#1303 Initial commit of MFA support
Browse files Browse the repository at this point in the history
  • Loading branch information
nadouani committed May 2, 2020
1 parent 5dcdfb5 commit 8236056
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 67 deletions.
3 changes: 3 additions & 0 deletions frontend/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
<script src="bower_components/js-url/url.js"></script>
<script src="bower_components/bootstrap-sass/assets/javascripts/bootstrap.js"></script>
<script src="bower_components/angular-bootstrap-multiselect/dist/angular-bootstrap-multiselect.js"></script>
<script src="bower_components/qrcode.js/lib/qrcode.js"></script>
<script src="bower_components/qrcode/lib/qrcode.js"></script>
<script src="bower_components/angular-qr/src/angular-qr.js"></script>
<!-- endbower -->

<script type="text/javascript" src="bower_components/ace-builds/src-min-noconflict/ace.js"></script>
Expand Down
1 change: 1 addition & 0 deletions frontend/app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ angular.module('thehive', [
'dndLists',
'colorpicker.module',
'btorfs.multiselect',
'ja.qr',
'theHiveControllers',
'theHiveServices',
'theHiveFilters',
Expand Down
8 changes: 6 additions & 2 deletions frontend/app/scripts/controllers/AuthenticationCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
'use strict';
angular.module('theHiveControllers')
.controller('AuthenticationCtrl', function($rootScope, $scope, $state, $location, $uibModalStack, $stateParams, AuthenticationSrv, NotificationSrv, UtilsSrv, UrlParser, appConfig) {
$scope.params = {};
$scope.params = {
requireMfa: false
};

$uibModalStack.dismissAll();

Expand Down Expand Up @@ -38,13 +40,15 @@

$scope.login = function() {
$scope.params.username = $scope.params.username.toLowerCase();
AuthenticationSrv.login($scope.params.username, $scope.params.password)
AuthenticationSrv.login($scope.params.username, $scope.params.password, $scope.params.mfaCode)
.then(function() {
$state.go('app.index');
})
.catch(function(err) {
if (err.status === 520) {
NotificationSrv.error('AuthenticationCtrl', err.data.message, err.status);
} else if(err.status === 402){
$scope.params.requireMfa = true;
} else {
NotificationSrv.log(err.data.message, 'error');
}
Expand Down
53 changes: 52 additions & 1 deletion frontend/app/scripts/controllers/SettingsCtrl.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function() {
'use strict';
angular.module('theHiveControllers').controller('SettingsCtrl',
function($scope, $state, UserSrv, AuthenticationSrv, NotificationSrv, resizeService, readLocalPicService, currentUser, appConfig) {
function($scope, $state, UserSrv, AuthenticationSrv, NotificationSrv, clipboard, resizeService, readLocalPicService, currentUser, appConfig) {
$scope.currentUser = currentUser;
$scope.appConfig = appConfig;

Expand All @@ -23,6 +23,14 @@
password: '',
passwordConfirm: ''
};

$scope.mfaData = {
enabled: $scope.currentUser.hasMFA,
secret: null,
uri: null,
code: null
};

$scope.canChangePass = appConfig.config.capabilities.indexOf('changePassword') !== -1;


Expand Down Expand Up @@ -86,6 +94,49 @@
}
};

$scope.copySecret = function(secret) {
clipboard.copyText(secret);
NotificationSrv.success('MFA Secret has been successfully copied to clipboard.');
};

$scope.enableMfa = function() {
if($scope.mfaData.enabled) {
// Fetch the secret
UserSrv.fetchMfaSecret()
.then(function(response) {
$scope.mfaData.secret = response.data.secret;
$scope.mfaData.uri = response.data.uri;
})
.catch(function(err) {
NotificationSrv.error('SettingsCtrl', err.data, err.status);
});
}
};

$scope.setMfaSettings = function(form) {
UserSrv.setMfa($scope.mfaData.code)
.then(function(/*response*/) {
NotificationSrv.log('Your multi-factor authentication has been successfully configured', 'success');
$state.reload();
})
.catch(function(/*err*/) {
$scope.mfaData.enabled = true;
form.mfaCode.$setValidity('mfaInvalid', false);
});
};

$scope.resetMfa = function() {
UserSrv.resetMfa()
.then(function(/*response*/) {
NotificationSrv.log('Your multi-factor authentication has been successfully disabled', 'success');
$state.reload();

})
.catch(function(err) {
NotificationSrv.error('SettingsCtrl', err.data, err.status);
});
};

$scope.clearPassword = function(form, changePassword) {

if (!changePassword) {
Expand Down
127 changes: 66 additions & 61 deletions frontend/app/scripts/services/api/AuthenticationSrv.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,75 @@
(function() {
'use strict';
angular
.module('theHiveServices')
.factory('AuthenticationSrv', function($http, $q, UtilsSrv, SecuritySrv) {
var self = {
currentUser: null,
login: function(username, password) {
return $http
.post('./api/login', {
user: username,
password: password
});
},
logout: function(success, failure) {
$http
.get('./api/logout')
.then(function(data, status, headers, config) {
self.currentUser = null;
'use strict';
angular
.module('theHiveServices')
.factory('AuthenticationSrv', function($http, $q, UtilsSrv, SecuritySrv) {
var self = {
currentUser: null,
login: function(username, password, code) {
var post = {
user: username,
password: password
};

if (angular.isFunction(success)) {
success(data, status, headers, config);
}
})
.catch(function(data, status, headers, config) {
if (angular.isFunction(failure)) {
failure(data, status, headers, config);
}
});
},
current: function() {
var result = {};
return $http
.get('./api/v1/user/current')
.then(function(response) {
self.currentUser = response.data;
UtilsSrv.shallowClearAndCopy(response.data, result);
if(code) {
post.code = code;
}

return $q.resolve(result);
})
.catch(function(err) {
self.currentUser = null;
return $q.reject(err);
});
},
ssoLogin: function(code, state) {
var url = angular.isDefined(code) && angular.isDefined(state) ? './api/ssoLogin?code=' + code + '&state=' + state : './api/ssoLogin';
return $http.post(url, {});
},
isSuperAdmin: function() {
var user = self.currentUser;
return $http.post('./api/login', post);
},
logout: function(success, failure) {
$http
.get('./api/logout')
.then(function(data, status, headers, config) {
self.currentUser = null;

return user && user.organisation === 'admin';
},
hasPermission: function(permissions) {
var user = self.currentUser;
if (angular.isFunction(success)) {
success(data, status, headers, config);
}
})
.catch(function(data, status, headers, config) {
if (angular.isFunction(failure)) {
failure(data, status, headers, config);
}
});
},
current: function() {
var result = {};
return $http
.get('./api/v1/user/current')
.then(function(response) {
self.currentUser = response.data;
UtilsSrv.shallowClearAndCopy(response.data, result);

if (!user) {
return false;
}
return $q.resolve(result);
})
.catch(function(err) {
self.currentUser = null;
return $q.reject(err);
});
},
ssoLogin: function(code, state) {
var url = angular.isDefined(code) && angular.isDefined(state) ? './api/ssoLogin?code=' + code + '&state=' + state : './api/ssoLogin';
return $http.post(url, {});
},
isSuperAdmin: function() {
var user = self.currentUser;

//return !_.isEmpty(_.intersection(user.permissions, permissions));
return user && user.organisation === 'admin';
},
hasPermission: function(permissions) {
var user = self.currentUser;

return SecuritySrv.checkPermissions(user.permissions, permissions);
}
};
if (!user) {
return false;
}

return self;
});
//return !_.isEmpty(_.intersection(user.permissions, permissions));

return SecuritySrv.checkPermissions(user.permissions, permissions);
}
};

return self;
});
})();
16 changes: 16 additions & 0 deletions frontend/app/scripts/services/api/UserSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@
});
};

this.fetchMfaSecret = function() {
return $http
.post('./api/v1/auth/totp/set', {});
};

this.setMfa = function(code) {
return $http
.post('./api/v1/auth/totp/set', {
code: code
});
};

this.resetMfa = function() {
return $http.post('./api/v1/auth/totp/unset');
};

this.list = function(organisation, query) {
// var post = {
// range: 'all',
Expand Down
10 changes: 10 additions & 0 deletions frontend/app/views/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
<input type="password" class="input form-control" placeholder="Password" ng-model="params.password" autocomplete="off" required>
<i class="form-control-feedback glyphicon glyphicon-lock "></i>
</div>
<div class="mv-s" ng-if="params.requireMfa === true">
<div class="text-danger text-center">
Please provide MFA code
</div>
<div class="form-group has-feedback has-feedback-left">
<input type="text" class="input form-control" placeholder="Code" ng-model="params.mfaCode" autocomplete="off" ng-required="params.requireMfa">
<i class="form-control-feedback glyphicon glyphicon-qrcode"></i>
</div>
</div>


<div class="row">
<div class="col-xs-offset-8 col-xs-4">
Expand Down
68 changes: 66 additions & 2 deletions frontend/app/views/partials/personal-settings.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="col-md-10 col-md-offset-1">
<h3>
<center>Settings</center>
</h3>
Expand Down Expand Up @@ -136,8 +136,69 @@ <h3 class="box-title"><input type="checkbox" ng-model="passData.changePassword"
</div>
</form>

<form name="form">
<!-- Update mfa -->
<form class="mt-s" name="mfaForm" ng-submit="setMfaSettings(mfaForm);" novalidate>
<div class="box">
<div class="box-header">
<h3 class="box-title">
<input type="checkbox" ng-model="mfaData.enabled" ng-change="enableMfa()" ng-disabled="currentUser.hasMFA">
Enable Multi-Factor Authentication
</h3>
</div>
<div class="box-body" ng-show="mfaData.enabled === true">
<div ng-if="currentUser.hasMFA === true" class="text-center">
Your have enable Multi-Factor Authentication, do you want to disabled it?
<div class="mv-s">
<button type="button" class="btn btn-danger" ng-click="resetMfa()">Disable MFA</button>
</div>
</div>

<div ng-if="currentUser.hasMFA === false">
<div class="mb-m text-center">
Your are going to enable Multi-Factor Authentication.
Use the QRCode or the Secret to generate a MFA code and submit it.
</div>

<div class="text-center" ng-if="mfaData.uri">
<qr text="mfaData.uri"></qr>
</div>

<div class="text-center mv-s" ng-if="mfaData.secret">
<h3>{{mfaData.secret}}</h3>
<div>
<a href ng-click="copySecret(mfaData.secret)"><i class="fa fa-copy"></i> Copy secret </a>
</div>
</div>

<div class="row">

<div class="col-xs-offset-3 col-xs-6 form-group" ng-class="{ 'has-error' : !mfaForm.mfaCode.$pristine && mfaForm.mfaCode.$invalid}">
<label class="control-label">Code
<i class="fa fa-asterisk text-danger"></i>
</label>
<input type="number" name="mfaCode" class="form-control input-lg text-center"
placeholder="MFA Code" autocomplete="off"
ng-model="mfaData.code"
ng-required="!currentUser.hasMFA"/>
<div ng-messages="mfaForm.mfaCode.$error" ng-show="!mfaForm.mfaCode.$pristine && mfaForm.mfaCode.$error">
<div ng-messages-include="settings-error.html"></div>
</div>
</div>

</div>



<div class="mt-s">
<button class="btn btn-default" ng-click="cancel()" type="button">Cancel</button>
<span class="ml-xxs">
<i class="fa fa-asterisk text-danger"></i>&nbsp;Required field</span>
<!-- <button class="btn btn-primary pull-right" ng-disabled="mfaForm.$invalid" type="submit">Save</button> -->
<button class="btn btn-primary pull-right" ng-disabled="!mfaData.code" type="submit">Save</button>
</div>
</div>
</div>
</div>
</form>

</div>
Expand All @@ -159,4 +220,7 @@ <h3 class="box-title"><input type="checkbox" ng-model="passData.changePassword"
<div class="mt-xxs text-danger" ng-message="compareTo">
Oops, that's not the same password as the first one
</div>
<div class="mt-xxs text-danger" ng-message="mfaInvalid">
Oops, that's not a valid MFA code
</div>
</script>
4 changes: 3 additions & 1 deletion frontend/bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
"file-saver": "1.3.4",
"js-url": "~2.5.3",
"bootstrap-sass": "bootstrap-sass-official#^3.4.1",
"angular-bootstrap-multiselect": "https://github.com/bentorfs/angular-bootstrap-multiselect.git#^1.1.11"
"angular-bootstrap-multiselect": "https://github.com/bentorfs/angular-bootstrap-multiselect.git#^1.1.11",
"qrcode.js": "qrcode#^1.0.2",
"angular-qr": "^0.2.0"
},
"devDependencies": {
"angular-mocks": "1.7.8"
Expand Down
Loading

0 comments on commit 8236056

Please sign in to comment.