1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Kyle Spearrin
3e18f812db lint errors 2017-02-11 18:22:13 -05:00
Kyle Spearrin
8cf02fd59a version bump 2017-02-11 17:12:50 -05:00
Kyle Spearrin
06bfab3afa version bump 2017-02-11 17:12:09 -05:00
Kyle Spearrin
71e4697562 two factor edits 2017-02-11 17:08:06 -05:00
Kyle Spearrin
cf1bffe2f1 change email button 2017-02-11 16:48:52 -05:00
Kyle Spearrin
55a5fd49dc Moved domain rules page out from modal into it's own page 2017-02-11 16:46:24 -05:00
Kyle Spearrin
3f6637eb8f move many account settings into main settings page instead of nav menu 2017-02-11 15:44:22 -05:00
Kyle Spearrin
7373e281ac print recovery code. changed vault and login route 2017-02-11 14:21:21 -05:00
Kyle Spearrin
52b89455d7 replace sjcl cryptoservice implementation with forge 2017-02-11 13:03:48 -05:00
17 changed files with 336 additions and 299 deletions

View File

@@ -17,7 +17,8 @@ var gulp = require('gulp'),
settings = require('./settings.json'),
project = require('./package.json'),
jshint = require('gulp-jshint'),
_ = require('lodash');
_ = require('lodash'),
webpack = require('webpack-stream');
var paths = {};
paths.dist = './dist/';
@@ -42,7 +43,7 @@ gulp.task('lint', function () {
gulp.task('build', function (cb) {
return runSequence(
'clean',
['lib', 'less', 'settings', 'lint'],
['lib', 'webpack', 'less', 'settings', 'lint'],
cb);
});
@@ -142,10 +143,6 @@ gulp.task('lib', ['clean:lib'], function () {
src: paths.npmDir + 'angular-messages/*messages*.js',
dest: paths.libDir + 'angular-messages'
},
{
src: [paths.npmDir + 'sjcl/core/cbc.js', paths.npmDir + 'sjcl/core/bitArray.js', paths.npmDir + 'sjcl/sjcl.js'],
dest: paths.libDir + 'sjcl'
},
{
src: paths.npmDir + 'ngstorage/*.js',
dest: paths.libDir + 'ngstorage'
@@ -178,6 +175,33 @@ gulp.task('lib', ['clean:lib'], function () {
return merge(tasks);
});
gulp.task('webpack', ['webpack:forge']);
gulp.task('webpack:forge', function () {
var forgeDir = paths.npmDir + '/node-forge/lib/';
return gulp.src([
forgeDir + 'pbkdf2.js',
forgeDir + 'aes.js',
forgeDir + 'hmac.js',
forgeDir + 'sha256.js',
forgeDir + 'random.js',
forgeDir + 'forge.js'
]).pipe(webpack({
output: {
filename: 'forge.js',
library: 'forge',
libraryTarget: 'umd'
},
node: {
Buffer: false,
process: false,
crypto: false,
setImmediate: false
}
})).pipe(gulp.dest(paths.libDir + 'forge'));
});
gulp.task('settings', function () {
return config()
.pipe(gulp.dest(paths.webroot + 'app'));
@@ -290,8 +314,6 @@ gulp.task('dist:js:app', function () {
gulp.task('dist:js:lib', function () {
return gulp
.src([
paths.libDir + 'sjcl/sjcl.js',
paths.libDir + 'sjcl/*.js',
paths.libDir + 'angulartics/angulartics.js',
paths.libDir + '**/*.js',
'!' + paths.libDir + '**/*.min.js',

View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden",
"version": "1.8.1",
"version": "1.9.0",
"production": true,
"devDependencies": {
"connect": "3.4.1",
@@ -24,7 +24,6 @@
"jquery": "2.2.4",
"font-awesome": "4.6.3",
"bootstrap": "3.3.6",
"sjcl": "1.0.3",
"angular": "1.5.6",
"angular-resource": "1.5.6",
"angular-bootstrap-npm": "0.14.3",
@@ -42,6 +41,8 @@
"clipboard": "1.5.12",
"ngclipboard": "1.1.1",
"angulartics": "1.1.2",
"angulartics-google-analytics": "0.2.1"
"angulartics-google-analytics": "0.2.1",
"node-forge": "0.7.0",
"webpack-stream": "3.2.0"
}
}

View File

@@ -76,7 +76,7 @@ angular
}
})
.state('backend.vault', {
url: '^/',
url: '^/vault',
templateUrl: 'app/vault/views/vault.html',
controller: 'vaultController',
data: { pageTitle: 'My Vault' }
@@ -87,6 +87,12 @@ angular
controller: 'settingsController',
data: { pageTitle: 'Settings' }
})
.state('backend.settingsDomains', {
url: '^/settings/domains',
templateUrl: 'app/settings/views/settingsDomains.html',
controller: 'settingsDomainsController',
data: { pageTitle: 'Domain Settings' }
})
.state('backend.tools', {
url: '^/tools',
templateUrl: 'app/tools/views/tools.html',
@@ -110,14 +116,14 @@ angular
}
})
.state('frontend.login.info', {
url: '^/login',
url: '^/',
templateUrl: 'app/accounts/views/accountsLoginInfo.html',
data: {
pageTitle: 'Log In'
}
})
.state('frontend.login.twoFactor', {
url: '^/login/two-factor',
url: '^/two-factor',
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
data: {
pageTitle: 'Log In (Two Factor)',

View File

@@ -48,30 +48,6 @@ angular
$scope.$broadcast('vaultAddFolder');
};
$scope.changeEmail = function () {
$scope.$broadcast('settingsChangeEmail');
};
$scope.changePassword = function () {
$scope.$broadcast('settingsChangePassword');
};
$scope.sessions = function () {
$scope.$broadcast('settingsSessions');
};
$scope.domains = function () {
$scope.$broadcast('settingsDomains');
};
$scope.delete = function () {
$scope.$broadcast('settingsDelete');
};
$scope.twoFactor = function () {
$scope.$broadcast('settingsTwoFactor');
};
$scope.import = function () {
$scope.$broadcast('toolsImport');
};

View File

@@ -4,15 +4,11 @@ angular
.factory('cryptoService', function ($sessionStorage) {
var _service = {},
_key,
_b64Key,
_aes,
_aesWithMac;
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
_b64Key;
_service.setKey = function (key) {
_key = key;
$sessionStorage.key = sjcl.codec.base64.fromBits(key);
$sessionStorage.key = forge.util.encode64(key);
};
_service.getKey = function (b64) {
@@ -24,11 +20,11 @@ angular
}
if ($sessionStorage.key) {
_key = sjcl.codec.base64.toBits($sessionStorage.key);
_key = forge.util.decode64($sessionStorage.key);
}
if (b64 && b64 === true) {
_b64Key = sjcl.codec.base64.fromBits(_key);
_b64Key = forge.util.encode64(_key);
return _b64Key;
}
@@ -37,24 +33,29 @@ angular
_service.getEncKey = function (key) {
key = key || _service.getKey();
return key.slice(0, 4);
var buffer = forge.util.createBuffer(key);
return buffer.getBytes(16);
};
_service.getMacKey = function (key) {
key = key || _service.getKey();
return key.slice(4);
var buffer = forge.util.createBuffer(key);
buffer.getBytes(16); // skip first half
return buffer.getBytes(16);
};
_service.clearKey = function () {
_key = _b64Key = _aes = _aesWithMac = null;
_key = _b64Key = null;
delete $sessionStorage.key;
};
_service.makeKey = function (password, salt, b64) {
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);
var key = forge.pbkdf2(password, salt, 5000, 256 / 8, 'sha256');
if (b64 && b64 === true) {
return sjcl.codec.base64.fromBits(key);
return forge.util.encode64(key);
}
return key;
@@ -69,24 +70,8 @@ angular
throw 'Invalid parameters.';
}
var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null);
return sjcl.codec.base64.fromBits(hashBits);
};
_service.getAes = function () {
if (!_aes && _service.getKey()) {
_aes = new sjcl.cipher.aes(_service.getKey());
}
return _aes;
};
_service.getAesWithMac = function () {
if (!_aesWithMac && _service.getKey()) {
_aesWithMac = new sjcl.cipher.aes(_service.getEncKey());
}
return _aesWithMac;
var hashBits = forge.pbkdf2(key, password, 1, 256 / 8, 'sha256');
return forge.util.encode64(hashBits);
};
_service.encrypt = function (plaintextValue, key) {
@@ -103,22 +88,21 @@ angular
encKey = key || _service.getKey();
}
var response = {};
var params = {
mode: 'cbc',
iv: sjcl.random.randomWords(4, 10)
};
var ctJson = sjcl.encrypt(encKey, plaintextValue, params, response);
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
var iv = sjcl.codec.base64.fromBits(response.iv);
var buffer = forge.util.createBuffer(plaintextValue, 'utf8');
var ivBytes = forge.random.getBytesSync(16);
var cipher = forge.cipher.createCipher('AES-CBC', encKey);
cipher.start({ iv: ivBytes });
cipher.update(buffer);
cipher.finish();
var iv = forge.util.encode64(ivBytes);
var ctBytes = cipher.output.getBytes();
var ct = forge.util.encode64(ctBytes);
var cipherString = iv + '|' + ct;
// TODO: Turn on whenever ready to support encrypt-then-mac
if (false) {
var mac = computeMac(ct, response.iv);
var mac = computeMac(ctBytes, ivBytes);
cipherString = cipherString + '|' + mac;
}
@@ -126,7 +110,7 @@ angular
};
_service.decrypt = function (encValue) {
if (!_service.getAes() || !_service.getAesWithMac()) {
if (!_service.getKey()) {
throw 'AES encryption unavailable.';
}
@@ -135,35 +119,33 @@ angular
return '';
}
var ivBits = sjcl.codec.base64.toBits(encPieces[0]);
var ctBits = sjcl.codec.base64.toBits(encPieces[1]);
var ivBytes = forge.util.decode64(encPieces[0]);
var ctBytes = forge.util.decode64(encPieces[1]);
var computedMac = null;
if (encPieces.length === 3) {
computedMac = computeMac(ctBits, ivBits);
computedMac = computeMac(ctBytes, ivBytes);
if (computedMac !== encPieces[2]) {
console.error('MAC failed.');
return '';
}
}
var decBits = sjcl.mode.cbc.decrypt(computedMac ? _service.getAesWithMac() : _service.getAes(), ctBits, ivBits, null);
return sjcl.codec.utf8String.fromBits(decBits);
var ctBuffer = forge.util.createBuffer(ctBytes);
var decipher = forge.cipher.createDecipher('AES-CBC', computedMac ? _service.getEncKey() : _service.getKey());
decipher.start({ iv: ivBytes });
decipher.update(ctBuffer);
decipher.finish();
return decipher.output.toString('utf8');
};
function computeMac(ct, iv) {
if (typeof ct === 'string') {
ct = sjcl.codec.base64.toBits(ct);
}
if (typeof iv === 'string') {
iv = sjcl.codec.base64.toBits(iv);
}
var macKey = _service.getMacKey();
var hmac = new sjcl.misc.hmac(macKey, sjcl.hash.sha256);
var bits = iv.concat(ct);
var mac = hmac.encrypt(bits);
return sjcl.codec.base64.fromBits(mac);
function computeMac(ct, iv, macKey) {
var hmac = forge.hmac.create();
hmac.start('sha256', macKey || _service.getMacKey());
hmac.update(iv + ct);
var mac = hmac.digest();
return forge.util.encode64(mac.getBytes());
}
return _service;

View File

@@ -1,2 +1,2 @@
angular.module("bit")
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","apiUri":"https://api.bitwarden.com","version":"1.8.1","environment":"Production"});
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","apiUri":"https://api.bitwarden.com","version":"1.9.0","environment":"Production"});

View File

@@ -2,20 +2,33 @@
.module('bit.settings')
.controller('settingsController', function ($scope, $uibModal, apiService, toastr, authService) {
$scope.model = {};
$scope.model = {
profile: {},
twoFactorEnabled: false,
email: null
};
apiService.accounts.getProfile({}, function (user) {
$scope.model = {
name: user.Name,
profile: {
name: user.Name,
masterPasswordHint: user.MasterPasswordHint,
culture: user.Culture
},
email: user.Email,
masterPasswordHint: user.MasterPasswordHint,
culture: user.Culture,
twoFactorEnabled: user.TwoFactorEnabled
};
});
$scope.save = function (model) {
$scope.savePromise = apiService.accounts.putProfile({}, model, function (profile) {
$scope.generalSave = function () {
$scope.generalPromise = apiService.accounts.putProfile({}, $scope.model.profile, function (profile) {
authService.setUserProfile(profile);
toastr.success('Account has been updated.', 'Success!');
}).$promise;
};
$scope.passwordHintSave = function () {
$scope.passwordHintPromise = apiService.accounts.putProfile({}, $scope.model.profile, function (profile) {
authService.setUserProfile(profile);
toastr.success('Account has been updated.', 'Success!');
}).$promise;
@@ -29,10 +42,6 @@
});
};
$scope.$on('settingsChangePassword', function (event, args) {
$scope.changePassword();
});
$scope.changeEmail = function () {
$uibModal.open({
animation: true,
@@ -42,21 +51,21 @@
});
};
$scope.$on('settingsChangeEmail', function (event, args) {
$scope.changeEmail();
});
$scope.twoFactor = function () {
$uibModal.open({
var twoFactorModal = $uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsTwoFactor.html',
controller: 'settingsTwoFactorController'
});
};
$scope.$on('settingsTwoFactor', function (event, args) {
$scope.twoFactor();
});
twoFactorModal.result.then(function (enabled) {
if (enabled === null) {
return;
}
$scope.model.twoFactorEnabled = enabled;
});
};
$scope.sessions = function () {
$uibModal.open({
@@ -66,22 +75,6 @@
});
};
$scope.$on('settingsSessions', function (event, args) {
$scope.sessions();
});
$scope.domains = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsDomains.html',
controller: 'settingsDomainsController'
});
};
$scope.$on('settingsDomains', function (event, args) {
$scope.domains();
});
$scope.delete = function () {
$uibModal.open({
animation: true,
@@ -90,8 +83,4 @@
size: 'sm'
});
};
$scope.$on('settingsDelete', function (event, args) {
$scope.delete();
});
});

View File

@@ -1,9 +1,7 @@
angular
.module('bit.settings')
.controller('settingsDomainsController', function ($scope, $state, apiService, $uibModalInstance, toastr, $analytics, $uibModal) {
$analytics.eventTrack('settingsDomainsController', { category: 'Modal' });
.controller('settingsDomainsController', function ($scope, $state, apiService, toastr, $analytics, $uibModal) {
$scope.globalEquivalentDomains = [];
$scope.equivalentDomains = [];
@@ -44,7 +42,6 @@
animation: true,
templateUrl: 'app/settings/views/settingsAddEditEquivalentDomain.html',
controller: 'settingsAddEditEquivalentDomainController',
size: 'sm',
resolve: {
domainIndex: function () { return i; },
domains: function () { return i !== null ? $scope.equivalentDomains[i] : null; }
@@ -65,7 +62,15 @@
});
};
$scope.save = function () {
$scope.saveGlobal = function () {
$scope.globalPromise = save();
};
$scope.saveCustom = function () {
$scope.customPromise = save();
};
var save = function () {
var request = {
ExcludedGlobalEquivalentDomains: [],
EquivalentDomains: []
@@ -89,13 +94,8 @@
request.ExcludedGlobalEquivalentDomains = null;
}
$scope.submitPromise = apiService.settings.putDomains(request, function (domains) {
$scope.close();
return apiService.settings.putDomains(request, function (domains) {
toastr.success('Domains have been updated.', 'Success!');
}).$promise;
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -74,7 +74,15 @@
}).$promise;
};
$scope.print = function (printContent) {
var w = window.open();
w.document.write('<div style="font-size: 18px; text-align: center;"><p>bitwarden two-step login recovery code:</p>' +
'<pre>' + printContent + '</pre>');
w.print();
w.close();
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
$uibModalInstance.close(!_profile.extended ? null : _profile.extended.twoFactorEnabled);
};
});

View File

@@ -9,33 +9,28 @@
<div class="box-header with-border">
<h3 class="box-title">General</h3>
</div>
<form role="form" name="profileForm" ng-submit="profileForm.$valid && save(model)" api-form="savePromise">
<form role="form" name="generalForm" ng-submit="generalForm.$valid && generalSave()" api-form="generalPromise">
<div class="box-body">
<div class="row">
<div class="col-sm-9">
<div class="callout callout-danger validation-errors" ng-show="profileForm.$errors">
<div class="callout callout-danger validation-errors" ng-show="generalForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in profileForm.$errors">{{e}}</li>
<li ng-repeat="e in generalForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="name">Name</label>
<input type="text" id="name" name="Name" ng-model="model.name" class="form-control"
<input type="text" id="name" name="Name" ng-model="model.profile.name" class="form-control"
required api-field />
</div>
<div class="form-group">
<label for="email">Email - <a href="javascript:void(0)" ng-click="changeEmail()">change</a></label>
<input type="text" id="email" ng-model="model.email" class="form-control" readonly />
</div>
<div class="form-group" show-errors>
<label for="hint">Master Password Hint</label>
<input type="text" id="hint" name="MasterPasswordHint" ng-model="model.masterPasswordHint"
class="form-control" api-field />
</div>
<div class="form-group" show-errors>
<label for="culture">Language/Culture</label>
<select id="culture" name="Culture" ng-model="model.culture" class="form-control" api-field>
<select id="culture" name="Culture" ng-model="model.profile.culture" class="form-control" api-field>
<option value="en-US">English (US)</option>
</select>
</div>
@@ -51,10 +46,83 @@
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="profileForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="profileForm.$loading"></i>Save
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="generalForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="generalForm.$loading"></i>Save
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="changeEmail()">
Change Email
</button>
</div>
</form>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Master Password</h3>
</div>
<form role="form" name="masterPasswordForm" ng-submit="masterPasswordForm.$valid && passwordHintSave()"
api-form="passwordHintPromise">
<div class="box-body">
<div class="row">
<div class="col-sm-9">
<div class="callout callout-danger validation-errors" ng-show="masterPasswordForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in masterPasswordForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="hint">Master Password Hint</label>
<input type="text" id="hint" name="MasterPasswordHint" ng-model="model.profile.masterPasswordHint"
class="form-control" api-field />
</div>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="masterPasswordForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="masterPasswordForm.$loading"></i>Save
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="changePassword()">
Change Master Password
</button>
</div>
</form>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Two-step Log In</h3>
</div>
<div class="box-body">
<p>
Current Status:
<span class="label bg-green" ng-show="model.twoFactorEnabled">ENABLED</span>
<span class="label bg-gray" ng-show="!model.twoFactorEnabled">DISABLED</span>
</p>
<p>
Two-step login helps keep your account more secure by requiring a code provided by an app on your mobile device
while logging in (in addition to your master password).
</p>
</div>
<div class="box-footer">
<button type="button" class="btn btn-default btn-flat" ng-click="twoFactor()">
Manage Two-step Log In
</button>
</div>
</div>
<div class="box box-danger">
<div class="box-header with-border">
<h3 class="box-title">Danger Zone</h3>
</div>
<div class="box-body">
Careful, these actions are not reversible!
</div>
<div class="box-footer">
<button type="submit" class="btn btn-default btn-flat" ng-click="sessions()">
Deauthorize Sessions
</button>
<button type="submit" class="btn btn-default btn-flat" ng-click="delete()">
Delete Account
</button>
</div>
</div>
</section>

View File

@@ -17,10 +17,12 @@
<label for="name">Domains</label> <span>*</span>
<textarea id="domains" name="Domains" ng-model="domains" class="form-control" placeholder="ex. google.com, gmail.com"
style="height: 100px;" required></textarea>
<p class="help-block">
Only "base" domains are allowed. Do not enter subdomains.
For example, enter "google.com" instead of "www.google.com".
Only "base" domains are allowed. Do not enter subdomains. For example, enter "google.com" instead of
"www.google.com".
</p>
<p class="help-block">
You can also enter "androidapp://package.name" to associate an android app with other website domains.
</p>
</div>
</div>

View File

@@ -1,83 +1,94 @@
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><i class="fa fa-globe"></i> Domain Rules</h4>
</div>
<form name="domainsForm" ng-submit="domainsForm.$valid && save()" api-form="submitPromise">
<div class="modal-body">
<p>
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
"Global" domains are ones created for you by bitwarden.
</p>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th colspan="2">Global Equivalent Domains</th>
</tr>
</thead>
<tbody ng-if="globalEquivalentDomains.length">
<tr ng-repeat="globalDomain in globalEquivalentDomains">
<td style="width: 80px; min-width: 80px;">
<button type="button" class="btn btn-link btn-table" uib-tooltip="Exclude"
ng-if="!globalDomain.excluded" ng-click="toggleExclude(globalDomain)">
<i class="fa fa-lg fa-ban"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Include"
ng-if="globalDomain.excluded" ng-click="toggleExclude(globalDomain)">
<i class="fa fa-lg fa-plus"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Customize"
ng-click="customize(globalDomain)">
<i class="fa fa-lg fa-cut"></i>
</button>
</td>
<td ng-class="{strike: globalDomain.excluded}">{{globalDomain.domains}}</td>
</tr>
</tbody>
<tbody ng-if="!globalEquivalentDomains.length">
<tr>
<td>No domains to list.</td>
</tr>
</tbody>
</table>
<section class="content-header">
<h1>Domain Rules</h1>
</section>
<section class="content">
<p>
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
"Global" domains are ones already created for you by bitwarden.
</p>
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Custom Equivalent Domains</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" ng-click="addEdit(null)">
<i class="fa fa-plus"></i> Add New
</button>
</div>
</div>
<div class="box-body no-padding">
<div class="table-responsive">
<table class="table table-striped table-hover">
<tbody ng-if="equivalentDomains.length">
<tr ng-repeat="customDomain in equivalentDomains track by $index">
<td style="width: 80px; min-width: 80px;">
<button type="button" class="btn btn-link btn-table" uib-tooltip="Edit" ng-click="addEdit($index)">
<i class="fa fa-lg fa-pencil"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Delete" ng-click="delete($index)">
<i class="fa fa-lg fa-trash"></i>
</button>
</td>
<td>{{customDomain}}</td>
</tr>
</tbody>
<tbody ng-if="!equivalentDomains.length">
<tr>
<td>No domains to list.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="customForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="customForm.$loading"></i>Save
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th colspan="2">
Custom Equivalent Domains
<a href="javascript:void(0)" ng-click="addEdit(null)">
<i class="fa fa-plus"></i> Add New
</a>
</th>
</tr>
</thead>
<tbody ng-if="equivalentDomains.length">
<tr ng-repeat="customDomain in equivalentDomains track by $index">
<td style="width: 80px; min-width: 80px;">
<button type="button" class="btn btn-link btn-table" uib-tooltip="Edit" ng-click="addEdit($index)">
<i class="fa fa-lg fa-pencil"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Delete" ng-click="delete($index)">
<i class="fa fa-lg fa-trash"></i>
</button>
</td>
<td>{{customDomain}}</td>
</tr>
</tbody>
<tbody ng-if="!equivalentDomains.length">
<tr>
<td>No domains to list.</td>
</tr>
</tbody>
</table>
</form>
<form name="globalForm" ng-submit="globalForm.$valid && saveGlobal()" api-form="globalPromise">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Global Equivalent Domains</h3>
</div>
<div class="box-body no-padding">
<div class="table-responsive">
<table class="table table-striped table-hover">
<tbody ng-if="globalEquivalentDomains.length">
<tr ng-repeat="globalDomain in globalEquivalentDomains">
<td style="width: 80px; min-width: 80px;">
<button type="button" class="btn btn-link btn-table" uib-tooltip="Exclude"
ng-if="!globalDomain.excluded" ng-click="toggleExclude(globalDomain)">
<i class="fa fa-lg fa-ban"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Include"
ng-if="globalDomain.excluded" ng-click="toggleExclude(globalDomain)">
<i class="fa fa-lg fa-plus"></i>
</button>
<button type="button" class="btn btn-link btn-table" uib-tooltip="Customize"
ng-click="customize(globalDomain)">
<i class="fa fa-lg fa-cut"></i>
</button>
</td>
<td ng-class="{strike: globalDomain.excluded}">{{globalDomain.domains}}</td>
</tr>
</tbody>
<tbody ng-if="!globalEquivalentDomains.length">
<tr>
<td>No domains to list.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="globalForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="globalForm.$loading"></i>Save
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="domainsForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="domainsForm.$loading"></i>Save
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>
</form>
</section>

View File

@@ -1,13 +1,11 @@
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="twoFactorModelLabel"><i class="fa fa-key"></i> Two-step Login</h4>
<h4 class="modal-title" id="twoFactorModelLabel"><i class="fa fa-key"></i> Two-step Log In</h4>
</div>
<form name="authTwoStepForm" ng-submit="authTwoStepForm.$valid && auth(authModel)" api-form="authPromise" ng-if="!twoFactorModel">
<div class="modal-body">
<p>Current Status: <span class="label bg-green" ng-show="enabled()">ENABLED</span><span class="label bg-gray" ng-show="!enabled()">DISABLED</span></p>
<p>Two-step login helps keep your account more secure by requiring a code provided by an app on your mobile device while logging in (in addition to your master password).</p>
<p ng-show="enabled()">Two-step login is already enabled on your account. To access your two-step settings enter your master password below.</p>
<p ng-show="!enabled()">To get started with two-step login enter your master password below.</p>
<p ng-show="enabled()">Two-step log in is already enabled on your account. To access your two-step settings enter your master password below.</p>
<p ng-show="!enabled()">To get started with two-step log in enter your master password below.</p>
<div class="callout callout-danger validation-errors" ng-show="authTwoStepForm.$errors">
<h4>Errors have occured</h4>
<ul>
@@ -29,7 +27,7 @@
<form name="updateTwoStepForm" ng-submit="updateTwoStepForm.$valid && update(updateModel)" api-form="updatePromise" ng-if="twoFactorModel">
<div class="modal-body">
<div ng-show="enabled()">
<p>Two-step login is <strong class="text-green">enabled</strong> on your account. Below is the code required by your verification app.</p>
<p>Two-step log in is <strong class="text-green">enabled</strong> on your account. Below is the code required by your verification app.</p>
<p>Need a two-step verification app? Download one of the following:</p>
</div>
<div ng-show="!enabled()">
@@ -58,17 +56,18 @@
</div>
<div ng-show="enabled()">
<hr />
<h4>Recovery Code</h4>
<p>
The recovery code allows you to access your account in the event that you lose your authenticator app.
bitwarden support won't be able to assist you if you lose access to your account. We recommend you write down or
print the recovery code below and keep it in a safe place.
</p>
<ul class="list-unstyled">
<li>
<strong>Recovery Code:</strong> <code>{{twoFactorModel.recovery}}</code>
</li>
</ul>
<div class="callout callout-danger">
<h4><i class="fa fa-warning"></i> Recovery Code <i class="fa fa-warning"></i></h4>
<p>
The recovery code allows you to access your account in the event that you lose your authenticator app.
bitwarden support won't be able to assist you if you lose access to your account. We recommend you write down or
print the recovery code below and keep it in a safe place.
</p>
<p><strong>Recovery Code:</strong> <code>{{twoFactorModel.recovery}}</code></p>
<button type="button" class="btn btn-default" ng-click="print(twoFactorModel.recovery)">
<i class="fa fa-print"></i> Print
</button>
</div>
</div>
<div class="callout callout-danger validation-errors" ng-show="updateTwoStepForm.$errors">
<h4>Errors have occured</h4>
@@ -77,12 +76,12 @@
</ul>
</div>
<hr ng-show="enabled()" />
<h4 style="margin-top: 30px;"><span ng-show="!enabled()">3. </span>Enter the resulting verification code from the app</h4>
<h4 style="margin-top: 30px;"><span ng-show="enabled()">Want to disable? </span><span ng-show="!enabled()">3. </span>Enter the resulting verification code from the app</h4>
<div class="form-group" show-errors>
<label for="token" class="sr-only">Verification Code</label>
<input type="text" id="token" name="Token" placeholder="Verification Code" ng-model="updateModel.token" class="form-control" required api-field />
</div>
<p ng-show="!enabled()">NOTE: After enabling two-step login, you will be required to enter the current code generated by your verification app each time you log in.</p>
<p ng-show="!enabled()">NOTE: After enabling two-step log in, you will be required to enter the current code generated by your verification app each time you log in.</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="updateTwoStepForm.$loading">

View File

@@ -21,7 +21,7 @@
$scope.generatePassword = function () {
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
$analytics.eventTrack('Generated Password From Add');
$scope.login.password = passwordService.generatePassword({ length: 10, special: true });
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
}
};

View File

@@ -25,7 +25,7 @@
$scope.generatePassword = function () {
if (!$scope.login.password || confirm('Are you sure you want to overwrite the current password?')) {
$analytics.eventTrack('Generated Password From Edit');
$scope.login.password = passwordService.generatePassword({ length: 10, special: true });
$scope.login.password = passwordService.generatePassword({ length: 12, special: true });
}
};

View File

@@ -56,39 +56,15 @@
</li>
</ul>
</li>
<li class="treeview" ng-class="{active: $state.includes('backend.settings')}">
<li class="treeview"
ng-class="{active: $state.includes('backend.settings') || $state.includes('backend.settingsDomains')}">
<a ui-sref="backend.settings"><i class="fa fa-cogs"></i> <span>Settings</span></a>
<ul class="treeview-menu">
<li>
<a href="javascript:void(0)" ng-click="changePassword()">
<i class="fa fa-circle-o"></i> Change Password
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="changeEmail()">
<i class="fa fa-circle-o"></i> Change Email
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="sessions()">
<i class="fa fa-circle-o"></i> Deauthorize Sessions
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="twoFactor()">
<i class="fa fa-circle-o"></i> Two-step Login
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="domains()">
<a ui-sref="backend.settingsDomains">
<i class="fa fa-circle-o"></i> Domain Rules
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="delete()">
<i class="fa fa-circle-o"></i> Delete Account
</a>
</li>
</ul>
</li>
<li class="treeview" ng-class="{active: $state.includes('backend.tools')}">

View File

@@ -55,10 +55,7 @@
<script src="lib/bootstrap/js/bootstrap.js"></script>
<script src="lib/admin-lte/js/app.js"></script>
<script src="lib/sjcl/sjcl.js"></script>
<script src="lib/sjcl/cbc.js"></script>
<script src="lib/sjcl/bitArray.js"></script>
<script src="lib/forge/forge.js"></script>
<script src="lib/papaparse/papaparse.js"></script>
<script src="lib/clipboard/clipboard.js"></script>