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

Compare commits

..

50 Commits

Author SHA1 Message Date
Kyle Spearrin
14bac6a744 fix userid comparisons 2018-04-16 16:26:54 -04:00
Kyle Spearrin
05cc9b45e6 bump version 2018-04-16 16:10:53 -04:00
Kyle Spearrin
dba596bf35 fix if when no currentid 2018-04-16 16:08:59 -04:00
Kyle Spearrin
db39d58ea8 remove empty uri on add 2018-04-16 15:17:50 -04:00
Kyle Spearrin
c0f38216ef manage group from entrypoint 2018-04-16 15:17:13 -04:00
Kyle Spearrin
3643222b3c added org duo to 2fa flow 2018-04-03 14:33:00 -04:00
Kyle Spearrin
551217ea38 filter for unassigned collection grouping 2018-04-03 08:35:45 -04:00
Kyle Spearrin
24bf1363ab org 2fa management for duo 2018-04-02 23:19:04 -04:00
Kyle Spearrin
08b2184e12 version bump 2018-04-02 21:22:30 -04:00
Kyle Spearrin
b73161882c make user homedir with helper 2018-04-02 21:12:45 -04:00
Kyle Spearrin
e2186ecd62 Revert "make user home dir"
This reverts commit b407402f3f.
2018-04-02 21:12:02 -04:00
Kyle Spearrin
b407402f3f make user home dir 2018-04-02 19:59:11 -04:00
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
68 changed files with 10731 additions and 948 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,15 @@
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
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: 66eacea870...62e62e3684

View File

