diff --git a/gulpfile.js b/gulpfile.js
index 16186a93f68..090cdd4f2a1 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -177,6 +177,10 @@ gulp.task('lib', ['clean:lib'], function () {
paths.npmDir + 'angulartics/src/angulartics.js'
],
dest: paths.libDir + 'angulartics'
+ },
+ {
+ src: paths.npmDir + 'duo_web_sdk/index.js',
+ dest: paths.libDir + 'duo'
}
];
diff --git a/package.json b/package.json
index 03e840decde..4259a1f2dce 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"browserify": "14.1.0",
"vinyl-source-stream": "1.1.0",
"gulp-derequire": "2.1.0",
- "exposify": "0.5.0"
+ "exposify": "0.5.0",
+ "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git"
}
}
diff --git a/src/app/accounts/accountsLoginController.js b/src/app/accounts/accountsLoginController.js
index 1917538c882..cc25604c2a6 100644
--- a/src/app/accounts/accountsLoginController.js
+++ b/src/app/accounts/accountsLoginController.js
@@ -57,6 +57,7 @@ angular
$state.go('frontend.login.twoFactor', { returnState: returnState }).then(function () {
$timeout(function () {
$("#code").focus();
+ init();
});
});
}
@@ -90,6 +91,7 @@ angular
$scope.twoFactorProvider = provider;
$timeout(function () {
$("#code").focus();
+ init();
});
});
};
@@ -102,4 +104,19 @@ angular
$state.go('backend.user.vault');
}
}
+
+ function init() {
+ if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
+ var params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
+
+ Duo.init({
+ host: params.Host,
+ sig_request: params.Signature,
+ submit_callback: function (theForm) {
+ var response = $(theForm).find('input[name="sig_response"]').val();
+ $scope.twoFactor(response);
+ }
+ });
+ }
+ }
});
diff --git a/src/app/accounts/views/accountsLoginTwoFactor.html b/src/app/accounts/views/accountsLoginTwoFactor.html
index ead9d22fc80..aef91aadf5f 100644
--- a/src/app/accounts/views/accountsLoginTwoFactor.html
+++ b/src/app/accounts/views/accountsLoginTwoFactor.html
@@ -57,3 +57,30 @@
+
+
+
+ Complete logging in with Duo.
+
+
+
diff --git a/src/app/services/apiService.js b/src/app/services/apiService.js
index 511ec7f222b..27b7e404c18 100644
--- a/src/app/services/apiService.js
+++ b/src/app/services/apiService.js
@@ -114,11 +114,13 @@
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
list: { method: 'GET', params: {} },
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
+ getDuo: { url: _apiUri + '/two-factor/get-duo', method: 'POST', params: {} },
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
putEmail: { url: _apiUri + '/two-factor/email', method: 'POST', params: {} },
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
+ putDuo: { url: _apiUri + '/two-factor/duo', method: 'POST', params: {} },
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
diff --git a/src/app/settings/settingsTwoStepAuthenticatorController.js b/src/app/settings/settingsTwoStepAuthenticatorController.js
index bec1dfa4a78..97eb22cb43a 100644
--- a/src/app/settings/settingsTwoStepAuthenticatorController.js
+++ b/src/app/settings/settingsTwoStepAuthenticatorController.js
@@ -7,7 +7,7 @@
var _issuer = 'bitwarden',
_profile = null,
_masterPasswordHash
- _key = null;
+ _key = null;
$scope.auth = function (model) {
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
@@ -69,6 +69,7 @@
}, function (response) {
$analytics.eventTrack('Disabled Two-step Authenticator');
toastr.success('Authenticator app has been disabled.');
+ $scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
@@ -86,6 +87,6 @@
}
$scope.close = function () {
- $uibModalInstance.close();
+ $uibModalInstance.close($scope.enabled);
};
});
diff --git a/src/app/settings/settingsTwoStepController.js b/src/app/settings/settingsTwoStepController.js
index 26aed1cb561..620c22f2192 100644
--- a/src/app/settings/settingsTwoStepController.js
+++ b/src/app/settings/settingsTwoStepController.js
@@ -65,8 +65,8 @@
}
});
- authenticatorModal.result.then(function () {
-
+ authenticatorModal.result.then(function (enabled) {
+ provider.enabled = enabled;
});
}
else if(provider.type === constants.twoFactorProvider.email) {
@@ -79,8 +79,8 @@
}
});
- emailModal.result.then(function () {
-
+ emailModal.result.then(function (enabled) {
+ provider.enabled = enabled;
});
}
else if (provider.type === constants.twoFactorProvider.yubikey) {
@@ -93,8 +93,22 @@
}
});
- yubiModal.result.then(function () {
+ yubiModal.result.then(function (enabled) {
+ provider.enabled = enabled;
+ });
+ }
+ else if (provider.type === constants.twoFactorProvider.duo) {
+ var yubiModal = $uibModal.open({
+ animation: true,
+ templateUrl: 'app/settings/views/settingsTwoStepDuo.html',
+ controller: 'settingsTwoStepDuoController',
+ resolve: {
+ enabled: function () { return provider.enabled; }
+ }
+ });
+ yubiModal.result.then(function (enabled) {
+ provider.enabled = enabled;
});
}
};
diff --git a/src/app/settings/settingsTwoStepDuoController.js b/src/app/settings/settingsTwoStepDuoController.js
new file mode 100644
index 00000000000..e073d6b0467
--- /dev/null
+++ b/src/app/settings/settingsTwoStepDuoController.js
@@ -0,0 +1,75 @@
+angular
+ .module('bit.settings')
+
+ .controller('settingsTwoStepDuoController', function ($scope, apiService, $uibModalInstance, cryptoService,
+ toastr, $analytics, constants) {
+ $analytics.eventTrack('settingsTwoStepDuoController', { category: 'Modal' });
+ var _masterPasswordHash;
+
+ $scope.updateModel = {
+ token: null,
+ host: null,
+ ikey: null,
+ skey: null
+ };
+
+ $scope.auth = function (model) {
+ _masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
+ $scope.authPromise = apiService.twoFactor.getDuo({}, {
+ masterPasswordHash: _masterPasswordHash
+ }).$promise.then(function (apiResponse) {
+ processResult(apiResponse);
+ $scope.authed = true;
+ });
+ };
+
+ $scope.submit = function (model) {
+ if ($scope.enabled) {
+ disable();
+ return;
+ }
+
+ update(model);
+ };
+
+ function disable() {
+ if (!confirm('Are you sure you want to disable the Duo provider?')) {
+ return;
+ }
+
+ $scope.submitPromise = apiService.twoFactor.disable({}, {
+ masterPasswordHash: _masterPasswordHash,
+ type: constants.twoFactorProvider.duo
+ }, function (response) {
+ $analytics.eventTrack('Disabled Two-step Duo');
+ toastr.success('Duo has been disabled.');
+ $scope.enabled = response.Enabled;
+ $scope.close();
+ }).$promise;
+ }
+
+ function update(model) {
+ $scope.submitPromise = apiService.twoFactor.putDuo({}, {
+ integrationKey: model.ikey,
+ secretKey: model.skey,
+ host: model.host,
+ masterPasswordHash: _masterPasswordHash
+ }, function (response) {
+ $analytics.eventTrack('Enabled Two-step Duo');
+ processResult(response);
+ }).$promise;
+ }
+
+ function processResult(response) {
+ $scope.enabled = response.Enabled;
+ $scope.updateModel = {
+ ikey: response.IntegrationKey,
+ skey: response.SecretKey,
+ host: response.Host
+ };
+ }
+
+ $scope.close = function () {
+ $uibModalInstance.close($scope.enabled);
+ };
+ });
diff --git a/src/app/settings/settingsTwoStepEmailController.js b/src/app/settings/settingsTwoStepEmailController.js
index 04d30409bef..5eb0dfb8075 100644
--- a/src/app/settings/settingsTwoStepEmailController.js
+++ b/src/app/settings/settingsTwoStepEmailController.js
@@ -74,6 +74,7 @@
}, function (response) {
$analytics.eventTrack('Disabled Two-step Email');
toastr.success('Email has been disabled.');
+ $scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
@@ -92,6 +93,6 @@
}
$scope.close = function () {
- $uibModalInstance.close();
+ $uibModalInstance.close($scope.enabled);
};
});
diff --git a/src/app/settings/settingsTwoStepYubiController.js b/src/app/settings/settingsTwoStepYubiController.js
index 77943fe7361..a9f606c74b7 100644
--- a/src/app/settings/settingsTwoStepYubiController.js
+++ b/src/app/settings/settingsTwoStepYubiController.js
@@ -56,6 +56,7 @@
$scope.disableLoading = false;
$analytics.eventTrack('Disabled Two-step YubiKey');
toastr.success('YubiKey has been disabled.');
+ $scope.enabled = response.Enabled;
$scope.close();
}, function (response) {
toastr.error('Failed to disable.');
@@ -102,6 +103,6 @@
}
$scope.close = function () {
- $uibModalInstance.close();
+ $uibModalInstance.close($scope.enabled);
};
});
diff --git a/src/app/settings/views/settingsTwoStepDuo.html b/src/app/settings/views/settingsTwoStepDuo.html
new file mode 100644
index 00000000000..82fc6a6ac42
--- /dev/null
+++ b/src/app/settings/views/settingsTwoStepDuo.html
@@ -0,0 +1,76 @@
+
+
+
diff --git a/src/images/loading.svg b/src/images/loading.svg
new file mode 100644
index 00000000000..70763105168
--- /dev/null
+++ b/src/images/loading.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/index.html b/src/index.html
index 41f1e7f806b..c93b7243bf5 100644
--- a/src/index.html
+++ b/src/index.html
@@ -29,10 +29,12 @@
https://fonts.gstatic.com;
child-src
'self'
- https://js.stripe.com;
+ https://js.stripe.com
+ https://*.duosecurity.com;
frame-src
'self'
- https://js.stripe.com;
+ https://js.stripe.com
+ https://*.duosecurity.com;
connect-src
*;">
@@ -85,6 +87,7 @@
+
@@ -190,6 +193,7 @@
+
diff --git a/src/less/vault.less b/src/less/vault.less
index 4697726081d..48c4ce67265 100644
--- a/src/less/vault.less
+++ b/src/less/vault.less
@@ -560,3 +560,24 @@ h1, h2, h3, h4, h5, h6 {
background-position: -73px -88px;
}
}
+
+#duoFrameWrapper {
+ margin: 0 -36px 10px -36px;
+ background: ~"url('../images/loading.svg') 0 0 no-repeat";
+
+ @media (min-width: 360px) {
+ margin: 0 -10px 10px -10px;
+ }
+
+ @media (min-width: @screen-sm) {
+ margin: 0 0 10px;
+ }
+
+ iframe {
+ width: 100%;
+ min-width: 304px;
+ max-width: 620px;
+ height: 500px;
+ border: none;
+ }
+}