1
0
mirror of https://github.com/bitwarden/web synced 2025-12-23 03:33:31 +00:00

Compare commits

..

55 Commits

Author SHA1 Message Date
Kyle Spearrin
8bb4132458 version bump 2018-03-30 10:56:24 -04:00
Kyle Spearrin
443822fd52 step down from host root LUID 2018-03-27 22:56:50 -04:00
Kyle Spearrin
68427fd2de bash 2018-03-27 21:15:16 -04:00
Kyle Spearrin
c3d3369601 proper and syntax for entrypoint conditions 2018-03-27 17:11:48 -04:00
Kyle Spearrin
3c5022d628 upsert bitwarden user 2018-03-27 16:37:50 -04:00
Kyle Spearrin
832ddddc58 gosu 2018-03-27 15:44:25 -04:00
Kyle Spearrin
0fc1415a06 chown deep directories 2018-03-26 14:30:37 -04:00
Kyle Spearrin
1ab408c591 non-root docker 2018-03-26 11:24:09 -04:00
Kyle Spearrin
3160d3f275 disable uglify for now 2018-03-24 20:55:00 -04:00
Kyle Spearrin
d083f1ddc3 version bump and lint fix 2018-03-24 20:49:20 -04:00
Kyle Spearrin
5fbc09b135 cannot create item in collection.
set collection after share.
2018-03-24 20:44:51 -04:00
Kyle Spearrin
6282fabf98 use bitwarden user for docker 2018-03-23 21:21:01 -04:00
Kyle Spearrin
2b528bad97 version json file on dist 2018-03-23 13:04:59 -04:00
Kyle Spearrin
c3be8195fd no edit/del of "no folder" 2018-03-20 15:58:00 -04:00
Kyle Spearrin
39471d0421 loading ciphers false after first chunk 2018-03-19 11:33:52 -04:00
Kyle Spearrin
7a50c0536c loading switches for cipher and groupings 2018-03-19 11:28:23 -04:00
Kyle Spearrin
4ccd9501a8 add back missing select function 2018-03-19 11:14:28 -04:00
Kyle Spearrin
75c05a4a85 version bump in settings 2018-03-17 12:03:07 -04:00
Kyle Spearrin
ca7e12370f version bump 2018-03-17 12:02:00 -04:00
Kyle Spearrin
8bc9dafff2 vault fixes 2018-03-17 12:01:03 -04:00
Kyle Spearrin
dcb0416fd6 re-factor vault listings 2018-03-17 11:42:35 -04:00
Kyle Spearrin
bbb69bba26 Update ISSUE_TEMPLATE.md 2018-03-10 16:36:53 -05:00
Kyle Spearrin
c1838b48ff Create ISSUE_TEMPLATE.md 2018-03-10 09:48:22 -05:00
Kyle Spearrin
d53f40002c totp-col breaks at sm, not md 2018-03-09 23:07:43 -05:00
Kyle Spearrin
866954b180 fix lint issues 2018-03-09 16:42:10 -05:00
Kyle Spearrin
befa9cbf08 version bump 2018-03-09 16:39:17 -05:00
Kyle Spearrin
859f44db43 only perpend http if there is no protocol 2018-03-05 22:15:22 -05:00
Kyle Spearrin
cca9c3c561 get rid of apps page and link to bitwarden.com 2018-03-02 22:42:32 -05:00
Kyle Spearrin
27e68e4c75 multi uri support for import/export 2018-03-02 22:13:53 -05:00
Kyle Spearrin
5c92350ed2 refactor for cipher response. add login uris. 2018-03-02 21:12:26 -05:00
Kyle Spearrin
b94c62d1e5 upadte security md 2018-02-27 23:00:10 -05:00
Kyle Spearrin
de888d8a37 remove pwnedtest 2018-02-27 22:42:39 -05:00
Kyle Spearrin
f8d6816101 Uppercase Bitwarden 2018-02-27 22:41:27 -05:00
Kyle Spearrin
119c6d5817 big-textarea not important 2018-02-27 08:21:26 -05:00
Kyle Spearrin
aaa21daa29 only intercept with headers when api is at start 2018-02-26 23:18:03 -05:00
Kyle Spearrin
10f41bf288 pwned test 2018-02-26 22:52:56 -05:00
Kyle Spearrin
91582691d8 whiteListedDomains for jwt 2018-02-26 13:48:26 -05:00
Kyle Spearrin
463efc2254 use new admin apis for attachments 2018-02-24 14:36:13 -05:00
Kyle Spearrin
0333354271 version bump 2018-02-20 23:34:10 -05:00
Kyle Spearrin
b85f56c681 restore collection ids on edit. resolves #174 2018-02-09 10:39:18 -05:00
Kyle Spearrin
be491be2cd Update organizationBilling.html 2018-02-04 16:00:00 -05:00
Kyle Spearrin
4be4a8115d Update settingsBilling.html 2018-02-04 15:58:41 -05:00
Kyle Spearrin
c0eb499f4d value.type should not be case sensitive 2018-01-26 11:55:57 -05:00
Kyle Spearrin
1b43f3facd check for empty name on SIC importer 2018-01-25 21:22:17 -05:00
Chuck
26d41d3cb9 Change npm to use https for gulp-gh-pages restore. (#168)
When using VS 2017 node.js integration, npm fails because a host key cannot be validated. Switching to https, provides security and no additional configuration to restore the package.
2018-01-23 11:43:51 -05:00
Kyle Spearrin
179765f6e4 use random bytes for each HMAC comparison 2018-01-18 12:07:32 -05:00
Kyle Spearrin
df2e332134 macBuf must exist if key has macKey 2018-01-18 09:03:51 -05:00
Kyle Spearrin
2952f9d158 manifest.json included with dist 2018-01-02 23:54:10 -05:00
Kyle Spearrin
3c9face597 disable autocomplete on duo and yubi setup 2018-01-02 23:38:54 -05:00
Kyle Spearrin
25f2e9c1b7 autocomplete="new-password" to disable autofilling 2018-01-02 22:49:05 -05:00
Kyle Spearrin
a6f8e1b9a3 duo connector moved to its own js file 2018-01-02 13:20:58 -05:00
Kyle Spearrin
d832031cec update cdn libs 2017-12-29 09:45:44 -05:00
Kyle Spearrin
7a1a3ab64d revert uglify removal 2017-12-29 09:28:49 -05:00
Kyle Spearrin
19491a684e additional user/pw field names for roboform 2017-12-29 08:43:07 -05:00
Kyle Spearrin
757224287e disable uglify since it seems to be conflicting 2017-12-29 08:40:37 -05:00
66 changed files with 10613 additions and 987 deletions

1
.gitignore vendored
View File

@@ -199,5 +199,4 @@ FakesAssemblies/
*.opt
# Other
package-lock.json
src/js/*.min.js

View File

@@ -1,10 +1,17 @@
FROM bitwarden/server
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/*
ENV ASPNETCORE_URLS http://+:5000
WORKDIR /app
EXPOSE 5000
COPY ./dist .
EXPOSE 80
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
RUN groupadd -g 999 bitwarden \
&& chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

5
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,5 @@
<!--
Please do not submit feature requests. The [Community Forums][1] has a
section for submitting, voting for, and discussing product feature requests.
[1]: https://community.bitwarden.com
-->

View File

@@ -1,8 +1,8 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/web) [![DockerHub](https://img.shields.io/docker/pulls/bitwarden/web.svg)](https://hub.docker.com/u/bitwarden/) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
# bitwarden Web
# Bitwarden Web Vault
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
The Bitwarden web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />

View File

@@ -1,4 +1,4 @@
bitwarden believes that working with security researchers across the globe is crucial to keeping our
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
# In-scope
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
The following bug classes are out-of scope:
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under bitwarden's control
- Vulnerabilities in outdated versions of bitwarden
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of bitwarden staff or contractors
- Any physical attempts against bitwarden property or data centers
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
Thank you for helping keep bitwarden and our users safe!
Thank you for helping keep Bitwarden and our users safe!

2
dist/.publish vendored

Submodule dist/.publish updated: 1990d717ea...66eacea870

View File

@@ -1,5 +1,28 @@
#!/bin/sh
#!/bin/bash
USERNAME="bitwarden"
NOUSER=`id -u $USERNAME > /dev/null 2>&1; echo $?`
LUID=${LOCAL_UID:-999}
# Step down from host root
if [ $LUID == 0 ]
then
LUID=999
fi
if [ $NOUSER == 0 ] && [ `id -u $USERNAME` != $LUID ]
then
usermod -u $LUID $USERNAME
elif [ $NOUSER == 1 ]
then
useradd -r -u $LUID -g $USERNAME $USERNAME
fi
chown -R $USERNAME:$USERNAME /etc/bitwarden
cp /etc/bitwarden/web/settings.js /app/js/settings.js
cp /etc/bitwarden/web/app-id.json /app/app-id.json
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false
chown -R $USERNAME:$USERNAME /app
chown -R $USERNAME:$USERNAME /bitwarden_server
gosu $USERNAME:$USERNAME dotnet /bitwarden_server/Server.dll \
/contentRoot=/app /webRoot=. /serveUnknown=false

View File

@@ -12,6 +12,7 @@ var gulp = require('gulp'),
ngAnnotate = require('gulp-ng-annotate'),
preprocess = require('gulp-preprocess'),
runSequence = require('run-sequence'),
jeditor = require("gulp-json-editor"),
merge = require('merge-stream'),
ngConfig = require('gulp-ng-config'),
settings = require('./settings.json'),
@@ -71,12 +72,13 @@ gulp.task('min:js', ['clean:js'], function () {
'!' + paths.minJs,
'!' + paths.jsDir + 'fallback*.js',
'!' + paths.jsDir + 'u2f-connector.js',
'!' + paths.jsDir + 'duo-connector.js',
'!' + paths.jsDir + 'duo.js',
'!' + paths.jsDir + 'settings.js'
], { base: '.' })
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
//.pipe(uglify())
.pipe(gulp.dest('.'));
});
@@ -335,6 +337,10 @@ gulp.task('dist:move', function () {
src: paths.jsDir + 'duo.js',
dest: paths.dist + 'js'
},
{
src: paths.jsDir + 'duo-connector.js',
dest: paths.dist + 'js'
},
{
src: paths.jsDir + 'settings.js',
dest: paths.dist + 'js'
@@ -351,6 +357,7 @@ gulp.task('dist:move', function () {
paths.webroot + 'u2f-connector.html',
paths.webroot + 'duo-connector.html',
paths.webroot + 'favicon.ico',
paths.webroot + 'manifest.json',
paths.webroot + 'app-id.json'
],
dest: paths.dist
@@ -389,7 +396,7 @@ gulp.task('dist:js:app', function () {
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(concat(paths.dist + '/js/app.min.js'))
.pipe(ngAnnotate())
.pipe(uglify())
//.pipe(uglify())
.pipe(gulp.dest('.'));
});
@@ -401,7 +408,7 @@ gulp.task('dist:js:fallback', function () {
merge(mainStream)
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(uglify())
//.pipe(uglify())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest(paths.dist + 'js'));
});
@@ -414,7 +421,7 @@ gulp.task('dist:js:u2f', function () {
merge(mainStream)
.pipe(concat(paths.dist + '/js/u2f.min.js'))
.pipe(uglify())
//.pipe(uglify())
.pipe(gulp.dest('.'));
});
@@ -429,7 +436,7 @@ gulp.task('dist:js:lib', function () {
'!' + paths.libDir + 'jquery/**/*'
])
.pipe(concat(paths.dist + '/js/lib.min.js'))
.pipe(uglify())
//.pipe(uglify())
.pipe(gulp.dest('.'));
});
@@ -442,10 +449,16 @@ gulp.task('dist:preprocess', function () {
.pipe(gulp.dest('.'));
});
gulp.task('dist:version', function () {
gulp.src(paths.webroot + 'version.json').pipe(jeditor({
'version': project.version
})).pipe(gulp.dest(paths.dist));
});
gulp.task('dist', ['build'], function (cb) {
return runSequence(
'dist:clean',
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f'],
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f', 'dist:version'],
'dist:preprocess',
cb);
});