@@ -1,5 +1,64 @@
#!/bin/sh
#!/bin/bash
# Setup
GROUPNAME="bitwarden"
USERNAME="bitwarden"
CURRENTGID=`getent group $GROUPNAME | cut -d: -f3`
LGID=${LOCAL_GID:-999}
NOUSER=`id -u $USERNAME > /dev/null 2>&1; echo $?`
LUID=${LOCAL_UID:-999}
# Step down from host root
if [ $LGID == 0 ]
then
LGID=999
fi
if [ $LUID == 0 ]
then
LUID=999
fi
# Create group
if [ $CURRENTGID ]
then
if [ "$CURRENTGID" != "$LGID" ]
then
groupmod -g $LGID $GROUPNAME
fi
else
groupadd -g $LGID $GROUPNAME
fi
# Create user and assign group
if [ $NOUSER == 0 ] && [ `id -u $USERNAME` != $LUID ]
then
usermod -u $LUID $USERNAME
elif [ $NOUSER == 1 ]
then
useradd -r -u $LUID -g $GROUPNAME $USERNAME
fi
# Make home directory for user
if [ ! -d "/home/$USERNAME" ]
then
mkhomedir_helper $USERNAME
fi
# The rest...
chown -R $USERNAME:$GROUPNAME /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:$GROUPNAME /app
chown -R $USERNAME:$GROUPNAME /bitwarden_server
gosu $USERNAME:$GROUPNAME 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'),
@@ -77,7 +78,7 @@ gulp.task('min:js', ['clean:js'], function () {
], { base: '.' })
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
//.pipe(uglify())
.pipe(gulp.dest('.'));
});
@@ -395,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('.'));
});
@@ -407,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'));
});
@@ -420,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('.'));
});
@@ -435,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('.'));
});
@@ -448,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.1",
"version": "1.26.0",
"env": "Production",
"devDependencies": {
"connect": "3.6.5",
@@ -16,6 +16,7 @@
"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

@@ -216,8 +216,9 @@ angular
function init() {
stopU2fCheck = true;
var params;
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo ||
$scope.twoFactorProvider === constants.twoFactorProvider.organizationDuo) {
params = $scope.twoFactorProviders[$scope.twoFactorProvider];
$window.Duo.init({
host: params.Host,

View File

@@ -6,6 +6,9 @@
$scope.providers = [];
if (providers.hasOwnProperty(constants.twoFactorProvider.organizationDuo)) {
add(constants.twoFactorProvider.organizationDuo);
}
if (providers.hasOwnProperty(constants.twoFactorProvider.authenticator)) {
add(constants.twoFactorProvider.authenticator);
}

View File

@@ -83,7 +83,8 @@
</form>
</div>
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo">
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo ||
twoFactorProvider === twoFactorProviderConstants.organizationDuo">
<p class="login-box-msg">
Complete logging in with Duo.
</p>

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

@@ -26,7 +26,8 @@ angular.module('bit')
duo: 2,
authenticator: 0,
email: 1,
remember: 5
remember: 5,
organizationDuo: 6
},
cipherType: {
login: 1,
@@ -98,14 +99,15 @@ angular.module('bit')
type: 0,
name: 'Authenticator App',
description: 'Use an authenticator app (such as Authy or Google Authenticator) to generate time-based ' +
'verification codes.',
'verification codes.',
enabled: false,
active: true,
free: true,
image: 'authapp.png',
displayOrder: 0,
priority: 1,
requiresUsb: false
requiresUsb: false,
organization: false
},
{
type: 3,
@@ -116,7 +118,8 @@ angular.module('bit')
image: 'yubico.png',
displayOrder: 1,
priority: 3,
requiresUsb: true
requiresUsb: true,
organization: false
},
{
type: 2,
@@ -127,7 +130,8 @@ angular.module('bit')
image: 'duo.png',
displayOrder: 2,
priority: 2,
requiresUsb: false
requiresUsb: false,
organization: false
},
{
type: 4,
@@ -138,7 +142,8 @@ angular.module('bit')
image: 'fido.png',
displayOrder: 3,
priority: 4,
requiresUsb: true
requiresUsb: true,
organization: false
},
{
type: 1,
@@ -150,7 +155,21 @@ angular.module('bit')
image: 'gmail.png',
displayOrder: 4,
priority: 0,
requiresUsb: false
requiresUsb: false,
organization: false
},
{
type: 6,
name: 'Duo (Organization)',
description: 'Verify with Duo Security for your organization using the Duo Mobile app, SMS, ' +
'phone call, or U2F security key.',
enabled: false,
active: true,
image: 'duo.png',
displayOrder: 1,
priority: 10,
requiresUsb: false,
organization: true
}
],
plans: {

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

@@ -2,11 +2,14 @@
.module('bit.organization')
.controller('organizationSettingsController', function ($scope, $state, apiService, toastr, authService, $uibModal,
$analytics, appSettings) {
$analytics, appSettings, constants, $filter) {
$scope.selfHosted = appSettings.selfHosted;
$scope.model = {};
$scope.twoStepProviders = $filter('filter')(constants.twoFactorProviderInfo, { organization: true });
$scope.use2fa = false;
$scope.$on('$viewContentLoaded', function () {
apiService.organizations.get({ id: $state.params.orgId }, function (org) {
apiService.organizations.get({ id: $state.params.orgId }).$promise.then(function (org) {
$scope.model = {
name: org.Name,
billingEmail: org.BillingEmail,
@@ -17,6 +20,29 @@
businessCountry: org.BusinessCountry,
businessTaxNumber: org.BusinessTaxNumber
};
$scope.use2fa = org.Use2fa;
if (org.Use2fa) {
return apiService.twoFactor.listOrganization({ orgId: $state.params.orgId }).$promise;
}
else {
return null;
}
}).then(function (response) {
if (!response || !response.Data) {
return;
}
for (var i = 0; i < response.Data.length; i++) {
if (!response.Data[i].Enabled) {
continue;
}
var provider = $filter('filter')($scope.twoStepProviders, { type: response.Data[i].Type });
if (provider.length) {
provider[0].enabled = true;
}
}
});
});
@@ -56,4 +82,30 @@
controller: 'organizationDeleteController'
});
};
$scope.edit = function (provider) {
if (provider.type === constants.twoFactorProvider.organizationDuo) {
typeName = 'Duo';
}
else {
return;
}
var modal = $uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
controller: 'settingsTwoStep' + typeName + 'Controller',
resolve: {
enabled: function () { return provider.enabled; },
orgId: function () { return $state.params.orgId; }
}
});
modal.result.then(function (enabled) {
if (enabled || enabled === false) {
// do not adjust when undefined or null
provider.enabled = enabled;
}
});
};
});

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,79 @@
}
});
};
$scope.filterCollection = function (col) {
resetSelected();
$scope.selectedCollection = col;
$scope.selectedIcon = 'fa-cube';
if (col.id) {
$scope.filter = function (c) {
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
};
}
else {
$scope.filter = function (c) {
return !c.collectionIds || c.collectionIds.length === 0;
};
}
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

@@ -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

@@ -63,6 +63,36 @@
</div>
</form>
</div>
<div class="box box-default" ng-if="use2fa">
<div class="box-header with-border">
<h3 class="box-title">Two-step Login Providers</h3>
</div>
<div class="box-body no-padding">
<div class="table-responsive">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="provider in twoStepProviders | orderBy: 'displayOrder'">
<td style="width: 120px; height: 75px;" align="center">
<a href="#" stop-click ng-click="edit(provider)">
<img alt="{{::provider.name}}" ng-src="{{'images/two-factor/' + provider.image}}" />
</a>
</td>
<td>
<a href="#" stop-click ng-click="edit(provider)">{{::provider.name}}</a>
<div class="text-muted text-sm">{{::provider.description}}</div>
</td>
<td style="width: 100px;" class="text-right">
<span class="label label-full"
ng-class="{ 'label-success': provider.enabled, 'label-default': !provider.enabled }">
{{provider.enabled ? 'Enabled' : 'Disabled'}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Import/Export</h3>

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', {}, {
@@ -153,9 +160,11 @@
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
list: { method: 'GET', params: {} },
listOrganization: { url: _apiUri + '/organizations/:orgId/two-factor', method: 'GET', params: { orgId: '@orgId' } },
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
getU2f: { url: _apiUri + '/two-factor/get-u2f', method: 'POST', params: {} },
getDuo: { url: _apiUri + '/two-factor/get-duo', method: 'POST', params: {} },
getOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/get-duo', method: 'POST', params: { orgId: '@orgId' } },
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
@@ -164,8 +173,10 @@
putU2f: { url: _apiUri + '/two-factor/u2f', method: 'POST', params: {} },
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
putDuo: { url: _apiUri + '/two-factor/duo', method: 'POST', params: {} },
putOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/duo', method: 'POST', params: { orgId: '@orgId' } },
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
disableOrganization: { url: _apiUri + '/organizations/:orgId/two-factor/disable', method: 'POST', params: { orgId: '@orgId' } },
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
getRecover: { url: _apiUri + '/two-factor/get-recover', method: 'POST', params: {} }
});

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;
};
@@ -152,6 +152,7 @@ angular
useGroups: profile.Organizations[i].UseGroups,
useDirectory: profile.Organizations[i].UseDirectory,
useEvents: profile.Organizations[i].UseEvents,
use2fa: profile.Organizations[i].Use2fa,
useTotp: profile.Organizations[i].UseTotp
};
}
@@ -187,6 +188,7 @@ angular
useGroups: org.UseGroups,
useDirectory: org.UseDirectory,
useEvents: org.UseEvents,
use2fa: org.Use2fa,
useTotp: org.UseTotp
};
profile.organizations[o.id] = o;

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

