mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165944624a | ||
|
|
11b96728f2 | ||
|
|
62e22ff149 | ||
|
|
09968a18d0 | ||
|
|
cc59d08a69 | ||
|
|
ac45ac2eca | ||
|
|
95c0e211c7 | ||
|
|
fed8990a6a | ||
|
|
b8431f0101 | ||
|
|
af47e25ead | ||
|
|
a315370e5d | ||
|
|
5a64b22190 | ||
|
|
4b05bc981c | ||
|
|
00f0dc3499 | ||
|
|
a1b77dc9ef | ||
|
|
9b38095aba | ||
|
|
714a574028 | ||
|
|
3e8194a3f7 | ||
|
|
6e4782784c | ||
|
|
ad40c38ca3 | ||
|
|
68f2de171e | ||
|
|
a9ef011cf3 | ||
|
|
53bd9a3b14 | ||
|
|
1466933e2c | ||
|
|
be515dc6a6 | ||
|
|
83859230cd | ||
|
|
ec7a40df0b | ||
|
|
aba98ba944 | ||
|
|
f6e8c7152e | ||
|
|
3a1fd5ba83 | ||
|
|
e43f816a8d | ||
|
|
9e61dbd512 | ||
|
|
8734d028d3 | ||
|
|
58850821ba | ||
|
|
f81ad479dd | ||
|
|
133d30ba97 | ||
|
|
09fba343fc | ||
|
|
ba3d4a2390 | ||
|
|
b1c59f3dc1 | ||
|
|
89dc3b70e1 | ||
|
|
769c247832 | ||
|
|
12e4b614f5 | ||
|
|
b28eaa1aae | ||
|
|
cd20b1c102 | ||
|
|
d6f80378eb | ||
|
|
32e9124b9c | ||
|
|
0aee3b7370 | ||
|
|
6bb6a674ec | ||
|
|
29d7a5e37e | ||
|
|
6067c1610c | ||
|
|
1b74d22b46 | ||
|
|
85a973afd4 | ||
|
|
1ea8762eeb | ||
|
|
35ecbcc11a | ||
|
|
3e988a741b | ||
|
|
db8f13d92f | ||
|
|
1a5885d6b4 | ||
|
|
31d2a09416 | ||
|
|
8ae96a6f88 | ||
|
|
d8aae1358b | ||
|
|
ed53c3b8f6 | ||
|
|
79ffafcc17 | ||
|
|
bdf6dcd8cd | ||
|
|
ec3154ea46 | ||
|
|
08fc18192d | ||
|
|
b01c71f579 | ||
|
|
a6c98f462a | ||
|
|
473dd8739a | ||
|
|
722bcfc31b | ||
|
|
929c3d7662 | ||
|
|
e25a8e051a | ||
|
|
4a1b46dd41 | ||
|
|
16877521e7 | ||
|
|
a16abb94cd | ||
|
|
5c8e9a990c | ||
|
|
c2515ed3ae | ||
|
|
227f457409 | ||
|
|
2e4a3501a2 | ||
|
|
fade7f1713 | ||
|
|
de84468ad8 | ||
|
|
2e20978cee | ||
|
|
2cc24335ef | ||
|
|
721a9f5f69 | ||
|
|
4ebbefa181 | ||
|
|
6ad930c609 | ||
|
|
85856d8390 | ||
|
|
a975f6df2b | ||
|
|
d2f1e39a9b | ||
|
|
8ef7944077 | ||
|
|
2a19189f04 | ||
|
|
cb4f318419 | ||
|
|
f239b0cd34 | ||
|
|
9d1b2b9f60 | ||
|
|
168f9a5525 | ||
|
|
13a04976fd | ||
|
|
84d03158b5 | ||
|
|
af7e2edbf0 | ||
|
|
2e7b88f149 | ||
|
|
5010736ca3 | ||
|
|
986f27294a | ||
|
|
1b8cddede8 | ||
|
|
66c814296b | ||
|
|
b14cdfcc72 | ||
|
|
aba2c70ad7 | ||
|
|
46e9158323 | ||
|
|
8449cdca75 | ||
|
|
8c0bc023b7 | ||
|
|
bcd488bb87 | ||
|
|
a7b7c716d4 | ||
|
|
6b29bb8468 | ||
|
|
3ffc035db3 | ||
|
|
d93392ba8b | ||
|
|
137b3b3490 | ||
|
|
99c8082866 | ||
|
|
24af5aca55 | ||
|
|
1429cb3f76 | ||
|
|
13cbba3e99 | ||
|
|
73d24162a0 | ||
|
|
4964ebd31e | ||
|
|
5a8198a878 | ||
|
|
03aa806af6 | ||
|
|
023bf0474c | ||
|
|
2e22ca9216 | ||
|
|
5a540bba9e | ||
|
|
2047a6378b | ||
|
|
dc87510a7a | ||
|
|
c3f4c6c03b | ||
|
|
e8b72477c9 | ||
|
|
862874c2ae | ||
|
|
4d2d686078 | ||
|
|
6d458646fa | ||
|
|
a1345488d0 | ||
|
|
c43012a5f2 | ||
|
|
577cab24c4 | ||
|
|
5c0a77aec8 | ||
|
|
a0904b14ed | ||
|
|
6774ae0ef3 | ||
|
|
5a76ca4676 | ||
|
|
3c5a972bc9 | ||
|
|
54b68ac543 | ||
|
|
ff378f05fe | ||
|
|
f207aa3a9d | ||
|
|
7b43dcb6a1 | ||
|
|
c487cf3284 | ||
|
|
c2e1d325f2 | ||
|
|
f090e8febf | ||
|
|
087c84bcfb | ||
|
|
a457c83242 | ||
|
|
bcd8963e8b | ||
|
|
1464e0fbe8 | ||
|
|
ec2b048289 | ||
|
|
04811c934f | ||
|
|
f84ee30b9d | ||
|
|
218caa28b0 | ||
|
|
a8af807650 | ||
|
|
826170507e | ||
|
|
c37979e48d | ||
|
|
7ebb046cd8 | ||
|
|
7c4d0a15dd | ||
|
|
512b9e0a92 | ||
|
|
5e95a8565c | ||
|
|
e83d0f2a9d | ||
|
|
eaebbcf6c8 | ||
|
|
7df5ed9b35 | ||
|
|
6b66f14319 | ||
|
|
2db1684b3c | ||
|
|
4625b44703 | ||
|
|
0356ecc17b | ||
|
|
1e7c27fba1 | ||
|
|
03f575f66f | ||
|
|
82b36c1b70 | ||
|
|
6878ab51fb | ||
|
|
8662033979 | ||
|
|
ef61652fba | ||
|
|
933a66b24c | ||
|
|
e2c6a5f8cd | ||
|
|
a818e7dd40 | ||
|
|
759dc647e5 | ||
|
|
37cf46d581 | ||
|
|
407032114e | ||
|
|
94aece134c | ||
|
|
7532bf9825 | ||
|
|
0f4f541b11 | ||
|
|
07a3d38bef | ||
|
|
e9273ff79a | ||
|
|
1aa708aed4 | ||
|
|
ebe5a6030e | ||
|
|
f6946085d8 | ||
|
|
beebe7c98b | ||
|
|
a51331d6b2 | ||
|
|
b7b970e654 |
@@ -1,4 +1,4 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
@@ -13,3 +13,6 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{ts}]
|
||||
quote_type = single
|
||||
|
||||
202
.github/workflows/build.yml
vendored
Normal file
202
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'gh-pages'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
cloc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cloc
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt -y install cloc
|
||||
|
||||
- name: Print lines of code
|
||||
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.x'
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Login to Azure
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "docker-password,
|
||||
docker-username,
|
||||
dct-delegate-2-repo-passphrase,
|
||||
dct-delegate-2-key"
|
||||
|
||||
- name: Log into docker
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
|
||||
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
|
||||
|
||||
- name: Setup Docker Trust
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
run: |
|
||||
mkdir -p ~/.docker/trust/private
|
||||
|
||||
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
|
||||
env:
|
||||
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
|
||||
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Restore
|
||||
run: dotnet tool restore
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
echo -e "# Building Web\n"
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
npm install
|
||||
npm run dist:selfhost
|
||||
|
||||
echo -e "\nBuilding docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web .
|
||||
|
||||
- name: Tag rc branch
|
||||
if: github.ref == 'refs/heads/rc'
|
||||
run: docker tag bitwarden/web bitwarden/web:rc
|
||||
|
||||
- name: Tag dev
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: docker tag bitwarden/web bitwarden/web:dev
|
||||
|
||||
- name: Tag beta
|
||||
if: github.event_name == 'release'
|
||||
run: docker tag bitwarden/web bitwarden/web:beta
|
||||
|
||||
- name: Tag version
|
||||
if: github.event_name == 'release'
|
||||
run: docker tag bitwarden/web bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
|
||||
shell: pwsh
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: List docker images
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
run: docker images
|
||||
|
||||
- name: Push rc images
|
||||
if: github.ref == 'refs/heads/rc'
|
||||
run: docker push bitwarden/web:rc
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Push dev images
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: docker push bitwarden/web:dev
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Push beta images
|
||||
if: github.event_name == 'release'
|
||||
run: docker push bitwarden/web:beta
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Push latest images
|
||||
if: github.event_name == 'release'
|
||||
run: docker push bitwarden/web:latest
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Push version images
|
||||
if: github.event_name == 'release'
|
||||
run: docker push bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
|
||||
shell: pwsh
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Log out of docker
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
|
||||
run: docker logout
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Set up NuGet
|
||||
uses: nuget/setup-nuget@v1
|
||||
with:
|
||||
nuget-version: 'latest'
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.x'
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help
|
||||
msbuild -version
|
||||
dotnet --info
|
||||
node --version
|
||||
npm --version
|
||||
Write-Output "GitHub ref: $env:GITHUB_REF"
|
||||
Write-Output "GitHub event: $env:GITHUB_EVENT"
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
|
||||
- name: npm build
|
||||
run: npm run build:prod
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ dist/
|
||||
*.zip
|
||||
build/
|
||||
!dev-server.shared.pem
|
||||
config/development.json
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
# How to Contribute
|
||||
|
||||
Contributions of all kinds are welcome!
|
||||
|
||||
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
|
||||
|
||||
Here is how you can get involved:
|
||||
|
||||
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
|
||||
|
||||
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
|
||||
|
||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
||||
|
||||
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
|
||||
|
||||
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
|
||||
|
||||
* **Translate:** See the localization (l10n) section below
|
||||
|
||||
## Contributor Agreement
|
||||
|
||||
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/web) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
* use `npm run lint` and fix any linting suggestions before submitting a pull request
|
||||
* commit any pull requests against the `master` branch
|
||||
* include a link to your Community Forums post
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
|
||||
41
README.md
41
README.md
@@ -5,8 +5,8 @@
|
||||
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://ci.appveyor.com/project/bitwarden/web/branch/master" target="_blank">
|
||||
<img src="https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true" alt="appveyor build" />
|
||||
<a href="https://github.com/bitwarden/web/actions?query=branch:master" target="_blank">
|
||||
<img src="https://github.com/bitwarden/web/actions/workflows/build.yml/badge.svg?branch=master" alt="Github Workflow build on master" />
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
|
||||
@@ -27,34 +27,43 @@
|
||||
|
||||
### Run the app
|
||||
|
||||
For local development, run the app with:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run build:watch
|
||||
```
|
||||
|
||||
You can now access the web vault in your browser at `https://localhost:8080`. You can adjust your API endpoint settings in `src/app/services/services.module.ts` by altering the `apiService.setUrls` call. For example:
|
||||
You can now access the web vault in your browser at `https://localhost:8080`.
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://mylocalapi' : null,
|
||||
identity: isDev ? 'http://mylocalidentity' : null,
|
||||
});
|
||||
If you want to point the development web vault to the production APIs, you can run using:
|
||||
|
||||
```
|
||||
npm install
|
||||
ENV=production npm run build:watch
|
||||
```
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can set:
|
||||
You can also manually adjusting your API endpoint settings by adding `config/development.js` overriding any of the values in `config/base.json`. For example:
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: null,
|
||||
api: 'https://api.bitwarden.com',
|
||||
identity: 'https://identity.bitwarden.com',
|
||||
});
|
||||
{
|
||||
"proxyApi": "http://your-api-url",
|
||||
"proxyIdentity": "http://your-identity-url",
|
||||
"proxyEvents": "http://your-events-url",
|
||||
"proxyNotifications": "http://your-notifications-url",
|
||||
"proxyPortal": "http://your-portal-url",
|
||||
"allowedHosts": ["hostnames-to-allow-in-webpack"]
|
||||
}
|
||||
```
|
||||
|
||||
To pick up the overrides in the newly created `config/development.js` file, run the app with:
|
||||
|
||||
```
|
||||
npm run build:dev:watch
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
||||
|
||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||
|
||||
83
appveyor.yml
83
appveyor.yml
@@ -1,83 +0,0 @@
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
- Ubuntu1804
|
||||
|
||||
branches:
|
||||
except:
|
||||
- l10n_master
|
||||
- gh-pages
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
stack: node 10
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
if($isWindows) {
|
||||
Install-Product node 10
|
||||
}
|
||||
|
||||
install:
|
||||
- ps: |
|
||||
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\package.json | ConvertFrom-Json).version
|
||||
$env:PUSH_DOCKER = "false"
|
||||
$env:PROD_DEPLOY = "false"
|
||||
$env:TAG_NAME = ""
|
||||
if($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_RE_BUILD -eq "True") {
|
||||
$env:PROD_DEPLOY = "true"
|
||||
$env:TAG_NAME = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
|
||||
echo "This is a production deployment for ${env:TAG_NAME}."
|
||||
}
|
||||
if("${env:DOCKER_USERNAME}" -ne "" -and "${env:DOCKER_PASSWORD}" -ne "") {
|
||||
$env:PUSH_DOCKER = "true"
|
||||
}
|
||||
if($isWindows) {
|
||||
choco install cloc --no-progress
|
||||
cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||
}
|
||||
|
||||
before_build:
|
||||
- node --version
|
||||
- npm --version
|
||||
- sh: |
|
||||
if [ "${PUSH_DOCKER}" == "true" ]
|
||||
then
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
fi
|
||||
- cmd: set "GIT_PATH=C:\Program Files\Git\mingw64\libexec\git-core"
|
||||
- cmd: set "PATH=%GIT_PATH%;%PATH%"
|
||||
|
||||
build_script:
|
||||
- sh: chmod +x ./build.sh
|
||||
- ps: |
|
||||
if($isLinux) {
|
||||
./build.sh
|
||||
./build.sh tag dev
|
||||
|
||||
if($env:PROD_DEPLOY -eq "true") {
|
||||
./build.sh tag beta
|
||||
./build.sh tag $env:TAG_NAME
|
||||
}
|
||||
|
||||
docker images
|
||||
|
||||
if($env:PUSH_DOCKER -eq "true") {
|
||||
./build.sh push dev
|
||||
|
||||
if($env:PROD_DEPLOY -eq "true") {
|
||||
./build.sh push beta
|
||||
./build.sh push latest
|
||||
./build.sh push $env:TAG_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
- cmd: npm install
|
||||
- cmd: npm run build:prod
|
||||
|
||||
after_build:
|
||||
- sh: |
|
||||
if [ "${PUSH_DOCKER}" == "true" ]
|
||||
then
|
||||
docker logout
|
||||
fi
|
||||
33
build.sh
33
build.sh
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 1 -a "$1" == "push" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "# Pushing Web ($TAG)"
|
||||
echo ""
|
||||
docker push bitwarden/web:$TAG
|
||||
elif [ $# -gt 1 -a "$1" == "tag" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "Tagging Web as '$TAG'"
|
||||
docker tag bitwarden/web bitwarden/web:$TAG
|
||||
else
|
||||
echo "# Building Web"
|
||||
|
||||
echo ""
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
npm install
|
||||
npm run sub:update
|
||||
npm run dist:selfhost
|
||||
|
||||
echo ""
|
||||
echo "Building docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web $DIR/.
|
||||
fi
|
||||
29
config.js
Normal file
29
config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
function load(envName) {
|
||||
const envOverrides = {
|
||||
'production': () => require('./config/production.json'),
|
||||
'qa': () => require('./config/qa.json'),
|
||||
'development': () => require('./config/development.json'),
|
||||
};
|
||||
|
||||
const baseConfig = require('./config/base.json');
|
||||
const overrideConfig = envOverrides.hasOwnProperty(envName) ? envOverrides[envName]() : {};
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
...overrideConfig
|
||||
};
|
||||
}
|
||||
|
||||
function log(configObj) {
|
||||
const repeatNum = 50
|
||||
console.log(`${"=".repeat(repeatNum)}\nenvConfig`)
|
||||
Object.entries(configObj).map(([key, value]) => {
|
||||
console.log(` ${key}: ${value}`)
|
||||
})
|
||||
console.log(`${"=".repeat(repeatNum)}`)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load,
|
||||
log
|
||||
};
|
||||
8
config/base.json
Normal file
8
config/base.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"proxyApi": "http://localhost:4000",
|
||||
"proxyIdentity": "http://localhost:33656",
|
||||
"proxyEvents": "http://localhost:46273",
|
||||
"proxyNotifications": "http://localhost:61840",
|
||||
"proxyPortal": "http://localhost:52313",
|
||||
"allowedHosts": []
|
||||
}
|
||||
7
config/production.json
Normal file
7
config/production.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"proxyApi": "https://api.bitwarden.com",
|
||||
"proxyIdentity": "https://identity.bitwarden.com",
|
||||
"proxyEvents": "https://events.bitwarden.com",
|
||||
"proxyNotifications": "https://notifications.bitwarden.com",
|
||||
"proxyPortal": "https://portal.bitwarden.com"
|
||||
}
|
||||
7
config/qa.json
Normal file
7
config/qa.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"proxyApi": "https://api.qa.bitwarden.com",
|
||||
"proxyIdentity": "https://identity.qa.bitwarden.com",
|
||||
"proxyEvents": "https://events.qa.bitwarden.com",
|
||||
"proxyNotifications": "https://notifications.qa.bitwarden.com",
|
||||
"proxyPortal": "https://portal.qa.bitwarden.com"
|
||||
}
|
||||
2
jslib
2
jslib
Submodule jslib updated: f30d6f8027...f6d91e2d92
1932
package-lock.json
generated
1932
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.16.2",
|
||||
"version": "2.20.1",
|
||||
"license": "GPL-3.0",
|
||||
"repository": "https://github.com/bitwarden/web",
|
||||
"scripts": {
|
||||
@@ -13,8 +13,12 @@
|
||||
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||
"build": "gulp prebuild && webpack",
|
||||
"build:watch": "gulp prebuild && webpack-dev-server",
|
||||
"build:prod": "gulp prebuild && cross-env NODE_ENV=production webpack",
|
||||
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production webpack-dev-server",
|
||||
"build:dev": "gulp prebuild && cross-env ENV=development webpack",
|
||||
"build:dev:watch": "gulp prebuild && cross-env ENV=development webpack-dev-server",
|
||||
"build:qa": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack",
|
||||
"build:qa:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack-dev-server",
|
||||
"build:prod": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack",
|
||||
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack-dev-server",
|
||||
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
|
||||
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
|
||||
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack",
|
||||
@@ -24,23 +28,23 @@
|
||||
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
|
||||
"deploy": "npm run dist && gh-pages -d build",
|
||||
"deploy:dev": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
|
||||
"lint": "tslint src/**/*.ts || true",
|
||||
"lint:fix": "tslint src/**/*.ts --fix"
|
||||
"lint": "tslint 'src/**/*.ts' || true",
|
||||
"lint:fix": "tslint 'src/**/*.ts' --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^9.1.12",
|
||||
"@ngtools/webpack": "^9.1.12",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^10.17.28",
|
||||
"@types/node-forge": "^0.7.5",
|
||||
"@types/papaparse": "^4.5.3",
|
||||
"@types/node-forge": "^0.9.7",
|
||||
"@types/papaparse": "^5.2.0",
|
||||
"@types/webcrypto": "^0.0.28",
|
||||
"@types/webpack": "^4.4.11",
|
||||
"@types/zxcvbn": "^4.4.0",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
@@ -73,25 +77,26 @@
|
||||
"@angular/platform-browser": "9.1.12",
|
||||
"@angular/platform-browser-dynamic": "9.1.12",
|
||||
"@angular/router": "9.1.12",
|
||||
"@microsoft/signalr": "3.1.0",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
||||
"@microsoft/signalr": "3.1.13",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.13",
|
||||
"angular2-toaster": "8.0.0",
|
||||
"angulartics2": "9.1.0",
|
||||
"big-integer": "1.6.36",
|
||||
"big-integer": "1.6.48",
|
||||
"bootstrap": "4.3.1",
|
||||
"braintree-web-drop-in": "1.13.0",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"core-js": "2.6.2",
|
||||
"date-input-polyfill": "^2.14.0",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git#410a9186cc34663c4913b17d6528067cd3331f1d",
|
||||
"font-awesome": "4.7.0",
|
||||
"jquery": "3.4.1",
|
||||
"jquery": "3.6.0",
|
||||
"lunr": "2.3.3",
|
||||
"ngx-infinite-scroll": "7.0.1",
|
||||
"node-forge": "0.7.6",
|
||||
"papaparse": "4.6.0",
|
||||
"node-forge": "0.10.0",
|
||||
"papaparse": "5.2.0",
|
||||
"popper.js": "1.14.4",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "6.6.2",
|
||||
"sweetalert2": "9.8.1",
|
||||
"sweetalert2": "10.15.4",
|
||||
"tslib": "^2.0.1",
|
||||
"web-animations-js": "2.3.1",
|
||||
"webcrypto-shim": "0.1.4",
|
||||
|
||||
50
src/404.html
Normal file
50
src/404.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="/404/bootstrap.min.css" rel="stylesheet" type="text/css"
|
||||
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l">
|
||||
<link href="/404/font-awesome.min.css" rel="stylesheet" type="text/css"
|
||||
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==">
|
||||
<link href="/404/styles.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<title>Page not found!</title>
|
||||
<meta name="description" content="404 Page Not Found">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="banner">
|
||||
<div class="container inner banner">
|
||||
<div class="row align-items-center">
|
||||
<div class="col brand">
|
||||
<i class="fa fa-shield"></i>
|
||||
<strong>bit</strong>warden</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container inner content">
|
||||
<h2>Page not found!</h2>
|
||||
<p>Sorry, but the page you were looking for could not be found.</p>
|
||||
<p>
|
||||
<a href="/">
|
||||
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%"/>
|
||||
</a>
|
||||
</p>
|
||||
<p>You can <a href="/">return to the web vault</a>, check our <a href="https://status.bitwarden.com/">status page</a>
|
||||
or <a href="https://bitwarden.com/contact/">contact us</a>.</p>
|
||||
</div>
|
||||
<div class="container footer text-muted content">
|
||||
© Copyright 2021 Bitwarden, Inc.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
7
src/404/bootstrap.min.css
vendored
Normal file
7
src/404/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
src/404/font-awesome.min.css
vendored
Normal file
4
src/404/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
119
src/404/styles.css
Normal file
119
src/404/styles.css
Normal file
@@ -0,0 +1,119 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url(../fonts/Open_Sans-italic-300.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url(../fonts/Open_Sans-italic-400.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url(../fonts/Open_Sans-italic-600.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url(../fonts/Open_Sans-italic-700.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url(../fonts/Open_Sans-italic-800.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(../fonts/Open_Sans-normal-300.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../fonts/Open_Sans-normal-400.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(../fonts/Open_Sans-normal-600.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(../fonts/Open_Sans-normal-700.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(../fonts/Open_Sans-normal-800.woff) format('woff');
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans';
|
||||
}
|
||||
|
||||
html, body, .row {
|
||||
height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 25px;
|
||||
margin-bottom: 12.5px;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 23px;
|
||||
line-height: 25px;
|
||||
color: #fff;
|
||||
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: #175DDC;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 40px 0 40px 0;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
34
src/app/accounts/accept-emergency.component.html
Normal file
34
src/app/accounts/accept-emergency.component.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" *ngIf="!loading && !authed">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{'emergencyAccess' | i18n}}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
{{name}}
|
||||
</p>
|
||||
<p>{{'acceptEmergencyAccess' | i18n}}</p>
|
||||
<hr>
|
||||
<div class="d-flex">
|
||||
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block">
|
||||
{{'logIn' | i18n}}
|
||||
</a>
|
||||
<a routerLink="/register" [queryParams]="{email: email}"
|
||||
class="btn btn-primary btn-block ml-2 mt-0">
|
||||
{{'createAccount' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
93
src/app/accounts/accept-emergency.component.ts
Normal file
93
src/app/accounts/accept-emergency.component.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import {
|
||||
Toast,
|
||||
ToasterService,
|
||||
} from 'angular2-toaster';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { EmergencyAccessAcceptRequest } from 'jslib/models/request/emergencyAccessAcceptRequest';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accept-emergency',
|
||||
templateUrl: 'accept-emergency.component.html',
|
||||
})
|
||||
export class AcceptEmergencyComponent implements OnInit {
|
||||
loading = true;
|
||||
authed = false;
|
||||
name: string;
|
||||
email: string;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
constructor(private router: Router, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private route: ActivatedRoute,
|
||||
private apiService: ApiService, private userService: UserService,
|
||||
private stateService: StateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async qParams => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
fired = true;
|
||||
await this.stateService.remove('emergencyInvitation');
|
||||
let error = qParams.id == null || qParams.name == null || qParams.email == null || qParams.token == null;
|
||||
let errorMessage: string = null;
|
||||
if (!error) {
|
||||
this.authed = await this.userService.isAuthenticated();
|
||||
if (this.authed) {
|
||||
const request = new EmergencyAccessAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
try {
|
||||
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
|
||||
await this.actionPromise;
|
||||
const toast: Toast = {
|
||||
type: 'success',
|
||||
title: this.i18nService.t('inviteAccepted'),
|
||||
body: this.i18nService.t('emergencyInviteAcceptedDesc'),
|
||||
timeout: 10000,
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/vault']);
|
||||
} catch (e) {
|
||||
error = true;
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
await this.stateService.save('emergencyInvitation', qParams);
|
||||
this.email = qParams.email;
|
||||
this.name = qParams.name;
|
||||
if (this.name != null) {
|
||||
// Fix URL encoding of space issue with Angular
|
||||
this.name = this.name.replace(/\+/g, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const toast: Toast = {
|
||||
type: 'error',
|
||||
title: null,
|
||||
body: errorMessage != null ? this.i18nService.t('emergencyInviteAcceptFailedShort', errorMessage) :
|
||||
this.i18nService.t('emergencyInviteAcceptFailed'),
|
||||
timeout: 10000,
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export class AcceptOrganizationComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async (qParams) => {
|
||||
this.route.queryParams.subscribe(async qParams => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,13 +33,6 @@ export class LockComponent extends BaseLockComponent {
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
const authed = await this.userService.isAuthenticated();
|
||||
if (!authed) {
|
||||
this.router.navigate(['/']);
|
||||
} else if (await this.cryptoService.hasKey()) {
|
||||
this.router.navigate(['vault']);
|
||||
}
|
||||
|
||||
this.onSuccessfulSubmit = () => {
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) {
|
||||
|
||||
@@ -34,7 +34,7 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
|
||||
this.email = qParams.email;
|
||||
}
|
||||
@@ -52,9 +52,12 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
const invite = await this.stateService.get<any>('orgInvitation');
|
||||
if (invite != null) {
|
||||
this.router.navigate(['accept-organization'], { queryParams: invite });
|
||||
const orgInvite = await this.stateService.get<any>('orgInvitation');
|
||||
const emergencyInvite = await this.stateService.get<any>('emergencyInvitation');
|
||||
if (orgInvite != null) {
|
||||
this.router.navigate(['accept-organization'], { queryParams: orgInvite });
|
||||
} else if (emergencyInvite != null) {
|
||||
this.router.navigate(['accept-emergency'], { queryParams: emergencyInvite });
|
||||
} else {
|
||||
const loginRedirect = await this.stateService.get<any>('loginRedirect');
|
||||
if (loginRedirect != null) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -18,8 +17,7 @@ export class RecoverDeleteComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService) {
|
||||
private toasterService: ToasterService, private i18nService: I18nService) {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -28,7 +26,6 @@ export class RecoverDeleteComponent {
|
||||
request.email = this.email.trim().toLowerCase();
|
||||
this.formPromise = this.apiService.postAccountRecoverDelete(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Started Delete Recovery' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deleteRecoverEmailSent'));
|
||||
this.router.navigate(['/']);
|
||||
} catch { }
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||
@@ -22,9 +21,8 @@ export class RecoverTwoFactorComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private cryptoService: CryptoService,
|
||||
private authService: AuthService) { }
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private cryptoService: CryptoService, private authService: AuthService) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -35,7 +33,6 @@ export class RecoverTwoFactorComponent {
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
this.formPromise = this.apiService.postTwoFactorRecover(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Recovered 2FA' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('twoStepRecoverDisabled'));
|
||||
this.router.navigate(['/']);
|
||||
} catch { }
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
</cite>
|
||||
</figcaption>
|
||||
<blockquote>
|
||||
"Bitwarden has become a popular choice among open-source software advocates. After using it for a
|
||||
few months, I can see why." - February 2020
|
||||
"Bitwarden has become a popular choice among open-source software advocates. After using
|
||||
it for a few months, I can see why." - February 2020
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
@@ -44,14 +44,15 @@
|
||||
<p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info" icon="fa-thumb-tack"
|
||||
*ngIf="showCreateOrgMessage">
|
||||
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info"
|
||||
icon="fa-thumb-tack" *ngIf="showCreateOrgMessage">
|
||||
{{'createOrganizationCreatePersonalAccount' | i18n}}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
||||
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email"
|
||||
required [appAutofocus]="email === ''" inputmode="email" appInputVerbatim="false">
|
||||
required [appAutofocus]="email === ''" inputmode="email"
|
||||
appInputVerbatim="false">
|
||||
<small class="form-text text-muted">{{'emailAddressDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -120,6 +121,19 @@
|
||||
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
|
||||
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showTerms">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="acceptPolicies"
|
||||
[(ngModel)]="acceptPolicies" name="AcceptPolicies">
|
||||
<label class="form-check-label small text-muted" for="acceptPolicies">
|
||||
{{'acceptPolicies' | i18n}}<br>
|
||||
<a href="https://bitwarden.com/terms/" target="_blank"
|
||||
rel="noopener">{{'termsOfService' | i18n}}</a>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank"
|
||||
rel="noopener">{{'privacyPolicy' | i18n}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex mb-2">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit"
|
||||
@@ -132,13 +146,6 @@
|
||||
{{'cancel' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<small class="text-muted" *ngIf="showTerms">
|
||||
{{'submitAgreePolicies' | i18n}}
|
||||
<a href="https://bitwarden.com/terms/" target="_blank"
|
||||
rel="noopener">{{'termsOfService' | i18n}}</a>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank"
|
||||
rel="noopener">{{'privacyPolicy' | i18n}}</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,6 @@ import { ReferenceEventRequest } from 'jslib/models/request/referenceEventReques
|
||||
})
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
showCreateOrgMessage = false;
|
||||
showTerms = true;
|
||||
layout = '';
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
@@ -40,7 +39,6 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService) {
|
||||
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
|
||||
passwordGenerationService);
|
||||
this.showTerms = !platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
getPasswordScoreAlertDisplay() {
|
||||
@@ -64,7 +62,7 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe((qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(qParams => {
|
||||
this.referenceData = new ReferenceEventRequest();
|
||||
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
|
||||
this.email = qParams.email;
|
||||
@@ -98,8 +96,8 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
|
||||
invite.email, invite.organizationUserId);
|
||||
if (policies.data != null) {
|
||||
const policiesData = policies.data.map((p) => new PolicyData(p));
|
||||
this.policies = policiesData.map((p) => new Policy(p));
|
||||
const policiesData = policies.data.map(p => new PolicyData(p));
|
||||
this.policies = policiesData.map(p => new Policy(p));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
@@ -24,8 +27,8 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
cryptoService: CryptoService, messagingService: MessagingService,
|
||||
userService: UserService, passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
|
||||
syncService: SyncService) {
|
||||
syncService: SyncService, route: ActivatedRoute) {
|
||||
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
|
||||
platformUtilsService, policyService, router, apiService, syncService);
|
||||
platformUtilsService, policyService, router, apiService, syncService, route);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
if (qParams.identifier != null) {
|
||||
this.identifier = qParams.identifier;
|
||||
} else {
|
||||
@@ -53,6 +53,9 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
|
||||
async submit() {
|
||||
await this.storageService.save(IdentifierStorageKey, this.identifier);
|
||||
if (this.clientId === 'browser') {
|
||||
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`;
|
||||
}
|
||||
super.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,10 @@
|
||||
required appAutofocus appInputVerbatim autocomplete="new-password">
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.U2f">
|
||||
<p class="text-center" *ngIf="!u2fReady">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="u2fReady">
|
||||
<p class="text-center">{{'insertU2f' | i18n}}</p>
|
||||
<img src="../../images/u2fkey.jpg" alt="" class="rounded img-fluid mb-3">
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
|
||||
<div id="web-authn-frame" class="mb-3">
|
||||
<iframe id="webauthn_iframe"></iframe>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo">
|
||||
@@ -51,7 +45,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
|
||||
*ngIf="form.loading && selectedProviderType === providerType.U2f" aria-hidden="true"></i>
|
||||
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i>
|
||||
<div class="form-check" *ngIf="selectedProviderType != null">
|
||||
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
|
||||
[(ngModel)]="remember">
|
||||
@@ -65,7 +59,7 @@
|
||||
<div class="d-flex mb-3">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"
|
||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
||||
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.U2f">
|
||||
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.WebAuthn">
|
||||
<span>
|
||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
|
||||
</span>
|
||||
@@ -84,4 +78,3 @@
|
||||
</div>
|
||||
</form>
|
||||
<ng-template #twoFactorOptions></ng-template>
|
||||
<iframe id="u2f_iframe" hidden></iframe>
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { TwoFactorOptionsComponent } from './two-factor-options.component';
|
||||
|
||||
@@ -34,9 +37,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
i18nService: I18nService, apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService, stateService: StateService,
|
||||
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
storageService: StorageService) {
|
||||
storageService: StorageService, route: ActivatedRoute) {
|
||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
||||
stateService, storageService);
|
||||
stateService, storageService, route);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
@@ -57,16 +60,23 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
const invite = await this.stateService.get<any>('orgInvitation');
|
||||
if (invite != null) {
|
||||
this.router.navigate(['accept-organization'], { queryParams: invite });
|
||||
const orgInvite = await this.stateService.get<any>('orgInvitation');
|
||||
const emergencyInvite = await this.stateService.get<any>('emergencyInvitation');
|
||||
if (orgInvite != null) {
|
||||
this.router.navigate(['accept-organization'], { queryParams: orgInvite });
|
||||
} else if (emergencyInvite != null) {
|
||||
this.router.navigate(['accept-emergency'], { queryParams: emergencyInvite });
|
||||
} else {
|
||||
const loginRedirect = await this.stateService.get<any>('loginRedirect');
|
||||
if (loginRedirect != null) {
|
||||
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
|
||||
await this.stateService.remove('loginRedirect');
|
||||
} else {
|
||||
this.router.navigate([this.successRoute]);
|
||||
this.router.navigate([this.successRoute], {
|
||||
queryParams: {
|
||||
identifier: this.identifier,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class VerifyEmailTokenComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async (qParams) => {
|
||||
this.route.queryParams.subscribe(async qParams => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -27,13 +26,13 @@ export class VerifyRecoverDeleteComponent implements OnInit {
|
||||
private token: string;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private route: ActivatedRoute) {
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async (qParams) => {
|
||||
this.route.queryParams.subscribe(async qParams => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
@@ -53,7 +52,6 @@ export class VerifyRecoverDeleteComponent implements OnInit {
|
||||
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
|
||||
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Recovered Delete' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
|
||||
this.i18nService.t('accountDeletedDesc'));
|
||||
this.router.navigate(['/']);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { FrontendLayoutComponent } from './layouts/frontend-layout.component';
|
||||
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
|
||||
import { UserLayoutComponent } from './layouts/user-layout.component';
|
||||
|
||||
import { AcceptEmergencyComponent } from './accounts/accept-emergency.component';
|
||||
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
|
||||
import { HintComponent } from './accounts/hint.component';
|
||||
import { LockComponent } from './accounts/lock.component';
|
||||
@@ -57,6 +58,9 @@ import {
|
||||
|
||||
import { VaultComponent as OrgVaultComponent } from './organizations/vault/vault.component';
|
||||
|
||||
import { AccessComponent } from './send/access.component';
|
||||
import { SendComponent } from './send/send.component';
|
||||
|
||||
import { AccountComponent } from './settings/account.component';
|
||||
import { CreateOrganizationComponent } from './settings/create-organization.component';
|
||||
import { DomainRulesComponent } from './settings/domain-rules.component';
|
||||
@@ -83,11 +87,15 @@ import { VaultComponent } from './vault/vault.component';
|
||||
|
||||
import { OrganizationGuardService } from './services/organization-guard.service';
|
||||
import { OrganizationTypeGuardService } from './services/organization-type-guard.service';
|
||||
import { UnauthGuardService } from './services/unauth-guard.service';
|
||||
|
||||
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
|
||||
import { LockGuardService } from 'jslib/angular/services/lock-guard.service';
|
||||
import { UnauthGuardService } from 'jslib/angular/services/unauth-guard.service';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { Permissions } from 'jslib/enums/permissions';
|
||||
|
||||
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
|
||||
import { EmergencyAccessComponent } from './settings/emergency-access.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -115,13 +123,22 @@ const routes: Routes = [
|
||||
canActivate: [UnauthGuardService],
|
||||
data: { titleId: 'passwordHint' },
|
||||
},
|
||||
{ path: 'lock', component: LockComponent },
|
||||
{
|
||||
path: 'lock',
|
||||
component: LockComponent,
|
||||
canActivate: [LockGuardService],
|
||||
},
|
||||
{ path: 'verify-email', component: VerifyEmailTokenComponent },
|
||||
{
|
||||
path: 'accept-organization',
|
||||
component: AcceptOrganizationComponent,
|
||||
data: { titleId: 'joinOrganization' },
|
||||
},
|
||||
{
|
||||
path: 'accept-emergency',
|
||||
component: AcceptEmergencyComponent,
|
||||
data: { titleId: 'acceptEmergency' },
|
||||
},
|
||||
{ path: 'recover', pathMatch: 'full', redirectTo: 'recover-2fa' },
|
||||
{
|
||||
path: 'recover-2fa',
|
||||
@@ -141,6 +158,11 @@ const routes: Routes = [
|
||||
canActivate: [UnauthGuardService],
|
||||
data: { titleId: 'deleteAccount' },
|
||||
},
|
||||
{
|
||||
path: 'send/:sendId/:key',
|
||||
component: AccessComponent,
|
||||
data: { title: 'Bitwarden Send' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -149,6 +171,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuardService],
|
||||
children: [
|
||||
{ path: 'vault', component: VaultComponent, data: { titleId: 'myVault' } },
|
||||
{ path: 'sends', component: SendComponent, data: { title: 'Send' } },
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
@@ -171,6 +194,21 @@ const routes: Routes = [
|
||||
component: CreateOrganizationComponent,
|
||||
data: { titleId: 'newOrganization' },
|
||||
},
|
||||
{
|
||||
path: 'emergency-access',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: EmergencyAccessComponent,
|
||||
data: { titleId: 'emergencyAccess'},
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: EmergencyAccessViewComponent,
|
||||
data: { titleId: 'emergencyAccess'},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -227,35 +265,75 @@ const routes: Routes = [
|
||||
path: 'tools',
|
||||
component: OrgToolsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
|
||||
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'import' },
|
||||
{ path: 'import', component: OrgImportComponent, data: { titleId: 'importData' } },
|
||||
{ path: 'export', component: OrgExportComponent, data: { titleId: 'exportVault' } },
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'import',
|
||||
},
|
||||
{
|
||||
path: 'import',
|
||||
component: OrgImportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'importData',
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'export',
|
||||
component: OrgExportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'exportVault',
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'exposed-passwords-report',
|
||||
component: OrgExposedPasswordsReportComponent,
|
||||
data: { titleId: 'exposedPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'exposedPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'inactive-two-factor-report',
|
||||
component: OrgInactiveTwoFactorReportComponent,
|
||||
data: { titleId: 'inactive2faReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'inactive2faReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'reused-passwords-report',
|
||||
component: OrgReusedPasswordsReportComponent,
|
||||
data: { titleId: 'reusedPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'reusedPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'unsecured-websites-report',
|
||||
component: OrgUnsecuredWebsitesReportComponent,
|
||||
data: { titleId: 'unsecuredWebsitesReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'unsecuredWebsitesReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'weak-passwords-report',
|
||||
component: OrgWeakPasswordsReportComponent,
|
||||
data: { titleId: 'weakPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'weakPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -264,26 +342,73 @@ const routes: Routes = [
|
||||
component: OrgManageComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
allowedTypes: [
|
||||
OrganizationUserType.Owner,
|
||||
OrganizationUserType.Admin,
|
||||
OrganizationUserType.Manager,
|
||||
permissions: [
|
||||
Permissions.ManageAssignedCollections,
|
||||
Permissions.ManageAllCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'people' },
|
||||
{ path: 'collections', component: OrgManageCollectionsComponent, data: { titleId: 'collections' } },
|
||||
{ path: 'events', component: OrgEventsComponent, data: { titleId: 'eventLogs' } },
|
||||
{ path: 'groups', component: OrgGroupsComponent, data: { titleId: 'groups' } },
|
||||
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
|
||||
{ path: 'policies', component: OrgPoliciesComponent, data: { titleId: 'policies' } },
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'people',
|
||||
},
|
||||
{
|
||||
path: 'collections',
|
||||
component: OrgManageCollectionsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'collections',
|
||||
permissions: [Permissions.ManageAssignedCollections, Permissions.ManageAllCollections],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'events',
|
||||
component: OrgEventsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'eventLogs',
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
component: OrgGroupsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'groups',
|
||||
permissions: [Permissions.ManageGroups],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'people',
|
||||
component: OrgPeopleComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'people',
|
||||
permissions: [Permissions.ManageUsers],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'policies',
|
||||
component: OrgPoliciesComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'policies',
|
||||
permissions: [Permissions.ManagePolicies],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: OrgSettingsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { allowedTypes: [OrganizationUserType.Owner] },
|
||||
data: { permissions: [Permissions.ManageOrganization] },
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'account' },
|
||||
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
|
||||
@@ -308,6 +433,7 @@ const routes: Routes = [
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {
|
||||
useHash: true,
|
||||
paramsInheritanceStrategy: 'always',
|
||||
/*enableTracing: true,*/
|
||||
})],
|
||||
exports: [RouterModule],
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import * as jq from 'jquery';
|
||||
import Swal from 'sweetalert2/src/sweetalert2.js';
|
||||
import Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||
|
||||
import {
|
||||
BodyOutputType,
|
||||
Toast,
|
||||
ToasterConfig,
|
||||
ToasterContainerComponent,
|
||||
ToasterService,
|
||||
} from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
|
||||
import {
|
||||
Component,
|
||||
@@ -70,12 +67,12 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
|
||||
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService, private userService: UserService,
|
||||
private tokenService: TokenService, private folderService: FolderService,
|
||||
private settingsService: SettingsService, private syncService: SyncService,
|
||||
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
|
||||
private authService: AuthService, private router: Router, private analytics: Angulartics2,
|
||||
private authService: AuthService, private router: Router,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
@@ -139,15 +136,18 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.router.navigate(['settings/premium']);
|
||||
}
|
||||
break;
|
||||
case 'emailVerificationRequired':
|
||||
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('emailVerificationRequiredDesc'),
|
||||
this.i18nService.t('emailVerificationRequired'),
|
||||
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
|
||||
if (emailVerificationConfirmed) {
|
||||
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/');
|
||||
}
|
||||
break;
|
||||
case 'showToast':
|
||||
this.showToast(message);
|
||||
break;
|
||||
case 'analyticsEventTrack':
|
||||
this.analytics.eventTrack.next({
|
||||
action: message.action,
|
||||
properties: { label: message.label },
|
||||
});
|
||||
break;
|
||||
case 'setFullWidth':
|
||||
this.setFullWidth();
|
||||
break;
|
||||
@@ -157,7 +157,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
});
|
||||
|
||||
this.router.events.subscribe((event) => {
|
||||
this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
const modals = Array.from(document.querySelectorAll('.modal'));
|
||||
for (const modal of modals) {
|
||||
@@ -198,7 +198,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
this.analytics.eventTrack.next({ action: 'Logged Out' });
|
||||
if (expired) {
|
||||
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
|
||||
this.i18nService.t('loginExpired'));
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'core-js';
|
||||
|
||||
import { ToasterModule } from 'angular2-toaster';
|
||||
import { Angulartics2Module } from 'angulartics2';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
@@ -27,6 +25,7 @@ import { NavbarComponent } from './layouts/navbar.component';
|
||||
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
|
||||
import { UserLayoutComponent } from './layouts/user-layout.component';
|
||||
|
||||
import { AcceptEmergencyComponent } from './accounts/accept-emergency.component';
|
||||
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
|
||||
import { HintComponent } from './accounts/hint.component';
|
||||
import { LockComponent } from './accounts/lock.component';
|
||||
@@ -60,13 +59,11 @@ import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/m
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
||||
import { ApiKeyComponent as OrgApiKeyComponent } from './organizations/settings/api-key.component';
|
||||
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
||||
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
|
||||
import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component';
|
||||
import { RotateApiKeyComponent as OrgRotateApiKeyComponent } from './organizations/settings/rotate-api-key.component';
|
||||
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
|
||||
import {
|
||||
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
|
||||
@@ -98,10 +95,15 @@ import { CollectionsComponent as OrgCollectionsComponent } from './organizations
|
||||
import { GroupingsComponent as OrgGroupingsComponent } from './organizations/vault/groupings.component';
|
||||
import { VaultComponent as OrgVaultComponent } from './organizations/vault/vault.component';
|
||||
|
||||
import { AccessComponent } from './send/access.component';
|
||||
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component';
|
||||
import { SendComponent } from './send/send.component';
|
||||
|
||||
import { AccountComponent } from './settings/account.component';
|
||||
import { AddCreditComponent } from './settings/add-credit.component';
|
||||
import { AdjustPaymentComponent } from './settings/adjust-payment.component';
|
||||
import { AdjustStorageComponent } from './settings/adjust-storage.component';
|
||||
import { ApiKeyComponent } from './settings/api-key.component';
|
||||
import { ChangeEmailComponent } from './settings/change-email.component';
|
||||
import { ChangeKdfComponent } from './settings/change-kdf.component';
|
||||
import { ChangePasswordComponent } from './settings/change-password.component';
|
||||
@@ -109,6 +111,13 @@ import { CreateOrganizationComponent } from './settings/create-organization.comp
|
||||
import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component';
|
||||
import { DeleteAccountComponent } from './settings/delete-account.component';
|
||||
import { DomainRulesComponent } from './settings/domain-rules.component';
|
||||
import { EmergencyAccessAddEditComponent } from './settings/emergency-access-add-edit.component';
|
||||
import { EmergencyAccessAttachmentsComponent } from './settings/emergency-access-attachments.component';
|
||||
import { EmergencyAccessConfirmComponent } from './settings/emergency-access-confirm.component';
|
||||
import { EmergencyAccessTakeoverComponent } from './settings/emergency-access-takeover.component';
|
||||
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
|
||||
import { EmergencyAccessComponent } from './settings/emergency-access.component';
|
||||
import { EmergencyAddEditComponent } from './settings/emergency-add-edit.component';
|
||||
import { LinkSsoComponent } from './settings/link-sso.component';
|
||||
import { OptionsComponent } from './settings/options.component';
|
||||
import { OrganizationPlansComponent } from './settings/organization-plans.component';
|
||||
@@ -124,8 +133,8 @@ import { TwoFactorDuoComponent } from './settings/two-factor-duo.component';
|
||||
import { TwoFactorEmailComponent } from './settings/two-factor-email.component';
|
||||
import { TwoFactorRecoveryComponent } from './settings/two-factor-recovery.component';
|
||||
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
|
||||
import { TwoFactorU2fComponent } from './settings/two-factor-u2f.component';
|
||||
import { TwoFactorVerifyComponent } from './settings/two-factor-verify.component';
|
||||
import { TwoFactorWebAuthnComponent } from './settings/two-factor-webauthn.component';
|
||||
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
|
||||
import { UpdateKeyComponent } from './settings/update-key.component';
|
||||
import { UpdateLicenseComponent } from './settings/update-license.component';
|
||||
@@ -156,6 +165,7 @@ import { CiphersComponent } from './vault/ciphers.component';
|
||||
import { CollectionsComponent } from './vault/collections.component';
|
||||
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||
import { GroupingsComponent } from './vault/groupings.component';
|
||||
import { SendInfoComponent } from './vault/send-info.component';
|
||||
import { ShareComponent } from './vault/share.component';
|
||||
import { VaultComponent } from './vault/vault.component';
|
||||
|
||||
@@ -179,7 +189,10 @@ import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
|
||||
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
||||
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import {
|
||||
DatePipe,
|
||||
registerLocaleData,
|
||||
} from '@angular/common';
|
||||
import localeCa from '@angular/common/locales/ca';
|
||||
import localeCs from '@angular/common/locales/cs';
|
||||
import localeDa from '@angular/common/locales/da';
|
||||
@@ -241,17 +254,14 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
FormsModule,
|
||||
AppRoutingModule,
|
||||
ServicesModule,
|
||||
Angulartics2Module.forRoot({
|
||||
pageTracking: {
|
||||
clearQueryParams: true,
|
||||
},
|
||||
}),
|
||||
ToasterModule.forRoot(),
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
],
|
||||
declarations: [
|
||||
A11yTitleDirective,
|
||||
AcceptEmergencyComponent,
|
||||
AccessComponent,
|
||||
AcceptOrganizationComponent,
|
||||
AccountComponent,
|
||||
SetPasswordComponent,
|
||||
@@ -261,6 +271,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
AdjustSeatsComponent,
|
||||
AdjustStorageComponent,
|
||||
ApiActionDirective,
|
||||
ApiKeyComponent,
|
||||
AppComponent,
|
||||
AttachmentsComponent,
|
||||
AutofocusDirective,
|
||||
@@ -287,6 +298,13 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
DeleteOrganizationComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAccessViewComponent,
|
||||
EmergencyAddEditComponent,
|
||||
ExportComponent,
|
||||
ExposedPasswordsReportComponent,
|
||||
FallbackSrcDirective,
|
||||
@@ -308,7 +326,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OptionsComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgApiKeyComponent,
|
||||
OrganizationBillingComponent,
|
||||
OrganizationPlansComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
@@ -332,7 +349,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OrgPolicyEditComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
@@ -358,6 +374,9 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
SearchCiphersPipe,
|
||||
SearchPipe,
|
||||
SelectCopyDirective,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendInfoComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SsoComponent,
|
||||
@@ -373,8 +392,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorU2fComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UnsecuredWebsitesReportComponent,
|
||||
UpdateKeyComponent,
|
||||
@@ -390,6 +409,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
],
|
||||
entryComponents: [
|
||||
AddEditComponent,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BulkActionsComponent,
|
||||
BulkDeleteComponent,
|
||||
@@ -400,10 +420,14 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAddEditComponent,
|
||||
FolderAddEditComponent,
|
||||
ModalComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgApiKeyComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgCollectionAddEditComponent,
|
||||
OrgCollectionsComponent,
|
||||
@@ -411,23 +435,23 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OrgEntityUsersComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
OrgUserGroupsComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PurgeVaultComponent,
|
||||
SendAddEditComponent,
|
||||
ShareComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
TwoFactorEmailComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorU2fComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdateKeyComponent,
|
||||
],
|
||||
providers: [],
|
||||
providers: [DatePipe],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -15,8 +15,8 @@ export class FooterComponent implements OnInit {
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
this.year = new Date().getFullYear().toString();
|
||||
this.version = this.platformUtilsService.getApplicationVersion();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ export class FrontendLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
this.year = new Date().getFullYear().toString();
|
||||
this.version = this.platformUtilsService.getApplicationVersion();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
document.body.classList.add('layout_frontend');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/sends">{{'send' | i18n}}</a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
|
||||
</li>
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" *ngIf="organization.isManager">
|
||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
{{'vault' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="manage" routerLinkActive="active">
|
||||
<li class="nav-item" *ngIf="showManageTab">
|
||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||
<i class="fa fa-sliders" aria-hidden="true"></i>
|
||||
{{'manage' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isAdmin">
|
||||
<a class="nav-link" routerLink="tools" routerLinkActive="active">
|
||||
<li class="nav-item" *ngIf="showToolsTab">
|
||||
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
|
||||
<i class="fa fa-wrench" aria-hidden="true"></i>
|
||||
{{'tools' | i18n}}
|
||||
</a>
|
||||
@@ -43,10 +43,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="ml-auto d-flex align-items-center">
|
||||
<button class="btn btn-primary" (click)="goToEnterprisePortal()" #enterpriseBtn
|
||||
[appApiAction]="enterpriseTokenPromise" *ngIf="organization.useBusinessPortal">
|
||||
<i class="fa fa-bank fa-fw" [hidden]="enterpriseBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!enterpriseBtn.loading" title="{{'loading' | i18n}}"
|
||||
<button class="btn btn-primary" (click)="goToBusinessPortal()" #businessBtn
|
||||
[appApiAction]="businessTokenPromise" *ngIf="showBusinessPortalButton">
|
||||
<i class="fa fa-bank fa-fw" [hidden]="businessBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!businessBtn.loading" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
{{'businessPortal' | i18n}} →
|
||||
</button>
|
||||
|
||||
@@ -24,9 +24,9 @@ const BroadcasterSubscriptionId = 'OrganizationLayoutComponent';
|
||||
})
|
||||
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
organization: Organization;
|
||||
enterpriseTokenPromise: Promise<any>;
|
||||
businessTokenPromise: Promise<any>;
|
||||
private organizationId: string;
|
||||
private enterpriseUrl: string;
|
||||
private businessUrl: string;
|
||||
|
||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||
@@ -34,15 +34,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
private environmentService: EnvironmentService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.enterpriseUrl = 'https://portal.bitwarden.com';
|
||||
this.businessUrl = 'https://portal.bitwarden.com';
|
||||
if (this.environmentService.enterpriseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.enterpriseUrl;
|
||||
this.businessUrl = this.environmentService.enterpriseUrl;
|
||||
} else if (this.environmentService.baseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.baseUrl + '/portal';
|
||||
this.businessUrl = this.environmentService.baseUrl + '/portal';
|
||||
}
|
||||
|
||||
document.body.classList.remove('layout_frontend');
|
||||
this.route.params.subscribe(async (params) => {
|
||||
this.route.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
});
|
||||
@@ -65,19 +65,68 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
this.organization = await this.userService.getOrganization(this.organizationId);
|
||||
}
|
||||
|
||||
async goToEnterprisePortal() {
|
||||
if (this.enterpriseTokenPromise != null) {
|
||||
async goToBusinessPortal() {
|
||||
if (this.businessTokenPromise != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.enterpriseTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||
const token = await this.enterpriseTokenPromise;
|
||||
this.businessTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||
const token = await this.businessTokenPromise;
|
||||
if (token != null) {
|
||||
const userId = await this.userService.getUserId();
|
||||
this.platformUtilsService.launchUri(this.enterpriseUrl + '/login?userId=' + userId +
|
||||
this.platformUtilsService.launchUri(this.businessUrl + '/login?userId=' + userId +
|
||||
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organization.id);
|
||||
}
|
||||
} catch { }
|
||||
this.enterpriseTokenPromise = null;
|
||||
this.businessTokenPromise = null;
|
||||
}
|
||||
|
||||
get showMenuBar() {
|
||||
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
||||
}
|
||||
|
||||
get showManageTab(): boolean {
|
||||
return this.organization.canManageUsers ||
|
||||
this.organization.canManageAssignedCollections ||
|
||||
this.organization.canManageAllCollections ||
|
||||
this.organization.canManageGroups ||
|
||||
this.organization.canManagePolicies ||
|
||||
this.organization.canAccessEventLogs;
|
||||
}
|
||||
|
||||
get showToolsTab(): boolean {
|
||||
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
||||
}
|
||||
|
||||
get showBusinessPortalButton(): boolean {
|
||||
return this.organization.useBusinessPortal && this.organization.canAccessBusinessPortal;
|
||||
}
|
||||
|
||||
get toolsRoute(): string {
|
||||
return this.organization.canAccessImportExport ?
|
||||
'tools/import' :
|
||||
'tools/exposed-passwords-report';
|
||||
}
|
||||
|
||||
get manageRoute(): string {
|
||||
let route: string;
|
||||
switch (true) {
|
||||
case this.organization.canManageUsers:
|
||||
route = 'manage/people';
|
||||
break;
|
||||
case this.organization.canManageAssignedCollections || this.organization.canManageAllCollections:
|
||||
route = 'manage/collections';
|
||||
break;
|
||||
case this.organization.canManageGroups:
|
||||
route = 'manage/groups';
|
||||
break;
|
||||
case this.organization.canManagePolicies:
|
||||
route = 'manage/policies';
|
||||
break;
|
||||
case this.organization.canAccessEventLogs:
|
||||
route = 'manage/events';
|
||||
break;
|
||||
}
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
@@ -15,7 +14,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { CipherString } from 'jslib/models/domain/cipherString';
|
||||
import { EncString } from 'jslib/models/domain/encString';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
import { CollectionRequest } from 'jslib/models/request/collectionRequest';
|
||||
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
|
||||
@@ -46,9 +45,8 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
private orgKey: SymmetricCryptoKey;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService,
|
||||
private userService: UserService) { }
|
||||
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
|
||||
private cryptoService: CryptoService, private userService: UserService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
@@ -56,7 +54,7 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
this.editMode = this.loading = this.collectionId != null;
|
||||
if (this.accessGroups) {
|
||||
const groupsResponse = await this.apiService.getGroups(this.organizationId);
|
||||
this.groups = groupsResponse.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, 'name'));
|
||||
this.groups = groupsResponse.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'name'));
|
||||
}
|
||||
this.orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
|
||||
@@ -65,11 +63,11 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
this.title = this.i18nService.t('editCollection');
|
||||
try {
|
||||
const collection = await this.apiService.getCollectionDetails(this.organizationId, this.collectionId);
|
||||
this.name = await this.cryptoService.decryptToUtf8(new CipherString(collection.name), this.orgKey);
|
||||
this.name = await this.cryptoService.decryptToUtf8(new EncString(collection.name), this.orgKey);
|
||||
this.externalId = collection.externalId;
|
||||
if (collection.groups != null && this.groups.length > 0) {
|
||||
collection.groups.forEach((s) => {
|
||||
const group = this.groups.filter((g) => !g.accessAll && g.id === s.id);
|
||||
collection.groups.forEach(s => {
|
||||
const group = this.groups.filter(g => !g.accessAll && g.id === s.id);
|
||||
if (group != null && group.length > 0) {
|
||||
(group[0] as any).checked = true;
|
||||
(group[0] as any).readOnly = s.readOnly;
|
||||
@@ -82,7 +80,7 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
this.title = this.i18nService.t('addCollection');
|
||||
}
|
||||
|
||||
this.groups.forEach((g) => {
|
||||
this.groups.forEach(g => {
|
||||
if (g.accessAll) {
|
||||
(g as any).checked = true;
|
||||
}
|
||||
@@ -103,7 +101,7 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.groups.forEach((g) => this.check(g, select));
|
||||
this.groups.forEach(g => this.check(g, select));
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -114,8 +112,8 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
const request = new CollectionRequest();
|
||||
request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString;
|
||||
request.externalId = this.externalId;
|
||||
request.groups = this.groups.filter((g) => (g as any).checked && !g.accessAll)
|
||||
.map((g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords));
|
||||
request.groups = this.groups.filter(g => (g as any).checked && !g.accessAll)
|
||||
.map(g => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords));
|
||||
|
||||
try {
|
||||
if (this.editMode) {
|
||||
@@ -124,7 +122,6 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
this.formPromise = this.apiService.postCollection(this.organizationId, request);
|
||||
}
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Collection' : 'Created Collection' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name));
|
||||
this.onSavedCollection.emit();
|
||||
@@ -146,7 +143,6 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
try {
|
||||
this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId);
|
||||
await this.deletePromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Collection' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', this.name));
|
||||
this.onDeletedCollection.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
@@ -52,15 +51,15 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
||||
private userService: UserService, private searchService: SearchService) { }
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||
private searchService: SearchService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
this.searchText = qParams.search;
|
||||
if (queryParamsSub != null) {
|
||||
queryParamsSub.unsubscribe();
|
||||
@@ -72,12 +71,12 @@ export class CollectionsComponent implements OnInit {
|
||||
async load() {
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
let response: ListResponse<CollectionResponse>;
|
||||
if (organization.isAdmin) {
|
||||
if (organization.canManageAllCollections) {
|
||||
response = await this.apiService.getCollections(this.organizationId);
|
||||
} else {
|
||||
response = await this.apiService.getUserCollections();
|
||||
}
|
||||
const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) =>
|
||||
const collections = response.data.filter(c => c.organizationId === this.organizationId).map(r =>
|
||||
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||
this.collections = await this.collectionService.decryptMany(collections);
|
||||
this.resetPaging();
|
||||
@@ -141,7 +140,6 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.apiService.deleteCollection(this.organizationId, collection.id);
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Collection' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', collection.name));
|
||||
this.removeCollection(collection);
|
||||
} catch { }
|
||||
|
||||
@@ -50,7 +50,7 @@ export class EntityEventsComponent implements OnInit {
|
||||
async load() {
|
||||
if (this.showUser) {
|
||||
const response = await this.apiService.getOrganizationUsers(this.organizationId);
|
||||
response.data.forEach((u) => {
|
||||
response.data.forEach(u => {
|
||||
const name = u.name == null || u.name.trim() === '' ? u.email : u.name;
|
||||
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
|
||||
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
|
||||
@@ -94,7 +94,7 @@ export class EntityEventsComponent implements OnInit {
|
||||
} catch { }
|
||||
|
||||
this.continuationToken = response.continuationToken;
|
||||
const events = response.data.map((r) => {
|
||||
const events = response.data.map(r => {
|
||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||
const eventInfo = this.eventService.getEventInfo(r);
|
||||
const user = this.showUser && userId != null && this.orgUsersUserIdMap.has(userId) ?
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
|
||||
</td>
|
||||
<td class="text-center" *ngIf="entity === 'collection'">
|
||||
<input type="checkbox" [(ngModel)]="u.hidePasswords"
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -42,7 +41,7 @@ export class EntityUsersComponent implements OnInit {
|
||||
private allUsers: OrganizationUserUserDetailsResponse[] = [];
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private toasterService: ToasterService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
await this.loadUsers();
|
||||
@@ -51,7 +50,7 @@ export class EntityUsersComponent implements OnInit {
|
||||
|
||||
get users() {
|
||||
if (this.showSelected) {
|
||||
return this.allUsers.filter((u) => (u as any).checked);
|
||||
return this.allUsers.filter(u => (u as any).checked);
|
||||
} else {
|
||||
return this.allUsers;
|
||||
}
|
||||
@@ -59,12 +58,12 @@ export class EntityUsersComponent implements OnInit {
|
||||
|
||||
async loadUsers() {
|
||||
const users = await this.apiService.getOrganizationUsers(this.organizationId);
|
||||
this.allUsers = users.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, 'email'));
|
||||
this.allUsers = users.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'email'));
|
||||
if (this.entity === 'group') {
|
||||
const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId);
|
||||
if (response != null && users.data.length > 0) {
|
||||
response.forEach((s) => {
|
||||
const user = users.data.filter((u) => u.id === s);
|
||||
response.forEach(s => {
|
||||
const user = users.data.filter(u => u.id === s);
|
||||
if (user != null && user.length > 0) {
|
||||
(user[0] as any).checked = true;
|
||||
}
|
||||
@@ -73,8 +72,8 @@ export class EntityUsersComponent implements OnInit {
|
||||
} else if (this.entity === 'collection') {
|
||||
const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId);
|
||||
if (response != null && users.data.length > 0) {
|
||||
response.forEach((s) => {
|
||||
const user = users.data.filter((u) => !u.accessAll && u.id === s.id);
|
||||
response.forEach(s => {
|
||||
const user = users.data.filter(u => !u.accessAll && u.id === s.id);
|
||||
if (user != null && user.length > 0) {
|
||||
(user[0] as any).checked = true;
|
||||
(user[0] as any).readOnly = s.readOnly;
|
||||
@@ -84,7 +83,7 @@ export class EntityUsersComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.allUsers.forEach((u) => {
|
||||
this.allUsers.forEach(u => {
|
||||
if (this.entity === 'collection' && u.accessAll) {
|
||||
(u as any).checked = true;
|
||||
}
|
||||
@@ -121,17 +120,14 @@ export class EntityUsersComponent implements OnInit {
|
||||
async submit() {
|
||||
try {
|
||||
if (this.entity === 'group') {
|
||||
const selections = this.users.filter((u) => (u as any).checked).map((u) => u.id);
|
||||
const selections = this.users.filter(u => (u as any).checked).map(u => u.id);
|
||||
this.formPromise = this.apiService.putGroupUsers(this.organizationId, this.entityId, selections);
|
||||
} else {
|
||||
const selections = this.users.filter((u) => (u as any).checked && !u.accessAll)
|
||||
.map((u) => new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords));
|
||||
const selections = this.users.filter(u => (u as any).checked && !u.accessAll)
|
||||
.map(u => new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords));
|
||||
this.formPromise = this.apiService.putCollectionUsers(this.organizationId, this.entityId, selections);
|
||||
}
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({
|
||||
action: this.entity === 'group' ? 'Edited Group Users' : 'Edited Collection Users',
|
||||
});
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('updatedUsers'));
|
||||
this.onEditedUsers.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -39,7 +39,7 @@ export class EventsComponent implements OnInit {
|
||||
private router: Router) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (organization == null || !organization.useEvents) {
|
||||
@@ -55,7 +55,7 @@ export class EventsComponent implements OnInit {
|
||||
|
||||
async load() {
|
||||
const response = await this.apiService.getOrganizationUsers(this.organizationId);
|
||||
response.data.forEach((u) => {
|
||||
response.data.forEach(u => {
|
||||
const name = u.name == null || u.name.trim() === '' ? u.email : u.name;
|
||||
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
|
||||
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
|
||||
@@ -92,7 +92,7 @@ export class EventsComponent implements OnInit {
|
||||
} catch { }
|
||||
|
||||
this.continuationToken = response.continuationToken;
|
||||
const events = response.data.map((r) => {
|
||||
const events = response.data.map(r => {
|
||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||
const eventInfo = this.eventService.getEventInfo(r);
|
||||
const user = userId != null && this.orgUsersUserIdMap.has(userId) ?
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
@@ -42,8 +41,8 @@ export class GroupAddEditComponent implements OnInit {
|
||||
deletePromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
|
||||
private toasterService: ToasterService, private collectionService: CollectionService,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.groupId != null;
|
||||
@@ -58,8 +57,8 @@ export class GroupAddEditComponent implements OnInit {
|
||||
this.name = group.name;
|
||||
this.externalId = group.externalId;
|
||||
if (group.collections != null && this.collections != null) {
|
||||
group.collections.forEach((s) => {
|
||||
const collection = this.collections.filter((c) => c.id === s.id);
|
||||
group.collections.forEach(s => {
|
||||
const collection = this.collections.filter(c => c.id === s.id);
|
||||
if (collection != null && collection.length > 0) {
|
||||
(collection[0] as any).checked = true;
|
||||
collection[0].readOnly = s.readOnly;
|
||||
@@ -77,7 +76,7 @@ export class GroupAddEditComponent implements OnInit {
|
||||
|
||||
async loadCollections() {
|
||||
const response = await this.apiService.getCollections(this.organizationId);
|
||||
const collections = response.data.map((r) =>
|
||||
const collections = response.data.map(r =>
|
||||
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||
this.collections = await this.collectionService.decryptMany(collections);
|
||||
}
|
||||
@@ -90,7 +89,7 @@ export class GroupAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.collections.forEach((c) => this.check(c, select));
|
||||
this.collections.forEach(c => this.check(c, select));
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -99,8 +98,8 @@ export class GroupAddEditComponent implements OnInit {
|
||||
request.externalId = this.externalId;
|
||||
request.accessAll = this.access === 'all';
|
||||
if (!request.accessAll) {
|
||||
request.collections = this.collections.filter((c) => (c as any).checked)
|
||||
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
|
||||
request.collections = this.collections.filter(c => (c as any).checked)
|
||||
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -110,7 +109,6 @@ export class GroupAddEditComponent implements OnInit {
|
||||
this.formPromise = this.apiService.postGroup(this.organizationId, request);
|
||||
}
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Group' : 'Created Group' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t(this.editMode ? 'editedGroupId' : 'createdGroupId', this.name));
|
||||
this.onSavedGroup.emit();
|
||||
@@ -132,7 +130,6 @@ export class GroupAddEditComponent implements OnInit {
|
||||
try {
|
||||
this.deletePromise = this.apiService.deleteGroup(this.organizationId, this.groupId);
|
||||
await this.deletePromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Group' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', this.name));
|
||||
this.onDeletedGroup.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -49,12 +48,12 @@ export class GroupsComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||
private router: Router, private searchService: SearchService) { }
|
||||
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
|
||||
private userService: UserService, private router: Router,
|
||||
private searchService: SearchService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (organization == null || !organization.useGroups) {
|
||||
@@ -62,7 +61,7 @@ export class GroupsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
this.searchText = qParams.search;
|
||||
if (queryParamsSub != null) {
|
||||
queryParamsSub.unsubscribe();
|
||||
@@ -136,7 +135,6 @@ export class GroupsComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.apiService.deleteGroup(this.organizationId, group.id);
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Group' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', group.name));
|
||||
this.removeGroup(group);
|
||||
} catch { }
|
||||
|
||||
@@ -5,22 +5,23 @@
|
||||
<div class="card-header">{{'manage' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="people" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin">
|
||||
*ngIf="organization.canManageUsers">
|
||||
{{'people' | i18n}}
|
||||
</a>
|
||||
<a routerLink="collections" class="list-group-item" routerLinkActive="active">
|
||||
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.canManageAssignedCollections || organization.canManageAllCollections">
|
||||
{{'collections' | i18n}}
|
||||
</a>
|
||||
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessGroups">
|
||||
*ngIf="organization.canManageGroups && accessGroups">
|
||||
{{'groups' | i18n}}
|
||||
</a>
|
||||
<a routerLink="policies" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessPolicies">
|
||||
*ngIf="organization.canManagePolicies && accessPolicies">
|
||||
{{'policies' | i18n}}
|
||||
</a>
|
||||
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessEvents">
|
||||
*ngIf="organization.canAccessEventLogs && accessEvents">
|
||||
{{'eventLogs' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export class ManageComponent implements OnInit {
|
||||
constructor(private route: ActivatedRoute, private userService: UserService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.accessPolicies = this.organization.usePolicies;
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ValidationService } from 'jslib/angular/services/validation.service';
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
@@ -70,16 +70,16 @@ export class PeopleComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private userService: UserService, private router: Router,
|
||||
private storageService: StorageService, private searchService: SearchService) { }
|
||||
private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private userService: UserService, private router: Router,
|
||||
private storageService: StorageService, private searchService: SearchService,
|
||||
private validationService: ValidationService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (!organization.isAdmin) {
|
||||
if (!organization.canManageUsers) {
|
||||
this.router.navigate(['../collections'], { relativeTo: this.route });
|
||||
return;
|
||||
}
|
||||
@@ -87,10 +87,10 @@ export class PeopleComponent implements OnInit {
|
||||
this.accessGroups = organization.useGroups;
|
||||
await this.load();
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
this.searchText = qParams.search;
|
||||
if (qParams.viewEvents != null) {
|
||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||
const user = this.users.filter(u => u.id === qParams.viewEvents);
|
||||
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
|
||||
this.events(user[0]);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class PeopleComponent implements OnInit {
|
||||
this.statusMap.clear();
|
||||
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.allUsers.sort(Utils.getSortFunction(this.i18nService, 'email'));
|
||||
this.allUsers.forEach((u) => {
|
||||
this.allUsers.forEach(u => {
|
||||
if (!this.statusMap.has(u.status)) {
|
||||
this.statusMap.set(u.status, [u]);
|
||||
} else {
|
||||
@@ -231,7 +231,6 @@ export class PeopleComponent implements OnInit {
|
||||
|
||||
try {
|
||||
await this.apiService.deleteOrganizationUser(this.organizationId, user.id);
|
||||
this.analytics.eventTrack.next({ action: 'Deleted User' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', user.name || user.email));
|
||||
this.removeUser(user);
|
||||
} catch { }
|
||||
@@ -243,7 +242,6 @@ export class PeopleComponent implements OnInit {
|
||||
}
|
||||
this.actionPromise = this.apiService.postOrganizationUserReinvite(this.organizationId, user.id);
|
||||
await this.actionPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Reinvited User' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenReinvited', user.name || user.email));
|
||||
this.actionPromise = null;
|
||||
}
|
||||
@@ -258,6 +256,20 @@ export class PeopleComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUser = async (publicKey: Uint8Array) => {
|
||||
try {
|
||||
this.actionPromise = this.doConfirmation(user, publicKey);
|
||||
await this.actionPromise;
|
||||
updateUser(this);
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email));
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
this.actionPromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
@@ -277,9 +289,14 @@ export class PeopleComponent implements OnInit {
|
||||
childComponent.organizationId = this.organizationId;
|
||||
childComponent.organizationUserId = user != null ? user.id : null;
|
||||
childComponent.userId = user != null ? user.userId : null;
|
||||
childComponent.onConfirmedUser.subscribe(() => {
|
||||
this.modal.close();
|
||||
updateUser(this);
|
||||
childComponent.onConfirmedUser.subscribe(async (publicKey: Uint8Array) => {
|
||||
try {
|
||||
await confirmUser(publicKey);
|
||||
this.modal.close();
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error('Handled exception:', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
@@ -288,12 +305,19 @@ export class PeopleComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actionPromise = this.doConfirmation(user);
|
||||
await this.actionPromise;
|
||||
updateUser(this);
|
||||
this.analytics.eventTrack.next({ action: 'Confirmed User' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email));
|
||||
this.actionPromise = null;
|
||||
try {
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
try {
|
||||
// tslint:disable-next-line
|
||||
console.log('User\'s fingerprint: ' +
|
||||
(await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
|
||||
} catch { }
|
||||
await confirmUser(publicKey);
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error('Handled exception:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async events(user: OrganizationUserUserDetailsResponse) {
|
||||
@@ -334,15 +358,8 @@ export class PeopleComponent implements OnInit {
|
||||
return !searching && this.users && this.users.length > this.pageSize;
|
||||
}
|
||||
|
||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
|
||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
|
||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
try {
|
||||
// tslint:disable-next-line
|
||||
console.log('User\'s fingerprint: ' +
|
||||
(await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
|
||||
} catch { }
|
||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
|
||||
const request = new OrganizationUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<app-callout *ngIf="userCanAccessBusinessPortal" [type]="'warning'">
|
||||
<p>{{'webPoliciesDeprecationWarning' | i18n}}</p>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
(click)="goToEnterprisePortal()">{{'businessPortal' | i18n}}</button>
|
||||
</app-callout>
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{'policies' | i18n}}</h1>
|
||||
</div>
|
||||
@@ -8,7 +13,7 @@
|
||||
<table class="table table-hover table-list" *ngIf="!loading">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of policies">
|
||||
<td>
|
||||
<td *ngIf="p.display">
|
||||
<a href="#" appStopClick (click)="edit(p)">{{p.name}}</a>
|
||||
<span class="badge badge-success" *ngIf="p.enabled">{{'enabled' | i18n}}</span>
|
||||
<small class="text-muted d-block">{{p.description}}</small>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import { PolicyType } from 'jslib/enums/policyType';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions';
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
@@ -34,6 +35,12 @@ export class PoliciesComponent implements OnInit {
|
||||
organizationId: string;
|
||||
policies: any[];
|
||||
|
||||
// Remove when removing deprecation warning
|
||||
enterpriseTokenPromise: Promise<any>;
|
||||
userCanAccessBusinessPortal = false;
|
||||
|
||||
private enterpriseUrl: string;
|
||||
|
||||
private modal: ModalComponent = null;
|
||||
private orgPolicies: PolicyResponse[];
|
||||
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||
@@ -41,48 +48,116 @@ export class PoliciesComponent implements OnInit {
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||
private router: Router) {
|
||||
this.policies = [
|
||||
{
|
||||
name: i18nService.t('twoStepLogin'),
|
||||
description: i18nService.t('twoStepLoginPolicyDesc'),
|
||||
type: PolicyType.TwoFactorAuthentication,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
name: i18nService.t('masterPass'),
|
||||
description: i18nService.t('masterPassPolicyDesc'),
|
||||
type: PolicyType.MasterPassword,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
name: i18nService.t('passwordGenerator'),
|
||||
description: i18nService.t('passwordGeneratorPolicyDesc'),
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
private router: Router, private environmentService: EnvironmentService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (organization == null || !organization.usePolicies) {
|
||||
this.router.navigate(['/organizations', this.organizationId]);
|
||||
return;
|
||||
}
|
||||
this.userCanAccessBusinessPortal = organization.canAccessBusinessPortal;
|
||||
this.policies = [
|
||||
{
|
||||
name: this.i18nService.t('twoStepLogin'),
|
||||
description: this.i18nService.t('twoStepLoginPolicyDesc'),
|
||||
type: PolicyType.TwoFactorAuthentication,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('masterPass'),
|
||||
description: this.i18nService.t('masterPassPolicyDesc'),
|
||||
type: PolicyType.MasterPassword,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('passwordGenerator'),
|
||||
description: this.i18nService.t('passwordGeneratorPolicyDesc'),
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('singleOrg'),
|
||||
description: this.i18nService.t('singleOrgDesc'),
|
||||
type: PolicyType.SingleOrg,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('requireSso'),
|
||||
description: this.i18nService.t('requireSsoPolicyDesc'),
|
||||
type: PolicyType.RequireSso,
|
||||
enabled: false,
|
||||
display: organization.useSso,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('personalOwnership'),
|
||||
description: this.i18nService.t('personalOwnershipPolicyDesc'),
|
||||
type: PolicyType.PersonalOwnership,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('disableSend'),
|
||||
description: this.i18nService.t('disableSendPolicyDesc'),
|
||||
type: PolicyType.DisableSend,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t('sendOptions'),
|
||||
description: this.i18nService.t('sendOptionsPolicyDesc'),
|
||||
type: PolicyType.SendOptions,
|
||||
enabled: false,
|
||||
display: true,
|
||||
},
|
||||
];
|
||||
await this.load();
|
||||
|
||||
// Handle policies component launch from Event message
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
if (qParams.policyId != null) {
|
||||
const policyIdFromEvents: string = qParams.policyId;
|
||||
for (const orgPolicy of this.orgPolicies) {
|
||||
if (orgPolicy.id === policyIdFromEvents) {
|
||||
for (let i = 0; i < this.policies.length; i++) {
|
||||
if (this.policies[i].type === orgPolicy.type) {
|
||||
this.edit(this.policies[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queryParamsSub != null) {
|
||||
queryParamsSub.unsubscribe();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove when removing deprecation warning
|
||||
this.enterpriseUrl = 'https://portal.bitwarden.com';
|
||||
if (this.environmentService.enterpriseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.enterpriseUrl;
|
||||
} else if (this.environmentService.baseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.baseUrl + '/portal';
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
const response = await this.apiService.getPolicies(this.organizationId);
|
||||
this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.orgPolicies.forEach((op) => {
|
||||
this.orgPolicies.forEach(op => {
|
||||
this.policiesEnabledMap.set(op.type, op.enabled);
|
||||
});
|
||||
this.policies.forEach((p) => {
|
||||
this.policies.forEach(p => {
|
||||
p.enabled = this.policiesEnabledMap.has(p.type) && this.policiesEnabledMap.get(p.type);
|
||||
});
|
||||
this.loading = false;
|
||||
@@ -102,6 +177,7 @@ export class PoliciesComponent implements OnInit {
|
||||
childComponent.description = p.description;
|
||||
childComponent.type = p.type;
|
||||
childComponent.organizationId = this.organizationId;
|
||||
childComponent.policiesEnabledMap = this.policiesEnabledMap;
|
||||
childComponent.onSavedPolicy.subscribe(() => {
|
||||
this.modal.close();
|
||||
this.load();
|
||||
@@ -111,4 +187,22 @@ export class PoliciesComponent implements OnInit {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Remove when removing deprecation warning
|
||||
async goToEnterprisePortal() {
|
||||
if (this.enterpriseTokenPromise != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.enterpriseTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||
const token = await this.enterpriseTokenPromise;
|
||||
if (token != null) {
|
||||
const userId = await this.userService.getUserId();
|
||||
this.platformUtilsService.launchUri(this.enterpriseUrl + '/login?userId=' + userId +
|
||||
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organizationId);
|
||||
}
|
||||
} catch { }
|
||||
this.enterpriseTokenPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,32 @@
|
||||
title="{{'warning' | i18n}}" icon="fa-warning">
|
||||
{{'twoStepLoginPolicyWarning' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="warning" *ngIf="type === policyType.SingleOrg" title="{{'warning' | i18n}}"
|
||||
icon="fa-warning">
|
||||
{{'singleOrgPolicyWarning' | i18n}}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="type === policyType.RequireSso">
|
||||
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
|
||||
{{'requireSsoPolicyReq' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="warning">
|
||||
{{'requireSsoExemption' | i18n}}
|
||||
</app-callout>
|
||||
</ng-container>
|
||||
<app-callout type="warning" *ngIf="type === policyType.PersonalOwnership">
|
||||
{{'personalOwnershipExemption' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="warning" *ngIf="type === policyType.DisableSend">
|
||||
{{'disableSendExemption' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="warning" *ngIf="type === policyType.SendOptions">
|
||||
{{'sendOptionsExemption' | i18n}}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
|
||||
name="Enabled">
|
||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
|
||||
<label class="form-check-label" for="enabled">{{checkboxDesc}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="type === policyType.MasterPassword">
|
||||
@@ -129,6 +150,14 @@
|
||||
<label class="form-check-label" for="passGenIncludeNumber">{{'includeNumber' | i18n}}</label>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === policyType.SendOptions">
|
||||
<h3 class="mt-4">{{'options' | i18n}}</h3>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sendDisableHideEmail" [(ngModel)]="sendDisableHideEmail"
|
||||
name="SendDisableHideEmail">
|
||||
<label class="form-check-label" for="sendDisableHideEmail">{{'disableHideEmail' | i18n}}</label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -27,6 +26,7 @@ export class PolicyEditComponent implements OnInit {
|
||||
@Input() description: string;
|
||||
@Input() type: PolicyType;
|
||||
@Input() organizationId: string;
|
||||
@Input() policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||
@Output() onSavedPolicy = new EventEmitter();
|
||||
|
||||
policyType = PolicyType;
|
||||
@@ -37,7 +37,6 @@ export class PolicyEditComponent implements OnInit {
|
||||
defaultTypes: any[];
|
||||
|
||||
// Master password
|
||||
|
||||
masterPassMinComplexity?: number = null;
|
||||
masterPassMinLength?: number;
|
||||
masterPassRequireUpper?: number;
|
||||
@@ -46,7 +45,6 @@ export class PolicyEditComponent implements OnInit {
|
||||
masterPassRequireSpecial?: number;
|
||||
|
||||
// Password generator
|
||||
|
||||
passGenDefaultType?: string;
|
||||
passGenMinLength?: number;
|
||||
passGenUseUpper?: boolean;
|
||||
@@ -59,10 +57,13 @@ export class PolicyEditComponent implements OnInit {
|
||||
passGenCapitalize?: boolean;
|
||||
passGenIncludeNumber?: boolean;
|
||||
|
||||
// Send options
|
||||
sendDisableHideEmail?: boolean;
|
||||
|
||||
private policy: PolicyResponse;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) {
|
||||
private toasterService: ToasterService) {
|
||||
this.passwordScores = [
|
||||
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
|
||||
{ name: i18nService.t('weak') + ' (0)', value: 0 },
|
||||
@@ -112,6 +113,9 @@ export class PolicyEditComponent implements OnInit {
|
||||
this.masterPassRequireNumbers = this.policy.data.requireNumbers;
|
||||
this.masterPassRequireSpecial = this.policy.data.requireSpecial;
|
||||
break;
|
||||
case PolicyType.SendOptions:
|
||||
this.sendDisableHideEmail = this.policy.data.disableHideEmail;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -127,45 +131,89 @@ export class PolicyEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const request = new PolicyRequest();
|
||||
request.enabled = this.enabled;
|
||||
request.type = this.type;
|
||||
request.data = null;
|
||||
switch (this.type) {
|
||||
case PolicyType.PasswordGenerator:
|
||||
request.data = {
|
||||
defaultType: this.passGenDefaultType,
|
||||
minLength: this.passGenMinLength || null,
|
||||
useUpper: this.passGenUseUpper,
|
||||
useLower: this.passGenUseLower,
|
||||
useNumbers: this.passGenUseNumbers,
|
||||
useSpecial: this.passGenUseSpecial,
|
||||
minNumbers: this.passGenMinNumbers || null,
|
||||
minSpecial: this.passGenMinSpecial || null,
|
||||
minNumberWords: this.passGenMinNumberWords || null,
|
||||
capitalize: this.passGenCapitalize,
|
||||
includeNumber: this.passGenIncludeNumber,
|
||||
};
|
||||
break;
|
||||
case PolicyType.MasterPassword:
|
||||
request.data = {
|
||||
minComplexity: this.masterPassMinComplexity || null,
|
||||
minLength: this.masterPassMinLength || null,
|
||||
requireUpper: this.masterPassRequireUpper,
|
||||
requireLower: this.masterPassRequireLower,
|
||||
requireNumbers: this.masterPassRequireNumbers,
|
||||
requireSpecial: this.masterPassRequireSpecial,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (this.preValidate()) {
|
||||
const request = new PolicyRequest();
|
||||
request.enabled = this.enabled;
|
||||
request.type = this.type;
|
||||
request.data = null;
|
||||
switch (this.type) {
|
||||
case PolicyType.PasswordGenerator:
|
||||
request.data = {
|
||||
defaultType: this.passGenDefaultType,
|
||||
minLength: this.passGenMinLength || null,
|
||||
useUpper: this.passGenUseUpper,
|
||||
useLower: this.passGenUseLower,
|
||||
useNumbers: this.passGenUseNumbers,
|
||||
useSpecial: this.passGenUseSpecial,
|
||||
minNumbers: this.passGenMinNumbers || null,
|
||||
minSpecial: this.passGenMinSpecial || null,
|
||||
minNumberWords: this.passGenMinNumberWords || null,
|
||||
capitalize: this.passGenCapitalize,
|
||||
includeNumber: this.passGenIncludeNumber,
|
||||
};
|
||||
break;
|
||||
case PolicyType.MasterPassword:
|
||||
request.data = {
|
||||
minComplexity: this.masterPassMinComplexity || null,
|
||||
minLength: this.masterPassMinLength || null,
|
||||
requireUpper: this.masterPassRequireUpper,
|
||||
requireLower: this.masterPassRequireLower,
|
||||
requireNumbers: this.masterPassRequireNumbers,
|
||||
requireSpecial: this.masterPassRequireSpecial,
|
||||
};
|
||||
break;
|
||||
case PolicyType.SendOptions:
|
||||
request.data = {
|
||||
disableHideEmail: this.sendDisableHideEmail,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
try {
|
||||
this.formPromise = this.apiService.putPolicy(this.organizationId, this.type, request);
|
||||
await this.formPromise;
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
|
||||
this.onSavedPolicy.emit();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
get checkboxDesc(): string {
|
||||
return this.type === PolicyType.PersonalOwnership ? this.i18nService.t('personalOwnershipCheckboxDesc') :
|
||||
this.i18nService.t('enabled');
|
||||
}
|
||||
|
||||
private preValidate(): boolean {
|
||||
switch (this.type) {
|
||||
case PolicyType.RequireSso:
|
||||
// Don't need prevalidation checks if submitting to disable
|
||||
if (!this.enabled) {
|
||||
return true;
|
||||
}
|
||||
// Have SingleOrg policy enabled?
|
||||
if (!(this.policiesEnabledMap.has(PolicyType.SingleOrg)
|
||||
&& this.policiesEnabledMap.get(PolicyType.SingleOrg))) {
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('requireSsoPolicyReqError'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case PolicyType.SingleOrg:
|
||||
// Don't need prevalidation checks if submitting to enable
|
||||
if (this.enabled) {
|
||||
return true;
|
||||
}
|
||||
// If RequireSso Policy is enabled prevent submittal
|
||||
if (this.policiesEnabledMap.has(PolicyType.RequireSso)
|
||||
&& this.policiesEnabledMap.get(PolicyType.RequireSso)) {
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('disableRequireSsoError'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
this.formPromise = this.apiService.putPolicy(this.organizationId, this.type, request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Edited Policy' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
|
||||
this.onSavedPolicy.emit();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,136 @@
|
||||
<small>{{'ownerDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input class="form-check-input" type="radio" name="userType" id="userTypeCustom"
|
||||
[value]="organizationUserType.Custom" [(ngModel)]="type">
|
||||
<label class="form-check-label" for="userTypeCustom">
|
||||
{{'custom' | i18n}}
|
||||
<small>{{'customDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<ng-container *ngIf="customUserTypeSelected">
|
||||
<h3 class="mt-4 d-flex">
|
||||
{{'permissions' | i18n}}
|
||||
</h3>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="font-weight-bold mb-0">Manager Permissions</label>
|
||||
<hr class="my-0 mr-2" />
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageAssignedCollections"
|
||||
id="manageAssignedCollections"
|
||||
[(ngModel)]="permissions.manageAssignedCollections">
|
||||
<label class="form-check-label font-weight-normal"
|
||||
for="manageAssignedCollections">
|
||||
{{'manageAssignedCollections' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="font-weight-bold mb-0">Admin Permissions</label>
|
||||
<hr class="my-0 mr-2" />
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessBusinessPortal"
|
||||
id="accessBusinessPortal" [(ngModel)]="permissions.accessBusinessPortal">
|
||||
<label class="form-check-label font-weight-normal" for="accessBusinessPortal">
|
||||
{{'accessBusinessPortal' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessEventLogs"
|
||||
id="accessEventLogs" [(ngModel)]="permissions.accessEventLogs">
|
||||
<label class="form-check-label font-weight-normal" for="accessEventLogs">
|
||||
{{'accessEventLogs' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessImportExport"
|
||||
id="accessImportExport" [(ngModel)]="permissions.accessImportExport">
|
||||
<label class="form-check-label font-weight-normal" for="accessImportExport">
|
||||
{{'accessImportExport' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessReports"
|
||||
id="accessReports" [(ngModel)]="permissions.accessReports">
|
||||
<label class="form-check-label font-weight-normal" for="accessReports">
|
||||
{{'accessReports' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageAllCollections"
|
||||
id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections">
|
||||
<label class="form-check-label font-weight-normal" for="manageAllCollections">
|
||||
{{'manageAllCollections' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageGroups"
|
||||
id="manageGroups" [(ngModel)]="permissions.manageGroups">
|
||||
<label class="form-check-label font-weight-normal" for="manageGroups">
|
||||
{{'manageGroups' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageSso"
|
||||
id="managePolicies" [(ngModel)]="permissions.manageSso">
|
||||
<label class="form-check-label font-weight-normal" for="manageSso">
|
||||
{{'manageSso' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="managePolicies"
|
||||
id="managePolicies" [(ngModel)]="permissions.managePolicies">
|
||||
<label class="form-check-label font-weight-normal" for="managePolicies">
|
||||
{{'managePolicies' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageUsers"
|
||||
id="manageUsers" [(ngModel)]="permissions.manageUsers">
|
||||
<label class="form-check-label font-weight-normal" for="manageUsers">
|
||||
{{'manageUsers' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageResetPassword"
|
||||
id="manageResetPassword" [(ngModel)]="permissions.manageResetPassword">
|
||||
<label class="form-check-label font-weight-normal" for="manageResetPassword">
|
||||
{{'manageResetPassword' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-4 d-flex">
|
||||
<div class="mb-2">
|
||||
<div class="mb-3">
|
||||
{{'accessControl' | i18n}}
|
||||
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
|
||||
href="https://bitwarden.com/help/article/user-types-access-control/#access-control">
|
||||
@@ -136,8 +264,9 @@
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{'cancel' | i18n}}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
@@ -23,6 +22,7 @@ import { CollectionDetailsResponse } from 'jslib/models/response/collectionRespo
|
||||
import { CollectionView } from 'jslib/models/view/collectionView';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { PermissionsApi } from 'jslib/models/api/permissionsApi';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-add-edit',
|
||||
@@ -40,15 +40,21 @@ export class UserAddEditComponent implements OnInit {
|
||||
title: string;
|
||||
emails: string;
|
||||
type: OrganizationUserType = OrganizationUserType.User;
|
||||
permissions = new PermissionsApi();
|
||||
showCustom = false;
|
||||
access: 'all' | 'selected' = 'selected';
|
||||
collections: CollectionView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
organizationUserType = OrganizationUserType;
|
||||
|
||||
get customUserTypeSelected(): boolean {
|
||||
return this.type === OrganizationUserType.Custom;
|
||||
}
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
|
||||
private toasterService: ToasterService, private collectionService: CollectionService,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.organizationUserId != null;
|
||||
@@ -61,9 +67,12 @@ export class UserAddEditComponent implements OnInit {
|
||||
const user = await this.apiService.getOrganizationUser(this.organizationId, this.organizationUserId);
|
||||
this.access = user.accessAll ? 'all' : 'selected';
|
||||
this.type = user.type;
|
||||
if (user.type === OrganizationUserType.Custom) {
|
||||
this.permissions = user.permissions;
|
||||
}
|
||||
if (user.collections != null && this.collections != null) {
|
||||
user.collections.forEach((s) => {
|
||||
const collection = this.collections.filter((c) => c.id === s.id);
|
||||
user.collections.forEach(s => {
|
||||
const collection = this.collections.filter(c => c.id === s.id);
|
||||
if (collection != null && collection.length > 0) {
|
||||
(collection[0] as any).checked = true;
|
||||
collection[0].readOnly = s.readOnly;
|
||||
@@ -81,7 +90,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
|
||||
async loadCollections() {
|
||||
const response = await this.apiService.getCollections(this.organizationId);
|
||||
const collections = response.data.map((r) =>
|
||||
const collections = response.data.map(r =>
|
||||
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||
this.collections = await this.collectionService.decryptMany(collections);
|
||||
}
|
||||
@@ -94,14 +103,51 @@ export class UserAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.collections.forEach((c) => this.check(c, select));
|
||||
this.collections.forEach(c => this.check(c, select));
|
||||
}
|
||||
|
||||
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
|
||||
p.accessBusinessPortal = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessBusinessPortal;
|
||||
p.accessEventLogs = this.permissions.accessEventLogs = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessEventLogs;
|
||||
p.accessImportExport = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessImportExport;
|
||||
p.accessReports = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessReports;
|
||||
p.manageAllCollections = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageAllCollections;
|
||||
p.manageAssignedCollections = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageAssignedCollections;
|
||||
p.manageGroups = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageGroups;
|
||||
p.manageSso = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageSso;
|
||||
p.managePolicies = clearPermissions ?
|
||||
false :
|
||||
this.permissions.managePolicies;
|
||||
p.manageUsers = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageUsers;
|
||||
p.manageResetPassword = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageResetPassword;
|
||||
return p;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
let collections: SelectionReadOnlyRequest[] = null;
|
||||
if (this.access !== 'all') {
|
||||
collections = this.collections.filter((c) => (c as any).checked)
|
||||
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
|
||||
collections = this.collections.filter(c => (c as any).checked)
|
||||
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -110,6 +156,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
request.accessAll = this.access === 'all';
|
||||
request.type = this.type;
|
||||
request.collections = collections;
|
||||
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
|
||||
this.formPromise = this.apiService.putOrganizationUser(this.organizationId, this.organizationUserId,
|
||||
request);
|
||||
} else {
|
||||
@@ -117,11 +164,11 @@ export class UserAddEditComponent implements OnInit {
|
||||
request.emails = this.emails.trim().split(/\s*,\s*/);
|
||||
request.accessAll = this.access === 'all';
|
||||
request.type = this.type;
|
||||
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
|
||||
request.collections = collections;
|
||||
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
|
||||
}
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited User' : 'Invited User' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
|
||||
this.onSavedUser.emit();
|
||||
@@ -143,9 +190,9 @@ export class UserAddEditComponent implements OnInit {
|
||||
try {
|
||||
this.deletePromise = this.apiService.deleteOrganizationUser(this.organizationId, this.organizationUserId);
|
||||
await this.deletePromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted User' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name));
|
||||
this.onDeletedUser.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="confirmUserTitle">
|
||||
{{'confirmUser' | i18n}}
|
||||
|
||||
@@ -6,18 +6,12 @@ import {
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
|
||||
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
@Component({
|
||||
@@ -34,13 +28,11 @@ export class UserConfirmComponent implements OnInit {
|
||||
dontAskAgain = false;
|
||||
loading = true;
|
||||
fingerprint: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private publicKey: Uint8Array = null;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private storageService: StorageService) { }
|
||||
constructor(private apiService: ApiService, private cryptoService: CryptoService,
|
||||
private storageService: StorageService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
@@ -65,20 +57,6 @@ export class UserConfirmComponent implements OnInit {
|
||||
await this.storageService.save(ConstantsService.autoConfirmFingerprints, true);
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.doConfirmation();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Confirmed User' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', this.name));
|
||||
this.onConfirmedUser.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
private async doConfirmation() {
|
||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, this.publicKey.buffer);
|
||||
const request = new OrganizationUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
await this.apiService.postOrganizationUserConfirm(this.organizationId, this.organizationUserId, request);
|
||||
this.onConfirmedUser.emit(this.publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -32,11 +31,11 @@ export class UserGroupsComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private toasterService: ToasterService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
const groupsResponse = await this.apiService.getGroups(this.organizationId);
|
||||
const groups = groupsResponse.data.map((r) => r);
|
||||
const groups = groupsResponse.data.map(r => r);
|
||||
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
|
||||
this.groups = groups;
|
||||
|
||||
@@ -44,8 +43,8 @@ export class UserGroupsComponent implements OnInit {
|
||||
const userGroups = await this.apiService.getOrganizationUserGroups(
|
||||
this.organizationId, this.organizationUserId);
|
||||
if (userGroups != null && this.groups != null) {
|
||||
userGroups.forEach((ug) => {
|
||||
const group = this.groups.filter((g) => g.id === ug);
|
||||
userGroups.forEach(ug => {
|
||||
const group = this.groups.filter(g => g.id === ug);
|
||||
if (group != null && group.length > 0) {
|
||||
(group[0] as any).checked = true;
|
||||
}
|
||||
@@ -64,18 +63,17 @@ export class UserGroupsComponent implements OnInit {
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.groups.forEach((g) => this.check(g, select));
|
||||
this.groups.forEach(g => this.check(g, select));
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const request = new OrganizationUserUpdateGroupsRequest();
|
||||
request.groupIds = this.groups.filter((g) => (g as any).checked).map((g) => g.id);
|
||||
request.groupIds = this.groups.filter(g => (g as any).checked).map(g => g.id);
|
||||
|
||||
try {
|
||||
this.formPromise = this.apiService.putOrganizationUserGroups(this.organizationId, this.organizationUserId,
|
||||
request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Edited User Groups' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('editedGroupsForUser', this.name));
|
||||
this.onSavedUser.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -18,11 +17,10 @@ import { OrganizationUpdateRequest } from 'jslib/models/request/organizationUpda
|
||||
import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
|
||||
|
||||
import { ModalComponent } from '../../modal.component';
|
||||
import { ApiKeyComponent } from '../../settings/api-key.component';
|
||||
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
|
||||
import { TaxInfoComponent } from '../../settings/tax-info.component';
|
||||
import { ApiKeyComponent } from './api-key.component';
|
||||
import { DeleteOrganizationComponent } from './delete-organization.component';
|
||||
import { RotateApiKeyComponent } from './rotate-api-key.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-account',
|
||||
@@ -47,13 +45,12 @@ export class AccountComponent {
|
||||
|
||||
constructor(private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private route: ActivatedRoute, private syncService: SyncService,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
private toasterService: ToasterService, private route: ActivatedRoute,
|
||||
private syncService: SyncService, private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
try {
|
||||
this.org = await this.apiService.getOrganization(this.organizationId);
|
||||
@@ -74,7 +71,6 @@ export class AccountComponent {
|
||||
return this.syncService.fullSync(true);
|
||||
});
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Updated Organization Settings' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated'));
|
||||
} catch { }
|
||||
}
|
||||
@@ -82,7 +78,6 @@ export class AccountComponent {
|
||||
async submitTaxInfo() {
|
||||
this.taxFormPromise = this.taxInfo.submitTaxInfo();
|
||||
await this.taxFormPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Updated Organization Tax Info' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('taxInfoUpdated'));
|
||||
}
|
||||
|
||||
@@ -125,7 +120,14 @@ export class AccountComponent {
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.apiKeyModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.apiKeyModalRef);
|
||||
childComponent.organizationId = this.organizationId;
|
||||
childComponent.keyType = 'organization';
|
||||
childComponent.entityId = this.organizationId;
|
||||
childComponent.postKey = this.apiService.postOrganizationApiKey.bind(this.apiService);
|
||||
childComponent.scope = 'api.organization';
|
||||
childComponent.grantType = 'client_credentials';
|
||||
childComponent.apiKeyTitle = 'apiKey';
|
||||
childComponent.apiKeyWarning = 'apiKeyWarning';
|
||||
childComponent.apiKeyDescription = 'apiKeyDesc';
|
||||
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
@@ -139,8 +141,16 @@ export class AccountComponent {
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.rotateApiKeyModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<RotateApiKeyComponent>(RotateApiKeyComponent, this.rotateApiKeyModalRef);
|
||||
childComponent.organizationId = this.organizationId;
|
||||
const childComponent = this.modal.show<ApiKeyComponent>(ApiKeyComponent, this.rotateApiKeyModalRef);
|
||||
childComponent.keyType = 'organization';
|
||||
childComponent.isRotation = true;
|
||||
childComponent.entityId = this.organizationId;
|
||||
childComponent.postKey = this.apiService.postOrganizationRotateApiKey.bind(this.apiService);
|
||||
childComponent.scope = 'api.organization';
|
||||
childComponent.grantType = 'client_credentials';
|
||||
childComponent.apiKeyTitle = 'apiKey';
|
||||
childComponent.apiKeyWarning = 'apiKeyWarning';
|
||||
childComponent.apiKeyDescription = 'apiKeyRotateDesc';
|
||||
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -39,8 +38,8 @@ export class AdjustSeatsComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
private toasterService: ToasterService, private router: Router,
|
||||
private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -63,7 +62,6 @@ export class AdjustSeatsComponent {
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Seats' : 'Removed Seats' });
|
||||
this.onAdjusted.emit(this.seatAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
|
||||
@@ -28,7 +28,6 @@ export class ChangePlanComponent {
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
this.platformUtilsService.eventTrack('Changed Plan');
|
||||
this.onChanged.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
@@ -21,8 +20,8 @@ export class DeleteOrganizationComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private router: Router) { }
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private router: Router) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
@@ -36,7 +35,6 @@ export class DeleteOrganizationComponent {
|
||||
try {
|
||||
this.formPromise = this.apiService.deleteOrganization(this.organizationId, request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Organization' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'),
|
||||
this.i18nService.t('organizationDeletedDesc'));
|
||||
this.router.navigate(['/']);
|
||||
|
||||
@@ -32,7 +32,6 @@ export class DownloadLicenseComponent {
|
||||
const license = await this.formPromise;
|
||||
const licenseString = JSON.stringify(license, null, 2);
|
||||
this.platformUtilsService.saveFile(window, licenseString, null, 'bitwarden_organization_license.json');
|
||||
this.platformUtilsService.eventTrack('Downloaded License');
|
||||
this.onDownloaded.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -18,14 +17,13 @@ import { UserBillingComponent } from '../../settings/user-billing.component';
|
||||
templateUrl: '../../settings/user-billing.component.html',
|
||||
})
|
||||
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
|
||||
constructor(apiService: ApiService, i18nService: I18nService,
|
||||
analytics: Angulartics2, toasterService: ToasterService,
|
||||
constructor(apiService: ApiService, i18nService: I18nService, toasterService: ToasterService,
|
||||
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService) {
|
||||
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
|
||||
super(apiService, i18nService, toasterService, platformUtilsService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { OrganizationSubscriptionResponse } from 'jslib/models/response/organizationSubscriptionResponse';
|
||||
|
||||
@@ -38,14 +37,13 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
||||
reinstatePromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private messagingService: MessagingService,
|
||||
private route: ActivatedRoute) {
|
||||
private i18nService: I18nService, private toasterService: ToasterService,
|
||||
private messagingService: MessagingService, private route: ActivatedRoute) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
@@ -75,7 +73,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
||||
try {
|
||||
this.reinstatePromise = this.apiService.postOrganizationReinstate(this.organizationId);
|
||||
await this.reinstatePromise;
|
||||
this.analytics.eventTrack.next({ action: 'Reinstated Plan' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('reinstated'));
|
||||
this.load();
|
||||
} catch { }
|
||||
@@ -95,7 +92,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
||||
try {
|
||||
this.cancelPromise = this.apiService.postOrganizationCancel(this.organizationId);
|
||||
await this.cancelPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Canceled Plan' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('canceledSubscription'));
|
||||
this.load();
|
||||
} catch { }
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="rotateKeyTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="rotateKeyTitle">{{'rotateApiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'apiKeyRotateDesc' | i18n}}</p>
|
||||
<ng-container *ngIf="!clientSecret">
|
||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
||||
[(ngModel)]="masterPassword" required appAutofocus appInputVerbatim>
|
||||
</ng-container>
|
||||
<app-callout type="warning" *ngIf="clientSecret">{{'apiKeyWarning' | i18n}}</app-callout>
|
||||
<app-callout type="info" title="{{'oauth2ClientCredentials' | i18n}}" icon="fa-key"
|
||||
*ngIf="clientSecret">
|
||||
<p class="mb-1">
|
||||
<strong>client_id:</strong><br>
|
||||
<code>{{clientId}}</code>
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<strong>client_secret:</strong><br>
|
||||
<code>{{clientSecret}}</code>
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<strong>scope:</strong><br>
|
||||
<code>{{scope}}</code>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<strong>grant_type:</strong><br>
|
||||
<code>client_credentials</code>
|
||||
</p>
|
||||
</app-callout>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
||||
*ngIf="!clientSecret">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'rotateApiKey' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
|
||||
|
||||
import { ApiKeyResponse } from 'jslib/models/response/apiKeyResponse';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rotate-api-key',
|
||||
templateUrl: 'rotate-api-key.component.html',
|
||||
})
|
||||
export class RotateApiKeyComponent {
|
||||
organizationId: string;
|
||||
|
||||
masterPassword: string;
|
||||
formPromise: Promise<ApiKeyResponse>;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
scope: string;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private router: Router) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new PasswordVerificationRequest();
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||
try {
|
||||
this.formPromise = this.apiService.postOrganizationRotateApiKey(this.organizationId, request);
|
||||
const response = await this.formPromise;
|
||||
this.clientSecret = response.apiKey;
|
||||
this.clientId = 'organization.' + this.organizationId;
|
||||
this.scope = 'api.organization';
|
||||
this.analytics.eventTrack.next({ action: 'Rotated Organization API Key' });
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class SettingsComponent {
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.params.subscribe(async params => {
|
||||
this.selfHosted = await this.platformUtilsService.isSelfHost();
|
||||
const organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.access2fa = organization.use2fa;
|
||||
|
||||
@@ -26,7 +26,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
await super.ngOnInit();
|
||||
});
|
||||
|
||||
@@ -16,8 +16,6 @@ import { EventType } from 'jslib/enums/eventType';
|
||||
templateUrl: '../../tools/export.component.html',
|
||||
})
|
||||
export class ExportComponent extends BaseExportComponent {
|
||||
organizationId: string;
|
||||
|
||||
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
||||
eventService: EventService, private route: ActivatedRoute) {
|
||||
@@ -25,7 +23,7 @@ export class ExportComponent extends BaseExportComponent {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent,
|
||||
} from '../../tools/exposed-passwords-report.component';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
@Component({
|
||||
@@ -20,6 +21,8 @@ import { CipherView } from 'jslib/models/view/cipherView';
|
||||
templateUrl: '../../tools/exposed-passwords-report.component.html',
|
||||
})
|
||||
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, auditService: AuditService,
|
||||
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
|
||||
userService: UserService, private route: ActivatedRoute) {
|
||||
@@ -27,8 +30,9 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@@ -36,4 +40,8 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import {
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { ImportService } from 'jslib/abstractions/import.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { ImportComponent as BaseImportComponent } from '../../tools/import.component';
|
||||
|
||||
@@ -17,17 +18,32 @@ import { ImportComponent as BaseImportComponent } from '../../tools/import.compo
|
||||
templateUrl: '../../tools/import.component.html',
|
||||
})
|
||||
export class ImportComponent extends BaseImportComponent {
|
||||
constructor(i18nService: I18nService, analytics: Angulartics2,
|
||||
toasterService: ToasterService, importService: ImportService,
|
||||
router: Router, private route: ActivatedRoute) {
|
||||
super(i18nService, analytics, toasterService, importService, router);
|
||||
organizationName: string;
|
||||
|
||||
constructor(i18nService: I18nService, toasterService: ToasterService,
|
||||
importService: ImportService, router: Router, private route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private userService: UserService) {
|
||||
super(i18nService, toasterService, importService, router, platformUtilsService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organizationId = params.organizationId;
|
||||
this.successNavigate = ['organizations', this.organizationId, 'vault'];
|
||||
super.ngOnInit();
|
||||
});
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
this.organizationName = organization.name;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('importWarning', this.organizationName),
|
||||
this.i18nService.t('warning'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
super.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorRepor
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
await super.ngOnInit();
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import {
|
||||
@@ -19,6 +21,8 @@ import {
|
||||
templateUrl: '../../tools/reused-passwords-report.component.html',
|
||||
})
|
||||
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, componentFactoryResolver: ComponentFactoryResolver,
|
||||
messagingService: MessagingService, userService: UserService,
|
||||
private route: ActivatedRoute) {
|
||||
@@ -26,8 +30,9 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
await super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@@ -35,4 +40,8 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">{{'tools' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{'importData' | i18n}}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{'exportVault' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex">
|
||||
{{'reports' | i18n}}
|
||||
<div class="ml-auto">
|
||||
<a href="#" appStopClick class="badge badge-primary" *ngIf="!accessReports"
|
||||
(click)="upgradeOrganization()">
|
||||
{{'upgrade' | i18n}}
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4" *ngIf="organization.canAccessImportExport">
|
||||
<div class="card-header">{{'tools' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
{{'importData' | i18n}}
|
||||
</a>
|
||||
<a routerLink="export" class="list-group-item" routerLinkActive="active">
|
||||
{{'exportVault' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'exposedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'reusedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'weakPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="unsecured-websites-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'unsecuredWebsitesReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'inactive2faReport' | i18n}}
|
||||
</a>
|
||||
<div class="card" *ngIf="organization.canAccessReports">
|
||||
<div class="card-header d-flex">
|
||||
{{'reports' | i18n}}
|
||||
<div class="ml-auto">
|
||||
<a href="#" appStopClick class="badge badge-primary" *ngIf="!accessReports"
|
||||
(click)="upgradeOrganization()">
|
||||
{{'upgrade' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="exposed-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'exposedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'reusedPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'weakPasswordsReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="unsecured-websites-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'unsecuredWebsitesReport' | i18n}}
|
||||
</a>
|
||||
<a routerLink="inactive-two-factor-report" class="list-group-item" routerLinkActive="active">
|
||||
{{'inactive2faReport' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -13,16 +13,18 @@ import { UserService } from 'jslib/abstractions/user.service';
|
||||
export class ToolsComponent {
|
||||
organization: Organization;
|
||||
accessReports = false;
|
||||
loading = true;
|
||||
|
||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||
private messagingService: MessagingService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
|
||||
// since all paid plans include useTotp
|
||||
this.accessReports = this.organization.useTotp;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesRepor
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
await super.ngOnInit();
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import {
|
||||
@@ -20,6 +22,8 @@ import {
|
||||
templateUrl: '../../tools/weak-passwords-report.component.html',
|
||||
})
|
||||
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, passwordGenerationService: PasswordGenerationService,
|
||||
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
|
||||
userService: UserService, private route: ActivatedRoute) {
|
||||
@@ -27,8 +31,9 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
await super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@@ -36,4 +41,8 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
@@ -36,16 +37,16 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
userService: UserService, collectionService: CollectionService,
|
||||
totpService: TotpService, passwordGenerationService: PasswordGenerationService,
|
||||
private apiService: ApiService, messagingService: MessagingService,
|
||||
eventService: EventService) {
|
||||
eventService: EventService, policyService: PolicyService) {
|
||||
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
|
||||
userService, collectionService, totpService, passwordGenerationService, messagingService,
|
||||
eventService);
|
||||
eventService, policyService);
|
||||
}
|
||||
|
||||
protected allowOwnershipAssignment() {
|
||||
if (this.ownershipOptions != null && this.ownershipOptions.length > 1) {
|
||||
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
|
||||
if (this.organization != null) {
|
||||
return this.cloneMode && this.organization.isAdmin;
|
||||
return this.cloneMode && this.organization.canManageAllCollections;
|
||||
} else {
|
||||
return !this.editMode || this.cloneMode;
|
||||
}
|
||||
@@ -54,14 +55,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@@ -71,14 +72,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected encryptCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.encryptCipher();
|
||||
}
|
||||
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
||||
}
|
||||
|
||||
protected async saveCipher(cipher: Cipher) {
|
||||
if (!this.organization.isAdmin || cipher.organizationId == null) {
|
||||
if (!this.organization.canManageAllCollections || cipher.organizationId == null) {
|
||||
return super.saveCipher(cipher);
|
||||
}
|
||||
if (this.editMode && !this.cloneMode) {
|
||||
@@ -91,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected async deleteCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipher();
|
||||
}
|
||||
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||
|
||||
@@ -20,22 +20,23 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from '../../vault/at
|
||||
templateUrl: '../../vault/attachments.component.html',
|
||||
})
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
viewOnly = false;
|
||||
organization: Organization;
|
||||
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
cryptoService: CryptoService, userService: UserService,
|
||||
platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService);
|
||||
platformUtilsService: PlatformUtilsService, apiService: ApiService) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.organization.isAdmin && this.showFixOldAttachments(attachment)) {
|
||||
if (this.organization.canManageAllCollections && this.showFixOldAttachments(attachment)) {
|
||||
await super.reuploadCipherAttachment(attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@@ -43,17 +44,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
}
|
||||
|
||||
protected saveCipherAttachment(file: File) {
|
||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.isAdmin);
|
||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canManageAllCollections);
|
||||
}
|
||||
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipherAttachment(attachmentId);
|
||||
}
|
||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.organization.isAdmin;
|
||||
return attachment.key == null && this.organization.canManageAllCollections;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
@@ -13,6 +12,8 @@ import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { Organization } from 'jslib/models/domain/organization';
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
@@ -31,27 +32,27 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
|
||||
protected allCiphers: CipherView[] = [];
|
||||
|
||||
constructor(searchService: SearchService, analytics: Angulartics2,
|
||||
toasterService: ToasterService, i18nService: I18nService,
|
||||
constructor(searchService: SearchService, toasterService: ToasterService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
|
||||
private apiService: ApiService, eventService: EventService) {
|
||||
super(searchService, analytics, toasterService, i18nService, platformUtilsService,
|
||||
cipherService, eventService);
|
||||
private apiService: ApiService, eventService: EventService, totpService: TotpService, userService: UserService) {
|
||||
super(searchService, toasterService, i18nService, platformUtilsService, cipherService,
|
||||
eventService, totpService, userService);
|
||||
}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await super.load(filter, this.deleted);
|
||||
return;
|
||||
}
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
this.applyFilter(filter);
|
||||
await this.searchService.indexCiphers(this.organization.id, this.allCiphers);
|
||||
await this.applyFilter(filter);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
await super.applyFilter(filter);
|
||||
} else {
|
||||
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
|
||||
@@ -60,40 +61,20 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
if (!this.organization.isAdmin) {
|
||||
return super.search(timeout);
|
||||
}
|
||||
this.searchPending = false;
|
||||
let filteredCiphers = this.allCiphers;
|
||||
|
||||
if (this.searchText == null || this.searchText.trim().length < 2) {
|
||||
this.ciphers = filteredCiphers.filter((c) => {
|
||||
if (c.isDeleted !== this.deleted) {
|
||||
return false;
|
||||
}
|
||||
return this.filter == null || this.filter(c);
|
||||
});
|
||||
} else {
|
||||
if (this.filter != null) {
|
||||
filteredCiphers = filteredCiphers.filter(this.filter);
|
||||
}
|
||||
this.ciphers = this.searchService.searchCiphersBasic(filteredCiphers, this.searchText, this.deleted);
|
||||
}
|
||||
await this.resetPaging();
|
||||
super.search(timeout, this.allCiphers);
|
||||
}
|
||||
|
||||
events(c: CipherView) {
|
||||
this.onEventsClicked.emit(c);
|
||||
}
|
||||
|
||||
protected deleteCipher(id: string) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipher(id, this.deleted);
|
||||
}
|
||||
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
return this.organization.isAdmin && c.hasOldAttachments;
|
||||
return this.organization.canManageAllCollections && c.hasOldAttachments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@@ -36,21 +36,21 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected loadCipherCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCipherCollections();
|
||||
}
|
||||
return this.collectionIds;
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
}
|
||||
|
||||
protected saveCollections() {
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
||||
} else {
|
||||
|
||||
@@ -29,14 +29,14 @@ export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
}
|
||||
|
||||
async loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await super.loadCollections(this.organization.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const collections = await this.apiService.getCollections(this.organization.id);
|
||||
if (collections != null && collections.data != null && collections.data.length) {
|
||||
const collectionDomains = collections.data.map((r) =>
|
||||
const collectionDomains = collections.data.map(r =>
|
||||
new Collection(new CollectionData(r as CollectionDetailsResponse)));
|
||||
this.collections = await this.collectionService.decryptMany(collectionDomains);
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-callout type="warning" *ngIf="deleted" icon="fa-warning">
|
||||
{{trashCleanupWarning}}
|
||||
</app-callout>
|
||||
<app-org-vault-ciphers (onCipherClicked)="editCipher($event)"
|
||||
(onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"
|
||||
(onCollectionsClicked)="editCipherCollections($event)" (onEventsClicked)="viewEvents($event)"
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
@@ -52,6 +53,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
collectionId: string = null;
|
||||
type: CipherType = null;
|
||||
deleted: boolean = false;
|
||||
trashCleanupWarning: string = null;
|
||||
|
||||
modal: ModalComponent = null;
|
||||
|
||||
@@ -59,17 +61,22 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private router: Router, private changeDetectorRef: ChangeDetectorRef,
|
||||
private syncService: SyncService, private i18nService: I18nService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
const queryParams = this.route.parent.params.subscribe(async (params) => {
|
||||
this.trashCleanupWarning = this.i18nService.t(
|
||||
this.platformUtilsService.isSelfHost() ? 'trashCleanupWarningSelfHosted' : 'trashCleanupWarning'
|
||||
);
|
||||
|
||||
const queryParams = this.route.parent.params.subscribe(async params => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.groupingsComponent.organization = this.organization;
|
||||
this.ciphersComponent.organization = this.organization;
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await this.syncService.fullSync(false);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
@@ -110,7 +117,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (qParams.viewEvents != null) {
|
||||
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
|
||||
const cipher = this.ciphersComponent.ciphers.filter(c => c.id === qParams.viewEvents);
|
||||
if (cipher.length > 0) {
|
||||
this.viewEvents(cipher[0]);
|
||||
}
|
||||
@@ -233,9 +240,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.modal = this.collectionsModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<CollectionsComponent>(CollectionsComponent, this.collectionsModalRef);
|
||||
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
childComponent.collectionIds = cipher.collectionIds;
|
||||
childComponent.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
childComponent.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||
}
|
||||
childComponent.organization = this.organization;
|
||||
childComponent.cipherId = cipher.id;
|
||||
@@ -253,8 +260,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const component = this.editCipher(null);
|
||||
component.organizationId = this.organization.id;
|
||||
component.type = this.type;
|
||||
if (this.organization.isAdmin) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||
}
|
||||
if (this.collectionId != null) {
|
||||
component.collectionIds = [this.collectionId];
|
||||
@@ -296,8 +303,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const component = this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.isAdmin) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||
}
|
||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||
// in the add-edit componenet
|
||||
|
||||
@@ -19,4 +19,5 @@ if (process.env.ENV === 'production') {
|
||||
// Other polyfills
|
||||
require('whatwg-fetch');
|
||||
require('webcrypto-shim');
|
||||
require('date-input-polyfill');
|
||||
/* tslint:enable */
|
||||
|
||||
92
src/app/send/access.component.html
Normal file
92
src/app/send/access.component.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<form #form (ngSubmit)="load()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-12">
|
||||
<p class="lead text-center mb-4">Bitwarden Send</p>
|
||||
</div>
|
||||
<div class="col-12 text-center" *ngIf="creatorIdentifier != null">
|
||||
<p>{{'sendCreatorIdentifier' | i18n: creatorIdentifier }}</p>
|
||||
</div>
|
||||
<div class="col-8" *ngIf="hideEmail">
|
||||
<app-callout type="warning" title="{{'warning' | i18n}}">
|
||||
{{'viewSendHiddenEmailWarning' | i18n }}
|
||||
<a href="https://bitwarden.com/help/article/receive-send/" target="_blank">{{'learnMore' | i18n}}</a>.
|
||||
</app-callout>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-5">
|
||||
<div class="card d-block">
|
||||
<div class="card-body" *ngIf="loading" class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && passwordRequired">
|
||||
<p>{{'sendProtectedPassword' | i18n}}</p>
|
||||
<p>{{'sendProtectedPasswordDontKnow' | i18n}}</p>
|
||||
<div class="form-group">
|
||||
<label for="password">{{'password' | i18n}}</label>
|
||||
<input id="password" type="password" name="Password" class="text-monospace form-control"
|
||||
[(ngModel)]="password" required appInputVerbatim appAutofocus>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>
|
||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
|
||||
</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && unavailable">
|
||||
{{'sendAccessUnavailable' | i18n}}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && error">
|
||||
{{'unexpectedError' | i18n}}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loading && !passwordRequired && send">
|
||||
<p class="text-center"><b>{{send.name}}</b></p>
|
||||
<hr>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="send.type === sendType.Text">
|
||||
<app-callout *ngIf="send.text.hidden" type="tip">{{'sendHiddenByDefault' | i18n}}</app-callout>
|
||||
<div class="form-group">
|
||||
<textarea id="text" rows="8" name="Text" [(ngModel)]="sendText" class="form-control"
|
||||
readonly></textarea>
|
||||
</div>
|
||||
<button class="btn btn-block btn-link" type="button" (click)="toggleText()"
|
||||
*ngIf="send.text.hidden">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showText, 'fa-eye-slash': showText}"></i>
|
||||
{{'toggleVisibility' | i18n}}
|
||||
</button>
|
||||
<button class="btn btn-block btn-link" type="button" (click)="copyText()">
|
||||
<i class="fa fa-copy" aria-hidden="true"></i> {{'copyValue' | i18n}}
|
||||
</button>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="send.type === sendType.File">
|
||||
<p>{{send.file.fileName}}</p>
|
||||
<button class="btn btn-primary btn-block" type="button" (click)="download()" *ngIf="!downloading">
|
||||
<i class="fa fa-download" aria-hidden="true"></i>
|
||||
{{'downloadFile' | i18n}} ({{send.file.sizeName}})</button>
|
||||
<button class="btn btn-primary btn-block" type="button" *ngIf="downloading" disabled="true">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
<p *ngIf="expirationDate" class="text-center text-muted">Expires:
|
||||
{{expirationDate | date: 'medium'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-center mt-5 text-muted">
|
||||
<p class="mb-0">{{'sendAccessTaglineProductDesc' | i18n}}<br>
|
||||
{{'sendAccessTaglineLearnMore' | i18n}} <a
|
||||
href="https://www.bitwarden.com/products/send?source=web-vault" target="_blank">Bitwarden Send</a>
|
||||
{{'sendAccessTaglineOr' | i18n}} <a
|
||||
href="https://vault.bitwarden.com/#/register" target="_blank">{{'sendAccessTaglineSignUp' | i18n}}</a>
|
||||
{{'sendAccessTaglineTryToday' | i18n}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
169
src/app/send/access.component.ts
Normal file
169
src/app/send/access.component.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
import { SendAccess } from 'jslib/models/domain/sendAccess';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
|
||||
import { SendAccessView } from 'jslib/models/view/sendAccessView';
|
||||
|
||||
import { SendType } from 'jslib/enums/sendType';
|
||||
import { SendAccessRequest } from 'jslib/models/request/sendAccessRequest';
|
||||
import { ErrorResponse } from 'jslib/models/response/errorResponse';
|
||||
|
||||
import { SendAccessResponse } from 'jslib/models/response/sendAccessResponse';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send-access',
|
||||
templateUrl: 'access.component.html',
|
||||
})
|
||||
export class AccessComponent implements OnInit {
|
||||
send: SendAccessView;
|
||||
sendType = SendType;
|
||||
downloading = false;
|
||||
loading = true;
|
||||
passwordRequired = false;
|
||||
formPromise: Promise<SendAccessResponse>;
|
||||
password: string;
|
||||
showText = false;
|
||||
unavailable = false;
|
||||
error = false;
|
||||
hideEmail = false;
|
||||
|
||||
private id: string;
|
||||
private key: string;
|
||||
private decKey: SymmetricCryptoKey;
|
||||
private accessRequest: SendAccessRequest;
|
||||
|
||||
constructor(private i18nService: I18nService, private cryptoFunctionService: CryptoFunctionService,
|
||||
private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
|
||||
private route: ActivatedRoute, private cryptoService: CryptoService) {
|
||||
}
|
||||
|
||||
get sendText() {
|
||||
if (this.send == null || this.send.text == null) {
|
||||
return null;
|
||||
}
|
||||
return this.showText ? this.send.text.text : this.send.text.maskedText;
|
||||
}
|
||||
|
||||
get expirationDate() {
|
||||
if (this.send == null || this.send.expirationDate == null) {
|
||||
return null;
|
||||
}
|
||||
return this.send.expirationDate;
|
||||
}
|
||||
|
||||
get creatorIdentifier() {
|
||||
if (this.send == null || this.send.creatorIdentifier == null) {
|
||||
return null;
|
||||
}
|
||||
return this.send.creatorIdentifier;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params.subscribe(async params => {
|
||||
this.id = params.sendId;
|
||||
this.key = params.key;
|
||||
if (this.key == null || this.id == null) {
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async download() {
|
||||
if (this.send == null || this.decKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const downloadData = await this.apiService.getSendFileDownloadData(this.send, this.accessRequest);
|
||||
|
||||
if (Utils.isNullOrWhitespace(downloadData.url)) {
|
||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('missingSendFile'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloading = true;
|
||||
const response = await fetch(new Request(downloadData.url, { cache: 'no-store' }));
|
||||
if (response.status !== 200) {
|
||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
||||
this.downloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const buf = await response.arrayBuffer();
|
||||
const decBuf = await this.cryptoService.decryptFromBytes(buf, this.decKey);
|
||||
this.platformUtilsService.saveFile(window, decBuf, null, this.send.file.fileName);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
||||
}
|
||||
|
||||
this.downloading = false;
|
||||
}
|
||||
|
||||
copyText() {
|
||||
this.platformUtilsService.copyToClipboard(this.send.text.text);
|
||||
this.platformUtilsService.showToast('success', null,
|
||||
this.i18nService.t('valueCopied', this.i18nService.t('sendTypeText')));
|
||||
}
|
||||
|
||||
toggleText() {
|
||||
this.showText = !this.showText;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.unavailable = false;
|
||||
this.error = false;
|
||||
this.hideEmail = false;
|
||||
const keyArray = Utils.fromUrlB64ToArray(this.key);
|
||||
this.accessRequest = new SendAccessRequest();
|
||||
if (this.password != null) {
|
||||
const passwordHash = await this.cryptoFunctionService.pbkdf2(this.password, keyArray, 'sha256', 100000);
|
||||
this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
|
||||
}
|
||||
try {
|
||||
let sendResponse: SendAccessResponse = null;
|
||||
if (this.loading) {
|
||||
sendResponse = await this.apiService.postSendAccess(this.id, this.accessRequest);
|
||||
} else {
|
||||
this.formPromise = this.apiService.postSendAccess(this.id, this.accessRequest);
|
||||
sendResponse = await this.formPromise;
|
||||
}
|
||||
this.passwordRequired = false;
|
||||
const sendAccess = new SendAccess(sendResponse);
|
||||
this.decKey = await this.cryptoService.makeSendKey(keyArray);
|
||||
this.send = await sendAccess.decrypt(this.decKey);
|
||||
this.showText = this.send.text != null ? !this.send.text.hidden : true;
|
||||
} catch (e) {
|
||||
if (e instanceof ErrorResponse) {
|
||||
if (e.statusCode === 401) {
|
||||
this.passwordRequired = true;
|
||||
} else if (e.statusCode === 404) {
|
||||
this.unavailable = true;
|
||||
} else {
|
||||
this.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
this.hideEmail = this.creatorIdentifier == null && !this.passwordRequired && !this.loading && !this.unavailable;
|
||||
}
|
||||
}
|
||||
257
src/app/send/add-edit.component.html
Normal file
257
src/app/send/add-edit.component.html
Normal file
@@ -0,0 +1,257 @@
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="sendAddEditTitle">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate
|
||||
autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="sendAddEditTitle">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="send">
|
||||
<app-callout *ngIf="disableSend">
|
||||
<span>{{'sendDisabledWarning' | i18n}}</span>
|
||||
</app-callout>
|
||||
<app-callout *ngIf="!disableSend && disableHideEmail">
|
||||
<span>{{'sendOptionsPolicyInEffect' | i18n}}</span>
|
||||
<ul class="mb-0">
|
||||
<li>{{'sendDisableHideEmailInEffect' | i18n}}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="name">{{'name' | i18n}}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="send.name" required
|
||||
[readOnly]="disableSend">
|
||||
<small class="form-text text-muted">{{'sendNameDesc' | i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="!editMode">
|
||||
<div class="col-6 form-group">
|
||||
<label>{{'whatTypeOfSend' | i18n}}</label>
|
||||
<div class="form-check" *ngFor="let o of typeOptions">
|
||||
<input class="form-check-input" type="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
|
||||
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
|
||||
[checked]="send.type === o.value">
|
||||
<label class="form-check-label" for="type_{{o.value}}">
|
||||
{{o.name}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="send.type === sendType.Text">
|
||||
<div class="form-group">
|
||||
<label for="text">{{'sendTypeText' | i18n}}</label>
|
||||
<textarea id="text" name="Text.Text" rows="6" [(ngModel)]="send.text.text" class="form-control"
|
||||
[readOnly]="disableSend"></textarea>
|
||||
<small class="form-text text-muted">{{'sendTextDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="send.text.hidden"
|
||||
id="text-hidden" name="Text.Hidden" [disabled]="disableSend">
|
||||
<label class="form-check-label" for="text-hidden">{{'textHiddenByDefault' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="send.type === sendType.File">
|
||||
<div class="form-group">
|
||||
<div *ngIf="editMode">
|
||||
<strong class="d-block">{{'file' | i18n}}</strong>
|
||||
{{send.file.fileName}} ({{send.file.sizeName}})
|
||||
</div>
|
||||
<div *ngIf="!editMode">
|
||||
<label for="file">{{'file' | i18n}}</label>
|
||||
<input type="file" id="file" class="form-control-file" name="file" required
|
||||
[disabled]="disableSend">
|
||||
<small class="form-text text-muted">{{'sendFileDesc' | i18n}} {{'maxFileSize' |
|
||||
i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-5">{{'share' | i18n}}</h3>
|
||||
<div class="form-group" *ngIf="link">
|
||||
<label for="link">{{'sendLinkLabel' | i18n}}</label>
|
||||
<input type="text" readonly id="link" name="Link" [(ngModel)]="link" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="copyLink" id="copy-link"
|
||||
name="CopyLink">
|
||||
<label class="form-check-label" for="copy-link">{{'copySendLinkOnSave' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="options-header" class="section-header d-flex flex-row align-items-center mt-5"
|
||||
(click)="toggleOptions()">
|
||||
<h3 class="mb-0 mr-2">{{'options' | i18n}}</h3>
|
||||
<a class="mb-1" href="#" appStopClick role="button">
|
||||
<i class="fa" aria-hidden="true"
|
||||
[ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="options" [hidden]="!showOptions">
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
|
||||
<ng-template #deletionDateCustom>
|
||||
<ng-container *ngIf="isDateTimeLocalSupported">
|
||||
<input id="deletionDateCustom" class="form-control mt-1" type="datetime-local"
|
||||
name="DeletionDate" [(ngModel)]="deletionDate" required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
|
||||
</ng-container>
|
||||
<div *ngIf="!isDateTimeLocalSupported" class="d-flex justify-content-around">
|
||||
<input id="deletionDateCustomFallback" class="form-control mt-1" type="date"
|
||||
name="DeletionDateFallback" [(ngModel)]="deletionDateFallback" required
|
||||
placeholder="MM/DD/YYYY" [readOnly]="disableSend" data-date-format="mm/dd/yyyy">
|
||||
<select *ngIf="isSafari" id="deletionTimeCustomFallback" class="form-control mt-1 ml-1" [required]="!editMode"
|
||||
[(ngModel)]="safariDeletionTime" name="SafariDeletionTime">
|
||||
<option *ngFor="let o of safariDeletionTimeOptions" [value]="o.military">{{o.standard}}</option>
|
||||
</select>
|
||||
<input *ngIf="!isSafari" id="deletionTimeCustomFallback" class="form-control mt-1 ml-1" type="time"
|
||||
name="DeletionTimeDate" [(ngModel)]="deletionTimeFallback" required
|
||||
placeholder="HH:MM AM/PM" [readOnly]="disableSend">
|
||||
</div>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect"
|
||||
class="form-control" required>
|
||||
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
|
||||
</option>
|
||||
</select>
|
||||
<ng-container *ngIf="deletionDateSelect === 0">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom">
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{'deletionDateDesc' | i18n}}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<div class="d-flex">
|
||||
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
|
||||
<a href="#" appStopClick (click)="clearExpiration()" class="ml-auto"
|
||||
*ngIf="editMode && !disableSend">
|
||||
{{'clear' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<ng-template #expirationDateCustom>
|
||||
<ng-container *ngIf="isDateTimeLocalSupported">
|
||||
<input id="expirationDateCustom" class="form-control mt-1" type="datetime-local"
|
||||
name="ExpirationDate" [(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
|
||||
</ng-container>
|
||||
<div class="d-flex justify-content-around" *ngIf="!isDateTimeLocalSupported">
|
||||
<input id="expirationDateCustomFallback" class="form-control mt-1" type="date"
|
||||
name="ExpirationDateFallback" [(ngModel)]="expirationDateFallback" [required]="!editMode"
|
||||
placeholder="MM/DD/YYYY" [readOnly]="disableSend" data-date-format="mm/dd/yyyy" (change)="expirationDateFallbackChanged()">
|
||||
<select *ngIf="isSafari" id="expirationTimeCustomFallback" class="form-control mt-1 ml-1" [required]="!editMode"
|
||||
[(ngModel)]="safariExpirationTime" name="SafariExpirationTime">
|
||||
<option *ngFor="let o of safariExpirationTimeOptions" [ngValue]="o.military">{{o.standard}}</option>
|
||||
</select>
|
||||
<input *ngIf="!isSafari" id="expirationTimeCustomFallback" class="form-control mt-1 ml-1" type="time"
|
||||
name="ExpirationTimeFallback" [(ngModel)]="expirationTimeFallback" [required]="!editMode"
|
||||
placeholder="HH:MM AM/PM" [readOnly]="disableSend">
|
||||
</div>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select id="expirationDate" name="ExpirationDateSelect"
|
||||
[(ngModel)]="expirationDateSelect" class="form-control" required>
|
||||
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
|
||||
</option>
|
||||
</select>
|
||||
<ng-container *ngIf="expirationDateSelect === 0">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom">
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{'expirationDateDesc' | i18n}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
|
||||
<input id="maxAccessCount" class="form-control" type="number" name="MaxAccessCount"
|
||||
[(ngModel)]="send.maxAccessCount" min="1" [readOnly]="disableSend">
|
||||
<div class="form-text text-muted small">{{'maxAccessCountDesc' | i18n}}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group" *ngIf="editMode">
|
||||
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
|
||||
<input id="accessCount" class="form-control" type="text" name="AccessCount" readonly
|
||||
[(ngModel)]="send.accessCount">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label>
|
||||
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
|
||||
<div class="input-group">
|
||||
<input id="password" class="form-control text-monospace"
|
||||
type="{{showPassword ? 'text' : 'password'}}" name="Password" [(ngModel)]="password"
|
||||
[readOnly]="disableSend">
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{'sendPasswordDesc' | i18n}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">{{'notes' | i18n}}</label>
|
||||
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes" class="form-control"
|
||||
[readOnly]="disableSend"></textarea>
|
||||
<div class="form-text text-muted small">{{'sendNotesDesc' | i18n}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="send.hideEmail" id="hideEmail"
|
||||
name="HideEmail" [disabled]="(disableHideEmail && !send.hideEmail) || disableSend">
|
||||
<label class="form-check-label" for="hideEmail">
|
||||
{{'hideEmail' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="send.disabled" id="disabled"
|
||||
name="Disabled" [disabled]="disableSend">
|
||||
<label class="form-check-label" for="disabled">{{'disableThisSend' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary disabled" disabled=true *ngIf="disableSend">
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!disableSend">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{'cancel' | i18n}}
|
||||
</button>
|
||||
<div class="ml-auto" *ngIf="send">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
33
src/app/send/add-edit.component.ts
Normal file
33
src/app/send/add-edit.component.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SendService } from 'jslib/abstractions/send.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send-add-edit',
|
||||
templateUrl: 'add-edit.component.html',
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService, datePipe: DatePipe,
|
||||
sendService: SendService, userService: UserService,
|
||||
messagingService: MessagingService, policyService: PolicyService) {
|
||||
super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService,
|
||||
messagingService, policyService);
|
||||
}
|
||||
|
||||
copyLinkToClipboard(link: string) {
|
||||
// Copy function on web depends on the modal being open or not. Since this event occurs during a transition
|
||||
// of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
|
||||
window.setTimeout(() => super.copyLinkToClipboard(link), 500);
|
||||
}
|
||||
}
|
||||
142
src/app/send/send.component.html
Normal file
142
src/app/send/send.component.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<div class="container page-content">
|
||||
<div class="row card border-warning mb-4" *ngIf="disableSend">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{'sendDisabled' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span>{{'sendDisabledWarning' | i18n}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3 groupings">
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{'filters' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="search" placeholder="{{searchPlaceholder || ('searchSends' | i18n)}}" id="search"
|
||||
class="form-control" [(ngModel)]="searchText" (input)="searchTextChanged()" autocomplete="off"
|
||||
appAutofocus>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{active: selectedAll}">
|
||||
<a href="#" appStopClick (click)="selectAll()">
|
||||
<i class="fa-li fa fa-fw fa-th"></i>{{'allSends' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{'types' | i18n}}</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{active: selectedType === sendType.Text}">
|
||||
<a href="#" appStopClick (click)="selectType(sendType.Text)">
|
||||
<i class="fa-li fa fa-fw fa-file-text-o"></i>{{'sendTypeText' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedType === sendType.File}">
|
||||
<a href="#" appStopClick (click)="selectType(sendType.File)">
|
||||
<i class="fa-li fa fa-fw fa-file-o"></i>{{'sendTypeFile' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{'send' | i18n}}
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addSend()"
|
||||
[disabled]="disableSend">
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'createSend' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--Listing Table-->
|
||||
<table class="table table-hover table-list" *ngIf="filteredSends && filteredSends.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let s of filteredSends">
|
||||
<td class="table-list-icon">
|
||||
<div class="icon" aria-hidden="true">
|
||||
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type == sendType.File"></i>
|
||||
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type == sendType.Text"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick appStopProp (click)="editSend(s)">{{s.name}}</a>
|
||||
<ng-container *ngIf="s.disabled">
|
||||
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'disabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.password">
|
||||
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'password' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.maxAccessCountReached">
|
||||
<i class="fa fa-ban" appStopProp title="{{'maxAccessCountReached' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.expired">
|
||||
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'expired' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="s.pendingDelete">
|
||||
<i class="fa fa-trash" appStopProp title="{{'pendingDeletion' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small appStopProp>{{s.deletionDate | date:'medium'}}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="copy(s)">
|
||||
<i class="fa fa-fw fa-copy" aria-hidden="true"></i>
|
||||
{{'copySendLink' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend">
|
||||
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
|
||||
{{'removePassword' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(s)">
|
||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||
{{'delete' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="no-items" *ngIf="filteredSends && !filteredSends.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p>{{'noSendsInList' | i18n}}</p>
|
||||
<button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend">
|
||||
<i class="fa fa-plus fa-fw"></i>{{'createSend' | i18n}}</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #sendAddEdit></ng-template>
|
||||
103
src/app/send/send.component.ts
Normal file
103
src/app/send/send.component.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
NgZone,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
|
||||
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
|
||||
import { ModalComponent } from '../modal.component';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
import { SendService } from 'jslib/abstractions/send.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
const BroadcasterSubscriptionId = 'SendComponent';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send',
|
||||
templateUrl: 'send.component.html',
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent {
|
||||
@ViewChild('sendAddEdit', { read: ViewContainerRef, static: true }) sendAddEditModalRef: ViewContainerRef;
|
||||
|
||||
modal: ModalComponent = null;
|
||||
|
||||
constructor(sendService: SendService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
ngZone: NgZone, searchService: SearchService, policyService: PolicyService, userService: UserService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver, private broadcasterService: BroadcasterService) {
|
||||
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
|
||||
policyService, userService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
await this.load();
|
||||
|
||||
// Broadcaster subscription - load if sync completes in the background
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case 'syncCompleted':
|
||||
if (message.successfully) {
|
||||
await this.load();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
addSend() {
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const component = this.editSend(null);
|
||||
component.type = this.type;
|
||||
}
|
||||
|
||||
editSend(send: SendView) {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.sendAddEditModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<AddEditComponent>(
|
||||
AddEditComponent, this.sendAddEditModalRef);
|
||||
|
||||
childComponent.sendId = send == null ? null : send.id;
|
||||
childComponent.onSavedSend.subscribe(async (s: SendView) => {
|
||||
this.modal.close();
|
||||
await this.load();
|
||||
});
|
||||
childComponent.onDeletedSend.subscribe(async (s: SendView) => {
|
||||
this.modal.close();
|
||||
await this.load();
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
});
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,15 @@ export class EventService {
|
||||
case EventType.OrganizationUser_UpdatedGroups:
|
||||
msg = this.i18nService.t('editedGroupsForUser', this.formatOrgUserId(ev));
|
||||
break;
|
||||
case EventType.OrganizationUser_UnlinkedSso:
|
||||
msg = this.i18nService.t('unlinkedSsoUser', this.formatOrgUserId(ev));
|
||||
break;
|
||||
case EventType.OrganizationUser_ResetPassword_Enroll:
|
||||
msg = this.i18nService.t('eventEnrollPasswordReset', this.formatOrgUserId(ev));
|
||||
break;
|
||||
case EventType.OrganizationUser_ResetPassword_Withdraw:
|
||||
msg = this.i18nService.t('eventWithdrawPasswordReset', this.formatOrgUserId(ev));
|
||||
break;
|
||||
// Org
|
||||
case EventType.Organization_Updated:
|
||||
msg = this.i18nService.t('editedOrgSettings');
|
||||
@@ -165,6 +174,11 @@ export class EventService {
|
||||
msg = this.i18nService.t('exportedOrganizationVault');
|
||||
break;
|
||||
*/
|
||||
// Policies
|
||||
case EventType.Policy_Updated:
|
||||
msg = this.i18nService.t('modifiedPolicy', this.formatPolicyId(ev));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -251,6 +265,13 @@ export class EventService {
|
||||
return a.outerHTML;
|
||||
}
|
||||
|
||||
private formatPolicyId(ev: EventResponse) {
|
||||
const shortId = this.getShortId(ev.policyId);
|
||||
const a = this.makeAnchor(shortId);
|
||||
a.setAttribute('href', '#/organizations/' + ev.organizationId + '/manage/policies?policyId=' + ev.policyId);
|
||||
return a.outerHTML;
|
||||
}
|
||||
|
||||
private makeAnchor(shortId: string) {
|
||||
const a = document.createElement('a');
|
||||
a.title = this.i18nService.t('view');
|
||||
|
||||
@@ -7,20 +7,32 @@ import {
|
||||
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { Permissions } from 'jslib/enums/permissions';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationTypeGuardService implements CanActivate {
|
||||
constructor(private userService: UserService, private router: Router) { }
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const org = await this.userService.getOrganization(route.parent.params.organizationId);
|
||||
const allowedTypes = route.data == null ? null : route.data.allowedTypes as OrganizationUserType[];
|
||||
if (allowedTypes == null || allowedTypes.indexOf(org.type) === -1) {
|
||||
this.router.navigate(['/organizations', org.id]);
|
||||
return false;
|
||||
const org = await this.userService.getOrganization(route.params.organizationId);
|
||||
const permissions = route.data == null ? null : route.data.permissions as Permissions[];
|
||||
|
||||
if (
|
||||
(permissions.indexOf(Permissions.AccessBusinessPortal) !== -1 && org.canAccessBusinessPortal) ||
|
||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
||||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
||||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
||||
(permissions.indexOf(Permissions.ManageAllCollections) !== -1 && org.canManageAllCollections) ||
|
||||
(permissions.indexOf(Permissions.ManageAssignedCollections) !== -1 && org.canManageAssignedCollections) ||
|
||||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
||||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
||||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
this.router.navigate(['/organizations', org.id]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export class RouterService {
|
||||
constructor(private router: Router, private activatedRoute: ActivatedRoute,
|
||||
private titleService: Title, i18nService: I18nService) {
|
||||
this.currentUrl = this.router.url;
|
||||
router.events.subscribe((event) => {
|
||||
router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.previousUrl = this.currentUrl;
|
||||
this.currentUrl = event.url;
|
||||
|
||||
@@ -16,32 +16,34 @@ import { EventService } from './event.service';
|
||||
import { OrganizationGuardService } from './organization-guard.service';
|
||||
import { OrganizationTypeGuardService } from './organization-type-guard.service';
|
||||
import { RouterService } from './router.service';
|
||||
import { UnauthGuardService } from './unauth-guard.service';
|
||||
|
||||
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
import { LockGuardService } from 'jslib/angular/services/lock-guard.service';
|
||||
import { UnauthGuardService } from 'jslib/angular/services/unauth-guard.service';
|
||||
import { ValidationService } from 'jslib/angular/services/validation.service';
|
||||
|
||||
import { Analytics } from 'jslib/misc/analytics';
|
||||
|
||||
import { ApiService } from 'jslib/services/api.service';
|
||||
import { AppIdService } from 'jslib/services/appId.service';
|
||||
import { AuditService } from 'jslib/services/audit.service';
|
||||
import { AuthService } from 'jslib/services/auth.service';
|
||||
import { CipherService } from 'jslib/services/cipher.service';
|
||||
import { CollectionService } from 'jslib/services/collection.service';
|
||||
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
import { ContainerService } from 'jslib/services/container.service';
|
||||
import { CryptoService } from 'jslib/services/crypto.service';
|
||||
import { EnvironmentService } from 'jslib/services/environment.service';
|
||||
import { EventService as EventLoggingService } from 'jslib/services/event.service';
|
||||
import { ExportService } from 'jslib/services/export.service';
|
||||
import { FileUploadService } from 'jslib/services/fileUpload.service';
|
||||
import { FolderService } from 'jslib/services/folder.service';
|
||||
import { ImportService } from 'jslib/services/import.service';
|
||||
import { NotificationsService } from 'jslib/services/notifications.service';
|
||||
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
|
||||
import { PolicyService } from 'jslib/services/policy.service';
|
||||
import { SearchService } from 'jslib/services/search.service';
|
||||
import { SendService } from 'jslib/services/send.service';
|
||||
import { SettingsService } from 'jslib/services/settings.service';
|
||||
import { StateService } from 'jslib/services/state.service';
|
||||
import { SyncService } from 'jslib/services/sync.service';
|
||||
@@ -62,6 +64,7 @@ import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib
|
||||
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
|
||||
import { EventService as EventLoggingServiceAbstraction } from 'jslib/abstractions/event.service';
|
||||
import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service';
|
||||
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib/abstractions/fileUpload.service';
|
||||
import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
||||
import { ImportService as ImportServiceAbstraction } from 'jslib/abstractions/import.service';
|
||||
@@ -74,6 +77,7 @@ import {
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService as PolicyServiceAbstraction } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service';
|
||||
import { SendService as SendServiceAbstraction } from 'jslib/abstractions/send.service';
|
||||
import { SettingsService as SettingsServiceAbstraction } from 'jslib/abstractions/settings.service';
|
||||
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
||||
import { StorageService as StorageServiceAbstraction } from 'jslib/abstractions/storage.service';
|
||||
@@ -92,8 +96,10 @@ const storageService: StorageServiceAbstraction = new HtmlStorageService(platfor
|
||||
const secureStorageService: StorageServiceAbstraction = new MemoryStorageService();
|
||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
|
||||
platformUtilsService);
|
||||
const consoleLogService = new ConsoleLogService(false);
|
||||
const cryptoService = new CryptoService(storageService,
|
||||
platformUtilsService.isDev() ? storageService : secureStorageService, cryptoFunctionService);
|
||||
platformUtilsService.isDev() ? storageService : secureStorageService, cryptoFunctionService, platformUtilsService,
|
||||
consoleLogService);
|
||||
const tokenService = new TokenService(storageService);
|
||||
const appIdService = new AppIdService(storageService);
|
||||
const apiService = new ApiService(tokenService, platformUtilsService,
|
||||
@@ -101,61 +107,56 @@ const apiService = new ApiService(tokenService, platformUtilsService,
|
||||
const userService = new UserService(tokenService, storageService);
|
||||
const settingsService = new SettingsService(userService, storageService);
|
||||
export let searchService: SearchService = null;
|
||||
const fileUploadService = new FileUploadService(consoleLogService, apiService);
|
||||
const cipherService = new CipherService(cryptoService, userService, settingsService,
|
||||
apiService, storageService, i18nService, () => searchService);
|
||||
apiService, fileUploadService, storageService, i18nService, () => searchService);
|
||||
const folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
||||
i18nService, cipherService);
|
||||
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
||||
searchService = new SearchService(cipherService);
|
||||
searchService = new SearchService(cipherService, consoleLogService, i18nService);
|
||||
const policyService = new PolicyService(userService, storageService);
|
||||
const sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
|
||||
i18nService, cryptoFunctionService);
|
||||
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
|
||||
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
|
||||
null, async () => messagingService.send('logout', { expired: false }));
|
||||
const syncService = new SyncService(userService, apiService, settingsService,
|
||||
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
|
||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, policyService);
|
||||
const totpService = new TotpService(storageService, cryptoFunctionService);
|
||||
const containerService = new ContainerService(cryptoService);
|
||||
const authService = new AuthService(cryptoService, apiService,
|
||||
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService);
|
||||
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
consoleLogService);
|
||||
const exportService = new ExportService(folderService, cipherService, apiService);
|
||||
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
|
||||
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService,
|
||||
platformUtilsService);
|
||||
const notificationsService = new NotificationsService(userService, syncService, appIdService,
|
||||
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }));
|
||||
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }), consoleLogService);
|
||||
const environmentService = new EnvironmentService(apiService, storageService, notificationsService);
|
||||
const auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService);
|
||||
|
||||
const analytics = new Analytics(window, () => platformUtilsService.isDev() || platformUtilsService.isSelfHost(),
|
||||
platformUtilsService, storageService, appIdService);
|
||||
containerService.attachToWindow(window);
|
||||
|
||||
export function initFactory(): Function {
|
||||
return async () => {
|
||||
await (storageService as HtmlStorageService).init();
|
||||
const isDev = platformUtilsService.isDev();
|
||||
if (!isDev && platformUtilsService.isSelfHost()) {
|
||||
|
||||
if (isDev || platformUtilsService.isSelfHost()) {
|
||||
environmentService.baseUrl = window.location.origin;
|
||||
} else {
|
||||
environmentService.notificationsUrl = isDev ? 'http://localhost:61840' :
|
||||
'https://notifications.bitwarden.com'; // window.location.origin + '/notifications';
|
||||
environmentService.enterpriseUrl = isDev ? 'http://localhost:52313' :
|
||||
'https://portal.bitwarden.com'; // window.location.origin + '/portal';
|
||||
environmentService.notificationsUrl = 'https://notifications.bitwarden.com';
|
||||
environmentService.enterpriseUrl = 'https://portal.bitwarden.com';
|
||||
}
|
||||
|
||||
apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://localhost:4000' : null,
|
||||
identity: isDev ? 'http://localhost:33656' : null,
|
||||
events: isDev ? 'http://localhost:46273' : null,
|
||||
|
||||
// Uncomment these (and comment out the above) if you want to target production
|
||||
// servers for local development.
|
||||
|
||||
// base: null,
|
||||
// api: 'https://api.bitwarden.com',
|
||||
// identity: 'https://identity.bitwarden.com',
|
||||
// events: 'https://events.bitwarden.com',
|
||||
base: window.location.origin,
|
||||
api: null,
|
||||
identity: null,
|
||||
events: null,
|
||||
});
|
||||
setTimeout(() => notificationsService.init(environmentService), 3000);
|
||||
|
||||
@@ -190,6 +191,7 @@ export function initFactory(): Function {
|
||||
UnauthGuardService,
|
||||
RouterService,
|
||||
EventService,
|
||||
LockGuardService,
|
||||
{ provide: AuditServiceAbstraction, useValue: auditService },
|
||||
{ provide: AuthServiceAbstraction, useValue: authService },
|
||||
{ provide: CipherServiceAbstraction, useValue: cipherService },
|
||||
@@ -203,6 +205,7 @@ export function initFactory(): Function {
|
||||
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
||||
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
||||
{ provide: ApiServiceAbstraction, useValue: apiService },
|
||||
{ provide: FileUploadServiceAbstraction, useValue: fileUploadService },
|
||||
{ provide: SyncServiceAbstraction, useValue: syncService },
|
||||
{ provide: UserServiceAbstraction, useValue: userService },
|
||||
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
||||
@@ -218,6 +221,7 @@ export function initFactory(): Function {
|
||||
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
||||
{ provide: EventLoggingServiceAbstraction, useValue: eventLoggingService },
|
||||
{ provide: PolicyServiceAbstraction, useValue: policyService },
|
||||
{ provide: SendServiceAbstraction, useValue: sendService },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: initFactory,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user