9469
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden",
"version": "1.22.0",
"version": "1.25.0",
"env": "Production",
"devDependencies": {
"connect": "3.6.5",
@@ -11,11 +11,12 @@
"gulp-less": "3.3.2",
"gulp-rename": "1.2.2",
"gulp-uglify": "3.0.0",
"gulp-gh-pages": "git@github.com:tekd/gulp-gh-pages.git#update-dependency",
"gulp-gh-pages": "git+https://github.com/tekd/gulp-gh-pages.git#update-dependency",
"gulp-preprocess": "2.0.0",
"gulp-ng-annotate": "2.0.0",
"gulp-ng-config": "1.5.0",
"gulp-connect": "5.0.0",
"gulp-json-editor": "2.2.2",
"jshint": "2.9.5",
"gulp-jshint": "2.0.4",
"rimraf": "2.6.2",

View File

@@ -49,7 +49,7 @@
<p class="login-box-msg">
Complete logging in with YubiKey.
</p>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
autocomplete="off">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>
@@ -63,7 +63,8 @@
</p>
<div class="form-group" show-errors>
<label for="code" class="sr-only">Token</label>
<input type="password" id="code" name="Token" class="form-control" ng-model="token" required api-field />
<input type="password" id="code" name="Token" class="form-control" ng-model="token"
autocomplete="new-password" required api-field />
</div>
<div class="row">
<div class="col-xs-7">
@@ -86,7 +87,7 @@
<p class="login-box-msg">
Complete logging in with Duo.
</p>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
autocomplete="off">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>

View File

@@ -14,7 +14,7 @@
<p class="text-center"><strong>{{state.params.email}}</strong></p>
<p>
You've been invited to join the organization listed above.
To accept the invitation, you need to log in or create a new bitwarden account.
To accept the invitation, you need to log in or create a new Bitwarden account.
</p>
<hr />
<div class="row">

View File

@@ -3,7 +3,7 @@
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<p class="login-box-msg">Enter your email address below to recover &amp; delete your bitwarden account.</p>
<p class="login-box-msg">Enter your email address below to recover &amp; delete your Bitwarden account.</p>
<div ng-show="success" class="text-center">
<div class="callout callout-success">
If your account exists ({{model.email}}) we've sent you an email with further instructions.

View File

@@ -12,7 +12,7 @@
This will permanently delete your account. This cannot be undone.
</div>
<p>
You have requested to delete your bitwarden account (<b>{{email}}</b>).
You have requested to delete your Bitwarden account (<b>{{email}}</b>).
Click the button below to confirm and proceed.
</p>
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>

View File

@@ -4,7 +4,7 @@ angular
.factory('apiInterceptor', function ($injector, $q, toastr, appSettings, utilsService) {
return {
request: function (config) {
if (config.url.indexOf(appSettings.apiUri + '/') > -1) {
if (config.url.indexOf(appSettings.apiUri + '/') === 0) {
config.headers['Device-Type'] = utilsService.getDeviceType();
}

View File

@@ -14,15 +14,13 @@ angular
$qProvider.errorOnUnhandledRejections(false);
$locationProvider.hashPrefix('');
// @if false
jwtOptionsProvider.config({
whiteListedDomains: ['localhost', 'api.bitwarden.com', 'vault.bitwarden.com']
whiteListedDomains: ['localhost', 'api.bitwarden.com', 'vault.bitwarden.com', 'haveibeenpwned.com']
});
// @endif
var refreshPromise;
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
if (options.url.indexOf(appSettings.apiUri + '/') === -1) {
if (options.url.indexOf(appSettings.apiUri + '/') !== 0) {
return;
}
@@ -154,12 +152,6 @@ angular
controller: 'reportsBreachController',
data: { pageTitle: 'Data Breach Report' }
})
.state('backend.user.apps', {
url: '^/apps',
templateUrl: 'app/views/apps.html',
controller: 'appsController',
data: { pageTitle: 'Get the Apps' }
})
.state('backend.org', {
templateUrl: 'app/views/organizationLayout.html',
abstract: true
@@ -198,7 +190,10 @@ angular
url: '/organization/:orgId/vault?viewEvents&search',
templateUrl: 'app/organization/views/organizationVault.html',
controller: 'organizationVaultController',
data: { pageTitle: 'Organization Vault' }
data: {
pageTitle: 'Organization Vault',
controlSidebar: true
}
})
.state('backend.org.groups', {
url: '/organization/:orgId/groups?search',
@@ -357,7 +352,7 @@ angular
// user is guaranteed to be authenticated becuase of previous check
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
// clear vault rootScope when visiting org admin section
$rootScope.vaultCiphers = $rootScope.vaultGroupings = null;
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
authService.getUserProfile().then(function (profile) {
var orgs = profile.organizations;

View File

@@ -6,7 +6,7 @@ angular
link: function (scope, element) {
var listener = function (event, toState, toParams, fromState, fromParams) {
// Default title
var title = 'bitwarden Web Vault';
var title = 'Bitwarden Web Vault';
if (toState.data && toState.data.pageTitle) {
title = toState.data.pageTitle + ' - ' + title;
}

View File

@@ -1,6 +0,0 @@
angular
.module('bit.global')
.controller('appsController', function ($scope, $state) {
});

View File

@@ -49,10 +49,6 @@ angular
vm.openControlSidebar = vm.usingControlSidebar && $document.width() > 768;
});
$scope.$on('setSearchVaultText', function (event, val) {
vm.searchVaultText = val;
});
$scope.addCipher = function () {
$scope.$broadcast('vaultAddCipher');
};

View File

@@ -51,14 +51,6 @@ angular
$state.go('backend.org.dashboard', { orgId: org.id });
};
$scope.searchVault = function () {
$state.go('backend.user.vault');
};
$scope.searchOrganizationVault = function () {
$state.go('backend.org.vault', { orgId: $state.params.orgId });
};
$scope.isOrgOwner = function (org) {
return org && org.type === constants.orgUserType.owner;
};

View File

@@ -26,8 +26,9 @@
return;
}
var i;
var collectionsDict = {};
for (var i = 0; i < decCollections.length; i++) {
for (i = 0; i < decCollections.length; i++) {
collectionsDict[decCollections[i].id] = decCollections[i];
}
@@ -78,10 +79,17 @@
switch (decCiphers[i].type) {
case constants.cipherType.login:
cipher.type = 'login';
cipher.login_uri = decCiphers[i].login.uri;
cipher.login_uri = null;
cipher.login_username = decCiphers[i].login.username;
cipher.login_password = decCiphers[i].login.password;
cipher.login_totp = decCiphers[i].login.totp;
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
cipher.login_uri = [];
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
}
}
break;
case constants.cipherType.secureNote:
cipher.type = 'note';

View File

@@ -11,7 +11,7 @@
$scope.options = [
{
id: 'bitwardencsv',
name: 'bitwarden (csv)',
name: 'Bitwarden (csv)',
featured: true,
sort: 1,
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +

View File

@@ -2,13 +2,19 @@
.module('bit.organization')
.controller('organizationVaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants) {
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants, selectedType) {
$analytics.eventTrack('organizationVaultAddCipherController', { category: 'Modal' });
$scope.constants = constants;
$scope.selectedType = constants.cipherType.login.toString();
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
$scope.cipher = {
type: constants.cipherType.login,
login: {},
type: selectedType || constants.cipherType.login,
login: {
uris: [{
uri: null,
match: null,
matchValue: null
}]
},
identity: {},
card: {},
secureNote: {
@@ -44,6 +50,42 @@
}
};
$scope.addUri = function () {
if (!$scope.cipher.login) {
return;
}
if (!$scope.cipher.login.uris) {
$scope.cipher.login.uris = [];
}
$scope.cipher.login.uris.push({
uri: null,
match: null,
matchValue: null
});
};
$scope.removeUri = function (uri) {
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
return;
}
var index = $scope.cipher.login.uris.indexOf(uri);
if (index > -1) {
$scope.cipher.login.uris.splice(index, 1);
}
};
$scope.uriMatchChanged = function (uri) {
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
uri.match = null;
}
else {
uri.match = parseInt(uri.matchValue);
}
};
$scope.addField = function () {
if (!$scope.cipher.fields) {
$scope.cipher.fields = [];

View File

@@ -29,7 +29,7 @@
var fd = new FormData();
var blob = new Blob([encValue.data], { type: 'application/octet-stream' });
fd.append('data', blob, encValue.fileName);
return apiService.ciphers.postAttachment({ id: cipherId }, fd).$promise;
return apiService.ciphers.postAttachmentAdmin({ id: cipherId }, fd).$promise;
}).then(function (response) {
$analytics.eventTrack('Added Attachment');
toastr.success('The attachment has been added.');
@@ -61,7 +61,7 @@
}
attachment.loading = true;
apiService.ciphers.delAttachment({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
apiService.ciphers.delAttachmentAdmin({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
attachment.loading = false;
$analytics.eventTrack('Deleted Organization Attachment');
var index = $scope.cipher.attachments.indexOf(attachment);

View File

@@ -2,11 +2,18 @@
.module('bit.organization')
.controller('organizationVaultController', function ($scope, apiService, cipherService, $analytics, $q, $state,
$localStorage, $uibModal, $filter, authService, $uibModalStack) {
$localStorage, $uibModal, $filter, authService, $uibModalStack, constants, $timeout) {
$scope.ciphers = [];
$scope.collections = [];
$scope.loading = true;
$scope.useEvents = false;
$scope.constants = constants;
$scope.filter = undefined;
$scope.selectedType = undefined;
$scope.selectedCollection = undefined;
$scope.selectedAll = true;
$scope.selectedTitle = 'All';
$scope.selectedIcon = 'fa-th';
$scope.$on('$viewContentLoaded', function () {
authService.getUserProfile().then(function (profile) {
@@ -19,14 +26,11 @@
var collectionPromise = apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (collections) {
var decCollections = [{
id: null,
name: 'Unassigned',
collapsed: $localStorage.collapsedOrgCollections && 'unassigned' in $localStorage.collapsedOrgCollections
name: 'Unassigned'
}];
for (var i = 0; i < collections.Data.length; i++) {
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
decCollection.collapsed = $localStorage.collapsedOrgCollections &&
decCollection.id in $localStorage.collapsedOrgCollections;
decCollections.push(decCollection);
}
@@ -47,11 +51,15 @@
$q.all([collectionPromise, cipherPromise]).then(function () {
$scope.loading = false;
$("#search").focus();
$timeout(function () {
if ($('body').hasClass('control-sidebar-open')) {
$("#search").focus();
}
}, 500);
if ($state.params.search) {
$uibModalStack.dismissAll();
$scope.$emit('setSearchVaultText', $state.params.search);
$scope.searchVaultText = $state.params.search;
}
if ($state.params.viewEvents) {
@@ -64,16 +72,6 @@
});
});
$scope.filterByCollection = function (collection) {
return function (cipher) {
if (!cipher.collectionIds || !cipher.collectionIds.length) {
return collection.id === null;
}
return cipher.collectionIds.indexOf(collection.id) > -1;
};
};
$scope.collectionSort = function (item) {
if (!item.id) {
return '';
@@ -82,21 +80,6 @@
return item.name.toLowerCase();
};
$scope.collapseExpand = function (collection) {
if (!$localStorage.collapsedOrgCollections) {
$localStorage.collapsedOrgCollections = {};
}
var id = collection.id || 'unassigned';
if (id in $localStorage.collapsedOrgCollections) {
delete $localStorage.collapsedOrgCollections[id];
}
else {
$localStorage.collapsedOrgCollections[id] = true;
}
};
$scope.editCipher = function (cipher) {
var editModel = $uibModal.open({
animation: true,
@@ -136,7 +119,8 @@
templateUrl: 'app/vault/views/vaultAddCipher.html',
controller: 'organizationVaultAddCipherController',
resolve: {
orgId: function () { return $state.params.orgId; }
orgId: function () { return $state.params.orgId; },
selectedType: function () { return $scope.selectedType; }
}
});
@@ -205,28 +189,6 @@
});
};
$scope.removeCipher = function (cipher, collection) {
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
'collection (' + collection.name + ') ?')) {
return;
}
var request = {
collectionIds: []
};
for (var i = 0; i < cipher.collectionIds.length; i++) {
if (cipher.collectionIds[i] !== collection.id) {
request.collectionIds.push(cipher.collectionIds[i]);
}
}
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
$analytics.eventTrack('Removed Cipher From Collection');
cipher.collectionIds = request.collectionIds;
});
};
$scope.deleteCipher = function (cipher) {
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
return;
@@ -240,4 +202,72 @@
}
});
};
$scope.filterCollection = function (col) {
resetSelected();
$scope.selectedCollection = col;
$scope.selectedIcon = 'fa-cube';
$scope.filter = function (c) {
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
};
fixLayout();
};
$scope.filterType = function (t) {
resetSelected();
$scope.selectedType = t;
switch (t) {
case constants.cipherType.login:
$scope.selectedTitle = 'Login';
$scope.selectedIcon = 'fa-globe';
break;
case constants.cipherType.card:
$scope.selectedTitle = 'Card';
$scope.selectedIcon = 'fa-credit-card';
break;
case constants.cipherType.identity:
$scope.selectedTitle = 'Identity';
$scope.selectedIcon = 'fa-id-card-o';
break;
case constants.cipherType.secureNote:
$scope.selectedTitle = 'Secure Note';
$scope.selectedIcon = 'fa-sticky-note-o';
break;
default:
break;
}
$scope.filter = function (c) {
return c.type === t;
};
fixLayout();
};
$scope.filterAll = function () {
resetSelected();
$scope.selectedAll = true;
$scope.selectedTitle = 'All';
$scope.selectedIcon = 'fa-th';
$scope.filter = null;
fixLayout();
};
function resetSelected() {
$scope.selectedCollection = undefined;
$scope.selectedType = undefined;
$scope.selectedAll = false;
}
function fixLayout() {
if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () {
$.AdminLTE.layout.fix();
}, 0);
}
}
$scope.cipherFilter = function () {
return function (cipher) {
return !$scope.filter || $scope.filter(cipher);
};
};
});

View File

@@ -11,6 +11,7 @@
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
$scope.cipher = cipherService.decryptCipher(cipher);
$scope.useTotp = $scope.cipher.organizationUseTotp;
setUriMatchValues();
});
$scope.save = function (model) {
@@ -32,6 +33,42 @@
}
};
$scope.addUri = function () {
if (!$scope.cipher.login) {
return;
}
if (!$scope.cipher.login.uris) {
$scope.cipher.login.uris = [];
}
$scope.cipher.login.uris.push({
uri: null,
match: null,
matchValue: null
});
};
$scope.removeUri = function (uri) {
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
return;
}
var index = $scope.cipher.login.uris.indexOf(uri);
if (index > -1) {
$scope.cipher.login.uris.splice(index, 1);
}
};
$scope.uriMatchChanged = function (uri) {
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
uri.match = null;
}
else {
uri.match = parseInt(uri.matchValue);
}
};
$scope.addField = function () {
if (!$scope.cipher.login.fields) {
$scope.cipher.login.fields = [];
@@ -98,4 +135,14 @@
}
});
};
function setUriMatchValues() {
if ($scope.cipher.login && $scope.cipher.login.uris) {
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
$scope.cipher.login.uris[i].matchValue =
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
$scope.cipher.login.uris[i].match.toString() : '';
}
}
}
});

View File

@@ -116,7 +116,7 @@
Loading...
</div>
<div ng-show="!loading">
You plan currently has a total of <b>{{plan.seats}}</b> seats.
Your plan currently has a total of <b>{{plan.seats}}</b> seats.
</div>
</div>
<div class="box-footer" ng-if="!selfHosted && !noSubscription && canAdjustSeats">
@@ -134,7 +134,7 @@
</div>
<div class="box-body">
<p>
You plan has a total of {{storage.maxGb}} GB of encrypted file storage.
Your plan has a total of {{storage.maxGb}} GB of encrypted file storage.
You are currently using {{storage.currentName}}.
</p>
<div class="progress" style="margin: 0;">

View File

@@ -22,7 +22,7 @@
<h3 class="box-title">Let's Get Started!</h3>
</div>
<div class="box-body">
<p>Dashboard features are coming soon. Get started by inviting users and creating your collections.</p>
<p>Get started by inviting users and creating your collections.</p>
<a class="btn btn-default btn-flat" ui-sref="backend.org.people({orgId: orgProfile.id})">
Invite Users
</a>

View File