@@ -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++) {
@@ -362,16 +395,18 @@
var valueType = value.type ? value.type.toLowerCase() : null;
switch (valueType) {
case 'login': case null: case undefined:
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
};
@@ -490,16 +525,18 @@
var valueType = value.type ? value.type.toLowerCase() : null;
switch (valueType) {
case 'login': case null: case undefined:
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
};
@@ -686,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
};
@@ -879,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');
@@ -980,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
},
@@ -996,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) {
@@ -1082,7 +1119,7 @@
name: null,
type: constants.cipherType.login,
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -1101,7 +1138,7 @@
switch (key) {
case 'URL':
cipher.login.uri = fixUri(value);
cipher.login.uris = makeUriArray(value);
break;
case 'UserName':
cipher.login.username = value;
@@ -1203,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
}
@@ -1316,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
@@ -1371,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
}
@@ -1389,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' &&
@@ -1443,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
}
@@ -1497,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,
}
@@ -1533,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
}
@@ -1580,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
},
@@ -1688,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
},
@@ -1788,7 +1817,7 @@
notes: note && note !== '' ? note : null,
fields: null,
login: {
uri: null,
uris: null,
password: null,
username: null,
totp: null
@@ -1805,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;
@@ -1907,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
}
@@ -1969,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) {
@@ -1993,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];
@@ -2010,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] === '') {
@@ -2022,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 {
@@ -2049,9 +2078,6 @@
if (cipher.notes === '') {
cipher.notes = null;
}
if (cipher.login.uri === '') {
cipher.login.uri = null;
}
ciphers.push(cipher);
}
@@ -2144,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
}
@@ -2210,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;
@@ -2295,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
},
@@ -2367,7 +2393,7 @@
notes: '',
name: entry.label && entry.label !== '' ? entry.label.split(' ')[0] : '--',
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -2395,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)) {
@@ -2458,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
}
@@ -2504,7 +2530,7 @@
notes: '',
name: outterTable.find('span.caption').text(),
login: {
uri: null,
uris: null,
username: null,
password: null
},
@@ -2513,7 +2539,7 @@
var url = outterTable.find('.subcaption').text();
if (url && url !== '') {
cipher.login.uri = fixUri(url);
cipher.login.uris = makeUriArray(url);
}
var fields = [];
@@ -2597,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
}
@@ -2631,7 +2657,7 @@
favorite: false,
notes: note && note !== '' ? note : null,
login: {
uri: null,
uris: null,
password: null,
username: null
},
@@ -2648,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;
@@ -2706,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
},
@@ -2850,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
},
@@ -2950,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);
@@ -3015,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
}
@@ -3072,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
}
@@ -3146,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.1","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.26.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

