mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a132ec4fd7 | ||
|
|
8291fa0ce1 | ||
|
|
37364ecd7e | ||
|
|
48d9e626f5 | ||
|
|
f0fbf664d4 | ||
|
|
7b8b4dc164 | ||
|
|
21635dd728 | ||
|
|
c7802940b1 | ||
|
|
f7b60febe9 | ||
|
|
6c93a63c06 | ||
|
|
c44a638644 | ||
|
|
0d3fead0f3 | ||
|
|
5ba4b37610 | ||
|
|
44a2d071ae | ||
|
|
3b22764368 | ||
|
|
11336da6df | ||
|
|
a0e5591f8e | ||
|
|
e952073c3c | ||
|
|
9bdd0d116a | ||
|
|
05c8a39e6d | ||
|
|
8fa6ff48cf | ||
|
|
7a31783ea4 | ||
|
|
96585b183d | ||
|
|
f81e7b02dc | ||
|
|
f7fbdf2081 | ||
|
|
06a877c755 | ||
|
|
30abd52189 | ||
|
|
6af0e62976 | ||
|
|
84a36a18d6 | ||
|
|
595cf6c375 | ||
|
|
4262e2cc1d | ||
|
|
c134986bbf | ||
|
|
d9981e1d71 | ||
|
|
2b6d7ec361 | ||
|
|
aaa91e50b7 | ||
|
|
ff9030e7af | ||
|
|
cc39e6402e | ||
|
|
c89b641b88 | ||
|
|
465304b004 | ||
|
|
63033ca12d | ||
|
|
f019dc6575 | ||
|
|
d15e3a64e7 | ||
|
|
7099b0579a | ||
|
|
2c2d08c7cc | ||
|
|
671e9ccb1c | ||
|
|
f93c5cb9a1 | ||
|
|
8c7f1c4359 | ||
|
|
d7c1c6efa1 | ||
|
|
30a2301697 | ||
|
|
c639186c60 | ||
|
|
5618cfb031 | ||
|
|
7e97c04d1e | ||
|
|
4d25077108 | ||
|
|
635caa9ad0 | ||
|
|
2772bffd09 | ||
|
|
995fc96a5d | ||
|
|
4660ad824d | ||
|
|
801049cbd0 | ||
|
|
09a7b4ea90 | ||
|
|
226c201925 | ||
|
|
4749a3da89 | ||
|
|
ae567ab462 | ||
|
|
bf382889d3 | ||
|
|
2272bcac71 | ||
|
|
a209c9450a | ||
|
|
2539a9c23f | ||
|
|
e95ede73ba | ||
|
|
ad970b1cb7 | ||
|
|
161e7d1763 | ||
|
|
3a823d32b5 | ||
|
|
4c46317f24 | ||
|
|
0271c223a6 | ||
|
|
9a4669067d | ||
|
|
53f3124345 | ||
|
|
b49a40b077 | ||
|
|
fb10da8ce3 | ||
|
|
b286c1a29b | ||
|
|
e5e7712716 | ||
|
|
2beb22e8cf | ||
|
|
747b5608e8 | ||
|
|
dad3cd9414 | ||
|
|
0c1fb3e118 | ||
|
|
afe223f410 | ||
|
|
e1ec50bcad | ||
|
|
04da844b22 | ||
|
|
f944910975 | ||
|
|
96b8467859 | ||
|
|
84554174ac |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!dist/*
|
||||
!entrypoint.sh
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -199,5 +199,5 @@ FakesAssemblies/
|
||||
*.opt
|
||||
|
||||
# Other
|
||||
project.lock.json
|
||||
package-lock.json
|
||||
src/js/*.min.js
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM bitwarden/server
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./dist .
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
13
build.ps1
Normal file
13
build.ps1
Normal file
@@ -0,0 +1,13 @@
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
echo "`n# Building Web"
|
||||
|
||||
echo "`nBuilding app"
|
||||
echo "npm version $(npm --version)"
|
||||
echo "gulp version $(gulp --version)"
|
||||
npm install
|
||||
gulp dist:selfHosted
|
||||
|
||||
echo "`nBuilding docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web $dir\.
|
||||
39
build.sh
Normal file
39
build.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 0 -a "$1" == "push" ]
|
||||
then
|
||||
echo "# Pushing Web"
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 1 ]
|
||||
then
|
||||
TAG=$2
|
||||
docker push bitwarden/web:$TAG
|
||||
else
|
||||
docker push bitwarden/web
|
||||
fi
|
||||
elif [ $# -gt 1 -a "$1" == "tag" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "Tagging Web as '$TAG'"
|
||||
docker tag bitwarden/web bitwarden/web:$TAG
|
||||
else
|
||||
echo "# Building Web"
|
||||
|
||||
echo ""
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
echo "gulp version $(gulp --version)"
|
||||
npm install
|
||||
gulp dist:selfHosted
|
||||
|
||||
echo ""
|
||||
echo "Building docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web $DIR/.
|
||||
fi
|
||||
5
entrypoint.sh
Normal file
5
entrypoint.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
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
|
||||
25
gulpfile.js
25
gulpfile.js
@@ -71,8 +71,10 @@ gulp.task('min:js', ['clean:js'], function () {
|
||||
'!' + paths.minJs,
|
||||
'!' + paths.jsDir + 'fallback*.js',
|
||||
'!' + paths.jsDir + 'u2f-connector.js',
|
||||
'!' + paths.jsDir + 'duo.js'
|
||||
'!' + paths.jsDir + 'duo.js',
|
||||
'!' + paths.jsDir + 'settings.js'
|
||||
], { base: '.' })
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(concat(paths.concatJsDest))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
@@ -240,6 +242,7 @@ function config() {
|
||||
createModule: false,
|
||||
constants: _.merge({}, {
|
||||
appSettings: {
|
||||
selfHosted: false,
|
||||
version: project.version,
|
||||
environment: project.env
|
||||
}
|
||||
@@ -288,7 +291,7 @@ gulp.task('browserify:cc', function () {
|
||||
});
|
||||
|
||||
gulp.task('dist:clean', function (cb) {
|
||||
return rimraf(paths.dist, cb);
|
||||
return rimraf(paths.dist + '**/*', cb);
|
||||
});
|
||||
|
||||
gulp.task('dist:move', function () {
|
||||
@@ -332,6 +335,10 @@ gulp.task('dist:move', function () {
|
||||
src: paths.jsDir + 'duo.js',
|
||||
dest: paths.dist + 'js'
|
||||
},
|
||||
{
|
||||
src: paths.jsDir + 'settings.js',
|
||||
dest: paths.dist + 'js'
|
||||
},
|
||||
{
|
||||
src: paths.jsDir + 'bw.min.js',
|
||||
dest: paths.dist + 'js'
|
||||
@@ -363,7 +370,7 @@ gulp.task('dist:css', function () {
|
||||
paths.cssDir + '**/*.css',
|
||||
'!' + paths.cssDir + '**/*.min.css'
|
||||
])
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(cssmin())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(gulp.dest(paths.dist + 'css'));
|
||||
@@ -379,7 +386,7 @@ gulp.task('dist:js:app', function () {
|
||||
]);
|
||||
|
||||
merge(mainStream, config())
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||
.pipe(ngAnnotate())
|
||||
.pipe(uglify())
|
||||
@@ -393,7 +400,7 @@ gulp.task('dist:js:fallback', function () {
|
||||
]);
|
||||
|
||||
merge(mainStream)
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(uglify())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(gulp.dest(paths.dist + 'js'));
|
||||
@@ -431,7 +438,7 @@ gulp.task('dist:preprocess', function () {
|
||||
.src([
|
||||
paths.dist + '/**/*.html'
|
||||
], { base: '.' })
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
@@ -443,6 +450,12 @@ gulp.task('dist', ['build'], function (cb) {
|
||||
cb);
|
||||
});
|
||||
|
||||
var selfHosted = false;
|
||||
gulp.task('dist:selfHosted', function (cb) {
|
||||
selfHosted = true;
|
||||
return runSequence('dist', cb);
|
||||
});
|
||||
|
||||
gulp.task('deploy', ['dist'], function () {
|
||||
return gulp.src(paths.dist + '**/*')
|
||||
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
|
||||
|
||||
34
package.json
34
package.json
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "bitwarden",
|
||||
"version": "1.14.0",
|
||||
"version": "1.17.3",
|
||||
"env": "Production",
|
||||
"devDependencies": {
|
||||
"connect": "3.6.0",
|
||||
"connect": "3.6.3",
|
||||
"lodash": "4.17.4",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-concat": "2.6.1",
|
||||
"gulp-cssmin": "0.1.7",
|
||||
"gulp-less": "3.3.0",
|
||||
"gulp-cssmin": "0.2.0",
|
||||
"gulp-less": "3.3.2",
|
||||
"gulp-rename": "1.2.2",
|
||||
"gulp-uglify": "2.1.2",
|
||||
"gulp-uglify": "3.0.0",
|
||||
"gulp-gh-pages": "0.5.4",
|
||||
"gulp-preprocess": "2.0.0",
|
||||
"gulp-ng-annotate": "2.0.0",
|
||||
"gulp-ng-config": "1.4.0",
|
||||
"gulp-connect": "5.0.0",
|
||||
"jshint": "2.9.4",
|
||||
"jshint": "2.9.5",
|
||||
"gulp-jshint": "2.0.4",
|
||||
"rimraf": "2.6.1",
|
||||
"run-sequence": "1.2.2",
|
||||
"run-sequence": "2.1.0",
|
||||
"merge-stream": "1.0.1",
|
||||
"jquery": "2.2.4",
|
||||
"font-awesome": "4.7.0",
|
||||
"bootstrap": "3.3.7",
|
||||
"angular": "1.6.3",
|
||||
"angular-resource": "1.6.3",
|
||||
"angular-sanitize": "1.6.3",
|
||||
"angular": "1.6.6",
|
||||
"angular-resource": "1.6.6",
|
||||
"angular-sanitize": "1.6.6",
|
||||
"angular-ui-bootstrap": "2.5.0",
|
||||
"angular-ui-router": "0.4.2",
|
||||
"angular-jwt": "0.1.9",
|
||||
"angular-cookies": "1.6.3",
|
||||
"angular-cookies": "1.6.6",
|
||||
"admin-lte": "2.3.11",
|
||||
"angular-toastr": "2.1.1",
|
||||
"angular-bootstrap-show-errors": "2.3.0",
|
||||
"angular-messages": "1.6.3",
|
||||
"angular-messages": "1.6.6",
|
||||
"ngstorage": "0.3.11",
|
||||
"papaparse": "4.2.0",
|
||||
"clipboard": "1.6.1",
|
||||
"papaparse": "4.3.5",
|
||||
"clipboard": "1.7.1",
|
||||
"ngclipboard": "1.1.1",
|
||||
"angulartics": "1.4.0",
|
||||
"angulartics-google-analytics": "0.4.0",
|
||||
"node-forge": "0.7.1",
|
||||
"webpack-stream": "3.2.0",
|
||||
"angular-stripe": "4.2.12",
|
||||
"webpack-stream": "4.0.0",
|
||||
"angular-stripe": "5.0.0",
|
||||
"angular-credit-cards": "3.1.6",
|
||||
"browserify": "14.1.0",
|
||||
"browserify": "14.4.0",
|
||||
"vinyl-source-stream": "1.1.0",
|
||||
"gulp-derequire": "2.1.0",
|
||||
"exposify": "0.5.0",
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"appSettings": {
|
||||
"apiUri": "https://preview-api.bitwarden.com",
|
||||
"identityUri": "https://preview-identity.bitwarden.com",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD"
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
"preview-api.bitwarden.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"appSettings": {
|
||||
"apiUri": "https://api.bitwarden.com",
|
||||
"identityUri": "https://identity.bitwarden.com",
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk"
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||
"whitelistDomains": [
|
||||
"api.bitwarden.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"appSettings": {
|
||||
"apiUri": "http://localhost:4000",
|
||||
"identityUri": "http://localhost:33656",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD"
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,18 +117,18 @@ angular
|
||||
}
|
||||
|
||||
var keys = Object.keys(twoFactorProviders);
|
||||
var cleanedProviders = [];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var provider = $filter('filter')(constants.twoFactorProviderInfo, {
|
||||
type: keys[i],
|
||||
active: true,
|
||||
requiresUsb: false
|
||||
});
|
||||
if (provider.length) {
|
||||
cleanedProviders.push(twoFactorProviders[keys[i]]);
|
||||
if (!provider.length) {
|
||||
delete twoFactorProviders[keys[i]];
|
||||
}
|
||||
}
|
||||
return cleanedProviders;
|
||||
|
||||
return twoFactorProviders;
|
||||
}
|
||||
|
||||
// ref: https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
|
||||
|
||||
13
src/app/accounts/accountsRecoverDeleteController.js
Normal file
13
src/app/accounts/accountsRecoverDeleteController.js
Normal file
@@ -0,0 +1,13 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsRecoverDeleteController', function ($scope, $rootScope, apiService, $analytics) {
|
||||
$scope.success = false;
|
||||
|
||||
$scope.submit = function (model) {
|
||||
$scope.submitPromise = apiService.accounts.postDeleteRecover({ email: model.email }, function () {
|
||||
$analytics.eventTrack('Started Delete Recovery');
|
||||
$scope.success = true;
|
||||
}).$promise;
|
||||
};
|
||||
});
|
||||
36
src/app/accounts/accountsVerifyRecoverDeleteController.js
Normal file
36
src/app/accounts/accountsVerifyRecoverDeleteController.js
Normal file
@@ -0,0 +1,36 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsVerifyRecoverDeleteController', function ($scope, $state, apiService, toastr, $analytics) {
|
||||
if (!$state.params.userId || !$state.params.token || !$state.params.email) {
|
||||
$state.go('frontend.login.info').then(function () {
|
||||
toastr.error('Invalid parameters.');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.email = $state.params.email;
|
||||
|
||||
$scope.delete = function () {
|
||||
if (!confirm('Are you sure you want to delete this account? This cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.deleting = true;
|
||||
apiService.accounts.postDeleteRecoverToken({},
|
||||
{
|
||||
token: $state.params.token,
|
||||
userId: $state.params.userId
|
||||
}, function () {
|
||||
$analytics.eventTrack('Recovered Delete');
|
||||
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||
toastr.success('Your account has been deleted. You can register a new account again if you like.',
|
||||
'Success');
|
||||
});
|
||||
}, function () {
|
||||
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||
toastr.error('Unable to delete account.', 'Error');
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -24,7 +24,8 @@
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="code" class="sr-only">Code</label>
|
||||
<input type="text" id="code" name="Code" class="form-control" placeholder="Verification code"
|
||||
ng-model="token" required api-field />
|
||||
ng-model="token" required api-field autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||
spellcheck="false" />
|
||||
<span class="fa fa-lock form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -48,7 +49,8 @@
|
||||
<p class="login-box-msg">
|
||||
Complete logging in with YubiKey.
|
||||
</p>
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise">
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
autocomplete="off">
|
||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
@@ -84,7 +86,8 @@
|
||||
<p class="login-box-msg">
|
||||
Complete logging in with Duo.
|
||||
</p>
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise">
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
autocomplete="off">
|
||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
@@ -115,7 +118,7 @@
|
||||
<p class="login-box-msg">
|
||||
Complete logging in with FIDO U2F.
|
||||
</p>
|
||||
<form name="twoFactorForm" api-form="twoFactorPromise">
|
||||
<form name="twoFactorForm" api-form="twoFactorPromise" autocomplete="off">
|
||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
|
||||
39
src/app/accounts/views/accountsRecoverDelete.html
Normal file
39
src/app/accounts/views/accountsRecoverDelete.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<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 & 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.
|
||||
</div>
|
||||
<a ui-sref="frontend.login.info">Return to log in</a>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" ng-show="!success"
|
||||
api-form="submitPromise">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="email" class="sr-only">Your account email address</label>
|
||||
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address"
|
||||
ng-model="model.email" required api-field />
|
||||
<span class="fa fa-envelope form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
<a ui-sref="frontend.login.info">Return to log in</a>
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,6 +72,11 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
By clicking the above "Submit" button, you are agreeing to the
|
||||
<a href="https://bitwarden.com/terms/" target="_blank">Terms of Service</a>
|
||||
and the
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank">Privacy Policy</a>.
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
21
src/app/accounts/views/accountsVerifyRecoverDelete.html
Normal file
21
src/app/accounts/views/accountsVerifyRecoverDelete.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||
</div>
|
||||
<div class="login-box-body">
|
||||
<div ng-if="deleting">
|
||||
Deleting account...
|
||||
</div>
|
||||
<div ng-if="!deleting">
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning fa-fw"></i> Warning</h4>
|
||||
This will permanently delete your account. This cannot be undone.
|
||||
</div>
|
||||
<p>
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,9 +6,11 @@
|
||||
'ui.bootstrap.showErrors',
|
||||
'toastr',
|
||||
'angulartics',
|
||||
// @if !selfHosted
|
||||
'angulartics.google.analytics',
|
||||
'angular-stripe',
|
||||
'credit-cards',
|
||||
// @endif
|
||||
'angular-promise-polyfill',
|
||||
|
||||
'bit.directives',
|
||||
|
||||
@@ -2,13 +2,33 @@ angular
|
||||
.module('bit')
|
||||
|
||||
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, jwtOptionsProvider,
|
||||
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, stripeProvider, appSettings) {
|
||||
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, appSettings
|
||||
// @if !selfHosted
|
||||
, stripeProvider
|
||||
// @endif
|
||||
) {
|
||||
angular.extend(appSettings, window.bitwardenAppSettings);
|
||||
|
||||
$qProvider.errorOnUnhandledRejections(false);
|
||||
$locationProvider.hashPrefix('');
|
||||
jwtOptionsProvider.config({
|
||||
urlParam: 'access_token3',
|
||||
whiteListedDomains: ['api.bitwarden.com', 'preview-api.bitwarden.com', 'localhost', '192.168.1.3']
|
||||
});
|
||||
|
||||
var jwtConfig = {
|
||||
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
|
||||
authHeader: 'Content-Language',
|
||||
whiteListedDomains: appSettings.whitelistDomains
|
||||
};
|
||||
|
||||
// Safari doesn't work with unconventional "Content-Language" header for CORS.
|
||||
// See notes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
|
||||
jwtConfig = {
|
||||
urlParam: 'access_token',
|
||||
whiteListedDomains: appSettings.whitelistDomains
|
||||
};
|
||||
}
|
||||
|
||||
jwtOptionsProvider.config(jwtConfig);
|
||||
var refreshPromise;
|
||||
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
|
||||
if (options.url.indexOf(appSettings.apiUri) !== 0) {
|
||||
@@ -35,7 +55,9 @@ angular
|
||||
return refreshPromise;
|
||||
};
|
||||
|
||||
// @if !selfHosted
|
||||
stripeProvider.setPublishableKey(appSettings.stripeKey);
|
||||
// @endif
|
||||
|
||||
angular.extend(toastrConfig, {
|
||||
closeButton: true,
|
||||
@@ -261,6 +283,24 @@ angular
|
||||
bodyClass: 'login-page'
|
||||
}
|
||||
})
|
||||
.state('frontend.recover-delete', {
|
||||
url: '^/recover-delete',
|
||||
templateUrl: 'app/accounts/views/accountsRecoverDelete.html',
|
||||
controller: 'accountsRecoverDeleteController',
|
||||
data: {
|
||||
pageTitle: 'Delete Account',
|
||||
bodyClass: 'login-page'
|
||||
}
|
||||
})
|
||||
.state('frontend.verify-recover-delete', {
|
||||
url: '^/verify-recover-delete?userId&token&email',
|
||||
templateUrl: 'app/accounts/views/accountsVerifyRecoverDelete.html',
|
||||
controller: 'accountsVerifyRecoverDeleteController',
|
||||
data: {
|
||||
pageTitle: 'Confirm Delete Account',
|
||||
bodyClass: 'login-page'
|
||||
}
|
||||
})
|
||||
.state('frontend.register', {
|
||||
url: '^/register?org&premium',
|
||||
templateUrl: 'app/accounts/views/accountsRegister.html',
|
||||
|
||||
@@ -31,7 +31,12 @@ angular
|
||||
}, function failure(reason) {
|
||||
$timeout(function () {
|
||||
form.$loading = false;
|
||||
validationService.addErrors(form, reason);
|
||||
if (typeof reason === 'string') {
|
||||
validationService.addError(form, null, reason, true);
|
||||
}
|
||||
else {
|
||||
validationService.addErrors(form, reason);
|
||||
}
|
||||
scope.$broadcast('show-errors-check-validity');
|
||||
$('html, body').animate({ scrollTop: 0 }, 200);
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@ angular
|
||||
link: function (scope, element) {
|
||||
var listener = function (event, toState, toParams, fromState, fromParams) {
|
||||
// Default title
|
||||
var title = 'bitwarden Password Manager';
|
||||
var title = 'bitwarden Web Vault';
|
||||
if (toState.data && toState.data.pageTitle) {
|
||||
title = toState.data.pageTitle + ' - bitwarden Password Manager';
|
||||
title = toState.data.pageTitle + ' - ' + title;
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
|
||||
@@ -4,11 +4,13 @@ angular
|
||||
.controller('mainController', function ($scope, $state, authService, appSettings, toastr, $window, $document,
|
||||
cryptoService, $uibModal, apiService) {
|
||||
var vm = this;
|
||||
vm.skinClass = appSettings.selfHosted ? 'skin-blue-light' : 'skin-blue';
|
||||
vm.bodyClass = '';
|
||||
vm.usingControlSidebar = vm.openControlSidebar = false;
|
||||
vm.searchVaultText = null;
|
||||
vm.version = appSettings.version;
|
||||
vm.outdatedBrowser = navigator.userAgent.indexOf('MSIE') !== -1;
|
||||
vm.outdatedBrowser = $window.navigator.userAgent.indexOf('MSIE') !== -1 ||
|
||||
$window.navigator.userAgent.indexOf('SamsungBrowser') !== -1;
|
||||
|
||||
$scope.currentYear = new Date().getFullYear();
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
angular
|
||||
.module('bit.global')
|
||||
|
||||
.controller('sideNavController', function ($scope, $state, authService, toastr, $analytics, constants) {
|
||||
.controller('sideNavController', function ($scope, $state, authService, toastr, $analytics, constants, appSettings) {
|
||||
$scope.$state = $state;
|
||||
$scope.params = $state.params;
|
||||
$scope.orgs = [];
|
||||
$scope.name = '';
|
||||
|
||||
if(appSettings.selfHosted) {
|
||||
$scope.orgIconBgColor = '#ffffff';
|
||||
$scope.orgIconBorder = '3px solid #a0a0a0';
|
||||
$scope.orgIconTextColor = '#333333';
|
||||
}
|
||||
else {
|
||||
$scope.orgIconBgColor = '#2c3b41';
|
||||
$scope.orgIconBorder = '3px solid #1a2226';
|
||||
$scope.orgIconTextColor = '#ffffff';
|
||||
}
|
||||
|
||||
authService.getUserProfile().then(function (userProfile) {
|
||||
$scope.name = userProfile.extended && userProfile.extended.name ?
|
||||
userProfile.extended.name : userProfile.email;
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService, stripe,
|
||||
$analytics, toastr, existingPaymentMethod) {
|
||||
.controller('organizationBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, existingPaymentMethod
|
||||
// @if !selfHosted
|
||||
, stripe
|
||||
// @endif
|
||||
) {
|
||||
$analytics.eventTrack('organizationBillingChangePaymentController', { category: 'Modal' });
|
||||
$scope.existingPaymentMethod = existingPaymentMethod;
|
||||
$scope.paymentMethod = 'card';
|
||||
$scope.showPaymentOptions = true;
|
||||
$scope.hidePaypal = true;
|
||||
$scope.card = {};
|
||||
$scope.bank = {};
|
||||
|
||||
$scope.changePaymentMethod = function (val) {
|
||||
$scope.paymentMethod = val;
|
||||
};
|
||||
|
||||
$scope.submit = function () {
|
||||
$scope.submitPromise = stripe.card.createToken($scope.card).then(function (response) {
|
||||
var stripeReq = null;
|
||||
if ($scope.paymentMethod === 'card') {
|
||||
stripeReq = stripe.card.createToken($scope.card);
|
||||
}
|
||||
else if ($scope.paymentMethod === 'bank') {
|
||||
$scope.bank.currency = 'USD';
|
||||
$scope.bank.country = 'US';
|
||||
stripeReq = stripe.bankAccount.createToken($scope.bank);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.submitPromise = stripeReq.then(function (response) {
|
||||
var request = {
|
||||
paymentToken: response.id
|
||||
};
|
||||
|
||||
return apiService.organizations.putPayment({ id: $state.params.orgId }, request).$promise;
|
||||
}, function (err) {
|
||||
throw err.message;
|
||||
}).then(function (response) {
|
||||
$scope.card = null;
|
||||
if (existingPaymentMethod) {
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationBillingController', function ($scope, apiService, $state, $uibModal, toastr, $analytics) {
|
||||
.controller('organizationBillingController', function ($scope, apiService, $state, $uibModal, toastr, $analytics,
|
||||
appSettings) {
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
$scope.charges = [];
|
||||
$scope.paymentSource = null;
|
||||
$scope.plan = null;
|
||||
$scope.subscription = null;
|
||||
$scope.loading = true;
|
||||
var license = null;
|
||||
$scope.expiration = null;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
load();
|
||||
});
|
||||
|
||||
$scope.changePayment = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingChangePayment.html',
|
||||
@@ -30,6 +38,10 @@
|
||||
};
|
||||
|
||||
$scope.changePlan = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/organization/views/organizationBillingChangePlan.html',
|
||||
@@ -47,6 +59,10 @@
|
||||
};
|
||||
|
||||
$scope.adjustSeats = function (add) {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/organization/views/organizationBillingAdjustSeats.html',
|
||||
@@ -64,6 +80,10 @@
|
||||
};
|
||||
|
||||
$scope.adjustStorage = function (add) {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingAdjustStorage.html',
|
||||
@@ -80,7 +100,27 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.verifyBank = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/organization/views/organizationBillingVerifyBank.html',
|
||||
controller: 'organizationBillingVerifyBankController'
|
||||
});
|
||||
|
||||
modal.result.then(function () {
|
||||
load();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to cancel? All users will lose access to the organization ' +
|
||||
'at the end of this billing cycle.')) {
|
||||
return;
|
||||
@@ -95,6 +135,10 @@
|
||||
};
|
||||
|
||||
$scope.reinstate = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to remove the cancellation request and reinstate this organization?')) {
|
||||
return;
|
||||
}
|
||||
@@ -107,12 +151,71 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateLicense = function () {
|
||||
if (!$scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingUpdateLicense.html',
|
||||
controller: 'organizationBillingUpdateLicenseController'
|
||||
});
|
||||
|
||||
modal.result.then(function () {
|
||||
load();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.license = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var installationId = prompt("Enter your installation id");
|
||||
if (!installationId || installationId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.organizations.getLicense({
|
||||
id: $state.params.orgId,
|
||||
installationId: installationId
|
||||
}, function (license) {
|
||||
var licenseString = JSON.stringify(license, null, 2);
|
||||
var licenseBlob = new Blob([licenseString]);
|
||||
|
||||
// IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(licenseBlob, 'bitwarden_organization_license.json');
|
||||
}
|
||||
else {
|
||||
var a = window.document.createElement('a');
|
||||
a.href = window.URL.createObjectURL(licenseBlob, { type: 'text/plain' });
|
||||
a.download = 'bitwarden_organization_license.json';
|
||||
document.body.appendChild(a);
|
||||
// IE: "Access is denied".
|
||||
// ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
}, function (err) {
|
||||
if (err.status === 400) {
|
||||
toastr.error("Invalid installation id.");
|
||||
}
|
||||
else {
|
||||
toastr.error("Unable to generate license.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function load() {
|
||||
apiService.organizations.getBilling({ id: $state.params.orgId }, function (org) {
|
||||
$scope.loading = false;
|
||||
$scope.noSubscription = org.PlanType === 0;
|
||||
|
||||
var i = 0;
|
||||
$scope.expiration = org.Expiration;
|
||||
license = org.License;
|
||||
|
||||
$scope.plan = {
|
||||
name: org.Plan,
|
||||
@@ -137,9 +240,8 @@
|
||||
trialEndDate: org.Subscription.TrialEndDate,
|
||||
cancelledDate: org.Subscription.CancelledDate,
|
||||
status: org.Subscription.Status,
|
||||
cancelled: org.Subscription.Status === 'cancelled',
|
||||
markedForCancel: (org.Subscription.Status === 'active' || org.Subscription.Status === 'trialing') &&
|
||||
org.Subscription.CancelledDate
|
||||
cancelled: org.Subscription.Cancelled,
|
||||
markedForCancel: !org.Subscription.Cancelled && org.Subscription.CancelAtEndDate
|
||||
};
|
||||
}
|
||||
|
||||
@@ -168,7 +270,8 @@
|
||||
$scope.paymentSource = {
|
||||
type: org.PaymentSource.Type,
|
||||
description: org.PaymentSource.Description,
|
||||
cardBrand: org.PaymentSource.CardBrand
|
||||
cardBrand: org.PaymentSource.CardBrand,
|
||||
needsVerification: org.PaymentSource.NeedsVerification
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationBillingUpdateLicenseController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, validationService) {
|
||||
$analytics.eventTrack('organizationBillingUpdateLicenseController', { category: 'Modal' });
|
||||
|
||||
$scope.submit = function (form) {
|
||||
var fileEl = document.getElementById('file');
|
||||
var files = fileEl.files;
|
||||
if (!files || !files.length) {
|
||||
validationService.addError(form, 'file', 'Select a license file.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('license', files[0]);
|
||||
|
||||
$scope.submitPromise = apiService.organizations.putLicense({ id: $state.params.orgId }, fd)
|
||||
.$promise.then(function (response) {
|
||||
$analytics.eventTrack('Updated License');
|
||||
toastr.success('You have updated your license.');
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationBillingVerifyBankController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr) {
|
||||
$analytics.eventTrack('organizationBillingVerifyBankController', { category: 'Modal' });
|
||||
|
||||
$scope.submit = function () {
|
||||
var request = {
|
||||
amount1: $scope.amount1,
|
||||
amount2: $scope.amount2
|
||||
};
|
||||
|
||||
$scope.submitPromise = apiService.organizations.postVerifyBank({ id: $state.params.orgId }, request)
|
||||
.$promise.then(function (response) {
|
||||
$analytics.eventTrack('Verified Bank Account');
|
||||
toastr.success('You have successfully verified your bank account.');
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -2,7 +2,8 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationSettingsController', function ($scope, $state, apiService, toastr, authService, $uibModal,
|
||||
$analytics) {
|
||||
$analytics, appSettings) {
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
$scope.model = {};
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
apiService.organizations.get({ id: $state.params.orgId }, function (org) {
|
||||
@@ -15,6 +16,10 @@
|
||||
});
|
||||
|
||||
$scope.generalSave = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.generalPromise = apiService.organizations.put({ id: $state.params.orgId }, $scope.model, function (org) {
|
||||
authService.updateProfileOrganization(org).then(function (updatedOrg) {
|
||||
$analytics.eventTrack('Updated Organization Settings');
|
||||
@@ -23,6 +28,22 @@
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.import = function () {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/tools/views/toolsImport.html',
|
||||
controller: 'organizationSettingsImportController'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.export = function () {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/tools/views/toolsExport.html',
|
||||
controller: 'organizationSettingsExportController'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.delete = function () {
|
||||
$uibModal.open({
|
||||
animation: true,
|
||||
|
||||
129
src/app/organization/organizationSettingsExportController.js
Normal file
129
src/app/organization/organizationSettingsExportController.js
Normal file
@@ -0,0 +1,129 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationSettingsExportController', function ($scope, apiService, $uibModalInstance, cipherService,
|
||||
$q, toastr, $analytics, $state) {
|
||||
$analytics.eventTrack('organizationSettingsExportController', { category: 'Modal' });
|
||||
$scope.export = function (model) {
|
||||
$scope.startedExport = true;
|
||||
var decLogins = [],
|
||||
decCollections = [];
|
||||
|
||||
var collectionsPromise = apiService.collections.listOrganization({ orgId: $state.params.orgId },
|
||||
function (collections) {
|
||||
decCollections = cipherService.decryptCollections(collections.Data, $state.params.orgId, true);
|
||||
}).$promise;
|
||||
|
||||
var loginsPromise = apiService.ciphers.listOrganizationDetails({ organizationId: $state.params.orgId },
|
||||
function (ciphers) {
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
if (ciphers.Data[i].Type === 1) {
|
||||
var decLogin = cipherService.decryptLogin(ciphers.Data[i]);
|
||||
decLogins.push(decLogin);
|
||||
}
|
||||
}
|
||||
}).$promise;
|
||||
|
||||
$q.all([collectionsPromise, loginsPromise]).then(function () {
|
||||
if (!decLogins.length) {
|
||||
toastr.error('Nothing to export.', 'Error!');
|
||||
$scope.close();
|
||||
return;
|
||||
}
|
||||
|
||||
var collectionsDict = {};
|
||||
for (var i = 0; i < decCollections.length; i++) {
|
||||
collectionsDict[decCollections[i].id] = decCollections[i];
|
||||
}
|
||||
|
||||
try {
|
||||
var exportLogins = [];
|
||||
for (i = 0; i < decLogins.length; i++) {
|
||||
var login = {
|
||||
name: decLogins[i].name,
|
||||
uri: decLogins[i].uri,
|
||||
username: decLogins[i].username,
|
||||
password: decLogins[i].password,
|
||||
notes: decLogins[i].notes,
|
||||
totp: decLogins[i].totp,
|
||||
collections: [],
|
||||
fields: null
|
||||
};
|
||||
|
||||
var j;
|
||||
if (decLogins[i].fields) {
|
||||
for (j = 0; j < decLogins[i].fields.length; j++) {
|
||||
if (!login.fields) {
|
||||
login.fields = '';
|
||||
}
|
||||
else {
|
||||
login.fields += '\n';
|
||||
}
|
||||
|
||||
login.fields += ((decLogins[i].fields[j].name || '') + ': ' + decLogins[i].fields[j].value);
|
||||
}
|
||||
}
|
||||
|
||||
if (decLogins[i].collectionIds) {
|
||||
for (j = 0; j < decLogins[i].collectionIds.length; j++) {
|
||||
if (collectionsDict.hasOwnProperty(decLogins[i].collectionIds[j])) {
|
||||
login.collections.push(collectionsDict[decLogins[i].collectionIds[j]].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exportLogins.push(login);
|
||||
}
|
||||
|
||||
var csvString = Papa.unparse(exportLogins);
|
||||
var csvBlob = new Blob([csvString]);
|
||||
|
||||
// IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(csvBlob, makeFileName());
|
||||
}
|
||||
else {
|
||||
var a = window.document.createElement('a');
|
||||
a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' });
|
||||
a.download = makeFileName();
|
||||
document.body.appendChild(a);
|
||||
// IE: "Access is denied".
|
||||
// ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Exported Organization Data');
|
||||
toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!');
|
||||
$scope.close();
|
||||
}
|
||||
catch (err) {
|
||||
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||
$scope.close();
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||
$scope.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
function makeFileName() {
|
||||
var now = new Date();
|
||||
var dateString =
|
||||
now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) +
|
||||
padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) +
|
||||
padNumber(now.getSeconds(), 2);
|
||||
|
||||
return 'bitwarden_org_export_' + dateString + '.csv';
|
||||
}
|
||||
|
||||
function padNumber(number, width, paddingCharacter) {
|
||||
paddingCharacter = paddingCharacter || '0';
|
||||
number = number + '';
|
||||
return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number;
|
||||
}
|
||||
});
|
||||
128
src/app/organization/organizationSettingsImportController.js
Normal file
128
src/app/organization/organizationSettingsImportController.js
Normal file
@@ -0,0 +1,128 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationSettingsImportController', function ($scope, $state, apiService, $uibModalInstance, cipherService,
|
||||
toastr, importService, $analytics, $sce, validationService, cryptoService) {
|
||||
$analytics.eventTrack('organizationSettingsImportController', { category: 'Modal' });
|
||||
$scope.model = { source: '' };
|
||||
$scope.source = {};
|
||||
$scope.splitFeatured = false;
|
||||
|
||||
$scope.options = [
|
||||
{
|
||||
id: 'bitwardencsv',
|
||||
name: 'bitwarden (csv)',
|
||||
featured: true,
|
||||
sort: 1,
|
||||
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
||||
'Log into the web vault and navigate to your organization\'s admin area. Then to go ' +
|
||||
'"Settings" > "Tools" > "Export".')
|
||||
},
|
||||
{
|
||||
id: 'lastpass',
|
||||
name: 'LastPass (csv)',
|
||||
featured: true,
|
||||
sort: 2,
|
||||
instructions: $sce.trustAsHtml('See detailed instructions on our help site at ' +
|
||||
'<a target="_blank" href="https://help.bitwarden.com/article/import-from-lastpass/">' +
|
||||
'https://help.bitwarden.com/article/import-from-lastpass/</a>')
|
||||
}
|
||||
];
|
||||
|
||||
$scope.setSource = function () {
|
||||
for (var i = 0; i < $scope.options.length; i++) {
|
||||
if ($scope.options[i].id === $scope.model.source) {
|
||||
$scope.source = $scope.options[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
$scope.setSource();
|
||||
|
||||
$scope.import = function (model, form) {
|
||||
if (!model.source || model.source === '') {
|
||||
validationService.addError(form, 'source', 'Select the format of the import file.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var file = document.getElementById('file').files[0];
|
||||
if (!file && (!model.fileContents || model.fileContents === '')) {
|
||||
validationService.addError(form, 'file', 'Select the import file or copy/paste the import file contents.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.processing = true;
|
||||
importService.importOrg(model.source, file || model.fileContents, importSuccess, importError);
|
||||
};
|
||||
|
||||
function importSuccess(collections, logins, collectionRelationships) {
|
||||
if (!collections.length && !logins.length) {
|
||||
importError('Nothing was imported.');
|
||||
return;
|
||||
}
|
||||
else if (logins.length) {
|
||||
var halfway = Math.floor(logins.length / 2);
|
||||
var last = logins.length - 1;
|
||||
if (loginIsBadData(logins[0]) && loginIsBadData(logins[halfway]) && loginIsBadData(logins[last])) {
|
||||
importError('CSV data is not formatted correctly. Please check your import file and try again.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
apiService.ciphers.importOrg({ orgId: $state.params.orgId }, {
|
||||
collections: cipherService.encryptCollections(collections, $state.params.orgId),
|
||||
ciphers: cipherService.encryptLogins(logins, cryptoService.getOrgKey($state.params.orgId)),
|
||||
collectionRelationships: collectionRelationships
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
$state.go('backend.org.vault', { orgId: $state.params.orgId }).then(function () {
|
||||
$analytics.eventTrack('Imported Org Data', { label: $scope.model.source });
|
||||
toastr.success('Data has been successfully imported into your vault.', 'Import Success');
|
||||
});
|
||||
}, importError);
|
||||
}
|
||||
|
||||
function loginIsBadData(login) {
|
||||
return (login.name === null || login.name === '--') && (login.password === null || login.password === '');
|
||||
}
|
||||
|
||||
function importError(error) {
|
||||
$analytics.eventTrack('Import Org Data Failed', { label: $scope.model.source });
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
|
||||
if (error) {
|
||||
var data = error.data;
|
||||
if (data && data.ValidationErrors) {
|
||||
var message = '';
|
||||
for (var key in data.ValidationErrors) {
|
||||
if (!data.ValidationErrors.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
|
||||
message += (key + ': ' + data.ValidationErrors[key][i] + ' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (message !== '') {
|
||||
toastr.error(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (data && data.Message) {
|
||||
toastr.error(data.Message);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
toastr.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
toastr.error('Something went wrong. Try again.', 'Oh No!');
|
||||
}
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -16,7 +16,7 @@
|
||||
$scope.save = function (model) {
|
||||
model.organizationId = orgId;
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.logins.postAdmin(login, function (loginResponse) {
|
||||
$scope.savePromise = apiService.ciphers.postAdmin(login, function (loginResponse) {
|
||||
$analytics.eventTrack('Created Organization Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close(decLogin);
|
||||
@@ -30,6 +30,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
e.clearSelection();
|
||||
selectPassword(e);
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
$scope.login = {};
|
||||
$scope.loading = true;
|
||||
$scope.isPremium = true;
|
||||
$scope.canUseAttachments = true;
|
||||
var closing = false;
|
||||
|
||||
apiService.logins.getAdmin({ id: loginId }, function (login) {
|
||||
apiService.ciphers.getAdmin({ id: loginId }, function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.loading = false;
|
||||
}, function () {
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
$scope.login = {};
|
||||
$scope.hideFolders = $scope.hideFavorite = $scope.fromOrg = true;
|
||||
|
||||
apiService.logins.getAdmin({ id: loginId }, function (login) {
|
||||
apiService.ciphers.getAdmin({ id: loginId }, function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.useTotp = $scope.login.organizationUseTotp;
|
||||
});
|
||||
|
||||
$scope.save = function (model) {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.logins.putAdmin({ id: loginId }, login, function (loginResponse) {
|
||||
$scope.savePromise = apiService.ciphers.putAdmin({ id: loginId }, login, function (loginResponse) {
|
||||
$analytics.eventTrack('Edited Organization Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close({
|
||||
@@ -31,6 +31,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
e.clearSelection();
|
||||
selectPassword(e);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
Billing
|
||||
<small>manage your payments</small>
|
||||
<small>manage your billing & licensing</small>
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
@@ -26,14 +26,28 @@
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<dl>
|
||||
<dl ng-if="selfHosted">
|
||||
<dt>Name</dt>
|
||||
<dd>{{plan.name || '-'}}</dd>
|
||||
<dt>Expiration</dt>
|
||||
<dd ng-if="loading">
|
||||
Loading...
|
||||
</dd>
|
||||
<dd ng-if="!loading && expiration">
|
||||
{{expiration | date: 'medium'}}
|
||||
</dd>
|
||||
<dd ng-if="!loading && !expiration">
|
||||
Never expires
|
||||
</dd>
|
||||
</dl>
|
||||
<dl ng-if="!selfHosted">
|
||||
<dt>Name</dt>
|
||||
<dd>{{plan.name || '-'}}</dd>
|
||||
<dt>Total Seats</dt>
|
||||
<dd>{{plan.seats || '-'}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-6" ng-if="!selfHosted">
|
||||
<dl>
|
||||
<dt>Status</dt>
|
||||
<dd>
|
||||
@@ -41,11 +55,11 @@
|
||||
<span ng-if="subscription.markedForCancel">- marked for cancellation</span>
|
||||
</dd>
|
||||
<dt>Next Charge</dt>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: format: mediumDate) + ', ' + (nextInvoice.amount | currency:'$')) : '-'}}</dd>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$')) : '-'}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-if="!noSubscription">
|
||||
<div class="row" ng-if="!selfHosted && !noSubscription">
|
||||
<div class="col-md-6">
|
||||
<strong>Details</strong>
|
||||
<div ng-show="loading">
|
||||
@@ -67,7 +81,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="box-footer" ng-if="!selfHosted">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="changePlan()">
|
||||
Change Plan
|
||||
</button>
|
||||
@@ -79,6 +93,18 @@
|
||||
ng-if="!noSubscription && subscription.markedForCancel">
|
||||
Reinstate Plan
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="license()"
|
||||
ng-if="!subscription.cancelled">
|
||||
Download License
|
||||
</button>
|
||||
</div>
|
||||
<div class="box-footer" ng-if="selfHosted">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="updateLicense()">
|
||||
Update License
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com" class="btn btn-default btn-flat" target="_blank">
|
||||
Manage Billing
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
@@ -93,7 +119,7 @@
|
||||
You plan currently has a total of <b>{{plan.seats}}</b> seats.
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer" ng-if="!noSubscription">
|
||||
<div class="box-footer" ng-if="!selfHosted && !noSubscription">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="adjustSeats(true)">
|
||||
Add Seats
|
||||
</button>
|
||||
@@ -102,7 +128,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default" ng-if="storage">
|
||||
<div class="box box-default" ng-if="storage && !selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Storage</h3>
|
||||
</div>
|
||||
@@ -128,7 +154,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box box-default" ng-if="!selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Payment Method</h3>
|
||||
</div>
|
||||
@@ -140,8 +166,17 @@
|
||||
<i class="fa fa-credit-card"></i> No payment method on file.
|
||||
</div>
|
||||
<div ng-show="!loading && paymentSource">
|
||||
<div class="callout callout-warning" ng-if="paymentSource.type === 1 && paymentSource.needsVerification">
|
||||
<h4><i class="fa fa-warning"></i> You must verify your bank account</h4>
|
||||
<p>
|
||||
We have made two micro-deposits to your bank account (it may take 1-2 business days to show up).
|
||||
Enter these amounts to verify the bank account. Failure to verify the bank account will result in a
|
||||
missed payment and your organization being disabled.
|
||||
</p>
|
||||
<button class="btn btn-default btn-flat" ng-click="verifyBank()">Verify Now</button>
|
||||
</div>
|
||||
<i class="fa" ng-class="{'fa-credit-card': paymentSource.type === 0,
|
||||
'fa-university': paymentSource.type === 1}"></i>
|
||||
'fa-university': paymentSource.type === 1, 'fa-paypal fa-fw text-blue': paymentSource.type === 2}"></i>
|
||||
{{paymentSource.description}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +186,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box box-default" ng-if="!selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Charges</h3>
|
||||
</div>
|
||||
@@ -167,7 +202,7 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="charge in charges">
|
||||
<td style="width: 200px">
|
||||
{{charge.date | date: format: mediumDate}}
|
||||
{{charge.date | date: 'mediumDate'}}
|
||||
</td>
|
||||
<td style="min-width: 150px">
|
||||
{{charge.paymentSource}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{add ? 'Add Seats' : 'Remove Seats'}}
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default" ng-show="add">
|
||||
<h4><i class="fa fa-dollar"></i> Note About Charges</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-file-text-o"></i> Change Plan</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
You can <a href="https://bitwarden.com/contact/" target="_blank">contact us</a>
|
||||
if you would like to change your plan. Please ensure that you have an active payment
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
Verify Bank Account
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Enter the two micro-deposit amounts from your bank account. Both amounts will be less than $1.00 each.
|
||||
For example, if we deposited $0.32 and $0.45 you would enter the values "32" and "45".
|
||||
</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="amount1">Amount 1</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">$ 0.</span>
|
||||
<input type="number" id="amount1" name="Amount1" ng-model="amount1" class="form-control"
|
||||
required min="1" max="99" placeholder="xx" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="amount2">Amount 2</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">$ 0.</span>
|
||||
<input type="number" id="amount2" name="Amount2" ng-model="amount2" class="form-control"
|
||||
required min="1" max="99" placeholder="xx" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Add New Collection</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> Note</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Edit Collection</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(collection)" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit(collection)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> Note</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-sitemap"></i> Add New Group</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> Note</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-sitemap"></i> Edit Group</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> Note</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-user"></i> Edit User <small>{{email}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-sitemap"></i> Edit User Groups <small>{{orgUser.email}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-user"></i> Invite User</h4>
|
||||
</div>
|
||||
<form name="inviteForm" ng-submit="inviteForm.$valid && submit(model)" api-form="submitPromise">
|
||||
<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
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">General</h3>
|
||||
</div>
|
||||
<form role="form" name="generalForm" ng-submit="generalForm.$valid && generalSave()" api-form="generalPromise">
|
||||
<form role="form" name="generalForm" ng-submit="generalForm.$valid && generalSave()" api-form="generalPromise"
|
||||
autocomplete="off">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
@@ -22,17 +23,17 @@
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Organization Name</label>
|
||||
<input type="text" id="name" name="Name" ng-model="model.name" class="form-control"
|
||||
required api-field />
|
||||
required api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Business Name</label>
|
||||
<input type="text" id="businessName" name="BusinessName" ng-model="model.businessName"
|
||||
class="form-control" api-field />
|
||||
class="form-control" api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Billing Email</label>
|
||||
<input type="email" id="billingEmail" name="BillingEmail" ng-model="model.billingEmail"
|
||||
class="form-control" required api-field />
|
||||
class="form-control" required api-field ng-readonly="selfHosted" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 settings-photo">
|
||||
@@ -42,13 +43,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="box-footer" ng-if="!selfHosted">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="generalForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="generalForm.$loading"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Import/Export</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
Quickly import logins, collections, and other data. You can also export all of your organization's
|
||||
vault data in <code>.csv</code> format.
|
||||
</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button class="btn btn-default btn-flat" type="button" ng-click="import()">Import Data</button>
|
||||
<button class="btn btn-default btn-flat" type="button" ng-click="export()">Export Data</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Danger Zone</h3>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Collections <small>{{cipher.name}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Edit the collections that this login is being shared with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
|
||||
@@ -6,17 +6,6 @@
|
||||
_apiUri = appSettings.apiUri,
|
||||
_identityUri = appSettings.identityUri;
|
||||
|
||||
_service.logins = $resource(_apiUri + '/logins/:id', {}, {
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
getAdmin: { url: _apiUri + '/logins/:id/admin', method: 'GET', params: { id: '@id' } },
|
||||
list: { method: 'GET', params: {} },
|
||||
post: { method: 'POST', params: {} },
|
||||
postAdmin: { url: _apiUri + '/logins/admin', method: 'POST', params: {} },
|
||||
put: { method: 'POST', params: { id: '@id' } },
|
||||
putAdmin: { url: _apiUri + '/logins/:id/admin', method: 'POST', params: { id: '@id' } },
|
||||
del: { url: _apiUri + '/logins/:id/delete', method: 'POST', params: { id: '@id' } }
|
||||
});
|
||||
|
||||
_service.folders = $resource(_apiUri + '/folders/:id', {}, {
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
list: { method: 'GET', params: {} },
|
||||
@@ -27,12 +16,17 @@
|
||||
|
||||
_service.ciphers = $resource(_apiUri + '/ciphers/:id', {}, {
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
getAdmin: { url: _apiUri + '/ciphers/:id/admin', method: 'GET', params: { id: '@id' } },
|
||||
getDetails: { url: _apiUri + '/ciphers/:id/details', method: 'GET', params: { id: '@id' } },
|
||||
list: { method: 'GET', params: { includeFolders: false, includeShared: true } },
|
||||
list: { method: 'GET', params: {} },
|
||||
listDetails: { url: _apiUri + '/ciphers/details', method: 'GET', params: {} },
|
||||
listOrganizationDetails: { url: _apiUri + '/ciphers/organization-details', method: 'GET', params: {} },
|
||||
post: { method: 'POST', params: {} },
|
||||
postAdmin: { url: _apiUri + '/ciphers/admin', method: 'POST', params: {} },
|
||||
put: { method: 'POST', params: { id: '@id' } },
|
||||
putAdmin: { url: _apiUri + '/ciphers/:id/admin', method: 'POST', params: { id: '@id' } },
|
||||
'import': { url: _apiUri + '/ciphers/import', method: 'POST', params: {} },
|
||||
favorite: { url: _apiUri + '/ciphers/:id/favorite', method: 'POST', params: { id: '@id' } },
|
||||
importOrg: { url: _apiUri + '/ciphers/import-organization?organizationId=:orgId', method: 'POST', params: { orgId: '@orgId' } },
|
||||
putPartial: { url: _apiUri + '/ciphers/:id/partial', method: 'POST', params: { id: '@id' } },
|
||||
putShare: { url: _apiUri + '/ciphers/:id/share', method: 'POST', params: { id: '@id' } },
|
||||
putCollections: { url: _apiUri + '/ciphers/:id/collections', method: 'POST', params: { id: '@id' } },
|
||||
@@ -59,6 +53,7 @@
|
||||
_service.organizations = $resource(_apiUri + '/organizations/:id', {}, {
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
getBilling: { url: _apiUri + '/organizations/:id/billing', method: 'GET', params: { id: '@id' } },
|
||||
getLicense: { url: _apiUri + '/organizations/:id/license', method: 'GET', params: { id: '@id' } },
|
||||
list: { method: 'GET', params: {} },
|
||||
post: { method: 'POST', params: {} },
|
||||
put: { method: 'POST', params: { id: '@id' } },
|
||||
@@ -69,7 +64,18 @@
|
||||
putCancel: { url: _apiUri + '/organizations/:id/cancel', method: 'POST', params: { id: '@id' } },
|
||||
putReinstate: { url: _apiUri + '/organizations/:id/reinstate', method: 'POST', params: { id: '@id' } },
|
||||
postLeave: { url: _apiUri + '/organizations/:id/leave', method: 'POST', params: { id: '@id' } },
|
||||
del: { url: _apiUri + '/organizations/:id/delete', method: 'POST', params: { id: '@id' } }
|
||||
postVerifyBank: { url: _apiUri + '/organizations/:id/verify-bank', method: 'POST', params: { id: '@id' } },
|
||||
del: { url: _apiUri + '/organizations/:id/delete', method: 'POST', params: { id: '@id' } },
|
||||
postLicense: {
|
||||
url: _apiUri + '/organizations/license',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined }
|
||||
},
|
||||
putLicense: {
|
||||
url: _apiUri + '/organizations/:id/license',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined }
|
||||
}
|
||||
});
|
||||
|
||||
_service.organizationUsers = $resource(_apiUri + '/organizations/:orgId/users/:id', {}, {
|
||||
@@ -88,7 +94,7 @@
|
||||
_service.collections = $resource(_apiUri + '/organizations/:orgId/collections/:id', {}, {
|
||||
get: { method: 'GET', params: { id: '@id', orgId: '@orgId' } },
|
||||
getDetails: { url: _apiUri + '/organizations/:orgId/collections/:id/details', method: 'GET', params: { id: '@id', orgId: '@orgId' } },
|
||||
listMe: { url: _apiUri + '/collections', method: 'GET', params: {} },
|
||||
listMe: { url: _apiUri + '/collections?writeOnly=:writeOnly', method: 'GET', params: { writeOnly: '@writeOnly' } },
|
||||
listOrganization: { method: 'GET', params: { orgId: '@orgId' } },
|
||||
listUsers: { url: _apiUri + '/organizations/:orgId/collections/:id/users', method: 'GET', params: { id: '@id', orgId: '@orgId' } },
|
||||
post: { method: 'POST', params: { orgId: '@orgId' } },
|
||||
@@ -114,6 +120,8 @@
|
||||
email: { url: _apiUri + '/accounts/email', method: 'POST', params: {} },
|
||||
verifyEmailToken: { url: _apiUri + '/accounts/verify-email-token', method: 'POST', params: {} },
|
||||
verifyEmail: { url: _apiUri + '/accounts/verify-email', method: 'POST', params: {} },
|
||||
postDeleteRecoverToken: { url: _apiUri + '/accounts/delete-recover-token', method: 'POST', params: {} },
|
||||
postDeleteRecover: { url: _apiUri + '/accounts/delete-recover', method: 'POST', params: {} },
|
||||
putPassword: { url: _apiUri + '/accounts/password', method: 'POST', params: {} },
|
||||
getProfile: { url: _apiUri + '/accounts/profile', method: 'GET', params: {} },
|
||||
putProfile: { url: _apiUri + '/accounts/profile', method: 'POST', params: {} },
|
||||
@@ -125,12 +133,21 @@
|
||||
putKey: { url: _apiUri + '/accounts/key', method: 'POST', params: {} },
|
||||
'import': { url: _apiUri + '/accounts/import', method: 'POST', params: {} },
|
||||
postDelete: { url: _apiUri + '/accounts/delete', method: 'POST', params: {} },
|
||||
postPremium: { url: _apiUri + '/accounts/premium', method: 'POST', params: {} },
|
||||
putStorage: { url: _apiUri + '/accounts/storage', method: 'POST', params: {} },
|
||||
putPayment: { url: _apiUri + '/accounts/payment', method: 'POST', params: {} },
|
||||
putCancelPremium: { url: _apiUri + '/accounts/cancel-premium', method: 'POST', params: {} },
|
||||
putReinstatePremium: { url: _apiUri + '/accounts/reinstate-premium', method: 'POST', params: {} },
|
||||
getBilling: { url: _apiUri + '/accounts/billing', method: 'GET', params: {} }
|
||||
getBilling: { url: _apiUri + '/accounts/billing', method: 'GET', params: {} },
|
||||
postPremium: {
|
||||
url: _apiUri + '/accounts/premium',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined }
|
||||
},
|
||||
putLicense: {
|
||||
url: _apiUri + '/accounts/license',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined }
|
||||
}
|
||||
});
|
||||
|
||||
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
|
||||
|
||||
@@ -35,7 +35,7 @@ angular
|
||||
attachments: null
|
||||
};
|
||||
|
||||
var loginData = encryptedLogin.Data || encryptedLogin;
|
||||
var loginData = encryptedLogin.Data;
|
||||
if (loginData) {
|
||||
login.name = cryptoService.decrypt(loginData.Name, key);
|
||||
login.uri = loginData.Uri && loginData.Uri !== '' ? cryptoService.decrypt(loginData.Uri, key) : null;
|
||||
@@ -43,6 +43,7 @@ angular
|
||||
login.password = loginData.Password && loginData.Password !== '' ? cryptoService.decrypt(loginData.Password, key) : null;
|
||||
login.notes = loginData.Notes && loginData.Notes !== '' ? cryptoService.decrypt(loginData.Notes, key) : null;
|
||||
login.totp = loginData.Totp && loginData.Totp !== '' ? cryptoService.decrypt(loginData.Totp, key) : null;
|
||||
login.fields = _service.decryptFields(key, loginData.Fields);
|
||||
}
|
||||
|
||||
if (!encryptedLogin.Attachments) {
|
||||
@@ -76,11 +77,11 @@ angular
|
||||
hasAttachments: !!encryptedCipher.Attachments && encryptedCipher.Attachments.length > 0
|
||||
};
|
||||
|
||||
var loginData = encryptedCipher.Data || encryptedCipher;
|
||||
var loginData = encryptedCipher.Data;
|
||||
if (loginData) {
|
||||
login.name = cryptoService.decrypt(loginData.Name, key);
|
||||
login.username = loginData.Username && loginData.Username !== '' ? cryptoService.decrypt(loginData.Username, key) : null;
|
||||
login.password = loginData.Password && loginData.Password !== '' ? cryptoService.decrypt(loginData.Password, key) : null;
|
||||
login.name = _service.decryptProperty(loginData.Name, key, false);
|
||||
login.username = _service.decryptProperty(loginData.Username, key, true);
|
||||
login.password = _service.decryptProperty(loginData.Password, key, true);
|
||||
}
|
||||
|
||||
return login;
|
||||
@@ -134,6 +135,28 @@ angular
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.decryptFields = function (key, encryptedFields) {
|
||||
var unencryptedFields = [];
|
||||
|
||||
if (encryptedFields) {
|
||||
for (var i = 0; i < encryptedFields.length; i++) {
|
||||
unencryptedFields.push(_service.decryptField(key, encryptedFields[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return unencryptedFields;
|
||||
};
|
||||
|
||||
_service.decryptField = function (key, encryptedField) {
|
||||
if (!encryptedField) throw "encryptedField is undefined or null";
|
||||
|
||||
return {
|
||||
type: encryptedField.Type.toString(),
|
||||
name: encryptedField.Name && encryptedField.Name !== '' ? cryptoService.decrypt(encryptedField.Name, key) : null,
|
||||
value: encryptedField.Value && encryptedField.Value !== '' ? cryptoService.decrypt(encryptedField.Value, key) : null
|
||||
};
|
||||
};
|
||||
|
||||
_service.decryptFolders = function (encryptedFolders) {
|
||||
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
|
||||
|
||||
@@ -227,12 +250,15 @@ angular
|
||||
organizationId: unencryptedLogin.organizationId || null,
|
||||
folderId: unencryptedLogin.folderId === '' ? null : unencryptedLogin.folderId,
|
||||
favorite: unencryptedLogin.favorite !== null ? unencryptedLogin.favorite : false,
|
||||
uri: !unencryptedLogin.uri || unencryptedLogin.uri === '' ? null : cryptoService.encrypt(unencryptedLogin.uri, key),
|
||||
name: cryptoService.encrypt(unencryptedLogin.name, key),
|
||||
username: !unencryptedLogin.username || unencryptedLogin.username === '' ? null : cryptoService.encrypt(unencryptedLogin.username, key),
|
||||
password: !unencryptedLogin.password || unencryptedLogin.password === '' ? null : cryptoService.encrypt(unencryptedLogin.password, key),
|
||||
notes: !unencryptedLogin.notes || unencryptedLogin.notes === '' ? null : cryptoService.encrypt(unencryptedLogin.notes, key),
|
||||
totp: !unencryptedLogin.totp || unencryptedLogin.totp === '' ? null : cryptoService.encrypt(unencryptedLogin.totp, key)
|
||||
login: {
|
||||
uri: !unencryptedLogin.uri || unencryptedLogin.uri === '' ? null : cryptoService.encrypt(unencryptedLogin.uri, key),
|
||||
username: !unencryptedLogin.username || unencryptedLogin.username === '' ? null : cryptoService.encrypt(unencryptedLogin.username, key),
|
||||
password: !unencryptedLogin.password || unencryptedLogin.password === '' ? null : cryptoService.encrypt(unencryptedLogin.password, key),
|
||||
totp: !unencryptedLogin.totp || unencryptedLogin.totp === '' ? null : cryptoService.encrypt(unencryptedLogin.totp, key)
|
||||
},
|
||||
fields: _service.encryptFields(unencryptedLogin.fields, key)
|
||||
};
|
||||
|
||||
if (unencryptedLogin.attachments && attachments) {
|
||||
@@ -272,6 +298,33 @@ angular
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.encryptFields = function (unencryptedFields, key) {
|
||||
if (!unencryptedFields || !unencryptedFields.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var encFields = [];
|
||||
for (var i = 0; i < unencryptedFields.length; i++) {
|
||||
if (!unencryptedFields[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
encFields.push(_service.encryptField(unencryptedFields[i], key));
|
||||
}
|
||||
|
||||
return encFields;
|
||||
};
|
||||
|
||||
_service.encryptField = function (unencryptedField, key) {
|
||||
if (!unencryptedField) throw "unencryptedField is undefined or null";
|
||||
|
||||
return {
|
||||
type: parseInt(unencryptedField.type),
|
||||
name: unencryptedField.name ? cryptoService.encrypt(unencryptedField.name, key) : null,
|
||||
value: unencryptedField.value ? cryptoService.encrypt(unencryptedField.value.toString(), key) : null
|
||||
};
|
||||
};
|
||||
|
||||
_service.encryptFolders = function (unencryptedFolders, key) {
|
||||
if (!unencryptedFolders) throw "unencryptedFolders is undefined or null";
|
||||
|
||||
@@ -312,56 +365,5 @@ angular
|
||||
};
|
||||
};
|
||||
|
||||
_service.updateKey = function (masterPasswordHash, success, error) {
|
||||
var madeEncKey = cryptoService.makeEncKey(null);
|
||||
encKey = madeEncKey.encKey;
|
||||
var encKeyEnc = madeEncKey.encKeyEnc;
|
||||
|
||||
var reencryptedLogins = [];
|
||||
var loginsPromise = apiService.logins.list({}, function (encryptedLogins) {
|
||||
var filteredEncryptedLogins = [];
|
||||
for (var i = 0; i < encryptedLogins.Data.length; i++) {
|
||||
if (encryptedLogins.Data[i].OrganizationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredEncryptedLogins.push(encryptedLogins.Data[i]);
|
||||
}
|
||||
|
||||
var unencryptedLogins = _service.decryptLogins(filteredEncryptedLogins);
|
||||
reencryptedLogins = _service.encryptLogins(unencryptedLogins, encKey);
|
||||
}).$promise;
|
||||
|
||||
var reencryptedFolders = [];
|
||||
var foldersPromise = apiService.folders.list({}, function (encryptedFolders) {
|
||||
var unencryptedFolders = _service.decryptFolders(encryptedFolders.Data);
|
||||
reencryptedFolders = _service.encryptFolders(unencryptedFolders, encKey);
|
||||
}).$promise;
|
||||
|
||||
var privateKey = cryptoService.getPrivateKey('raw'),
|
||||
reencryptedPrivateKey = null;
|
||||
if (privateKey) {
|
||||
reencryptedPrivateKey = cryptoService.encrypt(privateKey, encKey, 'raw');
|
||||
}
|
||||
|
||||
return $q.all([loginsPromise, foldersPromise]).then(function () {
|
||||
var request = {
|
||||
masterPasswordHash: masterPasswordHash,
|
||||
ciphers: reencryptedLogins,
|
||||
folders: reencryptedFolders,
|
||||
privateKey: reencryptedPrivateKey,
|
||||
key: encKeyEnc
|
||||
};
|
||||
|
||||
return apiService.accounts.putKey(request).$promise;
|
||||
}, error).then(function () {
|
||||
cryptoService.setEncKey(encKey, null, true);
|
||||
return success();
|
||||
}, function () {
|
||||
cryptoService.clearEncKey();
|
||||
error();
|
||||
});
|
||||
};
|
||||
|
||||
return _service;
|
||||
});
|
||||
|
||||
@@ -288,8 +288,12 @@ angular
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
_service.makeShareKeyCt = function () {
|
||||
return _service.rsaEncryptMe(forge.random.getBytesSync(512 / 8));
|
||||
_service.makeShareKey = function () {
|
||||
var key = forge.random.getBytesSync(512 / 8);
|
||||
return {
|
||||
key: new SymmetricCryptoKey(key),
|
||||
ct: _service.rsaEncryptMe(key)
|
||||
};
|
||||
};
|
||||
|
||||
_service.hashPassword = function (password, key) {
|
||||
@@ -477,121 +481,140 @@ angular
|
||||
};
|
||||
|
||||
_service.decrypt = function (encValue, key, outputEncoding) {
|
||||
key = key || _service.getEncKey() || _service.getKey();
|
||||
try {
|
||||
key = key || _service.getEncKey() || _service.getKey();
|
||||
|
||||
var headerPieces = encValue.split('.'),
|
||||
encType,
|
||||
encPieces;
|
||||
var headerPieces = encValue.split('.'),
|
||||
encType,
|
||||
encPieces;
|
||||
|
||||
if (headerPieces.length === 2) {
|
||||
try {
|
||||
encType = parseInt(headerPieces[0]);
|
||||
encPieces = headerPieces[1].split('|');
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
encPieces = encValue.split('|');
|
||||
encType = encPieces.length === 3 ? constants.encType.AesCbc128_HmacSha256_B64 :
|
||||
constants.encType.AesCbc256_B64;
|
||||
}
|
||||
|
||||
if (encType === constants.encType.AesCbc128_HmacSha256_B64 && key.encType === constants.encType.AesCbc256_B64) {
|
||||
// Old encrypt-then-mac scheme, swap out the key
|
||||
_legacyEtmKey = _legacyEtmKey ||
|
||||
new SymmetricCryptoKey(key.key, false, constants.encType.AesCbc128_HmacSha256_B64);
|
||||
key = _legacyEtmKey;
|
||||
}
|
||||
|
||||
if (encType !== key.encType) {
|
||||
throw 'encType unavailable.';
|
||||
}
|
||||
|
||||
switch (encType) {
|
||||
case constants.encType.AesCbc128_HmacSha256_B64:
|
||||
case constants.encType.AesCbc256_HmacSha256_B64:
|
||||
if (encPieces.length !== 3) {
|
||||
if (headerPieces.length === 2) {
|
||||
try {
|
||||
encType = parseInt(headerPieces[0]);
|
||||
encPieces = headerPieces[1].split('|');
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Cannot parse headerPieces.');
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case constants.encType.AesCbc256_B64:
|
||||
if (encPieces.length !== 2) {
|
||||
}
|
||||
else {
|
||||
encPieces = encValue.split('|');
|
||||
encType = encPieces.length === 3 ? constants.encType.AesCbc128_HmacSha256_B64 :
|
||||
constants.encType.AesCbc256_B64;
|
||||
}
|
||||
|
||||
if (encType === constants.encType.AesCbc128_HmacSha256_B64 && key.encType === constants.encType.AesCbc256_B64) {
|
||||
// Old encrypt-then-mac scheme, swap out the key
|
||||
_legacyEtmKey = _legacyEtmKey ||
|
||||
new SymmetricCryptoKey(key.key, false, constants.encType.AesCbc128_HmacSha256_B64);
|
||||
key = _legacyEtmKey;
|
||||
}
|
||||
|
||||
if (encType !== key.encType) {
|
||||
throw 'encType unavailable.';
|
||||
}
|
||||
|
||||
switch (encType) {
|
||||
case constants.encType.AesCbc128_HmacSha256_B64:
|
||||
case constants.encType.AesCbc256_HmacSha256_B64:
|
||||
if (encPieces.length !== 3) {
|
||||
console.error('Enc type (' + encType + ') not valid.');
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case constants.encType.AesCbc256_B64:
|
||||
if (encPieces.length !== 2) {
|
||||
console.error('Enc type (' + encType + ') not valid.');
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Enc type (' + encType + ') not supported.');
|
||||
return null;
|
||||
}
|
||||
|
||||
var ivBytes = forge.util.decode64(encPieces[0]);
|
||||
var ctBytes = forge.util.decode64(encPieces[1]);
|
||||
|
||||
if (key.macKey && encPieces.length > 2) {
|
||||
var macBytes = forge.util.decode64(encPieces[2]);
|
||||
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
|
||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
||||
console.error('MAC failed.');
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var ivBytes = forge.util.decode64(encPieces[0]);
|
||||
var ctBytes = forge.util.decode64(encPieces[1]);
|
||||
var ctBuffer = forge.util.createBuffer(ctBytes);
|
||||
var decipher = forge.cipher.createDecipher('AES-CBC', key.encKey);
|
||||
decipher.start({ iv: ivBytes });
|
||||
decipher.update(ctBuffer);
|
||||
decipher.finish();
|
||||
|
||||
if (key.macKey && encPieces.length > 2) {
|
||||
var macBytes = forge.util.decode64(encPieces[2]);
|
||||
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
|
||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
||||
console.error('MAC failed.');
|
||||
return null;
|
||||
outputEncoding = outputEncoding || 'utf8';
|
||||
if (outputEncoding === 'utf8') {
|
||||
return decipher.output.toString('utf8');
|
||||
}
|
||||
else {
|
||||
return decipher.output.getBytes();
|
||||
}
|
||||
}
|
||||
|
||||
var ctBuffer = forge.util.createBuffer(ctBytes);
|
||||
var decipher = forge.cipher.createDecipher('AES-CBC', key.encKey);
|
||||
decipher.start({ iv: ivBytes });
|
||||
decipher.update(ctBuffer);
|
||||
decipher.finish();
|
||||
|
||||
outputEncoding = outputEncoding || 'utf8';
|
||||
if (outputEncoding === 'utf8') {
|
||||
return decipher.output.toString('utf8');
|
||||
}
|
||||
else {
|
||||
return decipher.output.getBytes();
|
||||
catch (e) {
|
||||
console.error('Caught unhandled error in decrypt: ' + e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
_service.decryptFromBytes = function (encBuf, key) {
|
||||
if (!encBuf) {
|
||||
throw 'no encBuf.';
|
||||
}
|
||||
try {
|
||||
if (!encBuf) {
|
||||
throw 'no encBuf.';
|
||||
}
|
||||
|
||||
var encBytes = new Uint8Array(encBuf),
|
||||
encType = encBytes[0],
|
||||
ctBytes = null,
|
||||
ivBytes = null,
|
||||
macBytes = null;
|
||||
var encBytes = new Uint8Array(encBuf),
|
||||
encType = encBytes[0],
|
||||
ctBytes = null,
|
||||
ivBytes = null,
|
||||
macBytes = null;
|
||||
|
||||
switch (encType) {
|
||||
case constants.encType.AesCbc128_HmacSha256_B64:
|
||||
case constants.encType.AesCbc256_HmacSha256_B64:
|
||||
if (encBytes.length <= 49) { // 1 + 16 + 32 + ctLength
|
||||
switch (encType) {
|
||||
case constants.encType.AesCbc128_HmacSha256_B64:
|
||||
case constants.encType.AesCbc256_HmacSha256_B64:
|
||||
if (encBytes.length <= 49) { // 1 + 16 + 32 + ctLength
|
||||
console.error('Enc type (' + encType + ') not valid.');
|
||||
return null;
|
||||
}
|
||||
|
||||
ivBytes = slice(encBytes, 1, 17);
|
||||
macBytes = slice(encBytes, 17, 49);
|
||||
ctBytes = slice(encBytes, 49);
|
||||
break;
|
||||
case constants.encType.AesCbc256_B64:
|
||||
if (encBytes.length <= 17) { // 1 + 16 + ctLength
|
||||
console.error('Enc type (' + encType + ') not valid.');
|
||||
return null;
|
||||
}
|
||||
|
||||
ivBytes = slice(encBytes, 1, 17);
|
||||
ctBytes = slice(encBytes, 17);
|
||||
break;
|
||||
default:
|
||||
console.error('Enc type (' + encType + ') not supported.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ivBytes = slice(encBytes, 1, 17);
|
||||
macBytes = slice(encBytes, 17, 49);
|
||||
ctBytes = slice(encBytes, 49);
|
||||
break;
|
||||
case constants.encType.AesCbc256_B64:
|
||||
if (encBytes.length <= 17) { // 1 + 16 + ctLength
|
||||
return null;
|
||||
}
|
||||
|
||||
ivBytes = slice(encBytes, 1, 17);
|
||||
ctBytes = slice(encBytes, 17);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
return aesDecryptWC(
|
||||
encType,
|
||||
ctBytes.buffer,
|
||||
ivBytes.buffer,
|
||||
macBytes ? macBytes.buffer : null,
|
||||
key);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Caught unhandled error in decryptFromBytes: ' + e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return aesDecryptWC(
|
||||
encType,
|
||||
ctBytes.buffer,
|
||||
ivBytes.buffer,
|
||||
macBytes ? macBytes.buffer : null,
|
||||
key);
|
||||
};
|
||||
|
||||
function aesDecryptWC(encType, ctBuf, ivBuf, macBuf, key) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
importBitwardenCsv(file, success, error);
|
||||
break;
|
||||
case 'lastpass':
|
||||
importLastPass(file, success, error);
|
||||
importLastPass(file, success, error, false);
|
||||
break;
|
||||
case 'safeincloudxml':
|
||||
importSafeInCloudXml(file, success, error);
|
||||
@@ -109,6 +109,25 @@
|
||||
}
|
||||
};
|
||||
|
||||
_service.importOrg = function (source, file, success, error) {
|
||||
if (!file) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (source) {
|
||||
case 'bitwardencsv':
|
||||
importBitwardenOrgCsv(file, success, error);
|
||||
break;
|
||||
case 'lastpass':
|
||||
importLastPass(file, success, error, true);
|
||||
break;
|
||||
default:
|
||||
error();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var _passwordFieldNames = [
|
||||
'password', 'pass word', 'passphrase', 'pass phrase',
|
||||
'pass', 'code', 'code word', 'codeword',
|
||||
@@ -224,7 +243,8 @@
|
||||
|
||||
var folders = [],
|
||||
logins = [],
|
||||
folderRelationships = [];
|
||||
folderRelationships = [],
|
||||
i = 0;
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
var folderIndex = folders.length,
|
||||
@@ -233,7 +253,7 @@
|
||||
addFolder = hasFolder;
|
||||
|
||||
if (hasFolder) {
|
||||
for (var i = 0; i < folders.length; i++) {
|
||||
for (i = 0; i < folders.length; i++) {
|
||||
if (folders[i].name === value.folder) {
|
||||
addFolder = false;
|
||||
folderIndex = i;
|
||||
@@ -242,7 +262,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
logins.push({
|
||||
var login = {
|
||||
favorite: value.favorite && value.favorite !== '' && value.favorite !== '0' ? true : false,
|
||||
uri: value.uri && value.uri !== '' ? trimUri(value.uri) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
@@ -250,7 +270,39 @@
|
||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
||||
name: value.name && value.name !== '' ? value.name : '--',
|
||||
totp: value.totp && value.totp !== '' ? value.totp : null
|
||||
});
|
||||
};
|
||||
|
||||
if (value.fields && value.fields !== '') {
|
||||
var fields = value.fields.split('\n');
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i] || fields[i] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var delimPosition = fields[i].lastIndexOf(': ');
|
||||
if (delimPosition === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!login.fields) {
|
||||
login.fields = [];
|
||||
}
|
||||
|
||||
var field = {
|
||||
name: fields[i].substr(0, delimPosition),
|
||||
value: null,
|
||||
type: 0
|
||||
};
|
||||
|
||||
if (fields[i].length > (delimPosition + 2)) {
|
||||
field.value = fields[i].substr(delimPosition + 2);
|
||||
}
|
||||
|
||||
login.fields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
logins.push(login);
|
||||
|
||||
if (addFolder) {
|
||||
folders.push({
|
||||
@@ -272,7 +324,98 @@
|
||||
});
|
||||
}
|
||||
|
||||
function importLastPass(file, success, error) {
|
||||
function importBitwardenOrgCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var collections = [],
|
||||
logins = [],
|
||||
collectionRelationships = [],
|
||||
i;
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
var loginIndex = logins.length;
|
||||
|
||||
if (value.collections && value.collections !== '') {
|
||||
var loginCollections = value.collections.split(',');
|
||||
|
||||
for (i = 0; i < loginCollections.length; i++) {
|
||||
var addCollection = true;
|
||||
var collectionIndex = collections.length;
|
||||
|
||||
for (var j = 0; j < collections.length; j++) {
|
||||
if (collections[j].name === loginCollections[i]) {
|
||||
addCollection = false;
|
||||
collectionIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addCollection) {
|
||||
collections.push({
|
||||
name: loginCollections[i]
|
||||
});
|
||||
}
|
||||
|
||||
collectionRelationships.push({
|
||||
key: loginIndex,
|
||||
value: collectionIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var login = {
|
||||
favorite: false,
|
||||
uri: value.uri && value.uri !== '' ? trimUri(value.uri) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
||||
name: value.name && value.name !== '' ? value.name : '--',
|
||||
totp: value.totp && value.totp !== '' ? value.totp : null,
|
||||
};
|
||||
|
||||
if (value.fields && value.fields !== '') {
|
||||
var fields = value.fields.split('\n');
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i] || fields[i] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var delimPosition = fields[i].lastIndexOf(': ');
|
||||
if (delimPosition === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!login.fields) {
|
||||
login.fields = [];
|
||||
}
|
||||
|
||||
var field = {
|
||||
name: fields[i].substr(0, delimPosition),
|
||||
value: null,
|
||||
type: 0
|
||||
};
|
||||
|
||||
if (fields[i].length > (delimPosition + 2)) {
|
||||
field.value = fields[i].substr(delimPosition + 2);
|
||||
}
|
||||
|
||||
login.fields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
logins.push(login);
|
||||
});
|
||||
|
||||
success(collections, logins, collectionRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importLastPass(file, success, error, org) {
|
||||
if (typeof file !== 'string' && file.type && file.type === 'text/html') {
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
@@ -350,7 +493,7 @@
|
||||
}
|
||||
|
||||
logins.push({
|
||||
favorite: value.fav === '1',
|
||||
favorite: org ? false : value.fav === '1',
|
||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
@@ -770,7 +913,10 @@
|
||||
else if (!login.password && field[designationKey] && field[designationKey] === 'password') {
|
||||
login.password = field[valueKey];
|
||||
}
|
||||
else if (field[nameKey] && field[valueKey]) {
|
||||
else if (!login.totp && field[designationKey] && field[designationKey].startsWith("TOTP_")) {
|
||||
login.totp = field[valueKey];
|
||||
}
|
||||
else if (field[valueKey]) {
|
||||
if (login.notes === null) {
|
||||
login.notes = '';
|
||||
}
|
||||
@@ -778,7 +924,7 @@
|
||||
login.notes += '\n';
|
||||
}
|
||||
|
||||
login.notes += (field[nameKey] + ': ' +
|
||||
login.notes += ((field[nameKey] || 'no_name') + ': ' +
|
||||
field[valueKey].toString().split('\\r\\n').join('\n').split('\\n').join('\n'));
|
||||
}
|
||||
}
|
||||
@@ -803,6 +949,7 @@
|
||||
password: null,
|
||||
notes: null,
|
||||
name: item.title && item.title !== '' ? item.title : '--',
|
||||
totp: null
|
||||
};
|
||||
|
||||
if (item.secureContents) {
|
||||
@@ -1262,7 +1409,8 @@
|
||||
uri: null,
|
||||
password: null,
|
||||
username: null,
|
||||
notes: note && note !== '' ? note : null
|
||||
notes: note && note !== '' ? note : null,
|
||||
totp: null
|
||||
};
|
||||
|
||||
if (row.length > 2 && (row.length % 2) === 0) {
|
||||
@@ -1284,6 +1432,9 @@
|
||||
else if (fieldLower === 'password' && !login.password) {
|
||||
login.password = value;
|
||||
}
|
||||
else if (fieldLower === 'totp' && !login.totp) {
|
||||
login.totp = value;
|
||||
}
|
||||
else {
|
||||
// other custom fields
|
||||
login.notes = login.notes === null ? field + ': ' + value
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
angular.module("bit")
|
||||
.constant("appSettings", {"apiUri":"https://api.bitwarden.com","identityUri":"https://identity.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","version":"1.14.0","environment":"Production"});
|
||||
.constant("appSettings", {"apiUri":"https://api.bitwarden.com","identityUri":"https://identity.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","whitelistDomains":["api.bitwarden.com"],"selfHosted":false,"version":"1.17.3","environment":"Production"});
|
||||
|
||||
@@ -1,18 +1,66 @@
|
||||
angular
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('settingsBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService, stripe,
|
||||
$analytics, toastr, existingPaymentMethod) {
|
||||
.controller('settingsBillingChangePaymentController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, existingPaymentMethod, appSettings, $timeout
|
||||
// @if !selfHosted
|
||||
, stripe
|
||||
// @endif
|
||||
) {
|
||||
$analytics.eventTrack('settingsBillingChangePaymentController', { category: 'Modal' });
|
||||
$scope.existingPaymentMethod = existingPaymentMethod;
|
||||
$scope.paymentMethod = 'card';
|
||||
$scope.dropinLoaded = false;
|
||||
$scope.showPaymentOptions = false;
|
||||
$scope.hideBank = true;
|
||||
$scope.card = {};
|
||||
var btInstance = null;
|
||||
|
||||
$scope.changePaymentMethod = function (val) {
|
||||
$scope.paymentMethod = val;
|
||||
if ($scope.paymentMethod !== 'paypal') {
|
||||
return;
|
||||
}
|
||||
|
||||
braintree.dropin.create({
|
||||
authorization: appSettings.braintreeKey,
|
||||
container: '#bt-dropin-container',
|
||||
paymentOptionPriority: ['paypal'],
|
||||
paypal: {
|
||||
flow: 'vault',
|
||||
buttonStyle: {
|
||||
label: 'pay',
|
||||
size: 'medium',
|
||||
shape: 'pill',
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
}, function (createErr, instance) {
|
||||
if (createErr) {
|
||||
console.error(createErr);
|
||||
return;
|
||||
}
|
||||
|
||||
btInstance = instance;
|
||||
$timeout(function () {
|
||||
$scope.dropinLoaded = true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.submit = function () {
|
||||
$scope.submitPromise = stripe.card.createToken($scope.card).then(function (response) {
|
||||
$scope.submitPromise = getPaymentToken($scope.card).then(function (token) {
|
||||
if (!token) {
|
||||
throw 'No payment token.';
|
||||
}
|
||||
|
||||
var request = {
|
||||
paymentToken: response.id
|
||||
paymentToken: token
|
||||
};
|
||||
|
||||
return apiService.accounts.putPayment(null, request).$promise;
|
||||
}, function (err) {
|
||||
throw err;
|
||||
}).then(function (response) {
|
||||
$scope.card = null;
|
||||
if (existingPaymentMethod) {
|
||||
@@ -31,4 +79,21 @@
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
function getPaymentToken(card) {
|
||||
if ($scope.paymentMethod === 'paypal') {
|
||||
return btInstance.requestPaymentMethod().then(function (payload) {
|
||||
return payload.nonce;
|
||||
}).catch(function (err) {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
else {
|
||||
return stripe.card.createToken(card).then(function (response) {
|
||||
return response.id;
|
||||
}).catch(function (err) {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsBillingController', function ($scope, apiService, authService, $state, $uibModal, toastr, $analytics) {
|
||||
.controller('settingsBillingController', function ($scope, apiService, authService, $state, $uibModal, toastr, $analytics,
|
||||
appSettings) {
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
$scope.charges = [];
|
||||
$scope.paymentSource = null;
|
||||
$scope.subscription = null;
|
||||
$scope.loading = true;
|
||||
var license = null;
|
||||
$scope.expiration = null;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
load();
|
||||
});
|
||||
|
||||
$scope.changePayment = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingChangePayment.html',
|
||||
@@ -29,6 +37,10 @@
|
||||
};
|
||||
|
||||
$scope.adjustStorage = function (add) {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingAdjustStorage.html',
|
||||
@@ -46,6 +58,10 @@
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to cancel? You will lose access to all premium features at the end ' +
|
||||
'of this billing cycle.')) {
|
||||
return;
|
||||
@@ -60,6 +76,10 @@
|
||||
};
|
||||
|
||||
$scope.reinstate = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to remove the cancellation request and reinstate your premium membership?')) {
|
||||
return;
|
||||
}
|
||||
@@ -72,6 +92,46 @@
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateLicense = function () {
|
||||
if (!$scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsBillingUpdateLicense.html',
|
||||
controller: 'settingsBillingUpdateLicenseController'
|
||||
});
|
||||
|
||||
modal.result.then(function () {
|
||||
load();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.license = function () {
|
||||
if ($scope.selfHosted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var licenseString = JSON.stringify(license, null, 2);
|
||||
var licenseBlob = new Blob([licenseString]);
|
||||
|
||||
// IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(licenseBlob, 'bitwarden_premium_license.json');
|
||||
}
|
||||
else {
|
||||
var a = window.document.createElement('a');
|
||||
a.href = window.URL.createObjectURL(licenseBlob, { type: 'text/plain' });
|
||||
a.download = 'bitwarden_premium_license.json';
|
||||
document.body.appendChild(a);
|
||||
// IE: "Access is denied".
|
||||
// ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
};
|
||||
|
||||
function load() {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.premium = profile.premium;
|
||||
@@ -86,6 +146,8 @@
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
$scope.expiration = billing.Expiration;
|
||||
license = billing.License;
|
||||
|
||||
$scope.storage = null;
|
||||
if (billing && billing.MaxStorageGb) {
|
||||
@@ -104,8 +166,8 @@
|
||||
trialEndDate: billing.Subscription.TrialEndDate,
|
||||
cancelledDate: billing.Subscription.CancelledDate,
|
||||
status: billing.Subscription.Status,
|
||||
cancelled: billing.Subscription.Status === 'cancelled',
|
||||
markedForCancel: billing.Subscription.Status === 'active' && billing.Subscription.CancelledDate
|
||||
cancelled: billing.Subscription.Cancelled,
|
||||
markedForCancel: !billing.Subscription.Cancelled && billing.Subscription.CancelAtEndDate
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
30
src/app/settings/settingsBillingUpdateLicenseController.js
Normal file
30
src/app/settings/settingsBillingUpdateLicenseController.js
Normal file
@@ -0,0 +1,30 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsBillingUpdateLicenseController', function ($scope, $state, $uibModalInstance, apiService,
|
||||
$analytics, toastr, validationService) {
|
||||
$analytics.eventTrack('settingsBillingUpdateLicenseController', { category: 'Modal' });
|
||||
|
||||
$scope.submit = function (form) {
|
||||
var fileEl = document.getElementById('file');
|
||||
var files = fileEl.files;
|
||||
if (!files || !files.length) {
|
||||
validationService.addError(form, 'file', 'Select a license file.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('license', files[0]);
|
||||
|
||||
$scope.submitPromise = apiService.accounts.putLicense(fd)
|
||||
.$promise.then(function (response) {
|
||||
$analytics.eventTrack('Updated License');
|
||||
toastr.success('You have updated your license.');
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -2,49 +2,39 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsChangeEmailController', function ($scope, $state, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, authService, $q, toastr, $analytics) {
|
||||
authService, toastr, $analytics, validationService) {
|
||||
$analytics.eventTrack('settingsChangeEmailController', { category: 'Modal' });
|
||||
|
||||
var _masterPasswordHash,
|
||||
_masterPassword,
|
||||
_newEmail;
|
||||
|
||||
$scope.token = function (model) {
|
||||
$scope.token = function (model, form) {
|
||||
var encKey = cryptoService.getEncKey();
|
||||
if (!encKey) {
|
||||
validationService.addError(form, null,
|
||||
'You cannot change your email until you update your encryption key.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
_masterPassword = model.masterPassword;
|
||||
_newEmail = model.newEmail.toLowerCase();
|
||||
|
||||
cryptoService.hashPassword(_masterPassword).then(function (hash) {
|
||||
$scope.tokenPromise = cryptoService.hashPassword(_masterPassword).then(function (hash) {
|
||||
_masterPasswordHash = hash;
|
||||
|
||||
var encKey = cryptoService.getEncKey();
|
||||
if (encKey) {
|
||||
$scope.tokenPromise = requestToken();
|
||||
}
|
||||
else {
|
||||
// User is not using an enc key, let's make them one
|
||||
$scope.tokenPromise = cipherService.updateKey(_masterPasswordHash, function () {
|
||||
return requestToken();
|
||||
}, function (err) {
|
||||
toastr.error('Something went wrong.', 'Oh No!');
|
||||
});
|
||||
}
|
||||
var request = {
|
||||
newEmail: _newEmail,
|
||||
masterPasswordHash: _masterPasswordHash
|
||||
};
|
||||
|
||||
return apiService.accounts.emailToken(request, function () {
|
||||
$scope.tokenSent = true;
|
||||
}).$promise;
|
||||
});
|
||||
};
|
||||
|
||||
function requestToken() {
|
||||
var request = {
|
||||
newEmail: _newEmail,
|
||||
masterPasswordHash: _masterPasswordHash
|
||||
};
|
||||
|
||||
return apiService.accounts.emailToken(request, function () {
|
||||
$scope.tokenSent = true;
|
||||
}).$promise;
|
||||
}
|
||||
|
||||
$scope.confirm = function (model) {
|
||||
$scope.processing = true;
|
||||
|
||||
$scope.confirmPromise = cryptoService.makeKeyAndHash(_newEmail, _masterPassword).then(function (result) {
|
||||
var encKey = cryptoService.getEncKey();
|
||||
var newEncKey = cryptoService.encrypt(encKey.key, result.key, 'raw');
|
||||
@@ -64,9 +54,6 @@
|
||||
return $state.go('frontend.login.info');
|
||||
}).then(function () {
|
||||
toastr.success('Please log back in.', 'Email Changed');
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
toastr.error('Something went wrong. Try again.', 'Oh No!');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsChangePasswordController', function ($scope, $state, apiService, $uibModalInstance,
|
||||
cryptoService, authService, cipherService, validationService, toastr, $analytics) {
|
||||
cryptoService, authService, validationService, toastr, $analytics) {
|
||||
$analytics.eventTrack('settingsChangePasswordController', { category: 'Modal' });
|
||||
|
||||
$scope.save = function (model, form) {
|
||||
var error = false;
|
||||
|
||||
var encKey = cryptoService.getEncKey();
|
||||
if (!encKey) {
|
||||
validationService.addError(form, null,
|
||||
'You cannot change your master password until you update your encryption key.', true);
|
||||
error = true;
|
||||
}
|
||||
|
||||
if ($scope.model.newMasterPassword.length < 8) {
|
||||
validationService.addError(form, 'NewMasterPasswordHash',
|
||||
'Master password must be at least 8 characters long.', true);
|
||||
@@ -23,27 +30,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.processing = true;
|
||||
|
||||
var encKey = cryptoService.getEncKey();
|
||||
if (encKey) {
|
||||
$scope.savePromise = changePassword(model);
|
||||
}
|
||||
else {
|
||||
// User is not using an enc key, let's make them one
|
||||
$scope.savePromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
|
||||
return cipherService.updateKey(hash);
|
||||
}).then(function () {
|
||||
return changePassword(model);
|
||||
}, function (err) {
|
||||
toastr.error('Something went wrong.', 'Oh No!');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function changePassword(model) {
|
||||
var makeResult;
|
||||
return authService.getUserProfile().then(function (profile) {
|
||||
$scope.savePromise = authService.getUserProfile().then(function (profile) {
|
||||
return cryptoService.makeKeyAndHash(profile.email, model.newMasterPassword);
|
||||
}).then(function (result) {
|
||||
makeResult = result;
|
||||
@@ -66,11 +54,8 @@
|
||||
return $state.go('frontend.login.info');
|
||||
}).then(function () {
|
||||
toastr.success('Please log back in.', 'Master Password Changed');
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
toastr.error('Something went wrong.', 'Oh No!');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsCreateOrganizationController', function ($scope, $state, apiService, cryptoService,
|
||||
toastr, $analytics, authService, stripe, constants) {
|
||||
toastr, $analytics, authService, constants, appSettings, validationService
|
||||
// @if !selfHosted
|
||||
, stripe
|
||||
// @endif
|
||||
) {
|
||||
$scope.plans = constants.plans;
|
||||
$scope.storageGb = constants.storageGb;
|
||||
$scope.paymentMethod = 'card';
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
|
||||
$scope.model = {
|
||||
plan: 'free',
|
||||
@@ -27,6 +33,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changePaymentMethod = function (val) {
|
||||
$scope.paymentMethod = val;
|
||||
};
|
||||
|
||||
$scope.changedPlan = function () {
|
||||
if ($scope.plans[$scope.model.plan].hasOwnProperty('monthPlanType')) {
|
||||
$scope.model.interval = 'year';
|
||||
@@ -47,40 +57,75 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.submit = function (model) {
|
||||
var shareKeyCt = cryptoService.makeShareKeyCt();
|
||||
$scope.submit = function (model, form) {
|
||||
var shareKey = cryptoService.makeShareKey();
|
||||
var defaultCollectionCt = cryptoService.encrypt('Default Collection', shareKey.key);
|
||||
|
||||
if (model.plan === 'free') {
|
||||
var freeRequest = {
|
||||
name: model.name,
|
||||
planType: model.plan,
|
||||
key: shareKeyCt,
|
||||
billingEmail: model.billingEmail
|
||||
};
|
||||
if ($scope.selfHosted) {
|
||||
var fileEl = document.getElementById('file');
|
||||
var files = fileEl.files;
|
||||
if (!files || !files.length) {
|
||||
validationService.addError(form, 'file', 'Select a license file.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.submitPromise = apiService.organizations.post(freeRequest).$promise.then(finalizeCreate);
|
||||
var fd = new FormData();
|
||||
fd.append('license', files[0]);
|
||||
fd.append('key', shareKey.ct);
|
||||
fd.append('collectionName', defaultCollectionCt);
|
||||
|
||||
$scope.submitPromise = apiService.organizations.postLicense(fd).$promise.then(finalizeCreate);
|
||||
}
|
||||
else {
|
||||
$scope.submitPromise = stripe.card.createToken(model.card).then(function (response) {
|
||||
var paidRequest = {
|
||||
if (model.plan === 'free') {
|
||||
var freeRequest = {
|
||||
name: model.name,
|
||||
planType: model.interval === 'month' ? $scope.plans[model.plan].monthPlanType :
|
||||
$scope.plans[model.plan].annualPlanType,
|
||||
key: shareKeyCt,
|
||||
paymentToken: response.id,
|
||||
additionalSeats: model.additionalSeats,
|
||||
additionalStorageGb: model.additionalStorageGb,
|
||||
planType: model.plan,
|
||||
key: shareKey.ct,
|
||||
billingEmail: model.billingEmail,
|
||||
businessName: model.ownedBusiness ? model.businessName : null
|
||||
collectionName: defaultCollectionCt
|
||||
};
|
||||
|
||||
return apiService.organizations.post(paidRequest).$promise;
|
||||
}).then(finalizeCreate);
|
||||
$scope.submitPromise = apiService.organizations.post(freeRequest).$promise.then(finalizeCreate);
|
||||
}
|
||||
else {
|
||||
var stripeReq = null;
|
||||
if ($scope.paymentMethod === 'card') {
|
||||
stripeReq = stripe.card.createToken(model.card);
|
||||
}
|
||||
else if ($scope.paymentMethod === 'bank') {
|
||||
model.bank.currency = 'USD';
|
||||
model.bank.country = 'US';
|
||||
stripeReq = stripe.bankAccount.createToken(model.bank);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.submitPromise = stripeReq.then(function (response) {
|
||||
var paidRequest = {
|
||||
name: model.name,
|
||||
planType: model.interval === 'month' ? $scope.plans[model.plan].monthPlanType :
|
||||
$scope.plans[model.plan].annualPlanType,
|
||||
key: shareKey.ct,
|
||||
paymentToken: response.id,
|
||||
additionalSeats: model.additionalSeats,
|
||||
additionalStorageGb: model.additionalStorageGb,
|
||||
billingEmail: model.billingEmail,
|
||||
businessName: model.ownedBusiness ? model.businessName : null,
|
||||
collectionName: defaultCollectionCt
|
||||
};
|
||||
|
||||
return apiService.organizations.post(paidRequest).$promise;
|
||||
}, function (err) {
|
||||
throw err.message;
|
||||
}).then(finalizeCreate);
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeCreate(result) {
|
||||
$analytics.eventTrack('Created Organization');
|
||||
authService.addProfileOrganizationOwner(result, shareKeyCt);
|
||||
authService.addProfileOrganizationOwner(result, shareKey.ct);
|
||||
authService.refreshAccessToken().then(function () {
|
||||
goToOrg(result.Id);
|
||||
}, function () {
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsDeleteController', function ($scope, $state, apiService, $uibModalInstance, cryptoService,
|
||||
authService, toastr, $analytics) {
|
||||
authService, toastr, $analytics, tokenService) {
|
||||
$analytics.eventTrack('settingsDeleteController', { category: 'Modal' });
|
||||
$scope.submit = function (model) {
|
||||
$scope.submitPromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
|
||||
var profile;
|
||||
|
||||
$scope.submitPromise = authService.getUserProfile().then(function (theProfile) {
|
||||
profile = theProfile;
|
||||
return cryptoService.hashPassword(model.masterPassword);
|
||||
}).then(function (hash) {
|
||||
return apiService.accounts.postDelete({
|
||||
masterPasswordHash: hash
|
||||
}).$promise;
|
||||
}).then(function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
authService.logOut();
|
||||
tokenService.clearTwoFactorToken(profile.email);
|
||||
$analytics.eventTrack('Deleted Account');
|
||||
return $state.go('frontend.login.info');
|
||||
}).then(function () {
|
||||
|
||||
@@ -1,36 +1,111 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsPremiumController', function ($scope, $state, apiService, toastr, $analytics, authService, stripe,
|
||||
constants) {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
if (profile.premium) {
|
||||
.controller('settingsPremiumController', function ($scope, $state, apiService, toastr, $analytics, authService,
|
||||
constants, $timeout, appSettings, validationService
|
||||
// @if !selfHosted
|
||||
, stripe
|
||||
// @endif
|
||||
) {
|
||||
var profile = null;
|
||||
|
||||
authService.getUserProfile().then(function (theProfile) {
|
||||
profile = theProfile;
|
||||
if (profile && profile.premium) {
|
||||
return $state.go('backend.user.settingsBilling');
|
||||
}
|
||||
});
|
||||
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
|
||||
var btInstance = null;
|
||||
$scope.storageGbPrice = constants.storageGb.yearlyPrice;
|
||||
$scope.premiumPrice = constants.premium.price;
|
||||
$scope.paymentMethod = 'card';
|
||||
$scope.dropinLoaded = false;
|
||||
|
||||
$scope.model = {
|
||||
additionalStorageGb: null
|
||||
};
|
||||
|
||||
$scope.changePaymentMethod = function (val) {
|
||||
$scope.paymentMethod = val;
|
||||
if ($scope.paymentMethod !== 'paypal') {
|
||||
return;
|
||||
}
|
||||
|
||||
braintree.dropin.create({
|
||||
authorization: appSettings.braintreeKey,
|
||||
container: '#bt-dropin-container',
|
||||
paymentOptionPriority: ['paypal'],
|
||||
paypal: {
|
||||
flow: 'vault',
|
||||
buttonStyle: {
|
||||
label: 'pay',
|
||||
size: 'medium',
|
||||
shape: 'pill',
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
}, function (createErr, instance) {
|
||||
if (createErr) {
|
||||
console.error(createErr);
|
||||
return;
|
||||
}
|
||||
|
||||
btInstance = instance;
|
||||
$timeout(function () {
|
||||
$scope.dropinLoaded = true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.totalPrice = function () {
|
||||
return $scope.premiumPrice + (($scope.model.additionalStorageGb || 0) * $scope.storageGbPrice);
|
||||
};
|
||||
|
||||
$scope.submit = function (model) {
|
||||
$scope.submitPromise = stripe.card.createToken(model.card).then(function (response) {
|
||||
var request = {
|
||||
paymentToken: response.id,
|
||||
additionalStorageGb: model.additionalStorageGb
|
||||
};
|
||||
$scope.submit = function (model, form) {
|
||||
if ($scope.selfHosted) {
|
||||
if (profile && !profile.emailVerified) {
|
||||
validationService.addError(form, null, 'Your account\'s email address first must be verified.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
return apiService.accounts.postPremium(request).$promise;
|
||||
}).then(function (result) {
|
||||
return authService.updateProfilePremium(true);
|
||||
}).then(function () {
|
||||
var fileEl = document.getElementById('file');
|
||||
var files = fileEl.files;
|
||||
if (!files || !files.length) {
|
||||
validationService.addError(form, 'file', 'Select a license file.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('license', files[0]);
|
||||
|
||||
$scope.submitPromise = apiService.accounts.postPremium(fd).$promise.then(function (result) {
|
||||
return finalizePremium();
|
||||
});
|
||||
}
|
||||
else {
|
||||
$scope.submitPromise = getPaymentToken(model).then(function (token) {
|
||||
if (!token) {
|
||||
throw 'No payment token.';
|
||||
}
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('paymentToken', token);
|
||||
fd.append('additionalStorageGb', model.additionalStorageGb || 0);
|
||||
|
||||
return apiService.accounts.postPremium(fd).$promise;
|
||||
}, function (err) {
|
||||
throw err;
|
||||
}).then(function (result) {
|
||||
return finalizePremium();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function finalizePremium() {
|
||||
return authService.updateProfilePremium(true).then(function () {
|
||||
$analytics.eventTrack('Signed Up Premium');
|
||||
return authService.refreshAccessToken();
|
||||
}).then(function () {
|
||||
@@ -38,5 +113,22 @@
|
||||
}).then(function () {
|
||||
toastr.success('Premium upgrade complete.', 'Success');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPaymentToken(model) {
|
||||
if ($scope.paymentMethod === 'paypal') {
|
||||
return btInstance.requestPaymentMethod().then(function (payload) {
|
||||
return payload.nonce;
|
||||
}).catch(function (err) {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
else {
|
||||
return stripe.card.createToken(model.card).then(function (response) {
|
||||
return response.id;
|
||||
}).catch(function (err) {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
$scope.model = {
|
||||
key: formatString(_key),
|
||||
qr: 'https://chart.googleapis.com/chart?chs=120x120&chld=L|0&cht=qr&chl=otpauth://totp/' +
|
||||
qr: 'https://chart.googleapis.com/chart?chs=160x160&chld=L|0&cht=qr&chl=otpauth://totp/' +
|
||||
_issuer + ':' + encodeURIComponent(_profile.email) +
|
||||
'%3Fsecret=' + encodeURIComponent(_key) +
|
||||
'%26issuer=' + _issuer
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
angular
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsUpdateKeyController', function ($scope, $state, apiService, $uibModalInstance,
|
||||
cryptoService, authService, cipherService, validationService, toastr, $analytics) {
|
||||
.controller('settingsUpdateKeyController', function ($scope, $state, apiService, $uibModalInstance, cipherService,
|
||||
cryptoService, authService, validationService, toastr, $analytics, $q) {
|
||||
$analytics.eventTrack('settingsUpdateKeyController', { category: 'Modal' });
|
||||
|
||||
$scope.save = function (form) {
|
||||
@@ -13,27 +13,68 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.processing = true;
|
||||
$scope.savePromise = cryptoService.hashPassword($scope.masterPassword).then(function (hash) {
|
||||
return cipherService.updateKey(hash, function () {
|
||||
return null;
|
||||
}, function () {
|
||||
return null;
|
||||
});
|
||||
return updateKey(hash);
|
||||
}).then(function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
authService.logOut();
|
||||
$analytics.eventTrack('Key Updated');
|
||||
return $state.go('frontend.login.info');
|
||||
}, function (e) {
|
||||
throw e ? e : 'Error occurred.';
|
||||
}).then(function () {
|
||||
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 });
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
toastr.error('Something went wrong.', 'Oh No!');
|
||||
});
|
||||
};
|
||||
|
||||
function updateKey(masterPasswordHash) {
|
||||
var madeEncKey = cryptoService.makeEncKey(null);
|
||||
|
||||
var reencryptedLogins = [];
|
||||
var loginsPromise = apiService.ciphers.list({}, function (encryptedLogins) {
|
||||
var filteredEncryptedLogins = [];
|
||||
for (var i = 0; i < encryptedLogins.Data.length; i++) {
|
||||
if (encryptedLogins.Data[i].OrganizationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredEncryptedLogins.push(encryptedLogins.Data[i]);
|
||||
}
|
||||
|
||||
var unencryptedLogins = cipherService.decryptLogins(filteredEncryptedLogins);
|
||||
reencryptedLogins = cipherService.encryptLogins(unencryptedLogins, madeEncKey.encKey);
|
||||
}).$promise;
|
||||
|
||||
var reencryptedFolders = [];
|
||||
var foldersPromise = apiService.folders.list({}, function (encryptedFolders) {
|
||||
var unencryptedFolders = cipherService.decryptFolders(encryptedFolders.Data);
|
||||
reencryptedFolders = cipherService.encryptFolders(unencryptedFolders, madeEncKey.encKey);
|
||||
}).$promise;
|
||||
|
||||
var privateKey = cryptoService.getPrivateKey('raw'),
|
||||
reencryptedPrivateKey = null;
|
||||
if (privateKey) {
|
||||
reencryptedPrivateKey = cryptoService.encrypt(privateKey, madeEncKey.encKey, 'raw');
|
||||
}
|
||||
|
||||
return $q.all([loginsPromise, foldersPromise]).then(function () {
|
||||
var request = {
|
||||
masterPasswordHash: masterPasswordHash,
|
||||
ciphers: reencryptedLogins,
|
||||
folders: reencryptedFolders,
|
||||
privateKey: reencryptedPrivateKey,
|
||||
key: madeEncKey.encKeyEnc
|
||||
};
|
||||
|
||||
return apiService.accounts.putKey(request).$promise;
|
||||
}, function () {
|
||||
throw 'Error while encrypting data.';
|
||||
}).then(function () {
|
||||
cryptoService.setEncKey(madeEncKey.encKey, null, true);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">General</h3>
|
||||
</div>
|
||||
<form role="form" name="generalForm" ng-submit="generalForm.$valid && generalSave()" api-form="generalPromise">
|
||||
<form role="form" name="generalForm" ng-submit="generalForm.$valid && generalSave()" api-form="generalPromise"
|
||||
autocomplete="off">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
@@ -57,7 +58,7 @@
|
||||
<h3 class="box-title">Master Password</h3>
|
||||
</div>
|
||||
<form role="form" name="masterPasswordForm" ng-submit="masterPasswordForm.$valid && passwordHintSave()"
|
||||
api-form="passwordHintPromise">
|
||||
api-form="passwordHintPromise" autocomplete="off">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-globe"></i> {{index ? 'Edit Equivalent Domain' : 'Add Equivalent Domain'}}</h4>
|
||||
</div>
|
||||
<form name="domainAddEditForm" ng-submit="domainAddEditForm.$valid && submit(domainAddEditForm)">
|
||||
<form name="domainAddEditForm" ng-submit="domainAddEditForm.$valid && submit(domainAddEditForm)" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="domainAddEditForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -21,7 +21,19 @@
|
||||
<h3 class="box-title">Premium Membership</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<dl ng-if="selfHosted">
|
||||
<dt>Expiration</dt>
|
||||
<dd ng-if="loading">
|
||||
Loading...
|
||||
</dd>
|
||||
<dd ng-if="!loading && expiration">
|
||||
{{expiration | date: 'medium'}}
|
||||
</dd>
|
||||
<dd ng-if="!loading && !expiration">
|
||||
Never expires
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="row" ng-if="!selfHosted">
|
||||
<div class="col-md-5">
|
||||
<dl>
|
||||
<dt>Status</dt>
|
||||
@@ -30,7 +42,7 @@
|
||||
<span ng-if="subscription.markedForCancel">- marked for cancellation</span>
|
||||
</dd>
|
||||
<dt>Next Charge</dt>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: format: mediumDate) + ', ' + (nextInvoice.amount | currency:'$')) : '-'}}</dd>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$')) : '-'}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
@@ -54,7 +66,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="box-footer" ng-if="!selfHosted && !loading && subscription &&
|
||||
(!subscription.cancelled || subscription.markedForCancel)">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="cancel()"
|
||||
ng-if="!subscription.cancelled && !subscription.markedForCancel">
|
||||
Cancel
|
||||
@@ -63,9 +76,21 @@
|
||||
ng-if="subscription.markedForCancel">
|
||||
Reinstate
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="license()"
|
||||
ng-if="!subscription.cancelled">
|
||||
Download License
|
||||
</button>
|
||||
</div>
|
||||
<div class="box-footer" ng-if="selfHosted">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="updateLicense()">
|
||||
Update License
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com" class="btn btn-default btn-flat" target="_blank">
|
||||
Manage Membership
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default" ng-if="storage">
|
||||
<div class="box box-default" ng-if="storage && !selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Storage</h3>
|
||||
</div>
|
||||
@@ -82,7 +107,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="box-footer" ng-if="subscription && paymentSource && !subscription.cancelled">
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="adjustStorage(true)">
|
||||
Add Storage
|
||||
</button>
|
||||
@@ -91,7 +116,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box box-default" ng-if="!selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Payment Method</h3>
|
||||
</div>
|
||||
@@ -104,7 +129,7 @@
|
||||
</div>
|
||||
<div ng-show="!loading && paymentSource">
|
||||
<i class="fa" ng-class="{'fa-credit-card': paymentSource.type === 0,
|
||||
'fa-university': paymentSource.type === 1}"></i>
|
||||
'fa-university': paymentSource.type === 1, 'fa-paypal fa-fw text-blue': paymentSource.type === 2}"></i>
|
||||
{{paymentSource.description}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +139,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box box-default" ng-if="!selfHosted">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Charges</h3>
|
||||
</div>
|
||||
@@ -130,7 +155,7 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="charge in charges">
|
||||
<td style="width: 200px">
|
||||
{{charge.date | date: format: mediumDate}}
|
||||
{{charge.date | date: 'mediumDate'}}
|
||||
</td>
|
||||
<td style="min-width: 150px">
|
||||
{{charge.paymentSource}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{add ? 'Add Storage' : 'Remove Storage'}}
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-default" ng-show="add">
|
||||
<h4><i class="fa fa-dollar"></i> Note About Charges</h4>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{existingPaymentMethod ? 'Change Payment Method' : 'Add Payment Method'}}
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
@@ -13,344 +13,413 @@
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="card_number">Card Number</label>
|
||||
<input type="text" id="card_number" name="card_number" ng-model="card.number"
|
||||
class="form-control" cc-number required api-field />
|
||||
<div ng-if="showPaymentOptions">
|
||||
<label class="radio-inline radio-boxed" ng-show="!hideCard">
|
||||
<input type="radio" name="PaymentMethod" value="card" ng-model="paymentMethod"
|
||||
ng-change="changePaymentMethod('card')"><i class="fa fa-fw fa-credit-card"></i> Credit Card
|
||||
</label>
|
||||
<label class="radio-inline radio-boxed" ng-show="!hidePaypal">
|
||||
<input type="radio" name="PaymentMethod" value="paypal" ng-model="paymentMethod"
|
||||
ng-change="changePaymentMethod('paypal')"><i class="fa fa-fw fa-paypal"></i> PayPal
|
||||
</label>
|
||||
<label class="radio-inline radio-boxed" ng-show="!hideBank">
|
||||
<input type="radio" name="PaymentMethod" value="bank" ng-model="paymentMethod"
|
||||
ng-change="changePaymentMethod('bank')"><i class="fa fa-fw fa-bank"></i>
|
||||
Bank<span class="hidden-xs"> Account (ACH)</span>
|
||||
</label>
|
||||
<hr />
|
||||
</div>
|
||||
<div ng-if="paymentMethod === 'paypal'">
|
||||
<div id="bt-dropin-container"></div>
|
||||
</div>
|
||||
<div ng-if="paymentMethod === 'bank'">
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> You must verify your bank account</h4>
|
||||
<p>
|
||||
Payment with a bank account is <u>only available to customers in the United States</u>.
|
||||
You will be required to verify your bank account. We will make two micro-deposits within the next
|
||||
1-2 business days. Enter these amounts in the organization's billing area to verify the bank account.
|
||||
Failure to verify the bank account will result in a missed payment and your organization being
|
||||
disabled.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="routing_number">Routing Number</label>
|
||||
<input type="text" id="routing_number" name="routing_number"
|
||||
ng-model="bank.routing_number" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="account_number">Account Number</label>
|
||||
<input type="text" id="account_number" name="account_number"
|
||||
ng-model="bank.account_number" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="account_holder_name">Account Holder Name</label>
|
||||
<input type="text" id="account_holder_name" name="account_holder_name"
|
||||
ng-model="bank.account_holder_name" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="account_holder_type">Account Type</label>
|
||||
<select id="account_holder_type" class="form-control" name="account_holder_type"
|
||||
ng-model="bank.account_holder_type" required>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="company">Company (Business)</option>
|
||||
<option value="individual">Individual (Personal)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-inline">
|
||||
<li><div class="cc visa"></div></li>
|
||||
<li><div class="cc mastercard"></div></li>
|
||||
<li><div class="cc amex"></div></li>
|
||||
<li><div class="cc discover"></div></li>
|
||||
<li><div class="cc diners"></div></li>
|
||||
<li><div class="cc jcb"></div></li>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_month">Expiration Month</label>
|
||||
<select id="exp_month" class="form-control" ng-model="card.exp_month" required cc-exp-month
|
||||
name="exp_month" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="01">01 - January</option>
|
||||
<option value="02">02 - February</option>
|
||||
<option value="03">03 - March</option>
|
||||
<option value="04">04 - April</option>
|
||||
<option value="05">05 - May</option>
|
||||
<option value="06">06 - June</option>
|
||||
<option value="07">07 - July</option>
|
||||
<option value="08">08 - August</option>
|
||||
<option value="09">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
<div ng-if="paymentMethod === 'card'">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="card_number">Card Number</label>
|
||||
<input type="text" id="card_number" name="card_number" ng-model="card.number"
|
||||
class="form-control" cc-number required api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_year">Expiration Year</label>
|
||||
<select id="exp_year" class="form-control" ng-model="card.exp_year" required cc-exp-year
|
||||
name="exp_year" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="17">2017</option>
|
||||
<option value="18">2018</option>
|
||||
<option value="19">2019</option>
|
||||
<option value="20">2020</option>
|
||||
<option value="21">2021</option>
|
||||
<option value="22">2022</option>
|
||||
<option value="23">2023</option>
|
||||
<option value="24">2024</option>
|
||||
<option value="25">2025</option>
|
||||
<option value="26">2026</option>
|
||||
</select>
|
||||
<ul class="list-inline">
|
||||
<li><div class="cc visa"></div></li>
|
||||
<li><div class="cc mastercard"></div></li>
|
||||
<li><div class="cc amex"></div></li>
|
||||
<li><div class="cc discover"></div></li>
|
||||
<li><div class="cc diners"></div></li>
|
||||
<li><div class="cc jcb"></div></li>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_month">Expiration Month</label>
|
||||
<select id="exp_month" class="form-control" ng-model="card.exp_month" required cc-exp-month
|
||||
name="exp_month" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="01">01 - January</option>
|
||||
<option value="02">02 - February</option>
|
||||
<option value="03">03 - March</option>
|
||||
<option value="04">04 - April</option>
|
||||
<option value="05">05 - May</option>
|
||||
<option value="06">06 - June</option>
|
||||
<option value="07">07 - July</option>
|
||||
<option value="08">08 - August</option>
|
||||
<option value="09">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_year">Expiration Year</label>
|
||||
<select id="exp_year" class="form-control" ng-model="card.exp_year" required cc-exp-year
|
||||
name="exp_year" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="17">2017</option>
|
||||
<option value="18">2018</option>
|
||||
<option value="19">2019</option>
|
||||
<option value="20">2020</option>
|
||||
<option value="21">2021</option>
|
||||
<option value="22">2022</option>
|
||||
<option value="23">2023</option>
|
||||
<option value="24">2024</option>
|
||||
<option value="25">2025</option>
|
||||
<option value="26">2026</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cvc">
|
||||
CVC
|
||||
<a href="https://www.cvvnumber.com/cvv.html" target="_blank" title="What is this?"
|
||||
rel="noopener noreferrer">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" id="cvc" ng-model="card.cvc" class="form-control" name="cvc"
|
||||
cc-type="number.$ccType" cc-cvc required api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cvc">
|
||||
CVC
|
||||
<a href="https://www.cvvnumber.com/cvv.html" target="_blank" title="What is this?"
|
||||
rel="noopener noreferrer">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" id="cvc" ng-model="card.cvc" class="form-control" name="cvc"
|
||||
cc-type="number.$ccType" cc-cvc required api-field />
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_country">Country</label>
|
||||
<select id="address_country" class="form-control" ng-model="card.address_country"
|
||||
required name="address_country" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland Islands</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia, Plurinational State of</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo</option>
|
||||
<option value="CD">Congo, the Democratic Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard Island and McDonald Islands</option>
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran, Islamic Republic of</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, Democratic People's Republic of</option>
|
||||
<option value="KR">Korea, Republic of</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Lao People's Democratic Republic</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macao</option>
|
||||
<option value="MK">Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia, Federated States of</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestinian Territory, Occupied</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Réunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and the South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syrian Arab Republic</option>
|
||||
<option value="TW">Taiwan, Province of China</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania, United Republic of</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela, Bolivarian Republic of</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_country">Country</label>
|
||||
<select id="address_country" class="form-control" ng-model="card.address_country"
|
||||
required name="address_country" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland Islands</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia, Plurinational State of</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo</option>
|
||||
<option value="CD">Congo, the Democratic Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard Island and McDonald Islands</option>
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran, Islamic Republic of</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, Democratic People's Republic of</option>
|
||||
<option value="KR">Korea, Republic of</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Lao People's Democratic Republic</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macao</option>
|
||||
<option value="MK">Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia, Federated States of</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestinian Territory, Occupied</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Réunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and the South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syrian Arab Republic</option>
|
||||
<option value="TW">Taiwan, Province of China</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania, United Republic of</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela, Bolivarian Republic of</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_zip"
|
||||
ng-bind="card.address_country === 'US' ? 'Zip Code' : 'Postal Code'"></label>
|
||||
<input type="text" id="address_zip" ng-model="card.address_zip"
|
||||
class="form-control" required name="address_zip" api-field />
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_zip"
|
||||
ng-bind="card.address_country === 'US' ? 'Zip Code' : 'Postal Code'"></label>
|
||||
<input type="text" id="address_zip" ng-model="card.address_zip"
|
||||
class="form-control" required name="address_zip" api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
30
src/app/settings/views/settingsBillingUpdateLicense.html
Normal file
30
src/app/settings/views/settingsBillingUpdateLicense.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-drivers-license"></i>
|
||||
Update License
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(form)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group" show-error>
|
||||
<label for="file" class="sr-only">License</label>
|
||||
<input type="file" id="file" name="file" accept=".json" />
|
||||
<p class="help-block">
|
||||
Select your <code>.json</code> license file.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -2,7 +2,8 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="changeEmailModelLabel"><i class="fa fa-at"></i> Change Email</h4>
|
||||
</div>
|
||||
<form name="changeEmailForm" ng-submit="changeEmailForm.$valid && token(model)" api-form="tokenPromise" ng-show="!tokenSent && !processing">
|
||||
<form name="changeEmailForm" ng-submit="changeEmailForm.$valid && token(model, changeEmailForm)" api-form="tokenPromise"
|
||||
ng-show="!tokenSent">
|
||||
<div class="modal-body">
|
||||
<p>Below you can change your account's email address.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="changeEmailForm.$errors">
|
||||
@@ -29,12 +30,19 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="changeEmailConfirmForm" ng-submit="changeEmailConfirmForm.$valid && confirm(model)" api-form="confirmPromise"
|
||||
ng-show="tokenSent && !processing">
|
||||
ng-show="tokenSent" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>We have emailed a verification code to <b>{{model.newEmail}}</b>. Please check your email for this code and enter it below to finalize your the email address change.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will log you out of your current session, requiring you to log back in.
|
||||
Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices
|
||||
may continue to remain active for up to one hour.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="changeEmailConfirmForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in changeEmailConfirmForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="token">Code</label>
|
||||
@@ -42,13 +50,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">
|
||||
Change Email
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="changeEmailConfirmForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="changeEmailConfirmForm.$loading"></i>Change Email
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="processing" class="modal-body text-center">
|
||||
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||
<p>Please wait. We are now changing your email and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="changePasswrdModelLabel"><i class="fa fa-key"></i> Change Password</h4>
|
||||
</div>
|
||||
<form name="changePasswordForm" ng-submit="changePasswordForm.$valid && save(model, changePasswordForm)" api-form="savePromise" ng-show="!processing">
|
||||
<form name="changePasswordForm" ng-submit="changePasswordForm.$valid && save(model, changePasswordForm)" api-form="savePromise">
|
||||
<div class="modal-body">
|
||||
<p>Below you can change your account's master password.</p>
|
||||
<p>We recommend that you change your master password immediately if you believe that your credentials have been compromised.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will log you out of your current session, requiring you to log back in.
|
||||
Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices
|
||||
may continue to remain active for up to one hour.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="changePasswordForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
@@ -34,13 +35,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">
|
||||
Change Password
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="changePasswordForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="changePasswordForm.$loading"></i>Change Password
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="processing" class="modal-body text-center">
|
||||
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||
<p>Please wait. We are now changing your password and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
|
||||
"Global" domains are ones already created for you by bitwarden.
|
||||
</p>
|
||||
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise">
|
||||
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise" autocomplete="off">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Custom <span class="hidden-xs">Equivalent Domains</span></h3>
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="globalForm" ng-submit="globalForm.$valid && saveGlobal()" api-form="globalPromise">
|
||||
<form name="globalForm" ng-submit="globalForm.$valid && saveGlobal()" api-form="globalPromise" autocomplete="off">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Global <span class="hidden-xs">Equivalent Domains</span></h3>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h1>Premium<span class="hidden-xs"> Membership</span><small>get started today!</small></h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit(model, form)" api-form="submitPromise">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
<ul>
|
||||
@@ -43,401 +43,444 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Addons</h3>
|
||||
<div class="box-footer" ng-if="selfHosted">
|
||||
<a href="https://vault.bitwarden.com/#/?premium=purchase" class="btn btn-primary btn-flat" target="_blank">
|
||||
Purchase Premium
|
||||
</a>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group" show-errors style="margin: 0;">
|
||||
<label for="additionalStorage">Storage</label>
|
||||
<p>
|
||||
Your plan comes with 1 GB of encrypted file storage. You can add additional
|
||||
storage for {{storageGbPrice | currency:"$":0}} per GB /year.
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input type="number" id="additionalStorage" name="AdditionalStorageGb"
|
||||
ng-model="model.additionalStorageGb" min="0" max="99" step="1" class="form-control"
|
||||
placeholder="# of additional GB" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="selfHosted">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">License</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>To upgrade your account to a premium membership you need to upload a valid license file.</p>
|
||||
<div class="form-group" show-error>
|
||||
<label for="file" class="sr-only">License</label>
|
||||
<input type="file" id="file" name="file" accept=".json" />
|
||||
<p class="help-block">
|
||||
Your license file will be named something like <code>bitwarden_premium_license.json</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Billing Summary</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
Premium membership:
|
||||
{{premiumPrice | currency:"$"}}<br />
|
||||
Additional storage:
|
||||
{{model.additionalStorageGb || 0}} GB × {{storageGbPrice | currency:"$"}} =
|
||||
{{(model.additionalStorageGb || 0) * storageGbPrice | currency:"$"}}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<h4>
|
||||
<b>Total:</b>
|
||||
{{totalPrice() | currency:"USD $"}} /year
|
||||
</h4>
|
||||
Your card will be charged immediately and on a recurring basis each year. You may cancel at any time.
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Payment Information</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="card_number">Card Number</label>
|
||||
<input type="text" id="card_number" name="card_number" ng-model="model.card.number"
|
||||
class="form-control" cc-number required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<br class="hidden-sm hidden-xs" />
|
||||
<ul class="list-inline" style="margin: 0;">
|
||||
<li><div class="cc visa"></div></li>
|
||||
<li><div class="cc mastercard"></div></li>
|
||||
<li><div class="cc amex"></div></li>
|
||||
<li><div class="cc discover"></div></li>
|
||||
<li><div class="cc diners"></div></li>
|
||||
<li><div class="cc jcb"></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div ng-if="!selfHosted">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Addons</h3>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_month">Expiration Month</label>
|
||||
<select id="exp_month" class="form-control" ng-model="model.card.exp_month" required cc-exp-month
|
||||
name="exp_month" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="01">01 - January</option>
|
||||
<option value="02">02 - February</option>
|
||||
<option value="03">03 - March</option>
|
||||
<option value="04">04 - April</option>
|
||||
<option value="05">05 - May</option>
|
||||
<option value="06">06 - June</option>
|
||||
<option value="07">07 - July</option>
|
||||
<option value="08">08 - August</option>
|
||||
<option value="09">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_year">Expiration Year</label>
|
||||
<select id="exp_year" class="form-control" ng-model="model.card.exp_year" required cc-exp-year
|
||||
name="exp_year" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="17">2017</option>
|
||||
<option value="18">2018</option>
|
||||
<option value="19">2019</option>
|
||||
<option value="20">2020</option>
|
||||
<option value="21">2021</option>
|
||||
<option value="22">2022</option>
|
||||
<option value="23">2023</option>
|
||||
<option value="24">2024</option>
|
||||
<option value="25">2025</option>
|
||||
<option value="26">2026</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cvc">
|
||||
CVC
|
||||
<a href="https://www.cvvnumber.com/cvv.html" target="_blank" title="What is this?"
|
||||
rel="noopener noreferrer">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" id="cvc" ng-model="model.card.cvc" class="form-control" name="cvc"
|
||||
cc-type="number.$ccType" cc-cvc required api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_country">Country</label>
|
||||
<select id="address_country" class="form-control" ng-model="model.card.address_country"
|
||||
required name="address_country" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland Islands</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia, Plurinational State of</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo</option>
|
||||
<option value="CD">Congo, the Democratic Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard Island and McDonald Islands</option>
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran, Islamic Republic of</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, Democratic People's Republic of</option>
|
||||
<option value="KR">Korea, Republic of</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Lao People's Democratic Republic</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macao</option>
|
||||
<option value="MK">Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia, Federated States of</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestinian Territory, Occupied</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Réunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and the South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syrian Arab Republic</option>
|
||||
<option value="TW">Taiwan, Province of China</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania, United Republic of</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela, Bolivarian Republic of</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_zip"
|
||||
ng-bind="model.card.address_country === 'US' ? 'Zip Code' : 'Postal Code'"></label>
|
||||
<input type="text" id="address_zip" ng-model="model.card.address_zip"
|
||||
class="form-control" required name="address_zip" api-field />
|
||||
<div class="box-body">
|
||||
<div class="form-group" show-errors style="margin: 0;">
|
||||
<label for="additionalStorage">Storage</label>
|
||||
<p>
|
||||
Your plan comes with 1 GB of encrypted file storage. You can add additional
|
||||
storage for {{storageGbPrice | currency:"$":0}} per GB /year.
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input type="number" id="additionalStorage" name="AdditionalStorageGb"
|
||||
ng-model="model.additionalStorageGb" min="0" max="99" step="1" class="form-control"
|
||||
placeholder="# of additional GB" api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Billing Summary</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
Premium membership:
|
||||
{{premiumPrice | currency:"$"}}<br />
|
||||
Additional storage:
|
||||
{{model.additionalStorageGb || 0}} GB × {{storageGbPrice | currency:"$"}} =
|
||||
{{(model.additionalStorageGb || 0) * storageGbPrice | currency:"$"}}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<h4>
|
||||
<b>Total:</b>
|
||||
{{totalPrice() | currency:"USD $"}} /year
|
||||
</h4>
|
||||
Your card will be charged immediately and on a recurring basis each year. You may cancel at any time.
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Payment Information</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<label class="radio-inline radio-lg radio-boxed">
|
||||
<input type="radio" name="PaymentMethod" value="card" ng-model="paymentMethod"
|
||||
ng-change="changePaymentMethod('card')"><i class="fa fa-fw fa-credit-card"></i> Credit Card
|
||||
</label>
|
||||
<label class="radio-inline radio-lg radio-boxed">
|
||||
<input type="radio" name="PaymentMethod" value="paypal" ng-model="paymentMethod"
|
||||
ng-change="changePaymentMethod('paypal')"><i class="fa fa-fw fa-paypal"></i> PayPal
|
||||
</label>
|
||||
<hr />
|
||||
<div ng-if="paymentMethod === 'paypal'">
|
||||
<div id="bt-dropin-container"></div>
|
||||
</div>
|
||||
<div ng-if="paymentMethod === 'card'">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="card_number">Card Number</label>
|
||||
<input type="text" id="card_number" name="card_number" ng-model="model.card.number"
|
||||
class="form-control" cc-number required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<br class="hidden-sm hidden-xs" />
|
||||
<ul class="list-inline" style="margin: 0;">
|
||||
<li><div class="cc visa"></div></li>
|
||||
<li><div class="cc mastercard"></div></li>
|
||||
<li><div class="cc amex"></div></li>
|
||||
<li><div class="cc discover"></div></li>
|
||||
<li><div class="cc diners"></div></li>
|
||||
<li><div class="cc jcb"></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_month">Expiration Month</label>
|
||||
<select id="exp_month" class="form-control" ng-model="model.card.exp_month" required cc-exp-month
|
||||
name="exp_month" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="01">01 - January</option>
|
||||
<option value="02">02 - February</option>
|
||||
<option value="03">03 - March</option>
|
||||
<option value="04">04 - April</option>
|
||||
<option value="05">05 - May</option>
|
||||
<option value="06">06 - June</option>
|
||||
<option value="07">07 - July</option>
|
||||
<option value="08">08 - August</option>
|
||||
<option value="09">09 - September</option>
|
||||
<option value="10">10 - October</option>
|
||||
<option value="11">11 - November</option>
|
||||
<option value="12">12 - December</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="exp_year">Expiration Year</label>
|
||||
<select id="exp_year" class="form-control" ng-model="model.card.exp_year" required cc-exp-year
|
||||
name="exp_year" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="17">2017</option>
|
||||
<option value="18">2018</option>
|
||||
<option value="19">2019</option>
|
||||
<option value="20">2020</option>
|
||||
<option value="21">2021</option>
|
||||
<option value="22">2022</option>
|
||||
<option value="23">2023</option>
|
||||
<option value="24">2024</option>
|
||||
<option value="25">2025</option>
|
||||
<option value="26">2026</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="cvc">
|
||||
CVC
|
||||
<a href="https://www.cvvnumber.com/cvv.html" target="_blank" title="What is this?"
|
||||
rel="noopener noreferrer">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" id="cvc" ng-model="model.card.cvc" class="form-control" name="cvc"
|
||||
cc-type="number.$ccType" cc-cvc required api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_country">Country</label>
|
||||
<select id="address_country" class="form-control" ng-model="model.card.address_country"
|
||||
required name="address_country" api-field>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland Islands</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia, Plurinational State of</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo</option>
|
||||
<option value="CD">Congo, the Democratic Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard Island and McDonald Islands</option>
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran, Islamic Republic of</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, Democratic People's Republic of</option>
|
||||
<option value="KR">Korea, Republic of</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Lao People's Democratic Republic</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macao</option>
|
||||
<option value="MK">Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia, Federated States of</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestinian Territory, Occupied</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Réunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and the South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syrian Arab Republic</option>
|
||||
<option value="TW">Taiwan, Province of China</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania, United Republic of</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela, Bolivarian Republic of</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="address_zip"
|
||||
ng-bind="model.card.address_country === 'US' ? 'Zip Code' : 'Postal Code'"></label>
|
||||
<input type="text" id="address_zip" ng-model="model.card.address_zip"
|
||||
class="form-control" required name="address_zip" api-field />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted
|
||||
for two-step login again, if enabled.
|
||||
for two-step login again, if enabled. Active sessions on other devices may continue to remain active for up to
|
||||
one hour.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="logoutSessionsForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit(updateModel)" api-form="submitPromise"
|
||||
ng-if="model">
|
||||
ng-if="model" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div ng-if="enabled">
|
||||
<div class="callout callout-success">
|
||||
@@ -74,10 +74,10 @@
|
||||
<hr ng-if="enabled" />
|
||||
<h4 ng-if="!enabled" style="margin-top: 30px;">2. Scan this QR code with your authenticator app</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center">
|
||||
<p><img ng-src="{{model.qr}}" alt="QR" class="img-thumbnail" /></p>
|
||||
<div class="col-sm-4 text-center">
|
||||
<p><img ng-src="{{model.qr}}" alt="QR" /></p>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="col-sm-8">
|
||||
<p>
|
||||
<strong>Can't scan the code?</strong> You can add the code to your application manually using the
|
||||
following details:
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit(updateModel)" api-form="submitPromise"
|
||||
ng-if="authed">
|
||||
ng-if="authed" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div ng-if="enabled">
|
||||
<div class="callout callout-success">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit(updateModel)" api-form="submitPromise"
|
||||
ng-if="authed">
|
||||
ng-if="authed" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div ng-if="enabled">
|
||||
<div class="callout callout-success">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit()" api-form="submitPromise"
|
||||
ng-if="authed">
|
||||
ng-if="authed" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit(updateModel)" api-form="submitPromise"
|
||||
ng-if="authed">
|
||||
ng-if="authed" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-key"></i> Update Encryption Key</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save(form)" api-form="savePromise" ng-show="!processing">
|
||||
<form name="form" ng-submit="form.$valid && save(form)" api-form="savePromise">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This is <b>NOT</b> a security notification indicating that anything is wrong or has been compromised on your
|
||||
@@ -39,16 +39,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">
|
||||
Update Key
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="form.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Update Key
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="processing" class="modal-body text-center">
|
||||
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||
<p>
|
||||
Please wait. We are now generating a new encryption key and reencrypting all of your data. Do not close this window.
|
||||
You will be automatically logged out when this process has finished.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
angular
|
||||
.module('bit.tools')
|
||||
|
||||
.controller('toolsExportController', function ($scope, apiService, authService, $uibModalInstance, cryptoService,
|
||||
cipherService, $q, toastr, $analytics) {
|
||||
.controller('toolsExportController', function ($scope, apiService, $uibModalInstance, cipherService, $q,
|
||||
toastr, $analytics) {
|
||||
$analytics.eventTrack('toolsExportController', { category: 'Modal' });
|
||||
$scope.export = function (model) {
|
||||
$scope.startedExport = true;
|
||||
@@ -13,7 +13,7 @@
|
||||
decFolders = cipherService.decryptFolders(folders.Data);
|
||||
}).$promise;
|
||||
|
||||
var loginsPromise = apiService.logins.list({}, function (logins) {
|
||||
var loginsPromise = apiService.ciphers.list({}, function (logins) {
|
||||
decLogins = cipherService.decryptLogins(logins.Data);
|
||||
}).$promise;
|
||||
|
||||
@@ -41,9 +41,23 @@
|
||||
folder: decLogins[i].folderId && (decLogins[i].folderId in foldersDict) ?
|
||||
foldersDict[decLogins[i].folderId].name : null,
|
||||
favorite: decLogins[i].favorite ? 1 : null,
|
||||
totp: decLogins[i].totp
|
||||
totp: decLogins[i].totp,
|
||||
fields: null
|
||||
};
|
||||
|
||||
if (decLogins[i].fields) {
|
||||
for (var j = 0; j < decLogins[i].fields.length; j++) {
|
||||
if (!login.fields) {
|
||||
login.fields = '';
|
||||
}
|
||||
else {
|
||||
login.fields += '\n';
|
||||
}
|
||||
|
||||
login.fields += ((decLogins[i].fields[j].name || '') + ': ' + decLogins[i].fields[j].value);
|
||||
}
|
||||
}
|
||||
|
||||
exportLogins.push(login);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
$analytics.eventTrack('toolsImportController', { category: 'Modal' });
|
||||
$scope.model = { source: '' };
|
||||
$scope.source = {};
|
||||
$scope.splitFeatured = true;
|
||||
|
||||
$scope.options = [
|
||||
{
|
||||
@@ -287,7 +288,7 @@
|
||||
|
||||
apiService.ciphers.import({
|
||||
folders: cipherService.encryptFolders(folders),
|
||||
logins: cipherService.encryptLogins(logins),
|
||||
ciphers: cipherService.encryptLogins(logins),
|
||||
folderRelationships: folderRelationships
|
||||
}, function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<option value="">-- Select --</option>
|
||||
<option ng-repeat="option in options | filter: { featured: true } | orderBy: ['sort', 'name']"
|
||||
value="{{option.id}}">{{option.name}}</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="-" disabled ng-if="splitFeatured"></option>
|
||||
<option ng-repeat="option in options | filter: { featured: '!true' } | orderBy: ['sort', 'name']"
|
||||
value="{{option.id}}">{{option.name}}</option>
|
||||
</select>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function (model) {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.logins.post(login, function (loginResponse) {
|
||||
$scope.savePromise = apiService.ciphers.post(login, function (loginResponse) {
|
||||
$analytics.eventTrack('Created Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close(decLogin);
|
||||
@@ -31,6 +31,29 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFavorite = function () {
|
||||
$scope.login.favorite = !$scope.login.favorite;
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
e.clearSelection();
|
||||
selectPassword(e);
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.isPremium = profile.premium;
|
||||
return apiService.logins.get({ id: loginId }).$promise;
|
||||
}).then(function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.readOnly = !login.Edit;
|
||||
return apiService.ciphers.get({ id: loginId }).$promise;
|
||||
}).then(function (cipher) {
|
||||
$scope.login = cipherService.decryptLogin(cipher);
|
||||
$scope.readOnly = !$scope.login.edit;
|
||||
$scope.canUseAttachments = $scope.isPremium || $scope.login.organizationId;
|
||||
$scope.loading = false;
|
||||
}, function () {
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.logins.del({ id: login.id }, function () {
|
||||
apiService.ciphers.del({ id: login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Login');
|
||||
removeLoginFromScopes(login);
|
||||
});
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
$scope.useTotp = profile.premium;
|
||||
return apiService.logins.get({ id: loginId }).$promise;
|
||||
return apiService.ciphers.get({ id: loginId }).$promise;
|
||||
}).then(function (login) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
$scope.readOnly = !login.Edit;
|
||||
$scope.readOnly = !$scope.login.edit;
|
||||
$scope.useTotp = $scope.useTotp || $scope.login.organizationUseTotp;
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
else {
|
||||
var login = cipherService.encryptLogin(model);
|
||||
$scope.savePromise = apiService.logins.put({ id: loginId }, login, function (loginResponse) {
|
||||
$scope.savePromise = apiService.ciphers.put({ id: loginId }, login, function (loginResponse) {
|
||||
$analytics.eventTrack('Edited Login');
|
||||
var decLogin = cipherService.decryptLogin(loginResponse);
|
||||
$uibModalInstance.close({
|
||||
@@ -54,6 +54,29 @@
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addField = function () {
|
||||
if (!$scope.login.fields) {
|
||||
$scope.login.fields = [];
|
||||
}
|
||||
|
||||
$scope.login.fields.push({
|
||||
type: '0',
|
||||
name: null,
|
||||
value: null
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeField = function (field) {
|
||||
var index = $scope.login.fields.indexOf(field);
|
||||
if (index > -1) {
|
||||
$scope.login.fields.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFavorite = function () {
|
||||
$scope.login.favorite = !$scope.login.favorite;
|
||||
};
|
||||
|
||||
$scope.clipboardSuccess = function (e) {
|
||||
e.clearSelection();
|
||||
selectPassword(e);
|
||||
@@ -86,7 +109,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.logins.del({ id: $scope.login.id }, function () {
|
||||
apiService.ciphers.del({ id: $scope.login.id }, function () {
|
||||
$analytics.eventTrack('Deleted Login From Edit');
|
||||
$uibModalInstance.close({
|
||||
action: 'delete',
|
||||
|
||||
@@ -24,28 +24,34 @@
|
||||
var collections = {};
|
||||
if (cipher.CollectionIds) {
|
||||
for (var i = 0; i < cipher.CollectionIds.length; i++) {
|
||||
collections[cipher.CollectionIds[i]] = true;
|
||||
collections[cipher.CollectionIds[i]] = null;
|
||||
}
|
||||
}
|
||||
$scope.selectedCollections = collections;
|
||||
|
||||
return cipher;
|
||||
return {
|
||||
cipher: cipher,
|
||||
cipherCollections: collections
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}).then(function (cipher) {
|
||||
if (!cipher) {
|
||||
}).then(function (cipherAndCols) {
|
||||
if (!cipherAndCols) {
|
||||
$scope.loadingCollections = false;
|
||||
return;
|
||||
}
|
||||
|
||||
apiService.collections.listMe(function (response) {
|
||||
apiService.collections.listMe({ writeOnly: true }, function (response) {
|
||||
var collections = [];
|
||||
var selectedCollections = {};
|
||||
|
||||
for (var i = 0; i < response.Data.length; i++) {
|
||||
if (response.Data[i].OrganizationId !== cipher.OrganizationId || response.Data[i].ReadOnly) {
|
||||
if (response.Data[i].Id in $scope.selectedCollections) {
|
||||
delete $scope.selectedCollections[response.Data[i].Id];
|
||||
}
|
||||
// clean out selectCollections that aren't from this organization or read only
|
||||
if (response.Data[i].Id in cipherAndCols.cipherCollections &&
|
||||
response.Data[i].OrganizationId === cipherAndCols.cipher.OrganizationId) {
|
||||
selectedCollections[response.Data[i].Id] = true;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -55,6 +61,7 @@
|
||||
|
||||
$scope.loadingCollections = false;
|
||||
$scope.collections = collections;
|
||||
$scope.selectedCollections = selectedCollections;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
$scope.loading = true;
|
||||
$scope.readOnly = false;
|
||||
|
||||
apiService.logins.get({ id: loginId }).$promise.then(function (login) {
|
||||
apiService.ciphers.get({ id: loginId }).$promise.then(function (login) {
|
||||
$scope.readOnly = !login.Edit;
|
||||
if (login.Edit) {
|
||||
$scope.login = cipherService.decryptLogin(login);
|
||||
@@ -51,13 +51,9 @@
|
||||
|
||||
$scope.organizations = orgs;
|
||||
|
||||
apiService.collections.listMe(function (response) {
|
||||
apiService.collections.listMe({ writeOnly: true }, function (response) {
|
||||
var collections = [];
|
||||
for (var i = 0; i < response.Data.length; i++) {
|
||||
if (response.Data[i].ReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var decCollection = cipherService.decryptCollection(response.Data[i]);
|
||||
decCollection.organizationId = response.Data[i].OrganizationId;
|
||||
collections.push(decCollection);
|
||||
@@ -74,8 +70,7 @@
|
||||
var collections = {};
|
||||
if ($event.target.checked) {
|
||||
for (var i = 0; i < $scope.collections.length; i++) {
|
||||
if ($scope.model.organizationId && $scope.collections[i].organizationId === $scope.model.organizationId &&
|
||||
!$scope.collections[i].readOnly) {
|
||||
if ($scope.model.organizationId && $scope.collections[i].organizationId === $scope.model.organizationId) {
|
||||
collections[$scope.collections[i].id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
$scope.loading = true;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
var collectionPromise = apiService.collections.listMe({}, function (collections) {
|
||||
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
|
||||
var decCollections = [];
|
||||
|
||||
for (var i = 0; i < collections.Data.length; i++) {
|
||||
@@ -150,7 +150,7 @@
|
||||
login.folderId = rootLogin.folderId = returnVal.data.folderId;
|
||||
login.name = rootLogin.name = returnVal.data.name;
|
||||
login.username = rootLogin.username = returnVal.data.username;
|
||||
login.password = rootLogin.username = returnVal.data.password;
|
||||
login.password = rootLogin.password = returnVal.data.password;
|
||||
login.favorite = rootLogin.favorite = returnVal.data.favorite;
|
||||
}
|
||||
else if (returnVal.action === 'partialEdit') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addFolderModelLabel"><i class="fa fa-folder"></i> Add New Folder</h4>
|
||||
</div>
|
||||
<form name="addFolderForm" ng-submit="addFolderForm.$valid && save(folder)" api-form="savePromise">
|
||||
<form name="addFolderForm" ng-submit="addFolderForm.$valid && save(folder)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="addFolderForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addLoginModelLabel"><i class="fa fa-globe"></i> Add New Login</h4>
|
||||
</div>
|
||||
<form name="addLoginForm" ng-submit="addLoginForm.$valid && save(login)" api-form="savePromise">
|
||||
<form name="addLoginForm" ng-submit="addLoginForm.$valid && save(login)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="addLoginForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
@@ -11,13 +11,13 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="login.name" class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" ng-if="!hideFolders">
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="login.folderId" class="form-control" api-field>
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
||||
@@ -80,14 +80,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Totp" ng-model="login.totp" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 totp-col">
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
@@ -99,17 +99,73 @@
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="login.notes" api-field></textarea>
|
||||
</div>
|
||||
<div class="checkbox" ng-if="!hideFavorite">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="login.favorite" name="Favorite" />
|
||||
Favorite
|
||||
</label>
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
<div ng-repeat="field in login.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" name="Field.Name{{$index}}" class="form-control" ng-model="field.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" name="Field.Type{{$index}}" class="form-control" ng-model="field.type">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
name="Field.Value{{$index}}" class="form-control" ng-model="field.value" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" name="Field.Value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click>
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="addLoginForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="addLoginForm.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="login.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<i class="fa fa-paperclip"></i> Secure Attachments <small>{{login.name}}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save(form)" api-form="savePromise">
|
||||
<form name="form" ng-submit="form.$valid && save(form)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div ng-if="loading">
|
||||
Loading...
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="editFolderModelLabel"><i class="fa fa-folder"></i> Edit Folder <small>{{folder.name}}</small></h4>
|
||||
</div>
|
||||
<form name="editFolderForm" ng-submit="editFolderForm.$valid && save(folder)" api-form="savePromise">
|
||||
<form name="editFolderForm" ng-submit="editFolderForm.$valid && save(folder)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="editFolderForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<i class="fa fa-globe"></i> Login Information <small>{{login.name}}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<form name="editLoginForm" ng-submit="editLoginForm.$valid && save(login)" api-form="savePromise">
|
||||
<form name="editLoginForm" ng-submit="editLoginForm.$valid && save(login)" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="editLoginForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
@@ -13,14 +13,14 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="name">Name</label> <span>*</span>
|
||||
<input type="text" id="name" name="Name" ng-model="login.name" class="form-control"
|
||||
ng-readonly="readOnly" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" ng-if="!hideFolders">
|
||||
<div class="col-sm-6" ng-if="!hideFolders">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="folder">Folder</label>
|
||||
<select id="folder" name="FolderId" ng-model="login.folderId" class="form-control" api-field>
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="username">Username</label>
|
||||
<div class="input-group">
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors style="margin-bottom: 5px;">
|
||||
<div class="pull-right password-options">
|
||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left"
|
||||
@@ -92,14 +92,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="totp">Authenticator Key (TOTP)</label>
|
||||
<input type="text" id="totp" name="Totp" ng-model="login.totp" class="form-control"
|
||||
ng-readonly="readOnly" api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 totp-col">
|
||||
<div class="col-sm-6 totp-col">
|
||||
<div totp="login.totp" id="verification-code" ng-if="useTotp"></div>
|
||||
<div ng-if="!useTotp">
|
||||
<a href="#" stop-click ng-click="showUpgrade()"><img src="images/totp-countdown.png" alt="" /></a>
|
||||
@@ -112,20 +112,81 @@
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="login.notes"
|
||||
ng-readonly="readOnly" api-field></textarea>
|
||||
</div>
|
||||
<div class="checkbox" ng-if="!hideFavorite">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="login.favorite" name="Favorite" />
|
||||
Favorite
|
||||
</label>
|
||||
<div ng-if="!readOnly || (login.fields && login.fields.length)">
|
||||
<hr />
|
||||
<h4><i class="fa fa-list-ul"></i> Custom Fields</h4>
|
||||
</div>
|
||||
<div ng-repeat="field in login.fields">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_name{{$index}}">Name</label>
|
||||
<input type="text" id="field_name{{$index}}" class="form-control" ng-model="field.name"
|
||||
ng-readonly="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="form-group">
|
||||
<label for="field_type{{$index}}">Type</label>
|
||||
<select id="field_type{{$index}}" class="form-control" ng-model="field.type" ng-disabled="readOnly">
|
||||
<option value="0">Text</option>
|
||||
<option value="1">Hidden</option>
|
||||
<option value="2">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="form-group">
|
||||
<div class="pull-right password-options" ng-if="field.type === '1'">
|
||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Visibility" tooltip-placement="left"
|
||||
password-viewer="#field_value{{$index}}"></i>
|
||||
</div>
|
||||
<label for="field_value{{$index}}">Value</label>
|
||||
<div class="input-group" ng-if="field.type !== '2'">
|
||||
<input ng-attr-type="{{field.type === '0' ? 'text' : 'password'}}" id="field_value{{$index}}"
|
||||
class="form-control" ng-model="field.value" ng-readonly="readOnly" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Value" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
data-clipboard-text="{{field.value}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="field.type === '2'">
|
||||
<input type="checkbox" id="field_value{{$index}}" ng-model="field.value"
|
||||
data-ng-true-value="'true'" ng-disabled="readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addField()" stop-click ng-if="!readOnly">
|
||||
<i class="fa fa-plus-circle"></i> New Custom Field
|
||||
</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editLoginForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editLoginForm.$loading"></i>Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete" ng-disabled="readOnly">
|
||||
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete"
|
||||
tooltip-placement="left" ng-disabled="readOnly">
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
<span class="sr-only">Delete</span>
|
||||
</button>
|
||||
<button type="button" ng-if="!hideFavorite" class="btn btn-link pull-right" ng-click="toggleFavorite()"
|
||||
uib-tooltip="Toggle Favorite" tooltip-placement="left">
|
||||
<i class="fa fa-lg" ng-class="login.favorite ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<span class="sr-only">Toggle Favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-cubes"></i> Collections <small>{{login.name}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit()" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Edit the collections that this login is being shared with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<i class="fa fa-share"></i> Move Logins
|
||||
</h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && save()" api-form="savePromise">
|
||||
<form name="form" ng-submit="form.$valid && save()" api-form="savePromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><i class="fa fa-share-alt"></i> Share Login <small>{{login.name}}</small></h4>
|
||||
</div>
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise">
|
||||
<form name="form" ng-submit="form.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
||||
<div class="modal-body">
|
||||
<p>Choose an organization that you wish to share this login with.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
<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> Chrome
|
||||
<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> Firefox
|
||||
<i class="fa fa-firefox fa-lg fa-fw fa-li"></i> Mozilla Firefox
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -29,9 +29,8 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-edge fa-lg fa-fw fa-li"></i> Edge
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
<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>
|
||||
@@ -51,7 +50,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
|
||||
Tor
|
||||
Tor Browser
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</li>
|
||||
<li ng-class="{active: $state.is('backend.org.billing')}" ng-if="isOrgOwner(orgProfile)">
|
||||
<a ui-sref="backend.org.billing({orgId: params.orgId})">
|
||||
<i class="fa fa-credit-card fa-fw"></i> <span>Billing</span>
|
||||
<i class="fa fa-credit-card fa-fw"></i> Billing & Licensing
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: $state.is('backend.org.settings')}" ng-if="isOrgOwner(orgProfile)">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user