@@ -5,8 +5,8 @@
<form name="inviteForm" ng-submit="inviteForm.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
<div class="modal-body">
<p>
Invite a new user to your organization by entering their bitwarden account email address below. If they do not have
a bitwarden account already, they will be prompted to create a new account.
Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have
a Bitwarden account already, they will be prompted to create a new account.
</p>
<div class="callout callout-danger validation-errors" ng-show="inviteForm.$errors">
<h4>Errors have occurred</h4>

View File

@@ -10,31 +10,22 @@
</h1>
</section>
<section class="content">
<p ng-show="loading && !collections.length">Loading...</p>
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
orderBy: collectionSort track by collection.id"
ng-show="collections.length && (!main.searchVaultText || collectionCiphers.length)">
<p ng-show="loading">Loading...</p>
<div class="box" ng-show="!loading">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa" ng-class="{'fa-cube': collection.id, 'fa-sitemap': !collection.id}"></i>
{{collection.name}}
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
<i class="fa {{selectedIcon}}"></i>
{{selectedCollection ? selectedCollection.name : selectedTitle}}
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
</h3>
<div class="box-tools">
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
ng-click="collapseExpand(collection)">
<i class="fa" ng-class="{'fa-minus': !collection.collapsed, 'fa-plus': collection.collapsed}"></i>
</button>
</div>
</div>
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
<div ng-show="!collectionCiphers.length && collection.id">No items in this collection.</div>
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
<div class="table-responsive" ng-show="collectionCiphers.length">
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
<div ng-show="!filteredCiphers.length">No items to list.</div>
<div class="table-responsive" ng-show="filteredCiphers.length">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
filter: (main.searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
filter: (searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
<td style="width: 70px;">
<div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
@@ -61,12 +52,6 @@
<i class="fa fa-fw fa-file-text-o"></i> Event Logs
</a>
</li>
<li>
<a href="#" stop-click ng-click="removeCipher(cipher, collection)" class="text-red"
ng-if="collection.id">
<i class="fa fa-fw fa-remove"></i> Remove
</a>
</li>
<li>
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
<i class="fa fa-fw fa-trash"></i> Delete
@@ -93,3 +78,65 @@
</div>
</div>
</section>
<aside class="control-sidebar control-sidebar-light">
<div class="tab-content">
<form class="search-form">
<label for="search" class="sr-only">Search</label>
<div class="form-group has-feedback">
<input type="search" id="search" class="form-control" placeholder="Search org vault..."
ng-model="searchVaultText" />
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
</div>
</form>
<ul class="control-sidebar-menu">
<li ng-class="{active: selectedAll}">
<a href="#" stop-click ng-click="filterAll()">
<i class="fa fa-th fa-fw"></i> All Items
</a>
</li>
</ul>
<h3 class="control-sidebar-heading">Types</h3>
<div class="control-sidebar-section">
<ul class="control-sidebar-menu">
<li ng-class="{active: constants.cipherType.login === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
<i class="fa fa-globe fa-fw"></i> Login
</a>
</li>
<li ng-class="{active: constants.cipherType.card === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
<i class="fa fa-credit-card fa-fw"></i> Card
</a>
</li>
<li ng-class="{active: constants.cipherType.identity === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
<i class="fa fa-id-card-o fa-fw"></i> Identity
</a>
</li>
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
</a>
</li>
</ul>
</div>
<h3 class="control-sidebar-heading">Collections</h3>
<div ng-show="loading && !collections.length">
<p>Loading...</p>
</div>
<div ng-show="!loading && !collections.length">
<p>No collections.</p>
</div>
<div class="control-sidebar-section" ng-show="!loading && collections.length">
<ul class="control-sidebar-menu">
<li ng-repeat="collection in collections | orderBy: [collectionSort] track by collection.id"
ng-class="{active: selectedCollection && collection.id === selectedCollection.id}">
<a href="#" stop-click ng-click="filterCollection(collection)">
<i class="fa fa-caret-right fa-fw"></i>
{{collection.name}}
</a>
</li>
</ul>
</div>
</div>
</aside>

View File

@@ -42,13 +42,20 @@
headers: { 'Content-Type': undefined },
params: { id: '@id' }
},
postAttachmentAdmin: {
url: _apiUri + '/ciphers/:id/attachment-admin',
method: 'POST',
headers: { 'Content-Type': undefined },
params: { id: '@id' }
},
postShareAttachment: {
url: _apiUri + '/ciphers/:id/attachment/:attachmentId/share?organizationId=:orgId',
method: 'POST',
headers: { 'Content-Type': undefined },
params: { id: '@id', attachmentId: '@attachmentId', orgId: '@orgId' }
},
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } },
delAttachmentAdmin: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete-admin', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
});
_service.organizations = $resource(_apiUri + '/organizations/:id', {}, {

View File

@@ -95,7 +95,7 @@ angular
_service.logOut = function () {
tokenService.clearTokens();
cryptoService.clearKeys();
$rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
_userProfile = null;
};

View File

@@ -30,6 +30,9 @@ angular
organizationId: encryptedCipher.OrganizationId,
collectionIds: encryptedCipher.CollectionIds || [],
'type': encryptedCipher.Type,
name: cryptoService.decrypt(encryptedCipher.Name, key),
notes: _service.decryptProperty(encryptedCipher.Notes, key, true, false),
fields: _service.decryptFields(key, encryptedCipher.Fields),
folderId: encryptedCipher.FolderId,
favorite: encryptedCipher.Favorite,
edit: encryptedCipher.Edit,
@@ -38,62 +41,68 @@ angular
icon: null
};
var cipherData = encryptedCipher.Data;
if (cipherData) {
cipher.name = cryptoService.decrypt(cipherData.Name, key);
cipher.notes = _service.decryptProperty(cipherData.Notes, key, true, false);
cipher.fields = _service.decryptFields(key, cipherData.Fields);
var dataObj = {};
switch (cipher.type) {
case constants.cipherType.login:
dataObj.uri = _service.decryptProperty(cipherData.Uri, key, true, false);
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
dataObj.password = _service.decryptProperty(cipherData.Password, key, true, false);
dataObj.totp = _service.decryptProperty(cipherData.Totp, key, true, false);
cipher.login = dataObj;
cipher.icon = 'fa-globe';
break;
case constants.cipherType.secureNote:
dataObj.type = cipherData.Type;
cipher.secureNote = dataObj;
cipher.icon = 'fa-sticky-note-o';
break;
case constants.cipherType.card:
dataObj.cardholderName = _service.decryptProperty(cipherData.CardholderName, key, true, false);
dataObj.number = _service.decryptProperty(cipherData.Number, key, true, false);
dataObj.brand = _service.decryptProperty(cipherData.Brand, key, true, false);
dataObj.expMonth = _service.decryptProperty(cipherData.ExpMonth, key, true, false);
dataObj.expYear = _service.decryptProperty(cipherData.ExpYear, key, true, false);
dataObj.code = _service.decryptProperty(cipherData.Code, key, true, false);
cipher.card = dataObj;
cipher.icon = 'fa-credit-card';
break;
case constants.cipherType.identity:
dataObj.title = _service.decryptProperty(cipherData.Title, key, true, false);
dataObj.firstName = _service.decryptProperty(cipherData.FirstName, key, true, false);
dataObj.middleName = _service.decryptProperty(cipherData.MiddleName, key, true, false);
dataObj.lastName = _service.decryptProperty(cipherData.LastName, key, true, false);
dataObj.address1 = _service.decryptProperty(cipherData.Address1, key, true, false);
dataObj.address2 = _service.decryptProperty(cipherData.Address2, key, true, false);
dataObj.address3 = _service.decryptProperty(cipherData.Address3, key, true, false);
dataObj.city = _service.decryptProperty(cipherData.City, key, true, false);
dataObj.state = _service.decryptProperty(cipherData.State, key, true, false);
dataObj.postalCode = _service.decryptProperty(cipherData.PostalCode, key, true, false);
dataObj.country = _service.decryptProperty(cipherData.Country, key, true, false);
dataObj.company = _service.decryptProperty(cipherData.Company, key, true, false);
dataObj.email = _service.decryptProperty(cipherData.Email, key, true, false);
dataObj.phone = _service.decryptProperty(cipherData.Phone, key, true, false);
dataObj.ssn = _service.decryptProperty(cipherData.SSN, key, true, false);
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
dataObj.passportNumber = _service.decryptProperty(cipherData.PassportNumber, key, true, false);
dataObj.licenseNumber = _service.decryptProperty(cipherData.LicenseNumber, key, true, false);
cipher.identity = dataObj;
cipher.icon = 'fa-id-card-o';
break;
default:
break;
}
var i;
switch (cipher.type) {
case constants.cipherType.login:
cipher.login = {
username: _service.decryptProperty(encryptedCipher.Login.Username, key, true, false),
password: _service.decryptProperty(encryptedCipher.Login.Password, key, true, false),
totp: _service.decryptProperty(encryptedCipher.Login.Totp, key, true, false),
uris: null
};
if (encryptedCipher.Login.Uris) {
cipher.login.uris = [];
for (i = 0; i < encryptedCipher.Login.Uris.length; i++) {
cipher.login.uris.push({
uri: _service.decryptProperty(encryptedCipher.Login.Uris[i].Uri, key, true, false),
match: encryptedCipher.Login.Uris[i].Match
});
}
}
cipher.icon = 'fa-globe';
break;
case constants.cipherType.secureNote:
cipher.secureNote = {
type: encryptedCipher.SecureNote.Type
};
cipher.icon = 'fa-sticky-note-o';
break;
case constants.cipherType.card:
cipher.card = {
cardholderName: _service.decryptProperty(encryptedCipher.Card.CardholderName, key, true, false),
number: _service.decryptProperty(encryptedCipher.Card.Number, key, true, false),
brand: _service.decryptProperty(encryptedCipher.Card.Brand, key, true, false),
expMonth: _service.decryptProperty(encryptedCipher.Card.ExpMonth, key, true, false),
expYear: _service.decryptProperty(encryptedCipher.Card.ExpYear, key, true, false),
code: _service.decryptProperty(encryptedCipher.Card.Code, key, true, false)
};
cipher.icon = 'fa-credit-card';
break;
case constants.cipherType.identity:
cipher.identity = {
title: _service.decryptProperty(encryptedCipher.Identity.Title, key, true, false),
firstName: _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, false),
middleName: _service.decryptProperty(encryptedCipher.Identity.MiddleName, key, true, false),
lastName: _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, false),
address1: _service.decryptProperty(encryptedCipher.Identity.Address1, key, true, false),
address2: _service.decryptProperty(encryptedCipher.Identity.Address2, key, true, false),
address3: _service.decryptProperty(encryptedCipher.Identity.Address3, key, true, false),
city: _service.decryptProperty(encryptedCipher.Identity.City, key, true, false),
state: _service.decryptProperty(encryptedCipher.Identity.State, key, true, false),
postalCode: _service.decryptProperty(encryptedCipher.Identity.PostalCode, key, true, false),
country: _service.decryptProperty(encryptedCipher.Identity.Country, key, true, false),
company: _service.decryptProperty(encryptedCipher.Identity.Company, key, true, false),
email: _service.decryptProperty(encryptedCipher.Identity.Email, key, true, false),
phone: _service.decryptProperty(encryptedCipher.Identity.Phone, key, true, false),
ssn: _service.decryptProperty(encryptedCipher.Identity.SSN, key, true, false),
username: _service.decryptProperty(encryptedCipher.Identity.Username, key, true, false),
passportNumber: _service.decryptProperty(encryptedCipher.Identity.PassportNumber, key, true, false),
licenseNumber: _service.decryptProperty(encryptedCipher.Identity.LicenseNumber, key, true, false)
};
cipher.icon = 'fa-id-card-o';
break;
default:
break;
}
if (!encryptedCipher.Attachments) {
@@ -101,7 +110,7 @@ angular
}
cipher.attachments = [];
for (var i = 0; i < encryptedCipher.Attachments.length; i++) {
for (i = 0; i < encryptedCipher.Attachments.length; i++) {
cipher.attachments.push(_service.decryptAttachment(key, encryptedCipher.Attachments[i]));
}
@@ -121,6 +130,7 @@ angular
organizationId: encryptedCipher.OrganizationId,
collectionIds: encryptedCipher.CollectionIds || [],
'type': encryptedCipher.Type,
name: _service.decryptProperty(encryptedCipher.Name, key, false, true),
folderId: encryptedCipher.FolderId,
favorite: encryptedCipher.Favorite,
edit: encryptedCipher.Edit,
@@ -130,59 +140,56 @@ angular
icon: null
};
var cipherData = encryptedCipher.Data;
if (cipherData) {
cipher.name = _service.decryptProperty(cipherData.Name, key, false, true);
var dataObj = {};
switch (cipher.type) {
case constants.cipherType.login:
cipher.subTitle = _service.decryptProperty(cipherData.Username, key, true, true);
cipher.meta.password = _service.decryptProperty(cipherData.Password, key, true, true);
cipher.meta.uri = _service.decryptProperty(cipherData.Uri, key, true, true);
setLoginIcon(cipher, cipher.meta.uri, true);
break;
case constants.cipherType.secureNote:
cipher.subTitle = null;
cipher.icon = 'fa-sticky-note-o';
break;
case constants.cipherType.card:
cipher.subTitle = '';
cipher.meta.number = _service.decryptProperty(cipherData.Number, key, true, true);
var brand = _service.decryptProperty(cipherData.Brand, key, true, true);
if (brand) {
cipher.subTitle = brand;
}
if (cipher.meta.number && cipher.meta.number.length >= 4) {
if (cipher.subTitle !== '') {
cipher.subTitle += ', ';
}
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
}
cipher.icon = 'fa-credit-card';
break;
case constants.cipherType.identity:
var firstName = _service.decryptProperty(cipherData.FirstName, key, true, true);
var lastName = _service.decryptProperty(cipherData.LastName, key, true, true);
cipher.subTitle = '';
if (firstName) {
cipher.subTitle = firstName;
}
if (lastName) {
if (cipher.subTitle !== '') {
cipher.subTitle += ' ';
}
cipher.subTitle += lastName;
}
cipher.icon = 'fa-id-card-o';
break;
default:
break;
}
if (cipher.subTitle === '') {
switch (cipher.type) {
case constants.cipherType.login:
cipher.subTitle = _service.decryptProperty(encryptedCipher.Login.Username, key, true, true);
cipher.meta.password = _service.decryptProperty(encryptedCipher.Login.Password, key, true, true);
cipher.meta.uri = null;
if (encryptedCipher.Login.Uris && encryptedCipher.Login.Uris.length) {
cipher.meta.uri = _service.decryptProperty(encryptedCipher.Login.Uris[0].Uri, key, true, true);
}
setLoginIcon(cipher, cipher.meta.uri, true);
break;
case constants.cipherType.secureNote:
cipher.subTitle = null;
}
cipher.icon = 'fa-sticky-note-o';
break;
case constants.cipherType.card:
cipher.subTitle = '';
cipher.meta.number = _service.decryptProperty(encryptedCipher.Card.Number, key, true, true);
var brand = _service.decryptProperty(encryptedCipher.Card.Brand, key, true, true);
if (brand) {
cipher.subTitle = brand;
}
if (cipher.meta.number && cipher.meta.number.length >= 4) {
if (cipher.subTitle !== '') {
cipher.subTitle += ', ';
}
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
}
cipher.icon = 'fa-credit-card';
break;
case constants.cipherType.identity:
var firstName = _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, true);
var lastName = _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, true);
cipher.subTitle = '';
if (firstName) {
cipher.subTitle = firstName;
}
if (lastName) {
if (cipher.subTitle !== '') {
cipher.subTitle += ' ';
}
cipher.subTitle += lastName;
}
cipher.icon = 'fa-id-card-o';
break;
default:
break;
}
if (cipher.subTitle === '') {
cipher.subTitle = null;
}
return cipher;
@@ -389,15 +396,24 @@ angular
fields: _service.encryptFields(unencryptedCipher.fields, key)
};
var i;
switch (cipher.type) {
case constants.cipherType.login:
var loginData = unencryptedCipher.login;
cipher.login = {
uri: encryptProperty(loginData.uri, key),
username: encryptProperty(loginData.username, key),
password: encryptProperty(loginData.password, key),
totp: encryptProperty(loginData.totp, key)
};
if (loginData.uris && loginData.uris.length) {
cipher.login.uris = [];
for (i = 0; i < loginData.uris.length; i++) {
cipher.login.uris.push({
uri: encryptProperty(loginData.uris[i].uri, key),
match: loginData.uris[i].match
});
}
}
break;
case constants.cipherType.secureNote:
cipher.secureNote = {
@@ -444,7 +460,7 @@ angular
if (unencryptedCipher.attachments && attachments) {
cipher.attachments = {};
for (var i = 0; i < unencryptedCipher.attachments.length; i++) {
for (i = 0; i < unencryptedCipher.attachments.length; i++) {
cipher.attachments[unencryptedCipher.attachments[i].id] =
cryptoService.encrypt(unencryptedCipher.attachments[i].fileName, key);
}

View File

@@ -540,7 +540,7 @@ angular
if (key.macKey && encPieces.length > 2) {
var macBytes = forge.util.decode64(encPieces[2]);
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
if (!macsEqual(macBytes, computedMacBytes)) {
console.error('MAC failed.');
return null;
}
@@ -623,6 +623,10 @@ angular
throw 'Encryption key unavailable.';
}
if (key.macKey && !macBuf) {
throw 'macBuf required for this type of key.';
}
if (encType !== key.encType) {
throw 'encType unavailable.';
}
@@ -646,7 +650,7 @@ angular
if (computedMacBuf === null) {
return null;
}
return macsEqualWC(keyBuf.macKey, macBuf, computedMacBuf);
return macsEqualWC(macBuf, computedMacBuf);
}).then(function (macsMatch) {
if (macsMatch === false) {
console.error('MAC failed.');
@@ -704,7 +708,7 @@ angular
if (key && key.macKey && encPieces.length > 1) {
var macBytes = forge.util.decode64(encPieces[1]);
var computedMacBytes = computeMac(ctBytes, key.macKey, false);
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
if (!macsEqual(macBytes, computedMacBytes)) {
console.error('MAC failed.');
return null;
}
@@ -747,10 +751,11 @@ angular
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
function macsEqual(macKey, mac1, mac2) {
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
function macsEqual(mac1, mac2) {
var hmac = forge.hmac.create();
hmac.start('sha256', macKey);
hmac.start('sha256', getRandomBytes(32));
hmac.update(mac1);
mac1 = hmac.digest().getBytes();
@@ -761,11 +766,14 @@ angular
return mac1 === mac2;
}
function macsEqualWC(macKeyBuf, mac1Buf, mac2Buf) {
function macsEqualWC(mac1Buf, mac2Buf) {
var mac1,
macKey;
return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
var compareKey = new Uint8Array(32);
_crypto.getRandomValues(compareKey);
return window.crypto.subtle.importKey('raw', compareKey.buffer, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
.then(function (key) {
macKey = key;
return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac1Buf);
@@ -932,5 +940,15 @@ angular
return new Uint8Array(result);
}
function getRandomBytes(byteLength) {
var bytes = new Uint32Array(byteLength / 4);
_crypto.getRandomValues(bytes);
var buffer = forge.util.createBuffer();
for (var i = 0; i < bytes.length; i++) {
buffer.putInt32(bytes[i]);
}
return buffer.getBytes();
}
return _service;
});

View File

@@ -133,7 +133,7 @@
var _passwordFieldNames = [
'password', 'pass word', 'passphrase', 'pass phrase',
'pass', 'code', 'code word', 'codeword',
'secret', 'secret word',
'secret', 'secret word', 'personpwd',
'key', 'keyword', 'key word', 'keyphrase', 'key phrase',
'form_pw', 'wppassword', 'pin', 'pwd', 'pw', 'pword', 'passwd',
'p', 'serial', 'serial#', 'license key', 'reg #',
@@ -146,7 +146,7 @@
'user', 'name', 'user name', 'username', 'login name',
'email', 'e-mail', 'id', 'userid', 'user id',
'login', 'form_loginname', 'wpname', 'mail',
'loginid', 'login id', 'log',
'loginid', 'login id', 'log', 'personlogin',
'first name', 'last name', 'card#', 'account #',
'member', 'member #',
@@ -191,14 +191,10 @@
function fixUri(uri) {
uri = uri.toLowerCase().trim();
if (!uri.startsWith('http') && uri.indexOf('.') >= 0) {
if (uri.indexOf('://') === -1 && uri.indexOf('.') >= 0) {
uri = 'http://' + uri;
}
return trimUri(uri);
}
function trimUri(uri) {
if (uri.length > 1000) {
return uri.substring(0, 1000);
}
@@ -206,6 +202,43 @@
return uri;
}
function makeUriArray(uri) {
if (!uri) {
return null;
}
if (typeof uri === 'string') {
return [{
uri: fixUri(uri),
match: null
}];
}
if (uri.length) {
var returnArr = [];
for (var i = 0; i < uri.length; i++) {
returnArr.push({
uri: fixUri(uri[i]),
match: null
});
}
return returnArr;
}
return null;
}
function parseSingleRowCsv(rowData) {
if (!rowData || rowData === '') {
return null;
}
var parsedRow = Papa.parse(rowData);
if (parsedRow && parsedRow.data && parsedRow.data.length && parsedRow.data[0].length) {
return parsedRow.data[0];
}
return null;
}
function parseCsvErrors(results) {
if (results.errors && results.errors.length) {
for (var i = 0; i < results.errors.length; i++) {
@@ -360,17 +393,20 @@
}
}
switch (value.type) {
case 'login': case null: case undefined:
var valueType = value.type ? value.type.toLowerCase() : null;
switch (valueType) {
case 'login':
case null:
case undefined:
cipher.type = constants.cipherType.login;
var totp = value.login_totp || value.totp;
var uri = value.login_uri || value.uri;
var uris = parseSingleRowCsv(value.login_uri || value.uri);
var username = value.login_username || value.username;
var password = value.login_password || value.password;
cipher.login = {
totp: totp && totp !== '' ? totp : null,
uri: uri && uri !== '' ? trimUri(uri) : null,
uris: makeUriArray(uris),
username: username && username !== '' ? username : null,
password: password && password !== '' ? password : null
};
@@ -487,17 +523,20 @@
}
}
switch (value.type) {
case 'login': case null: case undefined:
var valueType = value.type ? value.type.toLowerCase() : null;
switch (valueType) {
case 'login':
case null:
case undefined:
cipher.type = constants.cipherType.login;
var totp = value.login_totp || value.totp;
var uri = value.login_uri || value.uri;
var uris = parseSingleRowCsv(value.login_uri || value.uri);
var username = value.login_username || value.username;
var password = value.login_password || value.password;
cipher.login = {
totp: totp && totp !== '' ? totp : null,
uri: uri && uri !== '' ? trimUri(uri) : null,
uris: makeUriArray(uris),
username: username && username !== '' ? username : null,
password: password && password !== '' ? password : null
};
@@ -684,7 +723,7 @@
if (cipher.type === constants.cipherType.login) {
cipher.login = {
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
uris: makeUriArray(value.url),
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null
};
@@ -844,6 +883,10 @@
fields: null
};
if (!cipher.name || cipher.name === '') {
cipher.name = '--';
}
if (card.attr('type') === 'note') {
cipher.type = constants.cipherType.secureNote;
cipher.secureNote = {
@@ -873,7 +916,7 @@
cipher.notes += (text + '\n');
}
else if (type === 'weblogin' || type === 'website') {
cipher.login.uri = trimUri(text);
cipher.login.uris = makeUriArray(text);
}
else if (text.length > 200) {
cipher.notes += (name + ': ' + text + '\n');
@@ -974,7 +1017,7 @@
notes: null,
name: value[0] && value[0] !== '' ? value[0] : '--',
login: {
uri: null,
uris: null,
username: value[2] && value[2] !== '' ? value[2] : null,
password: value[3] && value[3] !== '' ? value[3] : null
},
@@ -990,7 +1033,7 @@
var cfHeader = customFieldHeaders[j - 4];
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
cipher.login.uri = trimUri(cf);
cipher.login.uris = makeUriArray(cf);
}
else {
if (!cipher.fields) {
@@ -1076,7 +1119,7 @@
name: null,
type: constants.cipherType.login,
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -1095,7 +1138,7 @@
switch (key) {
case 'URL':
cipher.login.uri = fixUri(value);
cipher.login.uris = makeUriArray(value);
break;
case 'UserName':
cipher.login.username = value;
@@ -1197,7 +1240,7 @@
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
name: value.Title && value.Title !== '' ? value.Title : '--',
login: {
uri: value.URL && value.URL !== '' ? fixUri(value.URL) : null,
uris: makeUriArray(value.URL),
username: value.Username && value.Username !== '' ? value.Username : null,
password: value.Password && value.Password !== '' ? value.Password : null
}
@@ -1310,7 +1353,7 @@
else {
cipher.type = constants.cipherType.login;
cipher.login = {
uri: item.location && item.location !== '' ? fixUri(item.location) : null,
uris: makeUriArray(item.location),
username: null,
password: null,
totp: null
@@ -1365,7 +1408,7 @@
notes: value.notesPlain && value.notesPlain !== '' ? value.notesPlain : '',
name: value.title && value.title !== '' ? value.title : '--',
login: {
uri: null,
uris: null,
username: null,
password: null
}
@@ -1383,17 +1426,9 @@
else if (!cipher.login.username && property === 'username') {
cipher.login.username = value[property];
}
else if (!cipher.login.uri && property === 'urls') {
else if (!cipher.login.uris && property === 'urls') {
var urls = value[property].split(/(?:\r\n|\r|\n)/);
cipher.login.uri = fixUri(urls[0]);
for (var j = 1; j < urls.length; j++) {
if (cipher.notes !== '') {
cipher.notes += '\n';
}
cipher.notes += ('url ' + (j + 1) + ': ' + urls[j]);
}
cipher.login.uris = makeUriArray(urls);
}
else if (property !== 'ainfo' && property !== 'autosubmit' && property !== 'notesPlain' &&
property !== 'ps' && property !== 'scope' && property !== 'tags' && property !== 'title' &&
@@ -1437,7 +1472,7 @@
notes: null,
name: value.name && value.name !== '' ? value.name : '--',
login: {
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
uris: makeUriArray(value.url),
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null
}
@@ -1491,7 +1526,7 @@
notes: null,
name: getNameFromHost(host),
login: {
uri: host && host !== '' ? trimUri(host) : null,
uris: makeUriArray(host),
username: user && user !== '' ? user : null,
password: password && password !== '' ? password : null,
}
@@ -1527,7 +1562,7 @@
notes: value[4] && value[4] !== '' ? value[4] : null,
name: value[0] && value[0] !== '' ? value[0] : '--',
login: {
uri: value[3] && value[3] !== '' ? trimUri(value[3]) : null,
uris: makeUriArray(value[3]),
username: value[1] && value[1] !== '' ? value[1] : null,
password: value[2] && value[2] !== '' ? value[2] : null
}
@@ -1574,7 +1609,7 @@
notes: value[5] && value[5] !== '' ? value[5] : null,
name: value[1] && value[1] !== '' ? value[1] : '--',
login: {
uri: value[4] && value[4] !== '' ? trimUri(value[4]) : null,
uris: makeUriArray(value[4]),
username: value[2] && value[2] !== '' ? value[2] : null,
password: value[3] && value[3] !== '' ? value[3] : null
},
@@ -1682,7 +1717,7 @@
notes: notes && notes.text() !== '' ? notes.text() : null,
name: accountName && accountName.text() !== '' ? accountName.text() : '--',
login: {
uri: url && url.text() !== '' ? trimUri(url.text()) : null,
uris: url ? makeUriArray(url.text()) : null,
username: userId && userId.text() !== '' ? userId.text() : null,
password: password && password.text() !== '' ? password.text() : null
},
@@ -1782,7 +1817,7 @@
notes: note && note !== '' ? note : null,
fields: null,
login: {
uri: null,
uris: null,
password: null,
username: null,
totp: null
@@ -1799,8 +1834,8 @@
var field = row[i + 1];
var fieldLower = field.toLowerCase();
if (fieldLower === 'url' && !cipher.login.uri) {
cipher.login.uri = trimUri(value);
if (fieldLower === 'url' && !cipher.login.uris) {
cipher.login.uris = makeUriArray(value);
}
else if ((fieldLower === 'username' || fieldLower === 'email') && !cipher.login.username) {
cipher.login.username = value;
@@ -1901,7 +1936,7 @@
notes: notes && notesText !== '' ? notesText : null,
name: title && title.text() !== '' ? title.text() : '--',
login: {
uri: url && url.text() !== '' ? trimUri(url.text()) : null,
uris: url ? makeUriArray(url.text()) : null,
username: username && username.text() !== '' ? username.text() : null,
password: password && password.text() !== '' ? password.text() : null
}
@@ -1963,17 +1998,17 @@
favorite: false,
notes: null,
login: {
uri: null,
uris: null,
password: null,
username: null
}
};
if (row.length === 2) {
cipher.login.uri = fixUri(row[1]);
cipher.login.uris = makeUriArray(row[1]);
}
else if (row.length === 3) {
cipher.login.uri = fixUri(row[1]);
cipher.login.uris = makeUriArray(row[1]);
cipher.login.username = row[2];
}
else if (row.length === 4) {
@@ -1987,7 +2022,7 @@
}
}
else if (row.length === 5) {
cipher.login.uri = fixUri(row[1]);
cipher.login.uris = makeUriArray(row[1]);
cipher.login.username = row[2];
cipher.login.password = row[3];
cipher.notes = row[4];
@@ -2004,7 +2039,7 @@
cipher.notes = row[4] + '\n' + row[5];
}
cipher.login.uri = fixUri(row[1]);
cipher.login.uris = makeUriArray(row[1]);
}
else if (row.length === 7) {
if (row[2] === '') {
@@ -2016,7 +2051,7 @@
cipher.notes = row[3] + '\n' + row[4] + '\n' + row[6];
}
cipher.login.uri = fixUri(row[1]);
cipher.login.uris = makeUriArray(row[1]);
cipher.login.password = row[5];
}
else {
@@ -2043,9 +2078,6 @@
if (cipher.notes === '') {
cipher.notes = null;
}
if (cipher.login.uri === '') {
cipher.login.uri = null;
}
ciphers.push(cipher);
}
@@ -2138,7 +2170,7 @@
notes: notesText && notesText !== '' ? notesText : null,
name: titleText && titleText !== '' ? titleText : '--',
login: {
uri: linkText && linkText !== '' ? trimUri(linkText) : null,
uris: makeUriArray(linkText),
username: usernameText && usernameText !== '' ? usernameText : null,
password: passwordText && passwordText !== '' ? passwordText : null
}
@@ -2204,14 +2236,14 @@
notes: '',
name: value[2] && value[2] !== '' ? value[2] : null,
login: {
uri: null,
uris: null,
username: null,
password: null
}
};
if (value[1] === 'Web Logins') {
cipher.login.uri = value[4] && value[4] !== '' ? trimUri(value[4]) : null;
cipher.login.uris = makeUriArray(value[4]);
cipher.login.username = value[5] && value[5] !== '' ? value[5] : null;
cipher.login.password = value[6] && value[6] !== '' ? value[6] : null;
cipher.notes = value[3] && value[3] !== '' ? value[3].split('\\n').join('\n') : null;
@@ -2289,7 +2321,7 @@
notes: value.memo && value.memo !== '' ? value.memo : null,
name: value.name && value.name !== '' ? value.name : '--',
login: {
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
uris: makeUriArray(value.url),
username: value.login && value.login !== '' ? value.login : null,
password: value.password && value.password !== '' ? value.password : null
},
@@ -2361,7 +2393,7 @@
notes: '',
name: entry.label && entry.label !== '' ? entry.label.split(' ')[0] : '--',
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -2389,7 +2421,7 @@
cipher.login.username = field.value;
break;
case 'url':
cipher.login.uri = trimUri(field.value);
cipher.login.uris = makeUriArray(field.value);
break;
default:
if (!cipher.login.username && isField(field.label, _usernameFieldNames)) {
@@ -2452,7 +2484,7 @@
notes: null,
name: account.label && account.label !== '' ? account.label : account.domain,
login: {
uri: account.domain && account.domain !== '' ? fixUri(account.domain) : null,
uris: makeUriArray(account.domain),
username: account.username && account.username !== '' ? account.username : null,
password: account.password && account.password !== '' ? account.password : null
}
@@ -2498,7 +2530,7 @@
notes: '',
name: outterTable.find('span.caption').text(),
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -2507,7 +2539,7 @@
var url = outterTable.find('.subcaption').text();
if (url && url !== '') {
cipher.login.uri = fixUri(url);
cipher.login.uris = makeUriArray(url);
}
var fields = [];
@@ -2591,7 +2623,7 @@
notes: value.notes && value.notes !== '' ? value.notes : null,
name: value.url && value.url !== '' ? urlDomain(value.url) : '--',
login: {
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
uris: makeUriArray(value.url),
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null
}
@@ -2625,7 +2657,7 @@
favorite: false,
notes: note && note !== '' ? note : null,
login: {
uri: null,
uris: null,
password: null,
username: null
},
@@ -2642,8 +2674,8 @@
var fieldLower = field.toLowerCase();
if (!cipher.login.uri && isField(field, _uriFieldNames)) {
cipher.login.uri = fixUri(value);
if (!cipher.login.uris && isField(field, _uriFieldNames)) {
cipher.login.uris = makeUriArray(value);
}
else if (!cipher.login.username && isField(field, _usernameFieldNames)) {
cipher.login.username = value;
@@ -2700,7 +2732,7 @@
notes: '',
name: item.name && item.name !== '' ? item.name : '--',
login: {
uri: item.login_url && item.login_url !== '' ? fixUri(item.login_url) : null,
uris: makeUriArray(item.login_url),
username: null,
password: null
},
@@ -2844,7 +2876,7 @@
notes: value.Notes && value.Notes !== '' ? value.Notes : '',
name: value['Secret Name'] && value['Secret Name'] !== '' ? value['Secret Name'] : '--',
login: {
uri: value['Secret URL'] && value['Secret URL'] !== '' ? fixUri(value['Secret URL']) : null,
uris: makeUriArray(value['Secret URL']),
username: null,
password: null
},
@@ -2944,14 +2976,14 @@
name: value[1] && value[1] !== '' ? value[1] : '--',
fields: null,
login: {
uri: null,
uris: null,
username: null,
password: null
}
};
if (type === 'Web Logins' || type === 'Servers' || type === 'Email Accounts') {
cipher.login.uri = value[4] && value[4] !== '' ? fixUri(value[4]) : null;
cipher.login.uris = makeUriArray(value[4]);
cipher.login.username = value[2] && value[2] !== '' ? value[2] : null;
cipher.login.password = value[3] && value[3] !== '' ? value[3] : null;
parseFieldsToNotes(5, value, cipher);
@@ -3009,7 +3041,7 @@
favorite: false,
notes: row.Notes && row.Notes !== '' ? row.Notes : null,
login: {
uri: row.Url && row.Url !== '' ? fixUri(row.Url) : null,
uris: makeUriArray(row.Url),
password: row.Password && row.Password !== '' ? row.Password : null,
username: row.UserName && row.UserName !== '' ? row.UserName : null
}
@@ -3066,7 +3098,7 @@
notes: !!getValue('description', value) ? getValue('description', value) : null,
name: !!getValue('title', value) ? getValue('title', value) : '--',
login: {
uri: !!getValue('site', value) ? fixUri(getValue('site', value)) : null,
uris: !!getValue('site', value) ? makeUriArray(getValue('site', value)) : null,
username: !!getValue('username', value) ? getValue('username', value) : null,
password: !!getValue('password', value) ? getValue('password', value) : null
}
@@ -3140,7 +3172,7 @@
notes: '',
name: item.display_name.replace('http://', '').replace('https://', ''),
login: {
uri: fixUri(item.display_name),
uris: makeUriArray(item.display_name),
username: item.attributes.username_value && item.attributes.username_value !== '' ?
item.attributes.username_value : null,
password: item.secret && item.secret !== '' ? item.secret : null

View File

@@ -1,2 +1,2 @@
angular.module("bit")
.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.22.0","environment":"Production"});
.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.25.0","environment":"Production"});

View File

@@ -4,7 +4,7 @@
.controller('settingsTwoStepAuthenticatorController', function ($scope, apiService, $uibModalInstance, cryptoService,
authService, $q, toastr, $analytics, constants, $timeout) {
$analytics.eventTrack('settingsTwoStepAuthenticatorController', { category: 'Modal' });
var _issuer = 'bitwarden',
var _issuer = 'Bitwarden',
_profile = null,
_masterPasswordHash,
_key = null;

View File

@@ -28,7 +28,7 @@
$analytics.eventTrack('Print Recovery Code');
var w = window.open();
w.document.write('<div style="font-size: 18px; text-align: center;"><p>bitwarden two-step login recovery code:</p>' +
w.document.write('<div style="font-size: 18px; text-align: center;"><p>Bitwarden two-step login recovery code:</p>' +
'<code style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace;">' + $scope.code + '</code>' +
'</div><p style="text-align: center;">' + new Date() + '</p>');
w.print();

View File

@@ -23,7 +23,7 @@
}, function (e) {
throw e ? e : 'Error occurred.';
}).then(function () {
toastr.success('Please log back in. If you are using other bitwarden applications, ' +
toastr.success('Please log back in. If you are using other Bitwarden applications, ' +
'log out and back in to those as well.', 'Key Updated', { timeOut: 10000 });
});
};

View File

@@ -96,7 +96,7 @@
</div>
<div class="box-body">
<p>
You membership has a total of {{storage.maxGb}} GB of encrypted file storage.
Your membership has a total of {{storage.maxGb}} GB of encrypted file storage.
You are currently using {{storage.currentName}}.
</p>
<div class="progress" style="margin: 0;">

View File

@@ -4,7 +4,7 @@
<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.
"Global" domains are ones already created for you by Bitwarden.
</p>
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise" autocomplete="off">
<div class="box box-default">

View File

@@ -8,7 +8,7 @@
</div>
<div class="box-body">
The recovery code allows you to access your account in the event that you can no longer use your normal
two-step login provider (ex. you lose your device). bitwarden support will not be able to assist you if you lose
two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose
access to your account. We recommend you write down or print the recovery code and keep it in a safe place.
</div>
<div class="box-footer">

View File

@@ -48,7 +48,7 @@
<li ng-repeat="e in submitTwoStepForm.$errors">{{e}}</li>
</ul>
</div>
<p>Enter the bitwarden application information from your Duo Admin panel:</p>
<p>Enter the Bitwarden application information from your Duo Admin panel:</p>
<div class="form-group" show-errors>
<label for="ikey">Integration Key</label>
<input type="text" id="ikey" name="IntegrationKey" ng-model="updateModel.ikey" class="form-control"
@@ -57,7 +57,7 @@
<div class="form-group" show-errors>
<label for="skey">Secret Key</label>
<input type="password" id="skey" name="SecretKey" ng-model="updateModel.skey" class="form-control"
required api-field />
required api-field autocomplete="new-password" />
</div>
<div class="form-group" show-errors>
<label for="host">API Hostname</label>

View File

@@ -33,7 +33,7 @@
<div class="callout callout-warning">
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
<p>
Due to platform limitations, FIDO U2F cannot be used on all bitwarden applications. You should enable
Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should enable
another two-step login provider so that you can access your account when FIDO U2F cannot be used.
</p>
<p>Supported platforms:</p>

View File

@@ -33,7 +33,7 @@
<div class="callout callout-warning">
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
<p>
Due to platform limitations, YubiKeys cannot be used on all bitwarden applications. You should enable
Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should enable
another two-step login provider so that you can access your account when YubiKeys cannot be used.
</p>
<p>Supported platforms:</p>
@@ -77,7 +77,7 @@
{{updateModel.key1.existingKey}}
</div>
<input type="password" id="key1" name="Key1" ng-model="updateModel.key1.key" class="form-control" api-field
ng-show="!updateModel.key1.existingKey" />
ng-show="!updateModel.key1.existingKey" autocomplete="new-password" />
</div>
<div class="form-group" show-errors>
<label for="key2">YubiKey #2</label>
@@ -88,7 +88,7 @@
{{updateModel.key2.existingKey}}
</div>
<input type="password" id="key2" name="Key2" ng-model="updateModel.key2.key" class="form-control" api-field
ng-show="!updateModel.key2.existingKey" />
ng-show="!updateModel.key2.existingKey" autocomplete="new-password" />
</div>
<div class="form-group" show-errors>
<label for="key3">YubiKey #3</label>
@@ -99,7 +99,7 @@
{{updateModel.key3.existingKey}}
</div>
<input type="password" id="key3" name="Key3" ng-model="updateModel.key3.key" class="form-control" api-field
ng-show="!updateModel.key3.existingKey" />
ng-show="!updateModel.key3.existingKey" autocomplete="new-password" />
</div>
<strong>NFC Support</strong>
<div class="checkbox">

View File

@@ -21,7 +21,7 @@
<hr />
<div class="callout callout-warning">
<h4><i class="fa fa-warning"></i> Warning</h4>
After updating your encryption key, you are required to log out and back in to all bitwarden applications that you
After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you
are currently using (such as the mobile app or browser extensions). Failure to log out and back
in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out
automatically, however it may be delayed.

View File

@@ -53,8 +53,9 @@
login_totp: null
};
var j;
if (decCiphers[i].fields) {
for (var j = 0; j < decCiphers[i].fields.length; j++) {
for (j = 0; j < decCiphers[i].fields.length; j++) {
if (!cipher.fields) {
cipher.fields = '';
}
@@ -69,10 +70,16 @@
switch (decCiphers[i].type) {
case constants.cipherType.login:
cipher.type = 'login';
cipher.login_uri = decCiphers[i].login.uri;
cipher.login_username = decCiphers[i].login.username;
cipher.login_password = decCiphers[i].login.password;
cipher.login_totp = decCiphers[i].login.totp;
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
cipher.login_uri = [];
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
}
}
break;
case constants.cipherType.secureNote:
cipher.type = 'note';

View File

@@ -11,7 +11,7 @@
$scope.options = [
{
id: 'bitwardencsv',
name: 'bitwarden (csv)',
name: 'Bitwarden (csv)',
featured: true,
sort: 1,
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
@@ -243,7 +243,7 @@
'python script by Luke Plant to your desktop as <code>pw_helper.py</code>. Open terminal and run ' +
'<code>chmod +rx Desktop/pw_helper.py</code> and then ' +
'<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload ' +
'the resulting <code>my_passwords.json</code> file here to bitwarden.')
'the resulting <code>my_passwords.json</code> file here to Bitwarden.')
}
];

View File

@@ -2,16 +2,23 @@
.module('bit.vault')
.controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants, $filter) {
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants,
$filter, selectedType) {
$analytics.eventTrack('vaultAddCipherController', { category: 'Modal' });
$scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.folders = $rootScope.vaultFolders;
$scope.constants = constants;
$scope.selectedType = constants.cipherType.login.toString();
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
$scope.cipher = {
folderId: selectedFolder ? selectedFolder.id : null,
favorite: checkedFavorite === true,
type: constants.cipherType.login,
login: {},
type: selectedType || constants.cipherType.login,
login: {
uris: [{
uri: null,
match: null,
matchValue: null
}]
},
identity: {},
card: {},
secureNote: {
@@ -44,6 +51,42 @@
}
};
$scope.addUri = function () {
if (!$scope.cipher.login) {
return;
}
if (!$scope.cipher.login.uris) {
$scope.cipher.login.uris = [];
}
$scope.cipher.login.uris.push({
uri: null,
match: null,
matchValue: null
});
};
$scope.removeUri = function (uri) {
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
return;
}
var index = $scope.cipher.login.uris.indexOf(uri);
if (index > -1) {
$scope.cipher.login.uris.splice(index, 1);
}
};
$scope.uriMatchChanged = function (uri) {
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
uri.match = null;
}
else {
uri.match = parseInt(uri.matchValue);
}
};
$scope.addField = function () {
if (!$scope.cipher.fields) {
$scope.cipher.fields = [];

View File

@@ -3,26 +3,34 @@
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants, validationService) {
$scope.loading = true;
$scope.loadingCiphers = true;
$scope.loadingGroupings = true;
$scope.ciphers = [];
$scope.folderCount = 0;
$scope.collectionCount = 0;
$scope.firstCollectionId = null;
$scope.folders = [];
$scope.collections = [];
$scope.constants = constants;
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
$scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined;
$scope.filter = undefined;
$scope.selectedType = undefined;
$scope.selectedFolder = undefined;
$scope.selectedCollection = undefined;
$scope.selectedFavorites = false;
$scope.selectedAll = true;
$scope.selectedTitle = 'All';
$scope.selectedIcon = 'fa-th';
if ($state.params.refreshFromServer) {
$rootScope.vaultGroupings = $rootScope.vaultCiphers = null;
$rootScope.vaultFolders = $rootScope.vaultCollections = $rootScope.vaultCiphers = null;
}
$scope.$on('$viewContentLoaded', function () {
$("#search").focus();
$timeout(function () {
if ($('body').hasClass('control-sidebar-open')) {
$("#search").focus();
}
}, 500);
if ($rootScope.vaultGroupings && $rootScope.vaultCiphers) {
$scope.loading = false;
loadGroupingData($rootScope.vaultGroupings);
if (($rootScope.vaultFolders || $rootScope.vaultCollections) && $rootScope.vaultCiphers) {
$scope.loadingCiphers = $scope.loadingGroupings = false;
loadCipherData($rootScope.vaultCiphers);
return;
}
@@ -31,33 +39,34 @@
});
function loadDataFromServer() {
var decGroupings = [{
var decFolders = [{
id: null,
name: 'No Folder',
folder: true
name: 'No Folder'
}];
var decCollections = [];
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
for (var i = 0; i < collections.Data.length; i++) {
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
decCollection.collection = true;
decGroupings.push(decCollection);
decCollections.push(decCollection);
}
}).$promise;
var folderPromise = apiService.folders.list({}, function (folders) {
for (var i = 0; i < folders.Data.length; i++) {
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
decFolder.folder = true;
decGroupings.push(decFolder);
decFolders.push(decFolder);
}
}).$promise;
var groupingPromise = $q.all([collectionPromise, folderPromise]).then(function () {
loadGroupingData(decGroupings);
$rootScope.vaultCollections = decCollections;
$rootScope.vaultFolders = decFolders;
$scope.loadingGroupings = false;
});
var cipherPromise = apiService.ciphers.list({}, function (ciphers) {
apiService.ciphers.list({}, function (ciphers) {
var decCiphers = [];
for (var i = 0; i < ciphers.Data.length; i++) {
@@ -68,44 +77,13 @@
groupingPromise.then(function () {
loadCipherData(decCiphers);
});
}).$promise;
$q.all([cipherPromise, groupingPromise]).then(function () {
$scope.loading = false;
});
}
function loadGroupingData(decGroupings) {
$rootScope.vaultGroupings = $filter('orderBy')(decGroupings, ['folder', groupingSort]);
var collections = $filter('filter')($rootScope.vaultGroupings, { collection: true });
$scope.collectionCount = collections.length;
$scope.folderCount = decGroupings.length - collections.length - 1;
if (collections && collections.length) {
$scope.firstCollectionId = collections[0].id;
}
}
function loadCipherData(decCiphers) {
angular.forEach($rootScope.vaultGroupings, function (grouping, groupingIndex) {
grouping.collapsed = $localStorage.collapsedFolders &&
(grouping.id || 'none') in $localStorage.collapsedFolders;
angular.forEach(decCiphers, function (cipherValue) {
if (cipherValue.favorite) {
cipherValue.sort = -1;
}
else if (grouping.folder && cipherValue.folderId == grouping.id) {
cipherValue.sort = groupingIndex;
}
else if (grouping.collection && cipherValue.collectionIds.indexOf(grouping.id) > -1) {
cipherValue.sort = groupingIndex;
}
});
});
$rootScope.vaultCiphers = $scope.ciphers = $filter('orderBy')(decCiphers, ['sort', 'name', 'subTitle']);
var chunks = chunk($rootScope.vaultCiphers, 400);
var chunks = chunk($rootScope.vaultCiphers, 200);
if (chunks.length > 0) {
$scope.ciphers = chunks[0];
var delay = 200;
@@ -120,6 +98,8 @@
}
});
}
$scope.loadingCiphers = false;
}
function sortScopedCipherData() {
@@ -136,61 +116,19 @@
return chunks;
}
function groupingSort(item) {
$scope.groupingSort = function (item) {
if (!item.id) {
return '';
}
return item.name.toLowerCase();
}
};
$scope.clipboardError = function (e) {
alert('Your web browser does not support easy clipboard copying. ' +
'Edit the item and copy it manually instead.');
};
$scope.collapseExpand = function (grouping, favorite) {
if (!$localStorage.collapsedFolders) {
$localStorage.collapsedFolders = {};
}
var id = favorite ? 'favorite' : (grouping.id || 'none');
if (id in $localStorage.collapsedFolders) {
delete $localStorage.collapsedFolders[id];
}
else {
$localStorage.collapsedFolders[id] = true;
}
};
$scope.collapseAll = function () {
if (!$localStorage.collapsedFolders) {
$localStorage.collapsedFolders = {};
}
$localStorage.collapsedFolders.none = true;
$localStorage.collapsedFolders.favorite = true;
if ($rootScope.vaultGroupings) {
for (var i = 0; i < $rootScope.vaultGroupings.length; i++) {
$localStorage.collapsedFolders[$rootScope.vaultGroupings[i].id] = true;
}
}
$('.box').addClass('collapsed-box');
$('.box .box-header button i.fa-minus').removeClass('fa-minus').addClass('fa-plus');
};
$scope.expandAll = function () {
if ($localStorage.collapsedFolders) {
delete $localStorage.collapsedFolders;
}
$('.box').removeClass('collapsed-box');
$('.box-body').show();
$('.box .box-header button i.fa-plus').removeClass('fa-plus').addClass('fa-minus');
};
$scope.editCipher = function (cipher) {
var editModel = $uibModal.open({
animation: true,
@@ -205,6 +143,8 @@
if (returnVal.action === 'edit') {
var index = $scope.ciphers.indexOf(cipher);
if (index > -1) {
// restore collection ids since those cannot change on edit here.
returnVal.data.collectionIds = $rootScope.vaultCiphers[index].collectionIds;
$rootScope.vaultCiphers[index] = returnVal.data;
}
sortScopedCipherData();
@@ -223,14 +163,15 @@
$scope.addCipher();
});
$scope.addCipher = function (grouping, favorite) {
$scope.addCipher = function () {
var addModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultAddCipher.html',
controller: 'vaultAddCipherController',
resolve: {
selectedFolder: function () { return grouping && grouping.folder ? grouping : null; },
checkedFavorite: function () { return favorite; }
selectedFolder: function () { return $scope.selectedFolder; },
selectedType: function () { return $scope.selectedType; },
checkedFavorite: function () { return $scope.selectedFavorites; }
}
});
@@ -302,6 +243,10 @@
};
$scope.editFolder = function (folder) {
if (!folder.id) {
return;
}
var editModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultEditFolder.html',
@@ -330,36 +275,30 @@
});
addModel.result.then(function (addedFolder) {
addedFolder.folder = true;
$rootScope.vaultGroupings.push(addedFolder);
loadGroupingData($rootScope.vaultGroupings);
$rootScope.vaultFolders.push(addedFolder);
});
};
$scope.deleteFolder = function (folder) {
if (!confirm('Are you sure you want to delete this folder (' + folder.name + ')?')) {
if (!folder.id) {
return;
}
if (!confirm('Are you sure you want to delete this folder (' + folder.name + ')? ' +
'Any items will be moved to "No Folder".')) {
return;
}
apiService.folders.del({ id: folder.id }, function () {
$analytics.eventTrack('Deleted Folder');
var index = $rootScope.vaultGroupings.indexOf(folder);
var index = $rootScope.vaultFolders.indexOf(folder);
if (index > -1) {
$rootScope.vaultGroupings.splice(index, 1);
$scope.folderCount--;
$rootScope.vaultFolders.splice(index, 1);
$scope.filterAll();
}
});
};
$scope.canDeleteFolder = function (folder) {
if (!folder || !folder.id || !$rootScope.vaultCiphers) {
return false;
}
var ciphers = $filter('filter')($rootScope.vaultCiphers, { folderId: folder.id });
return ciphers && ciphers.length === 0;
};
$scope.share = function (cipher) {
var modal = $uibModal.open({
animation: true,
@@ -370,8 +309,9 @@
}
});
modal.result.then(function (orgId) {
cipher.organizationId = orgId;
modal.result.then(function (returned) {
cipher.organizationId = returned.orgId;
cipher.collectionIds = returned.collectionIds || [];
});
};
@@ -395,52 +335,94 @@
});
};
$scope.filterGrouping = function (grouping) {
$scope.groupingIdFilter = grouping.id;
$scope.filterCollection = function (col) {
resetSelected();
$scope.selectedCollection = col;
$scope.selectedIcon = 'fa-cube';
$scope.filter = function (c) {
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
};
fixLayout();
};
$scope.filterFolder = function (f) {
resetSelected();
$scope.selectedFolder = f;
$scope.selectedIcon = 'fa-folder-open' + (!f.id ? '-o' : '');
$scope.filter = function (c) {
return c.folderId === f.id;
};
fixLayout();
};
$scope.filterType = function (t) {
resetSelected();
$scope.selectedType = t;
switch (t) {
case constants.cipherType.login:
$scope.selectedTitle = 'Login';
$scope.selectedIcon = 'fa-globe';
break;
case constants.cipherType.card:
$scope.selectedTitle = 'Card';
$scope.selectedIcon = 'fa-credit-card';
break;
case constants.cipherType.identity:
$scope.selectedTitle = 'Identity';
$scope.selectedIcon = 'fa-id-card-o';
break;
case constants.cipherType.secureNote:
$scope.selectedTitle = 'Secure Note';
$scope.selectedIcon = 'fa-sticky-note-o';
break;
default:
break;
}
$scope.filter = function (c) {
return c.type === t;
};
fixLayout();
};
$scope.filterFavorites = function () {
resetSelected();
$scope.selectedFavorites = true;
$scope.selectedTitle = 'Favorites';
$scope.selectedIcon = 'fa-star';
$scope.filter = function (c) {
return !!c.favorite;
};
fixLayout();
};
$scope.filterAll = function () {
resetSelected();
$scope.selectedAll = true;
$scope.selectedTitle = 'All';
$scope.selectedIcon = 'fa-th';
$scope.filter = null;
fixLayout();
};
function resetSelected() {
$scope.selectedFolder = undefined;
$scope.selectedCollection = undefined;
$scope.selectedType = undefined;
$scope.selectedFavorites = false;
$scope.selectedAll = false;
}
function fixLayout() {
if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () {
$.AdminLTE.layout.fix();
}, 0);
}
};
}
$scope.filterType = function (type) {
$scope.typeFilter = type;
if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () {
$.AdminLTE.layout.fix();
}, 0);
}
};
$scope.clearFilters = function () {
$scope.groupingIdFilter = undefined;
$scope.typeFilter = undefined;
if ($.AdminLTE && $.AdminLTE.layout) {
$timeout(function () {
$.AdminLTE.layout.fix();
}, 0);
}
};
$scope.groupingFilter = function (grouping) {
return $scope.groupingIdFilter === undefined || grouping.id === $scope.groupingIdFilter;
};
$scope.cipherFilter = function (grouping) {
$scope.cipherFilter = function () {
return function (cipher) {
var matchesGrouping = grouping === null;
if (!matchesGrouping && grouping.folder && cipher.folderId === grouping.id) {
matchesGrouping = true;
}
else if (!matchesGrouping && grouping.collection && cipher.collectionIds.indexOf(grouping.id) > -1) {
matchesGrouping = true;
}
return matchesGrouping && ($scope.typeFilter === undefined || cipher.type === $scope.typeFilter);
return !$scope.filter || $scope.filter(cipher);
};
};
@@ -448,9 +430,8 @@
selectAll(false);
};
$scope.selectFolder = function (folder, $event) {
var checkbox = $($event.currentTarget).closest('.box').find('input[name="cipherSelection"]');
checkbox.prop('checked', true);
$scope.selectAll = function () {
selectAll(true);
};
$scope.select = function ($event) {

View File

@@ -4,7 +4,7 @@
.controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants, $filter) {
$analytics.eventTrack('vaultEditCipherController', { category: 'Modal' });
$scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.folders = $rootScope.vaultFolders;
$scope.cipher = {};
$scope.readOnly = false;
$scope.constants = constants;
@@ -16,6 +16,7 @@
$scope.cipher = cipherService.decryptCipher(cipher);
$scope.readOnly = !$scope.cipher.edit;
$scope.useTotp = $scope.useTotp || $scope.cipher.organizationUseTotp;
setUriMatchValues();
});
$scope.save = function (model) {
@@ -55,6 +56,42 @@
}
};
$scope.addUri = function () {
if (!$scope.cipher.login) {
return;
}
if (!$scope.cipher.login.uris) {
$scope.cipher.login.uris = [];
}
$scope.cipher.login.uris.push({
uri: null,
match: null,
matchValue: null
});
};
$scope.removeUri = function (uri) {
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
return;
}
var index = $scope.cipher.login.uris.indexOf(uri);
if (index > -1) {
$scope.cipher.login.uris.splice(index, 1);
}
};
$scope.uriMatchChanged = function (uri) {
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
uri.match = null;
}
else {
uri.match = parseInt(uri.matchValue);
}
};
$scope.addField = function () {
if (!$scope.cipher.fields) {
$scope.cipher.fields = [];
@@ -130,4 +167,14 @@
controller: 'premiumRequiredController'
});
};
function setUriMatchValues() {
if ($scope.cipher.login && $scope.cipher.login.uris) {
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
$scope.cipher.login.uris[i].matchValue =
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
$scope.cipher.login.uris[i].match.toString() : '';
}
}
}
});

View File

@@ -4,7 +4,7 @@
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
$rootScope, $filter) {
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
$scope.folders = $filter('filter')($rootScope.vaultGroupings, { folder: true });
$scope.folders = $rootScope.vaultFolders;
$scope.count = ids.length;
$scope.save = function () {

View File

@@ -141,6 +141,7 @@
}
}
var returnedCollectionIds = null;
$scope.submitPromise = $q.all(attachmentSharePromises).then(function () {
if (errorOnUpload) {
return;
@@ -159,11 +160,15 @@
}
}
returnedCollectionIds = request.collectionIds;
return apiService.ciphers.putShare({ id: cipherId }, request).$promise;
}).then(function (response) {
$analytics.eventTrack('Shared Cipher');
toastr.success('Item has been shared.');
$uibModalInstance.close(model.organizationId);
$uibModalInstance.close({
orgId: model.organizationId,
collectionIds: returnedCollectionIds
});
});
};

View File

@@ -16,19 +16,14 @@
</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a href="#" stop-click ng-click="selectAll()">
<i class="fa fa-fw fa-check-square-o"></i> Select All
</a>
</li>
<li>
<a href="#" stop-click ng-click="unselectAll()">
<i class="fa fa-fw fa-minus-circle"></i> Unselect All
</a>
</li>
<li>
<a href="#" stop-click ng-click="collapseAll()">
<i class="fa fa-fw fa-minus-square-o"></i> Collapse All
</a>
</li>
<li>
<a href="#" stop-click ng-click="expandAll()">
<i class="fa fa-fw fa-plus-square-o"></i> Expand All
<i class="fa fa-fw fa-minus-square-o"></i> Unselect All
</a>
</li>
</ul>
@@ -36,184 +31,58 @@
<h1>
My Vault
<small class="visible-md-inline visible-lg-inline">
<span ng-pluralize count="folderCount" when="{'1': '{} folder', 'other': '{} folders'}"></span>,
<span ng-pluralize count="collectionCount" when="{'1': '{} collection', 'other': '{} collections'}"></span>, &amp;
<span ng-pluralize count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
<span ng-pluralize count="vaultCollections.length"
when="{'1': '{} collection', 'other': '{} collections'}"></span>, &amp;
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
</small>
</h1>
</section>
<section class="content">
<div ng-show="loading && !vaultGroupings.length">
<div ng-show="loadingCiphers">
<p>Loading...</p>
</div>
<div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;"
ng-show="vaultGroupings.length && groupingIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">
<div class="box" ng-show="!loadingCiphers">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa fa-star"></i>
Favorites
<small ng-pluralize count="favoriteCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
<i class="fa {{selectedIcon}}"></i>
{{selectedFolder ? selectedFolder.name : selectedCollection ? selectedCollection.name : selectedTitle}}
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
</h3>
<div class="box-tools">
<div class="box-tools" ng-if="selectedFolder && selectedFolder.id">
<div class="btn-group">
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a href="#" stop-click ng-click="addCipher(null, true)">
<i class="fa fa-fw fa-plus-circle"></i> New Item
</a>
</li>
</ul>
</div>
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
ng-click="collapseExpand(null, true)">
<i class="fa" ng-class="{'fa-minus': !favoriteCollapsed, 'fa-plus': favoriteCollapsed}"></i>
</button>
</div>
</div>
<div class="box-body" ng-class="{'no-padding': favoriteCiphers.length}">
<div ng-show="!favoriteCiphers.length">
<p>No favorite items.</p>
<button type="button" ng-click="addCipher(null, true)" class="btn btn-default btn-flat">Add an Item</button>
</div>
<div class="table-responsive" ng-show="favoriteCiphers.length">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } |
filter: cipherFilter(null) | filter: (main.searchVaultText || '')) track by cipher.id">
<td style="width: 70px;">
<div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="#" stop-click ng-click="editCipher(cipher)">
<i class="fa fa-fw fa-pencil"></i> Edit
</a>
</li>
<li>
<a href="#" stop-click ng-click="attachments(cipher)">
<i class="fa fa-fw fa-paperclip"></i> Attachments
</a>
</li>
<li ng-show="!cipher.organizationId">
<a href="#" stop-click ng-click="share(cipher)">
<i class="fa fa-fw fa-share-alt"></i> Share
</a>
</li>
<li ng-show="cipher.organizationId && cipher.edit">
<a href="#" stop-click ng-click="editCollections(cipher)">
<i class="fa fa-fw fa-cubes"></i> Collections
</a>
</li>
<li ng-show="cipher.meta.password">
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-text="{{cipher.meta.password}}">
<i class="fa fa-fw fa-clipboard"></i> Copy Password
</a>
</li>
<li ng-show="cipher.edit">
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
<i class="fa fa-fw fa-trash"></i> Delete
</a>
</li>
</ul>
</div>
</td>
<td class="action-select" ng-click="select($event)">
<input type="checkbox" value="{{::cipher.id}}" name="cipherSelection" stop-prop />
</td>
<td class="vault-icon" ng-click="select($event)">
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
fallback-src="images/fa-globe.png" />
</td>
<td ng-click="select($event)">
<a href="#" stop-click ng-click="editCipher(cipher)" stop-prop>{{cipher.name}}</a>
<i class="fa fa-share-alt text-muted" title="Shared" ng-if="cipher.organizationId"
stop-prop></i>
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
stop-prop></i><br />
<span class="text-sm text-muted" stop-prop>{{cipher.subTitle}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="box" ng-class="{'collapsed-box': grouping.collapsed}"
ng-repeat="grouping in filteredVaultGroupings = (vaultGroupings | filter: groupingFilter) track by grouping.id"
ng-show="vaultGroupings.length && (!main.searchVaultText || groupingCiphers.length)"
ng-style="firstCollectionId && grouping.id === firstCollectionId &&
groupingIdFilter !== grouping.id && {'margin-top': '40px'}">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa" ng-if="grouping.folder"
ng-class="{'fa-folder-open': grouping.id !== null, 'fa-folder-open-o': grouping.id === null}"></i>
<i class="fa fa-cube" ng-if="grouping.collection"></i>
{{grouping.name}}
<small ng-pluralize count="groupingCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
</h3>
<div class="box-tools">
<div class="btn-group">
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" ng-if="grouping.folder">
<li>
<a href="#" stop-click ng-click="addCipher(grouping)">
<i class="fa fa-fw fa-plus-circle"></i> New Item
</a>
</li>
<li ng-show="grouping.id">
<a href="#" stop-click ng-click="editFolder(grouping)">
<a href="#" stop-click ng-click="editFolder(selectedFolder)">
<i class="fa fa-fw fa-pencil"></i> Edit Folder
</a>
</li>
<li ng-show="canDeleteFolder(grouping)">
<a href="#" stop-click ng-click="deleteFolder(grouping)" class="text-red">
<li>
<a href="#" stop-click ng-click="deleteFolder(selectedFolder)" class="text-red">
<i class="fa fa-fw fa-trash"></i> Delete Folder
</a>
</li>
<li>
<a href="#" stop-click ng-click="selectFolder(grouping, $event)">
<i class="fa fa-fw fa-check-square-o"></i> Select All
</a>
</li>
</ul>
<ul class="dropdown-menu dropdown-menu-right" ng-if="grouping.collection">
<li>
<a href="#" stop-click ng-click="selectFolder(grouping, $event)">
<i class="fa fa-fw fa-check-square-o"></i> Select All
</a>
</li>
</ul>
</div>
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
ng-click="collapseExpand(grouping)">
<i class="fa" ng-class="{'fa-minus': !grouping.collapsed, 'fa-plus': grouping.collapsed}"></i>
</button>
</div>
</div>
<div class="box-body" ng-class="{'no-padding': groupingCiphers.length}">
<div ng-show="!groupingCiphers.length">
<div ng-if="grouping.folder">
<p>No items in this folder.</p>
<button type="button" ng-click="addCipher(grouping)" class="btn btn-default btn-flat">Add an Item</button>
</div>
<div ng-if="!grouping.folder">
<p>No items in this collection.</p>
</div>
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
<div ng-show="!filteredCiphers.length">
<p>No items to list.</p>
<button type="button" ng-click="addCipher()" class="btn btn-default btn-flat"
ng-if="!selectedCollection">
Add an Item
</button>
</div>
<div class="table-responsive" ng-show="groupingCiphers.length">
<div class="table-responsive" ng-show="filteredCiphers.length">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="cipher in groupingCiphers = (ciphers | filter: cipherFilter(grouping) |
filter: (main.searchVaultText || '')) track by cipher.id">
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
filter: (searchVaultText || '')) track by cipher.id">
<td style="width: 70px;">
<div class="btn-group" data-append-to="body">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
@@ -281,80 +150,82 @@
</section>
<aside class="control-sidebar control-sidebar-light">
<div class="tab-content">
<form class="search-form">
<label for="search" class="sr-only">Search</label>
<div class="form-group has-feedback">
<input type="search" id="search" class="form-control" placeholder="Search my vault..."
ng-model="searchVaultText" />
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
</div>
</form>
<ul class="control-sidebar-menu">
<li>
<a href="#" stop-click ng-click="clearFilters()">
Clear All Filters
<li ng-class="{active: selectedAll}">
<a href="#" stop-click ng-click="filterAll()">
<i class="fa fa-th fa-fw"></i> All Items
</a>
</li>
<li ng-class="{active: selectedFavorites}">
<a href="#" stop-click ng-click="filterFavorites()">
<i class="fa fa-star fa-fw"></i> Favorites
</a>
</li>
</ul>
<h3 class="control-sidebar-heading">
<i class="fa fa-tag fa-fw"></i> Types
</h3>
<h3 class="control-sidebar-heading">Types</h3>
<div class="control-sidebar-section">
<ul class="control-sidebar-menu">
<li>
<li ng-class="{active: constants.cipherType.login === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.login === typeFilter"></i>
<i class="fa fa-globe fa-fw" ng-if="constants.cipherType.login !== typeFilter"></i> Login
<i class="fa fa-globe fa-fw"></i> Login
</a>
</li>
<li>
<li ng-class="{active: constants.cipherType.card === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.card === typeFilter"></i>
<i class="fa fa-credit-card fa-fw" ng-if="constants.cipherType.card !== typeFilter"></i> Card
<i class="fa fa-credit-card fa-fw"></i> Card
</a>
</li>
<li>
<li ng-class="{active: constants.cipherType.identity === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.identity === typeFilter"></i>
<i class="fa fa-id-card-o fa-fw" ng-if="constants.cipherType.identity !== typeFilter"></i> Identity
<i class="fa fa-id-card-o fa-fw"></i> Identity
</a>
</li>
<li>
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.secureNote === typeFilter"></i>
<i class="fa fa-sticky-note-o fa-fw" ng-if="constants.cipherType.secureNote !== typeFilter"></i> Secure Note
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
</a>
</li>
</ul>
</div>
<h3 class="control-sidebar-heading">
<i class="fa fa-folder fa-fw"></i> Folders
</h3>
<div ng-show="loading && !vaultGroupings.length">
<h3 class="control-sidebar-heading">Folders</h3>
<div ng-show="loadingGroupings">
<p>Loading...</p>
</div>
<div class="control-sidebar-section">
<ul class="control-sidebar-menu" ng-show="!loading && folders.length">
<li ng-repeat="folder in folders = (vaultGroupings | filter: {folder: true}) track by folder.id">
<a href="#" stop-click ng-click="filterGrouping(folder)">
<i class="fa fa-check fa-fw" ng-if="folder.id === groupingIdFilter"></i>
<i class="fa fa-caret-right fa-fw" ng-if="folder.id !== groupingIdFilter"></i>
<div class="control-sidebar-section" ng-show="!loadingGroupings">
<ul class="control-sidebar-menu">
<li ng-repeat="folder in vaultFolders | orderBy: [groupingSort] track by folder.id"
ng-class="{active: selectedFolder && folder.id === selectedFolder.id}">
<a href="#" stop-click ng-click="filterFolder(folder)">
<i class="fa fa-caret-right fa-fw"></i>
{{folder.name}}
</a>
</li>
</ul>
</div>
<h3 class="control-sidebar-heading">
<i class="fa fa-cubes fa-fw"></i> Collections
</h3>
<div ng-show="loading && !vaultGroupings.length">
<h3 class="control-sidebar-heading">Collections</h3>
<div ng-show="loadingGroupings">
<p>Loading...</p>
</div>
<div ng-show="!loading && !collections.length">
<div ng-show="!loadingGroupings && !vaultCollections.length">
<p>No collections are being shared with you. <i class="fa fa-frown-o"></i></p>
<a ui-sref="backend.user.settingsCreateOrg" class="btn btn-default btn-lint">
Create an Organization
</a>
</div>
<div class="control-sidebar-section">
<ul class="control-sidebar-menu" ng-show="!loading && collections.length">
<li ng-repeat="collection in collections =
(vaultGroupings | filter: {collection: true}) track by collection.id">
<a href="#" stop-click ng-click="filterGrouping(collection)">
<i class="fa fa-check fa-fw" ng-if="collection.id === groupingIdFilter"></i>
<i class="fa fa-caret-right fa-fw" ng-if="collection.id !== groupingIdFilter"></i>
<div class="control-sidebar-section" ng-show="!loadingGroupings && vaultCollections.length">
<ul class="control-sidebar-menu">
<li ng-repeat="collection in vaultCollections | orderBy: [groupingSort] track by collection.id"
ng-class="{active: selectedCollection && collection.id === selectedCollection.id}">
<a href="#" stop-click ng-click="filterCollection(collection)">
<i class="fa fa-caret-right fa-fw"></i>
{{collection.name}}
</a>
</li>

View File

@@ -39,26 +39,7 @@
</div>
</div>
</div>
<div ng-if="cipher.type === constants.cipherType.login">
<div class="form-group" show-errors>
<label for="uri">URI</label>
<div class="input-group">
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
placeholder="http://..." ng-readonly="readOnly" api-field />
<span class="input-group-btn">
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-target="#uri">
<i class="fa fa-clipboard"></i>
</button>
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
uib-tooltip="Go To Website" tooltip-placement="left">
<i class="fa fa-share"></i>
</a>
</span>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group" show-errors>
@@ -87,7 +68,8 @@
<label for="password">Password</label>
<div class="input-group">
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
class="form-control monospaced" ng-readonly="readOnly" api-field />
class="form-control monospaced" ng-readonly="readOnly" api-field
autocomplete="new-password" />
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
<button class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
@@ -119,6 +101,58 @@
</div>
</div>
</div>
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
<div class="row">
<div class="col-sm-7">
<div class="form-group" show-errors>
<label for="uri{{$index}}">URI {{$index + 1}}</label>
<div class="input-group">
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
ng-model="u.uri" class="form-control"
placeholder="http://..." ng-readonly="readOnly" api-field />
<span class="input-group-btn">
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-target="#uri{{$index}}">
<i class="fa fa-clipboard"></i>
</button>
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
uib-tooltip="Go To Website" tooltip-placement="left">
<i class="fa fa-share"></i>
</a>
</span>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="uri_match_{{$index}}">Match Detection</label>
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match"
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
<option value="">Default</option>
<option value="0">Base domain</option>
<option value="1">Host</option>
<option value="2">Starts with</option>
<option value="4">Regular Expression</option>
<option value="3">Exact</option>
<option value="5">Never</option>
</select>
</div>
</div>
<div class="col-sm-1">
<br class="hidden-xs" />
<a href="#" ng-click="removeUri(u)" stop-click>
<i class="fa fa-window-close-o fa-lg"></i>
<span class="visible-xs-inline">Remove URI</span>
</a>
</div>
</div>
<hr class="visible-xs-block" />
</div>
<a href="#" ng-click="addUri()" stop-click>
<i class="fa fa-plus-circle"></i> New URI
</a>
<br /><br />
</div>
<div ng-if="cipher.type === constants.cipherType.card">
<div class="row">
@@ -504,7 +538,6 @@
<div ng-if="cipher.type === constants.cipherType.secureNote">
<!-- Nothing for now -->
</div>
<div class="form-group" show-errors>
<label for="notes">Notes</label>
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes" api-field

View File

@@ -33,24 +33,6 @@
</div>
<div ng-if="cipher.type === constants.cipherType.login">
<div class="form-group" show-errors>
<label for="uri">URI</label>
<div class="input-group">
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
placeholder="http://..." ng-readonly="readOnly" api-field />
<span class="input-group-btn">
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-target="#uri">
<i class="fa fa-clipboard"></i>
</button>
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
uib-tooltip="Go To Website" tooltip-placement="left">
<i class="fa fa-share"></i>
</a>
</span>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group" show-errors>
@@ -79,7 +61,8 @@
<label for="password">Password</label>
<div class="input-group">
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
class="form-control monospaced" ng-readonly="readOnly" api-field />
class="form-control monospaced" ng-readonly="readOnly" api-field
autocomplete="new-password" />
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
<button class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
@@ -111,6 +94,60 @@
</div>
</div>
</div>
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
<div class="row">
<div class="col-sm-7">
<div class="form-group" show-errors>
<label for="uri{{$index}}">URI {{$index + 1}}</label>
<div class="input-group">
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
ng-model="u.uri" class="form-control"
placeholder="http://..." ng-readonly="readOnly" api-field />
<span class="input-group-btn">
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
data-clipboard-target="#uri{{$index}}">
<i class="fa fa-clipboard"></i>
</button>
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
uib-tooltip="Go To Website" tooltip-placement="left">
<i class="fa fa-share"></i>
</a>
</span>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="uri_match_{{$index}}">Match Detection</label>
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match" ng-disabled="readOnly"
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
<option value="">Default</option>
<option value="0">Base domain</option>
<option value="1">Host</option>
<option value="2">Starts with</option>
<option value="4">Regular Expression</option>
<option value="3">Exact</option>
<option value="5">Never</option>
</select>
</div>
</div>
<div class="col-sm-1" ng-if="!readOnly">
<br class="hidden-xs" />
<a href="#" ng-click="removeUri(u)" stop-click>
<i class="fa fa-window-close-o fa-lg"></i>
<span class="visible-xs-inline">Remove URI</span>
</a>
</div>
</div>
<hr class="visible-xs-block" />
</div>
<div ng-if="!readOnly">
<a href="#" ng-click="addUri()" stop-click>
<i class="fa fa-plus-circle"></i> New URI
</a>
<br /><br />
</div>
</div>
<div ng-if="cipher.type === constants.cipherType.card">
<div class="row">
@@ -551,9 +588,9 @@
</div>
</div>
</div>
<div class="col-sm-1">
<div class="col-sm-1" ng-if="!readOnly">
<br class="hidden-xs" />
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
<a href="#" ng-click="removeField(field)" stop-click>
<i class="fa fa-window-close-o fa-lg"></i>
<span class="visible-xs-inline">Remove Custom Field</span>
</a>

View File

@@ -14,7 +14,7 @@
<p ng-show="loading">Loading...</p>
<div ng-show="!loading && !organizations.length" class="callout callout-default">
<h4><i class="fa fa-info-circle"></i> No Organizations</h4>
<p>You do not belong to any organizations. Organizations allow you to share items with other bitwarden users.</p>
<p>You do not belong to any organizations. Organizations allow you to share items with other Bitwarden users.</p>
<a ng-click="createOrg()" class="btn btn-default btn-flat">
Create an Organization
</a>

View File

@@ -1,135 +0,0 @@
<section class="content-header">
<h1>
Apps
<small>for all of your devices</small>
</h1>
</section>
<section class="content">
<div class="box box-default box-apps">
<div class="box-header with-border">
<h3 class="box-title">Desktop/Browser</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
<i class="fa fa-chrome fa-lg fa-fw fa-li"></i> Google Chrome
</a>
</li>
<li>
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
<i class="fa fa-firefox fa-lg fa-fw fa-li"></i> Mozilla Firefox
</a>
</li>
<li>
<a href="https://addons.opera.com/extensions/details/bitwarden-free-password-manager/" target="_blank">
<i class="fa fa-opera fa-lg fa-fw fa-li"></i> Opera
</a>
</li>
<li>
<a href="https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl" target="_blank">
<i class="fa fa-edge fa-lg fa-fw fa-li"></i> Microsoft Edge
</a>
</li>
</ul>
</div>
<div class="col-sm-6">
Others:
<ul>
<li>
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
Vivaldi
</a>
</li>
<li>
<a href="https://brave.com/" target="_blank">
Brave
</a>
</li>
<li>
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
Tor Browser
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="box box-default box-apps">
<div class="box-header with-border">
<h3 class="box-title">Mobile</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<a href="https://itunes.apple.com/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank">
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> iOS
</a>
</li>
<li>
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank">
<i class="fa fa-android fa-lg fa-fw fa-li"></i> Android
</a>
</li>
</ul>
</div>
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<a href="#" stop-click>
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Windows
<small class="text-muted">(coming soon)</small>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="box box-default box-apps">
<div class="box-header with-border">
<h3 class="box-title">Other</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<a href="#" stop-click>
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Desktop Windows
<small class="text-muted">(coming soon)</small>
</a>
</li>
<li>
<a href="#" stop-click>
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> Desktop macOS
<small class="text-muted">(coming soon)</small>
</a>
</li>
</ul>
</div>
<div class="col-sm-6">
<ul class="fa-ul">
<li>
<a href="#" stop-click>
<i class="fa fa-linux fa-lg fa-fw fa-li"></i> Desktop Linux
<small class="text-muted">(coming soon)</small>
</a>
</li>
<li>
<a href="#" stop-click>
<i class="fa fa-terminal fa-lg fa-fw fa-li"></i> CLI
<small class="text-muted">(coming soon)</small>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>

View File

@@ -32,14 +32,6 @@
<a ui-sref="backend.user.vault"><i class="fa fa-arrow-left"></i> Return to my vault</a>
</div>
</div>
<form class="sidebar-form">
<label for="search" class="sr-only">Search</label>
<div class="form-group has-feedback">
<input type="text" id="search" class="form-control" placeholder="Search org. vault..."
ng-focus="searchOrganizationVault()" ng-model="main.searchVaultText" />
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
</div>
</form>
<ul class="sidebar-menu">
<li class="header">MY ORGANIZATION</li>
<li ng-class="{active: $state.is('backend.org.dashboard')}">

View File

@@ -32,14 +32,6 @@
<a ui-sref="frontend.logout">Log Out</a>
</div>
</div>
<form class="sidebar-form">
<label for="search" class="sr-only">Search</label>
<div class="form-group has-feedback">
<input type="text" id="search" class="form-control" placeholder="Search my vault..."
ng-focus="searchVault()" ng-model="main.searchVaultText" />
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
</div>
</form>
<ul class="sidebar-menu">
<li class="header">WEB VAULT</li>
<li class="treeview" ng-class="{active: $state.includes('backend.user.vault')}">
@@ -104,7 +96,7 @@
</ul>
</li>
<li ng-class="{active: $state.is('backend.user.apps')}">
<a ui-sref="backend.user.apps">
<a href="https://bitwarden.com/#download" target="_blank">
<small class="label pull-right bg-green">FREE</small>
<i class="fa fa-download fa-fw"></i> <span>Get the Apps</span>
</a>

View File

@@ -26,47 +26,6 @@
</head>
<body>
<script src="js/duo.js"></script>
<script>
!(function () {
var frameElement = document.createElement('iframe');
frameElement.setAttribute('id', 'duo_iframe');
setFrameHeight();
document.body.appendChild(frameElement);
var hostParam = getQsParam('host');
var requestParam = getQsParam('request');
Duo.init({
host: hostParam,
sig_request: requestParam,
submit_callback: function (form) {
invokeCSCode(form.elements.sig_response.value);
}
});
window.onresize = setFrameHeight;
function setFrameHeight() {
frameElement.style.height = window.innerHeight + 'px';
}
})();
function getQsParam(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
function invokeCSCode(data) {
try {
invokeCSharpAction(data);
}
catch (err) {
}
}
</script>
<script src="js/duo-connector.js"></script>
</body>
</html>

View File

@@ -31,7 +31,7 @@
<meta name="theme-color" content="#3c8dbc">
<base href="/" />
<title page-title>bitwarden Web Vault</title>
<title page-title>Bitwarden Web Vault</title>
<!-- @if !selfHosted -->
<script src="https://js.stripe.com/v2/"></script>
@@ -67,12 +67,12 @@
<div ui-view></div>
<!-- @if !selfHosted !>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"
integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"
integrity="sha384-AH/e+s4V4kUifvnNED2x1XZqArO5qTFU4YKRzUXbz4IgPG1H0Xmz6fP1XUmO4vT/" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"
integrity="sha384-R6kAKgTgRiD5889XyzYD/aMryNA4Yr9EBnt6rIXuukLgVONifQDnHNaadrSNakQl" crossorigin="anonymous"></script>
<!-- @endif -->
<!-- @if true !>
<script src="js/fallback-scripts.min.js?v=<!-- @echo cacheTag !>"></script>
@@ -153,7 +153,6 @@
<script src="app/global/mainController.js"></script>
<script src="app/global/topNavController.js"></script>
<script src="app/global/sideNavController.js"></script>
<script src="app/global/appsController.js"></script>
<script src="app/global/premiumRequiredController.js"></script>
<script src="app/global/paidOrgRequiredController.js"></script>

40
src/js/duo-connector.js Normal file
View File

@@ -0,0 +1,40 @@
!(function () {
var frameElement = document.createElement('iframe');
frameElement.setAttribute('id', 'duo_iframe');
setFrameHeight();
document.body.appendChild(frameElement);
var hostParam = getQsParam('host');
var requestParam = getQsParam('request');
Duo.init({
host: hostParam,
sig_request: requestParam,
submit_callback: function (form) {
invokeCSCode(form.elements.sig_response.value);
}
});
window.onresize = setFrameHeight;
function setFrameHeight() {
frameElement.style.height = window.innerHeight + 'px';
}
})();
function getQsParam(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
function invokeCSCode(data) {
try {
invokeCSharpAction(data);
}
catch (err) {
}
}

View File

@@ -152,7 +152,14 @@ body.skin-blue {
.control-sidebar-heading {
padding: 0;
margin: 10px 0 10px 0;
margin: 15px 0 10px 0;
text-transform: uppercase;
font-size: @font-size-base;
font-weight: normal;
}
.control-sidebar-light .control-sidebar-heading {
color: @text-muted;
}
.control-sidebar-menu {
@@ -160,10 +167,11 @@ body.skin-blue {
padding-top: 5px;
padding-bottom: 5px;
white-space: nowrap;
color: @text-color;
}
li.active a {
background-color: @component-active-bg;
li.active a, li.active a:hover {
background-color: lighten(@gray-lte, 5%);
}
}
@@ -172,6 +180,8 @@ body.skin-blue {
overflow-y: auto;
padding-right: 15px;
margin-right: -15px;
padding-left: 15px;
margin-left: -15px;
}
.control-sidebar-open {
@@ -735,7 +745,7 @@ h1, h2, h3, h4, h5, h6 {
.totp-col {
margin: -10px 0 10px 0;
@media (min-width: @screen-md) {
@media (min-width: @screen-sm) {
padding-top: 26px;
margin: 0;
}
@@ -815,9 +825,9 @@ h1, h2, h3, h4, h5, h6 {
textarea {
&#notes {
height: 100px;
}
&.big-textarea {
height: 200px !important;
&.big-textarea {
height: 200px;
}
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "bitwarden vault",
"name": "Bitwarden Vault",
"icons": [
{
"src": "images/icons/android-chrome-192x192.png",

3
src/version.json Normal file
View File

@@ -0,0 +1,3 @@
{
"version": "0.0.0"
}