mirror of
https://github.com/bitwarden/web
synced 2025-12-21 18:53:38 +00:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb4132458 | ||
|
|
443822fd52 | ||
|
|
68427fd2de | ||
|
|
c3d3369601 | ||
|
|
3c5022d628 | ||
|
|
832ddddc58 | ||
|
|
0fc1415a06 | ||
|
|
1ab408c591 | ||
|
|
3160d3f275 | ||
|
|
d083f1ddc3 | ||
|
|
5fbc09b135 | ||
|
|
6282fabf98 | ||
|
|
2b528bad97 | ||
|
|
c3be8195fd | ||
|
|
39471d0421 | ||
|
|
7a50c0536c | ||
|
|
4ccd9501a8 | ||
|
|
75c05a4a85 | ||
|
|
ca7e12370f | ||
|
|
8bc9dafff2 | ||
|
|
dcb0416fd6 | ||
|
|
bbb69bba26 | ||
|
|
c1838b48ff | ||
|
|
d53f40002c | ||
|
|
866954b180 | ||
|
|
befa9cbf08 | ||
|
|
859f44db43 | ||
|
|
cca9c3c561 | ||
|
|
27e68e4c75 | ||
|
|
5c92350ed2 | ||
|
|
b94c62d1e5 | ||
|
|
de888d8a37 | ||
|
|
f8d6816101 | ||
|
|
119c6d5817 | ||
|
|
aaa21daa29 | ||
|
|
10f41bf288 | ||
|
|
91582691d8 | ||
|
|
463efc2254 | ||
|
|
0333354271 | ||
|
|
b85f56c681 | ||
|
|
be491be2cd | ||
|
|
4be4a8115d | ||
|
|
c0eb499f4d | ||
|
|
1b43f3facd | ||
|
|
26d41d3cb9 | ||
|
|
179765f6e4 | ||
|
|
df2e332134 | ||
|
|
2952f9d158 | ||
|
|
3c9face597 | ||
|
|
25f2e9c1b7 | ||
|
|
a6f8e1b9a3 | ||
|
|
d832031cec | ||
|
|
7a1a3ab64d | ||
|
|
19491a684e | ||
|
|
757224287e | ||
|
|
c9b5426f6f | ||
|
|
bf885c184f | ||
|
|
0d2bf4f7a1 | ||
|
|
01ffc68fc2 | ||
|
|
16892239fb | ||
|
|
d5765d8814 | ||
|
|
8d6a96074d | ||
|
|
f54884eb79 | ||
|
|
828149b2d6 | ||
|
|
501c4fc263 | ||
|
|
1d0b45e17d | ||
|
|
a0f7ed68fb | ||
|
|
7bd0c17188 | ||
|
|
1ea9d28523 | ||
|
|
8a3fb92bbe | ||
|
|
de3a9b9903 | ||
|
|
9834f3d2aa | ||
|
|
ac079b9d88 | ||
|
|
9e96906f32 | ||
|
|
90c079e743 | ||
|
|
4ecf307285 | ||
|
|
6cf4c453d9 | ||
|
|
d2899d14c7 | ||
|
|
f3b438d514 | ||
|
|
2997f694f8 | ||
|
|
b78ab4db27 | ||
|
|
37dddea515 | ||
|
|
e307d1e87d | ||
|
|
62e1dbb642 | ||
|
|
b8a425f530 | ||
|
|
cafb6fa694 | ||
|
|
0482ddea2c | ||
|
|
b411176c8d | ||
|
|
2f13449cb6 | ||
|
|
b0c1b7b683 | ||
|
|
7e8978c7fc | ||
|
|
d58b422bd0 | ||
|
|
3563601382 | ||
|
|
d42e6ca3fd | ||
|
|
7f0d8c99e3 | ||
|
|
48a67dc2b3 | ||
|
|
8d0b42492d | ||
|
|
e4076e95dd | ||
|
|
30a2b878f6 | ||
|
|
e17f94a67d | ||
|
|
4dd60c3844 | ||
|
|
9d76990f24 | ||
|
|
ed3d15f075 | ||
|
|
2c36a2aa96 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -199,5 +199,4 @@ FakesAssemblies/
|
|||||||
*.opt
|
*.opt
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
package-lock.json
|
|
||||||
src/js/*.min.js
|
src/js/*.min.js
|
||||||
15
Dockerfile
15
Dockerfile
@@ -1,10 +1,17 @@
|
|||||||
FROM bitwarden/server
|
FROM bitwarden/server
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
gosu \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV ASPNETCORE_URLS http://+:5000
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
EXPOSE 5000
|
||||||
COPY ./dist .
|
COPY ./dist .
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
COPY entrypoint.sh /
|
COPY entrypoint.sh /
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
RUN groupadd -g 999 bitwarden \
|
||||||
|
&& chmod +x /entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
5
ISSUE_TEMPLATE.md
Normal file
5
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<!--
|
||||||
|
Please do not submit feature requests. The [Community Forums][1] has a
|
||||||
|
section for submitting, voting for, and discussing product feature requests.
|
||||||
|
[1]: https://community.bitwarden.com
|
||||||
|
-->
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[](https://ci.appveyor.com/project/bitwarden/web) [](https://gitter.im/bitwarden/Lobby)
|
[](https://ci.appveyor.com/project/bitwarden/web) [](https://hub.docker.com/u/bitwarden/) [](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# bitwarden Web
|
# Bitwarden Web Vault
|
||||||
|
|
||||||
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
The Bitwarden web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
||||||
|
|
||||||
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />
|
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />
|
||||||
|
|
||||||
|
|||||||
16
SECURITY.md
16
SECURITY.md
@@ -1,4 +1,4 @@
|
|||||||
bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
||||||
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
||||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
# In-scope
|
# In-scope
|
||||||
|
|
||||||
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
|
||||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
||||||
code is available at https://github.com/bitwarden.
|
code is available at https://github.com/bitwarden.
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
The following bug classes are out-of scope:
|
The following bug classes are out-of scope:
|
||||||
|
|
||||||
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
|
||||||
or that we already know of. Note that some of our issue tracking is private.
|
or that we already know of. Note that some of our issue tracking is private.
|
||||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
||||||
upstream maintainer.
|
upstream maintainer.
|
||||||
- Attacks requiring physical access to a user's device.
|
- Attacks requiring physical access to a user's device.
|
||||||
- Self-XSS
|
- Self-XSS
|
||||||
- Issues related to software or protocols not under bitwarden's control
|
- Issues related to software or protocols not under Bitwarden's control
|
||||||
- Vulnerabilities in outdated versions of bitwarden
|
- Vulnerabilities in outdated versions of Bitwarden
|
||||||
- Missing security best practices that do not directly lead to a vulnerability
|
- Missing security best practices that do not directly lead to a vulnerability
|
||||||
- Issues that do not have any impact on the general public
|
- Issues that do not have any impact on the general public
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
|
|||||||
|
|
||||||
- Denial of service
|
- Denial of service
|
||||||
- Spamming
|
- Spamming
|
||||||
- Social engineering (including phishing) of bitwarden staff or contractors
|
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||||
- Any physical attempts against bitwarden property or data centers
|
- Any physical attempts against Bitwarden property or data centers
|
||||||
|
|
||||||
Thank you for helping keep bitwarden and our users safe!
|
Thank you for helping keep Bitwarden and our users safe!
|
||||||
|
|||||||
2
dist/.publish
vendored
2
dist/.publish
vendored
Submodule dist/.publish updated: 856b11bf95...66eacea870
@@ -1,5 +1,28 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
|
USERNAME="bitwarden"
|
||||||
|
NOUSER=`id -u $USERNAME > /dev/null 2>&1; echo $?`
|
||||||
|
LUID=${LOCAL_UID:-999}
|
||||||
|
|
||||||
|
# Step down from host root
|
||||||
|
if [ $LUID == 0 ]
|
||||||
|
then
|
||||||
|
LUID=999
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $NOUSER == 0 ] && [ `id -u $USERNAME` != $LUID ]
|
||||||
|
then
|
||||||
|
usermod -u $LUID $USERNAME
|
||||||
|
elif [ $NOUSER == 1 ]
|
||||||
|
then
|
||||||
|
useradd -r -u $LUID -g $USERNAME $USERNAME
|
||||||
|
fi
|
||||||
|
|
||||||
|
chown -R $USERNAME:$USERNAME /etc/bitwarden
|
||||||
cp /etc/bitwarden/web/settings.js /app/js/settings.js
|
cp /etc/bitwarden/web/settings.js /app/js/settings.js
|
||||||
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
||||||
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false
|
chown -R $USERNAME:$USERNAME /app
|
||||||
|
chown -R $USERNAME:$USERNAME /bitwarden_server
|
||||||
|
|
||||||
|
gosu $USERNAME:$USERNAME dotnet /bitwarden_server/Server.dll \
|
||||||
|
/contentRoot=/app /webRoot=. /serveUnknown=false
|
||||||
|
|||||||
27
gulpfile.js
27
gulpfile.js
@@ -12,6 +12,7 @@ var gulp = require('gulp'),
|
|||||||
ngAnnotate = require('gulp-ng-annotate'),
|
ngAnnotate = require('gulp-ng-annotate'),
|
||||||
preprocess = require('gulp-preprocess'),
|
preprocess = require('gulp-preprocess'),
|
||||||
runSequence = require('run-sequence'),
|
runSequence = require('run-sequence'),
|
||||||
|
jeditor = require("gulp-json-editor"),
|
||||||
merge = require('merge-stream'),
|
merge = require('merge-stream'),
|
||||||
ngConfig = require('gulp-ng-config'),
|
ngConfig = require('gulp-ng-config'),
|
||||||
settings = require('./settings.json'),
|
settings = require('./settings.json'),
|
||||||
@@ -71,12 +72,13 @@ gulp.task('min:js', ['clean:js'], function () {
|
|||||||
'!' + paths.minJs,
|
'!' + paths.minJs,
|
||||||
'!' + paths.jsDir + 'fallback*.js',
|
'!' + paths.jsDir + 'fallback*.js',
|
||||||
'!' + paths.jsDir + 'u2f-connector.js',
|
'!' + paths.jsDir + 'u2f-connector.js',
|
||||||
|
'!' + paths.jsDir + 'duo-connector.js',
|
||||||
'!' + paths.jsDir + 'duo.js',
|
'!' + paths.jsDir + 'duo.js',
|
||||||
'!' + paths.jsDir + 'settings.js'
|
'!' + paths.jsDir + 'settings.js'
|
||||||
], { base: '.' })
|
], { base: '.' })
|
||||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(concat(paths.concatJsDest))
|
.pipe(concat(paths.concatJsDest))
|
||||||
.pipe(uglify())
|
//.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,6 +337,10 @@ gulp.task('dist:move', function () {
|
|||||||
src: paths.jsDir + 'duo.js',
|
src: paths.jsDir + 'duo.js',
|
||||||
dest: paths.dist + 'js'
|
dest: paths.dist + 'js'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: paths.jsDir + 'duo-connector.js',
|
||||||
|
dest: paths.dist + 'js'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
src: paths.jsDir + 'settings.js',
|
src: paths.jsDir + 'settings.js',
|
||||||
dest: paths.dist + 'js'
|
dest: paths.dist + 'js'
|
||||||
@@ -351,6 +357,7 @@ gulp.task('dist:move', function () {
|
|||||||
paths.webroot + 'u2f-connector.html',
|
paths.webroot + 'u2f-connector.html',
|
||||||
paths.webroot + 'duo-connector.html',
|
paths.webroot + 'duo-connector.html',
|
||||||
paths.webroot + 'favicon.ico',
|
paths.webroot + 'favicon.ico',
|
||||||
|
paths.webroot + 'manifest.json',
|
||||||
paths.webroot + 'app-id.json'
|
paths.webroot + 'app-id.json'
|
||||||
],
|
],
|
||||||
dest: paths.dist
|
dest: paths.dist
|
||||||
@@ -389,7 +396,7 @@ gulp.task('dist:js:app', function () {
|
|||||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(concat(paths.dist + '/js/app.min.js'))
|
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||||
.pipe(ngAnnotate())
|
.pipe(ngAnnotate())
|
||||||
.pipe(uglify())
|
//.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,7 +408,7 @@ gulp.task('dist:js:fallback', function () {
|
|||||||
|
|
||||||
merge(mainStream)
|
merge(mainStream)
|
||||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(uglify())
|
//.pipe(uglify())
|
||||||
.pipe(rename({ suffix: '.min' }))
|
.pipe(rename({ suffix: '.min' }))
|
||||||
.pipe(gulp.dest(paths.dist + 'js'));
|
.pipe(gulp.dest(paths.dist + 'js'));
|
||||||
});
|
});
|
||||||
@@ -414,7 +421,7 @@ gulp.task('dist:js:u2f', function () {
|
|||||||
|
|
||||||
merge(mainStream)
|
merge(mainStream)
|
||||||
.pipe(concat(paths.dist + '/js/u2f.min.js'))
|
.pipe(concat(paths.dist + '/js/u2f.min.js'))
|
||||||
.pipe(uglify())
|
//.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -429,7 +436,7 @@ gulp.task('dist:js:lib', function () {
|
|||||||
'!' + paths.libDir + 'jquery/**/*'
|
'!' + paths.libDir + 'jquery/**/*'
|
||||||
])
|
])
|
||||||
.pipe(concat(paths.dist + '/js/lib.min.js'))
|
.pipe(concat(paths.dist + '/js/lib.min.js'))
|
||||||
.pipe(uglify())
|
//.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,10 +449,16 @@ gulp.task('dist:preprocess', function () {
|
|||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:version', function () {
|
||||||
|
gulp.src(paths.webroot + 'version.json').pipe(jeditor({
|
||||||
|
'version': project.version
|
||||||
|
})).pipe(gulp.dest(paths.dist));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('dist', ['build'], function (cb) {
|
gulp.task('dist', ['build'], function (cb) {
|
||||||
return runSequence(
|
return runSequence(
|
||||||
'dist:clean',
|
'dist:clean',
|
||||||
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f'],
|
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f', 'dist:version'],
|
||||||
'dist:preprocess',
|
'dist:preprocess',
|
||||||
cb);
|
cb);
|
||||||
});
|
});
|
||||||
@@ -465,7 +478,7 @@ gulp.task('deploy-preview', ['dist'], function () {
|
|||||||
return gulp.src(paths.dist + '**/*')
|
return gulp.src(paths.dist + '**/*')
|
||||||
.pipe(ghPages({
|
.pipe(ghPages({
|
||||||
cacheDir: paths.dist + '.publish',
|
cacheDir: paths.dist + '.publish',
|
||||||
remoteUrl: 'git@github.com:kspearrin/bitwarden-web-preview.git'
|
remoteUrl: 'git@github.com:bitwarden/web-preview.git'
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
9469
package-lock.json
generated
Normal file
9469
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "bitwarden",
|
"name": "bitwarden",
|
||||||
"version": "1.19.0",
|
"version": "1.25.0",
|
||||||
"env": "Production",
|
"env": "Production",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"connect": "3.6.3",
|
"connect": "3.6.5",
|
||||||
"lodash": "4.17.4",
|
"lodash": "4.17.4",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-concat": "2.6.1",
|
"gulp-concat": "2.6.1",
|
||||||
@@ -11,41 +11,42 @@
|
|||||||
"gulp-less": "3.3.2",
|
"gulp-less": "3.3.2",
|
||||||
"gulp-rename": "1.2.2",
|
"gulp-rename": "1.2.2",
|
||||||
"gulp-uglify": "3.0.0",
|
"gulp-uglify": "3.0.0",
|
||||||
"gulp-gh-pages": "0.5.4",
|
"gulp-gh-pages": "git+https://github.com/tekd/gulp-gh-pages.git#update-dependency",
|
||||||
"gulp-preprocess": "2.0.0",
|
"gulp-preprocess": "2.0.0",
|
||||||
"gulp-ng-annotate": "2.0.0",
|
"gulp-ng-annotate": "2.0.0",
|
||||||
"gulp-ng-config": "1.4.0",
|
"gulp-ng-config": "1.5.0",
|
||||||
"gulp-connect": "5.0.0",
|
"gulp-connect": "5.0.0",
|
||||||
|
"gulp-json-editor": "2.2.2",
|
||||||
"jshint": "2.9.5",
|
"jshint": "2.9.5",
|
||||||
"gulp-jshint": "2.0.4",
|
"gulp-jshint": "2.0.4",
|
||||||
"rimraf": "2.6.1",
|
"rimraf": "2.6.2",
|
||||||
"run-sequence": "2.1.0",
|
"run-sequence": "2.2.0",
|
||||||
"merge-stream": "1.0.1",
|
"merge-stream": "1.0.1",
|
||||||
"jquery": "2.2.4",
|
"jquery": "3.2.1",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"bootstrap": "3.3.7",
|
"bootstrap": "3.3.7",
|
||||||
"angular": "1.6.6",
|
"angular": "1.6.7",
|
||||||
"angular-resource": "1.6.6",
|
"angular-resource": "1.6.7",
|
||||||
"angular-sanitize": "1.6.6",
|
"angular-sanitize": "1.6.7",
|
||||||
"angular-ui-bootstrap": "2.5.0",
|
"angular-ui-bootstrap": "2.5.6",
|
||||||
"angular-ui-router": "0.4.2",
|
"angular-ui-router": "0.4.2",
|
||||||
"angular-jwt": "0.1.9",
|
"angular-jwt": "0.1.9",
|
||||||
"angular-cookies": "1.6.6",
|
"angular-cookies": "1.6.7",
|
||||||
"admin-lte": "2.3.11",
|
"admin-lte": "2.3.11",
|
||||||
"angular-toastr": "2.1.1",
|
"angular-toastr": "2.1.1",
|
||||||
"angular-bootstrap-show-errors": "2.3.0",
|
"angular-bootstrap-show-errors": "2.3.0",
|
||||||
"angular-messages": "1.6.6",
|
"angular-messages": "1.6.7",
|
||||||
"ngstorage": "0.3.11",
|
"ngstorage": "0.3.11",
|
||||||
"papaparse": "4.3.5",
|
"papaparse": "4.3.6",
|
||||||
"clipboard": "1.7.1",
|
"clipboard": "1.7.1",
|
||||||
"ngclipboard": "1.1.1",
|
"ngclipboard": "1.1.2",
|
||||||
"angulartics": "1.4.0",
|
"angulartics": "1.5.0",
|
||||||
"angulartics-google-analytics": "0.4.0",
|
"angulartics-google-analytics": "0.4.0",
|
||||||
"node-forge": "0.7.1",
|
"node-forge": "0.7.1",
|
||||||
"webpack-stream": "4.0.0",
|
"webpack-stream": "4.0.0",
|
||||||
"angular-stripe": "5.0.0",
|
"angular-stripe": "5.0.0",
|
||||||
"angular-credit-cards": "3.1.6",
|
"angular-credit-cards": "3.1.6",
|
||||||
"browserify": "14.4.0",
|
"browserify": "14.5.0",
|
||||||
"vinyl-source-stream": "1.1.0",
|
"vinyl-source-stream": "1.1.0",
|
||||||
"gulp-derequire": "2.1.0",
|
"gulp-derequire": "2.1.0",
|
||||||
"exposify": "0.5.0",
|
"exposify": "0.5.0",
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
{
|
{
|
||||||
"appSettings": {
|
"appSettings": {
|
||||||
"apiUri": "https://preview-api.bitwarden.com",
|
"apiUri": "/api",
|
||||||
"identityUri": "https://preview-identity.bitwarden.com",
|
"identityUri": "/identity",
|
||||||
"iconsUri": "https://icons.bitwarden.com",
|
"iconsUri": "https://icons.bitwarden.com",
|
||||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2"
|
||||||
"whitelistDomains": [
|
|
||||||
"preview-api.bitwarden.com"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
{
|
{
|
||||||
"appSettings": {
|
"appSettings": {
|
||||||
"apiUri": "https://api.bitwarden.com",
|
"apiUri": "/api",
|
||||||
"identityUri": "https://identity.bitwarden.com",
|
"identityUri": "/identity",
|
||||||
"iconsUri": "https://icons.bitwarden.com",
|
"iconsUri": "https://icons.bitwarden.com",
|
||||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd"
|
||||||
"whitelistDomains": [
|
|
||||||
"api.bitwarden.com"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
"identityUri": "http://localhost:33656",
|
"identityUri": "http://localhost:33656",
|
||||||
"iconsUri": "https://icons.bitwarden.com",
|
"iconsUri": "https://icons.bitwarden.com",
|
||||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2"
|
||||||
"whitelistDomains": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<p class="login-box-msg">
|
<p class="login-box-msg">
|
||||||
Complete logging in with YubiKey.
|
Complete logging in with YubiKey.
|
||||||
</p>
|
</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">
|
autocomplete="off">
|
||||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
<h4>Errors have occurred</h4>
|
<h4>Errors have occurred</h4>
|
||||||
@@ -63,7 +63,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="code" class="sr-only">Token</label>
|
<label for="code" class="sr-only">Token</label>
|
||||||
<input type="password" id="code" name="Token" class="form-control" ng-model="token" required api-field />
|
<input type="password" id="code" name="Token" class="form-control" ng-model="token"
|
||||||
|
autocomplete="new-password" required api-field />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-7">
|
<div class="col-xs-7">
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
<p class="login-box-msg">
|
<p class="login-box-msg">
|
||||||
Complete logging in with Duo.
|
Complete logging in with Duo.
|
||||||
</p>
|
</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">
|
autocomplete="off">
|
||||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
<h4>Errors have occurred</h4>
|
<h4>Errors have occurred</h4>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<p class="text-center"><strong>{{state.params.email}}</strong></p>
|
<p class="text-center"><strong>{{state.params.email}}</strong></p>
|
||||||
<p>
|
<p>
|
||||||
You've been invited to join the organization listed above.
|
You've been invited to join the organization listed above.
|
||||||
To accept the invitation, you need to log in or create a new bitwarden account.
|
To accept the invitation, you need to log in or create a new Bitwarden account.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
</div>
|
</div>
|
||||||
<div class="login-box-body">
|
<div class="login-box-body">
|
||||||
<p class="login-box-msg">Enter your email address below to recover & delete your bitwarden account.</p>
|
<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 ng-show="success" class="text-center">
|
||||||
<div class="callout callout-success">
|
<div class="callout callout-success">
|
||||||
If your account exists ({{model.email}}) we've sent you an email with further instructions.
|
If your account exists ({{model.email}}) we've sent you an email with further instructions.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
This will permanently delete your account. This cannot be undone.
|
This will permanently delete your account. This cannot be undone.
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
You have requested to delete your bitwarden account (<b>{{email}}</b>).
|
You have requested to delete your Bitwarden account (<b>{{email}}</b>).
|
||||||
Click the button below to confirm and proceed.
|
Click the button below to confirm and proceed.
|
||||||
</p>
|
</p>
|
||||||
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>
|
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit')
|
.module('bit')
|
||||||
|
|
||||||
.factory('apiInterceptor', function ($injector, $q, toastr) {
|
.factory('apiInterceptor', function ($injector, $q, toastr, appSettings, utilsService) {
|
||||||
return {
|
return {
|
||||||
request: function (config) {
|
request: function (config) {
|
||||||
|
if (config.url.indexOf(appSettings.apiUri + '/') === 0) {
|
||||||
|
config.headers['Device-Type'] = utilsService.getDeviceType();
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
response: function (response) {
|
response: function (response) {
|
||||||
|
|||||||
@@ -14,27 +14,13 @@ angular
|
|||||||
$qProvider.errorOnUnhandledRejections(false);
|
$qProvider.errorOnUnhandledRejections(false);
|
||||||
$locationProvider.hashPrefix('');
|
$locationProvider.hashPrefix('');
|
||||||
|
|
||||||
var jwtConfig = {
|
jwtOptionsProvider.config({
|
||||||
whiteListedDomains: appSettings.whitelistDomains
|
whiteListedDomains: ['localhost', 'api.bitwarden.com', 'vault.bitwarden.com', 'haveibeenpwned.com']
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!appSettings.selfHosted) {
|
|
||||||
var userAgent = navigator.userAgent.toLowerCase();
|
|
||||||
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
|
|
||||||
// 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
|
|
||||||
jwtConfig.urlParam = 'access_token';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
|
|
||||||
jwtConfig.authHeader = 'Content-Language';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jwtOptionsProvider.config(jwtConfig);
|
|
||||||
var refreshPromise;
|
var refreshPromise;
|
||||||
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
|
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
|
||||||
if (options.url.indexOf(appSettings.apiUri) !== 0) {
|
if (options.url.indexOf(appSettings.apiUri + '/') !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +65,6 @@ angular
|
|||||||
appendToBody: true
|
appendToBody: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($httpProvider.defaults.headers.post) {
|
|
||||||
$httpProvider.defaults.headers.post = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
$httpProvider.defaults.headers.post['Content-Type'] = 'text/plain; charset=utf-8';
|
|
||||||
|
|
||||||
// stop IE from caching get requests
|
// stop IE from caching get requests
|
||||||
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
||||||
if (!$httpProvider.defaults.headers.get) {
|
if (!$httpProvider.defaults.headers.get) {
|
||||||
@@ -124,12 +104,6 @@ angular
|
|||||||
refreshFromServer: false
|
refreshFromServer: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('backend.user.shared', {
|
|
||||||
url: '^/shared',
|
|
||||||
templateUrl: 'app/vault/views/vaultShared.html',
|
|
||||||
controller: 'vaultSharedController',
|
|
||||||
data: { pageTitle: 'Shared' }
|
|
||||||
})
|
|
||||||
.state('backend.user.settings', {
|
.state('backend.user.settings', {
|
||||||
url: '^/settings',
|
url: '^/settings',
|
||||||
templateUrl: 'app/settings/views/settings.html',
|
templateUrl: 'app/settings/views/settings.html',
|
||||||
@@ -178,12 +152,6 @@ angular
|
|||||||
controller: 'reportsBreachController',
|
controller: 'reportsBreachController',
|
||||||
data: { pageTitle: 'Data Breach Report' }
|
data: { pageTitle: 'Data Breach Report' }
|
||||||
})
|
})
|
||||||
.state('backend.user.apps', {
|
|
||||||
url: '^/apps',
|
|
||||||
templateUrl: 'app/views/apps.html',
|
|
||||||
controller: 'appsController',
|
|
||||||
data: { pageTitle: 'Get the Apps' }
|
|
||||||
})
|
|
||||||
.state('backend.org', {
|
.state('backend.org', {
|
||||||
templateUrl: 'app/views/organizationLayout.html',
|
templateUrl: 'app/views/organizationLayout.html',
|
||||||
abstract: true
|
abstract: true
|
||||||
@@ -195,13 +163,13 @@ angular
|
|||||||
data: { pageTitle: 'Organization Dashboard' }
|
data: { pageTitle: 'Organization Dashboard' }
|
||||||
})
|
})
|
||||||
.state('backend.org.people', {
|
.state('backend.org.people', {
|
||||||
url: '/organization/:orgId/people',
|
url: '/organization/:orgId/people?viewEvents&search',
|
||||||
templateUrl: 'app/organization/views/organizationPeople.html',
|
templateUrl: 'app/organization/views/organizationPeople.html',
|
||||||
controller: 'organizationPeopleController',
|
controller: 'organizationPeopleController',
|
||||||
data: { pageTitle: 'Organization People' }
|
data: { pageTitle: 'Organization People' }
|
||||||
})
|
})
|
||||||
.state('backend.org.collections', {
|
.state('backend.org.collections', {
|
||||||
url: '/organization/:orgId/collections',
|
url: '/organization/:orgId/collections?search',
|
||||||
templateUrl: 'app/organization/views/organizationCollections.html',
|
templateUrl: 'app/organization/views/organizationCollections.html',
|
||||||
controller: 'organizationCollectionsController',
|
controller: 'organizationCollectionsController',
|
||||||
data: { pageTitle: 'Organization Collections' }
|
data: { pageTitle: 'Organization Collections' }
|
||||||
@@ -219,17 +187,26 @@ angular
|
|||||||
data: { pageTitle: 'Organization Billing' }
|
data: { pageTitle: 'Organization Billing' }
|
||||||
})
|
})
|
||||||
.state('backend.org.vault', {
|
.state('backend.org.vault', {
|
||||||
url: '/organization/:orgId/vault',
|
url: '/organization/:orgId/vault?viewEvents&search',
|
||||||
templateUrl: 'app/organization/views/organizationVault.html',
|
templateUrl: 'app/organization/views/organizationVault.html',
|
||||||
controller: 'organizationVaultController',
|
controller: 'organizationVaultController',
|
||||||
data: { pageTitle: 'Organization Vault' }
|
data: {
|
||||||
|
pageTitle: 'Organization Vault',
|
||||||
|
controlSidebar: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.state('backend.org.groups', {
|
.state('backend.org.groups', {
|
||||||
url: '/organization/:orgId/groups',
|
url: '/organization/:orgId/groups?search',
|
||||||
templateUrl: 'app/organization/views/organizationGroups.html',
|
templateUrl: 'app/organization/views/organizationGroups.html',
|
||||||
controller: 'organizationGroupsController',
|
controller: 'organizationGroupsController',
|
||||||
data: { pageTitle: 'Organization Groups' }
|
data: { pageTitle: 'Organization Groups' }
|
||||||
})
|
})
|
||||||
|
.state('backend.org.events', {
|
||||||
|
url: '/organization/:orgId/events',
|
||||||
|
templateUrl: 'app/organization/views/organizationEvents.html',
|
||||||
|
controller: 'organizationEventsController',
|
||||||
|
data: { pageTitle: 'Organization Events' }
|
||||||
|
})
|
||||||
|
|
||||||
// Frontend
|
// Frontend
|
||||||
.state('frontend', {
|
.state('frontend', {
|
||||||
@@ -375,7 +352,7 @@ angular
|
|||||||
// user is guaranteed to be authenticated becuase of previous check
|
// user is guaranteed to be authenticated becuase of previous check
|
||||||
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
|
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
|
||||||
// clear vault rootScope when visiting org admin section
|
// clear vault rootScope when visiting org admin section
|
||||||
$rootScope.vaultCiphers = $rootScope.vaultFolders = null;
|
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
|
||||||
|
|
||||||
authService.getUserProfile().then(function (profile) {
|
authService.getUserProfile().then(function (profile) {
|
||||||
var orgs = profile.organizations;
|
var orgs = profile.organizations;
|
||||||
|
|||||||
@@ -39,6 +39,60 @@ angular.module('bit')
|
|||||||
hidden: 1,
|
hidden: 1,
|
||||||
boolean: 2
|
boolean: 2
|
||||||
},
|
},
|
||||||
|
deviceType: {
|
||||||
|
android: 0,
|
||||||
|
ios: 1,
|
||||||
|
chromeExt: 2,
|
||||||
|
firefoxExt: 3,
|
||||||
|
operaExt: 4,
|
||||||
|
edgeExt: 5,
|
||||||
|
windowsDesktop: 6,
|
||||||
|
macOsDesktop: 7,
|
||||||
|
linuxDesktop: 8,
|
||||||
|
chrome: 9,
|
||||||
|
firefox: 10,
|
||||||
|
opera: 11,
|
||||||
|
edge: 12,
|
||||||
|
ie: 13,
|
||||||
|
unknown: 14,
|
||||||
|
uwp: 16,
|
||||||
|
safari: 17,
|
||||||
|
vivaldi: 18,
|
||||||
|
vivaldiExt: 19
|
||||||
|
},
|
||||||
|
eventType: {
|
||||||
|
User_LoggedIn: 1000,
|
||||||
|
User_ChangedPassword: 1001,
|
||||||
|
User_Enabled2fa: 1002,
|
||||||
|
User_Disabled2fa: 1003,
|
||||||
|
User_Recovered2fa: 1004,
|
||||||
|
User_FailedLogIn: 1005,
|
||||||
|
User_FailedLogIn2fa: 1006,
|
||||||
|
|
||||||
|
Cipher_Created: 1100,
|
||||||
|
Cipher_Updated: 1101,
|
||||||
|
Cipher_Deleted: 1102,
|
||||||
|
Cipher_AttachmentCreated: 1103,
|
||||||
|
Cipher_AttachmentDeleted: 1104,
|
||||||
|
Cipher_Shared: 1105,
|
||||||
|
Cipher_UpdatedCollections: 1106,
|
||||||
|
|
||||||
|
Collection_Created: 1300,
|
||||||
|
Collection_Updated: 1301,
|
||||||
|
Collection_Deleted: 1302,
|
||||||
|
|
||||||
|
Group_Created: 1400,
|
||||||
|
Group_Updated: 1401,
|
||||||
|
Group_Deleted: 1402,
|
||||||
|
|
||||||
|
OrganizationUser_Invited: 1500,
|
||||||
|
OrganizationUser_Confirmed: 1501,
|
||||||
|
OrganizationUser_Updated: 1502,
|
||||||
|
OrganizationUser_Removed: 1503,
|
||||||
|
OrganizationUser_UpdatedGroups: 1504,
|
||||||
|
|
||||||
|
Organization_Updated: 1600
|
||||||
|
},
|
||||||
twoFactorProviderInfo: [
|
twoFactorProviderInfo: [
|
||||||
{
|
{
|
||||||
type: 0,
|
type: 0,
|
||||||
@@ -106,14 +160,12 @@ angular.module('bit')
|
|||||||
noPayment: true,
|
noPayment: true,
|
||||||
upgradeSortOrder: -1
|
upgradeSortOrder: -1
|
||||||
},
|
},
|
||||||
personal: {
|
families: {
|
||||||
basePrice: 1,
|
basePrice: 1,
|
||||||
annualBasePrice: 12,
|
annualBasePrice: 12,
|
||||||
baseSeats: 5,
|
baseSeats: 5,
|
||||||
seatPrice: 1,
|
noAdditionalSeats: true,
|
||||||
annualSeatPrice: 12,
|
annualPlanType: 'familiesAnnually',
|
||||||
maxAdditionalSeats: 5,
|
|
||||||
annualPlanType: 'personalAnnually',
|
|
||||||
upgradeSortOrder: 1
|
upgradeSortOrder: 1
|
||||||
},
|
},
|
||||||
teams: {
|
teams: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ angular
|
|||||||
link: function (scope, element) {
|
link: function (scope, element) {
|
||||||
var listener = function (event, toState, toParams, fromState, fromParams) {
|
var listener = function (event, toState, toParams, fromState, fromParams) {
|
||||||
// Default title
|
// Default title
|
||||||
var title = 'bitwarden Web Vault';
|
var title = 'Bitwarden Web Vault';
|
||||||
if (toState.data && toState.data.pageTitle) {
|
if (toState.data && toState.data.pageTitle) {
|
||||||
title = toState.data.pageTitle + ' - ' + title;
|
title = toState.data.pageTitle + ' - ' + title;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
angular
|
|
||||||
.module('bit.global')
|
|
||||||
|
|
||||||
.controller('appsController', function ($scope, $state) {
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -51,14 +51,6 @@ angular
|
|||||||
$state.go('backend.org.dashboard', { orgId: org.id });
|
$state.go('backend.org.dashboard', { orgId: org.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.searchVault = function () {
|
|
||||||
$state.go('backend.user.vault');
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.searchOrganizationVault = function () {
|
|
||||||
$state.go('backend.org.vault', { orgId: $state.params.orgId });
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.isOrgOwner = function (org) {
|
$scope.isOrgOwner = function (org) {
|
||||||
return org && org.type === constants.orgUserType.owner;
|
return org && org.type === constants.orgUserType.owner;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.adjustSeats = function (add) {
|
$scope.adjustSeats = function (add) {
|
||||||
if ($scope.selfHosted) {
|
if ($scope.selfHosted || !$scope.canAdjustSeats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +221,7 @@
|
|||||||
apiService.organizations.getBilling({ id: $state.params.orgId }, function (org) {
|
apiService.organizations.getBilling({ id: $state.params.orgId }, function (org) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$scope.noSubscription = org.PlanType === 0;
|
$scope.noSubscription = org.PlanType === 0;
|
||||||
|
$scope.canAdjustSeats = org.PlanType > 1;
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
$scope.expiration = org.Expiration;
|
$scope.expiration = org.Expiration;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationCollectionsController', function ($scope, $state, apiService, $uibModal, cipherService, $filter,
|
.controller('organizationCollectionsController', function ($scope, $state, apiService, $uibModal, cipherService, $filter,
|
||||||
toastr, $analytics) {
|
toastr, $analytics, $uibModalStack) {
|
||||||
$scope.collections = [];
|
$scope.collections = [];
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
@@ -96,6 +96,12 @@
|
|||||||
apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (list) {
|
apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (list) {
|
||||||
$scope.collections = cipherService.decryptCollections(list.Data, $state.params.orgId, true);
|
$scope.collections = cipherService.decryptCollections(list.Data, $state.params.orgId, true);
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
||||||
|
if ($state.params.search) {
|
||||||
|
$uibModalStack.dismissAll();
|
||||||
|
$scope.filterSearch = $state.params.search;
|
||||||
|
$('#filterSearch').focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationDashboardController', function ($scope, authService, $state) {
|
.controller('organizationDashboardController', function ($scope, authService, $state, appSettings) {
|
||||||
|
$scope.selfHosted = appSettings.selfHosted;
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
authService.getUserProfile().then(function (userProfile) {
|
authService.getUserProfile().then(function (userProfile) {
|
||||||
if (!userProfile.organizations) {
|
if (!userProfile.organizations) {
|
||||||
@@ -10,4 +12,8 @@
|
|||||||
$scope.orgProfile = userProfile.organizations[$state.params.orgId];
|
$scope.orgProfile = userProfile.organizations[$state.params.orgId];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.goBilling = function () {
|
||||||
|
$state.go('backend.org.billing', { orgId: $state.params.orgId });
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
100
src/app/organization/organizationEventsController.js
Normal file
100
src/app/organization/organizationEventsController.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.organization')
|
||||||
|
|
||||||
|
.controller('organizationEventsController', function ($scope, $state, apiService, $uibModal, $filter,
|
||||||
|
toastr, $analytics, constants, eventService, $compile, $sce) {
|
||||||
|
$scope.events = [];
|
||||||
|
$scope.orgUsers = [];
|
||||||
|
$scope.loading = true;
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
|
||||||
|
var defaultFilters = eventService.getDefaultDateFilters();
|
||||||
|
$scope.filterStart = defaultFilters.start;
|
||||||
|
$scope.filterEnd = defaultFilters.end;
|
||||||
|
|
||||||
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.refresh = function () {
|
||||||
|
loadEvents(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.next = function () {
|
||||||
|
loadEvents(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
var i = 0,
|
||||||
|
orgUsersUserIdDict = {},
|
||||||
|
orgUsersIdDict = {};
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
apiService.organizationUsers.list({ orgId: $state.params.orgId }).$promise.then(function (list) {
|
||||||
|
var users = [];
|
||||||
|
for (i = 0; i < list.Data.length; i++) {
|
||||||
|
var user = {
|
||||||
|
id: list.Data[i].Id,
|
||||||
|
userId: list.Data[i].UserId,
|
||||||
|
name: list.Data[i].Name,
|
||||||
|
email: list.Data[i].Email
|
||||||
|
};
|
||||||
|
|
||||||
|
users.push(user);
|
||||||
|
|
||||||
|
var displayName = user.name || user.email;
|
||||||
|
orgUsersUserIdDict[user.userId] = displayName;
|
||||||
|
orgUsersIdDict[user.id] = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.orgUsers = users;
|
||||||
|
|
||||||
|
return loadEvents(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEvents(clearExisting) {
|
||||||
|
var filterResult = eventService.formatDateFilters($scope.filterStart, $scope.filterEnd);
|
||||||
|
if (filterResult.error) {
|
||||||
|
alert(filterResult.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearExisting) {
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
$scope.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
|
return apiService.events.listOrganization({
|
||||||
|
orgId: $state.params.orgId,
|
||||||
|
start: filterResult.start,
|
||||||
|
end: filterResult.end,
|
||||||
|
continuationToken: $scope.continuationToken
|
||||||
|
}).$promise.then(function (list) {
|
||||||
|
$scope.continuationToken = list.ContinuationToken;
|
||||||
|
|
||||||
|
var events = [];
|
||||||
|
for (i = 0; i < list.Data.length; i++) {
|
||||||
|
var userId = list.Data[i].ActingUserId || list.Data[i].UserId;
|
||||||
|
var eventInfo = eventService.getEventInfo(list.Data[i]);
|
||||||
|
var htmlMessage = $compile('<span>' + eventInfo.message + '</span>')($scope);
|
||||||
|
events.push({
|
||||||
|
message: $sce.trustAsHtml(htmlMessage[0].outerHTML),
|
||||||
|
appIcon: eventInfo.appIcon,
|
||||||
|
appName: eventInfo.appName,
|
||||||
|
userId: userId,
|
||||||
|
userName: userId ? (orgUsersUserIdDict[userId] || '-') : '-',
|
||||||
|
date: list.Data[i].Date,
|
||||||
|
ip: list.Data[i].IpAddress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($scope.events && $scope.events.length > 0) {
|
||||||
|
$scope.events = $scope.events.concat(events);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.events = events;
|
||||||
|
}
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationGroupsController', function ($scope, $state, apiService, $uibModal, $filter,
|
.controller('organizationGroupsController', function ($scope, $state, apiService, $uibModal, $filter,
|
||||||
toastr, $analytics) {
|
toastr, $analytics, $uibModalStack) {
|
||||||
$scope.groups = [];
|
$scope.groups = [];
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
@@ -88,6 +88,12 @@
|
|||||||
}
|
}
|
||||||
$scope.groups = groups;
|
$scope.groups = groups;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
||||||
|
if ($state.params.search) {
|
||||||
|
$uibModalStack.dismissAll();
|
||||||
|
$scope.filterSearch = $state.params.search;
|
||||||
|
$('#filterSearch').focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationPeopleController', function ($scope, $state, $uibModal, cryptoService, apiService, authService,
|
.controller('organizationPeopleController', function ($scope, $state, $uibModal, cryptoService, apiService, authService,
|
||||||
toastr, $analytics) {
|
toastr, $analytics, $filter, $uibModalStack) {
|
||||||
$scope.users = [];
|
$scope.users = [];
|
||||||
$scope.useGroups = false;
|
$scope.useGroups = false;
|
||||||
|
$scope.useEvents = false;
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
loadList();
|
loadList();
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
if (profile.organizations) {
|
if (profile.organizations) {
|
||||||
var org = profile.organizations[$state.params.orgId];
|
var org = profile.organizations[$state.params.orgId];
|
||||||
$scope.useGroups = !!org.useGroups;
|
$scope.useGroups = !!org.useGroups;
|
||||||
|
$scope.useEvents = !!org.useEvents;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -110,6 +112,18 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.events = function (user) {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/organization/views/organizationPeopleEvents.html',
|
||||||
|
controller: 'organizationPeopleEventsController',
|
||||||
|
resolve: {
|
||||||
|
orgUser: function () { return user; },
|
||||||
|
orgId: function () { return $state.params.orgId; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function loadList() {
|
function loadList() {
|
||||||
apiService.organizationUsers.list({ orgId: $state.params.orgId }, function (list) {
|
apiService.organizationUsers.list({ orgId: $state.params.orgId }, function (list) {
|
||||||
var users = [];
|
var users = [];
|
||||||
@@ -129,6 +143,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.users = users;
|
$scope.users = users;
|
||||||
|
|
||||||
|
if ($state.params.search) {
|
||||||
|
$uibModalStack.dismissAll();
|
||||||
|
$scope.filterSearch = $state.params.search;
|
||||||
|
$('#filterSearch').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state.params.viewEvents) {
|
||||||
|
$uibModalStack.dismissAll();
|
||||||
|
var eventUser = $filter('filter')($scope.users, { id: $state.params.viewEvents });
|
||||||
|
if (eventUser && eventUser.length) {
|
||||||
|
$scope.events(eventUser[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
75
src/app/organization/organizationPeopleEventsController.js
Normal file
75
src/app/organization/organizationPeopleEventsController.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.organization')
|
||||||
|
|
||||||
|
.controller('organizationPeopleEventsController', function ($scope, apiService, $uibModalInstance,
|
||||||
|
orgUser, $analytics, eventService, orgId, $compile, $sce) {
|
||||||
|
$analytics.eventTrack('organizationPeopleEventsController', { category: 'Modal' });
|
||||||
|
$scope.email = orgUser.email;
|
||||||
|
$scope.events = [];
|
||||||
|
$scope.loading = true;
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
|
||||||
|
var defaultFilters = eventService.getDefaultDateFilters();
|
||||||
|
$scope.filterStart = defaultFilters.start;
|
||||||
|
$scope.filterEnd = defaultFilters.end;
|
||||||
|
|
||||||
|
$uibModalInstance.opened.then(function () {
|
||||||
|
loadEvents(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.refresh = function () {
|
||||||
|
loadEvents(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.next = function () {
|
||||||
|
loadEvents(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadEvents(clearExisting) {
|
||||||
|
var filterResult = eventService.formatDateFilters($scope.filterStart, $scope.filterEnd);
|
||||||
|
if (filterResult.error) {
|
||||||
|
alert(filterResult.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearExisting) {
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
$scope.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
|
return apiService.events.listOrganizationUser({
|
||||||
|
orgId: orgId,
|
||||||
|
id: orgUser.id,
|
||||||
|
start: filterResult.start,
|
||||||
|
end: filterResult.end,
|
||||||
|
continuationToken: $scope.continuationToken
|
||||||
|
}).$promise.then(function (list) {
|
||||||
|
$scope.continuationToken = list.ContinuationToken;
|
||||||
|
|
||||||
|
var events = [];
|
||||||
|
for (var i = 0; i < list.Data.length; i++) {
|
||||||
|
var eventInfo = eventService.getEventInfo(list.Data[i]);
|
||||||
|
var htmlMessage = $compile('<span>' + eventInfo.message + '</span>')($scope);
|
||||||
|
events.push({
|
||||||
|
message: $sce.trustAsHtml(htmlMessage[0].outerHTML),
|
||||||
|
appIcon: eventInfo.appIcon,
|
||||||
|
appName: eventInfo.appName,
|
||||||
|
date: list.Data[i].Date,
|
||||||
|
ip: list.Data[i].IpAddress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($scope.events && $scope.events.length > 0) {
|
||||||
|
$scope.events = $scope.events.concat(events);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.events = events;
|
||||||
|
}
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -26,8 +26,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var i;
|
||||||
var collectionsDict = {};
|
var collectionsDict = {};
|
||||||
for (var i = 0; i < decCollections.length; i++) {
|
for (i = 0; i < decCollections.length; i++) {
|
||||||
collectionsDict[decCollections[i].id] = decCollections[i];
|
collectionsDict[decCollections[i].id] = decCollections[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +79,17 @@
|
|||||||
switch (decCiphers[i].type) {
|
switch (decCiphers[i].type) {
|
||||||
case constants.cipherType.login:
|
case constants.cipherType.login:
|
||||||
cipher.type = 'login';
|
cipher.type = 'login';
|
||||||
cipher.login_uri = decCiphers[i].login.uri;
|
cipher.login_uri = null;
|
||||||
cipher.login_username = decCiphers[i].login.username;
|
cipher.login_username = decCiphers[i].login.username;
|
||||||
cipher.login_password = decCiphers[i].login.password;
|
cipher.login_password = decCiphers[i].login.password;
|
||||||
cipher.login_totp = decCiphers[i].login.totp;
|
cipher.login_totp = decCiphers[i].login.totp;
|
||||||
|
|
||||||
|
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
|
||||||
|
cipher.login_uri = [];
|
||||||
|
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
|
||||||
|
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case constants.cipherType.secureNote:
|
case constants.cipherType.secureNote:
|
||||||
cipher.type = 'note';
|
cipher.type = 'note';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
$scope.options = [
|
$scope.options = [
|
||||||
{
|
{
|
||||||
id: 'bitwardencsv',
|
id: 'bitwardencsv',
|
||||||
name: 'bitwarden (csv)',
|
name: 'Bitwarden (csv)',
|
||||||
featured: true,
|
featured: true,
|
||||||
sort: 1,
|
sort: 1,
|
||||||
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
var halfway = Math.floor(ciphers.length / 2);
|
var halfway = Math.floor(ciphers.length / 2);
|
||||||
var last = ciphers.length - 1;
|
var last = ciphers.length - 1;
|
||||||
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
||||||
importError('CSV data is not formatted correctly. Please check your import file and try again.');
|
importError('Data is not formatted correctly. Please check your import file and try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,19 @@
|
|||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationVaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
.controller('organizationVaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||||
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants) {
|
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants, selectedType) {
|
||||||
$analytics.eventTrack('organizationVaultAddCipherController', { category: 'Modal' });
|
$analytics.eventTrack('organizationVaultAddCipherController', { category: 'Modal' });
|
||||||
$scope.constants = constants;
|
$scope.constants = constants;
|
||||||
$scope.selectedType = constants.cipherType.login.toString();
|
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
|
||||||
$scope.cipher = {
|
$scope.cipher = {
|
||||||
type: constants.cipherType.login,
|
type: selectedType || constants.cipherType.login,
|
||||||
login: {},
|
login: {
|
||||||
|
uris: [{
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
}]
|
||||||
|
},
|
||||||
identity: {},
|
identity: {},
|
||||||
card: {},
|
card: {},
|
||||||
secureNote: {
|
secureNote: {
|
||||||
@@ -40,7 +46,43 @@
|
|||||||
$scope.generatePassword = function () {
|
$scope.generatePassword = function () {
|
||||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
$analytics.eventTrack('Generated Password From Add');
|
$analytics.eventTrack('Generated Password From Add');
|
||||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
$scope.cipher.login.password = passwordService.generatePassword({ length: 14, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addUri = function () {
|
||||||
|
if (!$scope.cipher.login) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.cipher.login.uris) {
|
||||||
|
$scope.cipher.login.uris = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.cipher.login.uris.push({
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeUri = function (uri) {
|
||||||
|
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||||
|
if (index > -1) {
|
||||||
|
$scope.cipher.login.uris.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.uriMatchChanged = function (uri) {
|
||||||
|
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||||
|
uri.match = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uri.match = parseInt(uri.matchValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,19 +29,15 @@
|
|||||||
var fd = new FormData();
|
var fd = new FormData();
|
||||||
var blob = new Blob([encValue.data], { type: 'application/octet-stream' });
|
var blob = new Blob([encValue.data], { type: 'application/octet-stream' });
|
||||||
fd.append('data', blob, encValue.fileName);
|
fd.append('data', blob, encValue.fileName);
|
||||||
return apiService.ciphers.postAttachment({ id: cipherId }, fd).$promise;
|
return apiService.ciphers.postAttachmentAdmin({ id: cipherId }, fd).$promise;
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
$analytics.eventTrack('Added Attachment');
|
$analytics.eventTrack('Added Attachment');
|
||||||
toastr.success('The attachment has been added.');
|
toastr.success('The attachment has been added.');
|
||||||
closing = true;
|
closing = true;
|
||||||
$uibModalInstance.close(true);
|
$uibModalInstance.close(true);
|
||||||
}, function (err) {
|
}, function (e) {
|
||||||
if (err) {
|
var errors = validationService.parseErrors(e);
|
||||||
validationService.addError(form, 'file', err, true);
|
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||||
}
|
|
||||||
else {
|
|
||||||
validationService.addError(form, 'file', 'Something went wrong.', true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachment.loading = true;
|
attachment.loading = true;
|
||||||
apiService.ciphers.delAttachment({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
apiService.ciphers.delAttachmentAdmin({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
||||||
attachment.loading = false;
|
attachment.loading = false;
|
||||||
$analytics.eventTrack('Deleted Organization Attachment');
|
$analytics.eventTrack('Deleted Organization Attachment');
|
||||||
var index = $scope.cipher.attachments.indexOf(attachment);
|
var index = $scope.cipher.attachments.indexOf(attachment);
|
||||||
|
|||||||
104
src/app/organization/organizationVaultCipherEventsController.js
Normal file
104
src/app/organization/organizationVaultCipherEventsController.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.organization')
|
||||||
|
|
||||||
|
.controller('organizationVaultCipherEventsController', function ($scope, apiService, $uibModalInstance,
|
||||||
|
cipher, $analytics, eventService) {
|
||||||
|
$analytics.eventTrack('organizationVaultCipherEventsController', { category: 'Modal' });
|
||||||
|
$scope.cipher = cipher;
|
||||||
|
$scope.events = [];
|
||||||
|
$scope.loading = true;
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
|
||||||
|
var defaultFilters = eventService.getDefaultDateFilters();
|
||||||
|
$scope.filterStart = defaultFilters.start;
|
||||||
|
$scope.filterEnd = defaultFilters.end;
|
||||||
|
|
||||||
|
$uibModalInstance.opened.then(function () {
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.refresh = function () {
|
||||||
|
loadEvents(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.next = function () {
|
||||||
|
loadEvents(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
var i = 0,
|
||||||
|
orgUsersUserIdDict = {},
|
||||||
|
orgUsersIdDict = {};
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
apiService.organizationUsers.list({ orgId: cipher.organizationId }).$promise.then(function (list) {
|
||||||
|
var users = [];
|
||||||
|
for (i = 0; i < list.Data.length; i++) {
|
||||||
|
var user = {
|
||||||
|
id: list.Data[i].Id,
|
||||||
|
userId: list.Data[i].UserId,
|
||||||
|
name: list.Data[i].Name,
|
||||||
|
email: list.Data[i].Email
|
||||||
|
};
|
||||||
|
|
||||||
|
users.push(user);
|
||||||
|
|
||||||
|
var displayName = user.name || user.email;
|
||||||
|
orgUsersUserIdDict[user.userId] = displayName;
|
||||||
|
orgUsersIdDict[user.id] = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.orgUsers = users;
|
||||||
|
|
||||||
|
return loadEvents(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEvents(clearExisting) {
|
||||||
|
var filterResult = eventService.formatDateFilters($scope.filterStart, $scope.filterEnd);
|
||||||
|
if (filterResult.error) {
|
||||||
|
alert(filterResult.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearExisting) {
|
||||||
|
$scope.continuationToken = null;
|
||||||
|
$scope.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
|
return apiService.events.listCipher({
|
||||||
|
id: cipher.id,
|
||||||
|
start: filterResult.start,
|
||||||
|
end: filterResult.end,
|
||||||
|
continuationToken: $scope.continuationToken
|
||||||
|
}).$promise.then(function (list) {
|
||||||
|
$scope.continuationToken = list.ContinuationToken;
|
||||||
|
|
||||||
|
var events = [];
|
||||||
|
for (i = 0; i < list.Data.length; i++) {
|
||||||
|
var userId = list.Data[i].ActingUserId || list.Data[i].UserId;
|
||||||
|
var eventInfo = eventService.getEventInfo(list.Data[i], { cipherInfo: false });
|
||||||
|
events.push({
|
||||||
|
message: eventInfo.message,
|
||||||
|
appIcon: eventInfo.appIcon,
|
||||||
|
appName: eventInfo.appName,
|
||||||
|
userId: userId,
|
||||||
|
userName: userId ? (orgUsersUserIdDict[userId] || '-') : '-',
|
||||||
|
date: list.Data[i].Date,
|
||||||
|
ip: list.Data[i].IpAddress
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($scope.events && $scope.events.length > 0) {
|
||||||
|
$scope.events = $scope.events.concat(events);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.events = events;
|
||||||
|
}
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -2,23 +2,35 @@
|
|||||||
.module('bit.organization')
|
.module('bit.organization')
|
||||||
|
|
||||||
.controller('organizationVaultController', function ($scope, apiService, cipherService, $analytics, $q, $state,
|
.controller('organizationVaultController', function ($scope, apiService, cipherService, $analytics, $q, $state,
|
||||||
$localStorage, $uibModal, $filter, authService) {
|
$localStorage, $uibModal, $filter, authService, $uibModalStack, constants, $timeout) {
|
||||||
$scope.ciphers = [];
|
$scope.ciphers = [];
|
||||||
$scope.collections = [];
|
$scope.collections = [];
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
$scope.useEvents = false;
|
||||||
|
$scope.constants = constants;
|
||||||
|
$scope.filter = undefined;
|
||||||
|
$scope.selectedType = undefined;
|
||||||
|
$scope.selectedCollection = undefined;
|
||||||
|
$scope.selectedAll = true;
|
||||||
|
$scope.selectedTitle = 'All';
|
||||||
|
$scope.selectedIcon = 'fa-th';
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
|
authService.getUserProfile().then(function (profile) {
|
||||||
|
if (profile.organizations) {
|
||||||
|
var org = profile.organizations[$state.params.orgId];
|
||||||
|
$scope.useEvents = !!org.useEvents;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var collectionPromise = apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (collections) {
|
var collectionPromise = apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (collections) {
|
||||||
var decCollections = [{
|
var decCollections = [{
|
||||||
id: null,
|
id: null,
|
||||||
name: 'Unassigned',
|
name: 'Unassigned'
|
||||||
collapsed: $localStorage.collapsedOrgCollections && 'unassigned' in $localStorage.collapsedOrgCollections
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
for (var i = 0; i < collections.Data.length; i++) {
|
for (var i = 0; i < collections.Data.length; i++) {
|
||||||
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
|
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
|
||||||
decCollection.collapsed = $localStorage.collapsedOrgCollections &&
|
|
||||||
decCollection.id in $localStorage.collapsedOrgCollections;
|
|
||||||
decCollections.push(decCollection);
|
decCollections.push(decCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,18 +51,26 @@
|
|||||||
|
|
||||||
$q.all([collectionPromise, cipherPromise]).then(function () {
|
$q.all([collectionPromise, cipherPromise]).then(function () {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
});
|
$timeout(function () {
|
||||||
});
|
if ($('body').hasClass('control-sidebar-open')) {
|
||||||
|
$("#search").focus();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
$scope.filterByCollection = function (collection) {
|
if ($state.params.search) {
|
||||||
return function (cipher) {
|
$uibModalStack.dismissAll();
|
||||||
if (!cipher.collectionIds || !cipher.collectionIds.length) {
|
$scope.searchVaultText = $state.params.search;
|
||||||
return collection.id === null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cipher.collectionIds.indexOf(collection.id) > -1;
|
if ($state.params.viewEvents) {
|
||||||
};
|
$uibModalStack.dismissAll();
|
||||||
};
|
var cipher = $filter('filter')($scope.ciphers, { id: $state.params.viewEvents });
|
||||||
|
if (cipher && cipher.length) {
|
||||||
|
$scope.viewEvents(cipher[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$scope.collectionSort = function (item) {
|
$scope.collectionSort = function (item) {
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
@@ -60,21 +80,6 @@
|
|||||||
return item.name.toLowerCase();
|
return item.name.toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.collapseExpand = function (collection) {
|
|
||||||
if (!$localStorage.collapsedOrgCollections) {
|
|
||||||
$localStorage.collapsedOrgCollections = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = collection.id || 'unassigned';
|
|
||||||
|
|
||||||
if (id in $localStorage.collapsedOrgCollections) {
|
|
||||||
delete $localStorage.collapsedOrgCollections[id];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$localStorage.collapsedOrgCollections[id] = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.editCipher = function (cipher) {
|
$scope.editCipher = function (cipher) {
|
||||||
var editModel = $uibModal.open({
|
var editModel = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
@@ -114,7 +119,8 @@
|
|||||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||||
controller: 'organizationVaultAddCipherController',
|
controller: 'organizationVaultAddCipherController',
|
||||||
resolve: {
|
resolve: {
|
||||||
orgId: function () { return $state.params.orgId; }
|
orgId: function () { return $state.params.orgId; },
|
||||||
|
selectedType: function () { return $scope.selectedType; }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,6 +147,17 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.viewEvents = function (cipher) {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/organization/views/organizationVaultCipherEvents.html',
|
||||||
|
controller: 'organizationVaultCipherEventsController',
|
||||||
|
resolve: {
|
||||||
|
cipher: function () { return cipher; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.attachments = function (cipher) {
|
$scope.attachments = function (cipher) {
|
||||||
authService.getUserProfile().then(function (profile) {
|
authService.getUserProfile().then(function (profile) {
|
||||||
return !!profile.organizations[cipher.organizationId].maxStorageGb;
|
return !!profile.organizations[cipher.organizationId].maxStorageGb;
|
||||||
@@ -172,28 +189,6 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeCipher = function (cipher, collection) {
|
|
||||||
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
|
|
||||||
'collection (' + collection.name + ') ?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = {
|
|
||||||
collectionIds: []
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var i = 0; i < cipher.collectionIds.length; i++) {
|
|
||||||
if (cipher.collectionIds[i] !== collection.id) {
|
|
||||||
request.collectionIds.push(cipher.collectionIds[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
|
|
||||||
$analytics.eventTrack('Removed Cipher From Collection');
|
|
||||||
cipher.collectionIds = request.collectionIds;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.deleteCipher = function (cipher) {
|
$scope.deleteCipher = function (cipher) {
|
||||||
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
|
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
|
||||||
return;
|
return;
|
||||||
@@ -207,4 +202,72 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.filterCollection = function (col) {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedCollection = col;
|
||||||
|
$scope.selectedIcon = 'fa-cube';
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterType = function (t) {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedType = t;
|
||||||
|
switch (t) {
|
||||||
|
case constants.cipherType.login:
|
||||||
|
$scope.selectedTitle = 'Login';
|
||||||
|
$scope.selectedIcon = 'fa-globe';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.card:
|
||||||
|
$scope.selectedTitle = 'Card';
|
||||||
|
$scope.selectedIcon = 'fa-credit-card';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.identity:
|
||||||
|
$scope.selectedTitle = 'Identity';
|
||||||
|
$scope.selectedIcon = 'fa-id-card-o';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.secureNote:
|
||||||
|
$scope.selectedTitle = 'Secure Note';
|
||||||
|
$scope.selectedIcon = 'fa-sticky-note-o';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return c.type === t;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterAll = function () {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedAll = true;
|
||||||
|
$scope.selectedTitle = 'All';
|
||||||
|
$scope.selectedIcon = 'fa-th';
|
||||||
|
$scope.filter = null;
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetSelected() {
|
||||||
|
$scope.selectedCollection = undefined;
|
||||||
|
$scope.selectedType = undefined;
|
||||||
|
$scope.selectedAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixLayout() {
|
||||||
|
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||||
|
$timeout(function () {
|
||||||
|
$.AdminLTE.layout.fix();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.cipherFilter = function () {
|
||||||
|
return function (cipher) {
|
||||||
|
return !$scope.filter || $scope.filter(cipher);
|
||||||
|
};
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
|
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
|
||||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||||
$scope.useTotp = $scope.cipher.organizationUseTotp;
|
$scope.useTotp = $scope.cipher.organizationUseTotp;
|
||||||
|
setUriMatchValues();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.save = function (model) {
|
$scope.save = function (model) {
|
||||||
@@ -28,7 +29,43 @@
|
|||||||
$scope.generatePassword = function () {
|
$scope.generatePassword = function () {
|
||||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
$analytics.eventTrack('Generated Password From Edit');
|
$analytics.eventTrack('Generated Password From Edit');
|
||||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
$scope.cipher.login.password = passwordService.generatePassword({ length: 14, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addUri = function () {
|
||||||
|
if (!$scope.cipher.login) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.cipher.login.uris) {
|
||||||
|
$scope.cipher.login.uris = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.cipher.login.uris.push({
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeUri = function (uri) {
|
||||||
|
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||||
|
if (index > -1) {
|
||||||
|
$scope.cipher.login.uris.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.uriMatchChanged = function (uri) {
|
||||||
|
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||||
|
uri.match = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uri.match = parseInt(uri.matchValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,4 +135,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setUriMatchValues() {
|
||||||
|
if ($scope.cipher.login && $scope.cipher.login.uris) {
|
||||||
|
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
|
||||||
|
$scope.cipher.login.uris[i].matchValue =
|
||||||
|
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
|
||||||
|
$scope.cipher.login.uris[i].match.toString() : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,10 +116,10 @@
|
|||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!loading">
|
<div ng-show="!loading">
|
||||||
You plan currently has a total of <b>{{plan.seats}}</b> seats.
|
Your plan currently has a total of <b>{{plan.seats}}</b> seats.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-footer" ng-if="!selfHosted && !noSubscription">
|
<div class="box-footer" ng-if="!selfHosted && !noSubscription && canAdjustSeats">
|
||||||
<button type="button" class="btn btn-default btn-flat" ng-click="adjustSeats(true)">
|
<button type="button" class="btn btn-default btn-flat" ng-click="adjustSeats(true)">
|
||||||
Add Seats
|
Add Seats
|
||||||
</button>
|
</button>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<p>
|
<p>
|
||||||
You plan has a total of {{storage.maxGb}} GB of encrypted file storage.
|
Your plan has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||||
You are currently using {{storage.currentName}}.
|
You are currently using {{storage.currentName}}.
|
||||||
</p>
|
</p>
|
||||||
<div class="progress" style="margin: 0;">
|
<div class="progress" style="margin: 0;">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<div class="box-filters hidden-xs">
|
<div class="box-filters hidden-xs">
|
||||||
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search collections..."
|
<input type="text" id="filterSearch" class="form-control" placeholder="Search collections..."
|
||||||
style="width: 200px;" ng-model="filterSearch">
|
style="width: 200px;" ng-model="filterSearch">
|
||||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,10 +7,12 @@
|
|||||||
<section class="content">
|
<section class="content">
|
||||||
<div class="callout callout-warning" ng-if="!orgProfile.enabled">
|
<div class="callout callout-warning" ng-if="!orgProfile.enabled">
|
||||||
<h4><i class="fa fa-warning"></i> Organization Disabled</h4>
|
<h4><i class="fa fa-warning"></i> Organization Disabled</h4>
|
||||||
<p>
|
<p>This organization is currently disabled. Users will not see your shared logins or collections.</p>
|
||||||
This organization is currently disabled. Users will not see your shared logins or collections.
|
<p ng-if="!selfHosted">Contact us if you would like to reinstate this organization.</p>
|
||||||
Contact us if you would like to reinstate this organization.
|
<p ng-if="selfHosted">Update your license to reinstate this organization.</p>
|
||||||
</p>
|
<a ng-if="selfHosted" class="btn btn-default btn-flat" href="#" stop-click ng-click="goBilling()">
|
||||||
|
Billing & Licensing
|
||||||
|
</a>
|
||||||
<a class="btn btn-default btn-flat" href="https://bitwarden.com/contact/" target="_blank">
|
<a class="btn btn-default btn-flat" href="https://bitwarden.com/contact/" target="_blank">
|
||||||
Contact Us
|
Contact Us
|
||||||
</a>
|
</a>
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
<h3 class="box-title">Let's Get Started!</h3>
|
<h3 class="box-title">Let's Get Started!</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<p>Dashboard features are coming soon. Get started by inviting users and creating your collections.</p>
|
<p>Get started by inviting users and creating your collections.</p>
|
||||||
<a class="btn btn-default btn-flat" ui-sref="backend.org.people({orgId: orgProfile.id})">
|
<a class="btn btn-default btn-flat" ui-sref="backend.org.people({orgId: orgProfile.id})">
|
||||||
Invite Users
|
Invite Users
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
67
src/app/organization/views/organizationEvents.html
Normal file
67
src/app/organization/views/organizationEvents.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<section class="content-header">
|
||||||
|
<h1>
|
||||||
|
Events
|
||||||
|
<small>audit your organization</small>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
<section class="content">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
|
||||||
|
<div class="box-filters hidden-xs hidden-sm">
|
||||||
|
<input type="datetime-local" ng-model="filterStart" required
|
||||||
|
class="form-control input-sm" style="width:initial;" />
|
||||||
|
-
|
||||||
|
<input type="datetime-local" ng-model="filterEnd" required
|
||||||
|
class="form-control input-sm" style="width:initial;" />
|
||||||
|
</div>
|
||||||
|
<div class="box-tools">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm btn-flat" ng-click="refresh()">
|
||||||
|
<i class="fa fa-fw fa-refresh" ng-class="{'fa-spin': loading}"></i> Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-body" ng-class="{'no-padding': filteredEvents.length}">
|
||||||
|
<div ng-show="loading && !events.length">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loading && !events.length">
|
||||||
|
<p>There are no events to list.</p>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive" ng-show="events.length">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th><span class="sr-only">App</span></th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Event</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="event in filteredEvents = (events)">
|
||||||
|
<td style="width: 210px; min-width: 100px;">
|
||||||
|
{{event.date | date:'medium'}}
|
||||||
|
</td>
|
||||||
|
<td style="width: 20px;" class="text-center">
|
||||||
|
<i class="text-muted fa fa-lg {{event.appIcon}}" title="{{event.appName}}, {{event.ip}}"></i>
|
||||||
|
</td>
|
||||||
|
<td style="width: 150px; min-width: 100px;">
|
||||||
|
{{event.userName}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div ng-bind-html="event.message"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer text-center" ng-show="continuationToken">
|
||||||
|
<button class="btn btn-link btn-block" ng-click="next()" ng-if="!loading">
|
||||||
|
Load more...
|
||||||
|
</button>
|
||||||
|
<i class="fa fa-fw fa-refresh fa-spin text-muted" ng-if="loading"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<div class="box-filters hidden-xs">
|
<div class="box-filters hidden-xs">
|
||||||
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search groups..."
|
<input type="text" id="filterSearch" class="form-control" placeholder="Search groups..."
|
||||||
style="width: 200px;" ng-model="filterSearch">
|
style="width: 200px;" ng-model="filterSearch">
|
||||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<div class="box-filters hidden-xs">
|
<div class="box-filters hidden-xs">
|
||||||
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
<div class="form-group form-group-sm has-feedback has-feedback-left">
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search people..."
|
<input type="text" id="filterSearch" class="form-control" placeholder="Search people..."
|
||||||
style="width: 200px;" ng-model="filterSearch">
|
style="width: 200px;" ng-model="filterSearch">
|
||||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,6 +46,12 @@
|
|||||||
<i class="fa fa-fw fa-sitemap"></i> Groups
|
<i class="fa fa-fw fa-sitemap"></i> Groups
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" stop-click ng-click="events(user)"
|
||||||
|
ng-if="useEvents && user.status === 2">
|
||||||
|
<i class="fa fa-fw fa-file-text-o"></i> Event Logs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li ng-show="user.status === 1">
|
<li ng-show="user.status === 1">
|
||||||
<a href="#" stop-click ng-click="confirm(user)">
|
<a href="#" stop-click ng-click="confirm(user)">
|
||||||
<i class="fa fa-fw fa-check"></i> Confirm
|
<i class="fa fa-fw fa-check"></i> Confirm
|
||||||
|
|||||||
56
src/app/organization/views/organizationPeopleEvents.html
Normal file
56
src/app/organization/views/organizationPeopleEvents.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<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-file-text-o"></i> User Event Logs <small>{{email}}</small></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="hidden-xs">
|
||||||
|
<input type="datetime-local" ng-model="filterStart" required
|
||||||
|
class="form-control input-sm" style="width:initial; display: inline;" />
|
||||||
|
-
|
||||||
|
<input type="datetime-local" ng-model="filterEnd" required
|
||||||
|
class="form-control input-sm" style="width:initial; display: inline;" />
|
||||||
|
<button type="button" class="btn btn-primary btn-sm btn-flat" ng-click="refresh()">
|
||||||
|
<i class="fa fa-fw fa-refresh" ng-class="{'fa-spin': loading}"></i> Refresh
|
||||||
|
</button>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div ng-show="loading && !events.length">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loading && !events.length">
|
||||||
|
<p>There are no events to list.</p>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive" ng-show="events.length" style="margin: 0;">
|
||||||
|
<table class="table table-striped table-hover" style="{{ !continuationToken ? 'margin: 0;' : '' }}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th><span class="sr-only">App</span></th>
|
||||||
|
<th>Event</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="event in filteredEvents = (events)">
|
||||||
|
<td style="width: 210px; min-width: 100px;">
|
||||||
|
{{event.date | date:'medium'}}
|
||||||
|
</td>
|
||||||
|
<td style="width: 20px;" class="text-center">
|
||||||
|
<i class="text-muted fa fa-lg {{event.appIcon}}" title="{{event.appName}}, {{event.ip}}"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div ng-bind-html="event.message"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="text-center" ng-show="continuationToken">
|
||||||
|
<button class="btn btn-link btn-block" ng-click="next()" ng-if="!loading">
|
||||||
|
Load more...
|
||||||
|
</button>
|
||||||
|
<i class="fa fa-fw fa-refresh fa-spin text-muted" ng-if="loading"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
<form name="inviteForm" ng-submit="inviteForm.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
<form name="inviteForm" ng-submit="inviteForm.$valid && submit(model)" api-form="submitPromise" autocomplete="off">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>
|
<p>
|
||||||
Invite a new user to your organization by entering their bitwarden account email address below. If they do not have
|
Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have
|
||||||
a bitwarden account already, they will be prompted to create a new account.
|
a Bitwarden account already, they will be prompted to create a new account.
|
||||||
</p>
|
</p>
|
||||||
<div class="callout callout-danger validation-errors" ng-show="inviteForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="inviteForm.$errors">
|
||||||
<h4>Errors have occurred</h4>
|
<h4>Errors have occurred</h4>
|
||||||
|
|||||||
@@ -10,31 +10,22 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</section>
|
</section>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<p ng-show="loading && !collections.length">Loading...</p>
|
<p ng-show="loading">Loading...</p>
|
||||||
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
|
<div class="box" ng-show="!loading">
|
||||||
orderBy: collectionSort track by collection.id"
|
|
||||||
ng-show="collections.length && (!main.searchVaultText || collectionCiphers.length)">
|
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">
|
<h3 class="box-title">
|
||||||
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
|
<i class="fa {{selectedIcon}}"></i>
|
||||||
{{collection.name}}
|
{{selectedCollection ? selectedCollection.name : selectedTitle}}
|
||||||
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="box-tools">
|
|
||||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
|
||||||
ng-click="collapseExpand(collection)">
|
|
||||||
<i class="fa" ng-class="{'fa-minus': !collection.collapsed, 'fa-plus': collection.collapsed}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
|
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
|
||||||
<div ng-show="!collectionCiphers.length && collection.id">No items in this collection.</div>
|
<div ng-show="!filteredCiphers.length">No items to list.</div>
|
||||||
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
|
<div class="table-responsive" ng-show="filteredCiphers.length">
|
||||||
<div class="table-responsive" ng-show="collectionCiphers.length">
|
|
||||||
<table class="table table-striped table-hover table-vmiddle">
|
<table class="table table-striped table-hover table-vmiddle">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
|
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
|
||||||
filter: (main.searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
|
filter: (searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
|
||||||
<td style="width: 70px;">
|
<td style="width: 70px;">
|
||||||
<div class="btn-group" data-append-to="body">
|
<div class="btn-group" data-append-to="body">
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||||
@@ -57,9 +48,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" stop-click ng-click="removeCipher(cipher, collection)" class="text-red"
|
<a href="#" stop-click ng-click="viewEvents(cipher)" ng-if="useEvents">
|
||||||
ng-if="collection.id">
|
<i class="fa fa-fw fa-file-text-o"></i> Event Logs
|
||||||
<i class="fa fa-fw fa-remove"></i> Remove
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -88,3 +78,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<aside class="control-sidebar control-sidebar-light">
|
||||||
|
<div class="tab-content">
|
||||||
|
<form class="search-form">
|
||||||
|
<label for="search" class="sr-only">Search</label>
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<input type="search" id="search" class="form-control" placeholder="Search org vault..."
|
||||||
|
ng-model="searchVaultText" />
|
||||||
|
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ul class="control-sidebar-menu">
|
||||||
|
<li ng-class="{active: selectedAll}">
|
||||||
|
<a href="#" stop-click ng-click="filterAll()">
|
||||||
|
<i class="fa fa-th fa-fw"></i> All Items
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 class="control-sidebar-heading">Types</h3>
|
||||||
|
<div class="control-sidebar-section">
|
||||||
|
<ul class="control-sidebar-menu">
|
||||||
|
<li ng-class="{active: constants.cipherType.login === selectedType}">
|
||||||
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
||||||
|
<i class="fa fa-globe fa-fw"></i> Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-class="{active: constants.cipherType.card === selectedType}">
|
||||||
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
||||||
|
<i class="fa fa-credit-card fa-fw"></i> Card
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-class="{active: constants.cipherType.identity === selectedType}">
|
||||||
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
||||||
|
<i class="fa fa-id-card-o fa-fw"></i> Identity
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
|
||||||
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
||||||
|
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<h3 class="control-sidebar-heading">Collections</h3>
|
||||||
|
<div ng-show="loading && !collections.length">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loading && !collections.length">
|
||||||
|
<p>No collections.</p>
|
||||||
|
</div>
|
||||||
|
<div class="control-sidebar-section" ng-show="!loading && collections.length">
|
||||||
|
<ul class="control-sidebar-menu">
|
||||||
|
<li ng-repeat="collection in collections | orderBy: [collectionSort] track by collection.id"
|
||||||
|
ng-class="{active: selectedCollection && collection.id === selectedCollection.id}">
|
||||||
|
<a href="#" stop-click ng-click="filterCollection(collection)">
|
||||||
|
<i class="fa fa-caret-right fa-fw"></i>
|
||||||
|
{{collection.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<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-file-text-o"></i> Event Logs <small>{{cipher.name}}</small></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="hidden-xs">
|
||||||
|
<input type="datetime-local" ng-model="filterStart" required
|
||||||
|
class="form-control input-sm" style="width:initial; display: inline;" />
|
||||||
|
-
|
||||||
|
<input type="datetime-local" ng-model="filterEnd" required
|
||||||
|
class="form-control input-sm" style="width:initial; display: inline;" />
|
||||||
|
<button type="button" class="btn btn-primary btn-sm btn-flat" ng-click="refresh()">
|
||||||
|
<i class="fa fa-fw fa-refresh" ng-class="{'fa-spin': loading}"></i> Refresh
|
||||||
|
</button>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div ng-show="loading && !events.length">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loading && !events.length">
|
||||||
|
<p>There are no events to list.</p>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive" ng-show="events.length" style="margin: 0;">
|
||||||
|
<table class="table table-striped table-hover" style="{{ !continuationToken ? 'margin: 0;' : '' }}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th><span class="sr-only">App</span></th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Event</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="event in filteredEvents = (events)">
|
||||||
|
<td style="width: 210px; min-width: 100px;">
|
||||||
|
{{event.date | date:'medium'}}
|
||||||
|
</td>
|
||||||
|
<td style="width: 20px;" class="text-center">
|
||||||
|
<i class="text-muted fa fa-lg {{event.appIcon}}" title="{{event.appName}}, {{event.ip}}"></i>
|
||||||
|
</td>
|
||||||
|
<td style="width: 150px; min-width: 100px;">
|
||||||
|
{{event.userName}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{event.message}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="text-center" ng-show="continuationToken">
|
||||||
|
<button class="btn btn-link btn-block" ng-click="next()" ng-if="!loading">
|
||||||
|
Load more...
|
||||||
|
</button>
|
||||||
|
<i class="fa fa-fw fa-refresh fa-spin text-muted" ng-if="loading"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit.services')
|
.module('bit.services')
|
||||||
|
|
||||||
.factory('apiService', function ($resource, tokenService, appSettings, $httpParamSerializer) {
|
.factory('apiService', function ($resource, tokenService, appSettings, $httpParamSerializer, utilsService) {
|
||||||
var _service = {},
|
var _service = {},
|
||||||
_apiUri = appSettings.apiUri,
|
_apiUri = appSettings.apiUri,
|
||||||
_identityUri = appSettings.identityUri;
|
_identityUri = appSettings.identityUri;
|
||||||
@@ -42,13 +42,20 @@
|
|||||||
headers: { 'Content-Type': undefined },
|
headers: { 'Content-Type': undefined },
|
||||||
params: { id: '@id' }
|
params: { id: '@id' }
|
||||||
},
|
},
|
||||||
|
postAttachmentAdmin: {
|
||||||
|
url: _apiUri + '/ciphers/:id/attachment-admin',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': undefined },
|
||||||
|
params: { id: '@id' }
|
||||||
|
},
|
||||||
postShareAttachment: {
|
postShareAttachment: {
|
||||||
url: _apiUri + '/ciphers/:id/attachment/:attachmentId/share?organizationId=:orgId',
|
url: _apiUri + '/ciphers/:id/attachment/:attachmentId/share?organizationId=:orgId',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': undefined },
|
headers: { 'Content-Type': undefined },
|
||||||
params: { id: '@id', attachmentId: '@attachmentId', orgId: '@orgId' }
|
params: { id: '@id', attachmentId: '@attachmentId', orgId: '@orgId' }
|
||||||
},
|
},
|
||||||
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
|
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } },
|
||||||
|
delAttachmentAdmin: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete-admin', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
|
||||||
});
|
});
|
||||||
|
|
||||||
_service.organizations = $resource(_apiUri + '/organizations/:id', {}, {
|
_service.organizations = $resource(_apiUri + '/organizations/:id', {}, {
|
||||||
@@ -179,11 +186,21 @@
|
|||||||
getPublicKey: { url: _apiUri + '/users/:id/public-key', method: 'GET', params: { id: '@id' } }
|
getPublicKey: { url: _apiUri + '/users/:id/public-key', method: 'GET', params: { id: '@id' } }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_service.events = $resource(_apiUri + '/events', {}, {
|
||||||
|
list: { method: 'GET', params: {} },
|
||||||
|
listOrganization: { url: _apiUri + '/organizations/:orgId/events', method: 'GET', params: { id: '@orgId' } },
|
||||||
|
listCipher: { url: _apiUri + '/ciphers/:id/events', method: 'GET', params: { id: '@id' } },
|
||||||
|
listOrganizationUser: { url: _apiUri + '/organizations/:orgId/users/:id/events', method: 'GET', params: { orgId: '@orgId', id: '@id' } }
|
||||||
|
});
|
||||||
|
|
||||||
_service.identity = $resource(_identityUri + '/connect', {}, {
|
_service.identity = $resource(_identityUri + '/connect', {}, {
|
||||||
token: {
|
token: {
|
||||||
url: _identityUri + '/connect/token',
|
url: _identityUri + '/connect/token',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' },
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
'Device-Type': utilsService.getDeviceType()
|
||||||
|
},
|
||||||
transformRequest: transformUrlEncoded,
|
transformRequest: transformUrlEncoded,
|
||||||
skipAuthorization: true,
|
skipAuthorization: true,
|
||||||
params: {}
|
params: {}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ angular
|
|||||||
_service.logOut = function () {
|
_service.logOut = function () {
|
||||||
tokenService.clearTokens();
|
tokenService.clearTokens();
|
||||||
cryptoService.clearKeys();
|
cryptoService.clearKeys();
|
||||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
|
||||||
_userProfile = null;
|
_userProfile = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,6 +150,8 @@ angular
|
|||||||
maxStorageGb: profile.Organizations[i].MaxStorageGb,
|
maxStorageGb: profile.Organizations[i].MaxStorageGb,
|
||||||
seats: profile.Organizations[i].Seats,
|
seats: profile.Organizations[i].Seats,
|
||||||
useGroups: profile.Organizations[i].UseGroups,
|
useGroups: profile.Organizations[i].UseGroups,
|
||||||
|
useDirectory: profile.Organizations[i].UseDirectory,
|
||||||
|
useEvents: profile.Organizations[i].UseEvents,
|
||||||
useTotp: profile.Organizations[i].UseTotp
|
useTotp: profile.Organizations[i].UseTotp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -183,6 +185,8 @@ angular
|
|||||||
maxStorageGb: org.MaxStorageGb,
|
maxStorageGb: org.MaxStorageGb,
|
||||||
seats: org.Seats,
|
seats: org.Seats,
|
||||||
useGroups: org.UseGroups,
|
useGroups: org.UseGroups,
|
||||||
|
useDirectory: org.UseDirectory,
|
||||||
|
useEvents: org.UseEvents,
|
||||||
useTotp: org.UseTotp
|
useTotp: org.UseTotp
|
||||||
};
|
};
|
||||||
profile.organizations[o.id] = o;
|
profile.organizations[o.id] = o;
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ angular
|
|||||||
organizationId: encryptedCipher.OrganizationId,
|
organizationId: encryptedCipher.OrganizationId,
|
||||||
collectionIds: encryptedCipher.CollectionIds || [],
|
collectionIds: encryptedCipher.CollectionIds || [],
|
||||||
'type': encryptedCipher.Type,
|
'type': encryptedCipher.Type,
|
||||||
|
name: cryptoService.decrypt(encryptedCipher.Name, key),
|
||||||
|
notes: _service.decryptProperty(encryptedCipher.Notes, key, true, false),
|
||||||
|
fields: _service.decryptFields(key, encryptedCipher.Fields),
|
||||||
folderId: encryptedCipher.FolderId,
|
folderId: encryptedCipher.FolderId,
|
||||||
favorite: encryptedCipher.Favorite,
|
favorite: encryptedCipher.Favorite,
|
||||||
edit: encryptedCipher.Edit,
|
edit: encryptedCipher.Edit,
|
||||||
@@ -38,62 +41,68 @@ angular
|
|||||||
icon: null
|
icon: null
|
||||||
};
|
};
|
||||||
|
|
||||||
var cipherData = encryptedCipher.Data;
|
var i;
|
||||||
if (cipherData) {
|
switch (cipher.type) {
|
||||||
cipher.name = cryptoService.decrypt(cipherData.Name, key);
|
case constants.cipherType.login:
|
||||||
cipher.notes = _service.decryptProperty(cipherData.Notes, key, true, false);
|
cipher.login = {
|
||||||
cipher.fields = _service.decryptFields(key, cipherData.Fields);
|
username: _service.decryptProperty(encryptedCipher.Login.Username, key, true, false),
|
||||||
|
password: _service.decryptProperty(encryptedCipher.Login.Password, key, true, false),
|
||||||
var dataObj = {};
|
totp: _service.decryptProperty(encryptedCipher.Login.Totp, key, true, false),
|
||||||
switch (cipher.type) {
|
uris: null
|
||||||
case constants.cipherType.login:
|
};
|
||||||
dataObj.uri = _service.decryptProperty(cipherData.Uri, key, true, false);
|
if (encryptedCipher.Login.Uris) {
|
||||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
cipher.login.uris = [];
|
||||||
dataObj.password = _service.decryptProperty(cipherData.Password, key, true, false);
|
for (i = 0; i < encryptedCipher.Login.Uris.length; i++) {
|
||||||
dataObj.totp = _service.decryptProperty(cipherData.Totp, key, true, false);
|
cipher.login.uris.push({
|
||||||
cipher.login = dataObj;
|
uri: _service.decryptProperty(encryptedCipher.Login.Uris[i].Uri, key, true, false),
|
||||||
cipher.icon = 'fa-globe';
|
match: encryptedCipher.Login.Uris[i].Match
|
||||||
break;
|
});
|
||||||
case constants.cipherType.secureNote:
|
}
|
||||||
dataObj.type = cipherData.Type;
|
}
|
||||||
cipher.secureNote = dataObj;
|
cipher.icon = 'fa-globe';
|
||||||
cipher.icon = 'fa-sticky-note-o';
|
break;
|
||||||
break;
|
case constants.cipherType.secureNote:
|
||||||
case constants.cipherType.card:
|
cipher.secureNote = {
|
||||||
dataObj.cardholderName = _service.decryptProperty(cipherData.CardholderName, key, true, false);
|
type: encryptedCipher.SecureNote.Type
|
||||||
dataObj.number = _service.decryptProperty(cipherData.Number, key, true, false);
|
};
|
||||||
dataObj.brand = _service.decryptProperty(cipherData.Brand, key, true, false);
|
cipher.icon = 'fa-sticky-note-o';
|
||||||
dataObj.expMonth = _service.decryptProperty(cipherData.ExpMonth, key, true, false);
|
break;
|
||||||
dataObj.expYear = _service.decryptProperty(cipherData.ExpYear, key, true, false);
|
case constants.cipherType.card:
|
||||||
dataObj.code = _service.decryptProperty(cipherData.Code, key, true, false);
|
cipher.card = {
|
||||||
cipher.card = dataObj;
|
cardholderName: _service.decryptProperty(encryptedCipher.Card.CardholderName, key, true, false),
|
||||||
cipher.icon = 'fa-credit-card';
|
number: _service.decryptProperty(encryptedCipher.Card.Number, key, true, false),
|
||||||
break;
|
brand: _service.decryptProperty(encryptedCipher.Card.Brand, key, true, false),
|
||||||
case constants.cipherType.identity:
|
expMonth: _service.decryptProperty(encryptedCipher.Card.ExpMonth, key, true, false),
|
||||||
dataObj.title = _service.decryptProperty(cipherData.Title, key, true, false);
|
expYear: _service.decryptProperty(encryptedCipher.Card.ExpYear, key, true, false),
|
||||||
dataObj.firstName = _service.decryptProperty(cipherData.FirstName, key, true, false);
|
code: _service.decryptProperty(encryptedCipher.Card.Code, key, true, false)
|
||||||
dataObj.middleName = _service.decryptProperty(cipherData.MiddleName, key, true, false);
|
};
|
||||||
dataObj.lastName = _service.decryptProperty(cipherData.LastName, key, true, false);
|
cipher.icon = 'fa-credit-card';
|
||||||
dataObj.address1 = _service.decryptProperty(cipherData.Address1, key, true, false);
|
break;
|
||||||
dataObj.address2 = _service.decryptProperty(cipherData.Address2, key, true, false);
|
case constants.cipherType.identity:
|
||||||
dataObj.address3 = _service.decryptProperty(cipherData.Address3, key, true, false);
|
cipher.identity = {
|
||||||
dataObj.city = _service.decryptProperty(cipherData.City, key, true, false);
|
title: _service.decryptProperty(encryptedCipher.Identity.Title, key, true, false),
|
||||||
dataObj.state = _service.decryptProperty(cipherData.State, key, true, false);
|
firstName: _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, false),
|
||||||
dataObj.postalCode = _service.decryptProperty(cipherData.PostalCode, key, true, false);
|
middleName: _service.decryptProperty(encryptedCipher.Identity.MiddleName, key, true, false),
|
||||||
dataObj.country = _service.decryptProperty(cipherData.Country, key, true, false);
|
lastName: _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, false),
|
||||||
dataObj.company = _service.decryptProperty(cipherData.Company, key, true, false);
|
address1: _service.decryptProperty(encryptedCipher.Identity.Address1, key, true, false),
|
||||||
dataObj.email = _service.decryptProperty(cipherData.Email, key, true, false);
|
address2: _service.decryptProperty(encryptedCipher.Identity.Address2, key, true, false),
|
||||||
dataObj.phone = _service.decryptProperty(cipherData.Phone, key, true, false);
|
address3: _service.decryptProperty(encryptedCipher.Identity.Address3, key, true, false),
|
||||||
dataObj.ssn = _service.decryptProperty(cipherData.SSN, key, true, false);
|
city: _service.decryptProperty(encryptedCipher.Identity.City, key, true, false),
|
||||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
state: _service.decryptProperty(encryptedCipher.Identity.State, key, true, false),
|
||||||
dataObj.passportNumber = _service.decryptProperty(cipherData.PassportNumber, key, true, false);
|
postalCode: _service.decryptProperty(encryptedCipher.Identity.PostalCode, key, true, false),
|
||||||
dataObj.licenseNumber = _service.decryptProperty(cipherData.LicenseNumber, key, true, false);
|
country: _service.decryptProperty(encryptedCipher.Identity.Country, key, true, false),
|
||||||
cipher.identity = dataObj;
|
company: _service.decryptProperty(encryptedCipher.Identity.Company, key, true, false),
|
||||||
cipher.icon = 'fa-id-card-o';
|
email: _service.decryptProperty(encryptedCipher.Identity.Email, key, true, false),
|
||||||
break;
|
phone: _service.decryptProperty(encryptedCipher.Identity.Phone, key, true, false),
|
||||||
default:
|
ssn: _service.decryptProperty(encryptedCipher.Identity.SSN, key, true, false),
|
||||||
break;
|
username: _service.decryptProperty(encryptedCipher.Identity.Username, key, true, false),
|
||||||
}
|
passportNumber: _service.decryptProperty(encryptedCipher.Identity.PassportNumber, key, true, false),
|
||||||
|
licenseNumber: _service.decryptProperty(encryptedCipher.Identity.LicenseNumber, key, true, false)
|
||||||
|
};
|
||||||
|
cipher.icon = 'fa-id-card-o';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!encryptedCipher.Attachments) {
|
if (!encryptedCipher.Attachments) {
|
||||||
@@ -101,7 +110,7 @@ angular
|
|||||||
}
|
}
|
||||||
|
|
||||||
cipher.attachments = [];
|
cipher.attachments = [];
|
||||||
for (var i = 0; i < encryptedCipher.Attachments.length; i++) {
|
for (i = 0; i < encryptedCipher.Attachments.length; i++) {
|
||||||
cipher.attachments.push(_service.decryptAttachment(key, encryptedCipher.Attachments[i]));
|
cipher.attachments.push(_service.decryptAttachment(key, encryptedCipher.Attachments[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +130,7 @@ angular
|
|||||||
organizationId: encryptedCipher.OrganizationId,
|
organizationId: encryptedCipher.OrganizationId,
|
||||||
collectionIds: encryptedCipher.CollectionIds || [],
|
collectionIds: encryptedCipher.CollectionIds || [],
|
||||||
'type': encryptedCipher.Type,
|
'type': encryptedCipher.Type,
|
||||||
|
name: _service.decryptProperty(encryptedCipher.Name, key, false, true),
|
||||||
folderId: encryptedCipher.FolderId,
|
folderId: encryptedCipher.FolderId,
|
||||||
favorite: encryptedCipher.Favorite,
|
favorite: encryptedCipher.Favorite,
|
||||||
edit: encryptedCipher.Edit,
|
edit: encryptedCipher.Edit,
|
||||||
@@ -130,59 +140,56 @@ angular
|
|||||||
icon: null
|
icon: null
|
||||||
};
|
};
|
||||||
|
|
||||||
var cipherData = encryptedCipher.Data;
|
switch (cipher.type) {
|
||||||
if (cipherData) {
|
case constants.cipherType.login:
|
||||||
cipher.name = _service.decryptProperty(cipherData.Name, key, false, true);
|
cipher.subTitle = _service.decryptProperty(encryptedCipher.Login.Username, key, true, true);
|
||||||
|
cipher.meta.password = _service.decryptProperty(encryptedCipher.Login.Password, key, true, true);
|
||||||
var dataObj = {};
|
cipher.meta.uri = null;
|
||||||
switch (cipher.type) {
|
if (encryptedCipher.Login.Uris && encryptedCipher.Login.Uris.length) {
|
||||||
case constants.cipherType.login:
|
cipher.meta.uri = _service.decryptProperty(encryptedCipher.Login.Uris[0].Uri, key, true, true);
|
||||||
cipher.subTitle = _service.decryptProperty(cipherData.Username, key, true, true);
|
}
|
||||||
cipher.meta.password = _service.decryptProperty(cipherData.Password, key, true, true);
|
setLoginIcon(cipher, cipher.meta.uri, true);
|
||||||
cipher.meta.uri = _service.decryptProperty(cipherData.Uri, key, true, true);
|
break;
|
||||||
setLoginIcon(cipher, cipher.meta.uri, true);
|
case constants.cipherType.secureNote:
|
||||||
break;
|
|
||||||
case constants.cipherType.secureNote:
|
|
||||||
cipher.subTitle = null;
|
|
||||||
cipher.icon = 'fa-sticky-note-o';
|
|
||||||
break;
|
|
||||||
case constants.cipherType.card:
|
|
||||||
cipher.subTitle = '';
|
|
||||||
cipher.meta.number = _service.decryptProperty(cipherData.Number, key, true, true);
|
|
||||||
var brand = _service.decryptProperty(cipherData.Brand, key, true, true);
|
|
||||||
if (brand) {
|
|
||||||
cipher.subTitle = brand;
|
|
||||||
}
|
|
||||||
if (cipher.meta.number && cipher.meta.number.length >= 4) {
|
|
||||||
if (cipher.subTitle !== '') {
|
|
||||||
cipher.subTitle += ', ';
|
|
||||||
}
|
|
||||||
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
|
|
||||||
}
|
|
||||||
cipher.icon = 'fa-credit-card';
|
|
||||||
break;
|
|
||||||
case constants.cipherType.identity:
|
|
||||||
var firstName = _service.decryptProperty(cipherData.FirstName, key, true, true);
|
|
||||||
var lastName = _service.decryptProperty(cipherData.LastName, key, true, true);
|
|
||||||
cipher.subTitle = '';
|
|
||||||
if (firstName) {
|
|
||||||
cipher.subTitle = firstName;
|
|
||||||
}
|
|
||||||
if (lastName) {
|
|
||||||
if (cipher.subTitle !== '') {
|
|
||||||
cipher.subTitle += ' ';
|
|
||||||
}
|
|
||||||
cipher.subTitle += lastName;
|
|
||||||
}
|
|
||||||
cipher.icon = 'fa-id-card-o';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cipher.subTitle === '') {
|
|
||||||
cipher.subTitle = null;
|
cipher.subTitle = null;
|
||||||
}
|
cipher.icon = 'fa-sticky-note-o';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.card:
|
||||||
|
cipher.subTitle = '';
|
||||||
|
cipher.meta.number = _service.decryptProperty(encryptedCipher.Card.Number, key, true, true);
|
||||||
|
var brand = _service.decryptProperty(encryptedCipher.Card.Brand, key, true, true);
|
||||||
|
if (brand) {
|
||||||
|
cipher.subTitle = brand;
|
||||||
|
}
|
||||||
|
if (cipher.meta.number && cipher.meta.number.length >= 4) {
|
||||||
|
if (cipher.subTitle !== '') {
|
||||||
|
cipher.subTitle += ', ';
|
||||||
|
}
|
||||||
|
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
|
||||||
|
}
|
||||||
|
cipher.icon = 'fa-credit-card';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.identity:
|
||||||
|
var firstName = _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, true);
|
||||||
|
var lastName = _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, true);
|
||||||
|
cipher.subTitle = '';
|
||||||
|
if (firstName) {
|
||||||
|
cipher.subTitle = firstName;
|
||||||
|
}
|
||||||
|
if (lastName) {
|
||||||
|
if (cipher.subTitle !== '') {
|
||||||
|
cipher.subTitle += ' ';
|
||||||
|
}
|
||||||
|
cipher.subTitle += lastName;
|
||||||
|
}
|
||||||
|
cipher.icon = 'fa-id-card-o';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipher.subTitle === '') {
|
||||||
|
cipher.subTitle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
@@ -389,15 +396,24 @@ angular
|
|||||||
fields: _service.encryptFields(unencryptedCipher.fields, key)
|
fields: _service.encryptFields(unencryptedCipher.fields, key)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var i;
|
||||||
switch (cipher.type) {
|
switch (cipher.type) {
|
||||||
case constants.cipherType.login:
|
case constants.cipherType.login:
|
||||||
var loginData = unencryptedCipher.login;
|
var loginData = unencryptedCipher.login;
|
||||||
cipher.login = {
|
cipher.login = {
|
||||||
uri: encryptProperty(loginData.uri, key),
|
|
||||||
username: encryptProperty(loginData.username, key),
|
username: encryptProperty(loginData.username, key),
|
||||||
password: encryptProperty(loginData.password, key),
|
password: encryptProperty(loginData.password, key),
|
||||||
totp: encryptProperty(loginData.totp, key)
|
totp: encryptProperty(loginData.totp, key)
|
||||||
};
|
};
|
||||||
|
if (loginData.uris && loginData.uris.length) {
|
||||||
|
cipher.login.uris = [];
|
||||||
|
for (i = 0; i < loginData.uris.length; i++) {
|
||||||
|
cipher.login.uris.push({
|
||||||
|
uri: encryptProperty(loginData.uris[i].uri, key),
|
||||||
|
match: loginData.uris[i].match
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case constants.cipherType.secureNote:
|
case constants.cipherType.secureNote:
|
||||||
cipher.secureNote = {
|
cipher.secureNote = {
|
||||||
@@ -444,7 +460,7 @@ angular
|
|||||||
|
|
||||||
if (unencryptedCipher.attachments && attachments) {
|
if (unencryptedCipher.attachments && attachments) {
|
||||||
cipher.attachments = {};
|
cipher.attachments = {};
|
||||||
for (var i = 0; i < unencryptedCipher.attachments.length; i++) {
|
for (i = 0; i < unencryptedCipher.attachments.length; i++) {
|
||||||
cipher.attachments[unencryptedCipher.attachments[i].id] =
|
cipher.attachments[unencryptedCipher.attachments[i].id] =
|
||||||
cryptoService.encrypt(unencryptedCipher.attachments[i].fileName, key);
|
cryptoService.encrypt(unencryptedCipher.attachments[i].fileName, key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ angular
|
|||||||
if (key.macKey && encPieces.length > 2) {
|
if (key.macKey && encPieces.length > 2) {
|
||||||
var macBytes = forge.util.decode64(encPieces[2]);
|
var macBytes = forge.util.decode64(encPieces[2]);
|
||||||
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
|
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
|
||||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
if (!macsEqual(macBytes, computedMacBytes)) {
|
||||||
console.error('MAC failed.');
|
console.error('MAC failed.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -623,6 +623,10 @@ angular
|
|||||||
throw 'Encryption key unavailable.';
|
throw 'Encryption key unavailable.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key.macKey && !macBuf) {
|
||||||
|
throw 'macBuf required for this type of key.';
|
||||||
|
}
|
||||||
|
|
||||||
if (encType !== key.encType) {
|
if (encType !== key.encType) {
|
||||||
throw 'encType unavailable.';
|
throw 'encType unavailable.';
|
||||||
}
|
}
|
||||||
@@ -646,7 +650,7 @@ angular
|
|||||||
if (computedMacBuf === null) {
|
if (computedMacBuf === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return macsEqualWC(keyBuf.macKey, macBuf, computedMacBuf);
|
return macsEqualWC(macBuf, computedMacBuf);
|
||||||
}).then(function (macsMatch) {
|
}).then(function (macsMatch) {
|
||||||
if (macsMatch === false) {
|
if (macsMatch === false) {
|
||||||
console.error('MAC failed.');
|
console.error('MAC failed.');
|
||||||
@@ -704,7 +708,7 @@ angular
|
|||||||
if (key && key.macKey && encPieces.length > 1) {
|
if (key && key.macKey && encPieces.length > 1) {
|
||||||
var macBytes = forge.util.decode64(encPieces[1]);
|
var macBytes = forge.util.decode64(encPieces[1]);
|
||||||
var computedMacBytes = computeMac(ctBytes, key.macKey, false);
|
var computedMacBytes = computeMac(ctBytes, key.macKey, false);
|
||||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
if (!macsEqual(macBytes, computedMacBytes)) {
|
||||||
console.error('MAC failed.');
|
console.error('MAC failed.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -747,10 +751,11 @@ angular
|
|||||||
|
|
||||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||||
function macsEqual(macKey, mac1, mac2) {
|
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||||
|
function macsEqual(mac1, mac2) {
|
||||||
var hmac = forge.hmac.create();
|
var hmac = forge.hmac.create();
|
||||||
|
|
||||||
hmac.start('sha256', macKey);
|
hmac.start('sha256', getRandomBytes(32));
|
||||||
hmac.update(mac1);
|
hmac.update(mac1);
|
||||||
mac1 = hmac.digest().getBytes();
|
mac1 = hmac.digest().getBytes();
|
||||||
|
|
||||||
@@ -761,11 +766,14 @@ angular
|
|||||||
return mac1 === mac2;
|
return mac1 === mac2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function macsEqualWC(macKeyBuf, mac1Buf, mac2Buf) {
|
function macsEqualWC(mac1Buf, mac2Buf) {
|
||||||
var mac1,
|
var mac1,
|
||||||
macKey;
|
macKey;
|
||||||
|
|
||||||
return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
var compareKey = new Uint8Array(32);
|
||||||
|
_crypto.getRandomValues(compareKey);
|
||||||
|
|
||||||
|
return window.crypto.subtle.importKey('raw', compareKey.buffer, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
||||||
.then(function (key) {
|
.then(function (key) {
|
||||||
macKey = key;
|
macKey = key;
|
||||||
return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac1Buf);
|
return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac1Buf);
|
||||||
@@ -932,5 +940,15 @@ angular
|
|||||||
return new Uint8Array(result);
|
return new Uint8Array(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRandomBytes(byteLength) {
|
||||||
|
var bytes = new Uint32Array(byteLength / 4);
|
||||||
|
_crypto.getRandomValues(bytes);
|
||||||
|
var buffer = forge.util.createBuffer();
|
||||||
|
for (var i = 0; i < bytes.length; i++) {
|
||||||
|
buffer.putInt32(bytes[i]);
|
||||||
|
}
|
||||||
|
return buffer.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
return _service;
|
return _service;
|
||||||
});
|
});
|
||||||
269
src/app/services/eventService.js
Normal file
269
src/app/services/eventService.js
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('eventService', function (constants, $filter) {
|
||||||
|
var _service = {};
|
||||||
|
|
||||||
|
_service.getDefaultDateFilters = function () {
|
||||||
|
var d = new Date();
|
||||||
|
var filterEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59);
|
||||||
|
d.setDate(d.getDate() - 30);
|
||||||
|
var filterStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: filterStart,
|
||||||
|
end: filterEnd
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.formatDateFilters = function (filterStart, filterEnd) {
|
||||||
|
var result = {
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
var format = 'yyyy-MM-ddTHH:mm';
|
||||||
|
result.start = $filter('date')(filterStart, format + 'Z', 'UTC');
|
||||||
|
result.end = $filter('date')(filterEnd, format + ':59.999Z', 'UTC');
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
if (!result.start || !result.end || result.end < result.start) {
|
||||||
|
result.error = 'Invalid date range.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.getEventInfo = function (ev, options) {
|
||||||
|
options = options || {
|
||||||
|
cipherInfo: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var appInfo = getAppInfo(ev);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: getEventMessage(ev, options),
|
||||||
|
appIcon: appInfo.icon,
|
||||||
|
appName: appInfo.name
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function getEventMessage(ev, options) {
|
||||||
|
var msg = '';
|
||||||
|
switch (ev.Type) {
|
||||||
|
// User
|
||||||
|
case constants.eventType.User_LoggedIn:
|
||||||
|
msg = 'Logged in.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_ChangedPassword:
|
||||||
|
msg = 'Changed account password.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_Enabled2fa:
|
||||||
|
msg = 'Enabled two-step login.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_Disabled2fa:
|
||||||
|
msg = 'Disabled two-step login.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_Recovered2fa:
|
||||||
|
msg = 'Recovered account from two-step login.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_FailedLogIn:
|
||||||
|
msg = 'Login attempt failed with incorrect password.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.User_FailedLogIn2fa:
|
||||||
|
msg = 'Login attempt failed with incorrect two-step login.';
|
||||||
|
break;
|
||||||
|
// Cipher
|
||||||
|
case constants.eventType.Cipher_Created:
|
||||||
|
msg = options.cipherInfo ? 'Created item ' + formatCipherId(ev) + '.' : 'Created.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_Updated:
|
||||||
|
msg = options.cipherInfo ? 'Edited item ' + formatCipherId(ev) + '.' : 'Edited.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_Deleted:
|
||||||
|
msg = options.cipherInfo ? 'Deleted item ' + formatCipherId(ev) + '.' : 'Deleted';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_AttachmentCreated:
|
||||||
|
msg = options.cipherInfo ? 'Created attachment for item ' + formatCipherId(ev) + '.' :
|
||||||
|
'Created attachment.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_AttachmentDeleted:
|
||||||
|
msg = options.cipherInfo ? 'Deleted attachment for item ' + formatCipherId(ev) + '.' :
|
||||||
|
'Deleted attachment.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_Shared:
|
||||||
|
msg = options.cipherInfo ? 'Shared item ' + formatCipherId(ev) + '.' : 'Shared.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Cipher_UpdatedCollections:
|
||||||
|
msg = options.cipherInfo ? 'Update collections for item ' + formatCipherId(ev) + '.' :
|
||||||
|
'Updated collections.';
|
||||||
|
break;
|
||||||
|
// Collection
|
||||||
|
case constants.eventType.Collection_Created:
|
||||||
|
msg = 'Created collection ' + formatCollectionId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Collection_Updated:
|
||||||
|
msg = 'Edited collection ' + formatCollectionId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Collection_Deleted:
|
||||||
|
msg = 'Deleted collection ' + formatCollectionId(ev) + '.';
|
||||||
|
break;
|
||||||
|
// Group
|
||||||
|
case constants.eventType.Group_Created:
|
||||||
|
msg = 'Created group ' + formatGroupId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Group_Updated:
|
||||||
|
msg = 'Edited group ' + formatGroupId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.Group_Deleted:
|
||||||
|
msg = 'Deleted group ' + formatGroupId(ev) + '.';
|
||||||
|
break;
|
||||||
|
// Org user
|
||||||
|
case constants.eventType.OrganizationUser_Invited:
|
||||||
|
msg = 'Invited user ' + formatOrgUserId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.OrganizationUser_Confirmed:
|
||||||
|
msg = 'Confirmed user ' + formatOrgUserId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.OrganizationUser_Updated:
|
||||||
|
msg = 'Edited user ' + formatOrgUserId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.OrganizationUser_Removed:
|
||||||
|
msg = 'Removed user ' + formatOrgUserId(ev) + '.';
|
||||||
|
break;
|
||||||
|
case constants.eventType.OrganizationUser_UpdatedGroups:
|
||||||
|
msg = 'Edited groups for user ' + formatOrgUserId(ev) + '.';
|
||||||
|
break;
|
||||||
|
// Org
|
||||||
|
case constants.eventType.Organization_Updated:
|
||||||
|
msg = 'Edited organization settings.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg === '' ? null : msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppInfo(ev) {
|
||||||
|
var appInfo = {
|
||||||
|
icon: 'fa-globe',
|
||||||
|
name: 'Unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (ev.DeviceType) {
|
||||||
|
case constants.deviceType.android:
|
||||||
|
appInfo.icon = 'fa-android';
|
||||||
|
appInfo.name = 'Mobile App - Android';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.ios:
|
||||||
|
appInfo.icon = 'fa-apple';
|
||||||
|
appInfo.name = 'Mobile App - iOS';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.uwp:
|
||||||
|
appInfo.icon = 'fa-windows';
|
||||||
|
appInfo.name = 'Mobile App - Windows';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.chromeExt:
|
||||||
|
appInfo.icon = 'fa-chrome';
|
||||||
|
appInfo.name = 'Extension - Chrome';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.firefoxExt:
|
||||||
|
appInfo.icon = 'fa-firefox';
|
||||||
|
appInfo.name = 'Extension - Firefox';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.operaExt:
|
||||||
|
appInfo.icon = 'fa-opera';
|
||||||
|
appInfo.name = 'Extension - Opera';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.edgeExt:
|
||||||
|
appInfo.icon = 'fa-edge';
|
||||||
|
appInfo.name = 'Extension - Edge';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.vivaldiExt:
|
||||||
|
appInfo.icon = 'fa-puzzle-piece';
|
||||||
|
appInfo.name = 'Extension - Vivaldi';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.windowsDesktop:
|
||||||
|
appInfo.icon = 'fa-windows';
|
||||||
|
appInfo.name = 'Desktop - Windows';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.macOsDesktop:
|
||||||
|
appInfo.icon = 'fa-apple';
|
||||||
|
appInfo.name = 'Desktop - macOS';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.linuxDesktop:
|
||||||
|
appInfo.icon = 'fa-linux';
|
||||||
|
appInfo.name = 'Desktop - Linux';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.chrome:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Chrome';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.firefox:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Firefox';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.opera:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Opera';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.safari:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Safari';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.vivaldi:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Vivaldi';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.edge:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Edge';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.ie:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - IE';
|
||||||
|
break;
|
||||||
|
case constants.deviceType.unknown:
|
||||||
|
appInfo.icon = 'fa-globe';
|
||||||
|
appInfo.name = 'Web Vault - Unknown';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCipherId(ev) {
|
||||||
|
var shortId = ev.CipherId.substring(0, 8);
|
||||||
|
if (!ev.OrganizationId) {
|
||||||
|
return '<code>' + shortId + '</code>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<a title="View item ' + ev.CipherId + '" ui-sref="backend.org.vault({orgId:\'' + ev.OrganizationId + '\',search:\'' + shortId + '\',viewEvents:\'' + ev.CipherId + '\'})">' +
|
||||||
|
'<code>' + shortId + '</code></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatGroupId(ev) {
|
||||||
|
var shortId = ev.GroupId.substring(0, 8);
|
||||||
|
return '<a title="View group ' + ev.GroupId + '" ui-sref="backend.org.groups({orgId:\'' + ev.OrganizationId + '\',search:\'' + shortId + '\'})">' +
|
||||||
|
'<code>' + shortId + '</code></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCollectionId(ev) {
|
||||||
|
var shortId = ev.CollectionId.substring(0, 8);
|
||||||
|
return '<a title="View collection ' + ev.CollectionId + '" ui-sref="backend.org.collections({orgId:\'' + ev.OrganizationId + '\',search:\'' + shortId + '\'})">' +
|
||||||
|
'<code>' + shortId + '</code></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatOrgUserId(ev) {
|
||||||
|
var shortId = ev.OrganizationUserId.substring(0, 8);
|
||||||
|
return '<a title="View user ' + ev.OrganizationUserId + '" ui-sref="backend.org.people({orgId:\'' + ev.OrganizationId + '\',search:\'' + shortId + '\'})">' +
|
||||||
|
'<code>' + shortId + '</code></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
var _passwordFieldNames = [
|
var _passwordFieldNames = [
|
||||||
'password', 'pass word', 'passphrase', 'pass phrase',
|
'password', 'pass word', 'passphrase', 'pass phrase',
|
||||||
'pass', 'code', 'code word', 'codeword',
|
'pass', 'code', 'code word', 'codeword',
|
||||||
'secret', 'secret word',
|
'secret', 'secret word', 'personpwd',
|
||||||
'key', 'keyword', 'key word', 'keyphrase', 'key phrase',
|
'key', 'keyword', 'key word', 'keyphrase', 'key phrase',
|
||||||
'form_pw', 'wppassword', 'pin', 'pwd', 'pw', 'pword', 'passwd',
|
'form_pw', 'wppassword', 'pin', 'pwd', 'pw', 'pword', 'passwd',
|
||||||
'p', 'serial', 'serial#', 'license key', 'reg #',
|
'p', 'serial', 'serial#', 'license key', 'reg #',
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
'user', 'name', 'user name', 'username', 'login name',
|
'user', 'name', 'user name', 'username', 'login name',
|
||||||
'email', 'e-mail', 'id', 'userid', 'user id',
|
'email', 'e-mail', 'id', 'userid', 'user id',
|
||||||
'login', 'form_loginname', 'wpname', 'mail',
|
'login', 'form_loginname', 'wpname', 'mail',
|
||||||
'loginid', 'login id', 'log',
|
'loginid', 'login id', 'log', 'personlogin',
|
||||||
'first name', 'last name', 'card#', 'account #',
|
'first name', 'last name', 'card#', 'account #',
|
||||||
'member', 'member #',
|
'member', 'member #',
|
||||||
|
|
||||||
@@ -191,14 +191,10 @@
|
|||||||
|
|
||||||
function fixUri(uri) {
|
function fixUri(uri) {
|
||||||
uri = uri.toLowerCase().trim();
|
uri = uri.toLowerCase().trim();
|
||||||
if (!uri.startsWith('http') && uri.indexOf('.') >= 0) {
|
if (uri.indexOf('://') === -1 && uri.indexOf('.') >= 0) {
|
||||||
uri = 'http://' + uri;
|
uri = 'http://' + uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimUri(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
function trimUri(uri) {
|
|
||||||
if (uri.length > 1000) {
|
if (uri.length > 1000) {
|
||||||
return uri.substring(0, 1000);
|
return uri.substring(0, 1000);
|
||||||
}
|
}
|
||||||
@@ -206,6 +202,43 @@
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeUriArray(uri) {
|
||||||
|
if (!uri) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof uri === 'string') {
|
||||||
|
return [{
|
||||||
|
uri: fixUri(uri),
|
||||||
|
match: null
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.length) {
|
||||||
|
var returnArr = [];
|
||||||
|
for (var i = 0; i < uri.length; i++) {
|
||||||
|
returnArr.push({
|
||||||
|
uri: fixUri(uri[i]),
|
||||||
|
match: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSingleRowCsv(rowData) {
|
||||||
|
if (!rowData || rowData === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var parsedRow = Papa.parse(rowData);
|
||||||
|
if (parsedRow && parsedRow.data && parsedRow.data.length && parsedRow.data[0].length) {
|
||||||
|
return parsedRow.data[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function parseCsvErrors(results) {
|
function parseCsvErrors(results) {
|
||||||
if (results.errors && results.errors.length) {
|
if (results.errors && results.errors.length) {
|
||||||
for (var i = 0; i < results.errors.length; i++) {
|
for (var i = 0; i < results.errors.length; i++) {
|
||||||
@@ -236,6 +269,63 @@
|
|||||||
}, errorCallback);
|
}, errorCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ref https://stackoverflow.com/a/5911300
|
||||||
|
function getCardType(number) {
|
||||||
|
if (!number) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visa
|
||||||
|
var re = new RegExp('^4');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Visa';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mastercard
|
||||||
|
// Updated for Mastercard 2017 BINs expansion
|
||||||
|
if (/^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/.test(number)) {
|
||||||
|
return 'Mastercard';
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMEX
|
||||||
|
re = new RegExp('^3[47]');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Amex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover
|
||||||
|
re = new RegExp('^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Discover';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diners
|
||||||
|
re = new RegExp('^36');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Diners Club';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diners - Carte Blanche
|
||||||
|
re = new RegExp('^30[0-5]');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Diners Club';
|
||||||
|
}
|
||||||
|
|
||||||
|
// JCB
|
||||||
|
re = new RegExp('^35(2[89]|[3-8][0-9])');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'JCB';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visa Electron
|
||||||
|
re = new RegExp('^(4026|417500|4508|4844|491(3|7))');
|
||||||
|
if (number.match(re) != null) {
|
||||||
|
return 'Visa';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// importers
|
// importers
|
||||||
|
|
||||||
function importBitwardenCsv(file, success, error) {
|
function importBitwardenCsv(file, success, error) {
|
||||||
@@ -303,17 +393,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (value.type) {
|
var valueType = value.type ? value.type.toLowerCase() : null;
|
||||||
case 'login': case null: case undefined:
|
switch (valueType) {
|
||||||
|
case 'login':
|
||||||
|
case null:
|
||||||
|
case undefined:
|
||||||
cipher.type = constants.cipherType.login;
|
cipher.type = constants.cipherType.login;
|
||||||
|
|
||||||
var totp = value.login_totp || value.totp;
|
var totp = value.login_totp || value.totp;
|
||||||
var uri = value.login_uri || value.uri;
|
var uris = parseSingleRowCsv(value.login_uri || value.uri);
|
||||||
var username = value.login_username || value.username;
|
var username = value.login_username || value.username;
|
||||||
var password = value.login_password || value.password;
|
var password = value.login_password || value.password;
|
||||||
cipher.login = {
|
cipher.login = {
|
||||||
totp: totp && totp !== '' ? totp : null,
|
totp: totp && totp !== '' ? totp : null,
|
||||||
uri: uri && uri !== '' ? trimUri(uri) : null,
|
uris: makeUriArray(uris),
|
||||||
username: username && username !== '' ? username : null,
|
username: username && username !== '' ? username : null,
|
||||||
password: password && password !== '' ? password : null
|
password: password && password !== '' ? password : null
|
||||||
};
|
};
|
||||||
@@ -430,17 +523,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (value.type) {
|
var valueType = value.type ? value.type.toLowerCase() : null;
|
||||||
case 'login': case null: case undefined:
|
switch (valueType) {
|
||||||
|
case 'login':
|
||||||
|
case null:
|
||||||
|
case undefined:
|
||||||
cipher.type = constants.cipherType.login;
|
cipher.type = constants.cipherType.login;
|
||||||
|
|
||||||
var totp = value.login_totp || value.totp;
|
var totp = value.login_totp || value.totp;
|
||||||
var uri = value.login_uri || value.uri;
|
var uris = parseSingleRowCsv(value.login_uri || value.uri);
|
||||||
var username = value.login_username || value.username;
|
var username = value.login_username || value.username;
|
||||||
var password = value.login_password || value.password;
|
var password = value.login_password || value.password;
|
||||||
cipher.login = {
|
cipher.login = {
|
||||||
totp: totp && totp !== '' ? totp : null,
|
totp: totp && totp !== '' ? totp : null,
|
||||||
uri: uri && uri !== '' ? trimUri(uri) : null,
|
uris: makeUriArray(uris),
|
||||||
username: username && username !== '' ? username : null,
|
username: username && username !== '' ? username : null,
|
||||||
password: password && password !== '' ? password : null
|
password: password && password !== '' ? password : null
|
||||||
};
|
};
|
||||||
@@ -557,6 +653,28 @@
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseCard(value) {
|
||||||
|
var cardData = {
|
||||||
|
cardholderName: value.ccname && value.ccname !== '' ? value.ccname : null,
|
||||||
|
number: value.ccnum && value.ccnum !== '' ? value.ccnum : null,
|
||||||
|
brand: value.ccnum && value.ccnum !== '' ? getCardType(value.ccnum) : null,
|
||||||
|
code: value.cccsc && value.cccsc !== '' ? value.cccsc : null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value.ccexp && value.ccexp !== '' && value.ccexp.indexOf('-') > -1) {
|
||||||
|
var ccexpParts = value.ccexp.split('-');
|
||||||
|
if (ccexpParts.length > 1) {
|
||||||
|
cardData.expYear = ccexpParts[0];
|
||||||
|
cardData.expMonth = ccexpParts[1];
|
||||||
|
if (cardData.expMonth.length === 2 && cardData.expMonth[0] === '0') {
|
||||||
|
cardData.expMonth = cardData.expMonth[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardData;
|
||||||
|
}
|
||||||
|
|
||||||
function parseData(data) {
|
function parseData(data) {
|
||||||
var folders = [],
|
var folders = [],
|
||||||
ciphers = [],
|
ciphers = [],
|
||||||
@@ -579,15 +697,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cipher = {
|
var cipher;
|
||||||
favorite: org ? false : value.fav === '1',
|
if (value.hasOwnProperty('profilename') && value.hasOwnProperty('profilelanguage')) {
|
||||||
name: value.name && value.name !== '' ? value.name : '--',
|
// form fill
|
||||||
type: value.url === 'http://sn' ? constants.cipherType.secureNote : constants.cipherType.login
|
cipher = {
|
||||||
};
|
favorite: false,
|
||||||
|
name: value.profilename && value.profilename !== '' ? value.profilename : '--',
|
||||||
|
type: constants.cipherType.card
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value.title !== '' || value.firstname !== '' || value.lastname !== '' ||
|
||||||
|
value.address1 !== '' || value.phone !== '' || value.username !== '' ||
|
||||||
|
value.email !== '') {
|
||||||
|
cipher.type = constants.cipherType.identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// site or secure note
|
||||||
|
cipher = {
|
||||||
|
favorite: org ? false : value.fav === '1',
|
||||||
|
name: value.name && value.name !== '' ? value.name : '--',
|
||||||
|
type: value.url === 'http://sn' ? constants.cipherType.secureNote : constants.cipherType.login
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (cipher.type === constants.cipherType.login) {
|
if (cipher.type === constants.cipherType.login) {
|
||||||
cipher.login = {
|
cipher.login = {
|
||||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
uris: makeUriArray(value.url),
|
||||||
username: value.username && value.username !== '' ? value.username : null,
|
username: value.username && value.username !== '' ? value.username : null,
|
||||||
password: value.password && value.password !== '' ? value.password : null
|
password: value.password && value.password !== '' ? value.password : null
|
||||||
};
|
};
|
||||||
@@ -644,6 +780,46 @@
|
|||||||
cipher.notes = value.extra && value.extra !== '' ? value.extra : null;
|
cipher.notes = value.extra && value.extra !== '' ? value.extra : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (cipher.type === constants.cipherType.card) {
|
||||||
|
cipher.card = parseCard(value);
|
||||||
|
cipher.notes = value.notes && value.notes !== '' ? value.notes : null;
|
||||||
|
}
|
||||||
|
else if (cipher.type === constants.cipherType.identity) {
|
||||||
|
cipher.identity = {
|
||||||
|
title: value.title && value.title !== '' ? value.title : null,
|
||||||
|
firstName: value.firstname && value.firstname !== '' ? value.firstname : null,
|
||||||
|
middleName: value.middlename && value.middlename !== '' ? value.middlename : null,
|
||||||
|
lastName: value.lastname && value.lastname !== '' ? value.lastname : null,
|
||||||
|
username: value.username && value.username !== '' ? value.username : null,
|
||||||
|
company: value.company && value.company !== '' ? value.company : null,
|
||||||
|
ssn: value.ssn && value.ssn !== '' ? value.ssn : null,
|
||||||
|
address1: value.address1 && value.address1 !== '' ? value.address1 : null,
|
||||||
|
address2: value.address2 && value.address2 !== '' ? value.address2 : null,
|
||||||
|
address3: value.address3 && value.address3 !== '' ? value.address3 : null,
|
||||||
|
city: value.city && value.city !== '' ? value.city : null,
|
||||||
|
state: value.state && value.state !== '' ? value.state : null,
|
||||||
|
postalCode: value.zip && value.zip !== '' ? value.zip : null,
|
||||||
|
country: value.country && value.country !== '' ? value.country : null,
|
||||||
|
email: value.email && value.email !== '' ? value.email : null,
|
||||||
|
phone: value.phone && value.phone !== '' ? value.phone : null
|
||||||
|
};
|
||||||
|
|
||||||
|
cipher.notes = value.notes && value.notes !== '' ? value.notes : null;
|
||||||
|
|
||||||
|
if (cipher.identity.title) {
|
||||||
|
cipher.identity.title = cipher.identity.title.charAt(0).toUpperCase() +
|
||||||
|
cipher.identity.title.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.ccnum && value.ccnum !== '') {
|
||||||
|
// there is a card on this identity too
|
||||||
|
var cardCipher = JSON.parse(JSON.stringify(cipher)); // cloned
|
||||||
|
cardCipher.identity = null;
|
||||||
|
cardCipher.type = constants.cipherType.card;
|
||||||
|
cardCipher.card = parseCard(value);
|
||||||
|
ciphers.push(cardCipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ciphers.push(cipher);
|
ciphers.push(cipher);
|
||||||
|
|
||||||
@@ -707,6 +883,10 @@
|
|||||||
fields: null
|
fields: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!cipher.name || cipher.name === '') {
|
||||||
|
cipher.name = '--';
|
||||||
|
}
|
||||||
|
|
||||||
if (card.attr('type') === 'note') {
|
if (card.attr('type') === 'note') {
|
||||||
cipher.type = constants.cipherType.secureNote;
|
cipher.type = constants.cipherType.secureNote;
|
||||||
cipher.secureNote = {
|
cipher.secureNote = {
|
||||||
@@ -736,7 +916,10 @@
|
|||||||
cipher.notes += (text + '\n');
|
cipher.notes += (text + '\n');
|
||||||
}
|
}
|
||||||
else if (type === 'weblogin' || type === 'website') {
|
else if (type === 'weblogin' || type === 'website') {
|
||||||
cipher.login.uri = trimUri(text);
|
cipher.login.uris = makeUriArray(text);
|
||||||
|
}
|
||||||
|
else if (text.length > 200) {
|
||||||
|
cipher.notes += (name + ': ' + text + '\n');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
@@ -834,7 +1017,7 @@
|
|||||||
notes: null,
|
notes: null,
|
||||||
name: value[0] && value[0] !== '' ? value[0] : '--',
|
name: value[0] && value[0] !== '' ? value[0] : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: value[2] && value[2] !== '' ? value[2] : null,
|
username: value[2] && value[2] !== '' ? value[2] : null,
|
||||||
password: value[3] && value[3] !== '' ? value[3] : null
|
password: value[3] && value[3] !== '' ? value[3] : null
|
||||||
},
|
},
|
||||||
@@ -850,7 +1033,7 @@
|
|||||||
|
|
||||||
var cfHeader = customFieldHeaders[j - 4];
|
var cfHeader = customFieldHeaders[j - 4];
|
||||||
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
|
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
|
||||||
cipher.login.uri = trimUri(cf);
|
cipher.login.uris = makeUriArray(cf);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
@@ -936,7 +1119,7 @@
|
|||||||
name: null,
|
name: null,
|
||||||
type: constants.cipherType.login,
|
type: constants.cipherType.login,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
},
|
||||||
@@ -955,7 +1138,7 @@
|
|||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'URL':
|
case 'URL':
|
||||||
cipher.login.uri = fixUri(value);
|
cipher.login.uris = makeUriArray(value);
|
||||||
break;
|
break;
|
||||||
case 'UserName':
|
case 'UserName':
|
||||||
cipher.login.username = value;
|
cipher.login.username = value;
|
||||||
@@ -970,16 +1153,25 @@
|
|||||||
cipher.notes = cipher.notes === null ? value + '\n' : cipher.notes + value + '\n';
|
cipher.notes = cipher.notes === null ? value + '\n' : cipher.notes + value + '\n';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!cipher.fields) {
|
if (value.length > 200 || value.indexOf('\n') > -1) {
|
||||||
cipher.fields = [];
|
if (!cipher.notes) {
|
||||||
}
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
// other custom fields
|
cipher.notes += (key + ': ' + value + '\n');
|
||||||
cipher.fields.push({
|
}
|
||||||
name: key,
|
else {
|
||||||
value: value,
|
if (!cipher.fields) {
|
||||||
type: constants.fieldType.text
|
cipher.fields = [];
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// other custom fields
|
||||||
|
cipher.fields.push({
|
||||||
|
name: key,
|
||||||
|
value: value,
|
||||||
|
type: constants.fieldType.text
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1048,7 +1240,7 @@
|
|||||||
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
|
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
|
||||||
name: value.Title && value.Title !== '' ? value.Title : '--',
|
name: value.Title && value.Title !== '' ? value.Title : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value.URL && value.URL !== '' ? fixUri(value.URL) : null,
|
uris: makeUriArray(value.URL),
|
||||||
username: value.Username && value.Username !== '' ? value.Username : null,
|
username: value.Username && value.Username !== '' ? value.Username : null,
|
||||||
password: value.Password && value.Password !== '' ? value.Password : null
|
password: value.Password && value.Password !== '' ? value.Password : null
|
||||||
}
|
}
|
||||||
@@ -1106,7 +1298,7 @@
|
|||||||
}
|
}
|
||||||
else if (fieldValue) {
|
else if (fieldValue) {
|
||||||
var fieldName = (field[nameKey] || 'no_name');
|
var fieldName = (field[nameKey] || 'no_name');
|
||||||
if (fieldValue.indexOf('\\n') > -1) {
|
if (fieldValue.indexOf('\\n') > -1 || fieldValue.length > 200) {
|
||||||
if (cipher.notes === null) {
|
if (cipher.notes === null) {
|
||||||
cipher.notes = '';
|
cipher.notes = '';
|
||||||
}
|
}
|
||||||
@@ -1161,7 +1353,7 @@
|
|||||||
else {
|
else {
|
||||||
cipher.type = constants.cipherType.login;
|
cipher.type = constants.cipherType.login;
|
||||||
cipher.login = {
|
cipher.login = {
|
||||||
uri: item.location && item.location !== '' ? fixUri(item.location) : null,
|
uris: makeUriArray(item.location),
|
||||||
username: null,
|
username: null,
|
||||||
password: null,
|
password: null,
|
||||||
totp: null
|
totp: null
|
||||||
@@ -1216,7 +1408,7 @@
|
|||||||
notes: value.notesPlain && value.notesPlain !== '' ? value.notesPlain : '',
|
notes: value.notesPlain && value.notesPlain !== '' ? value.notesPlain : '',
|
||||||
name: value.title && value.title !== '' ? value.title : '--',
|
name: value.title && value.title !== '' ? value.title : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
}
|
}
|
||||||
@@ -1234,17 +1426,9 @@
|
|||||||
else if (!cipher.login.username && property === 'username') {
|
else if (!cipher.login.username && property === 'username') {
|
||||||
cipher.login.username = value[property];
|
cipher.login.username = value[property];
|
||||||
}
|
}
|
||||||
else if (!cipher.login.uri && property === 'urls') {
|
else if (!cipher.login.uris && property === 'urls') {
|
||||||
var urls = value[property].split(/(?:\r\n|\r|\n)/);
|
var urls = value[property].split(/(?:\r\n|\r|\n)/);
|
||||||
cipher.login.uri = fixUri(urls[0]);
|
cipher.login.uris = makeUriArray(urls);
|
||||||
|
|
||||||
for (var j = 1; j < urls.length; j++) {
|
|
||||||
if (cipher.notes !== '') {
|
|
||||||
cipher.notes += '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
cipher.notes += ('url ' + (j + 1) + ': ' + urls[j]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (property !== 'ainfo' && property !== 'autosubmit' && property !== 'notesPlain' &&
|
else if (property !== 'ainfo' && property !== 'autosubmit' && property !== 'notesPlain' &&
|
||||||
property !== 'ps' && property !== 'scope' && property !== 'tags' && property !== 'title' &&
|
property !== 'ps' && property !== 'scope' && property !== 'tags' && property !== 'title' &&
|
||||||
@@ -1288,7 +1472,7 @@
|
|||||||
notes: null,
|
notes: null,
|
||||||
name: value.name && value.name !== '' ? value.name : '--',
|
name: value.name && value.name !== '' ? value.name : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
uris: makeUriArray(value.url),
|
||||||
username: value.username && value.username !== '' ? value.username : null,
|
username: value.username && value.username !== '' ? value.username : null,
|
||||||
password: value.password && value.password !== '' ? value.password : null
|
password: value.password && value.password !== '' ? value.password : null
|
||||||
}
|
}
|
||||||
@@ -1342,7 +1526,7 @@
|
|||||||
notes: null,
|
notes: null,
|
||||||
name: getNameFromHost(host),
|
name: getNameFromHost(host),
|
||||||
login: {
|
login: {
|
||||||
uri: host && host !== '' ? trimUri(host) : null,
|
uris: makeUriArray(host),
|
||||||
username: user && user !== '' ? user : null,
|
username: user && user !== '' ? user : null,
|
||||||
password: password && password !== '' ? password : null,
|
password: password && password !== '' ? password : null,
|
||||||
}
|
}
|
||||||
@@ -1378,7 +1562,7 @@
|
|||||||
notes: value[4] && value[4] !== '' ? value[4] : null,
|
notes: value[4] && value[4] !== '' ? value[4] : null,
|
||||||
name: value[0] && value[0] !== '' ? value[0] : '--',
|
name: value[0] && value[0] !== '' ? value[0] : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value[3] && value[3] !== '' ? trimUri(value[3]) : null,
|
uris: makeUriArray(value[3]),
|
||||||
username: value[1] && value[1] !== '' ? value[1] : null,
|
username: value[1] && value[1] !== '' ? value[1] : null,
|
||||||
password: value[2] && value[2] !== '' ? value[2] : null
|
password: value[2] && value[2] !== '' ? value[2] : null
|
||||||
}
|
}
|
||||||
@@ -1425,7 +1609,7 @@
|
|||||||
notes: value[5] && value[5] !== '' ? value[5] : null,
|
notes: value[5] && value[5] !== '' ? value[5] : null,
|
||||||
name: value[1] && value[1] !== '' ? value[1] : '--',
|
name: value[1] && value[1] !== '' ? value[1] : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value[4] && value[4] !== '' ? trimUri(value[4]) : null,
|
uris: makeUriArray(value[4]),
|
||||||
username: value[2] && value[2] !== '' ? value[2] : null,
|
username: value[2] && value[2] !== '' ? value[2] : null,
|
||||||
password: value[3] && value[3] !== '' ? value[3] : null
|
password: value[3] && value[3] !== '' ? value[3] : null
|
||||||
},
|
},
|
||||||
@@ -1434,14 +1618,25 @@
|
|||||||
|
|
||||||
if (value.length > 6) {
|
if (value.length > 6) {
|
||||||
// we have some custom fields.
|
// we have some custom fields.
|
||||||
cipher.fields = [];
|
|
||||||
|
|
||||||
for (i = 6; i < value.length; i = i + 2) {
|
for (i = 6; i < value.length; i = i + 2) {
|
||||||
cipher.fields.push({
|
if (value[i + 1] && value[i + 1].length > 200) {
|
||||||
name: value[i],
|
if (!cipher.notes) {
|
||||||
value: value[i + 1],
|
cipher.notes = '';
|
||||||
type: constants.fieldType.text
|
}
|
||||||
});
|
|
||||||
|
cipher.notes += (value[i] + ': ' + value[i + 1] + '\n');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!cipher.fields) {
|
||||||
|
cipher.fields = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.fields.push({
|
||||||
|
name: value[i],
|
||||||
|
value: value[i + 1],
|
||||||
|
type: constants.fieldType.text
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1522,7 +1717,7 @@
|
|||||||
notes: notes && notes.text() !== '' ? notes.text() : null,
|
notes: notes && notes.text() !== '' ? notes.text() : null,
|
||||||
name: accountName && accountName.text() !== '' ? accountName.text() : '--',
|
name: accountName && accountName.text() !== '' ? accountName.text() : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: url && url.text() !== '' ? trimUri(url.text()) : null,
|
uris: url ? makeUriArray(url.text()) : null,
|
||||||
username: userId && userId.text() !== '' ? userId.text() : null,
|
username: userId && userId.text() !== '' ? userId.text() : null,
|
||||||
password: password && password.text() !== '' ? password.text() : null
|
password: password && password.text() !== '' ? password.text() : null
|
||||||
},
|
},
|
||||||
@@ -1549,15 +1744,24 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cipher.fields) {
|
if (attrValue.length > 200) {
|
||||||
cipher.fields = [];
|
if (!cipher.notes) {
|
||||||
}
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
cipher.fields.push({
|
cipher.notes += (attrName + ': ' + attrValue + '\n');
|
||||||
name: attrName,
|
}
|
||||||
value: attrValue,
|
else {
|
||||||
type: constants.fieldType.text
|
if (!cipher.fields) {
|
||||||
});
|
cipher.fields = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.fields.push({
|
||||||
|
name: attrName,
|
||||||
|
value: attrValue,
|
||||||
|
type: constants.fieldType.text
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1613,7 +1817,7 @@
|
|||||||
notes: note && note !== '' ? note : null,
|
notes: note && note !== '' ? note : null,
|
||||||
fields: null,
|
fields: null,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
password: null,
|
password: null,
|
||||||
username: null,
|
username: null,
|
||||||
totp: null
|
totp: null
|
||||||
@@ -1630,8 +1834,8 @@
|
|||||||
var field = row[i + 1];
|
var field = row[i + 1];
|
||||||
var fieldLower = field.toLowerCase();
|
var fieldLower = field.toLowerCase();
|
||||||
|
|
||||||
if (fieldLower === 'url' && !cipher.login.uri) {
|
if (fieldLower === 'url' && !cipher.login.uris) {
|
||||||
cipher.login.uri = trimUri(value);
|
cipher.login.uris = makeUriArray(value);
|
||||||
}
|
}
|
||||||
else if ((fieldLower === 'username' || fieldLower === 'email') && !cipher.login.username) {
|
else if ((fieldLower === 'username' || fieldLower === 'email') && !cipher.login.username) {
|
||||||
cipher.login.username = value;
|
cipher.login.username = value;
|
||||||
@@ -1642,6 +1846,13 @@
|
|||||||
else if (fieldLower === 'totp' && !cipher.login.totp) {
|
else if (fieldLower === 'totp' && !cipher.login.totp) {
|
||||||
cipher.login.totp = value;
|
cipher.login.totp = value;
|
||||||
}
|
}
|
||||||
|
else if (value.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (field + ': ' + value + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// other fields
|
// other fields
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
@@ -1725,7 +1936,7 @@
|
|||||||
notes: notes && notesText !== '' ? notesText : null,
|
notes: notes && notesText !== '' ? notesText : null,
|
||||||
name: title && title.text() !== '' ? title.text() : '--',
|
name: title && title.text() !== '' ? title.text() : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: url && url.text() !== '' ? trimUri(url.text()) : null,
|
uris: url ? makeUriArray(url.text()) : null,
|
||||||
username: username && username.text() !== '' ? username.text() : null,
|
username: username && username.text() !== '' ? username.text() : null,
|
||||||
password: password && password.text() !== '' ? password.text() : null
|
password: password && password.text() !== '' ? password.text() : null
|
||||||
}
|
}
|
||||||
@@ -1787,17 +1998,17 @@
|
|||||||
favorite: false,
|
favorite: false,
|
||||||
notes: null,
|
notes: null,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
password: null,
|
password: null,
|
||||||
username: null
|
username: null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (row.length === 2) {
|
if (row.length === 2) {
|
||||||
cipher.login.uri = fixUri(row[1]);
|
cipher.login.uris = makeUriArray(row[1]);
|
||||||
}
|
}
|
||||||
else if (row.length === 3) {
|
else if (row.length === 3) {
|
||||||
cipher.login.uri = fixUri(row[1]);
|
cipher.login.uris = makeUriArray(row[1]);
|
||||||
cipher.login.username = row[2];
|
cipher.login.username = row[2];
|
||||||
}
|
}
|
||||||
else if (row.length === 4) {
|
else if (row.length === 4) {
|
||||||
@@ -1811,7 +2022,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (row.length === 5) {
|
else if (row.length === 5) {
|
||||||
cipher.login.uri = fixUri(row[1]);
|
cipher.login.uris = makeUriArray(row[1]);
|
||||||
cipher.login.username = row[2];
|
cipher.login.username = row[2];
|
||||||
cipher.login.password = row[3];
|
cipher.login.password = row[3];
|
||||||
cipher.notes = row[4];
|
cipher.notes = row[4];
|
||||||
@@ -1828,7 +2039,7 @@
|
|||||||
cipher.notes = row[4] + '\n' + row[5];
|
cipher.notes = row[4] + '\n' + row[5];
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher.login.uri = fixUri(row[1]);
|
cipher.login.uris = makeUriArray(row[1]);
|
||||||
}
|
}
|
||||||
else if (row.length === 7) {
|
else if (row.length === 7) {
|
||||||
if (row[2] === '') {
|
if (row[2] === '') {
|
||||||
@@ -1840,7 +2051,7 @@
|
|||||||
cipher.notes = row[3] + '\n' + row[4] + '\n' + row[6];
|
cipher.notes = row[3] + '\n' + row[4] + '\n' + row[6];
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher.login.uri = fixUri(row[1]);
|
cipher.login.uris = makeUriArray(row[1]);
|
||||||
cipher.login.password = row[5];
|
cipher.login.password = row[5];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1867,9 +2078,6 @@
|
|||||||
if (cipher.notes === '') {
|
if (cipher.notes === '') {
|
||||||
cipher.notes = null;
|
cipher.notes = null;
|
||||||
}
|
}
|
||||||
if (cipher.login.uri === '') {
|
|
||||||
cipher.login.uri = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ciphers.push(cipher);
|
ciphers.push(cipher);
|
||||||
}
|
}
|
||||||
@@ -1962,7 +2170,7 @@
|
|||||||
notes: notesText && notesText !== '' ? notesText : null,
|
notes: notesText && notesText !== '' ? notesText : null,
|
||||||
name: titleText && titleText !== '' ? titleText : '--',
|
name: titleText && titleText !== '' ? titleText : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: linkText && linkText !== '' ? trimUri(linkText) : null,
|
uris: makeUriArray(linkText),
|
||||||
username: usernameText && usernameText !== '' ? usernameText : null,
|
username: usernameText && usernameText !== '' ? usernameText : null,
|
||||||
password: passwordText && passwordText !== '' ? passwordText : null
|
password: passwordText && passwordText !== '' ? passwordText : null
|
||||||
}
|
}
|
||||||
@@ -2028,14 +2236,14 @@
|
|||||||
notes: '',
|
notes: '',
|
||||||
name: value[2] && value[2] !== '' ? value[2] : null,
|
name: value[2] && value[2] !== '' ? value[2] : null,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (value[1] === 'Web Logins') {
|
if (value[1] === 'Web Logins') {
|
||||||
cipher.login.uri = value[4] && value[4] !== '' ? trimUri(value[4]) : null;
|
cipher.login.uris = makeUriArray(value[4]);
|
||||||
cipher.login.username = value[5] && value[5] !== '' ? value[5] : null;
|
cipher.login.username = value[5] && value[5] !== '' ? value[5] : null;
|
||||||
cipher.login.password = value[6] && value[6] !== '' ? value[6] : null;
|
cipher.login.password = value[6] && value[6] !== '' ? value[6] : null;
|
||||||
cipher.notes = value[3] && value[3] !== '' ? value[3].split('\\n').join('\n') : null;
|
cipher.notes = value[3] && value[3] !== '' ? value[3].split('\\n').join('\n') : null;
|
||||||
@@ -2113,7 +2321,7 @@
|
|||||||
notes: value.memo && value.memo !== '' ? value.memo : null,
|
notes: value.memo && value.memo !== '' ? value.memo : null,
|
||||||
name: value.name && value.name !== '' ? value.name : '--',
|
name: value.name && value.name !== '' ? value.name : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
uris: makeUriArray(value.url),
|
||||||
username: value.login && value.login !== '' ? value.login : null,
|
username: value.login && value.login !== '' ? value.login : null,
|
||||||
password: value.password && value.password !== '' ? value.password : null
|
password: value.password && value.password !== '' ? value.password : null
|
||||||
},
|
},
|
||||||
@@ -2132,16 +2340,25 @@
|
|||||||
for (var property in value) {
|
for (var property in value) {
|
||||||
if (value.hasOwnProperty(property) && propsToIgnore.indexOf(property.toLowerCase()) < 0 &&
|
if (value.hasOwnProperty(property) && propsToIgnore.indexOf(property.toLowerCase()) < 0 &&
|
||||||
value[property] && value[property] !== '') {
|
value[property] && value[property] !== '') {
|
||||||
if (!cipher.fields) {
|
if (value[property].length > 200) {
|
||||||
cipher.fields = [];
|
if (!cipher.notes) {
|
||||||
}
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
// other custom fields
|
cipher.notes += (property + ': ' + value[property] + '\n');
|
||||||
cipher.fields.push({
|
}
|
||||||
name: property,
|
else {
|
||||||
value: value[property],
|
if (!cipher.fields) {
|
||||||
type: constants.fieldType.text
|
cipher.fields = [];
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// other custom fields
|
||||||
|
cipher.fields.push({
|
||||||
|
name: property,
|
||||||
|
value: value[property],
|
||||||
|
type: constants.fieldType.text
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2176,7 +2393,7 @@
|
|||||||
notes: '',
|
notes: '',
|
||||||
name: entry.label && entry.label !== '' ? entry.label.split(' ')[0] : '--',
|
name: entry.label && entry.label !== '' ? entry.label.split(' ')[0] : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
},
|
||||||
@@ -2204,7 +2421,7 @@
|
|||||||
cipher.login.username = field.value;
|
cipher.login.username = field.value;
|
||||||
break;
|
break;
|
||||||
case 'url':
|
case 'url':
|
||||||
cipher.login.uri = trimUri(field.value);
|
cipher.login.uris = makeUriArray(field.value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!cipher.login.username && isField(field.label, _usernameFieldNames)) {
|
if (!cipher.login.username && isField(field.label, _usernameFieldNames)) {
|
||||||
@@ -2213,6 +2430,13 @@
|
|||||||
else if (!cipher.login.password && isField(field.label, _passwordFieldNames)) {
|
else if (!cipher.login.password && isField(field.label, _passwordFieldNames)) {
|
||||||
cipher.login.password = field.value;
|
cipher.login.password = field.value;
|
||||||
}
|
}
|
||||||
|
else if (field.value.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (field.label + ': ' + field.value + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = [];
|
cipher.fields = [];
|
||||||
@@ -2260,7 +2484,7 @@
|
|||||||
notes: null,
|
notes: null,
|
||||||
name: account.label && account.label !== '' ? account.label : account.domain,
|
name: account.label && account.label !== '' ? account.label : account.domain,
|
||||||
login: {
|
login: {
|
||||||
uri: account.domain && account.domain !== '' ? fixUri(account.domain) : null,
|
uris: makeUriArray(account.domain),
|
||||||
username: account.username && account.username !== '' ? account.username : null,
|
username: account.username && account.username !== '' ? account.username : null,
|
||||||
password: account.password && account.password !== '' ? account.password : null
|
password: account.password && account.password !== '' ? account.password : null
|
||||||
}
|
}
|
||||||
@@ -2306,7 +2530,7 @@
|
|||||||
notes: '',
|
notes: '',
|
||||||
name: outterTable.find('span.caption').text(),
|
name: outterTable.find('span.caption').text(),
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
},
|
||||||
@@ -2315,7 +2539,7 @@
|
|||||||
|
|
||||||
var url = outterTable.find('.subcaption').text();
|
var url = outterTable.find('.subcaption').text();
|
||||||
if (url && url !== '') {
|
if (url && url !== '') {
|
||||||
cipher.login.uri = fixUri(url);
|
cipher.login.uris = makeUriArray(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fields = [];
|
var fields = [];
|
||||||
@@ -2340,6 +2564,13 @@
|
|||||||
else if (!cipher.login.username && isField(field.replace(':', ''), _usernameFieldNames)) {
|
else if (!cipher.login.username && isField(field.replace(':', ''), _usernameFieldNames)) {
|
||||||
cipher.login.username = fieldValue;
|
cipher.login.username = fieldValue;
|
||||||
}
|
}
|
||||||
|
else if (fieldValue.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (field + ': ' + fieldValue + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = [];
|
cipher.fields = [];
|
||||||
@@ -2392,7 +2623,7 @@
|
|||||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
notes: value.notes && value.notes !== '' ? value.notes : null,
|
||||||
name: value.url && value.url !== '' ? urlDomain(value.url) : '--',
|
name: value.url && value.url !== '' ? urlDomain(value.url) : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
|
uris: makeUriArray(value.url),
|
||||||
username: value.username && value.username !== '' ? value.username : null,
|
username: value.username && value.username !== '' ? value.username : null,
|
||||||
password: value.password && value.password !== '' ? value.password : null
|
password: value.password && value.password !== '' ? value.password : null
|
||||||
}
|
}
|
||||||
@@ -2426,7 +2657,7 @@
|
|||||||
favorite: false,
|
favorite: false,
|
||||||
notes: note && note !== '' ? note : null,
|
notes: note && note !== '' ? note : null,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
password: null,
|
password: null,
|
||||||
username: null
|
username: null
|
||||||
},
|
},
|
||||||
@@ -2443,8 +2674,8 @@
|
|||||||
|
|
||||||
var fieldLower = field.toLowerCase();
|
var fieldLower = field.toLowerCase();
|
||||||
|
|
||||||
if (!cipher.login.uri && isField(field, _uriFieldNames)) {
|
if (!cipher.login.uris && isField(field, _uriFieldNames)) {
|
||||||
cipher.login.uri = fixUri(value);
|
cipher.login.uris = makeUriArray(value);
|
||||||
}
|
}
|
||||||
else if (!cipher.login.username && isField(field, _usernameFieldNames)) {
|
else if (!cipher.login.username && isField(field, _usernameFieldNames)) {
|
||||||
cipher.login.username = value;
|
cipher.login.username = value;
|
||||||
@@ -2452,6 +2683,13 @@
|
|||||||
else if (!cipher.login.password && isField(field, _passwordFieldNames)) {
|
else if (!cipher.login.password && isField(field, _passwordFieldNames)) {
|
||||||
cipher.login.password = value;
|
cipher.login.password = value;
|
||||||
}
|
}
|
||||||
|
else if (value.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (field + ': ' + value + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = [];
|
cipher.fields = [];
|
||||||
@@ -2494,7 +2732,7 @@
|
|||||||
notes: '',
|
notes: '',
|
||||||
name: item.name && item.name !== '' ? item.name : '--',
|
name: item.name && item.name !== '' ? item.name : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: item.login_url && item.login_url !== '' ? fixUri(item.login_url) : null,
|
uris: makeUriArray(item.login_url),
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
},
|
||||||
@@ -2522,6 +2760,13 @@
|
|||||||
else if (property === 'password') {
|
else if (property === 'password') {
|
||||||
cipher.login.password = value;
|
cipher.login.password = value;
|
||||||
}
|
}
|
||||||
|
else if (value.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (property + ': ' + value + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = [];
|
cipher.fields = [];
|
||||||
@@ -2575,6 +2820,13 @@
|
|||||||
else if (fieldLower === 'password') {
|
else if (fieldLower === 'password') {
|
||||||
cipher.login.password = value;
|
cipher.login.password = value;
|
||||||
}
|
}
|
||||||
|
else if (value.length > 200) {
|
||||||
|
if (!cipher.notes) {
|
||||||
|
cipher.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.notes += (field + ': ' + value + '\n');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = [];
|
cipher.fields = [];
|
||||||
@@ -2624,7 +2876,7 @@
|
|||||||
notes: value.Notes && value.Notes !== '' ? value.Notes : '',
|
notes: value.Notes && value.Notes !== '' ? value.Notes : '',
|
||||||
name: value['Secret Name'] && value['Secret Name'] !== '' ? value['Secret Name'] : '--',
|
name: value['Secret Name'] && value['Secret Name'] !== '' ? value['Secret Name'] : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: value['Secret URL'] && value['Secret URL'] !== '' ? fixUri(value['Secret URL']) : null,
|
uris: makeUriArray(value['Secret URL']),
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
},
|
||||||
@@ -2724,14 +2976,14 @@
|
|||||||
name: value[1] && value[1] !== '' ? value[1] : '--',
|
name: value[1] && value[1] !== '' ? value[1] : '--',
|
||||||
fields: null,
|
fields: null,
|
||||||
login: {
|
login: {
|
||||||
uri: null,
|
uris: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'Web Logins' || type === 'Servers' || type === 'Email Accounts') {
|
if (type === 'Web Logins' || type === 'Servers' || type === 'Email Accounts') {
|
||||||
cipher.login.uri = value[4] && value[4] !== '' ? fixUri(value[4]) : null;
|
cipher.login.uris = makeUriArray(value[4]);
|
||||||
cipher.login.username = value[2] && value[2] !== '' ? value[2] : null;
|
cipher.login.username = value[2] && value[2] !== '' ? value[2] : null;
|
||||||
cipher.login.password = value[3] && value[3] !== '' ? value[3] : null;
|
cipher.login.password = value[3] && value[3] !== '' ? value[3] : null;
|
||||||
parseFieldsToNotes(5, value, cipher);
|
parseFieldsToNotes(5, value, cipher);
|
||||||
@@ -2789,7 +3041,7 @@
|
|||||||
favorite: false,
|
favorite: false,
|
||||||
notes: row.Notes && row.Notes !== '' ? row.Notes : null,
|
notes: row.Notes && row.Notes !== '' ? row.Notes : null,
|
||||||
login: {
|
login: {
|
||||||
uri: row.Url && row.Url !== '' ? fixUri(row.Url) : null,
|
uris: makeUriArray(row.Url),
|
||||||
password: row.Password && row.Password !== '' ? row.Password : null,
|
password: row.Password && row.Password !== '' ? row.Password : null,
|
||||||
username: row.UserName && row.UserName !== '' ? row.UserName : null
|
username: row.UserName && row.UserName !== '' ? row.UserName : null
|
||||||
}
|
}
|
||||||
@@ -2846,7 +3098,7 @@
|
|||||||
notes: !!getValue('description', value) ? getValue('description', value) : null,
|
notes: !!getValue('description', value) ? getValue('description', value) : null,
|
||||||
name: !!getValue('title', value) ? getValue('title', value) : '--',
|
name: !!getValue('title', value) ? getValue('title', value) : '--',
|
||||||
login: {
|
login: {
|
||||||
uri: !!getValue('site', value) ? fixUri(getValue('site', value)) : null,
|
uris: !!getValue('site', value) ? makeUriArray(getValue('site', value)) : null,
|
||||||
username: !!getValue('username', value) ? getValue('username', value) : null,
|
username: !!getValue('username', value) ? getValue('username', value) : null,
|
||||||
password: !!getValue('password', value) ? getValue('password', value) : null
|
password: !!getValue('password', value) ? getValue('password', value) : null
|
||||||
}
|
}
|
||||||
@@ -2920,7 +3172,7 @@
|
|||||||
notes: '',
|
notes: '',
|
||||||
name: item.display_name.replace('http://', '').replace('https://', ''),
|
name: item.display_name.replace('http://', '').replace('https://', ''),
|
||||||
login: {
|
login: {
|
||||||
uri: fixUri(item.display_name),
|
uris: makeUriArray(item.display_name),
|
||||||
username: item.attributes.username_value && item.attributes.username_value !== '' ?
|
username: item.attributes.username_value && item.attributes.username_value !== '' ?
|
||||||
item.attributes.username_value : null,
|
item.attributes.username_value : null,
|
||||||
password: item.secret && item.secret !== '' ? item.secret : null
|
password: item.secret && item.secret !== '' ? item.secret : null
|
||||||
|
|||||||
47
src/app/services/utilsService.js
Normal file
47
src/app/services/utilsService.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('utilsService', function (constants) {
|
||||||
|
var _service = {};
|
||||||
|
var _browserCache;
|
||||||
|
|
||||||
|
_service.getDeviceType = function (token) {
|
||||||
|
if (_browserCache) {
|
||||||
|
return _browserCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.userAgent.indexOf(' Vivaldi/') >= 0) {
|
||||||
|
_browserCache = constants.deviceType.vivaldi;
|
||||||
|
}
|
||||||
|
else if (!!window.chrome && !!window.chrome.webstore) {
|
||||||
|
_browserCache = constants.deviceType.chrome;
|
||||||
|
}
|
||||||
|
else if (typeof InstallTrigger !== 'undefined') {
|
||||||
|
_browserCache = constants.deviceType.firefox;
|
||||||
|
}
|
||||||
|
else if ((!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) {
|
||||||
|
_browserCache = constants.deviceType.firefox;
|
||||||
|
}
|
||||||
|
else if (/constructor/i.test(window.HTMLElement) ||
|
||||||
|
safariCheck(!window.safari || (typeof safari !== 'undefined' && safari.pushNotification))) {
|
||||||
|
_browserCache = constants.deviceType.opera;
|
||||||
|
}
|
||||||
|
else if (!!document.documentMode) {
|
||||||
|
_browserCache = constants.deviceType.ie;
|
||||||
|
}
|
||||||
|
else if (!!window.StyleMedia) {
|
||||||
|
_browserCache = constants.deviceType.edge;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_browserCache = constants.deviceType.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _browserCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
function safariCheck(p) {
|
||||||
|
return p.toString() === '[object SafariRemoteNotification]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
||||||
@@ -62,5 +62,41 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_service.parseErrors = function (reason) {
|
||||||
|
var data = reason.data;
|
||||||
|
var defaultErrorMessage = 'An unexpected error has occurred.';
|
||||||
|
var errors = [];
|
||||||
|
|
||||||
|
if (!data || !angular.isObject(data)) {
|
||||||
|
errors.push(defaultErrorMessage);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.ErrorModel) {
|
||||||
|
data = data.ErrorModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ValidationErrors) {
|
||||||
|
if (data.Message) {
|
||||||
|
errors.push(data.Message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errors.push(defaultErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in data.ValidationErrors) {
|
||||||
|
if (!data.ValidationErrors.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
|
||||||
|
errors.push(data.ValidationErrors[key][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
return _service;
|
return _service;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
angular.module("bit")
|
angular.module("bit")
|
||||||
.constant("appSettings", {"apiUri":"https://api.bitwarden.com","identityUri":"https://identity.bitwarden.com","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","whitelistDomains":["api.bitwarden.com"],"selfHosted":false,"version":"1.18.0","environment":"Production"});
|
.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.25.0","environment":"Production"});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
.controller('settingsTwoStepAuthenticatorController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
.controller('settingsTwoStepAuthenticatorController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||||
authService, $q, toastr, $analytics, constants, $timeout) {
|
authService, $q, toastr, $analytics, constants, $timeout) {
|
||||||
$analytics.eventTrack('settingsTwoStepAuthenticatorController', { category: 'Modal' });
|
$analytics.eventTrack('settingsTwoStepAuthenticatorController', { category: 'Modal' });
|
||||||
var _issuer = 'bitwarden',
|
var _issuer = 'Bitwarden',
|
||||||
_profile = null,
|
_profile = null,
|
||||||
_masterPasswordHash,
|
_masterPasswordHash,
|
||||||
_key = null;
|
_key = null;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
$analytics.eventTrack('Print Recovery Code');
|
$analytics.eventTrack('Print Recovery Code');
|
||||||
var w = window.open();
|
var w = window.open();
|
||||||
w.document.write('<div style="font-size: 18px; text-align: center;"><p>bitwarden two-step login recovery code:</p>' +
|
w.document.write('<div style="font-size: 18px; text-align: center;"><p>Bitwarden two-step login recovery code:</p>' +
|
||||||
'<code style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace;">' + $scope.code + '</code>' +
|
'<code style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace;">' + $scope.code + '</code>' +
|
||||||
'</div><p style="text-align: center;">' + new Date() + '</p>');
|
'</div><p style="text-align: center;">' + new Date() + '</p>');
|
||||||
w.print();
|
w.print();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
}, function (e) {
|
}, function (e) {
|
||||||
throw e ? e : 'Error occurred.';
|
throw e ? e : 'Error occurred.';
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
toastr.success('Please log back in. If you are using other bitwarden applications, ' +
|
toastr.success('Please log back in. If you are using other Bitwarden applications, ' +
|
||||||
'log out and back in to those as well.', 'Key Updated', { timeOut: 10000 });
|
'log out and back in to those as well.', 'Key Updated', { timeOut: 10000 });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<p>
|
<p>
|
||||||
You membership has a total of {{storage.maxGb}} GB of encrypted file storage.
|
Your membership has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||||
You are currently using {{storage.currentName}}.
|
You are currently using {{storage.currentName}}.
|
||||||
</p>
|
</p>
|
||||||
<div class="progress" style="margin: 0;">
|
<div class="progress" style="margin: 0;">
|
||||||
|
|||||||
@@ -93,17 +93,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="radio radio-block" ng-if="!model.ownedBusiness" ng-click="changedPlan()">
|
<div class="radio radio-block" ng-if="!model.ownedBusiness" ng-click="changedPlan()">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" ng-model="model.plan" name="PlanType" value="personal">
|
<input type="radio" ng-model="model.plan" name="PlanType" value="families">
|
||||||
Personal
|
Families
|
||||||
<span>For personal users such as families & friends.</span>
|
<span>For personal use, to share with family & friends.</span>
|
||||||
<span>- Add and share with up to 10 users (5 included with base price)</span>
|
<span>- Add and share with up to 5 users</span>
|
||||||
<span>- Create unlimited collections</span>
|
<span>- Create unlimited collections</span>
|
||||||
<span>- 1 GB encrypted file storage</span>
|
<span>- 1 GB encrypted file storage</span>
|
||||||
|
<span>- Self-hosting (optional)</span>
|
||||||
<span>- Priority customer support</span>
|
<span>- Priority customer support</span>
|
||||||
<span>- 7 day free trial, cancel anytime</span>
|
<span>- 7 day free trial, cancel anytime</span>
|
||||||
<span class="bottom-line">
|
<span class="bottom-line">
|
||||||
{{plans.personal.basePrice | currency:'$'}} /month includes {{plans.personal.baseSeats}} users,
|
{{plans.families.basePrice | currency:'$'}} /month includes {{plans.families.baseSeats}} users
|
||||||
additional users {{plans.personal.seatPrice | currency:'$'}} /month
|
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +243,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio radio-block" ng-if="model.plan !== 'personal'">
|
<div class="radio radio-block" ng-if="model.plan !== 'families'">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" ng-model="model.interval" name="BillingInterval" value="month">
|
<input type="radio" ng-model="model.interval" name="BillingInterval" value="month">
|
||||||
Monthly
|
Monthly
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<section class="content">
|
<section class="content">
|
||||||
<p>
|
<p>
|
||||||
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
|
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
|
||||||
"Global" domains are ones already created for you by bitwarden.
|
"Global" domains are ones already created for you by Bitwarden.
|
||||||
</p>
|
</p>
|
||||||
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise" autocomplete="off">
|
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise" autocomplete="off">
|
||||||
<div class="box box-default">
|
<div class="box box-default">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
The recovery code allows you to access your account in the event that you can no longer use your normal
|
The recovery code allows you to access your account in the event that you can no longer use your normal
|
||||||
two-step login provider (ex. you lose your device). bitwarden support will not be able to assist you if you lose
|
two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose
|
||||||
access to your account. We recommend you write down or print the recovery code and keep it in a safe place.
|
access to your account. We recommend you write down or print the recovery code and keep it in a safe place.
|
||||||
</div>
|
</div>
|
||||||
<div class="box-footer">
|
<div class="box-footer">
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<li ng-repeat="e in submitTwoStepForm.$errors">{{e}}</li>
|
<li ng-repeat="e in submitTwoStepForm.$errors">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>Enter the bitwarden application information from your Duo Admin panel:</p>
|
<p>Enter the Bitwarden application information from your Duo Admin panel:</p>
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="ikey">Integration Key</label>
|
<label for="ikey">Integration Key</label>
|
||||||
<input type="text" id="ikey" name="IntegrationKey" ng-model="updateModel.ikey" class="form-control"
|
<input type="text" id="ikey" name="IntegrationKey" ng-model="updateModel.ikey" class="form-control"
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="skey">Secret Key</label>
|
<label for="skey">Secret Key</label>
|
||||||
<input type="password" id="skey" name="SecretKey" ng-model="updateModel.skey" class="form-control"
|
<input type="password" id="skey" name="SecretKey" ng-model="updateModel.skey" class="form-control"
|
||||||
required api-field />
|
required api-field autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="host">API Hostname</label>
|
<label for="host">API Hostname</label>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="callout callout-warning">
|
<div class="callout callout-warning">
|
||||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||||
<p>
|
<p>
|
||||||
Due to platform limitations, FIDO U2F cannot be used on all bitwarden applications. You should enable
|
Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should enable
|
||||||
another two-step login provider so that you can access your account when FIDO U2F cannot be used.
|
another two-step login provider so that you can access your account when FIDO U2F cannot be used.
|
||||||
</p>
|
</p>
|
||||||
<p>Supported platforms:</p>
|
<p>Supported platforms:</p>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="callout callout-warning">
|
<div class="callout callout-warning">
|
||||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||||
<p>
|
<p>
|
||||||
Due to platform limitations, YubiKeys cannot be used on all bitwarden applications. You should enable
|
Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should enable
|
||||||
another two-step login provider so that you can access your account when YubiKeys cannot be used.
|
another two-step login provider so that you can access your account when YubiKeys cannot be used.
|
||||||
</p>
|
</p>
|
||||||
<p>Supported platforms:</p>
|
<p>Supported platforms:</p>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
{{updateModel.key1.existingKey}}
|
{{updateModel.key1.existingKey}}
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="key1" name="Key1" ng-model="updateModel.key1.key" class="form-control" api-field
|
<input type="password" id="key1" name="Key1" ng-model="updateModel.key1.key" class="form-control" api-field
|
||||||
ng-show="!updateModel.key1.existingKey" />
|
ng-show="!updateModel.key1.existingKey" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="key2">YubiKey #2</label>
|
<label for="key2">YubiKey #2</label>
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
{{updateModel.key2.existingKey}}
|
{{updateModel.key2.existingKey}}
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="key2" name="Key2" ng-model="updateModel.key2.key" class="form-control" api-field
|
<input type="password" id="key2" name="Key2" ng-model="updateModel.key2.key" class="form-control" api-field
|
||||||
ng-show="!updateModel.key2.existingKey" />
|
ng-show="!updateModel.key2.existingKey" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="key3">YubiKey #3</label>
|
<label for="key3">YubiKey #3</label>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
{{updateModel.key3.existingKey}}
|
{{updateModel.key3.existingKey}}
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="key3" name="Key3" ng-model="updateModel.key3.key" class="form-control" api-field
|
<input type="password" id="key3" name="Key3" ng-model="updateModel.key3.key" class="form-control" api-field
|
||||||
ng-show="!updateModel.key3.existingKey" />
|
ng-show="!updateModel.key3.existingKey" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
<strong>NFC Support</strong>
|
<strong>NFC Support</strong>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<div class="callout callout-warning">
|
<div class="callout callout-warning">
|
||||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||||
After updating your encryption key, you are required to log out and back in to all bitwarden applications that you
|
After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you
|
||||||
are currently using (such as the mobile app or browser extensions). Failure to log out and back
|
are currently using (such as the mobile app or browser extensions). Failure to log out and back
|
||||||
in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out
|
in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out
|
||||||
automatically, however it may be delayed.
|
automatically, however it may be delayed.
|
||||||
|
|||||||
@@ -53,8 +53,9 @@
|
|||||||
login_totp: null
|
login_totp: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var j;
|
||||||
if (decCiphers[i].fields) {
|
if (decCiphers[i].fields) {
|
||||||
for (var j = 0; j < decCiphers[i].fields.length; j++) {
|
for (j = 0; j < decCiphers[i].fields.length; j++) {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = '';
|
cipher.fields = '';
|
||||||
}
|
}
|
||||||
@@ -69,10 +70,16 @@
|
|||||||
switch (decCiphers[i].type) {
|
switch (decCiphers[i].type) {
|
||||||
case constants.cipherType.login:
|
case constants.cipherType.login:
|
||||||
cipher.type = 'login';
|
cipher.type = 'login';
|
||||||
cipher.login_uri = decCiphers[i].login.uri;
|
|
||||||
cipher.login_username = decCiphers[i].login.username;
|
cipher.login_username = decCiphers[i].login.username;
|
||||||
cipher.login_password = decCiphers[i].login.password;
|
cipher.login_password = decCiphers[i].login.password;
|
||||||
cipher.login_totp = decCiphers[i].login.totp;
|
cipher.login_totp = decCiphers[i].login.totp;
|
||||||
|
|
||||||
|
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
|
||||||
|
cipher.login_uri = [];
|
||||||
|
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
|
||||||
|
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case constants.cipherType.secureNote:
|
case constants.cipherType.secureNote:
|
||||||
cipher.type = 'note';
|
cipher.type = 'note';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
$scope.options = [
|
$scope.options = [
|
||||||
{
|
{
|
||||||
id: 'bitwardencsv',
|
id: 'bitwardencsv',
|
||||||
name: 'bitwarden (csv)',
|
name: 'Bitwarden (csv)',
|
||||||
featured: true,
|
featured: true,
|
||||||
sort: 1,
|
sort: 1,
|
||||||
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
||||||
@@ -243,7 +243,7 @@
|
|||||||
'python script by Luke Plant to your desktop as <code>pw_helper.py</code>. Open terminal and run ' +
|
'python script by Luke Plant to your desktop as <code>pw_helper.py</code>. Open terminal and run ' +
|
||||||
'<code>chmod +rx Desktop/pw_helper.py</code> and then ' +
|
'<code>chmod +rx Desktop/pw_helper.py</code> and then ' +
|
||||||
'<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload ' +
|
'<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload ' +
|
||||||
'the resulting <code>my_passwords.json</code> file here to bitwarden.')
|
'the resulting <code>my_passwords.json</code> file here to Bitwarden.')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
var halfway = Math.floor(ciphers.length / 2);
|
var halfway = Math.floor(ciphers.length / 2);
|
||||||
var last = ciphers.length - 1;
|
var last = ciphers.length - 1;
|
||||||
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
if (cipherIsBadData(ciphers[0]) && cipherIsBadData(ciphers[halfway]) && cipherIsBadData(ciphers[last])) {
|
||||||
importError('CSV data is not formatted correctly. Please check your import file and try again.');
|
importError('Data is not formatted correctly. Please check your import file and try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,27 @@
|
|||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
.controller('vaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||||
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants) {
|
passwordService, selectedFolder, $analytics, checkedFavorite, $rootScope, authService, $uibModal, constants,
|
||||||
|
$filter, selectedType) {
|
||||||
$analytics.eventTrack('vaultAddCipherController', { category: 'Modal' });
|
$analytics.eventTrack('vaultAddCipherController', { category: 'Modal' });
|
||||||
$scope.folders = $rootScope.vaultFolders;
|
$scope.folders = $rootScope.vaultFolders;
|
||||||
$scope.constants = constants;
|
$scope.constants = constants;
|
||||||
$scope.selectedType = constants.cipherType.login.toString();
|
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
|
||||||
$scope.cipher = {
|
$scope.cipher = {
|
||||||
folderId: selectedFolder ? selectedFolder.id : null,
|
folderId: selectedFolder ? selectedFolder.id : null,
|
||||||
favorite: checkedFavorite === true,
|
favorite: checkedFavorite === true,
|
||||||
type: constants.cipherType.login,
|
type: selectedType || constants.cipherType.login,
|
||||||
login: {},
|
login: {
|
||||||
|
uris: [{
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
}]
|
||||||
|
},
|
||||||
identity: {},
|
identity: {},
|
||||||
card: {},
|
card: {},
|
||||||
secureNote: {
|
secureNote: {
|
||||||
type: '0'
|
type: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,7 +47,43 @@
|
|||||||
$scope.generatePassword = function () {
|
$scope.generatePassword = function () {
|
||||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
$analytics.eventTrack('Generated Password From Add');
|
$analytics.eventTrack('Generated Password From Add');
|
||||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
$scope.cipher.login.password = passwordService.generatePassword({ length: 14, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addUri = function () {
|
||||||
|
if (!$scope.cipher.login) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.cipher.login.uris) {
|
||||||
|
$scope.cipher.login.uris = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.cipher.login.uris.push({
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeUri = function (uri) {
|
||||||
|
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||||
|
if (index > -1) {
|
||||||
|
$scope.cipher.login.uris.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.uriMatchChanged = function (uri) {
|
||||||
|
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||||
|
uri.match = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uri.match = parseInt(uri.matchValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,9 @@
|
|||||||
fileEl.type = '';
|
fileEl.type = '';
|
||||||
fileEl.type = 'file';
|
fileEl.type = 'file';
|
||||||
fileEl.value = '';
|
fileEl.value = '';
|
||||||
}, function (err) {
|
}, function (e) {
|
||||||
if (err) {
|
var errors = validationService.parseErrors(e);
|
||||||
validationService.addError(form, 'file', err, true);
|
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||||
}
|
|
||||||
else {
|
|
||||||
validationService.addError(form, 'file', 'Something went wrong.', true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,35 @@
|
|||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
|
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
|
||||||
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) {
|
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants, validationService) {
|
||||||
$scope.loading = true;
|
$scope.loadingCiphers = true;
|
||||||
|
$scope.loadingGroupings = true;
|
||||||
$scope.ciphers = [];
|
$scope.ciphers = [];
|
||||||
|
$scope.folders = [];
|
||||||
|
$scope.collections = [];
|
||||||
$scope.constants = constants;
|
$scope.constants = constants;
|
||||||
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
|
$scope.filter = undefined;
|
||||||
$scope.folderIdFilter = undefined;
|
$scope.selectedType = undefined;
|
||||||
$scope.typeFilter = undefined;
|
$scope.selectedFolder = undefined;
|
||||||
|
$scope.selectedCollection = undefined;
|
||||||
|
$scope.selectedFavorites = false;
|
||||||
|
$scope.selectedAll = true;
|
||||||
|
$scope.selectedTitle = 'All';
|
||||||
|
$scope.selectedIcon = 'fa-th';
|
||||||
|
|
||||||
if ($state.params.refreshFromServer) {
|
if ($state.params.refreshFromServer) {
|
||||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
$rootScope.vaultFolders = $rootScope.vaultCollections = $rootScope.vaultCiphers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
if ($rootScope.vaultFolders && $rootScope.vaultCiphers) {
|
$timeout(function () {
|
||||||
$scope.loading = false;
|
if ($('body').hasClass('control-sidebar-open')) {
|
||||||
loadFolderData($rootScope.vaultFolders);
|
$("#search").focus();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
if (($rootScope.vaultFolders || $rootScope.vaultCollections) && $rootScope.vaultCiphers) {
|
||||||
|
$scope.loadingCiphers = $scope.loadingGroupings = false;
|
||||||
loadCipherData($rootScope.vaultCiphers);
|
loadCipherData($rootScope.vaultCiphers);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -26,21 +39,34 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function loadDataFromServer() {
|
function loadDataFromServer() {
|
||||||
var folderPromise = apiService.folders.list({}, function (folders) {
|
var decFolders = [{
|
||||||
var decFolders = [{
|
id: null,
|
||||||
id: null,
|
name: 'No Folder'
|
||||||
name: 'No Folder'
|
}];
|
||||||
}];
|
|
||||||
|
|
||||||
|
var decCollections = [];
|
||||||
|
|
||||||
|
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
|
||||||
|
for (var i = 0; i < collections.Data.length; i++) {
|
||||||
|
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
|
||||||
|
decCollections.push(decCollection);
|
||||||
|
}
|
||||||
|
}).$promise;
|
||||||
|
|
||||||
|
var folderPromise = apiService.folders.list({}, function (folders) {
|
||||||
for (var i = 0; i < folders.Data.length; i++) {
|
for (var i = 0; i < folders.Data.length; i++) {
|
||||||
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
|
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
|
||||||
decFolders.push(decFolder);
|
decFolders.push(decFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFolderData(decFolders);
|
|
||||||
}).$promise;
|
}).$promise;
|
||||||
|
|
||||||
var cipherPromise = apiService.ciphers.list({}, function (ciphers) {
|
var groupingPromise = $q.all([collectionPromise, folderPromise]).then(function () {
|
||||||
|
$rootScope.vaultCollections = decCollections;
|
||||||
|
$rootScope.vaultFolders = decFolders;
|
||||||
|
$scope.loadingGroupings = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
apiService.ciphers.list({}, function (ciphers) {
|
||||||
var decCiphers = [];
|
var decCiphers = [];
|
||||||
|
|
||||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||||
@@ -48,38 +74,16 @@
|
|||||||
decCiphers.push(decCipher);
|
decCiphers.push(decCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPromise.then(function () {
|
groupingPromise.then(function () {
|
||||||
loadCipherData(decCiphers);
|
loadCipherData(decCiphers);
|
||||||
});
|
});
|
||||||
}).$promise;
|
|
||||||
|
|
||||||
$q.all([cipherPromise, folderPromise]).then(function () {
|
|
||||||
$scope.loading = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFolderData(decFolders) {
|
|
||||||
$rootScope.vaultFolders = $filter('orderBy')(decFolders, folderSort);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadCipherData(decCiphers) {
|
function loadCipherData(decCiphers) {
|
||||||
angular.forEach($rootScope.vaultFolders, function (folderValue, folderIndex) {
|
|
||||||
folderValue.collapsed = $localStorage.collapsedFolders &&
|
|
||||||
(folderValue.id || 'none') in $localStorage.collapsedFolders;
|
|
||||||
|
|
||||||
angular.forEach(decCiphers, function (cipherValue) {
|
|
||||||
if (cipherValue.favorite) {
|
|
||||||
cipherValue.sort = -1;
|
|
||||||
}
|
|
||||||
else if (cipherValue.folderId == folderValue.id) {
|
|
||||||
cipherValue.sort = folderIndex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$rootScope.vaultCiphers = $scope.ciphers = $filter('orderBy')(decCiphers, ['sort', 'name', 'subTitle']);
|
$rootScope.vaultCiphers = $scope.ciphers = $filter('orderBy')(decCiphers, ['sort', 'name', 'subTitle']);
|
||||||
|
|
||||||
var chunks = chunk($rootScope.vaultCiphers, 400);
|
var chunks = chunk($rootScope.vaultCiphers, 200);
|
||||||
if (chunks.length > 0) {
|
if (chunks.length > 0) {
|
||||||
$scope.ciphers = chunks[0];
|
$scope.ciphers = chunks[0];
|
||||||
var delay = 200;
|
var delay = 200;
|
||||||
@@ -94,6 +98,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.loadingCiphers = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortScopedCipherData() {
|
function sortScopedCipherData() {
|
||||||
@@ -110,33 +116,19 @@
|
|||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function folderSort(item) {
|
$scope.groupingSort = function (item) {
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.name.toLowerCase();
|
return item.name.toLowerCase();
|
||||||
}
|
};
|
||||||
|
|
||||||
$scope.clipboardError = function (e) {
|
$scope.clipboardError = function (e) {
|
||||||
alert('Your web browser does not support easy clipboard copying. ' +
|
alert('Your web browser does not support easy clipboard copying. ' +
|
||||||
'Edit the item and copy it manually instead.');
|
'Edit the item and copy it manually instead.');
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.collapseExpand = function (folder, favorite) {
|
|
||||||
if (!$localStorage.collapsedFolders) {
|
|
||||||
$localStorage.collapsedFolders = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = favorite ? 'favorite' : (folder.id || 'none');
|
|
||||||
if (id in $localStorage.collapsedFolders) {
|
|
||||||
delete $localStorage.collapsedFolders[id];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$localStorage.collapsedFolders[id] = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.editCipher = function (cipher) {
|
$scope.editCipher = function (cipher) {
|
||||||
var editModel = $uibModal.open({
|
var editModel = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
@@ -151,6 +143,8 @@
|
|||||||
if (returnVal.action === 'edit') {
|
if (returnVal.action === 'edit') {
|
||||||
var index = $scope.ciphers.indexOf(cipher);
|
var index = $scope.ciphers.indexOf(cipher);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
// restore collection ids since those cannot change on edit here.
|
||||||
|
returnVal.data.collectionIds = $rootScope.vaultCiphers[index].collectionIds;
|
||||||
$rootScope.vaultCiphers[index] = returnVal.data;
|
$rootScope.vaultCiphers[index] = returnVal.data;
|
||||||
}
|
}
|
||||||
sortScopedCipherData();
|
sortScopedCipherData();
|
||||||
@@ -169,14 +163,15 @@
|
|||||||
$scope.addCipher();
|
$scope.addCipher();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.addCipher = function (folder, favorite) {
|
$scope.addCipher = function () {
|
||||||
var addModel = $uibModal.open({
|
var addModel = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||||
controller: 'vaultAddCipherController',
|
controller: 'vaultAddCipherController',
|
||||||
resolve: {
|
resolve: {
|
||||||
selectedFolder: function () { return folder; },
|
selectedFolder: function () { return $scope.selectedFolder; },
|
||||||
checkedFavorite: function () { return favorite; }
|
selectedType: function () { return $scope.selectedType; },
|
||||||
|
checkedFavorite: function () { return $scope.selectedFavorites; }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,6 +243,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.editFolder = function (folder) {
|
$scope.editFolder = function (folder) {
|
||||||
|
if (!folder.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var editModel = $uibModal.open({
|
var editModel = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: 'app/vault/views/vaultEditFolder.html',
|
templateUrl: 'app/vault/views/vaultEditFolder.html',
|
||||||
@@ -277,12 +276,16 @@
|
|||||||
|
|
||||||
addModel.result.then(function (addedFolder) {
|
addModel.result.then(function (addedFolder) {
|
||||||
$rootScope.vaultFolders.push(addedFolder);
|
$rootScope.vaultFolders.push(addedFolder);
|
||||||
loadFolderData($rootScope.vaultFolders);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.deleteFolder = function (folder) {
|
$scope.deleteFolder = function (folder) {
|
||||||
if (!confirm('Are you sure you want to delete this folder (' + folder.name + ')?')) {
|
if (!folder.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Are you sure you want to delete this folder (' + folder.name + ')? ' +
|
||||||
|
'Any items will be moved to "No Folder".')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,19 +294,11 @@
|
|||||||
var index = $rootScope.vaultFolders.indexOf(folder);
|
var index = $rootScope.vaultFolders.indexOf(folder);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
$rootScope.vaultFolders.splice(index, 1);
|
$rootScope.vaultFolders.splice(index, 1);
|
||||||
|
$scope.filterAll();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.canDeleteFolder = function (folder) {
|
|
||||||
if (!folder || !folder.id || !$rootScope.vaultCiphers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ciphers = $filter('filter')($rootScope.vaultCiphers, { folderId: folder.id });
|
|
||||||
return ciphers && ciphers.length === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.share = function (cipher) {
|
$scope.share = function (cipher) {
|
||||||
var modal = $uibModal.open({
|
var modal = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
@@ -314,12 +309,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.result.then(function (orgId) {
|
modal.result.then(function (returned) {
|
||||||
cipher.organizationId = orgId;
|
cipher.organizationId = returned.orgId;
|
||||||
|
cipher.collectionIds = returned.collectionIds || [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.collections = function (cipher) {
|
$scope.editCollections = function (cipher) {
|
||||||
var modal = $uibModal.open({
|
var modal = $uibModal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
||||||
@@ -333,55 +329,109 @@
|
|||||||
if (response.collectionIds && !response.collectionIds.length) {
|
if (response.collectionIds && !response.collectionIds.length) {
|
||||||
removeCipherFromScopes(cipher);
|
removeCipherFromScopes(cipher);
|
||||||
}
|
}
|
||||||
|
else if (response.collectionIds) {
|
||||||
|
cipher.collectionIds = response.collectionIds;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.filterFolder = function (folder) {
|
$scope.filterCollection = function (col) {
|
||||||
$scope.folderIdFilter = folder.id;
|
resetSelected();
|
||||||
|
$scope.selectedCollection = col;
|
||||||
|
$scope.selectedIcon = 'fa-cube';
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterFolder = function (f) {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedFolder = f;
|
||||||
|
$scope.selectedIcon = 'fa-folder-open' + (!f.id ? '-o' : '');
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return c.folderId === f.id;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterType = function (t) {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedType = t;
|
||||||
|
switch (t) {
|
||||||
|
case constants.cipherType.login:
|
||||||
|
$scope.selectedTitle = 'Login';
|
||||||
|
$scope.selectedIcon = 'fa-globe';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.card:
|
||||||
|
$scope.selectedTitle = 'Card';
|
||||||
|
$scope.selectedIcon = 'fa-credit-card';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.identity:
|
||||||
|
$scope.selectedTitle = 'Identity';
|
||||||
|
$scope.selectedIcon = 'fa-id-card-o';
|
||||||
|
break;
|
||||||
|
case constants.cipherType.secureNote:
|
||||||
|
$scope.selectedTitle = 'Secure Note';
|
||||||
|
$scope.selectedIcon = 'fa-sticky-note-o';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return c.type === t;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterFavorites = function () {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedFavorites = true;
|
||||||
|
$scope.selectedTitle = 'Favorites';
|
||||||
|
$scope.selectedIcon = 'fa-star';
|
||||||
|
$scope.filter = function (c) {
|
||||||
|
return !!c.favorite;
|
||||||
|
};
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.filterAll = function () {
|
||||||
|
resetSelected();
|
||||||
|
$scope.selectedAll = true;
|
||||||
|
$scope.selectedTitle = 'All';
|
||||||
|
$scope.selectedIcon = 'fa-th';
|
||||||
|
$scope.filter = null;
|
||||||
|
fixLayout();
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetSelected() {
|
||||||
|
$scope.selectedFolder = undefined;
|
||||||
|
$scope.selectedCollection = undefined;
|
||||||
|
$scope.selectedType = undefined;
|
||||||
|
$scope.selectedFavorites = false;
|
||||||
|
$scope.selectedAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixLayout() {
|
||||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||||
$timeout(function () {
|
$timeout(function () {
|
||||||
$.AdminLTE.layout.fix();
|
$.AdminLTE.layout.fix();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$scope.filterType = function (type) {
|
$scope.cipherFilter = function () {
|
||||||
$scope.typeFilter = type;
|
return function (cipher) {
|
||||||
|
return !$scope.filter || $scope.filter(cipher);
|
||||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
};
|
||||||
$timeout(function () {
|
|
||||||
$.AdminLTE.layout.fix();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.clearFilters = function () {
|
|
||||||
$scope.folderIdFilter = undefined;
|
|
||||||
$scope.typeFilter = undefined;
|
|
||||||
|
|
||||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
|
||||||
$timeout(function () {
|
|
||||||
$.AdminLTE.layout.fix();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.folderFilter = function (folder) {
|
|
||||||
return $scope.folderIdFilter === undefined || folder.id === $scope.folderIdFilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.cipherFilter = function (cipher) {
|
|
||||||
return $scope.typeFilter === undefined || cipher.type === $scope.typeFilter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.unselectAll = function () {
|
$scope.unselectAll = function () {
|
||||||
selectAll(false);
|
selectAll(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.selectFolder = function (folder, $event) {
|
$scope.selectAll = function () {
|
||||||
var checkbox = $($event.currentTarget).closest('.box').find('input[name="cipherSelection"]');
|
selectAll(true);
|
||||||
checkbox.prop('checked', true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.select = function ($event) {
|
$scope.select = function ($event) {
|
||||||
@@ -445,7 +495,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.bulkActionLoading = true;
|
$scope.actionLoading = true;
|
||||||
apiService.ciphers.delMany({ ids: ids }, function () {
|
apiService.ciphers.delMany({ ids: ids }, function () {
|
||||||
$analytics.eventTrack('Bulk Deleted Items');
|
$analytics.eventTrack('Bulk Deleted Items');
|
||||||
|
|
||||||
@@ -457,11 +507,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectAll(false);
|
selectAll(false);
|
||||||
$scope.bulkActionLoading = false;
|
$scope.actionLoading = false;
|
||||||
toastr.success('Items have been deleted!');
|
toastr.success('Items have been deleted!');
|
||||||
}, function () {
|
}, function (e) {
|
||||||
toastr.error('An error occurred.');
|
var errors = validationService.parseErrors(e);
|
||||||
$scope.bulkActionLoading = false;
|
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||||
|
$scope.actionLoading = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
.controller('vaultEditCipherController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService,
|
||||||
passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants) {
|
passwordService, cipherId, $analytics, $rootScope, authService, $uibModal, constants, $filter) {
|
||||||
$analytics.eventTrack('vaultEditCipherController', { category: 'Modal' });
|
$analytics.eventTrack('vaultEditCipherController', { category: 'Modal' });
|
||||||
$scope.folders = $rootScope.vaultFolders;
|
$scope.folders = $rootScope.vaultFolders;
|
||||||
$scope.cipher = {};
|
$scope.cipher = {};
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||||
$scope.readOnly = !$scope.cipher.edit;
|
$scope.readOnly = !$scope.cipher.edit;
|
||||||
$scope.useTotp = $scope.useTotp || $scope.cipher.organizationUseTotp;
|
$scope.useTotp = $scope.useTotp || $scope.cipher.organizationUseTotp;
|
||||||
|
setUriMatchValues();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.save = function (model) {
|
$scope.save = function (model) {
|
||||||
@@ -51,7 +52,43 @@
|
|||||||
$scope.generatePassword = function () {
|
$scope.generatePassword = function () {
|
||||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
$analytics.eventTrack('Generated Password From Edit');
|
$analytics.eventTrack('Generated Password From Edit');
|
||||||
$scope.cipher.login.password = passwordService.generatePassword({ length: 12, special: true });
|
$scope.cipher.login.password = passwordService.generatePassword({ length: 14, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addUri = function () {
|
||||||
|
if (!$scope.cipher.login) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.cipher.login.uris) {
|
||||||
|
$scope.cipher.login.uris = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.cipher.login.uris.push({
|
||||||
|
uri: null,
|
||||||
|
match: null,
|
||||||
|
matchValue: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeUri = function (uri) {
|
||||||
|
if (!$scope.cipher.login || !$scope.cipher.login.uris) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = $scope.cipher.login.uris.indexOf(uri);
|
||||||
|
if (index > -1) {
|
||||||
|
$scope.cipher.login.uris.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.uriMatchChanged = function (uri) {
|
||||||
|
if ((!uri.matchValue && uri.matchValue !== 0) || uri.matchValue === '') {
|
||||||
|
uri.match = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uri.match = parseInt(uri.matchValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,4 +167,14 @@
|
|||||||
controller: 'premiumRequiredController'
|
controller: 'premiumRequiredController'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setUriMatchValues() {
|
||||||
|
if ($scope.cipher.login && $scope.cipher.login.uris) {
|
||||||
|
for (var i = 0; i < $scope.cipher.login.uris.length; i++) {
|
||||||
|
$scope.cipher.login.uris[i].matchValue =
|
||||||
|
$scope.cipher.login.uris[i].match || $scope.cipher.login.uris[i].match === 0 ?
|
||||||
|
$scope.cipher.login.uris[i].match.toString() : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
|
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
|
||||||
$rootScope) {
|
$rootScope, $filter) {
|
||||||
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
|
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
|
||||||
$scope.folders = $rootScope.vaultFolders;
|
$scope.folders = $rootScope.vaultFolders;
|
||||||
$scope.count = ids.length;
|
$scope.count = ids.length;
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var returnedCollectionIds = null;
|
||||||
$scope.submitPromise = $q.all(attachmentSharePromises).then(function () {
|
$scope.submitPromise = $q.all(attachmentSharePromises).then(function () {
|
||||||
if (errorOnUpload) {
|
if (errorOnUpload) {
|
||||||
return;
|
return;
|
||||||
@@ -159,11 +160,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnedCollectionIds = request.collectionIds;
|
||||||
return apiService.ciphers.putShare({ id: cipherId }, request).$promise;
|
return apiService.ciphers.putShare({ id: cipherId }, request).$promise;
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
$analytics.eventTrack('Shared Cipher');
|
$analytics.eventTrack('Shared Cipher');
|
||||||
toastr.success('Item has been shared.');
|
toastr.success('Item has been shared.');
|
||||||
$uibModalInstance.close(model.organizationId);
|
$uibModalInstance.close({
|
||||||
|
orgId: model.organizationId,
|
||||||
|
collectionIds: returnedCollectionIds
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
angular
|
|
||||||
.module('bit.vault')
|
|
||||||
|
|
||||||
.controller('vaultSharedController', function ($scope, apiService, cipherService, $analytics, $q, $localStorage,
|
|
||||||
$uibModal, $filter, $rootScope, authService, cryptoService) {
|
|
||||||
$scope.ciphers = [];
|
|
||||||
$scope.collections = [];
|
|
||||||
$scope.loading = true;
|
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function () {
|
|
||||||
var collectionPromise = apiService.collections.listMe({ writeOnly: false }, function (collections) {
|
|
||||||
var decCollections = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < collections.Data.length; i++) {
|
|
||||||
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
|
|
||||||
decCollection.collapsed = $localStorage.collapsedCollections &&
|
|
||||||
decCollection.id in $localStorage.collapsedCollections;
|
|
||||||
decCollections.push(decCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.collections = decCollections;
|
|
||||||
}).$promise;
|
|
||||||
|
|
||||||
var cipherPromise = apiService.ciphers.listDetails({}, function (ciphers) {
|
|
||||||
var decCiphers = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
|
||||||
var decCipher = cipherService.decryptCipherPreview(ciphers.Data[i]);
|
|
||||||
decCiphers.push(decCipher);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decCiphers.length) {
|
|
||||||
$scope.collections.push({
|
|
||||||
id: null,
|
|
||||||
name: 'Unassigned',
|
|
||||||
collapsed: $localStorage.collapsedCollections && 'unassigned' in $localStorage.collapsedCollections
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.ciphers = decCiphers;
|
|
||||||
}).$promise;
|
|
||||||
|
|
||||||
$q.all([collectionPromise, cipherPromise]).then(function () {
|
|
||||||
$scope.loading = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.clipboardError = function (e) {
|
|
||||||
alert('Your web browser does not support easy clipboard copying. ' +
|
|
||||||
'Edit the item and copy it manually instead.');
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.attachments = function (cipher) {
|
|
||||||
authService.getUserProfile().then(function (profile) {
|
|
||||||
return {
|
|
||||||
isPremium: profile.premium,
|
|
||||||
orgUseStorage: cipher.organizationId && !!profile.organizations[cipher.organizationId].maxStorageGb
|
|
||||||
};
|
|
||||||
}).then(function (perms) {
|
|
||||||
if (cipher.organizationId && !perms.orgUseStorage) {
|
|
||||||
$uibModal.open({
|
|
||||||
animation: true,
|
|
||||||
templateUrl: 'app/views/paidOrgRequired.html',
|
|
||||||
controller: 'paidOrgRequiredController',
|
|
||||||
resolve: {
|
|
||||||
orgId: function () { return cipher.organizationId; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cipher.organizationId && !perms.isPremium) {
|
|
||||||
$uibModal.open({
|
|
||||||
animation: true,
|
|
||||||
templateUrl: 'app/views/premiumRequired.html',
|
|
||||||
controller: 'premiumRequiredController'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cipher.organizationId && !cryptoService.getEncKey()) {
|
|
||||||
toastr.error('You cannot use this feature until you update your encryption key.', 'Feature Unavailable');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachmentModel = $uibModal.open({
|
|
||||||
animation: true,
|
|
||||||
templateUrl: 'app/vault/views/vaultAttachments.html',
|
|
||||||
controller: 'vaultAttachmentsController',
|
|
||||||
resolve: {
|
|
||||||
cipherId: function () { return cipher.id; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
attachmentModel.result.then(function (hasAttachments) {
|
|
||||||
cipher.hasAttachments = hasAttachments;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.filterByCollection = function (collection) {
|
|
||||||
return function (cipher) {
|
|
||||||
if (!cipher.collectionIds || !cipher.collectionIds.length) {
|
|
||||||
return collection.id === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cipher.collectionIds.indexOf(collection.id) > -1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.collectionSort = function (item) {
|
|
||||||
if (!item.id) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.name.toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.collapseExpand = function (collection) {
|
|
||||||
if (!$localStorage.collapsedCollections) {
|
|
||||||
$localStorage.collapsedCollections = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = collection.id || 'unassigned';
|
|
||||||
|
|
||||||
if (id in $localStorage.collapsedCollections) {
|
|
||||||
delete $localStorage.collapsedCollections[id];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$localStorage.collapsedCollections[id] = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.editCipher = function (cipher) {
|
|
||||||
var editModel = $uibModal.open({
|
|
||||||
animation: true,
|
|
||||||
templateUrl: 'app/vault/views/vaultEditCipher.html',
|
|
||||||
controller: 'vaultEditCipherController',
|
|
||||||
resolve: {
|
|
||||||
cipherId: function () { return cipher.id; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
editModel.result.then(function (returnVal) {
|
|
||||||
var rootCipher = findRootCipher(cipher) || { meta: {} },
|
|
||||||
index;
|
|
||||||
|
|
||||||
if (returnVal.action === 'edit') {
|
|
||||||
index = $scope.ciphers.indexOf(cipher);
|
|
||||||
if (index > -1) {
|
|
||||||
returnVal.data.collectionIds = $scope.ciphers[index].collectionIds;
|
|
||||||
$scope.ciphers[index] = returnVal.data;
|
|
||||||
|
|
||||||
if ($rootScope.vaultCiphers) {
|
|
||||||
index = $rootScope.vaultCiphers.indexOf(rootCipher);
|
|
||||||
if (index > -1) {
|
|
||||||
$rootScope.vaultCiphers[index] = returnVal.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (returnVal.action === 'partialEdit') {
|
|
||||||
cipher.folderId = rootCipher.folderId = returnVal.data.folderId;
|
|
||||||
cipher.favorite = rootCipher.favorite = returnVal.data.favorite;
|
|
||||||
}
|
|
||||||
else if (returnVal.action === 'delete') {
|
|
||||||
index = $scope.ciphers.indexOf(cipher);
|
|
||||||
if (index > -1) {
|
|
||||||
$scope.ciphers.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeRootCipher(rootCipher);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.editCollections = function (cipher) {
|
|
||||||
var modal = $uibModal.open({
|
|
||||||
animation: true,
|
|
||||||
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
|
||||||
controller: 'vaultCipherCollectionsController',
|
|
||||||
resolve: {
|
|
||||||
cipherId: function () { return cipher.id; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
modal.result.then(function (response) {
|
|
||||||
if (response.collectionIds) {
|
|
||||||
cipher.collectionIds = response.collectionIds;
|
|
||||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
|
|
||||||
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.removeCipher = function (cipher, collection) {
|
|
||||||
if (!confirm('Are you sure you want to remove this item (' + cipher.name + ') from the ' +
|
|
||||||
'collection (' + collection.name + ') ?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = {
|
|
||||||
collectionIds: []
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var i = 0; i < cipher.collectionIds.length; i++) {
|
|
||||||
if (cipher.collectionIds[i] !== collection.id) {
|
|
||||||
request.collectionIds.push(cipher.collectionIds[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apiService.ciphers.putCollections({ id: cipher.id }, request).$promise.then(function (response) {
|
|
||||||
$analytics.eventTrack('Removed From Collection');
|
|
||||||
cipher.collectionIds = request.collectionIds;
|
|
||||||
// TODO: if there are no collectionIds now, it is possible that the user no longer has access to this cipher
|
|
||||||
// which means it should be removed by calling removeRootCipher(findRootCipher(cipher))
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function findRootCipher(cipher) {
|
|
||||||
if ($rootScope.vaultCiphers) {
|
|
||||||
var rootCiphers = $filter('filter')($rootScope.vaultCiphers, { id: cipher.id });
|
|
||||||
if (rootCiphers && rootCiphers.length) {
|
|
||||||
return rootCiphers[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRootCipher(rootCipher) {
|
|
||||||
if (rootCipher && rootCipher.id) {
|
|
||||||
var index = $rootScope.vaultCiphers.indexOf(rootCipher);
|
|
||||||
if (index > -1) {
|
|
||||||
$rootScope.vaultCiphers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<section class="content-header">
|
<section class="content-header">
|
||||||
<div class="btn-group pull-right">
|
<div class="btn-group pull-right">
|
||||||
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown"
|
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown" ng-disabled="actionLoading">
|
||||||
ng-disabled="bulkActionLoading">
|
<i class="fa fa-refresh fa-spin" ng-show="actionLoading"></i>
|
||||||
<i class="fa fa-refresh fa-spin" ng-show="bulkActionLoading"></i>
|
Actions <span class="caret"></span>
|
||||||
Bulk Actions <span class="caret"></span>
|
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
@@ -17,6 +16,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
|
<li>
|
||||||
|
<a href="#" stop-click ng-click="selectAll()">
|
||||||
|
<i class="fa fa-fw fa-check-square-o"></i> Select All
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" stop-click ng-click="unselectAll()">
|
<a href="#" stop-click ng-click="unselectAll()">
|
||||||
<i class="fa fa-fw fa-minus-square-o"></i> Unselect All
|
<i class="fa fa-fw fa-minus-square-o"></i> Unselect All
|
||||||
@@ -26,170 +30,59 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1>
|
||||||
My Vault
|
My Vault
|
||||||
<small>
|
<small class="visible-md-inline visible-lg-inline">
|
||||||
<span ng-pluralize
|
<span ng-pluralize count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
|
||||||
count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
|
|
||||||
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
|
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
|
||||||
|
<span ng-pluralize count="vaultCollections.length"
|
||||||
|
when="{'1': '{} collection', 'other': '{} collections'}"></span>, &
|
||||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
</section>
|
</section>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<div ng-show="loading && !vaultFolders.length">
|
<div ng-show="loadingCiphers">
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;"
|
<div class="box" ng-show="!loadingCiphers">
|
||||||
ng-show="vaultFolders.length && folderIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">
|
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">
|
<h3 class="box-title">
|
||||||
<i class="fa fa-star"></i>
|
<i class="fa {{selectedIcon}}"></i>
|
||||||
Favorites
|
{{selectedFolder ? selectedFolder.name : selectedCollection ? selectedCollection.name : selectedTitle}}
|
||||||
<small ng-pluralize count="favoriteCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="box-tools">
|
<div class="box-tools" ng-if="selectedFolder && selectedFolder.id">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
|
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
<i class="fa fa-cog"></i> <span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" stop-click ng-click="addCipher(null, true)">
|
<a href="#" stop-click ng-click="editFolder(selectedFolder)">
|
||||||
<i class="fa fa-fw fa-plus-circle"></i> New Item
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
|
||||||
ng-click="collapseExpand(null, true)">
|
|
||||||
<i class="fa" ng-class="{'fa-minus': !favoriteCollapsed, 'fa-plus': favoriteCollapsed}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box-body" ng-class="{'no-padding': favoriteCiphers.length}">
|
|
||||||
<div ng-show="!favoriteCiphers.length">
|
|
||||||
<p>No favorite items.</p>
|
|
||||||
<button type="button" ng-click="addCipher(null, true)" class="btn btn-default btn-flat">Add an Item</button>
|
|
||||||
</div>
|
|
||||||
<div class="table-responsive" ng-show="favoriteCiphers.length">
|
|
||||||
<table class="table table-striped table-hover table-vmiddle">
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } |
|
|
||||||
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id">
|
|
||||||
<td style="width: 70px;">
|
|
||||||
<div class="btn-group" data-append-to="body">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
||||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
|
||||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
|
||||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="!cipher.organizationId">
|
|
||||||
<a href="#" stop-click ng-click="share(cipher)">
|
|
||||||
<i class="fa fa-fw fa-share-alt"></i> Share
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.organizationId && cipher.edit">
|
|
||||||
<a href="#" stop-click ng-click="collections(cipher)">
|
|
||||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.meta.password">
|
|
||||||
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-text="{{cipher.meta.password}}">
|
|
||||||
<i class="fa fa-fw fa-clipboard"></i> Copy Password
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.edit">
|
|
||||||
<a href="#" stop-click ng-click="deleteCipher(cipher)" class="text-red">
|
|
||||||
<i class="fa fa-fw fa-trash"></i> Delete
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="action-select" ng-click="select($event)">
|
|
||||||
<input type="checkbox" value="{{::cipher.id}}" name="cipherSelection" stop-prop />
|
|
||||||
</td>
|
|
||||||
<td class="vault-icon" ng-click="select($event)">
|
|
||||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
|
||||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
|
||||||
fallback-src="images/fa-globe.png" />
|
|
||||||
</td>
|
|
||||||
<td ng-click="select($event)">
|
|
||||||
<a href="#" stop-click ng-click="editCipher(cipher)" stop-prop>{{cipher.name}}</a>
|
|
||||||
<i class="fa fa-share-alt text-muted" title="Shared" ng-if="cipher.organizationId"
|
|
||||||
stop-prop></i>
|
|
||||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
|
||||||
stop-prop></i><br />
|
|
||||||
<span class="text-sm text-muted" stop-prop>{{cipher.subTitle}}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box" ng-class="{'collapsed-box': folder.collapsed}"
|
|
||||||
ng-repeat="folder in filteredVaultFolders = (vaultFolders | filter: folderFilter) track by folder.id"
|
|
||||||
ng-show="vaultFolders.length && (!main.searchVaultText || folderCiphers.length)">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">
|
|
||||||
<i class="fa" ng-class="{'fa-folder-open': folder.id !== null, 'fa-folder-open-o': folder.id === null}"></i>
|
|
||||||
{{folder.name}}
|
|
||||||
<small ng-pluralize count="folderCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
|
||||||
</h3>
|
|
||||||
<div class="box-tools">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
|
|
||||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click ng-click="addCipher(folder)">
|
|
||||||
<i class="fa fa-fw fa-plus-circle"></i> New Item
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="folder.id">
|
|
||||||
<a href="#" stop-click ng-click="editFolder(folder)">
|
|
||||||
<i class="fa fa-fw fa-pencil"></i> Edit Folder
|
<i class="fa fa-fw fa-pencil"></i> Edit Folder
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" stop-click ng-click="selectFolder(folder, $event)">
|
<a href="#" stop-click ng-click="deleteFolder(selectedFolder)" class="text-red">
|
||||||
<i class="fa fa-fw fa-check-square-o"></i> Select All
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="canDeleteFolder(folder)">
|
|
||||||
<a href="#" stop-click ng-click="deleteFolder(folder)" class="text-red">
|
|
||||||
<i class="fa fa-fw fa-trash"></i> Delete Folder
|
<i class="fa fa-fw fa-trash"></i> Delete Folder
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
|
||||||
ng-click="collapseExpand(folder)">
|
|
||||||
<i class="fa" ng-class="{'fa-minus': !folder.collapsed, 'fa-plus': folder.collapsed}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body" ng-class="{'no-padding': folderCiphers.length}">
|
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
|
||||||
<div ng-show="!folderCiphers.length">
|
<div ng-show="!filteredCiphers.length">
|
||||||
<p>No items in this folder.</p>
|
<p>No items to list.</p>
|
||||||
<button type="button" ng-click="addCipher(folder)" class="btn btn-default btn-flat">Add an Item</button>
|
<button type="button" ng-click="addCipher()" class="btn btn-default btn-flat"
|
||||||
|
ng-if="!selectedCollection">
|
||||||
|
Add an Item
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive" ng-show="folderCiphers.length">
|
<div class="table-responsive" ng-show="filteredCiphers.length">
|
||||||
<table class="table table-striped table-hover table-vmiddle">
|
<table class="table table-striped table-hover table-vmiddle">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="cipher in folderCiphers = (ciphers | filter: { folderId: folder.id } |
|
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
|
||||||
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id">
|
filter: (searchVaultText || '')) track by cipher.id">
|
||||||
<td style="width: 70px;">
|
<td style="width: 70px;">
|
||||||
<div class="btn-group" data-append-to="body">
|
<div class="btn-group" data-append-to="body">
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||||
@@ -212,7 +105,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li ng-show="cipher.organizationId && cipher.edit">
|
<li ng-show="cipher.organizationId && cipher.edit">
|
||||||
<a href="#" stop-click ng-click="collections(cipher)">
|
<a href="#" stop-click ng-click="editCollections(cipher)">
|
||||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
<i class="fa fa-fw fa-cubes"></i> Collections
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -257,60 +150,86 @@
|
|||||||
</section>
|
</section>
|
||||||
<aside class="control-sidebar control-sidebar-light">
|
<aside class="control-sidebar control-sidebar-light">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
<form class="search-form">
|
||||||
|
<label for="search" class="sr-only">Search</label>
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<input type="search" id="search" class="form-control" placeholder="Search my vault..."
|
||||||
|
ng-model="searchVaultText" />
|
||||||
|
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
<ul class="control-sidebar-menu">
|
<ul class="control-sidebar-menu">
|
||||||
<li>
|
<li ng-class="{active: selectedAll}">
|
||||||
<a href="#" stop-click ng-click="clearFilters()">
|
<a href="#" stop-click ng-click="filterAll()">
|
||||||
Clear All Filters
|
<i class="fa fa-th fa-fw"></i> All Items
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-class="{active: selectedFavorites}">
|
||||||
|
<a href="#" stop-click ng-click="filterFavorites()">
|
||||||
|
<i class="fa fa-star fa-fw"></i> Favorites
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3 class="control-sidebar-heading">
|
<h3 class="control-sidebar-heading">Types</h3>
|
||||||
<i class="fa fa-tag fa-fw"></i> Types
|
|
||||||
</h3>
|
|
||||||
<div class="control-sidebar-section">
|
<div class="control-sidebar-section">
|
||||||
<ul class="control-sidebar-menu">
|
<ul class="control-sidebar-menu">
|
||||||
<li>
|
<li ng-class="{active: constants.cipherType.login === selectedType}">
|
||||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
||||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.login === typeFilter"></i>
|
<i class="fa fa-globe fa-fw"></i> Login
|
||||||
<i class="fa fa-globe fa-fw" ng-if="constants.cipherType.login !== typeFilter"></i> Login
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li ng-class="{active: constants.cipherType.card === selectedType}">
|
||||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
||||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.card === typeFilter"></i>
|
<i class="fa fa-credit-card fa-fw"></i> Card
|
||||||
<i class="fa fa-credit-card fa-fw" ng-if="constants.cipherType.card !== typeFilter"></i> Card
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li ng-class="{active: constants.cipherType.identity === selectedType}">
|
||||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
||||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.identity === typeFilter"></i>
|
<i class="fa fa-id-card-o fa-fw"></i> Identity
|
||||||
<i class="fa fa-id-card-o fa-fw" ng-if="constants.cipherType.identity !== typeFilter"></i> Identity
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
|
||||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
||||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.secureNote === typeFilter"></i>
|
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
|
||||||
<i class="fa fa-sticky-note-o fa-fw" ng-if="constants.cipherType.secureNote !== typeFilter"></i> Secure Note
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="control-sidebar-heading">
|
<h3 class="control-sidebar-heading">Folders</h3>
|
||||||
<i class="fa fa-folder fa-fw"></i> Folders
|
<div ng-show="loadingGroupings">
|
||||||
</h3>
|
|
||||||
<div ng-show="loading && !vaultFolders.length">
|
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-sidebar-section">
|
<div class="control-sidebar-section" ng-show="!loadingGroupings">
|
||||||
<ul class="control-sidebar-menu" ng-show="!loading && vaultFolders.length">
|
<ul class="control-sidebar-menu">
|
||||||
<li ng-repeat="folder in vaultFolders track by folder.id">
|
<li ng-repeat="folder in vaultFolders | orderBy: [groupingSort] track by folder.id"
|
||||||
|
ng-class="{active: selectedFolder && folder.id === selectedFolder.id}">
|
||||||
<a href="#" stop-click ng-click="filterFolder(folder)">
|
<a href="#" stop-click ng-click="filterFolder(folder)">
|
||||||
<i class="fa fa-check fa-fw" ng-if="folder.id === folderIdFilter"></i>
|
<i class="fa fa-caret-right fa-fw"></i>
|
||||||
<i class="fa fa-caret-right fa-fw" ng-if="folder.id !== folderIdFilter"></i>
|
|
||||||
{{folder.name}}
|
{{folder.name}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="control-sidebar-heading">Collections</h3>
|
||||||
|
<div ng-show="loadingGroupings">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loadingGroupings && !vaultCollections.length">
|
||||||
|
<p>No collections are being shared with you. <i class="fa fa-frown-o"></i></p>
|
||||||
|
<a ui-sref="backend.user.settingsCreateOrg" class="btn btn-default btn-lint">
|
||||||
|
Create an Organization
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="control-sidebar-section" ng-show="!loadingGroupings && vaultCollections.length">
|
||||||
|
<ul class="control-sidebar-menu">
|
||||||
|
<li ng-repeat="collection in vaultCollections | orderBy: [groupingSort] track by collection.id"
|
||||||
|
ng-class="{active: selectedCollection && collection.id === selectedCollection.id}">
|
||||||
|
<a href="#" stop-click ng-click="filterCollection(collection)">
|
||||||
|
<i class="fa fa-caret-right fa-fw"></i>
|
||||||
|
{{collection.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -39,26 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="cipher.type === constants.cipherType.login">
|
<div ng-if="cipher.type === constants.cipherType.login">
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="uri">URI</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
|
||||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
|
||||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#uri">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
|
||||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
|
||||||
<i class="fa fa-share"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
@@ -87,7 +68,8 @@
|
|||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
class="form-control monospaced" ng-readonly="readOnly" api-field
|
||||||
|
autocomplete="new-password" />
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||||
@@ -119,6 +101,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="uri{{$index}}">URI {{$index + 1}}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
|
||||||
|
ng-model="u.uri" class="form-control"
|
||||||
|
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||||
|
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#uri{{$index}}">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||||
|
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||||
|
<i class="fa fa-share"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="uri_match_{{$index}}">Match Detection</label>
|
||||||
|
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||||
|
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||||
|
<option value="">Default</option>
|
||||||
|
<option value="0">Base domain</option>
|
||||||
|
<option value="1">Host</option>
|
||||||
|
<option value="2">Starts with</option>
|
||||||
|
<option value="4">Regular Expression</option>
|
||||||
|
<option value="3">Exact</option>
|
||||||
|
<option value="5">Never</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<br class="hidden-xs" />
|
||||||
|
<a href="#" ng-click="removeUri(u)" stop-click>
|
||||||
|
<i class="fa fa-window-close-o fa-lg"></i>
|
||||||
|
<span class="visible-xs-inline">Remove URI</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="visible-xs-block" />
|
||||||
|
</div>
|
||||||
|
<a href="#" ng-click="addUri()" stop-click>
|
||||||
|
<i class="fa fa-plus-circle"></i> New URI
|
||||||
|
</a>
|
||||||
|
<br /><br />
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="cipher.type === constants.cipherType.card">
|
<div ng-if="cipher.type === constants.cipherType.card">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -504,7 +538,6 @@
|
|||||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||||
<!-- Nothing for now -->
|
<!-- Nothing for now -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
<label for="notes">Notes</label>
|
<label for="notes">Notes</label>
|
||||||
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes" api-field
|
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes" api-field
|
||||||
|
|||||||
@@ -33,24 +33,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="cipher.type === constants.cipherType.login">
|
<div ng-if="cipher.type === constants.cipherType.login">
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="uri">URI</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
|
||||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
|
||||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#uri">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
|
||||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
|
||||||
<i class="fa fa-share"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="form-group" show-errors>
|
<div class="form-group" show-errors>
|
||||||
@@ -79,7 +61,8 @@
|
|||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
class="form-control monospaced" ng-readonly="readOnly" api-field
|
||||||
|
autocomplete="new-password" />
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||||
@@ -111,6 +94,60 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="uri{{$index}}">URI {{$index + 1}}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
|
||||||
|
ng-model="u.uri" class="form-control"
|
||||||
|
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||||
|
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#uri{{$index}}">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||||
|
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||||
|
<i class="fa fa-share"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="uri_match_{{$index}}">Match Detection</label>
|
||||||
|
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match" ng-disabled="readOnly"
|
||||||
|
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||||
|
<option value="">Default</option>
|
||||||
|
<option value="0">Base domain</option>
|
||||||
|
<option value="1">Host</option>
|
||||||
|
<option value="2">Starts with</option>
|
||||||
|
<option value="4">Regular Expression</option>
|
||||||
|
<option value="3">Exact</option>
|
||||||
|
<option value="5">Never</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1" ng-if="!readOnly">
|
||||||
|
<br class="hidden-xs" />
|
||||||
|
<a href="#" ng-click="removeUri(u)" stop-click>
|
||||||
|
<i class="fa fa-window-close-o fa-lg"></i>
|
||||||
|
<span class="visible-xs-inline">Remove URI</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="visible-xs-block" />
|
||||||
|
</div>
|
||||||
|
<div ng-if="!readOnly">
|
||||||
|
<a href="#" ng-click="addUri()" stop-click>
|
||||||
|
<i class="fa fa-plus-circle"></i> New URI
|
||||||
|
</a>
|
||||||
|
<br /><br />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="cipher.type === constants.cipherType.card">
|
<div ng-if="cipher.type === constants.cipherType.card">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -551,9 +588,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1">
|
<div class="col-sm-1" ng-if="!readOnly">
|
||||||
<br class="hidden-xs" />
|
<br class="hidden-xs" />
|
||||||
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
|
<a href="#" ng-click="removeField(field)" stop-click>
|
||||||
<i class="fa fa-window-close-o fa-lg"></i>
|
<i class="fa fa-window-close-o fa-lg"></i>
|
||||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<p ng-show="loading">Loading...</p>
|
<p ng-show="loading">Loading...</p>
|
||||||
<div ng-show="!loading && !organizations.length" class="callout callout-default">
|
<div ng-show="!loading && !organizations.length" class="callout callout-default">
|
||||||
<h4><i class="fa fa-info-circle"></i> No Organizations</h4>
|
<h4><i class="fa fa-info-circle"></i> No Organizations</h4>
|
||||||
<p>You do not belong to any organizations. Organizations allow you to share items with other bitwarden users.</p>
|
<p>You do not belong to any organizations. Organizations allow you to share items with other Bitwarden users.</p>
|
||||||
<a ng-click="createOrg()" class="btn btn-default btn-flat">
|
<a ng-click="createOrg()" class="btn btn-default btn-flat">
|
||||||
Create an Organization
|
Create an Organization
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
<section class="content-header">
|
|
||||||
<h1>
|
|
||||||
Shared
|
|
||||||
<small>
|
|
||||||
<span ng-pluralize
|
|
||||||
count="collections.length > 0 && ciphers.length ? collections.length - 1 : collections.length"
|
|
||||||
when="{'1': '{} collection', 'other': '{} collections'}"></span>,
|
|
||||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
</section>
|
|
||||||
<section class="content">
|
|
||||||
<p ng-show="loading && !collections.length">Loading...</p>
|
|
||||||
<div class="callout callout-default" style="background: #fff;" ng-show="!loading && !collections.length && !ciphers.length">
|
|
||||||
<h4>Nothing shared <i class="fa fa-frown-o"></i></h4>
|
|
||||||
<p>
|
|
||||||
You do not have any items or collections being shared with you.
|
|
||||||
To start sharing, create an organization or ask an existing organization to invite you.
|
|
||||||
</p>
|
|
||||||
<a ui-sref="backend.user.settingsCreateOrg" class="btn btn-default btn-flat">
|
|
||||||
Create an Organization
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
|
|
||||||
orderBy: collectionSort track by collection.id"
|
|
||||||
ng-show="collections.length">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">
|
|
||||||
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
|
|
||||||
{{collection.name}}
|
|
||||||
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
|
||||||
</h3>
|
|
||||||
<div class="box-tools">
|
|
||||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
|
||||||
ng-click="collapseExpand(collection)">
|
|
||||||
<i class="fa" ng-class="{'fa-minus': !collection.collapsed, 'fa-plus': collection.collapsed}"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
|
|
||||||
<div ng-show="!collectionCiphers.length && collection.id">
|
|
||||||
<p>No items in this collection.</p>
|
|
||||||
<p>
|
|
||||||
Share an item to this collection by selecting <i class="fa fa-share-alt"></i> <b>Share</b> or
|
|
||||||
<i class="fa fa-cubes"></i> <b>Collections</b> from the item's options (<i class="fa fa-cog"></i>) menu.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
|
|
||||||
<div class="table-responsive" ng-show="collectionCiphers.length">
|
|
||||||
<table class="table table-striped table-hover table-vmiddle">
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
|
|
||||||
orderBy: ['name', 'subTitle']) track by cipher.id">
|
|
||||||
<td style="width: 70px;">
|
|
||||||
<div class="btn-group" data-append-to="body">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
||||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click ng-click="editCipher(cipher)">
|
|
||||||
<i class="fa fa-fw fa-pencil"></i> Edit
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click ng-click="attachments(cipher)">
|
|
||||||
<i class="fa fa-fw fa-paperclip"></i> Attachments
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.edit">
|
|
||||||
<a href="#" stop-click ng-click="editCollections(cipher)">
|
|
||||||
<i class="fa fa-fw fa-cubes"></i> Collections
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.meta.password">
|
|
||||||
<a href="#" stop-click ngclipboard ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-text="{{cipher.meta.password}}">
|
|
||||||
<i class="fa fa-fw fa-clipboard"></i> Copy Password
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li ng-show="cipher.edit">
|
|
||||||
<a href="#" stop-click ng-click="removeCipher(cipher, collection)"
|
|
||||||
ng-if="collection.id" class="text-red">
|
|
||||||
<i class="fa fa-fw fa-remove"></i> Remove
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="vault-icon">
|
|
||||||
<i class="fa fa-fw fa-lg {{::cipher.icon}}" ng-if="!cipher.meta.image"></i>
|
|
||||||
<img alt="" ng-if="cipher.meta.image" ng-src="{{cipher.meta.image}}"
|
|
||||||
fallback-src="images/fa-globe.png" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" stop-click ng-click="editCipher(cipher)">{{cipher.name}}</a>
|
|
||||||
<i class="fa fa-star text-muted" title="Favorite" ng-show="cipher.favorite"></i>
|
|
||||||
<i class="fa fa-paperclip text-muted" title="Attachments" ng-if="cipher.hasAttachments"
|
|
||||||
stop-prop></i><br />
|
|
||||||
<div class="text-sm text-muted">{{cipher.subTitle}}</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
<section class="content-header">
|
|
||||||
<h1>
|
|
||||||
Apps
|
|
||||||
<small>for all of your devices</small>
|
|
||||||
</h1>
|
|
||||||
</section>
|
|
||||||
<section class="content">
|
|
||||||
<div class="box box-default box-apps">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Desktop/Browser</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
|
|
||||||
<i class="fa fa-chrome fa-lg fa-fw fa-li"></i> Google Chrome
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
|
|
||||||
<i class="fa fa-firefox fa-lg fa-fw fa-li"></i> Mozilla Firefox
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://addons.opera.com/extensions/details/bitwarden-free-password-manager/" target="_blank">
|
|
||||||
<i class="fa fa-opera fa-lg fa-fw fa-li"></i> Opera
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl" target="_blank">
|
|
||||||
<i class="fa fa-edge fa-lg fa-fw fa-li"></i> Microsoft Edge
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
Others:
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
|
|
||||||
Vivaldi
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://brave.com/" target="_blank">
|
|
||||||
Brave
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
|
|
||||||
Tor Browser
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box box-default box-apps">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Mobile</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="https://itunes.apple.com/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank">
|
|
||||||
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> iOS
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank">
|
|
||||||
<i class="fa fa-android fa-lg fa-fw fa-li"></i> Android
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="https://www.amazon.com/dp/B06XMYGPMV" target="_blank">
|
|
||||||
<i class="fa fa-amazon fa-lg fa-fw fa-li"></i> Amazon
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click>
|
|
||||||
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Windows
|
|
||||||
<small class="text-muted">(coming soon)</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box box-default box-apps">
|
|
||||||
<div class="box-header with-border">
|
|
||||||
<h3 class="box-title">Other</h3>
|
|
||||||
</div>
|
|
||||||
<div class="box-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click>
|
|
||||||
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Desktop Windows
|
|
||||||
<small class="text-muted">(coming soon)</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click>
|
|
||||||
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> Desktop macOS
|
|
||||||
<small class="text-muted">(coming soon)</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click>
|
|
||||||
<i class="fa fa-linux fa-lg fa-fw fa-li"></i> Desktop Linux
|
|
||||||
<small class="text-muted">(coming soon)</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" stop-click>
|
|
||||||
<i class="fa fa-terminal fa-lg fa-fw fa-li"></i> CLI
|
|
||||||
<small class="text-muted">(coming soon)</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
@@ -32,14 +32,6 @@
|
|||||||
<a ui-sref="backend.user.vault"><i class="fa fa-arrow-left"></i> Return to my vault</a>
|
<a ui-sref="backend.user.vault"><i class="fa fa-arrow-left"></i> Return to my vault</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form class="sidebar-form">
|
|
||||||
<label for="search" class="sr-only">Search</label>
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search org. vault..."
|
|
||||||
ng-focus="searchOrganizationVault()" ng-model="main.searchVaultText" />
|
|
||||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<ul class="sidebar-menu">
|
<ul class="sidebar-menu">
|
||||||
<li class="header">MY ORGANIZATION</li>
|
<li class="header">MY ORGANIZATION</li>
|
||||||
<li ng-class="{active: $state.is('backend.org.dashboard')}">
|
<li ng-class="{active: $state.is('backend.org.dashboard')}">
|
||||||
@@ -95,6 +87,11 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li ng-class="{active: $state.is('backend.org.events')}" ng-if="orgProfile.useEvents">
|
||||||
|
<a ui-sref="backend.org.events({orgId: params.orgId})">
|
||||||
|
<i class="fa fa-file-text-o fa-fw"></i> <span>Event Logs</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li ng-class="{active: $state.is('backend.org.billing')}" ng-if="isOrgOwner(orgProfile)">
|
<li ng-class="{active: $state.is('backend.org.billing')}" ng-if="isOrgOwner(orgProfile)">
|
||||||
<a ui-sref="backend.org.billing({orgId: params.orgId})">
|
<a ui-sref="backend.org.billing({orgId: params.orgId})">
|
||||||
<i class="fa fa-credit-card fa-fw"></i> Billing & Licensing
|
<i class="fa fa-credit-card fa-fw"></i> Billing & Licensing
|
||||||
|
|||||||
@@ -32,14 +32,6 @@
|
|||||||
<a ui-sref="frontend.logout">Log Out</a>
|
<a ui-sref="frontend.logout">Log Out</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form class="sidebar-form">
|
|
||||||
<label for="search" class="sr-only">Search</label>
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search my vault..."
|
|
||||||
ng-focus="searchVault()" ng-model="main.searchVaultText" />
|
|
||||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<ul class="sidebar-menu">
|
<ul class="sidebar-menu">
|
||||||
<li class="header">WEB VAULT</li>
|
<li class="header">WEB VAULT</li>
|
||||||
<li class="treeview" ng-class="{active: $state.includes('backend.user.vault')}">
|
<li class="treeview" ng-class="{active: $state.includes('backend.user.vault')}">
|
||||||
@@ -57,11 +49,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="treeview" ng-class="{active: $state.is('backend.user.shared')}">
|
|
||||||
<a ui-sref="backend.user.shared">
|
|
||||||
<i class="fa fa-share-alt fa-fw"></i> <span>Shared</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="treeview" ng-class="{active: $state.is('backend.user.tools') ||
|
<li class="treeview" ng-class="{active: $state.is('backend.user.tools') ||
|
||||||
$state.is('backend.user.reportsBreach')}">
|
$state.is('backend.user.reportsBreach')}">
|
||||||
<a ui-sref="backend.user.tools"><i class="fa fa-wrench fa-fw"></i> <span>Tools</span></a>
|
<a ui-sref="backend.user.tools"><i class="fa fa-wrench fa-fw"></i> <span>Tools</span></a>
|
||||||
@@ -109,7 +96,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li ng-class="{active: $state.is('backend.user.apps')}">
|
<li ng-class="{active: $state.is('backend.user.apps')}">
|
||||||
<a ui-sref="backend.user.apps">
|
<a href="https://bitwarden.com/#download" target="_blank">
|
||||||
<small class="label pull-right bg-green">FREE</small>
|
<small class="label pull-right bg-green">FREE</small>
|
||||||
<i class="fa fa-download fa-fw"></i> <span>Get the Apps</span>
|
<i class="fa fa-download fa-fw"></i> <span>Get the Apps</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -26,47 +26,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="js/duo.js"></script>
|
<script src="js/duo.js"></script>
|
||||||
<script>
|
<script src="js/duo-connector.js"></script>
|
||||||
!(function () {
|
|
||||||
var frameElement = document.createElement('iframe');
|
|
||||||
frameElement.setAttribute('id', 'duo_iframe');
|
|
||||||
setFrameHeight();
|
|
||||||
document.body.appendChild(frameElement);
|
|
||||||
|
|
||||||
var hostParam = getQsParam('host');
|
|
||||||
var requestParam = getQsParam('request');
|
|
||||||
Duo.init({
|
|
||||||
host: hostParam,
|
|
||||||
sig_request: requestParam,
|
|
||||||
submit_callback: function (form) {
|
|
||||||
invokeCSCode(form.elements.sig_response.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.onresize = setFrameHeight;
|
|
||||||
function setFrameHeight() {
|
|
||||||
frameElement.style.height = window.innerHeight + 'px';
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
function getQsParam(name) {
|
|
||||||
var url = window.location.href;
|
|
||||||
name = name.replace(/[\[\]]/g, '\\$&');
|
|
||||||
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
|
||||||
results = regex.exec(url);
|
|
||||||
if (!results) return null;
|
|
||||||
if (!results[2]) return '';
|
|
||||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
function invokeCSCode(data) {
|
|
||||||
try {
|
|
||||||
invokeCSharpAction(data);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,55 +2,6 @@
|
|||||||
<html ng-app="bit" ng-csp>
|
<html ng-app="bit" ng-csp>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<!-- @if !selfHosted -->
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="
|
|
||||||
default-src
|
|
||||||
'self';
|
|
||||||
script-src
|
|
||||||
'self'
|
|
||||||
'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w='
|
|
||||||
https://www.google-analytics.com
|
|
||||||
https://js.stripe.com
|
|
||||||
https://js.braintreegateway.com
|
|
||||||
https://www.paypalobjects.com
|
|
||||||
https://maxcdn.bootstrapcdn.com
|
|
||||||
https://ajax.googleapis.com;
|
|
||||||
style-src
|
|
||||||
'self'
|
|
||||||
'unsafe-inline'
|
|
||||||
https://maxcdn.bootstrapcdn.com
|
|
||||||
https://assets.braintreegateway.com
|
|
||||||
https://*.paypal.com
|
|
||||||
https://fonts.googleapis.com;
|
|
||||||
img-src
|
|
||||||
'self'
|
|
||||||
data:
|
|
||||||
https://icons.bitwarden.com
|
|
||||||
https://*.paypal.com
|
|
||||||
https://www.paypalobjects.com
|
|
||||||
https://q.stripe.com
|
|
||||||
https://haveibeenpwned.com
|
|
||||||
https://chart.googleapis.com
|
|
||||||
https://www.google-analytics.com;
|
|
||||||
font-src
|
|
||||||
'self'
|
|
||||||
https://maxcdn.bootstrapcdn.com
|
|
||||||
https://fonts.gstatic.com;
|
|
||||||
child-src
|
|
||||||
'self'
|
|
||||||
https://js.stripe.com
|
|
||||||
https://assets.braintreegateway.com
|
|
||||||
https://*.paypal.com
|
|
||||||
https://*.duosecurity.com;
|
|
||||||
frame-src
|
|
||||||
'self'
|
|
||||||
https://js.stripe.com
|
|
||||||
https://assets.braintreegateway.com
|
|
||||||
https://*.paypal.com
|
|
||||||
https://*.duosecurity.com;
|
|
||||||
connect-src
|
|
||||||
*;">
|
|
||||||
<!-- @endif -->
|
|
||||||
<!-- @if selfHosted !>
|
<!-- @if selfHosted !>
|
||||||
<meta http-equiv="Content-Security-Policy" content="
|
<meta http-equiv="Content-Security-Policy" content="
|
||||||
default-src
|
default-src
|
||||||
@@ -80,7 +31,7 @@
|
|||||||
<meta name="theme-color" content="#3c8dbc">
|
<meta name="theme-color" content="#3c8dbc">
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<title page-title>bitwarden Web Vault</title>
|
<title page-title>Bitwarden Web Vault</title>
|
||||||
|
|
||||||
<!-- @if !selfHosted -->
|
<!-- @if !selfHosted -->
|
||||||
<script src="https://js.stripe.com/v2/"></script>
|
<script src="https://js.stripe.com/v2/"></script>
|
||||||
@@ -116,12 +67,12 @@
|
|||||||
<div ui-view></div>
|
<div ui-view></div>
|
||||||
|
|
||||||
<!-- @if !selfHosted !>
|
<!-- @if !selfHosted !>
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
|
||||||
integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB" crossorigin="anonymous"></script>
|
integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f" crossorigin="anonymous"></script>
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
|
||||||
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"
|
||||||
integrity="sha384-AH/e+s4V4kUifvnNED2x1XZqArO5qTFU4YKRzUXbz4IgPG1H0Xmz6fP1XUmO4vT/" crossorigin="anonymous"></script>
|
integrity="sha384-R6kAKgTgRiD5889XyzYD/aMryNA4Yr9EBnt6rIXuukLgVONifQDnHNaadrSNakQl" crossorigin="anonymous"></script>
|
||||||
<!-- @endif -->
|
<!-- @endif -->
|
||||||
<!-- @if true !>
|
<!-- @if true !>
|
||||||
<script src="js/fallback-scripts.min.js?v=<!-- @echo cacheTag !>"></script>
|
<script src="js/fallback-scripts.min.js?v=<!-- @echo cacheTag !>"></script>
|
||||||
@@ -195,12 +146,13 @@
|
|||||||
<script src="app/services/validationService.js"></script>
|
<script src="app/services/validationService.js"></script>
|
||||||
<script src="app/services/passwordService.js"></script>
|
<script src="app/services/passwordService.js"></script>
|
||||||
<script src="app/services/importService.js"></script>
|
<script src="app/services/importService.js"></script>
|
||||||
|
<script src="app/services/eventService.js"></script>
|
||||||
|
<script src="app/services/utilsService.js"></script>
|
||||||
|
|
||||||
<script src="app/global/globalModule.js"></script>
|
<script src="app/global/globalModule.js"></script>
|
||||||
<script src="app/global/mainController.js"></script>
|
<script src="app/global/mainController.js"></script>
|
||||||
<script src="app/global/topNavController.js"></script>
|
<script src="app/global/topNavController.js"></script>
|
||||||
<script src="app/global/sideNavController.js"></script>
|
<script src="app/global/sideNavController.js"></script>
|
||||||
<script src="app/global/appsController.js"></script>
|
|
||||||
<script src="app/global/premiumRequiredController.js"></script>
|
<script src="app/global/premiumRequiredController.js"></script>
|
||||||
<script src="app/global/paidOrgRequiredController.js"></script>
|
<script src="app/global/paidOrgRequiredController.js"></script>
|
||||||
|
|
||||||
@@ -223,7 +175,6 @@
|
|||||||
<script src="app/vault/vaultEditFolderController.js"></script>
|
<script src="app/vault/vaultEditFolderController.js"></script>
|
||||||
<script src="app/vault/vaultAddFolderController.js"></script>
|
<script src="app/vault/vaultAddFolderController.js"></script>
|
||||||
<script src="app/vault/vaultShareCipherController.js"></script>
|
<script src="app/vault/vaultShareCipherController.js"></script>
|
||||||
<script src="app/vault/vaultSharedController.js"></script>
|
|
||||||
<script src="app/vault/vaultCipherCollectionsController.js"></script>
|
<script src="app/vault/vaultCipherCollectionsController.js"></script>
|
||||||
<script src="app/vault/vaultMoveCiphersController.js"></script>
|
<script src="app/vault/vaultMoveCiphersController.js"></script>
|
||||||
<script src="app/vault/vaultAttachmentsController.js"></script>
|
<script src="app/vault/vaultAttachmentsController.js"></script>
|
||||||
@@ -234,6 +185,7 @@
|
|||||||
<script src="app/organization/organizationPeopleInviteController.js"></script>
|
<script src="app/organization/organizationPeopleInviteController.js"></script>
|
||||||
<script src="app/organization/organizationPeopleEditController.js"></script>
|
<script src="app/organization/organizationPeopleEditController.js"></script>
|
||||||
<script src="app/organization/organizationPeopleGroupsController.js"></script>
|
<script src="app/organization/organizationPeopleGroupsController.js"></script>
|
||||||
|
<script src="app/organization/organizationPeopleEventsController.js"></script>
|
||||||
<script src="app/organization/organizationCollectionsController.js"></script>
|
<script src="app/organization/organizationCollectionsController.js"></script>
|
||||||
<script src="app/organization/organizationCollectionsAddController.js"></script>
|
<script src="app/organization/organizationCollectionsAddController.js"></script>
|
||||||
<script src="app/organization/organizationCollectionsEditController.js"></script>
|
<script src="app/organization/organizationCollectionsEditController.js"></script>
|
||||||
@@ -253,11 +205,13 @@
|
|||||||
<script src="app/organization/organizationVaultAddCipherController.js"></script>
|
<script src="app/organization/organizationVaultAddCipherController.js"></script>
|
||||||
<script src="app/organization/organizationVaultEditCipherController.js"></script>
|
<script src="app/organization/organizationVaultEditCipherController.js"></script>
|
||||||
<script src="app/organization/organizationVaultCipherCollectionsController.js"></script>
|
<script src="app/organization/organizationVaultCipherCollectionsController.js"></script>
|
||||||
|
<script src="app/organization/organizationVaultCipherEventsController.js"></script>
|
||||||
<script src="app/organization/organizationVaultAttachmentsController.js"></script>
|
<script src="app/organization/organizationVaultAttachmentsController.js"></script>
|
||||||
<script src="app/organization/organizationGroupsController.js"></script>
|
<script src="app/organization/organizationGroupsController.js"></script>
|
||||||
<script src="app/organization/organizationGroupsAddController.js"></script>
|
<script src="app/organization/organizationGroupsAddController.js"></script>
|
||||||
<script src="app/organization/organizationGroupsEditController.js"></script>
|
<script src="app/organization/organizationGroupsEditController.js"></script>
|
||||||
<script src="app/organization/organizationGroupsUsersController.js"></script>
|
<script src="app/organization/organizationGroupsUsersController.js"></script>
|
||||||
|
<script src="app/organization/organizationEventsController.js"></script>
|
||||||
|
|
||||||
<script src="app/settings/settingsModule.js"></script>
|
<script src="app/settings/settingsModule.js"></script>
|
||||||
<script src="app/settings/settingsController.js"></script>
|
<script src="app/settings/settingsController.js"></script>
|
||||||
|
|||||||
40
src/js/duo-connector.js
Normal file
40
src/js/duo-connector.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
!(function () {
|
||||||
|
var frameElement = document.createElement('iframe');
|
||||||
|
frameElement.setAttribute('id', 'duo_iframe');
|
||||||
|
setFrameHeight();
|
||||||
|
document.body.appendChild(frameElement);
|
||||||
|
|
||||||
|
var hostParam = getQsParam('host');
|
||||||
|
var requestParam = getQsParam('request');
|
||||||
|
Duo.init({
|
||||||
|
host: hostParam,
|
||||||
|
sig_request: requestParam,
|
||||||
|
submit_callback: function (form) {
|
||||||
|
invokeCSCode(form.elements.sig_response.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.onresize = setFrameHeight;
|
||||||
|
function setFrameHeight() {
|
||||||
|
frameElement.style.height = window.innerHeight + 'px';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function getQsParam(name) {
|
||||||
|
var url = window.location.href;
|
||||||
|
name = name.replace(/[\[\]]/g, '\\$&');
|
||||||
|
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
||||||
|
results = regex.exec(url);
|
||||||
|
if (!results) return null;
|
||||||
|
if (!results[2]) return '';
|
||||||
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeCSCode(data) {
|
||||||
|
try {
|
||||||
|
invokeCSharpAction(data);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,7 +152,14 @@ body.skin-blue {
|
|||||||
|
|
||||||
.control-sidebar-heading {
|
.control-sidebar-heading {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 10px 0 10px 0;
|
margin: 15px 0 10px 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-sidebar-light .control-sidebar-heading {
|
||||||
|
color: @text-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-sidebar-menu {
|
.control-sidebar-menu {
|
||||||
@@ -160,10 +167,11 @@ body.skin-blue {
|
|||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: @text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.active a {
|
li.active a, li.active a:hover {
|
||||||
background-color: @component-active-bg;
|
background-color: lighten(@gray-lte, 5%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +180,8 @@ body.skin-blue {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
margin-right: -15px;
|
margin-right: -15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-left: -15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-sidebar-open {
|
.control-sidebar-open {
|
||||||
@@ -524,6 +534,10 @@ form .btn .loading-icon {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-body p:last-child {
|
.box-body p:last-child {
|
||||||
@@ -731,7 +745,7 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
.totp-col {
|
.totp-col {
|
||||||
margin: -10px 0 10px 0;
|
margin: -10px 0 10px 0;
|
||||||
|
|
||||||
@media (min-width: @screen-md) {
|
@media (min-width: @screen-sm) {
|
||||||
padding-top: 26px;
|
padding-top: 26px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -811,9 +825,9 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
textarea {
|
textarea {
|
||||||
&#notes {
|
&#notes {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
}
|
|
||||||
|
|
||||||
&.big-textarea {
|
&.big-textarea {
|
||||||
height: 200px !important;
|
height: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "bitwarden vault",
|
"name": "Bitwarden Vault",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "images/icons/android-chrome-192x192.png",
|
"src": "images/icons/android-chrome-192x192.png",
|
||||||
|
|||||||
3
src/version.json
Normal file
3
src/version.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user