mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
192eb7e7c9 | ||
|
|
48a36649fa | ||
|
|
6b83b53401 | ||
|
|
c953c2b93f | ||
|
|
7cdce165fb | ||
|
|
b393064f26 | ||
|
|
31bf22063e | ||
|
|
cd9a43f359 | ||
|
|
c326e03eb2 | ||
|
|
f0a986aa04 | ||
|
|
4225da355d | ||
|
|
14bac6a744 | ||
|
|
05cc9b45e6 | ||
|
|
dba596bf35 | ||
|
|
db39d58ea8 | ||
|
|
c0f38216ef | ||
|
|
3643222b3c | ||
|
|
551217ea38 | ||
|
|
24bf1363ab | ||
|
|
08b2184e12 | ||
|
|
b73161882c | ||
|
|
e2186ecd62 | ||
|
|
b407402f3f | ||
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -199,5 +199,4 @@ FakesAssemblies/
|
||||
*.opt
|
||||
|
||||
# Other
|
||||
package-lock.json
|
||||
src/js/*.min.js
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,10 +1,15 @@
|
||||
FROM bitwarden/server
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
COPY ./dist .
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
5
ISSUE_TEMPLATE.md
Normal file
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,10 +1,10 @@
|
||||
[](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/WB8J2bt.png" alt="" />
|
||||
|
||||
# Build/Run
|
||||
|
||||
|
||||
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
|
||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||
|
||||
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
||||
|
||||
# In-scope
|
||||
|
||||
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
||||
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
|
||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
||||
code is available at https://github.com/bitwarden.
|
||||
|
||||
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
||||
|
||||
The following bug classes are out-of scope:
|
||||
|
||||
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
||||
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
|
||||
or that we already know of. Note that some of our issue tracking is private.
|
||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
||||
upstream maintainer.
|
||||
- Attacks requiring physical access to a user's device.
|
||||
- Self-XSS
|
||||
- Issues related to software or protocols not under bitwarden's control
|
||||
- Vulnerabilities in outdated versions of bitwarden
|
||||
- Issues related to software or protocols not under Bitwarden's control
|
||||
- Vulnerabilities in outdated versions of Bitwarden
|
||||
- Missing security best practices that do not directly lead to a vulnerability
|
||||
- Issues that do not have any impact on the general public
|
||||
|
||||
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
|
||||
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of bitwarden staff or contractors
|
||||
- Any physical attempts against bitwarden property or data centers
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
|
||||
Thank you for helping keep bitwarden and our users safe!
|
||||
Thank you for helping keep Bitwarden and our users safe!
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.9
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "bitwarden-web", ".", "{25BEDEF4-2CAF-445A-807D-63C17FF85694}"
|
||||
ProjectSection(WebsiteProperties) = preProject
|
||||
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.6.1"
|
||||
Debug.AspNetCompiler.VirtualPath = "/localhost_15509"
|
||||
Debug.AspNetCompiler.PhysicalPath = "."
|
||||
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
|
||||
Debug.AspNetCompiler.Updateable = "true"
|
||||
Debug.AspNetCompiler.ForceOverwrite = "true"
|
||||
Debug.AspNetCompiler.FixedNames = "false"
|
||||
Debug.AspNetCompiler.Debug = "True"
|
||||
Release.AspNetCompiler.VirtualPath = "/localhost_15509"
|
||||
Release.AspNetCompiler.PhysicalPath = "."
|
||||
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
|
||||
Release.AspNetCompiler.Updateable = "true"
|
||||
Release.AspNetCompiler.ForceOverwrite = "true"
|
||||
Release.AspNetCompiler.FixedNames = "false"
|
||||
Release.AspNetCompiler.Debug = "False"
|
||||
VWDPort = "15509"
|
||||
SlnRelativePath = "."
|
||||
DefaultWebSiteLanguage = "Visual C#"
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
2
dist/.publish
vendored
2
dist/.publish
vendored
Submodule dist/.publish updated: 697d06985c...be3a93a581
@@ -1,5 +1,39 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
# Setup
|
||||
|
||||
GROUPNAME="bitwarden"
|
||||
USERNAME="bitwarden"
|
||||
|
||||
LUID=${LOCAL_UID:-0}
|
||||
LGID=${LOCAL_GID:-0}
|
||||
|
||||
# Step down from host root to well-known nobody/nogroup user
|
||||
|
||||
if [ $LUID -eq 0 ]
|
||||
then
|
||||
LUID=65534
|
||||
fi
|
||||
if [ $LGID -eq 0 ]
|
||||
then
|
||||
LGID=65534
|
||||
fi
|
||||
|
||||
# Create user and group
|
||||
|
||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||
mkhomedir_helper $USERNAME
|
||||
|
||||
# The rest...
|
||||
|
||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||
cp /etc/bitwarden/web/settings.js /app/js/settings.js
|
||||
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
||||
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false
|
||||
chown -R $USERNAME:$GROUPNAME /app
|
||||
chown -R $USERNAME:$GROUPNAME /bitwarden_server
|
||||
|
||||
exec gosu $USERNAME:$GROUPNAME 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'),
|
||||
preprocess = require('gulp-preprocess'),
|
||||
runSequence = require('run-sequence'),
|
||||
jeditor = require("gulp-json-editor"),
|
||||
merge = require('merge-stream'),
|
||||
ngConfig = require('gulp-ng-config'),
|
||||
settings = require('./settings.json'),
|
||||
@@ -71,12 +72,13 @@ gulp.task('min:js', ['clean:js'], function () {
|
||||
'!' + paths.minJs,
|
||||
'!' + paths.jsDir + 'fallback*.js',
|
||||
'!' + paths.jsDir + 'u2f-connector.js',
|
||||
'!' + paths.jsDir + 'duo-connector.js',
|
||||
'!' + paths.jsDir + 'duo.js',
|
||||
'!' + paths.jsDir + 'settings.js'
|
||||
], { base: '.' })
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(concat(paths.concatJsDest))
|
||||
.pipe(uglify())
|
||||
//.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
@@ -335,6 +337,10 @@ gulp.task('dist:move', function () {
|
||||
src: paths.jsDir + 'duo.js',
|
||||
dest: paths.dist + 'js'
|
||||
},
|
||||
{
|
||||
src: paths.jsDir + 'duo-connector.js',
|
||||
dest: paths.dist + 'js'
|
||||
},
|
||||
{
|
||||
src: paths.jsDir + 'settings.js',
|
||||
dest: paths.dist + 'js'
|
||||
@@ -351,6 +357,7 @@ gulp.task('dist:move', function () {
|
||||
paths.webroot + 'u2f-connector.html',
|
||||
paths.webroot + 'duo-connector.html',
|
||||
paths.webroot + 'favicon.ico',
|
||||
paths.webroot + 'manifest.json',
|
||||
paths.webroot + 'app-id.json'
|
||||
],
|
||||
dest: paths.dist
|
||||
@@ -389,7 +396,7 @@ gulp.task('dist:js:app', function () {
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||
.pipe(ngAnnotate())
|
||||
.pipe(uglify())
|
||||
//.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
@@ -401,7 +408,7 @@ gulp.task('dist:js:fallback', function () {
|
||||
|
||||
merge(mainStream)
|
||||
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||
.pipe(uglify())
|
||||
//.pipe(uglify())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(gulp.dest(paths.dist + 'js'));
|
||||
});
|
||||
@@ -414,7 +421,7 @@ gulp.task('dist:js:u2f', function () {
|
||||
|
||||
merge(mainStream)
|
||||
.pipe(concat(paths.dist + '/js/u2f.min.js'))
|
||||
.pipe(uglify())
|
||||
//.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
@@ -429,7 +436,7 @@ gulp.task('dist:js:lib', function () {
|
||||
'!' + paths.libDir + 'jquery/**/*'
|
||||
])
|
||||
.pipe(concat(paths.dist + '/js/lib.min.js'))
|
||||
.pipe(uglify())
|
||||
//.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
@@ -442,10 +449,16 @@ gulp.task('dist:preprocess', function () {
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('dist:version', function () {
|
||||
gulp.src(paths.webroot + 'version.json').pipe(jeditor({
|
||||
'version': project.version
|
||||
})).pipe(gulp.dest(paths.dist));
|
||||
});
|
||||
|
||||
gulp.task('dist', ['build'], function (cb) {
|
||||
return runSequence(
|
||||
'dist:clean',
|
||||
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f'],
|
||||
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f', 'dist:version'],
|
||||
'dist:preprocess',
|
||||
cb);
|
||||
});
|
||||
@@ -465,7 +478,7 @@ gulp.task('deploy-preview', ['dist'], function () {
|
||||
return gulp.src(paths.dist + '**/*')
|
||||
.pipe(ghPages({
|
||||
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",
|
||||
"version": "1.20.1",
|
||||
"version": "1.27.0",
|
||||
"env": "Production",
|
||||
"devDependencies": {
|
||||
"connect": "3.6.3",
|
||||
"connect": "3.6.5",
|
||||
"lodash": "4.17.4",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-concat": "2.6.1",
|
||||
@@ -11,41 +11,42 @@
|
||||
"gulp-less": "3.3.2",
|
||||
"gulp-rename": "1.2.2",
|
||||
"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-ng-annotate": "2.0.0",
|
||||
"gulp-ng-config": "1.4.0",
|
||||
"gulp-ng-config": "1.5.0",
|
||||
"gulp-connect": "5.0.0",
|
||||
"gulp-json-editor": "2.2.2",
|
||||
"jshint": "2.9.5",
|
||||
"gulp-jshint": "2.0.4",
|
||||
"rimraf": "2.6.1",
|
||||
"run-sequence": "2.1.0",
|
||||
"rimraf": "2.6.2",
|
||||
"run-sequence": "2.2.0",
|
||||
"merge-stream": "1.0.1",
|
||||
"jquery": "2.2.4",
|
||||
"jquery": "3.2.1",
|
||||
"font-awesome": "4.7.0",
|
||||
"bootstrap": "3.3.7",
|
||||
"angular": "1.6.6",
|
||||
"angular-resource": "1.6.6",
|
||||
"angular-sanitize": "1.6.6",
|
||||
"angular-ui-bootstrap": "2.5.0",
|
||||
"angular": "1.6.7",
|
||||
"angular-resource": "1.6.7",
|
||||
"angular-sanitize": "1.6.7",
|
||||
"angular-ui-bootstrap": "2.5.6",
|
||||
"angular-ui-router": "0.4.2",
|
||||
"angular-jwt": "0.1.9",
|
||||
"angular-cookies": "1.6.6",
|
||||
"angular-cookies": "1.6.7",
|
||||
"admin-lte": "2.3.11",
|
||||
"angular-toastr": "2.1.1",
|
||||
"angular-bootstrap-show-errors": "2.3.0",
|
||||
"angular-messages": "1.6.6",
|
||||
"angular-messages": "1.6.7",
|
||||
"ngstorage": "0.3.11",
|
||||
"papaparse": "4.3.5",
|
||||
"papaparse": "4.3.6",
|
||||
"clipboard": "1.7.1",
|
||||
"ngclipboard": "1.1.1",
|
||||
"angulartics": "1.4.0",
|
||||
"ngclipboard": "1.1.2",
|
||||
"angulartics": "1.5.0",
|
||||
"angulartics-google-analytics": "0.4.0",
|
||||
"node-forge": "0.7.1",
|
||||
"webpack-stream": "4.0.0",
|
||||
"angular-stripe": "5.0.0",
|
||||
"angular-credit-cards": "3.1.6",
|
||||
"browserify": "14.4.0",
|
||||
"browserify": "14.5.0",
|
||||
"vinyl-source-stream": "1.1.0",
|
||||
"gulp-derequire": "2.1.0",
|
||||
"exposify": "0.5.0",
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"apiUri": "https://preview-api.bitwarden.com",
|
||||
"identityUri": "https://preview-identity.bitwarden.com",
|
||||
"apiUri": "/api",
|
||||
"identityUri": "/identity",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
"preview-api.bitwarden.com"
|
||||
]
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"apiUri": "https://api.bitwarden.com",
|
||||
"identityUri": "https://identity.bitwarden.com",
|
||||
"apiUri": "/api",
|
||||
"identityUri": "/identity",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||
"whitelistDomains": [
|
||||
"api.bitwarden.com"
|
||||
]
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
"identityUri": "http://localhost:33656",
|
||||
"iconsUri": "https://icons.bitwarden.com",
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"whitelistDomains": [
|
||||
"localhost"
|
||||
]
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,8 +216,9 @@ angular
|
||||
function init() {
|
||||
stopU2fCheck = true;
|
||||
var params;
|
||||
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
|
||||
params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
|
||||
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo ||
|
||||
$scope.twoFactorProvider === constants.twoFactorProvider.organizationDuo) {
|
||||
params = $scope.twoFactorProviders[$scope.twoFactorProvider];
|
||||
|
||||
$window.Duo.init({
|
||||
host: params.Host,
|
||||
|
||||
@@ -29,6 +29,13 @@ angular
|
||||
};
|
||||
$scope.readOnlyEmail = stateParams.email !== null;
|
||||
|
||||
var registerOrgUserId = null;
|
||||
var registerToken = null;
|
||||
if(stateParams.returnState && stateParams.returnState.params &&
|
||||
stateParams.returnState.name === 'frontend.organizationAccept') {
|
||||
registerOrgUserId = stateParams.returnState.params.organizationUserId || null;
|
||||
registerToken = stateParams.returnState.params.token || null;
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
if ($scope.model.email) {
|
||||
@@ -73,7 +80,9 @@ angular
|
||||
keys: {
|
||||
publicKey: result.publicKey,
|
||||
encryptedPrivateKey: result.privateKeyEnc
|
||||
}
|
||||
},
|
||||
token: registerToken,
|
||||
organizationUserId: registerOrgUserId
|
||||
};
|
||||
|
||||
return apiService.accounts.register(request).$promise;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
$scope.providers = [];
|
||||
|
||||
if (providers.hasOwnProperty(constants.twoFactorProvider.organizationDuo)) {
|
||||
add(constants.twoFactorProvider.organizationDuo);
|
||||
}
|
||||
if (providers.hasOwnProperty(constants.twoFactorProvider.authenticator)) {
|
||||
add(constants.twoFactorProvider.authenticator);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<p class="login-box-msg">
|
||||
Complete logging in with YubiKey.
|
||||
</p>
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
autocomplete="off">
|
||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
@@ -63,7 +63,8 @@
|
||||
</p>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="code" class="sr-only">Token</label>
|
||||
<input type="password" id="code" name="Token" class="form-control" ng-model="token" required api-field />
|
||||
<input type="password" id="code" name="Token" class="form-control" ng-model="token"
|
||||
autocomplete="new-password" required api-field />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
@@ -82,11 +83,12 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo">
|
||||
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo ||
|
||||
twoFactorProvider === twoFactorProviderConstants.organizationDuo">
|
||||
<p class="login-box-msg">
|
||||
Complete logging in with Duo.
|
||||
</p>
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||
autocomplete="off">
|
||||
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<p class="text-center"><strong>{{state.params.email}}</strong></p>
|
||||
<p>
|
||||
You've been invited to join the organization listed above.
|
||||
To accept the invitation, you need to log in or create a new bitwarden account.
|
||||
To accept the invitation, you need to log in or create a new Bitwarden account.
|
||||
</p>
|
||||
<hr />
|
||||
<div class="row">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||
</div>
|
||||
<div class="login-box-body">
|
||||
<p class="login-box-msg">Enter your email address below to recover & 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 class="callout callout-success">
|
||||
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.
|
||||
</div>
|
||||
<p>
|
||||
You have requested to delete your bitwarden account (<b>{{email}}</b>).
|
||||
You have requested to delete your Bitwarden account (<b>{{email}}</b>).
|
||||
Click the button below to confirm and proceed.
|
||||
</p>
|
||||
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
angular
|
||||
.module('bit')
|
||||
|
||||
.factory('apiInterceptor', function ($injector, $q, toastr) {
|
||||
.factory('apiInterceptor', function ($injector, $q, toastr, appSettings, utilsService) {
|
||||
return {
|
||||
request: function (config) {
|
||||
if (config.url.indexOf(appSettings.apiUri + '/') === 0) {
|
||||
config.headers['Device-Type'] = utilsService.getDeviceType();
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
response: function (response) {
|
||||
|
||||
@@ -14,27 +14,13 @@ angular
|
||||
$qProvider.errorOnUnhandledRejections(false);
|
||||
$locationProvider.hashPrefix('');
|
||||
|
||||
var jwtConfig = {
|
||||
whiteListedDomains: appSettings.whitelistDomains
|
||||
};
|
||||
jwtOptionsProvider.config({
|
||||
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;
|
||||
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
|
||||
if (options.url.indexOf(appSettings.apiUri) !== 0) {
|
||||
if (options.url.indexOf(appSettings.apiUri + '/') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,12 +65,6 @@ angular
|
||||
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
|
||||
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
||||
if (!$httpProvider.defaults.headers.get) {
|
||||
@@ -124,12 +104,6 @@ angular
|
||||
refreshFromServer: false
|
||||
}
|
||||
})
|
||||
.state('backend.user.shared', {
|
||||
url: '^/shared',
|
||||
templateUrl: 'app/vault/views/vaultShared.html',
|
||||
controller: 'vaultSharedController',
|
||||
data: { pageTitle: 'Shared' }
|
||||
})
|
||||
.state('backend.user.settings', {
|
||||
url: '^/settings',
|
||||
templateUrl: 'app/settings/views/settings.html',
|
||||
@@ -178,12 +152,6 @@ angular
|
||||
controller: 'reportsBreachController',
|
||||
data: { pageTitle: 'Data Breach Report' }
|
||||
})
|
||||
.state('backend.user.apps', {
|
||||
url: '^/apps',
|
||||
templateUrl: 'app/views/apps.html',
|
||||
controller: 'appsController',
|
||||
data: { pageTitle: 'Get the Apps' }
|
||||
})
|
||||
.state('backend.org', {
|
||||
templateUrl: 'app/views/organizationLayout.html',
|
||||
abstract: true
|
||||
@@ -195,13 +163,13 @@ angular
|
||||
data: { pageTitle: 'Organization Dashboard' }
|
||||
})
|
||||
.state('backend.org.people', {
|
||||
url: '/organization/:orgId/people',
|
||||
url: '/organization/:orgId/people?viewEvents&search',
|
||||
templateUrl: 'app/organization/views/organizationPeople.html',
|
||||
controller: 'organizationPeopleController',
|
||||
data: { pageTitle: 'Organization People' }
|
||||
})
|
||||
.state('backend.org.collections', {
|
||||
url: '/organization/:orgId/collections',
|
||||
url: '/organization/:orgId/collections?search',
|
||||
templateUrl: 'app/organization/views/organizationCollections.html',
|
||||
controller: 'organizationCollectionsController',
|
||||
data: { pageTitle: 'Organization Collections' }
|
||||
@@ -219,17 +187,26 @@ angular
|
||||
data: { pageTitle: 'Organization Billing' }
|
||||
})
|
||||
.state('backend.org.vault', {
|
||||
url: '/organization/:orgId/vault',
|
||||
url: '/organization/:orgId/vault?viewEvents&search',
|
||||
templateUrl: 'app/organization/views/organizationVault.html',
|
||||
controller: 'organizationVaultController',
|
||||
data: { pageTitle: 'Organization Vault' }
|
||||
data: {
|
||||
pageTitle: 'Organization Vault',
|
||||
controlSidebar: true
|
||||
}
|
||||
})
|
||||
.state('backend.org.groups', {
|
||||
url: '/organization/:orgId/groups',
|
||||
url: '/organization/:orgId/groups?search',
|
||||
templateUrl: 'app/organization/views/organizationGroups.html',
|
||||
controller: 'organizationGroupsController',
|
||||
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
|
||||
.state('frontend', {
|
||||
@@ -375,7 +352,7 @@ angular
|
||||
// user is guaranteed to be authenticated becuase of previous check
|
||||
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
|
||||
// clear vault rootScope when visiting org admin section
|
||||
$rootScope.vaultCiphers = $rootScope.vaultFolders = null;
|
||||
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
var orgs = profile.organizations;
|
||||
|
||||
@@ -26,7 +26,8 @@ angular.module('bit')
|
||||
duo: 2,
|
||||
authenticator: 0,
|
||||
email: 1,
|
||||
remember: 5
|
||||
remember: 5,
|
||||
organizationDuo: 6
|
||||
},
|
||||
cipherType: {
|
||||
login: 1,
|
||||
@@ -39,19 +40,74 @@ angular.module('bit')
|
||||
hidden: 1,
|
||||
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: [
|
||||
{
|
||||
type: 0,
|
||||
name: 'Authenticator App',
|
||||
description: 'Use an authenticator app (such as Authy or Google Authenticator) to generate time-based ' +
|
||||
'verification codes.',
|
||||
'verification codes.',
|
||||
enabled: false,
|
||||
active: true,
|
||||
free: true,
|
||||
image: 'authapp.png',
|
||||
displayOrder: 0,
|
||||
priority: 1,
|
||||
requiresUsb: false
|
||||
requiresUsb: false,
|
||||
organization: false
|
||||
},
|
||||
{
|
||||
type: 3,
|
||||
@@ -62,7 +118,8 @@ angular.module('bit')
|
||||
image: 'yubico.png',
|
||||
displayOrder: 1,
|
||||
priority: 3,
|
||||
requiresUsb: true
|
||||
requiresUsb: true,
|
||||
organization: false
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
@@ -73,7 +130,8 @@ angular.module('bit')
|
||||
image: 'duo.png',
|
||||
displayOrder: 2,
|
||||
priority: 2,
|
||||
requiresUsb: false
|
||||
requiresUsb: false,
|
||||
organization: false
|
||||
},
|
||||
{
|
||||
type: 4,
|
||||
@@ -84,7 +142,8 @@ angular.module('bit')
|
||||
image: 'fido.png',
|
||||
displayOrder: 3,
|
||||
priority: 4,
|
||||
requiresUsb: true
|
||||
requiresUsb: true,
|
||||
organization: false
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
@@ -96,7 +155,21 @@ angular.module('bit')
|
||||
image: 'gmail.png',
|
||||
displayOrder: 4,
|
||||
priority: 0,
|
||||
requiresUsb: false
|
||||
requiresUsb: false,
|
||||
organization: false
|
||||
},
|
||||
{
|
||||
type: 6,
|
||||
name: 'Duo (Organization)',
|
||||
description: 'Verify with Duo Security for your organization using the Duo Mobile app, SMS, ' +
|
||||
'phone call, or U2F security key.',
|
||||
enabled: false,
|
||||
active: true,
|
||||
image: 'duo.png',
|
||||
displayOrder: 1,
|
||||
priority: 10,
|
||||
requiresUsb: false,
|
||||
organization: true
|
||||
}
|
||||
],
|
||||
plans: {
|
||||
|
||||
@@ -6,7 +6,7 @@ angular
|
||||
link: function (scope, element) {
|
||||
var listener = function (event, toState, toParams, fromState, fromParams) {
|
||||
// Default title
|
||||
var title = 'bitwarden Web Vault';
|
||||
var title = 'Bitwarden Web Vault';
|
||||
if (toState.data && toState.data.pageTitle) {
|
||||
title = toState.data.pageTitle + ' - ' + title;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
$scope.searchVault = function () {
|
||||
$state.go('backend.user.vault');
|
||||
};
|
||||
|
||||
$scope.searchOrganizationVault = function () {
|
||||
$state.go('backend.org.vault', { orgId: $state.params.orgId });
|
||||
};
|
||||
|
||||
$scope.isOrgOwner = function (org) {
|
||||
return org && org.type === constants.orgUserType.owner;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationCollectionsController', function ($scope, $state, apiService, $uibModal, cipherService, $filter,
|
||||
toastr, $analytics) {
|
||||
toastr, $analytics, $uibModalStack) {
|
||||
$scope.collections = [];
|
||||
$scope.loading = true;
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
@@ -96,6 +96,12 @@
|
||||
apiService.collections.listOrganization({ orgId: $state.params.orgId }, function (list) {
|
||||
$scope.collections = cipherService.decryptCollections(list.Data, $state.params.orgId, true);
|
||||
$scope.loading = false;
|
||||
|
||||
if ($state.params.search) {
|
||||
$uibModalStack.dismissAll();
|
||||
$scope.filterSearch = $state.params.search;
|
||||
$('#filterSearch').focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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')
|
||||
|
||||
.controller('organizationGroupsController', function ($scope, $state, apiService, $uibModal, $filter,
|
||||
toastr, $analytics) {
|
||||
toastr, $analytics, $uibModalStack) {
|
||||
$scope.groups = [];
|
||||
$scope.loading = true;
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
@@ -88,6 +88,12 @@
|
||||
}
|
||||
$scope.groups = groups;
|
||||
$scope.loading = false;
|
||||
|
||||
if ($state.params.search) {
|
||||
$uibModalStack.dismissAll();
|
||||
$scope.filterSearch = $state.params.search;
|
||||
$('#filterSearch').focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationPeopleController', function ($scope, $state, $uibModal, cryptoService, apiService, authService,
|
||||
toastr, $analytics) {
|
||||
toastr, $analytics, $filter, $uibModalStack) {
|
||||
$scope.users = [];
|
||||
$scope.useGroups = false;
|
||||
$scope.useEvents = false;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
loadList();
|
||||
@@ -13,6 +14,7 @@
|
||||
if (profile.organizations) {
|
||||
var org = profile.organizations[$state.params.orgId];
|
||||
$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() {
|
||||
apiService.organizationUsers.list({ orgId: $state.params.orgId }, function (list) {
|
||||
var users = [];
|
||||
@@ -129,6 +143,20 @@
|
||||
}
|
||||
|
||||
$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');
|
||||
};
|
||||
});
|
||||
@@ -2,11 +2,14 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationSettingsController', function ($scope, $state, apiService, toastr, authService, $uibModal,
|
||||
$analytics, appSettings) {
|
||||
$analytics, appSettings, constants, $filter) {
|
||||
$scope.selfHosted = appSettings.selfHosted;
|
||||
$scope.model = {};
|
||||
$scope.twoStepProviders = $filter('filter')(constants.twoFactorProviderInfo, { organization: true });
|
||||
$scope.use2fa = false;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
apiService.organizations.get({ id: $state.params.orgId }, function (org) {
|
||||
apiService.organizations.get({ id: $state.params.orgId }).$promise.then(function (org) {
|
||||
$scope.model = {
|
||||
name: org.Name,
|
||||
billingEmail: org.BillingEmail,
|
||||
@@ -17,6 +20,29 @@
|
||||
businessCountry: org.BusinessCountry,
|
||||
businessTaxNumber: org.BusinessTaxNumber
|
||||
};
|
||||
|
||||
$scope.use2fa = org.Use2fa;
|
||||
if (org.Use2fa) {
|
||||
return apiService.twoFactor.listOrganization({ orgId: $state.params.orgId }).$promise;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}).then(function (response) {
|
||||
if (!response || !response.Data) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < response.Data.length; i++) {
|
||||
if (!response.Data[i].Enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var provider = $filter('filter')($scope.twoStepProviders, { type: response.Data[i].Type });
|
||||
if (provider.length) {
|
||||
provider[0].enabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,4 +82,30 @@
|
||||
controller: 'organizationDeleteController'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.edit = function (provider) {
|
||||
if (provider.type === constants.twoFactorProvider.organizationDuo) {
|
||||
typeName = 'Duo';
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
|
||||
controller: 'settingsTwoStep' + typeName + 'Controller',
|
||||
resolve: {
|
||||
enabled: function () { return provider.enabled; },
|
||||
orgId: function () { return $state.params.orgId; }
|
||||
}
|
||||
});
|
||||
|
||||
modal.result.then(function (enabled) {
|
||||
if (enabled || enabled === false) {
|
||||
// do not adjust when undefined or null
|
||||
provider.enabled = enabled;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var i;
|
||||
var collectionsDict = {};
|
||||
for (var i = 0; i < decCollections.length; i++) {
|
||||
for (i = 0; i < decCollections.length; i++) {
|
||||
collectionsDict[decCollections[i].id] = decCollections[i];
|
||||
}
|
||||
|
||||
@@ -78,10 +79,17 @@
|
||||
switch (decCiphers[i].type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = decCiphers[i].login.uri;
|
||||
cipher.login_uri = null;
|
||||
cipher.login_username = decCiphers[i].login.username;
|
||||
cipher.login_password = decCiphers[i].login.password;
|
||||
cipher.login_totp = decCiphers[i].login.totp;
|
||||
|
||||
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
|
||||
cipher.login_uri = [];
|
||||
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
|
||||
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.type = 'note';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
$scope.options = [
|
||||
{
|
||||
id: 'bitwardencsv',
|
||||
name: 'bitwarden (csv)',
|
||||
name: 'Bitwarden (csv)',
|
||||
featured: true,
|
||||
sort: 1,
|
||||
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
||||
@@ -64,7 +64,7 @@
|
||||
var halfway = Math.floor(ciphers.length / 2);
|
||||
var last = ciphers.length - 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
.module('bit.organization')
|
||||
|
||||
.controller('organizationVaultAddCipherController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants) {
|
||||
cipherService, passwordService, $analytics, authService, orgId, $uibModal, constants, selectedType) {
|
||||
$analytics.eventTrack('organizationVaultAddCipherController', { category: 'Modal' });
|
||||
$scope.constants = constants;
|
||||
$scope.selectedType = constants.cipherType.login.toString();
|
||||
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
|
||||
$scope.cipher = {
|
||||
type: constants.cipherType.login,
|
||||
login: {},
|
||||
type: selectedType || constants.cipherType.login,
|
||||
login: {
|
||||
uris: [{
|
||||
uri: null,
|
||||
match: null,
|
||||
matchValue: null
|
||||
}]
|
||||
},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
@@ -40,7 +46,43 @@
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$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 blob = new Blob([encValue.data], { type: 'application/octet-stream' });
|
||||
fd.append('data', blob, encValue.fileName);
|
||||
return apiService.ciphers.postAttachment({ id: cipherId }, fd).$promise;
|
||||
return apiService.ciphers.postAttachmentAdmin({ id: cipherId }, fd).$promise;
|
||||
}).then(function (response) {
|
||||
$analytics.eventTrack('Added Attachment');
|
||||
toastr.success('The attachment has been added.');
|
||||
closing = true;
|
||||
$uibModalInstance.close(true);
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
validationService.addError(form, 'file', err, true);
|
||||
}
|
||||
else {
|
||||
validationService.addError(form, 'file', 'Something went wrong.', true);
|
||||
}
|
||||
}, function (e) {
|
||||
var errors = validationService.parseErrors(e);
|
||||
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -65,7 +61,7 @@
|
||||
}
|
||||
|
||||
attachment.loading = true;
|
||||
apiService.ciphers.delAttachment({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
apiService.ciphers.delAttachmentAdmin({ id: cipherId, attachmentId: attachment.id }).$promise.then(function () {
|
||||
attachment.loading = false;
|
||||
$analytics.eventTrack('Deleted Organization Attachment');
|
||||
var index = $scope.cipher.attachments.indexOf(attachment);
|
||||
|
||||
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')
|
||||
|
||||
.controller('organizationVaultController', function ($scope, apiService, cipherService, $analytics, $q, $state,
|
||||
$localStorage, $uibModal, $filter, authService) {
|
||||
$localStorage, $uibModal, $filter, authService, $uibModalStack, constants, $timeout) {
|
||||
$scope.ciphers = [];
|
||||
$scope.collections = [];
|
||||
$scope.loading = true;
|
||||
$scope.useEvents = false;
|
||||
$scope.constants = constants;
|
||||
$scope.filter = undefined;
|
||||
$scope.selectedType = undefined;
|
||||
$scope.selectedCollection = undefined;
|
||||
$scope.selectedAll = true;
|
||||
$scope.selectedTitle = 'All';
|
||||
$scope.selectedIcon = 'fa-th';
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
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 decCollections = [{
|
||||
id: null,
|
||||
name: 'Unassigned',
|
||||
collapsed: $localStorage.collapsedOrgCollections && 'unassigned' in $localStorage.collapsedOrgCollections
|
||||
name: 'Unassigned'
|
||||
}];
|
||||
|
||||
for (var i = 0; i < collections.Data.length; i++) {
|
||||
var decCollection = cipherService.decryptCollection(collections.Data[i], null, true);
|
||||
decCollection.collapsed = $localStorage.collapsedOrgCollections &&
|
||||
decCollection.id in $localStorage.collapsedOrgCollections;
|
||||
decCollections.push(decCollection);
|
||||
}
|
||||
|
||||
@@ -39,18 +51,26 @@
|
||||
|
||||
$q.all([collectionPromise, cipherPromise]).then(function () {
|
||||
$scope.loading = false;
|
||||
});
|
||||
});
|
||||
$timeout(function () {
|
||||
if ($('body').hasClass('control-sidebar-open')) {
|
||||
$("#search").focus();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
$scope.filterByCollection = function (collection) {
|
||||
return function (cipher) {
|
||||
if (!cipher.collectionIds || !cipher.collectionIds.length) {
|
||||
return collection.id === null;
|
||||
if ($state.params.search) {
|
||||
$uibModalStack.dismissAll();
|
||||
$scope.searchVaultText = $state.params.search;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!item.id) {
|
||||
@@ -60,21 +80,6 @@
|
||||
return item.name.toLowerCase();
|
||||
};
|
||||
|
||||
$scope.collapseExpand = function (collection) {
|
||||
if (!$localStorage.collapsedOrgCollections) {
|
||||
$localStorage.collapsedOrgCollections = {};
|
||||
}
|
||||
|
||||
var id = collection.id || 'unassigned';
|
||||
|
||||
if (id in $localStorage.collapsedOrgCollections) {
|
||||
delete $localStorage.collapsedOrgCollections[id];
|
||||
}
|
||||
else {
|
||||
$localStorage.collapsedOrgCollections[id] = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.editCipher = function (cipher) {
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
@@ -114,7 +119,8 @@
|
||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||
controller: 'organizationVaultAddCipherController',
|
||||
resolve: {
|
||||
orgId: function () { return $state.params.orgId; }
|
||||
orgId: function () { return $state.params.orgId; },
|
||||
selectedType: function () { return $scope.selectedType; }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
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) {
|
||||
if (!confirm('Are you sure you want to delete this item (' + cipher.name + ')?')) {
|
||||
return;
|
||||
@@ -207,4 +202,79 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.filterCollection = function (col) {
|
||||
resetSelected();
|
||||
$scope.selectedCollection = col;
|
||||
$scope.selectedIcon = 'fa-cube';
|
||||
if (col.id) {
|
||||
$scope.filter = function (c) {
|
||||
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
|
||||
};
|
||||
}
|
||||
else {
|
||||
$scope.filter = function (c) {
|
||||
return !c.collectionIds || c.collectionIds.length === 0;
|
||||
};
|
||||
}
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterType = function (t) {
|
||||
resetSelected();
|
||||
$scope.selectedType = t;
|
||||
switch (t) {
|
||||
case constants.cipherType.login:
|
||||
$scope.selectedTitle = 'Login';
|
||||
$scope.selectedIcon = 'fa-globe';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
$scope.selectedTitle = 'Card';
|
||||
$scope.selectedIcon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
$scope.selectedTitle = 'Identity';
|
||||
$scope.selectedIcon = 'fa-id-card-o';
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
$scope.selectedTitle = 'Secure Note';
|
||||
$scope.selectedIcon = 'fa-sticky-note-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$scope.filter = function (c) {
|
||||
return c.type === t;
|
||||
};
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterAll = function () {
|
||||
resetSelected();
|
||||
$scope.selectedAll = true;
|
||||
$scope.selectedTitle = 'All';
|
||||
$scope.selectedIcon = 'fa-th';
|
||||
$scope.filter = null;
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
function resetSelected() {
|
||||
$scope.selectedCollection = undefined;
|
||||
$scope.selectedType = undefined;
|
||||
$scope.selectedAll = false;
|
||||
}
|
||||
|
||||
function fixLayout() {
|
||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||
$timeout(function () {
|
||||
$.AdminLTE.layout.fix();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.cipherFilter = function () {
|
||||
return function (cipher) {
|
||||
return !$scope.filter || $scope.filter(cipher);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
apiService.ciphers.getAdmin({ id: cipherId }, function (cipher) {
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.useTotp = $scope.cipher.organizationUseTotp;
|
||||
setUriMatchValues();
|
||||
});
|
||||
|
||||
$scope.save = function (model) {
|
||||
@@ -28,7 +29,43 @@
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$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,7 +116,7 @@
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-show="!loading">
|
||||
You plan currently has a total of <b>{{plan.seats}}</b> seats.
|
||||
Your plan currently has a total of <b>{{plan.seats}}</b> seats.
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer" ng-if="!selfHosted && !noSubscription && canAdjustSeats">
|
||||
@@ -134,7 +134,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
You plan has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||
Your plan has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||
You are currently using {{storage.currentName}}.
|
||||
</p>
|
||||
<div class="progress" style="margin: 0;">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<div class="box-filters hidden-xs">
|
||||
<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">
|
||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<h3 class="box-title">Let's Get Started!</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>Dashboard features are coming soon. Get started by inviting users and creating your collections.</p>
|
||||
<p>Get started by inviting users and creating your collections.</p>
|
||||
<a class="btn btn-default btn-flat" ui-sref="backend.org.people({orgId: orgProfile.id})">
|
||||
Invite Users
|
||||
</a>
|
||||
|
||||
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="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">
|
||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<div class="box-filters hidden-xs">
|
||||
<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">
|
||||
<span class="fa fa-search form-control-feedback text-muted" aria-hidden="true"></span>
|
||||
</div>
|
||||
@@ -46,6 +46,12 @@
|
||||
<i class="fa fa-fw fa-sitemap"></i> Groups
|
||||
</a>
|
||||
</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">
|
||||
<a href="#" stop-click ng-click="confirm(user)">
|
||||
<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">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Invite a new user to your organization by entering their bitwarden account email address below. If they do not have
|
||||
a bitwarden account already, they will be prompted to create a new account.
|
||||
Invite a new user to your organization by entering their Bitwarden account email address below. If they do not have
|
||||
a Bitwarden account already, they will be prompted to create a new account.
|
||||
</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="inviteForm.$errors">
|
||||
<h4>Errors have occurred</h4>
|
||||
|
||||
@@ -63,6 +63,36 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box box-default" ng-if="use2fa">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Two-step Login Providers</h3>
|
||||
</div>
|
||||
<div class="box-body no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="provider in twoStepProviders | orderBy: 'displayOrder'">
|
||||
<td style="width: 120px; height: 75px;" align="center">
|
||||
<a href="#" stop-click ng-click="edit(provider)">
|
||||
<img alt="{{::provider.name}}" ng-src="{{'images/two-factor/' + provider.image}}" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" stop-click ng-click="edit(provider)">{{::provider.name}}</a>
|
||||
<div class="text-muted text-sm">{{::provider.description}}</div>
|
||||
</td>
|
||||
<td style="width: 100px;" class="text-right">
|
||||
<span class="label label-full"
|
||||
ng-class="{ 'label-success': provider.enabled, 'label-default': !provider.enabled }">
|
||||
{{provider.enabled ? 'Enabled' : 'Disabled'}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Import/Export</h3>
|
||||
|
||||
@@ -10,31 +10,22 @@
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<p ng-show="loading && !collections.length">Loading...</p>
|
||||
<div class="box" ng-class="{'collapsed-box': collection.collapsed}" ng-repeat="collection in collections |
|
||||
orderBy: collectionSort track by collection.id"
|
||||
ng-show="collections.length && (!main.searchVaultText || collectionCiphers.length)">
|
||||
<p ng-show="loading">Loading...</p>
|
||||
<div class="box" ng-show="!loading">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa" ng-class="{'fa-cubes': collection.id, 'fa-sitemap': !collection.id}"></i>
|
||||
{{collection.name}}
|
||||
<small ng-pluralize count="collectionCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
<i class="fa {{selectedIcon}}"></i>
|
||||
{{selectedCollection ? selectedCollection.name : selectedTitle}}
|
||||
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
||||
ng-click="collapseExpand(collection)">
|
||||
<i class="fa" ng-class="{'fa-minus': !collection.collapsed, 'fa-plus': collection.collapsed}"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': collectionCiphers.length}">
|
||||
<div ng-show="!collectionCiphers.length && collection.id">No items in this collection.</div>
|
||||
<div ng-show="!collectionCiphers.length && !collection.id">No unassigned items.</div>
|
||||
<div class="table-responsive" ng-show="collectionCiphers.length">
|
||||
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
|
||||
<div ng-show="!filteredCiphers.length">No items to list.</div>
|
||||
<div class="table-responsive" ng-show="filteredCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="cipher in collectionCiphers = (ciphers | filter: filterByCollection(collection) |
|
||||
filter: (main.searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
|
||||
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
|
||||
filter: (searchVaultText || '') | orderBy: ['name', 'subTitle']) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -57,9 +48,8 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="removeCipher(cipher, collection)" class="text-red"
|
||||
ng-if="collection.id">
|
||||
<i class="fa fa-fw fa-remove"></i> Remove
|
||||
<a href="#" stop-click ng-click="viewEvents(cipher)" ng-if="useEvents">
|
||||
<i class="fa fa-fw fa-file-text-o"></i> Event Logs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -88,3 +78,65 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<aside class="control-sidebar control-sidebar-light">
|
||||
<div class="tab-content">
|
||||
<form class="search-form">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="search" id="search" class="form-control" placeholder="Search org vault..."
|
||||
ng-model="searchVaultText" />
|
||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="control-sidebar-menu">
|
||||
<li ng-class="{active: selectedAll}">
|
||||
<a href="#" stop-click ng-click="filterAll()">
|
||||
<i class="fa fa-th fa-fw"></i> All Items
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="control-sidebar-heading">Types</h3>
|
||||
<div class="control-sidebar-section">
|
||||
<ul class="control-sidebar-menu">
|
||||
<li ng-class="{active: constants.cipherType.login === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
||||
<i class="fa fa-globe fa-fw"></i> Login
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: constants.cipherType.card === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
||||
<i class="fa fa-credit-card fa-fw"></i> Card
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: constants.cipherType.identity === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
||||
<i class="fa fa-id-card-o fa-fw"></i> Identity
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
||||
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="control-sidebar-heading">Collections</h3>
|
||||
<div ng-show="loading && !collections.length">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div ng-show="!loading && !collections.length">
|
||||
<p>No collections.</p>
|
||||
</div>
|
||||
<div class="control-sidebar-section" ng-show="!loading && collections.length">
|
||||
<ul class="control-sidebar-menu">
|
||||
<li ng-repeat="collection in collections | orderBy: [collectionSort] track by collection.id"
|
||||
ng-class="{active: selectedCollection && collection.id === selectedCollection.id}">
|
||||
<a href="#" stop-click ng-click="filterCollection(collection)">
|
||||
<i class="fa fa-caret-right fa-fw"></i>
|
||||
{{collection.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -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
|
||||
.module('bit.services')
|
||||
|
||||
.factory('apiService', function ($resource, tokenService, appSettings, $httpParamSerializer) {
|
||||
.factory('apiService', function ($resource, tokenService, appSettings, $httpParamSerializer, utilsService) {
|
||||
var _service = {},
|
||||
_apiUri = appSettings.apiUri,
|
||||
_identityUri = appSettings.identityUri;
|
||||
@@ -19,7 +19,6 @@
|
||||
getAdmin: { url: _apiUri + '/ciphers/:id/admin', method: 'GET', params: { id: '@id' } },
|
||||
getDetails: { url: _apiUri + '/ciphers/:id/details', method: 'GET', params: { id: '@id' } },
|
||||
list: { method: 'GET', params: {} },
|
||||
listDetails: { url: _apiUri + '/ciphers/details', method: 'GET', params: {} },
|
||||
listOrganizationDetails: { url: _apiUri + '/ciphers/organization-details', method: 'GET', params: {} },
|
||||
post: { method: 'POST', params: {} },
|
||||
postAdmin: { url: _apiUri + '/ciphers/admin', method: 'POST', params: {} },
|
||||
@@ -42,13 +41,20 @@
|
||||
headers: { 'Content-Type': undefined },
|
||||
params: { id: '@id' }
|
||||
},
|
||||
postAttachmentAdmin: {
|
||||
url: _apiUri + '/ciphers/:id/attachment-admin',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined },
|
||||
params: { id: '@id' }
|
||||
},
|
||||
postShareAttachment: {
|
||||
url: _apiUri + '/ciphers/:id/attachment/:attachmentId/share?organizationId=:orgId',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': undefined },
|
||||
params: { id: '@id', attachmentId: '@attachmentId', orgId: '@orgId' }
|
||||
},
|
||||
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
|
||||
delAttachment: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } },
|
||||
delAttachmentAdmin: { url: _apiUri + '/ciphers/:id/attachment/:attachmentId/delete-admin', method: 'POST', params: { id: '@id', attachmentId: '@attachmentId' } }
|
||||
});
|
||||
|
||||
_service.organizations = $resource(_apiUri + '/organizations/:id', {}, {
|
||||
@@ -153,9 +159,11 @@
|
||||
|
||||
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
|
||||
list: { method: 'GET', params: {} },
|
||||
listOrganization: { url: _apiUri + '/organizations/:orgId/two-factor', method: 'GET', params: { orgId: '@orgId' } },
|
||||
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
|
||||
getU2f: { url: _apiUri + '/two-factor/get-u2f', method: 'POST', params: {} },
|
||||
getDuo: { url: _apiUri + '/two-factor/get-duo', method: 'POST', params: {} },
|
||||
getOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/get-duo', method: 'POST', params: { orgId: '@orgId' } },
|
||||
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
|
||||
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
|
||||
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
|
||||
@@ -164,8 +172,10 @@
|
||||
putU2f: { url: _apiUri + '/two-factor/u2f', method: 'POST', params: {} },
|
||||
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
|
||||
putDuo: { url: _apiUri + '/two-factor/duo', method: 'POST', params: {} },
|
||||
putOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/duo', method: 'POST', params: { orgId: '@orgId' } },
|
||||
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
|
||||
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
|
||||
disableOrganization: { url: _apiUri + '/organizations/:orgId/two-factor/disable', method: 'POST', params: { orgId: '@orgId' } },
|
||||
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
|
||||
getRecover: { url: _apiUri + '/two-factor/get-recover', method: 'POST', params: {} }
|
||||
});
|
||||
@@ -179,11 +189,21 @@
|
||||
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', {}, {
|
||||
token: {
|
||||
url: _identityUri + '/connect/token',
|
||||
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,
|
||||
skipAuthorization: true,
|
||||
params: {}
|
||||
|
||||
@@ -95,7 +95,7 @@ angular
|
||||
_service.logOut = function () {
|
||||
tokenService.clearTokens();
|
||||
cryptoService.clearKeys();
|
||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
||||
$rootScope.vaultCiphers = $rootScope.vaultFolders = $rootScope.vaultCollections = null;
|
||||
_userProfile = null;
|
||||
};
|
||||
|
||||
@@ -150,6 +150,9 @@ angular
|
||||
maxStorageGb: profile.Organizations[i].MaxStorageGb,
|
||||
seats: profile.Organizations[i].Seats,
|
||||
useGroups: profile.Organizations[i].UseGroups,
|
||||
useDirectory: profile.Organizations[i].UseDirectory,
|
||||
useEvents: profile.Organizations[i].UseEvents,
|
||||
use2fa: profile.Organizations[i].Use2fa,
|
||||
useTotp: profile.Organizations[i].UseTotp
|
||||
};
|
||||
}
|
||||
@@ -183,6 +186,9 @@ angular
|
||||
maxStorageGb: org.MaxStorageGb,
|
||||
seats: org.Seats,
|
||||
useGroups: org.UseGroups,
|
||||
useDirectory: org.UseDirectory,
|
||||
useEvents: org.UseEvents,
|
||||
use2fa: org.Use2fa,
|
||||
useTotp: org.UseTotp
|
||||
};
|
||||
profile.organizations[o.id] = o;
|
||||
|
||||
@@ -30,6 +30,9 @@ angular
|
||||
organizationId: encryptedCipher.OrganizationId,
|
||||
collectionIds: encryptedCipher.CollectionIds || [],
|
||||
'type': encryptedCipher.Type,
|
||||
name: cryptoService.decrypt(encryptedCipher.Name, key),
|
||||
notes: _service.decryptProperty(encryptedCipher.Notes, key, true, false),
|
||||
fields: _service.decryptFields(key, encryptedCipher.Fields),
|
||||
folderId: encryptedCipher.FolderId,
|
||||
favorite: encryptedCipher.Favorite,
|
||||
edit: encryptedCipher.Edit,
|
||||
@@ -38,62 +41,68 @@ angular
|
||||
icon: null
|
||||
};
|
||||
|
||||
var cipherData = encryptedCipher.Data;
|
||||
if (cipherData) {
|
||||
cipher.name = cryptoService.decrypt(cipherData.Name, key);
|
||||
cipher.notes = _service.decryptProperty(cipherData.Notes, key, true, false);
|
||||
cipher.fields = _service.decryptFields(key, cipherData.Fields);
|
||||
|
||||
var dataObj = {};
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
dataObj.uri = _service.decryptProperty(cipherData.Uri, key, true, false);
|
||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
||||
dataObj.password = _service.decryptProperty(cipherData.Password, key, true, false);
|
||||
dataObj.totp = _service.decryptProperty(cipherData.Totp, key, true, false);
|
||||
cipher.login = dataObj;
|
||||
cipher.icon = 'fa-globe';
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
dataObj.type = cipherData.Type;
|
||||
cipher.secureNote = dataObj;
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
dataObj.cardholderName = _service.decryptProperty(cipherData.CardholderName, key, true, false);
|
||||
dataObj.number = _service.decryptProperty(cipherData.Number, key, true, false);
|
||||
dataObj.brand = _service.decryptProperty(cipherData.Brand, key, true, false);
|
||||
dataObj.expMonth = _service.decryptProperty(cipherData.ExpMonth, key, true, false);
|
||||
dataObj.expYear = _service.decryptProperty(cipherData.ExpYear, key, true, false);
|
||||
dataObj.code = _service.decryptProperty(cipherData.Code, key, true, false);
|
||||
cipher.card = dataObj;
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
dataObj.title = _service.decryptProperty(cipherData.Title, key, true, false);
|
||||
dataObj.firstName = _service.decryptProperty(cipherData.FirstName, key, true, false);
|
||||
dataObj.middleName = _service.decryptProperty(cipherData.MiddleName, key, true, false);
|
||||
dataObj.lastName = _service.decryptProperty(cipherData.LastName, key, true, false);
|
||||
dataObj.address1 = _service.decryptProperty(cipherData.Address1, key, true, false);
|
||||
dataObj.address2 = _service.decryptProperty(cipherData.Address2, key, true, false);
|
||||
dataObj.address3 = _service.decryptProperty(cipherData.Address3, key, true, false);
|
||||
dataObj.city = _service.decryptProperty(cipherData.City, key, true, false);
|
||||
dataObj.state = _service.decryptProperty(cipherData.State, key, true, false);
|
||||
dataObj.postalCode = _service.decryptProperty(cipherData.PostalCode, key, true, false);
|
||||
dataObj.country = _service.decryptProperty(cipherData.Country, key, true, false);
|
||||
dataObj.company = _service.decryptProperty(cipherData.Company, key, true, false);
|
||||
dataObj.email = _service.decryptProperty(cipherData.Email, key, true, false);
|
||||
dataObj.phone = _service.decryptProperty(cipherData.Phone, key, true, false);
|
||||
dataObj.ssn = _service.decryptProperty(cipherData.SSN, key, true, false);
|
||||
dataObj.username = _service.decryptProperty(cipherData.Username, key, true, false);
|
||||
dataObj.passportNumber = _service.decryptProperty(cipherData.PassportNumber, key, true, false);
|
||||
dataObj.licenseNumber = _service.decryptProperty(cipherData.LicenseNumber, key, true, false);
|
||||
cipher.identity = dataObj;
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
var i;
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.login = {
|
||||
username: _service.decryptProperty(encryptedCipher.Login.Username, key, true, false),
|
||||
password: _service.decryptProperty(encryptedCipher.Login.Password, key, true, false),
|
||||
totp: _service.decryptProperty(encryptedCipher.Login.Totp, key, true, false),
|
||||
uris: null
|
||||
};
|
||||
if (encryptedCipher.Login.Uris) {
|
||||
cipher.login.uris = [];
|
||||
for (i = 0; i < encryptedCipher.Login.Uris.length; i++) {
|
||||
cipher.login.uris.push({
|
||||
uri: _service.decryptProperty(encryptedCipher.Login.Uris[i].Uri, key, true, false),
|
||||
match: encryptedCipher.Login.Uris[i].Match
|
||||
});
|
||||
}
|
||||
}
|
||||
cipher.icon = 'fa-globe';
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.secureNote = {
|
||||
type: encryptedCipher.SecureNote.Type
|
||||
};
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
cipher.card = {
|
||||
cardholderName: _service.decryptProperty(encryptedCipher.Card.CardholderName, key, true, false),
|
||||
number: _service.decryptProperty(encryptedCipher.Card.Number, key, true, false),
|
||||
brand: _service.decryptProperty(encryptedCipher.Card.Brand, key, true, false),
|
||||
expMonth: _service.decryptProperty(encryptedCipher.Card.ExpMonth, key, true, false),
|
||||
expYear: _service.decryptProperty(encryptedCipher.Card.ExpYear, key, true, false),
|
||||
code: _service.decryptProperty(encryptedCipher.Card.Code, key, true, false)
|
||||
};
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
cipher.identity = {
|
||||
title: _service.decryptProperty(encryptedCipher.Identity.Title, key, true, false),
|
||||
firstName: _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, false),
|
||||
middleName: _service.decryptProperty(encryptedCipher.Identity.MiddleName, key, true, false),
|
||||
lastName: _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, false),
|
||||
address1: _service.decryptProperty(encryptedCipher.Identity.Address1, key, true, false),
|
||||
address2: _service.decryptProperty(encryptedCipher.Identity.Address2, key, true, false),
|
||||
address3: _service.decryptProperty(encryptedCipher.Identity.Address3, key, true, false),
|
||||
city: _service.decryptProperty(encryptedCipher.Identity.City, key, true, false),
|
||||
state: _service.decryptProperty(encryptedCipher.Identity.State, key, true, false),
|
||||
postalCode: _service.decryptProperty(encryptedCipher.Identity.PostalCode, key, true, false),
|
||||
country: _service.decryptProperty(encryptedCipher.Identity.Country, key, true, false),
|
||||
company: _service.decryptProperty(encryptedCipher.Identity.Company, key, true, false),
|
||||
email: _service.decryptProperty(encryptedCipher.Identity.Email, key, true, false),
|
||||
phone: _service.decryptProperty(encryptedCipher.Identity.Phone, key, true, false),
|
||||
ssn: _service.decryptProperty(encryptedCipher.Identity.SSN, key, true, false),
|
||||
username: _service.decryptProperty(encryptedCipher.Identity.Username, key, true, false),
|
||||
passportNumber: _service.decryptProperty(encryptedCipher.Identity.PassportNumber, key, true, false),
|
||||
licenseNumber: _service.decryptProperty(encryptedCipher.Identity.LicenseNumber, key, true, false)
|
||||
};
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!encryptedCipher.Attachments) {
|
||||
@@ -101,7 +110,7 @@ angular
|
||||
}
|
||||
|
||||
cipher.attachments = [];
|
||||
for (var i = 0; i < encryptedCipher.Attachments.length; i++) {
|
||||
for (i = 0; i < encryptedCipher.Attachments.length; i++) {
|
||||
cipher.attachments.push(_service.decryptAttachment(key, encryptedCipher.Attachments[i]));
|
||||
}
|
||||
|
||||
@@ -121,6 +130,7 @@ angular
|
||||
organizationId: encryptedCipher.OrganizationId,
|
||||
collectionIds: encryptedCipher.CollectionIds || [],
|
||||
'type': encryptedCipher.Type,
|
||||
name: _service.decryptProperty(encryptedCipher.Name, key, false, true),
|
||||
folderId: encryptedCipher.FolderId,
|
||||
favorite: encryptedCipher.Favorite,
|
||||
edit: encryptedCipher.Edit,
|
||||
@@ -130,59 +140,56 @@ angular
|
||||
icon: null
|
||||
};
|
||||
|
||||
var cipherData = encryptedCipher.Data;
|
||||
if (cipherData) {
|
||||
cipher.name = _service.decryptProperty(cipherData.Name, key, false, true);
|
||||
|
||||
var dataObj = {};
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.subTitle = _service.decryptProperty(cipherData.Username, key, true, true);
|
||||
cipher.meta.password = _service.decryptProperty(cipherData.Password, key, true, true);
|
||||
cipher.meta.uri = _service.decryptProperty(cipherData.Uri, key, true, true);
|
||||
setLoginIcon(cipher, cipher.meta.uri, true);
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.subTitle = null;
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
cipher.subTitle = '';
|
||||
cipher.meta.number = _service.decryptProperty(cipherData.Number, key, true, true);
|
||||
var brand = _service.decryptProperty(cipherData.Brand, key, true, true);
|
||||
if (brand) {
|
||||
cipher.subTitle = brand;
|
||||
}
|
||||
if (cipher.meta.number && cipher.meta.number.length >= 4) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ', ';
|
||||
}
|
||||
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
|
||||
}
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
var firstName = _service.decryptProperty(cipherData.FirstName, key, true, true);
|
||||
var lastName = _service.decryptProperty(cipherData.LastName, key, true, true);
|
||||
cipher.subTitle = '';
|
||||
if (firstName) {
|
||||
cipher.subTitle = firstName;
|
||||
}
|
||||
if (lastName) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ' ';
|
||||
}
|
||||
cipher.subTitle += lastName;
|
||||
}
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (cipher.subTitle === '') {
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.subTitle = _service.decryptProperty(encryptedCipher.Login.Username, key, true, true);
|
||||
cipher.meta.password = _service.decryptProperty(encryptedCipher.Login.Password, key, true, true);
|
||||
cipher.meta.uri = null;
|
||||
if (encryptedCipher.Login.Uris && encryptedCipher.Login.Uris.length) {
|
||||
cipher.meta.uri = _service.decryptProperty(encryptedCipher.Login.Uris[0].Uri, key, true, true);
|
||||
}
|
||||
setLoginIcon(cipher, cipher.meta.uri, true);
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.subTitle = null;
|
||||
}
|
||||
cipher.icon = 'fa-sticky-note-o';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
cipher.subTitle = '';
|
||||
cipher.meta.number = _service.decryptProperty(encryptedCipher.Card.Number, key, true, true);
|
||||
var brand = _service.decryptProperty(encryptedCipher.Card.Brand, key, true, true);
|
||||
if (brand) {
|
||||
cipher.subTitle = brand;
|
||||
}
|
||||
if (cipher.meta.number && cipher.meta.number.length >= 4) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ', ';
|
||||
}
|
||||
cipher.subTitle += ('*' + cipher.meta.number.substr(cipher.meta.number.length - 4));
|
||||
}
|
||||
cipher.icon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
var firstName = _service.decryptProperty(encryptedCipher.Identity.FirstName, key, true, true);
|
||||
var lastName = _service.decryptProperty(encryptedCipher.Identity.LastName, key, true, true);
|
||||
cipher.subTitle = '';
|
||||
if (firstName) {
|
||||
cipher.subTitle = firstName;
|
||||
}
|
||||
if (lastName) {
|
||||
if (cipher.subTitle !== '') {
|
||||
cipher.subTitle += ' ';
|
||||
}
|
||||
cipher.subTitle += lastName;
|
||||
}
|
||||
cipher.icon = 'fa-id-card-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (cipher.subTitle === '') {
|
||||
cipher.subTitle = null;
|
||||
}
|
||||
|
||||
return cipher;
|
||||
@@ -389,15 +396,24 @@ angular
|
||||
fields: _service.encryptFields(unencryptedCipher.fields, key)
|
||||
};
|
||||
|
||||
var i;
|
||||
switch (cipher.type) {
|
||||
case constants.cipherType.login:
|
||||
var loginData = unencryptedCipher.login;
|
||||
cipher.login = {
|
||||
uri: encryptProperty(loginData.uri, key),
|
||||
username: encryptProperty(loginData.username, key),
|
||||
password: encryptProperty(loginData.password, key),
|
||||
totp: encryptProperty(loginData.totp, key)
|
||||
};
|
||||
if (loginData.uris && loginData.uris.length) {
|
||||
cipher.login.uris = [];
|
||||
for (i = 0; i < loginData.uris.length; i++) {
|
||||
cipher.login.uris.push({
|
||||
uri: encryptProperty(loginData.uris[i].uri, key),
|
||||
match: loginData.uris[i].match
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.secureNote = {
|
||||
@@ -444,7 +460,7 @@ angular
|
||||
|
||||
if (unencryptedCipher.attachments && attachments) {
|
||||
cipher.attachments = {};
|
||||
for (var i = 0; i < unencryptedCipher.attachments.length; i++) {
|
||||
for (i = 0; i < unencryptedCipher.attachments.length; i++) {
|
||||
cipher.attachments[unencryptedCipher.attachments[i].id] =
|
||||
cryptoService.encrypt(unencryptedCipher.attachments[i].fileName, key);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ angular
|
||||
};
|
||||
|
||||
_service.makeKey = function (password, salt) {
|
||||
if (!$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) {
|
||||
if (_subtle != null && !$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) {
|
||||
return pbkdf2WC(password, salt, 5000, 256).then(function (keyBuf) {
|
||||
return new SymmetricCryptoKey(bufToB64(keyBuf), true);
|
||||
});
|
||||
@@ -305,7 +305,7 @@ angular
|
||||
throw 'Invalid parameters.';
|
||||
}
|
||||
|
||||
if (!$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) {
|
||||
if (_subtle != null && !$window.cryptoShimmed && $window.navigator.userAgent.indexOf('Edge') === -1) {
|
||||
var keyBuf = key.getBuffers();
|
||||
return pbkdf2WC(new Uint8Array(keyBuf.key), password, 1, 256).then(function (hashBuf) {
|
||||
return bufToB64(hashBuf);
|
||||
@@ -540,7 +540,7 @@ angular
|
||||
if (key.macKey && encPieces.length > 2) {
|
||||
var macBytes = forge.util.decode64(encPieces[2]);
|
||||
var computedMacBytes = computeMac(ivBytes + ctBytes, key.macKey, false);
|
||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
||||
if (!macsEqual(macBytes, computedMacBytes)) {
|
||||
console.error('MAC failed.');
|
||||
return null;
|
||||
}
|
||||
@@ -623,6 +623,10 @@ angular
|
||||
throw 'Encryption key unavailable.';
|
||||
}
|
||||
|
||||
if (key.macKey && !macBuf) {
|
||||
throw 'macBuf required for this type of key.';
|
||||
}
|
||||
|
||||
if (encType !== key.encType) {
|
||||
throw 'encType unavailable.';
|
||||
}
|
||||
@@ -646,7 +650,7 @@ angular
|
||||
if (computedMacBuf === null) {
|
||||
return null;
|
||||
}
|
||||
return macsEqualWC(keyBuf.macKey, macBuf, computedMacBuf);
|
||||
return macsEqualWC(macBuf, computedMacBuf);
|
||||
}).then(function (macsMatch) {
|
||||
if (macsMatch === false) {
|
||||
console.error('MAC failed.');
|
||||
@@ -704,7 +708,7 @@ angular
|
||||
if (key && key.macKey && encPieces.length > 1) {
|
||||
var macBytes = forge.util.decode64(encPieces[1]);
|
||||
var computedMacBytes = computeMac(ctBytes, key.macKey, false);
|
||||
if (!macsEqual(key.macKey, macBytes, computedMacBytes)) {
|
||||
if (!macsEqual(macBytes, computedMacBytes)) {
|
||||
console.error('MAC failed.');
|
||||
return null;
|
||||
}
|
||||
@@ -747,10 +751,11 @@ angular
|
||||
|
||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
function macsEqual(macKey, mac1, mac2) {
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
function macsEqual(mac1, mac2) {
|
||||
var hmac = forge.hmac.create();
|
||||
|
||||
hmac.start('sha256', macKey);
|
||||
hmac.start('sha256', getRandomBytes(32));
|
||||
hmac.update(mac1);
|
||||
mac1 = hmac.digest().getBytes();
|
||||
|
||||
@@ -761,11 +766,14 @@ angular
|
||||
return mac1 === mac2;
|
||||
}
|
||||
|
||||
function macsEqualWC(macKeyBuf, mac1Buf, mac2Buf) {
|
||||
function macsEqualWC(mac1Buf, mac2Buf) {
|
||||
var mac1,
|
||||
macKey;
|
||||
|
||||
return window.crypto.subtle.importKey('raw', macKeyBuf, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
||||
var compareKey = new Uint8Array(32);
|
||||
_crypto.getRandomValues(compareKey);
|
||||
|
||||
return window.crypto.subtle.importKey('raw', compareKey.buffer, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign'])
|
||||
.then(function (key) {
|
||||
macKey = key;
|
||||
return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, macKey, mac1Buf);
|
||||
@@ -932,5 +940,15 @@ angular
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
|
||||
function getRandomBytes(byteLength) {
|
||||
var bytes = new Uint32Array(byteLength / 4);
|
||||
_crypto.getRandomValues(bytes);
|
||||
var buffer = forge.util.createBuffer();
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
buffer.putInt32(bytes[i]);
|
||||
}
|
||||
return buffer.getBytes();
|
||||
}
|
||||
|
||||
return _service;
|
||||
});
|
||||
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;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
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;
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
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.20.1","environment":"Production"});
|
||||
.constant("appSettings", {"apiUri":"/api","identityUri":"/identity","iconsUri":"https://icons.bitwarden.com","stripeKey":"pk_live_bpN0P37nMxrMQkcaHXtAybJk","braintreeKey":"production_qfbsv8kc_njj2zjtyngtjmbjd","selfHosted":false,"version":"1.27.0","environment":"Production"});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
.controller('settingsTwoStepAuthenticatorController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
authService, $q, toastr, $analytics, constants, $timeout) {
|
||||
$analytics.eventTrack('settingsTwoStepAuthenticatorController', { category: 'Modal' });
|
||||
var _issuer = 'bitwarden',
|
||||
var _issuer = 'Bitwarden',
|
||||
_profile = null,
|
||||
_masterPasswordHash,
|
||||
_key = null;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.controller('settingsTwoStepController', function ($scope, apiService, toastr, $analytics, constants,
|
||||
$filter, $uibModal, authService) {
|
||||
$scope.providers = constants.twoFactorProviderInfo;
|
||||
$scope.providers = $filter('filter')(constants.twoFactorProviderInfo, { organization: false });
|
||||
$scope.premium = true;
|
||||
|
||||
authService.getUserProfile().then(function (profile) {
|
||||
@@ -60,7 +60,8 @@
|
||||
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
|
||||
controller: 'settingsTwoStep' + typeName + 'Controller',
|
||||
resolve: {
|
||||
enabled: function () { return provider.enabled; }
|
||||
enabled: function () { return provider.enabled; },
|
||||
orgId: function () { return null; }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsTwoStepDuoController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||
toastr, $analytics, constants, $timeout) {
|
||||
toastr, $analytics, constants, $timeout, orgId) {
|
||||
$analytics.eventTrack('settingsTwoStepDuoController', { category: 'Modal' });
|
||||
var _masterPasswordHash;
|
||||
|
||||
@@ -20,9 +20,16 @@
|
||||
$scope.auth = function (model) {
|
||||
$scope.authPromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
|
||||
_masterPasswordHash = hash;
|
||||
return apiService.twoFactor.getDuo({}, {
|
||||
var requestModel = {
|
||||
masterPasswordHash: _masterPasswordHash
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
if (orgId) {
|
||||
return apiService.twoFactor.getOrganizationDuo({ orgId: orgId }, requestModel).$promise;
|
||||
}
|
||||
else {
|
||||
return apiService.twoFactor.getDuo({}, requestModel).$promise;
|
||||
}
|
||||
}).then(function (apiResponse) {
|
||||
processResult(apiResponse);
|
||||
$scope.authed = true;
|
||||
@@ -43,27 +50,52 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.submitPromise = apiService.twoFactor.disable({}, {
|
||||
masterPasswordHash: _masterPasswordHash,
|
||||
type: constants.twoFactorProvider.duo
|
||||
}, function (response) {
|
||||
$analytics.eventTrack('Disabled Two-step Duo');
|
||||
toastr.success('Duo has been disabled.');
|
||||
$scope.enabled = response.Enabled;
|
||||
$scope.close();
|
||||
}).$promise;
|
||||
if (orgId) {
|
||||
$scope.submitPromise = apiService.twoFactor.disableOrganization({ orgId: orgId }, {
|
||||
masterPasswordHash: _masterPasswordHash,
|
||||
type: constants.twoFactorProvider.organizationDuo
|
||||
}, function (response) {
|
||||
$analytics.eventTrack('Disabled Two-step Organization Duo');
|
||||
toastr.success('Duo has been disabled.');
|
||||
$scope.enabled = response.Enabled;
|
||||
$scope.close();
|
||||
}).$promise;
|
||||
}
|
||||
else {
|
||||
$scope.submitPromise = apiService.twoFactor.disable({}, {
|
||||
masterPasswordHash: _masterPasswordHash,
|
||||
type: constants.twoFactorProvider.duo
|
||||
}, function (response) {
|
||||
$analytics.eventTrack('Disabled Two-step Duo');
|
||||
toastr.success('Duo has been disabled.');
|
||||
$scope.enabled = response.Enabled;
|
||||
$scope.close();
|
||||
}).$promise;
|
||||
}
|
||||
}
|
||||
|
||||
function update(model) {
|
||||
$scope.submitPromise = apiService.twoFactor.putDuo({}, {
|
||||
var requestModel = {
|
||||
integrationKey: model.ikey,
|
||||
secretKey: model.skey,
|
||||
host: model.host,
|
||||
masterPasswordHash: _masterPasswordHash
|
||||
}, function (response) {
|
||||
$analytics.eventTrack('Enabled Two-step Duo');
|
||||
processResult(response);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
if (orgId) {
|
||||
$scope.submitPromise = apiService.twoFactor.putOrganizationDuo({ orgId: orgId }, requestModel,
|
||||
function (response) {
|
||||
$analytics.eventTrack('Enabled Two-step Organization Duo');
|
||||
processResult(response);
|
||||
}).$promise;
|
||||
}
|
||||
else {
|
||||
$scope.submitPromise = apiService.twoFactor.putDuo({}, requestModel,
|
||||
function (response) {
|
||||
$analytics.eventTrack('Enabled Two-step Duo');
|
||||
processResult(response);
|
||||
}).$promise;
|
||||
}
|
||||
}
|
||||
|
||||
function processResult(response) {
|
||||
@@ -80,7 +112,7 @@
|
||||
closing = true;
|
||||
$uibModalInstance.close($scope.enabled);
|
||||
};
|
||||
|
||||
|
||||
$scope.$on('modal.closing', function (e, reason, closed) {
|
||||
if (closing) {
|
||||
return;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
$analytics.eventTrack('Print Recovery Code');
|
||||
var w = window.open();
|
||||
w.document.write('<div style="font-size: 18px; text-align: center;"><p>bitwarden two-step login recovery code:</p>' +
|
||||
w.document.write('<div style="font-size: 18px; text-align: center;"><p>Bitwarden two-step login recovery code:</p>' +
|
||||
'<code style="font-family: Menlo, Monaco, Consolas, \'Courier New\', monospace;">' + $scope.code + '</code>' +
|
||||
'</div><p style="text-align: center;">' + new Date() + '</p>');
|
||||
w.print();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}, function (e) {
|
||||
throw e ? e : 'Error occurred.';
|
||||
}).then(function () {
|
||||
toastr.success('Please log back in. If you are using other bitwarden applications, ' +
|
||||
toastr.success('Please log back in. If you are using other Bitwarden applications, ' +
|
||||
'log out and back in to those as well.', 'Key Updated', { timeOut: 10000 });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
You membership has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||
Your membership has a total of {{storage.maxGb}} GB of encrypted file storage.
|
||||
You are currently using {{storage.currentName}}.
|
||||
</p>
|
||||
<div class="progress" style="margin: 0;">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<section class="content">
|
||||
<p>
|
||||
If you have the same login across multiple different website domains, you can mark the website as "equivalent".
|
||||
"Global" domains are ones already created for you by bitwarden.
|
||||
"Global" domains are ones already created for you by Bitwarden.
|
||||
</p>
|
||||
<form name="customForm" ng-submit="customForm.$valid && saveCustom()" api-form="customPromise" autocomplete="off">
|
||||
<div class="box box-default">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
The recovery code allows you to access your account in the event that you can no longer use your normal
|
||||
two-step login provider (ex. you lose your device). bitwarden support will not be able to assist you if you lose
|
||||
two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose
|
||||
access to your account. We recommend you write down or print the recovery code and keep it in a safe place.
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<li ng-repeat="e in submitTwoStepForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>Enter the bitwarden application information from your Duo Admin panel:</p>
|
||||
<p>Enter the Bitwarden application information from your Duo Admin panel:</p>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="ikey">Integration Key</label>
|
||||
<input type="text" id="ikey" name="IntegrationKey" ng-model="updateModel.ikey" class="form-control"
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="form-group" show-errors>
|
||||
<label for="skey">Secret Key</label>
|
||||
<input type="password" id="skey" name="SecretKey" ng-model="updateModel.skey" class="form-control"
|
||||
required api-field />
|
||||
required api-field autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="host">API Hostname</label>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||
<p>
|
||||
Due to platform limitations, FIDO U2F cannot be used on all bitwarden applications. You should enable
|
||||
Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should enable
|
||||
another two-step login provider so that you can access your account when FIDO U2F cannot be used.
|
||||
</p>
|
||||
<p>Supported platforms:</p>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning <i class="fa fa-warning"></i></h4>
|
||||
<p>
|
||||
Due to platform limitations, YubiKeys cannot be used on all bitwarden applications. You should enable
|
||||
Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should enable
|
||||
another two-step login provider so that you can access your account when YubiKeys cannot be used.
|
||||
</p>
|
||||
<p>Supported platforms:</p>
|
||||
@@ -77,7 +77,7 @@
|
||||
{{updateModel.key1.existingKey}}
|
||||
</div>
|
||||
<input type="password" id="key1" name="Key1" ng-model="updateModel.key1.key" class="form-control" api-field
|
||||
ng-show="!updateModel.key1.existingKey" />
|
||||
ng-show="!updateModel.key1.existingKey" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="key2">YubiKey #2</label>
|
||||
@@ -88,7 +88,7 @@
|
||||
{{updateModel.key2.existingKey}}
|
||||
</div>
|
||||
<input type="password" id="key2" name="Key2" ng-model="updateModel.key2.key" class="form-control" api-field
|
||||
ng-show="!updateModel.key2.existingKey" />
|
||||
ng-show="!updateModel.key2.existingKey" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="key3">YubiKey #3</label>
|
||||
@@ -99,7 +99,7 @@
|
||||
{{updateModel.key3.existingKey}}
|
||||
</div>
|
||||
<input type="password" id="key3" name="Key3" ng-model="updateModel.key3.key" class="form-control" api-field
|
||||
ng-show="!updateModel.key3.existingKey" />
|
||||
ng-show="!updateModel.key3.existingKey" autocomplete="new-password" />
|
||||
</div>
|
||||
<strong>NFC Support</strong>
|
||||
<div class="checkbox">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<hr />
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
After updating your encryption key, you are required to log out and back in to all bitwarden applications that you
|
||||
After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you
|
||||
are currently using (such as the mobile app or browser extensions). Failure to log out and back
|
||||
in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out
|
||||
automatically, however it may be delayed.
|
||||
|
||||
@@ -53,8 +53,9 @@
|
||||
login_totp: null
|
||||
};
|
||||
|
||||
var j;
|
||||
if (decCiphers[i].fields) {
|
||||
for (var j = 0; j < decCiphers[i].fields.length; j++) {
|
||||
for (j = 0; j < decCiphers[i].fields.length; j++) {
|
||||
if (!cipher.fields) {
|
||||
cipher.fields = '';
|
||||
}
|
||||
@@ -69,10 +70,16 @@
|
||||
switch (decCiphers[i].type) {
|
||||
case constants.cipherType.login:
|
||||
cipher.type = 'login';
|
||||
cipher.login_uri = decCiphers[i].login.uri;
|
||||
cipher.login_username = decCiphers[i].login.username;
|
||||
cipher.login_password = decCiphers[i].login.password;
|
||||
cipher.login_totp = decCiphers[i].login.totp;
|
||||
|
||||
if (decCiphers[i].login.uris && decCiphers[i].login.uris.length) {
|
||||
cipher.login_uri = [];
|
||||
for (j = 0; j < decCiphers[i].login.uris.length; j++) {
|
||||
cipher.login_uri.push(decCiphers[i].login.uris[j].uri);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
cipher.type = 'note';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
$scope.options = [
|
||||
{
|
||||
id: 'bitwardencsv',
|
||||
name: 'bitwarden (csv)',
|
||||
name: 'Bitwarden (csv)',
|
||||
featured: true,
|
||||
sort: 1,
|
||||
instructions: $sce.trustAsHtml('Export using the web vault (vault.bitwarden.com). ' +
|
||||
@@ -36,16 +36,13 @@
|
||||
'https://help.bitwarden.com/article/import-from-chrome/</a>')
|
||||
},
|
||||
{
|
||||
id: 'firefoxpasswordexportercsvxml',
|
||||
name: 'Firefox Password Exporter (xml)',
|
||||
id: 'firefoxpasswordexportercsv',
|
||||
name: 'Firefox Password Exporter (csv)',
|
||||
featured: true,
|
||||
sort: 4,
|
||||
instructions: $sce.trustAsHtml('Use the ' +
|
||||
'<a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/password-exporter/">' +
|
||||
'Password Exporter</a> addon for FireFox to export your passwords to a XML file. After installing ' +
|
||||
'the addon, type <code>about:addons</code> in your FireFox navigation bar. Locate the Password Exporter ' +
|
||||
'addon and click the "Options" button. In the dialog that pops up, click the "Export Passwords" button ' +
|
||||
'to save the XML file.')
|
||||
'<a target="_blank" href="https://github.com/kspearrin/ff-password-exporter#ff-password-exporter">' +
|
||||
'FF Password Exporter</a> application to export your passwords to a CSV file.')
|
||||
},
|
||||
{
|
||||
id: 'keepass2xml',
|
||||
@@ -243,7 +240,7 @@
|
||||
'python script by Luke Plant to your desktop as <code>pw_helper.py</code>. Open terminal and run ' +
|
||||
'<code>chmod +rx Desktop/pw_helper.py</code> and then ' +
|
||||
'<code>python Desktop/pw_helper.py export Desktop/my_passwords.json</code>. Then upload ' +
|
||||
'the resulting <code>my_passwords.json</code> file here to bitwarden.')
|
||||
'the resulting <code>my_passwords.json</code> file here to Bitwarden.')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -282,7 +279,7 @@
|
||||
var halfway = Math.floor(ciphers.length / 2);
|
||||
var last = ciphers.length - 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,27 @@
|
||||
.module('bit.vault')
|
||||
|
||||
.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' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.constants = constants;
|
||||
$scope.selectedType = constants.cipherType.login.toString();
|
||||
$scope.selectedType = selectedType ? selectedType.toString() : constants.cipherType.login.toString();
|
||||
$scope.cipher = {
|
||||
folderId: selectedFolder ? selectedFolder.id : null,
|
||||
favorite: checkedFavorite === true,
|
||||
type: constants.cipherType.login,
|
||||
login: {},
|
||||
type: selectedType || constants.cipherType.login,
|
||||
login: {
|
||||
uris: [{
|
||||
uri: null,
|
||||
match: null,
|
||||
matchValue: null
|
||||
}]
|
||||
},
|
||||
identity: {},
|
||||
card: {},
|
||||
secureNote: {
|
||||
type: '0'
|
||||
type: 0
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +36,11 @@
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function () {
|
||||
if ($scope.cipher.type === constants.cipherType.login && $scope.cipher.login.uris.length === 1 &&
|
||||
($scope.cipher.login.uris[0].uri == null || $scope.cipher.login.uris[0].uri === '')) {
|
||||
$scope.cipher.login.uris = null;
|
||||
}
|
||||
|
||||
var cipher = cipherService.encryptCipher($scope.cipher);
|
||||
$scope.savePromise = apiService.ciphers.post(cipher, function (cipherResponse) {
|
||||
$analytics.eventTrack('Created Cipher');
|
||||
@@ -40,7 +52,43 @@
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$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 = 'file';
|
||||
fileEl.value = '';
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
validationService.addError(form, 'file', err, true);
|
||||
}
|
||||
else {
|
||||
validationService.addError(form, 'file', 'Something went wrong.', true);
|
||||
}
|
||||
}, function (e) {
|
||||
var errors = validationService.parseErrors(e);
|
||||
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,22 +2,35 @@
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr,
|
||||
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants) {
|
||||
$scope.loading = true;
|
||||
cipherService, $q, $localStorage, $timeout, $rootScope, $state, $analytics, constants, validationService) {
|
||||
$scope.loadingCiphers = true;
|
||||
$scope.loadingGroupings = true;
|
||||
$scope.ciphers = [];
|
||||
$scope.folders = [];
|
||||
$scope.collections = [];
|
||||
$scope.constants = constants;
|
||||
$scope.favoriteCollapsed = $localStorage.collapsedFolders && 'favorite' in $localStorage.collapsedFolders;
|
||||
$scope.folderIdFilter = undefined;
|
||||
$scope.typeFilter = undefined;
|
||||
$scope.filter = undefined;
|
||||
$scope.selectedType = undefined;
|
||||
$scope.selectedFolder = undefined;
|
||||
$scope.selectedCollection = undefined;
|
||||
$scope.selectedFavorites = false;
|
||||
$scope.selectedAll = true;
|
||||
$scope.selectedTitle = 'All';
|
||||
$scope.selectedIcon = 'fa-th';
|
||||
|
||||
if ($state.params.refreshFromServer) {
|
||||
$rootScope.vaultFolders = $rootScope.vaultCiphers = null;
|
||||
$rootScope.vaultFolders = $rootScope.vaultCollections = $rootScope.vaultCiphers = null;
|
||||
}
|
||||
|
||||
$scope.$on('$viewContentLoaded', function () {
|
||||
if ($rootScope.vaultFolders && $rootScope.vaultCiphers) {
|
||||
$scope.loading = false;
|
||||
loadFolderData($rootScope.vaultFolders);
|
||||
$timeout(function () {
|
||||
if ($('body').hasClass('control-sidebar-open')) {
|
||||
$("#search").focus();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
if (($rootScope.vaultFolders || $rootScope.vaultCollections) && $rootScope.vaultCiphers) {
|
||||
$scope.loadingCiphers = $scope.loadingGroupings = false;
|
||||
loadCipherData($rootScope.vaultCiphers);
|
||||
return;
|
||||
}
|
||||
@@ -26,21 +39,34 @@
|
||||
});
|
||||
|
||||
function loadDataFromServer() {
|
||||
var folderPromise = apiService.folders.list({}, function (folders) {
|
||||
var decFolders = [{
|
||||
id: null,
|
||||
name: 'No Folder'
|
||||
}];
|
||||
var decFolders = [{
|
||||
id: null,
|
||||
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++) {
|
||||
var decFolder = cipherService.decryptFolderPreview(folders.Data[i]);
|
||||
decFolders.push(decFolder);
|
||||
}
|
||||
|
||||
loadFolderData(decFolders);
|
||||
}).$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 = [];
|
||||
|
||||
for (var i = 0; i < ciphers.Data.length; i++) {
|
||||
@@ -48,38 +74,16 @@
|
||||
decCiphers.push(decCipher);
|
||||
}
|
||||
|
||||
folderPromise.then(function () {
|
||||
groupingPromise.then(function () {
|
||||
loadCipherData(decCiphers);
|
||||
});
|
||||
}).$promise;
|
||||
|
||||
$q.all([cipherPromise, folderPromise]).then(function () {
|
||||
$scope.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function loadFolderData(decFolders) {
|
||||
$rootScope.vaultFolders = $filter('orderBy')(decFolders, folderSort);
|
||||
}
|
||||
|
||||
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']);
|
||||
|
||||
var chunks = chunk($rootScope.vaultCiphers, 400);
|
||||
var chunks = chunk($rootScope.vaultCiphers, 200);
|
||||
if (chunks.length > 0) {
|
||||
$scope.ciphers = chunks[0];
|
||||
var delay = 200;
|
||||
@@ -94,6 +98,8 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.loadingCiphers = false;
|
||||
}
|
||||
|
||||
function sortScopedCipherData() {
|
||||
@@ -110,33 +116,19 @@
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function folderSort(item) {
|
||||
$scope.groupingSort = function (item) {
|
||||
if (!item.id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return item.name.toLowerCase();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clipboardError = function (e) {
|
||||
alert('Your web browser does not support easy clipboard copying. ' +
|
||||
'Edit the item and copy it manually instead.');
|
||||
};
|
||||
|
||||
$scope.collapseExpand = function (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) {
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
@@ -151,6 +143,8 @@
|
||||
if (returnVal.action === 'edit') {
|
||||
var index = $scope.ciphers.indexOf(cipher);
|
||||
if (index > -1) {
|
||||
// restore collection ids since those cannot change on edit here.
|
||||
returnVal.data.collectionIds = $rootScope.vaultCiphers[index].collectionIds;
|
||||
$rootScope.vaultCiphers[index] = returnVal.data;
|
||||
}
|
||||
sortScopedCipherData();
|
||||
@@ -169,14 +163,15 @@
|
||||
$scope.addCipher();
|
||||
});
|
||||
|
||||
$scope.addCipher = function (folder, favorite) {
|
||||
$scope.addCipher = function () {
|
||||
var addModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultAddCipher.html',
|
||||
controller: 'vaultAddCipherController',
|
||||
resolve: {
|
||||
selectedFolder: function () { return folder; },
|
||||
checkedFavorite: function () { return favorite; }
|
||||
selectedFolder: function () { return $scope.selectedFolder; },
|
||||
selectedType: function () { return $scope.selectedType; },
|
||||
checkedFavorite: function () { return $scope.selectedFavorites; }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -248,6 +243,10 @@
|
||||
};
|
||||
|
||||
$scope.editFolder = function (folder) {
|
||||
if (!folder.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
var editModel = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultEditFolder.html',
|
||||
@@ -277,12 +276,16 @@
|
||||
|
||||
addModel.result.then(function (addedFolder) {
|
||||
$rootScope.vaultFolders.push(addedFolder);
|
||||
loadFolderData($rootScope.vaultFolders);
|
||||
});
|
||||
};
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -291,19 +294,16 @@
|
||||
var index = $rootScope.vaultFolders.indexOf(folder);
|
||||
if (index > -1) {
|
||||
$rootScope.vaultFolders.splice(index, 1);
|
||||
for(var i = 0; i < $rootScope.vaultCiphers.length; i++) {
|
||||
if($rootScope.vaultCiphers[i].folderId === folder.id) {
|
||||
$rootScope.vaultCiphers[i].folderId = null;
|
||||
}
|
||||
}
|
||||
$scope.filterAll();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.canDeleteFolder = function (folder) {
|
||||
if (!folder || !folder.id || !$rootScope.vaultCiphers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var ciphers = $filter('filter')($rootScope.vaultCiphers, { folderId: folder.id });
|
||||
return ciphers && ciphers.length === 0;
|
||||
};
|
||||
|
||||
$scope.share = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
@@ -314,12 +314,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
modal.result.then(function (orgId) {
|
||||
cipher.organizationId = orgId;
|
||||
modal.result.then(function (returned) {
|
||||
cipher.organizationId = returned.orgId;
|
||||
cipher.collectionIds = returned.collectionIds || [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.collections = function (cipher) {
|
||||
$scope.editCollections = function (cipher) {
|
||||
var modal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'app/vault/views/vaultCipherCollections.html',
|
||||
@@ -333,55 +334,109 @@
|
||||
if (response.collectionIds && !response.collectionIds.length) {
|
||||
removeCipherFromScopes(cipher);
|
||||
}
|
||||
else if (response.collectionIds) {
|
||||
cipher.collectionIds = response.collectionIds;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.filterFolder = function (folder) {
|
||||
$scope.folderIdFilter = folder.id;
|
||||
$scope.filterCollection = function (col) {
|
||||
resetSelected();
|
||||
$scope.selectedCollection = col;
|
||||
$scope.selectedIcon = 'fa-cube';
|
||||
$scope.filter = function (c) {
|
||||
return c.collectionIds && c.collectionIds.indexOf(col.id) > -1;
|
||||
};
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterFolder = function (f) {
|
||||
resetSelected();
|
||||
$scope.selectedFolder = f;
|
||||
$scope.selectedIcon = 'fa-folder-open' + (!f.id ? '-o' : '');
|
||||
$scope.filter = function (c) {
|
||||
return c.folderId === f.id;
|
||||
};
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterType = function (t) {
|
||||
resetSelected();
|
||||
$scope.selectedType = t;
|
||||
switch (t) {
|
||||
case constants.cipherType.login:
|
||||
$scope.selectedTitle = 'Login';
|
||||
$scope.selectedIcon = 'fa-globe';
|
||||
break;
|
||||
case constants.cipherType.card:
|
||||
$scope.selectedTitle = 'Card';
|
||||
$scope.selectedIcon = 'fa-credit-card';
|
||||
break;
|
||||
case constants.cipherType.identity:
|
||||
$scope.selectedTitle = 'Identity';
|
||||
$scope.selectedIcon = 'fa-id-card-o';
|
||||
break;
|
||||
case constants.cipherType.secureNote:
|
||||
$scope.selectedTitle = 'Secure Note';
|
||||
$scope.selectedIcon = 'fa-sticky-note-o';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$scope.filter = function (c) {
|
||||
return c.type === t;
|
||||
};
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterFavorites = function () {
|
||||
resetSelected();
|
||||
$scope.selectedFavorites = true;
|
||||
$scope.selectedTitle = 'Favorites';
|
||||
$scope.selectedIcon = 'fa-star';
|
||||
$scope.filter = function (c) {
|
||||
return !!c.favorite;
|
||||
};
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
$scope.filterAll = function () {
|
||||
resetSelected();
|
||||
$scope.selectedAll = true;
|
||||
$scope.selectedTitle = 'All';
|
||||
$scope.selectedIcon = 'fa-th';
|
||||
$scope.filter = null;
|
||||
fixLayout();
|
||||
};
|
||||
|
||||
function resetSelected() {
|
||||
$scope.selectedFolder = undefined;
|
||||
$scope.selectedCollection = undefined;
|
||||
$scope.selectedType = undefined;
|
||||
$scope.selectedFavorites = false;
|
||||
$scope.selectedAll = false;
|
||||
}
|
||||
|
||||
function fixLayout() {
|
||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||
$timeout(function () {
|
||||
$.AdminLTE.layout.fix();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$scope.filterType = function (type) {
|
||||
$scope.typeFilter = type;
|
||||
|
||||
if ($.AdminLTE && $.AdminLTE.layout) {
|
||||
$timeout(function () {
|
||||
$.AdminLTE.layout.fix();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.clearFilters = function () {
|
||||
$scope.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.cipherFilter = function () {
|
||||
return function (cipher) {
|
||||
return !$scope.filter || $scope.filter(cipher);
|
||||
};
|
||||
};
|
||||
|
||||
$scope.unselectAll = function () {
|
||||
selectAll(false);
|
||||
};
|
||||
|
||||
$scope.selectFolder = function (folder, $event) {
|
||||
var checkbox = $($event.currentTarget).closest('.box').find('input[name="cipherSelection"]');
|
||||
checkbox.prop('checked', true);
|
||||
$scope.selectAll = function () {
|
||||
selectAll(true);
|
||||
};
|
||||
|
||||
$scope.select = function ($event) {
|
||||
@@ -445,7 +500,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bulkActionLoading = true;
|
||||
$scope.actionLoading = true;
|
||||
apiService.ciphers.delMany({ ids: ids }, function () {
|
||||
$analytics.eventTrack('Bulk Deleted Items');
|
||||
|
||||
@@ -457,11 +512,12 @@
|
||||
}
|
||||
|
||||
selectAll(false);
|
||||
$scope.bulkActionLoading = false;
|
||||
$scope.actionLoading = false;
|
||||
toastr.success('Items have been deleted!');
|
||||
}, function () {
|
||||
toastr.error('An error occurred.');
|
||||
$scope.bulkActionLoading = false;
|
||||
}, function (e) {
|
||||
var errors = validationService.parseErrors(e);
|
||||
toastr.error(errors.length ? errors[0] : 'An error occurred.');
|
||||
$scope.actionLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.module('bit.vault')
|
||||
|
||||
.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' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.cipher = {};
|
||||
@@ -16,6 +16,7 @@
|
||||
$scope.cipher = cipherService.decryptCipher(cipher);
|
||||
$scope.readOnly = !$scope.cipher.edit;
|
||||
$scope.useTotp = $scope.useTotp || $scope.cipher.organizationUseTotp;
|
||||
setUriMatchValues();
|
||||
});
|
||||
|
||||
$scope.save = function (model) {
|
||||
@@ -51,7 +52,43 @@
|
||||
$scope.generatePassword = function () {
|
||||
if (!$scope.cipher.login.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||
$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'
|
||||
});
|
||||
};
|
||||
|
||||
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')
|
||||
|
||||
.controller('vaultMoveCiphersController', function ($scope, apiService, $uibModalInstance, ids, $analytics,
|
||||
$rootScope) {
|
||||
$rootScope, $filter) {
|
||||
$analytics.eventTrack('vaultMoveCiphersController', { category: 'Modal' });
|
||||
$scope.folders = $rootScope.vaultFolders;
|
||||
$scope.count = ids.length;
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
var returnedCollectionIds = null;
|
||||
$scope.submitPromise = $q.all(attachmentSharePromises).then(function () {
|
||||
if (errorOnUpload) {
|
||||
return;
|
||||
@@ -159,11 +160,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
returnedCollectionIds = request.collectionIds;
|
||||
return apiService.ciphers.putShare({ id: cipherId }, request).$promise;
|
||||
}).then(function (response) {
|
||||
$analytics.eventTrack('Shared Cipher');
|
||||
toastr.success('Item has been shared.');
|
||||
$uibModalInstance.close(model.organizationId);
|
||||
$uibModalInstance.close({
|
||||
orgId: model.organizationId,
|
||||
collectionIds: returnedCollectionIds
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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">
|
||||
<div class="btn-group pull-right">
|
||||
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown"
|
||||
ng-disabled="bulkActionLoading">
|
||||
<i class="fa fa-refresh fa-spin" ng-show="bulkActionLoading"></i>
|
||||
Bulk Actions <span class="caret"></span>
|
||||
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown" ng-disabled="actionLoading">
|
||||
<i class="fa fa-refresh fa-spin" ng-show="actionLoading"></i>
|
||||
Actions <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
@@ -17,6 +16,11 @@
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="selectAll()">
|
||||
<i class="fa fa-fw fa-check-square-o"></i> Select All
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="unselectAll()">
|
||||
<i class="fa fa-fw fa-minus-square-o"></i> Unselect All
|
||||
@@ -26,170 +30,59 @@
|
||||
</div>
|
||||
<h1>
|
||||
My Vault
|
||||
<small>
|
||||
<span ng-pluralize
|
||||
count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
|
||||
<small class="visible-md-inline visible-lg-inline">
|
||||
<span ng-pluralize count="vaultFolders.length > 0 ? vaultFolders.length - 1 : 0"
|
||||
when="{'1': '{} folder', 'other': '{} folders'}"></span>,
|
||||
<span ng-pluralize count="vaultCollections.length"
|
||||
when="{'1': '{} collection', 'other': '{} collections'}"></span>, &
|
||||
<span ng-pluralize count="ciphers.length" when="{'1': '{} item', 'other': '{} items'}"></span>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div ng-show="loading && !vaultFolders.length">
|
||||
<div ng-show="loadingCiphers">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="box box-primary" ng-class="{'collapsed-box': favoriteCollapsed}" style="margin-bottom: 40px;"
|
||||
ng-show="vaultFolders.length && folderIdFilter === undefined && (!main.searchVaultText || favoriteCiphers.length)">
|
||||
<div class="box" ng-show="!loadingCiphers">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-star"></i>
|
||||
Favorites
|
||||
<small ng-pluralize count="favoriteCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
<i class="fa {{selectedIcon}}"></i>
|
||||
{{selectedFolder ? selectedFolder.name : selectedCollection ? selectedCollection.name : selectedTitle}}
|
||||
<small ng-pluralize count="filteredCiphers.length" when="{'1': '{} item', 'other': '{} items'}"></small>
|
||||
</h3>
|
||||
<div class="box-tools">
|
||||
<div class="box-tools" ng-if="selectedFolder && selectedFolder.id">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="addCipher(null, true)">
|
||||
<i class="fa fa-fw fa-plus-circle"></i> New Item
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" title="Collapse/Expand"
|
||||
ng-click="collapseExpand(null, true)">
|
||||
<i class="fa" ng-class="{'fa-minus': !favoriteCollapsed, 'fa-plus': favoriteCollapsed}"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body" ng-class="{'no-padding': favoriteCiphers.length}">
|
||||
<div ng-show="!favoriteCiphers.length">
|
||||
<p>No favorite items.</p>
|
||||
<button type="button" ng-click="addCipher(null, true)" class="btn btn-default btn-flat">Add an Item</button>
|
||||
</div>
|
||||
<div class="table-responsive" ng-show="favoriteCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="cipher in favoriteCiphers = (ciphers | filter: { favorite: true } |
|
||||
filter: cipherFilter | 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)">
|
||||
<a href="#" stop-click ng-click="editFolder(selectedFolder)">
|
||||
<i class="fa fa-fw fa-pencil"></i> Edit Folder
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="selectFolder(folder, $event)">
|
||||
<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">
|
||||
<a href="#" stop-click ng-click="deleteFolder(selectedFolder)" class="text-red">
|
||||
<i class="fa fa-fw fa-trash"></i> Delete Folder
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</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 class="box-body" ng-class="{'no-padding': folderCiphers.length}">
|
||||
<div ng-show="!folderCiphers.length">
|
||||
<p>No items in this folder.</p>
|
||||
<button type="button" ng-click="addCipher(folder)" class="btn btn-default btn-flat">Add an Item</button>
|
||||
<div class="box-body" ng-class="{'no-padding': filteredCiphers.length}">
|
||||
<div ng-show="!filteredCiphers.length">
|
||||
<p>No items to list.</p>
|
||||
<button type="button" ng-click="addCipher()" class="btn btn-default btn-flat"
|
||||
ng-if="!selectedCollection">
|
||||
Add an Item
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-responsive" ng-show="folderCiphers.length">
|
||||
<div class="table-responsive" ng-show="filteredCiphers.length">
|
||||
<table class="table table-striped table-hover table-vmiddle">
|
||||
<tbody>
|
||||
<tr ng-repeat="cipher in folderCiphers = (ciphers | filter: { folderId: folder.id } |
|
||||
filter: cipherFilter | filter: (main.searchVaultText || '')) track by cipher.id">
|
||||
<tr ng-repeat="cipher in filteredCiphers = (ciphers | filter: cipherFilter() |
|
||||
filter: (searchVaultText || '')) track by cipher.id">
|
||||
<td style="width: 70px;">
|
||||
<div class="btn-group" data-append-to="body">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
@@ -212,7 +105,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<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
|
||||
</a>
|
||||
</li>
|
||||
@@ -257,60 +150,86 @@
|
||||
</section>
|
||||
<aside class="control-sidebar control-sidebar-light">
|
||||
<div class="tab-content">
|
||||
<form class="search-form">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="search" id="search" class="form-control" placeholder="Search my vault..."
|
||||
ng-model="searchVaultText" />
|
||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="control-sidebar-menu">
|
||||
<li>
|
||||
<a href="#" stop-click ng-click="clearFilters()">
|
||||
Clear All Filters
|
||||
<li ng-class="{active: selectedAll}">
|
||||
<a href="#" stop-click ng-click="filterAll()">
|
||||
<i class="fa fa-th fa-fw"></i> All Items
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: selectedFavorites}">
|
||||
<a href="#" stop-click ng-click="filterFavorites()">
|
||||
<i class="fa fa-star fa-fw"></i> Favorites
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="control-sidebar-heading">
|
||||
<i class="fa fa-tag fa-fw"></i> Types
|
||||
</h3>
|
||||
<h3 class="control-sidebar-heading">Types</h3>
|
||||
<div class="control-sidebar-section">
|
||||
<ul class="control-sidebar-menu">
|
||||
<li>
|
||||
<li ng-class="{active: constants.cipherType.login === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.login)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.login === typeFilter"></i>
|
||||
<i class="fa fa-globe fa-fw" ng-if="constants.cipherType.login !== typeFilter"></i> Login
|
||||
<i class="fa fa-globe fa-fw"></i> Login
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li ng-class="{active: constants.cipherType.card === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.card)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.card === typeFilter"></i>
|
||||
<i class="fa fa-credit-card fa-fw" ng-if="constants.cipherType.card !== typeFilter"></i> Card
|
||||
<i class="fa fa-credit-card fa-fw"></i> Card
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li ng-class="{active: constants.cipherType.identity === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.identity)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.identity === typeFilter"></i>
|
||||
<i class="fa fa-id-card-o fa-fw" ng-if="constants.cipherType.identity !== typeFilter"></i> Identity
|
||||
<i class="fa fa-id-card-o fa-fw"></i> Identity
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li ng-class="{active: constants.cipherType.secureNote === selectedType}">
|
||||
<a href="#" stop-click ng-click="filterType(constants.cipherType.secureNote)">
|
||||
<i class="fa fa-check fa-fw" ng-if="constants.cipherType.secureNote === typeFilter"></i>
|
||||
<i class="fa fa-sticky-note-o fa-fw" ng-if="constants.cipherType.secureNote !== typeFilter"></i> Secure Note
|
||||
<i class="fa fa-sticky-note-o fa-fw"></i> Secure Note
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="control-sidebar-heading">
|
||||
<i class="fa fa-folder fa-fw"></i> Folders
|
||||
</h3>
|
||||
<div ng-show="loading && !vaultFolders.length">
|
||||
<h3 class="control-sidebar-heading">Folders</h3>
|
||||
<div ng-show="loadingGroupings">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="control-sidebar-section">
|
||||
<ul class="control-sidebar-menu" ng-show="!loading && vaultFolders.length">
|
||||
<li ng-repeat="folder in vaultFolders track by folder.id">
|
||||
<div class="control-sidebar-section" ng-show="!loadingGroupings">
|
||||
<ul class="control-sidebar-menu">
|
||||
<li ng-repeat="folder in vaultFolders | orderBy: [groupingSort] track by folder.id"
|
||||
ng-class="{active: selectedFolder && folder.id === selectedFolder.id}">
|
||||
<a href="#" stop-click ng-click="filterFolder(folder)">
|
||||
<i class="fa fa-check fa-fw" ng-if="folder.id === folderIdFilter"></i>
|
||||
<i class="fa fa-caret-right fa-fw" ng-if="folder.id !== folderIdFilter"></i>
|
||||
<i class="fa fa-caret-right fa-fw"></i>
|
||||
{{folder.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
</aside>
|
||||
|
||||
@@ -39,26 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
@@ -87,7 +68,8 @@
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field
|
||||
autocomplete="new-password" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
@@ -119,6 +101,58 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
|
||||
<div class="row">
|
||||
<div class="col-sm-7">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri{{$index}}">URI {{$index + 1}}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri{{$index}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="uri_match_{{$index}}">Match Detection</label>
|
||||
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match"
|
||||
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">Default</option>
|
||||
<option value="0">Base domain</option>
|
||||
<option value="1">Host</option>
|
||||
<option value="2">Starts with</option>
|
||||
<option value="4">Regular Expression</option>
|
||||
<option value="3">Exact</option>
|
||||
<option value="5">Never</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeUri(u)" stop-click>
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove URI</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<a href="#" ng-click="addUri()" stop-click>
|
||||
<i class="fa fa-plus-circle"></i> New URI
|
||||
</a>
|
||||
<br /><br />
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="row">
|
||||
@@ -504,7 +538,6 @@
|
||||
<div ng-if="cipher.type === constants.cipherType.secureNote">
|
||||
<!-- Nothing for now -->
|
||||
</div>
|
||||
|
||||
<div class="form-group" show-errors>
|
||||
<label for="notes">Notes</label>
|
||||
<textarea id="notes" name="Notes" class="form-control" ng-model="cipher.notes" api-field
|
||||
|
||||
@@ -33,24 +33,6 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="cipher.type === constants.cipherType.login">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri">URI</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri" name="Login.Uri" ng-model="cipher.login.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{cipher.login.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group" show-errors>
|
||||
@@ -79,7 +61,8 @@
|
||||
<label for="password">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="Login.Password" ng-model="cipher.login.password"
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field />
|
||||
class="form-control monospaced" ng-readonly="readOnly" api-field
|
||||
autocomplete="new-password" />
|
||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||
<button class="btn btn-default btn-flat" type="button" ngclipboard
|
||||
ngclipboard-success="clipboardSuccess(e)" ngclipboard-error="clipboardError(e, true)"
|
||||
@@ -111,6 +94,60 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-repeat="u in cipher.login.uris" ng-if="cipher.login.uris && cipher.login.uris.length">
|
||||
<div class="row">
|
||||
<div class="col-sm-7">
|
||||
<div class="form-group" show-errors>
|
||||
<label for="uri{{$index}}">URI {{$index + 1}}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="uri{{$index}}" name="Login.Uris[{{$index}}].Uri"
|
||||
ng-model="u.uri" class="form-control"
|
||||
placeholder="http://..." ng-readonly="readOnly" api-field />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI"
|
||||
tooltip-placement="left" ngclipboard ngclipboard-error="clipboardError(e)"
|
||||
data-clipboard-target="#uri{{$index}}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
</button>
|
||||
<a href="{{u.uri}}" target="_blank" class="btn btn-default btn-flat"
|
||||
uib-tooltip="Go To Website" tooltip-placement="left">
|
||||
<i class="fa fa-share"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<label for="uri_match_{{$index}}">Match Detection</label>
|
||||
<select id="uri_match_{{$index}}" name="Login.Uris[{{$index}}].Match" ng-disabled="readOnly"
|
||||
class="form-control" ng-model="u.matchValue" ng-change="uriMatchChanged(u)">
|
||||
<option value="">Default</option>
|
||||
<option value="0">Base domain</option>
|
||||
<option value="1">Host</option>
|
||||
<option value="2">Starts with</option>
|
||||
<option value="4">Regular Expression</option>
|
||||
<option value="3">Exact</option>
|
||||
<option value="5">Never</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1" ng-if="!readOnly">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeUri(u)" stop-click>
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove URI</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="visible-xs-block" />
|
||||
</div>
|
||||
<div ng-if="!readOnly">
|
||||
<a href="#" ng-click="addUri()" stop-click>
|
||||
<i class="fa fa-plus-circle"></i> New URI
|
||||
</a>
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="cipher.type === constants.cipherType.card">
|
||||
<div class="row">
|
||||
@@ -551,9 +588,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<div class="col-sm-1" ng-if="!readOnly">
|
||||
<br class="hidden-xs" />
|
||||
<a href="#" ng-click="removeField(field)" stop-click ng-if="!readOnly">
|
||||
<a href="#" ng-click="removeField(field)" stop-click>
|
||||
<i class="fa fa-window-close-o fa-lg"></i>
|
||||
<span class="visible-xs-inline">Remove Custom Field</span>
|
||||
</a>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<p ng-show="loading">Loading...</p>
|
||||
<div ng-show="!loading && !organizations.length" class="callout callout-default">
|
||||
<h4><i class="fa fa-info-circle"></i> No Organizations</h4>
|
||||
<p>You do not belong to any organizations. Organizations allow you to share items with other bitwarden users.</p>
|
||||
<p>You do not belong to any organizations. Organizations allow you to share items with other Bitwarden users.</p>
|
||||
<a ng-click="createOrg()" class="btn btn-default btn-flat">
|
||||
Create an Organization
|
||||
</a>
|
||||
|
||||
@@ -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,135 +0,0 @@
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
Apps
|
||||
<small>for all of your devices</small>
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="box box-default box-apps">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Desktop/Browser</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
|
||||
<i class="fa fa-chrome fa-lg fa-fw fa-li"></i> Google Chrome
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
|
||||
<i class="fa fa-firefox fa-lg fa-fw fa-li"></i> Mozilla Firefox
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://addons.opera.com/extensions/details/bitwarden-free-password-manager/" target="_blank">
|
||||
<i class="fa fa-opera fa-lg fa-fw fa-li"></i> Opera
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl" target="_blank">
|
||||
<i class="fa fa-edge fa-lg fa-fw fa-li"></i> Microsoft Edge
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
Others:
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" target="_blank">
|
||||
Vivaldi
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://brave.com/" target="_blank">
|
||||
Brave
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/" target="_blank">
|
||||
Tor Browser
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default box-apps">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Mobile</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<a href="https://itunes.apple.com/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank">
|
||||
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> iOS
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank">
|
||||
<i class="fa fa-android fa-lg fa-fw fa-li"></i> Android
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Windows
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box box-default box-apps">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Other</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-windows fa-lg fa-fw fa-li"></i> Desktop Windows
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-apple fa-lg fa-fw fa-li"></i> Desktop macOS
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-linux fa-lg fa-fw fa-li"></i> Desktop Linux
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" stop-click>
|
||||
<i class="fa fa-terminal fa-lg fa-fw fa-li"></i> CLI
|
||||
<small class="text-muted">(coming soon)</small>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -32,14 +32,6 @@
|
||||
<a ui-sref="backend.user.vault"><i class="fa fa-arrow-left"></i> Return to my vault</a>
|
||||
</div>
|
||||
</div>
|
||||
<form class="sidebar-form">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="text" id="search" class="form-control" placeholder="Search org. vault..."
|
||||
ng-focus="searchOrganizationVault()" ng-model="main.searchVaultText" />
|
||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="sidebar-menu">
|
||||
<li class="header">MY ORGANIZATION</li>
|
||||
<li ng-class="{active: $state.is('backend.org.dashboard')}">
|
||||
@@ -95,6 +87,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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)">
|
||||
<a ui-sref="backend.org.billing({orgId: params.orgId})">
|
||||
<i class="fa fa-credit-card fa-fw"></i> Billing & Licensing
|
||||
|
||||
@@ -32,14 +32,6 @@
|
||||
<a ui-sref="frontend.logout">Log Out</a>
|
||||
</div>
|
||||
</div>
|
||||
<form class="sidebar-form">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="text" id="search" class="form-control" placeholder="Search my vault..."
|
||||
ng-focus="searchVault()" ng-model="main.searchVaultText" autofocus />
|
||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="sidebar-menu">
|
||||
<li class="header">WEB VAULT</li>
|
||||
<li class="treeview" ng-class="{active: $state.includes('backend.user.vault')}">
|
||||
@@ -57,11 +49,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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') ||
|
||||
$state.is('backend.user.reportsBreach')}">
|
||||
<a ui-sref="backend.user.tools"><i class="fa fa-wrench fa-fw"></i> <span>Tools</span></a>
|
||||
@@ -109,7 +96,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-class="{active: $state.is('backend.user.apps')}">
|
||||
<a ui-sref="backend.user.apps">
|
||||
<a href="https://bitwarden.com/#download" target="_blank">
|
||||
<small class="label pull-right bg-green">FREE</small>
|
||||
<i class="fa fa-download fa-fw"></i> <span>Get the Apps</span>
|
||||
</a>
|
||||
|
||||
@@ -26,47 +26,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="js/duo.js"></script>
|
||||
<script>
|
||||
!(function () {
|
||||
var frameElement = document.createElement('iframe');
|
||||
frameElement.setAttribute('id', 'duo_iframe');
|
||||
setFrameHeight();
|
||||
document.body.appendChild(frameElement);
|
||||
|
||||
var hostParam = getQsParam('host');
|
||||
var requestParam = getQsParam('request');
|
||||
Duo.init({
|
||||
host: hostParam,
|
||||
sig_request: requestParam,
|
||||
submit_callback: function (form) {
|
||||
invokeCSCode(form.elements.sig_response.value);
|
||||
}
|
||||
});
|
||||
|
||||
window.onresize = setFrameHeight;
|
||||
function setFrameHeight() {
|
||||
frameElement.style.height = window.innerHeight + 'px';
|
||||
}
|
||||
})();
|
||||
|
||||
function getQsParam(name) {
|
||||
var url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, '\\$&');
|
||||
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
function invokeCSCode(data) {
|
||||
try {
|
||||
invokeCSharpAction(data);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="js/duo-connector.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,55 +2,6 @@
|
||||
<html ng-app="bit" ng-csp>
|
||||
<head>
|
||||
<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 !>
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src
|
||||
@@ -80,7 +31,7 @@
|
||||
<meta name="theme-color" content="#3c8dbc">
|
||||
<base href="/" />
|
||||
|
||||
<title page-title>bitwarden Web Vault</title>
|
||||
<title page-title>Bitwarden Web Vault</title>
|
||||
|
||||
<!-- @if !selfHosted -->
|
||||
<script src="https://js.stripe.com/v2/"></script>
|
||||
@@ -116,12 +67,12 @@
|
||||
<div ui-view></div>
|
||||
|
||||
<!-- @if !selfHosted !>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"
|
||||
integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB" crossorigin="anonymous"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
|
||||
integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
|
||||
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"
|
||||
integrity="sha384-AH/e+s4V4kUifvnNED2x1XZqArO5qTFU4YKRzUXbz4IgPG1H0Xmz6fP1XUmO4vT/" crossorigin="anonymous"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"
|
||||
integrity="sha384-R6kAKgTgRiD5889XyzYD/aMryNA4Yr9EBnt6rIXuukLgVONifQDnHNaadrSNakQl" crossorigin="anonymous"></script>
|
||||
<!-- @endif -->
|
||||
<!-- @if true !>
|
||||
<script src="js/fallback-scripts.min.js?v=<!-- @echo cacheTag !>"></script>
|
||||
@@ -195,12 +146,13 @@
|
||||
<script src="app/services/validationService.js"></script>
|
||||
<script src="app/services/passwordService.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/mainController.js"></script>
|
||||
<script src="app/global/topNavController.js"></script>
|
||||
<script src="app/global/sideNavController.js"></script>
|
||||
<script src="app/global/appsController.js"></script>
|
||||
<script src="app/global/premiumRequiredController.js"></script>
|
||||
<script src="app/global/paidOrgRequiredController.js"></script>
|
||||
|
||||
@@ -223,7 +175,6 @@
|
||||
<script src="app/vault/vaultEditFolderController.js"></script>
|
||||
<script src="app/vault/vaultAddFolderController.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/vaultMoveCiphersController.js"></script>
|
||||
<script src="app/vault/vaultAttachmentsController.js"></script>
|
||||
@@ -234,6 +185,7 @@
|
||||
<script src="app/organization/organizationPeopleInviteController.js"></script>
|
||||
<script src="app/organization/organizationPeopleEditController.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/organizationCollectionsAddController.js"></script>
|
||||
<script src="app/organization/organizationCollectionsEditController.js"></script>
|
||||
@@ -253,11 +205,13 @@
|
||||
<script src="app/organization/organizationVaultAddCipherController.js"></script>
|
||||
<script src="app/organization/organizationVaultEditCipherController.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/organizationGroupsController.js"></script>
|
||||
<script src="app/organization/organizationGroupsAddController.js"></script>
|
||||
<script src="app/organization/organizationGroupsEditController.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/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 {
|
||||
padding: 0;
|
||||
margin: 10px 0 10px 0;
|
||||
margin: 15px 0 10px 0;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-base;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.control-sidebar-light .control-sidebar-heading {
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
.control-sidebar-menu {
|
||||
@@ -160,10 +167,11 @@ body.skin-blue {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
li.active a {
|
||||
background-color: @component-active-bg;
|
||||
li.active a, li.active a:hover {
|
||||
background-color: lighten(@gray-lte, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +180,8 @@ body.skin-blue {
|
||||
overflow-y: auto;
|
||||
padding-right: 15px;
|
||||
margin-right: -15px;
|
||||
padding-left: 15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.control-sidebar-open {
|
||||
@@ -524,6 +534,10 @@ form .btn .loading-icon {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 5px;
|
||||
|
||||
input {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.box-body p:last-child {
|
||||
@@ -731,7 +745,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||
.totp-col {
|
||||
margin: -10px 0 10px 0;
|
||||
|
||||
@media (min-width: @screen-md) {
|
||||
@media (min-width: @screen-sm) {
|
||||
padding-top: 26px;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -811,9 +825,9 @@ h1, h2, h3, h4, h5, h6 {
|
||||
textarea {
|
||||
&#notes {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&.big-textarea {
|
||||
height: 200px !important;
|
||||
&.big-textarea {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "bitwarden vault",
|
||||
"name": "Bitwarden Vault",
|
||||
"icons": [
|
||||
{
|
||||
"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