@@ -3,7 +3,7 @@
.controller('settingsTwoStepController', function ($scope, apiService, toastr, $analytics, constants,
$filter, $uibModal, authService) {
$scope.providers = constants.twoFactorProviderInfo;
$scope.providers = $filter('filter')(constants.twoFactorProviderInfo, { organization: false });
$scope.premium = true;
authService.getUserProfile().then(function (profile) {
@@ -60,7 +60,8 @@
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
controller: 'settingsTwoStep' + typeName + 'Controller',
resolve: {
enabled: function () { return provider.enabled; }
enabled: function () { return provider.enabled; },
orgId: function () { return null; }
}
});

View File

@@ -2,7 +2,7 @@
.module('bit.settings')
.controller('settingsTwoStepDuoController', function ($scope, apiService, $uibModalInstance, cryptoService,
toastr, $analytics, constants, $timeout) {
toastr, $analytics, constants, $timeout, orgId) {
$analytics.eventTrack('settingsTwoStepDuoController', { category: 'Modal' });
var _masterPasswordHash;
@@ -20,9 +20,16 @@
$scope.auth = function (model) {
$scope.authPromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
_masterPasswordHash = hash;
return apiService.twoFactor.getDuo({}, {
var requestModel = {
masterPasswordHash: _masterPasswordHash
}).$promise;
};
if (orgId) {
return apiService.twoFactor.getOrganizationDuo({ orgId: orgId }, requestModel).$promise;
}
else {
return apiService.twoFactor.getDuo({}, requestModel).$promise;
}
}).then(function (apiResponse) {
processResult(apiResponse);
$scope.authed = true;
@@ -43,27 +50,52 @@
return;
}
$scope.submitPromise = apiService.twoFactor.disable({}, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.duo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
if (orgId) {
$scope.submitPromise = apiService.twoFactor.disableOrganization({ orgId: orgId }, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.organizationDuo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Organization Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
else {
$scope.submitPromise = apiService.twoFactor.disable({}, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.duo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
}
function update(model) {
$scope.submitPromise = apiService.twoFactor.putDuo({}, {
var requestModel = {
integrationKey: model.ikey,
secretKey: model.skey,
host: model.host,
masterPasswordHash: _masterPasswordHash
}, function (response) {
$analytics.eventTrack('Enabled Two-step Duo');
processResult(response);
}).$promise;
};
if (orgId) {
$scope.submitPromise = apiService.twoFactor.putOrganizationDuo({ orgId: orgId }, requestModel,
function (response) {
$analytics.eventTrack('Enabled Two-step Organization Duo');
processResult(response);
}).$promise;
}
else {
$scope.submitPromise = apiService.twoFactor.putDuo({}, requestModel,
function (response) {
$analytics.eventTrack('Enabled Two-step Duo');
processResult(response);
}).$promise;
}
}
function processResult(response) {
@@ -80,7 +112,7 @@
closing = true;
$uibModalInstance.close($scope.enabled);
};
$scope.$on('modal.closing', function (e, reason, closed) {
if (closing) {
return;

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

@@ -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"

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>

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: {
@@ -29,6 +36,11 @@
$scope.savePromise = null;
$scope.save = function () {
if ($scope.cipher.type === constants.cipherType.login && $scope.cipher.login.uris.length === 1 &&
($scope.cipher.login.uris[0].uri == null || $scope.cipher.login.uris[0].uri === '')) {
$scope.cipher.login.uris = null;
}
var cipher = cipherService.encryptCipher($scope.cipher);
$scope.savePromise = apiService.ciphers.post(cipher, function (cipherResponse) {
$analytics.eventTrack('Created Cipher');
@@ -44,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 = [];

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,
@@ -225,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; }
}
});
@@ -304,6 +243,10 @@
};
$scope.editFolder = function (folder) {
if (!folder.id) {
return;
}
var editModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultEditFolder.html',
@@ -332,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,
@@ -372,8 +309,9 @@
}
});
modal.result.then(function (orgId) {
cipher.organizationId = orgId;
modal.result.then(function (returned) {
cipher.organizationId = returned.orgId;
cipher.collectionIds = returned.collectionIds || [];
});
};
@@ -397,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);
};
};
@@ -450,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>
@@ -120,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">
@@ -505,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>
@@ -112,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">
@@ -552,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

@@ -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>
@@ -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>

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"
}