mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9326a9a4a7 | ||
|
|
6b3b1a901d | ||
|
|
41d0880bd5 | ||
|
|
3e66a7162c | ||
|
|
c93e39566f | ||
|
|
6af405b89d | ||
|
|
f9a2ec138e | ||
|
|
ddab1a3cee | ||
|
|
f24836b0a5 |
118
.github/workflows/build.yml
vendored
118
.github/workflows/build.yml
vendored
@@ -5,6 +5,9 @@ on:
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'gh-pages'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
cloc:
|
||||
@@ -38,40 +41,27 @@ jobs:
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Login to Azure
|
||||
if: github.ref == 'refs/heads/master' || 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.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.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 }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Log into docker
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Setup Docker Trust
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: |
|
||||
mkdir -p ~/.docker/trust/private
|
||||
|
||||
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
|
||||
echo "${{ secrets.DOCKER_DELEGATION_KEY }}" > ~/.docker/trust/private/$DOCKER_DELEGATION_KEY_ID.key
|
||||
echo "${{ secrets.DOCKER_REPO_WEB_KEY }}" > ~/.docker/trust/private/$DOCKER_WEB_KEY_ID.key
|
||||
env:
|
||||
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
|
||||
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
|
||||
DOCKER_DELEGATION_KEY_ID: "5702b22123e058cbd96a7a43000cb981ae98ef3f2f4aa34138ab3dc1d011e446"
|
||||
DOCKER_WEB_KEY_ID: "0f88641697187f42a31b584897cd4edfe80360a5209122d9e7f71af17a6422e4"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
@@ -81,44 +71,60 @@ jobs:
|
||||
|
||||
- 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
|
||||
chmod +x ./build.sh
|
||||
./build.sh
|
||||
|
||||
- name: Tag dev
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: docker tag bitwarden/web bitwarden/web:dev
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./build.sh tag dev
|
||||
|
||||
- name: List Docker images
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||
- name: Tag beta
|
||||
if: github.event_name == 'release'
|
||||
run: ./build.sh tag beta
|
||||
|
||||
- name: Tag version
|
||||
if: github.event_name == 'release'
|
||||
run: ./build.sh tag $($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'
|
||||
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'
|
||||
run: docker push bitwarden/web:dev
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: ./build.sh push dev
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||
|
||||
- name: Log out of Docker
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
|
||||
- name: Push beta images
|
||||
if: github.event_name == 'release'
|
||||
run: ./build.sh push beta
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||
|
||||
- name: Push latest images
|
||||
if: github.event_name == 'release'
|
||||
run: ./build.sh push latest
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||
|
||||
- name: Push version images
|
||||
if: github.event_name == 'release'
|
||||
run: ./build.sh push $($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: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||
|
||||
- name: Log out of docker
|
||||
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
|
||||
run: docker logout
|
||||
|
||||
windows:
|
||||
|
||||
157
.github/workflows/release.yml
vendored
157
.github/workflows/release.yml
vendored
@@ -1,157 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag_name_input:
|
||||
description: "Release Tag Name <X.X.X>"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
release_version: ${{ steps.create_tags.outputs.package_version }}
|
||||
tag_version: ${{ steps.create_tags.outputs.tag_version }}
|
||||
steps:
|
||||
- name: Branch check
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]]; then
|
||||
echo "==================================="
|
||||
echo "[!] Can only release from rc branch"
|
||||
echo "==================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
||||
|
||||
- name: Create Release Vars
|
||||
id: create_tags
|
||||
run: |
|
||||
case "${RELEASE_TAG_NAME_INPUT:0:1}" in
|
||||
v)
|
||||
echo "RELEASE_NAME=${RELEASE_TAG_NAME_INPUT:1}" >> $GITHUB_ENV
|
||||
echo "RELEASE_TAG_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||
echo "::set-output name=package_version::${RELEASE_TAG_NAME_INPUT:1}"
|
||||
echo "::set-output name=tag_version::$RELEASE_TAG_NAME_INPUT"
|
||||
;;
|
||||
[0-9])
|
||||
echo "RELEASE_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||
echo "RELEASE_TAG_NAME=v$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
|
||||
echo "::set-output name=package_version::$RELEASE_TAG_NAME_INPUT"
|
||||
echo "::set-output name=tag_version::v$RELEASE_TAG_NAME_INPUT"
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
env:
|
||||
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
|
||||
|
||||
- name: Create Draft Release
|
||||
id: create_release
|
||||
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # 1.1.4 - Repo Archived
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG_NAME }}
|
||||
release_name: Version ${{ env.RELEASE_NAME }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
env:
|
||||
RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g npm@7
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
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
|
||||
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@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- 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 version
|
||||
run: docker tag bitwarden/web bitwarden/web:$RELEASE_VERSION
|
||||
|
||||
- name: List Docker images
|
||||
run: docker images
|
||||
|
||||
- name: Push latest images
|
||||
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
|
||||
run: docker push bitwarden/web:$RELEASE_VERSION
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
|
||||
|
||||
- name: Log out of Docker
|
||||
run: docker logout
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,4 +11,3 @@ dist/
|
||||
*.zip
|
||||
build/
|
||||
!dev-server.shared.pem
|
||||
config/development.json
|
||||
|
||||
56
README.md
56
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://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 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>
|
||||
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
|
||||
@@ -34,36 +34,52 @@ npm install
|
||||
npm run build:watch
|
||||
```
|
||||
|
||||
You can now access the web vault in your browser at `https://localhost:8080`.
|
||||
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:
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can run using:
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://mylocalapi' : null,
|
||||
identity: isDev ? 'http://mylocalidentity' : null,
|
||||
events: isDev ? 'http://mylocalevents' : null,
|
||||
});
|
||||
```
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can set:
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: null,
|
||||
api: 'https://api.bitwarden.com',
|
||||
identity: 'https://identity.bitwarden.com',
|
||||
events: 'https://events.bitwarden.com',
|
||||
});
|
||||
```
|
||||
|
||||
And note to run the app with:
|
||||
|
||||
```
|
||||
npm install
|
||||
ENV=production npm run build:watch
|
||||
npm run build:prod:watch
|
||||
```
|
||||
|
||||
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:
|
||||
## Common Issues:
|
||||
|
||||
```typescript
|
||||
{
|
||||
"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:
|
||||
### CORS
|
||||
|
||||
If you run the frontend and receive a notification after attempting to login that says:
|
||||
```
|
||||
npm run build:dev:watch
|
||||
An error has occurred.
|
||||
NetworkError when attempting to fetch resource.
|
||||
```
|
||||
And in the console:
|
||||
```
|
||||
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.bitwarden.com/accounts/prelogin. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
|
||||
```
|
||||
This means that you are having a CORS header issue. This can be mitigated by using a CORS header changing extension in your browser such as [this one.](https://mybrowseraddon.com/access-control-allow-origin.html)
|
||||
|
||||
## Contribute
|
||||
|
||||
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.
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
|
||||
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
Normal file
83
appveyor.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
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
Normal file
33
build.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/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
29
config.js
@@ -1,29 +0,0 @@
|
||||
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
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"proxyApi": "http://localhost:4000",
|
||||
"proxyIdentity": "http://localhost:33656",
|
||||
"proxyEvents": "http://localhost:46273",
|
||||
"proxyNotifications": "http://localhost:61840",
|
||||
"proxyPortal": "http://localhost:52313",
|
||||
"allowedHosts": []
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"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: f6d91e2d92...9ddec9baf8
1927
package-lock.json
generated
1927
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.20.4",
|
||||
"version": "2.18.1",
|
||||
"license": "GPL-3.0",
|
||||
"repository": "https://github.com/bitwarden/web",
|
||||
"scripts": {
|
||||
@@ -13,12 +13,8 @@
|
||||
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||
"build": "gulp prebuild && webpack",
|
||||
"build:watch": "gulp prebuild && 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:prod": "gulp prebuild && cross-env NODE_ENV=production webpack",
|
||||
"build:prod:watch": "gulp prebuild && cross-env NODE_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",
|
||||
@@ -28,23 +24,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.5",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^10.17.28",
|
||||
"@types/node-forge": "^0.9.7",
|
||||
"@types/papaparse": "^5.2.0",
|
||||
"@types/node-forge": "^0.7.5",
|
||||
"@types/papaparse": "^4.5.3",
|
||||
"@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": "^5.1.1",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
@@ -77,26 +73,26 @@
|
||||
"@angular/platform-browser": "9.1.12",
|
||||
"@angular/platform-browser-dynamic": "9.1.12",
|
||||
"@angular/router": "9.1.12",
|
||||
"@microsoft/signalr": "3.1.13",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.13",
|
||||
"@microsoft/signalr": "3.1.0",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
||||
"angular2-toaster": "8.0.0",
|
||||
"big-integer": "1.6.48",
|
||||
"angulartics2": "9.1.0",
|
||||
"big-integer": "1.6.36",
|
||||
"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.6.0",
|
||||
"jquery": "3.4.1",
|
||||
"lunr": "2.3.3",
|
||||
"ngx-infinite-scroll": "7.0.1",
|
||||
"node-forge": "0.10.0",
|
||||
"papaparse": "5.2.0",
|
||||
"node-forge": "0.7.6",
|
||||
"papaparse": "4.6.0",
|
||||
"popper.js": "1.14.4",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "6.6.2",
|
||||
"sweetalert2": "10.15.4",
|
||||
"sweetalert2": "9.8.1",
|
||||
"tslib": "^2.0.1",
|
||||
"web-animations-js": "2.3.1",
|
||||
"webcrypto-shim": "0.1.4",
|
||||
|
||||
35
src/404.css
Normal file
35
src/404.css
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
14
src/404.html
14
src/404.html
@@ -5,11 +5,15 @@
|
||||
<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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
|
||||
<link rel="stylesheet" crossorigin="anonymous"
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0= sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic"
|
||||
rel="stylesheet" type="text/css">
|
||||
|
||||
<link href="/404.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">
|
||||
|
||||
7
src/404/bootstrap.min.css
vendored
7
src/404/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
4
src/404/font-awesome.min.css
vendored
4
src/404/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,119 +0,0 @@
|
||||
@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;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export class AcceptEmergencyComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async qParams => {
|
||||
this.route.queryParams.subscribe(async (qParams) => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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,6 +33,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
@@ -17,7 +18,8 @@ export class RecoverDeleteComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private toasterService: ToasterService, private i18nService: I18nService) {
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService) {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -26,6 +28,7 @@ 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,6 +2,7 @@ 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';
|
||||
@@ -21,8 +22,9 @@ export class RecoverTwoFactorComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private cryptoService: CryptoService, private authService: AuthService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private cryptoService: CryptoService,
|
||||
private authService: AuthService) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -33,6 +35,7 @@ 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 { }
|
||||
|
||||
@@ -62,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;
|
||||
@@ -96,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 { }
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -33,10 +33,16 @@
|
||||
required appAutofocus appInputVerbatim autocomplete="new-password">
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
|
||||
<div id="web-authn-frame" class="mb-3">
|
||||
<iframe id="webauthn_iframe"></iframe>
|
||||
</div>
|
||||
<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>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo">
|
||||
@@ -45,7 +51,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
|
||||
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i>
|
||||
*ngIf="form.loading && selectedProviderType === providerType.U2f" aria-hidden="true"></i>
|
||||
<div class="form-check" *ngIf="selectedProviderType != null">
|
||||
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
|
||||
[(ngModel)]="remember">
|
||||
@@ -59,7 +65,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.WebAuthn">
|
||||
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.U2f">
|
||||
<span>
|
||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
|
||||
</span>
|
||||
@@ -78,3 +84,4 @@
|
||||
</div>
|
||||
</form>
|
||||
<ng-template #twoFactorOptions></ng-template>
|
||||
<iframe id="u2f_iframe" hidden></iframe>
|
||||
|
||||
@@ -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,6 +8,7 @@ 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';
|
||||
@@ -26,13 +27,13 @@ export class VerifyRecoverDeleteComponent implements OnInit {
|
||||
private token: string;
|
||||
|
||||
constructor(private router: Router, private apiService: ApiService,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private route: ActivatedRoute) {
|
||||
private analytics: Angulartics2, 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;
|
||||
}
|
||||
@@ -52,6 +53,7 @@ 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(['/']);
|
||||
|
||||
@@ -87,10 +87,9 @@ 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 { Permissions } from 'jslib/enums/permissions';
|
||||
|
||||
@@ -123,11 +122,7 @@ const routes: Routes = [
|
||||
canActivate: [UnauthGuardService],
|
||||
data: { titleId: 'passwordHint' },
|
||||
},
|
||||
{
|
||||
path: 'lock',
|
||||
component: LockComponent,
|
||||
canActivate: [LockGuardService],
|
||||
},
|
||||
{ path: 'lock', component: LockComponent },
|
||||
{ path: 'verify-email', component: VerifyEmailTokenComponent },
|
||||
{
|
||||
path: 'accept-organization',
|
||||
@@ -158,11 +153,11 @@ const routes: Routes = [
|
||||
canActivate: [UnauthGuardService],
|
||||
data: { titleId: 'deleteAccount' },
|
||||
},
|
||||
{
|
||||
/*{
|
||||
path: 'send/:sendId/:key',
|
||||
component: AccessComponent,
|
||||
data: { title: 'Bitwarden Send' },
|
||||
},
|
||||
},*/
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
BodyOutputType,
|
||||
Toast,
|
||||
ToasterConfig,
|
||||
ToasterContainerComponent,
|
||||
ToasterService,
|
||||
} from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
|
||||
import {
|
||||
Component,
|
||||
@@ -67,12 +70,12 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
|
||||
constructor(
|
||||
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||
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 authService: AuthService, private router: Router, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
@@ -136,18 +139,15 @@ 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,6 +198,7 @@ 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,6 +1,8 @@
|
||||
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';
|
||||
@@ -112,11 +114,10 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co
|
||||
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 { EmergencyAccessComponent } from './settings/emergency-access.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';
|
||||
@@ -133,8 +134,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';
|
||||
@@ -165,7 +166,6 @@ 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';
|
||||
|
||||
@@ -190,8 +190,8 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
||||
|
||||
import {
|
||||
DatePipe,
|
||||
registerLocaleData,
|
||||
DatePipe,
|
||||
} from '@angular/common';
|
||||
import localeCa from '@angular/common/locales/ca';
|
||||
import localeCs from '@angular/common/locales/cs';
|
||||
@@ -254,6 +254,11 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
FormsModule,
|
||||
AppRoutingModule,
|
||||
ServicesModule,
|
||||
Angulartics2Module.forRoot({
|
||||
pageTracking: {
|
||||
clearQueryParams: true,
|
||||
},
|
||||
}),
|
||||
ToasterModule.forRoot(),
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
@@ -299,7 +304,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
@@ -376,7 +380,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
SelectCopyDirective,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendInfoComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SsoComponent,
|
||||
@@ -392,8 +395,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorU2fComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UnsecuredWebsitesReportComponent,
|
||||
UpdateKeyComponent,
|
||||
@@ -421,7 +424,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAddEditComponent,
|
||||
@@ -447,7 +449,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
TwoFactorEmailComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorU2fComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdateKeyComponent,
|
||||
],
|
||||
|
||||
@@ -15,8 +15,8 @@ export class FooterComponent implements OnInit {
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
ngOnInit() {
|
||||
this.year = new Date().getFullYear().toString();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
this.version = this.platformUtilsService.getApplicationVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ export class FrontendLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
ngOnInit() {
|
||||
this.year = new Date().getFullYear().toString();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
this.version = this.platformUtilsService.getApplicationVersion();
|
||||
document.body.classList.add('layout_frontend');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +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="/sends">Send</a>
|
||||
</li>-->
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
|
||||
</li>
|
||||
|
||||
@@ -42,7 +42,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -14,7 +15,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { EncString } from 'jslib/models/domain/encString';
|
||||
import { CipherString } from 'jslib/models/domain/cipherString';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
import { CollectionRequest } from 'jslib/models/request/collectionRequest';
|
||||
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
|
||||
@@ -45,8 +46,9 @@ export class CollectionAddEditComponent implements OnInit {
|
||||
private orgKey: SymmetricCryptoKey;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
|
||||
private cryptoService: CryptoService, private userService: UserService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService,
|
||||
private userService: UserService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
@@ -54,7 +56,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);
|
||||
|
||||
@@ -63,11 +65,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 EncString(collection.name), this.orgKey);
|
||||
this.name = await this.cryptoService.decryptToUtf8(new CipherString(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;
|
||||
@@ -80,7 +82,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;
|
||||
}
|
||||
@@ -101,7 +103,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() {
|
||||
@@ -112,8 +114,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) {
|
||||
@@ -122,6 +124,7 @@ 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();
|
||||
@@ -143,6 +146,7 @@ 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,6 +8,7 @@ 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';
|
||||
@@ -51,15 +52,15 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||
private searchService: SearchService) { }
|
||||
private analytics: Angulartics2, 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();
|
||||
@@ -76,7 +77,7 @@ export class CollectionsComponent implements OnInit {
|
||||
} 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();
|
||||
@@ -140,6 +141,7 @@ 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) ?
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -41,7 +42,7 @@ export class EntityUsersComponent implements OnInit {
|
||||
private allUsers: OrganizationUserUserDetailsResponse[] = [];
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
await this.loadUsers();
|
||||
@@ -50,7 +51,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;
|
||||
}
|
||||
@@ -58,12 +59,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;
|
||||
}
|
||||
@@ -72,8 +73,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;
|
||||
@@ -83,7 +84,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;
|
||||
}
|
||||
@@ -120,14 +121,17 @@ 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,6 +7,7 @@ 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';
|
||||
@@ -41,8 +42,8 @@ export class GroupAddEditComponent implements OnInit {
|
||||
deletePromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private collectionService: CollectionService,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.groupId != null;
|
||||
@@ -57,8 +58,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;
|
||||
@@ -76,7 +77,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);
|
||||
}
|
||||
@@ -89,7 +90,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() {
|
||||
@@ -98,8 +99,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 {
|
||||
@@ -109,6 +110,7 @@ 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();
|
||||
@@ -130,6 +132,7 @@ 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,6 +11,7 @@ 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';
|
||||
@@ -48,12 +49,12 @@ export class GroupsComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
|
||||
private userService: UserService, private router: Router,
|
||||
private searchService: SearchService) { }
|
||||
private analytics: Angulartics2, 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) {
|
||||
@@ -61,7 +62,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();
|
||||
@@ -135,6 +136,7 @@ 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 { }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,13 +70,13 @@ export class PeopleComponent implements OnInit {
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private userService: UserService, private router: Router,
|
||||
private storageService: StorageService, private searchService: SearchService,
|
||||
private validationService: ValidationService) { }
|
||||
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private userService: UserService, private router: Router,
|
||||
private storageService: StorageService, 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.canManageUsers) {
|
||||
@@ -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,6 +231,7 @@ 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 { }
|
||||
@@ -242,6 +243,7 @@ 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;
|
||||
}
|
||||
@@ -256,20 +258,6 @@ 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;
|
||||
}
|
||||
@@ -289,14 +277,9 @@ 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(async (publicKey: Uint8Array) => {
|
||||
try {
|
||||
await confirmUser(publicKey);
|
||||
this.modal.close();
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error('Handled exception:', e);
|
||||
}
|
||||
childComponent.onConfirmedUser.subscribe(() => {
|
||||
this.modal.close();
|
||||
updateUser(this);
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
@@ -305,19 +288,12 @@ export class PeopleComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
async events(user: OrganizationUserUserDetailsResponse) {
|
||||
@@ -358,8 +334,15 @@ export class PeopleComponent implements OnInit {
|
||||
return !searching && this.users && this.users.length > this.pageSize;
|
||||
}
|
||||
|
||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
|
||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
|
||||
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;
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
|
||||
import { PolicyType } from 'jslib/enums/policyType';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions';
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
@@ -51,7 +51,7 @@ export class PoliciesComponent implements OnInit {
|
||||
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) {
|
||||
@@ -102,25 +102,11 @@ export class PoliciesComponent implements OnInit {
|
||||
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 => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
if (qParams.policyId != null) {
|
||||
const policyIdFromEvents: string = qParams.policyId;
|
||||
for (const orgPolicy of this.orgPolicies) {
|
||||
@@ -154,10 +140,10 @@ export class PoliciesComponent implements OnInit {
|
||||
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;
|
||||
|
||||
@@ -32,12 +32,6 @@
|
||||
<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"
|
||||
@@ -150,14 +144,6 @@
|
||||
<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,6 +7,7 @@ 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';
|
||||
@@ -37,6 +38,7 @@ export class PolicyEditComponent implements OnInit {
|
||||
defaultTypes: any[];
|
||||
|
||||
// Master password
|
||||
|
||||
masterPassMinComplexity?: number = null;
|
||||
masterPassMinLength?: number;
|
||||
masterPassRequireUpper?: number;
|
||||
@@ -45,6 +47,7 @@ export class PolicyEditComponent implements OnInit {
|
||||
masterPassRequireSpecial?: number;
|
||||
|
||||
// Password generator
|
||||
|
||||
passGenDefaultType?: string;
|
||||
passGenMinLength?: number;
|
||||
passGenUseUpper?: boolean;
|
||||
@@ -57,13 +60,10 @@ export class PolicyEditComponent implements OnInit {
|
||||
passGenCapitalize?: boolean;
|
||||
passGenIncludeNumber?: boolean;
|
||||
|
||||
// Send options
|
||||
sendDisableHideEmail?: boolean;
|
||||
|
||||
private policy: PolicyResponse;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) {
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) {
|
||||
this.passwordScores = [
|
||||
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
|
||||
{ name: i18nService.t('weak') + ' (0)', value: 0 },
|
||||
@@ -113,9 +113,6 @@ 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;
|
||||
}
|
||||
@@ -162,17 +159,13 @@ export class PolicyEditComponent implements OnInit {
|
||||
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.analytics.eventTrack.next({ action: 'Edited Policy' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
|
||||
this.onSavedPolicy.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -178,15 +178,6 @@
|
||||
</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>
|
||||
@@ -264,9 +255,8 @@
|
||||
<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,6 +7,7 @@ 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';
|
||||
@@ -53,8 +54,8 @@ export class UserAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private collectionService: CollectionService,
|
||||
private platformUtilsService: PlatformUtilsService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.organizationUserId != null;
|
||||
@@ -71,8 +72,8 @@ export class UserAddEditComponent implements OnInit {
|
||||
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;
|
||||
@@ -90,7 +91,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);
|
||||
}
|
||||
@@ -103,7 +104,7 @@ 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) {
|
||||
@@ -137,17 +138,14 @@ export class UserAddEditComponent implements OnInit {
|
||||
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 {
|
||||
@@ -169,6 +167,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
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();
|
||||
@@ -190,6 +189,7 @@ 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()">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="confirmUserTitle">
|
||||
{{'confirmUser' | i18n}}
|
||||
|
||||
@@ -6,12 +6,18 @@ 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({
|
||||
@@ -28,11 +34,13 @@ export class UserConfirmComponent implements OnInit {
|
||||
dontAskAgain = false;
|
||||
loading = true;
|
||||
fingerprint: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private publicKey: Uint8Array = null;
|
||||
|
||||
constructor(private apiService: ApiService, private cryptoService: CryptoService,
|
||||
private storageService: StorageService) { }
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private storageService: StorageService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
@@ -57,6 +65,20 @@ export class UserConfirmComponent implements OnInit {
|
||||
await this.storageService.save(ConstantsService.autoConfirmFingerprints, true);
|
||||
}
|
||||
|
||||
this.onConfirmedUser.emit(this.publicKey);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -31,11 +32,11 @@ export class UserGroupsComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, 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;
|
||||
|
||||
@@ -43,8 +44,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;
|
||||
}
|
||||
@@ -63,17 +64,18 @@ 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,6 +7,7 @@ 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';
|
||||
@@ -45,12 +46,13 @@ export class AccountComponent {
|
||||
|
||||
constructor(private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private route: ActivatedRoute,
|
||||
private syncService: SyncService, private platformUtilsService: PlatformUtilsService) { }
|
||||
private analytics: Angulartics2, 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);
|
||||
@@ -71,6 +73,7 @@ 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 { }
|
||||
}
|
||||
@@ -78,6 +81,7 @@ 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'));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
@@ -38,8 +39,8 @@ export class AdjustSeatsComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private router: Router,
|
||||
private activatedRoute: ActivatedRoute) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -62,6 +63,7 @@ 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,6 +28,7 @@ export class ChangePlanComponent {
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
this.platformUtilsService.eventTrack('Changed Plan');
|
||||
this.onChanged.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
@@ -20,8 +21,8 @@ export class DeleteOrganizationComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private router: Router) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private router: Router) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
@@ -35,6 +36,7 @@ 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,6 +32,7 @@ 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,6 +5,7 @@ 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';
|
||||
@@ -17,13 +18,14 @@ 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, toasterService: ToasterService,
|
||||
constructor(apiService: ApiService, i18nService: I18nService,
|
||||
analytics: Angulartics2, toasterService: ToasterService,
|
||||
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService) {
|
||||
super(apiService, i18nService, toasterService, platformUtilsService);
|
||||
super(apiService, i18nService, analytics, 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,6 +5,7 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { OrganizationSubscriptionResponse } from 'jslib/models/response/organizationSubscriptionResponse';
|
||||
|
||||
@@ -37,13 +38,14 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
||||
reinstatePromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService, private toasterService: ToasterService,
|
||||
private messagingService: MessagingService, private route: ActivatedRoute) {
|
||||
private i18nService: I18nService, private analytics: Angulartics2,
|
||||
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;
|
||||
@@ -73,6 +75,7 @@ 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 { }
|
||||
@@ -92,6 +95,7 @@ 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 { }
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -23,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,8 +13,8 @@ import {
|
||||
ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent,
|
||||
} from '../../tools/exposed-passwords-report.component';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
@Component({
|
||||
selector: 'app-exposed-passwords-report',
|
||||
@@ -30,7 +30,7 @@ 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();
|
||||
|
||||
@@ -5,11 +5,10 @@ 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';
|
||||
|
||||
@@ -18,32 +17,17 @@ import { ImportComponent as BaseImportComponent } from '../../tools/import.compo
|
||||
templateUrl: '../../tools/import.component.html',
|
||||
})
|
||||
export class ImportComponent extends BaseImportComponent {
|
||||
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);
|
||||
constructor(i18nService: I18nService, analytics: Angulartics2,
|
||||
toasterService: ToasterService, importService: ImportService,
|
||||
router: Router, private route: ActivatedRoute) {
|
||||
super(i18nService, analytics, toasterService, importService, router);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async params => {
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ 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();
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ToolsComponent {
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ 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();
|
||||
|
||||
@@ -20,13 +20,12 @@ 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, apiService: ApiService) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService);
|
||||
platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -32,22 +33,22 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
|
||||
protected allCiphers: CipherView[] = [];
|
||||
|
||||
constructor(searchService: SearchService, toasterService: ToasterService, i18nService: I18nService,
|
||||
constructor(searchService: SearchService, analytics: Angulartics2,
|
||||
toasterService: ToasterService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
|
||||
private apiService: ApiService, eventService: EventService, totpService: TotpService, userService: UserService) {
|
||||
super(searchService, toasterService, i18nService, platformUtilsService, cipherService,
|
||||
eventService, totpService, userService);
|
||||
super(searchService, analytics, toasterService, i18nService, platformUtilsService,
|
||||
cipherService, eventService, totpService, userService);
|
||||
}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
} else {
|
||||
this.allCiphers = (await this.cipherService.getAllDecrypted()).filter(c => c.organizationId === this.organization.id);
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await super.load(filter, this.deleted);
|
||||
return;
|
||||
}
|
||||
await this.searchService.indexCiphers(this.organization.id, this.allCiphers);
|
||||
await this.applyFilter(filter);
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
this.applyFilter(filter);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
@@ -61,8 +62,28 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
await super.search(timeout, this.allCiphers);
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
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();
|
||||
}
|
||||
|
||||
events(c: CipherView) {
|
||||
this.onEventsClicked.emit(c);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
|
||||
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,9 +29,6 @@
|
||||
</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,7 +15,6 @@ 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';
|
||||
|
||||
@@ -53,7 +52,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
collectionId: string = null;
|
||||
type: CipherType = null;
|
||||
deleted: boolean = false;
|
||||
trashCleanupWarning: string = null;
|
||||
|
||||
modal: ModalComponent = null;
|
||||
|
||||
@@ -61,20 +59,15 @@ 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 platformUtilsService: PlatformUtilsService) { }
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.trashCleanupWarning = this.i18nService.t(
|
||||
this.platformUtilsService.isSelfHost() ? 'trashCleanupWarningSelfHosted' : 'trashCleanupWarning'
|
||||
);
|
||||
|
||||
const queryParams = this.route.parent.params.subscribe(async params => {
|
||||
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.canManageAllCollections) {
|
||||
await this.syncService.fullSync(false);
|
||||
@@ -117,7 +110,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]);
|
||||
}
|
||||
@@ -242,7 +235,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
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;
|
||||
@@ -261,7 +254,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
component.organizationId = this.organization.id;
|
||||
component.type = this.type;
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
}
|
||||
if (this.collectionId != null) {
|
||||
component.collectionIds = [this.collectionId];
|
||||
@@ -304,7 +297,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
|
||||
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,5 +19,4 @@ if (process.env.ENV === 'production') {
|
||||
// Other polyfills
|
||||
require('whatwg-fetch');
|
||||
require('webcrypto-shim');
|
||||
require('date-input-polyfill');
|
||||
/* tslint:enable */
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
<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="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">Bitwarden Send</p>
|
||||
<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}}"
|
||||
@@ -67,26 +54,12 @@
|
||||
<!-- 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">
|
||||
<button class="btn btn-primary btn-block" type="button" (click)="download()">
|
||||
<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>
|
||||
|
||||
@@ -13,8 +13,8 @@ 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 { SendAccess } from 'jslib/models/domain/sendAccess';
|
||||
|
||||
import { SendAccessView } from 'jslib/models/view/sendAccessView';
|
||||
|
||||
@@ -39,12 +39,10 @@ export class AccessComponent implements OnInit {
|
||||
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,
|
||||
@@ -58,22 +56,8 @@ export class AccessComponent implements OnInit {
|
||||
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.route.params.subscribe(async (params) => {
|
||||
this.id = params.sendId;
|
||||
this.key = params.key;
|
||||
if (this.key == null || this.id == null) {
|
||||
@@ -92,16 +76,8 @@ export class AccessComponent implements OnInit {
|
||||
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' }));
|
||||
const response = await fetch(new Request(this.send.file.url, { cache: 'no-store' }));
|
||||
if (response.status !== 200) {
|
||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
|
||||
this.downloading = false;
|
||||
@@ -132,19 +108,18 @@ export class AccessComponent implements OnInit {
|
||||
async load() {
|
||||
this.unavailable = false;
|
||||
this.error = false;
|
||||
this.hideEmail = false;
|
||||
const keyArray = Utils.fromUrlB64ToArray(this.key);
|
||||
this.accessRequest = new SendAccessRequest();
|
||||
const accessRequest = new SendAccessRequest();
|
||||
if (this.password != null) {
|
||||
const passwordHash = await this.cryptoFunctionService.pbkdf2(this.password, keyArray, 'sha256', 100000);
|
||||
this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
|
||||
accessRequest.password = Utils.fromBufferToB64(passwordHash);
|
||||
}
|
||||
try {
|
||||
let sendResponse: SendAccessResponse = null;
|
||||
if (this.loading) {
|
||||
sendResponse = await this.apiService.postSendAccess(this.id, this.accessRequest);
|
||||
sendResponse = await this.apiService.postSendAccess(this.id, accessRequest);
|
||||
} else {
|
||||
this.formPromise = this.apiService.postSendAccess(this.id, this.accessRequest);
|
||||
this.formPromise = this.apiService.postSendAccess(this.id, accessRequest);
|
||||
sendResponse = await this.formPromise;
|
||||
}
|
||||
this.passwordRequired = false;
|
||||
@@ -164,6 +139,5 @@ export class AccessComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
this.hideEmail = this.creatorIdentifier == null && !this.passwordRequired && !this.loading && !this.unavailable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,48 +9,32 @@
|
||||
</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" *ngIf="!editMode">
|
||||
<div class="col-6 form-group">
|
||||
<label for="type">{{'whatTypeOfSend' | i18n}}</label>
|
||||
<select id="type" name="Type" [(ngModel)]="send.type" class="form-control" appAutofocus
|
||||
(change)='typeChanged()'>
|
||||
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="send.name" required>
|
||||
</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>
|
||||
<textarea id="text" name="Text.Text" rows="6" [(ngModel)]="send.text.text"
|
||||
class="form-control"></textarea>
|
||||
</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">
|
||||
id="text-hidden" name="Text.Hidden">
|
||||
<label class="form-check-label" for="text-hidden">{{'textHiddenByDefault' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,178 +48,96 @@
|
||||
</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>
|
||||
<input type="file" id="file" class="form-control-file" name="file" required>
|
||||
<small class="form-text text-muted">{{'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">
|
||||
<h3 class="mt-5">{{'options' | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
|
||||
<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>
|
||||
<input id="deletionDateCustom" class="form-control mt-1" type="datetime-local"
|
||||
name="DeletionDate" [(ngModel)]="deletionDate" required *ngIf="deletionDateSelect === 0"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM">
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<input id="deletionDate" class="form-control" type="datetime-local" name="DeletionDate"
|
||||
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
|
||||
</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">
|
||||
{{'clear' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<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>
|
||||
<input id="expirationDateCustom" class="form-control mt-1" type="datetime-local"
|
||||
name="ExpirationDate" [(ngModel)]="expirationDate" required
|
||||
*ngIf="expirationDateSelect === 0" placeholder="MM/DD/YYYY HH:MM AM/PM">
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<input id="expirationDate" class="form-control" type="datetime-local" name="ExpirationDate"
|
||||
[(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM">
|
||||
</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">
|
||||
<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="number" 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>
|
||||
<input id="password" class="form-control" type="password" name="Password"
|
||||
[(ngModel)]="password">
|
||||
<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"></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)]="copyLink" id="copy-link"
|
||||
name="CopyLink">
|
||||
<label class="form-check-label" for="copy-link">{{'copySendLinkOnSave' | i18n}}</label>
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="send.disabled" id="disabled"
|
||||
name="Disabled">
|
||||
<label class="form-check-label" for="disabled">{{'disableThisSend' | 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>
|
||||
<h3 class="mt-5" *ngIf="link">{{'share' | i18n}}</h3>
|
||||
<div class="form-group" *ngIf="link">
|
||||
<label for="link">{{'sendLink' | i18n}}</label>
|
||||
<input type="text" readonly id="link" name="Link" [(ngModel)]="link" class="form-control">
|
||||
</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">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,33 +1,236 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { SendType } from 'jslib/enums/sendType';
|
||||
|
||||
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';
|
||||
import { SendFileView } from 'jslib/models/view/sendFileView';
|
||||
import { SendTextView } from 'jslib/models/view/sendTextView';
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { Send } from 'jslib/models/domain/send';
|
||||
|
||||
@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);
|
||||
export class AddEditComponent {
|
||||
@Input() sendId: string;
|
||||
@Input() type: SendType;
|
||||
|
||||
@Output() onSavedSend = new EventEmitter<SendView>();
|
||||
@Output() onDeletedSend = new EventEmitter<SendView>();
|
||||
@Output() onCancelled = new EventEmitter<SendView>();
|
||||
|
||||
editMode: boolean = false;
|
||||
send: SendView;
|
||||
link: string;
|
||||
title: string;
|
||||
deletionDate: string;
|
||||
expirationDate: string;
|
||||
hasPassword: boolean;
|
||||
password: string;
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
sendType = SendType;
|
||||
typeOptions: any[];
|
||||
deletionDateOptions: any[];
|
||||
expirationDateOptions: any[];
|
||||
deletionDateSelect = 168;
|
||||
expirationDateSelect: number = null;
|
||||
canAccessPremium = true;
|
||||
premiumRequiredAlertShown = false;
|
||||
|
||||
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
||||
private environmentService: EnvironmentService, private datePipe: DatePipe,
|
||||
private sendService: SendService, private userService: UserService,
|
||||
private messagingService: MessagingService) {
|
||||
this.typeOptions = [
|
||||
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
|
||||
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
|
||||
];
|
||||
this.deletionDateOptions = this.expirationDateOptions = [
|
||||
{ name: i18nService.t('oneHour'), value: 1 },
|
||||
{ name: i18nService.t('oneDay'), value: 24 },
|
||||
{ name: i18nService.t('days', '2'), value: 48 },
|
||||
{ name: i18nService.t('days', '3'), value: 72 },
|
||||
{ name: i18nService.t('days', '7'), value: 168 },
|
||||
{ name: i18nService.t('days', '30'), value: 720 },
|
||||
{ name: i18nService.t('custom'), value: 0 },
|
||||
];
|
||||
this.expirationDateOptions = [
|
||||
{ name: i18nService.t('never'), value: null },
|
||||
].concat([...this.deletionDateOptions]);
|
||||
}
|
||||
|
||||
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);
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.editMode = this.sendId != null;
|
||||
if (this.editMode) {
|
||||
this.editMode = true;
|
||||
this.title = this.i18nService.t('editSend');
|
||||
} else {
|
||||
this.title = this.i18nService.t('createSend');
|
||||
}
|
||||
|
||||
this.canAccessPremium = await this.userService.canAccessPremium();
|
||||
if (!this.canAccessPremium) {
|
||||
this.type = SendType.Text;
|
||||
}
|
||||
|
||||
if (this.send == null) {
|
||||
if (this.editMode) {
|
||||
const send = await this.loadSend();
|
||||
this.send = await send.decrypt();
|
||||
} else {
|
||||
this.send = new SendView();
|
||||
this.send.type = this.type == null ? SendType.File : this.type;
|
||||
this.send.file = new SendFileView();
|
||||
this.send.text = new SendTextView();
|
||||
this.send.deletionDate = new Date();
|
||||
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
|
||||
}
|
||||
}
|
||||
|
||||
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
|
||||
|
||||
// Parse dates
|
||||
this.deletionDate = this.dateToString(this.send.deletionDate);
|
||||
this.expirationDate = this.dateToString(this.send.expirationDate);
|
||||
|
||||
if (this.editMode) {
|
||||
let webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
if (webVaultUrl == null) {
|
||||
webVaultUrl = 'https://vault.bitwarden.com';
|
||||
}
|
||||
this.link = webVaultUrl + '/#/send/' + this.send.accessId + '/' + this.send.urlB64Key;
|
||||
}
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
if (this.send.name == null || this.send.name === '') {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('nameRequired'));
|
||||
return false;
|
||||
}
|
||||
|
||||
let file: File = null;
|
||||
if (this.send.type === SendType.File && !this.editMode) {
|
||||
const fileEl = document.getElementById('file') as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if (files == null || files.length === 0) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('selectFile'));
|
||||
return;
|
||||
}
|
||||
|
||||
file = files[0];
|
||||
if (file.size > 104857600) { // 100 MB
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('maxFileSize'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.editMode) {
|
||||
const now = new Date();
|
||||
if (this.deletionDateSelect > 0) {
|
||||
const d = new Date();
|
||||
d.setHours(now.getHours() + this.deletionDateSelect);
|
||||
this.deletionDate = this.dateToString(d);
|
||||
}
|
||||
if (this.expirationDateSelect != null && this.expirationDateSelect > 0) {
|
||||
const d = new Date();
|
||||
d.setHours(now.getHours() + this.expirationDateSelect);
|
||||
this.expirationDate = this.dateToString(d);
|
||||
}
|
||||
}
|
||||
|
||||
const encSend = await this.encryptSend(file);
|
||||
try {
|
||||
this.formPromise = this.sendService.saveWithServer(encSend);
|
||||
await this.formPromise;
|
||||
this.send.id = encSend[0].id;
|
||||
this.platformUtilsService.showToast('success', null,
|
||||
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'));
|
||||
this.onSavedSend.emit(this.send);
|
||||
return true;
|
||||
} catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
clearExpiration() {
|
||||
this.expirationDate = null;
|
||||
}
|
||||
|
||||
async delete(): Promise<void> {
|
||||
if (this.deletePromise != null) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('deleteSendConfirmation'),
|
||||
this.i18nService.t('deleteSend'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
|
||||
await this.deletePromise;
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
|
||||
await this.load();
|
||||
this.onDeletedSend.emit(this.send);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
typeChanged() {
|
||||
if (!this.canAccessPremium && this.send.type === SendType.File && !this.premiumRequiredAlertShown) {
|
||||
this.premiumRequiredAlertShown = true;
|
||||
this.messagingService.send('premiumRequired');
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadSend(): Promise<Send> {
|
||||
return this.sendService.get(this.sendId);
|
||||
}
|
||||
|
||||
protected async encryptSend(file: File): Promise<[Send, ArrayBuffer]> {
|
||||
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
|
||||
|
||||
// Parse dates
|
||||
try {
|
||||
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
|
||||
} catch {
|
||||
sendData[0].deletionDate = null;
|
||||
}
|
||||
try {
|
||||
sendData[0].expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate);
|
||||
} catch {
|
||||
sendData[0].expirationDate = null;
|
||||
}
|
||||
|
||||
return sendData;
|
||||
}
|
||||
|
||||
protected dateToString(d: Date) {
|
||||
return d == null ? null : this.datePipe.transform(d, 'yyyy-MM-ddTHH:mm');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
<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">
|
||||
@@ -43,7 +35,7 @@
|
||||
<div class="col-9">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{'send' | i18n}}
|
||||
Send
|
||||
<small #actionSpinner [appApiAction]="actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
@@ -53,8 +45,7 @@
|
||||
</small>
|
||||
</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addSend()"
|
||||
[disabled]="disableSend">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addSend()">
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'createSend' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -71,11 +62,9 @@
|
||||
</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>
|
||||
<span appStopClick class="badge badge-secondary" *ngIf="s.disabled">
|
||||
{{'disabled' | i18n}}
|
||||
</span>
|
||||
<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>
|
||||
@@ -86,8 +75,7 @@
|
||||
<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>
|
||||
<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">
|
||||
@@ -111,7 +99,7 @@
|
||||
{{'copySendLink' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="removePassword(s)"
|
||||
*ngIf="s.password && !disableSend">
|
||||
*ngIf="s.password">
|
||||
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
|
||||
{{'removePassword' | i18n}}
|
||||
</a>
|
||||
@@ -132,7 +120,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p>{{'noSendsInList' | i18n}}</p>
|
||||
<button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend">
|
||||
<button (click)="addSend()" class="btn btn-outline-primary">
|
||||
<i class="fa fa-plus fa-fw"></i>{{'createSend' | i18n}}</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,75 +1,105 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
NgZone,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
import { SendType } from 'jslib/enums/sendType';
|
||||
|
||||
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
|
||||
import { SendView } from 'jslib/models/view/sendView';
|
||||
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
|
||||
import { ModalComponent } from '../modal.component';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
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 {
|
||||
export class SendComponent implements OnInit {
|
||||
@ViewChild('sendAddEdit', { read: ViewContainerRef, static: true }) sendAddEditModalRef: ViewContainerRef;
|
||||
|
||||
modal: ModalComponent = null;
|
||||
sendType = SendType;
|
||||
loaded = false;
|
||||
loading = true;
|
||||
refreshing = false;
|
||||
expired: boolean = false;
|
||||
type: SendType = null;
|
||||
sends: SendView[] = [];
|
||||
filteredSends: SendView[] = [];
|
||||
searchText: string;
|
||||
selectedType: SendType;
|
||||
selectedAll: boolean;
|
||||
searchPlaceholder: string;
|
||||
filter: (cipher: SendView) => boolean;
|
||||
searchPending = false;
|
||||
|
||||
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);
|
||||
}
|
||||
modal: ModalComponent = null;
|
||||
actionPromise: any;
|
||||
|
||||
private searchTimeout: any;
|
||||
|
||||
constructor(private apiService: ApiService, private sendService: SendService,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService) { }
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
async load(filter: (send: SendView) => boolean = null) {
|
||||
this.loading = true;
|
||||
const sends = await this.sendService.getAllDecrypted();
|
||||
this.sends = sends;
|
||||
this.selectAll();
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
async reload(filter: (send: SendView) => boolean = null) {
|
||||
this.loaded = false;
|
||||
this.sends = [];
|
||||
await this.load(filter);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
this.refreshing = true;
|
||||
await this.reload(this.filter);
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async applyFilter(filter: (send: SendView) => boolean = null) {
|
||||
this.filter = filter;
|
||||
await this.search(null);
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
this.searchPending = false;
|
||||
if (this.searchTimeout != null) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
}
|
||||
if (timeout == null) {
|
||||
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
|
||||
return;
|
||||
}
|
||||
this.searchPending = true;
|
||||
this.searchTimeout = setTimeout(async () => {
|
||||
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
|
||||
this.searchPending = false;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
addSend() {
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
|
||||
const component = this.editSend(null);
|
||||
component.type = this.type;
|
||||
}
|
||||
@@ -100,4 +130,78 @@ export class SendComponent extends BaseSendComponent {
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
async removePassword(s: SendView): Promise<boolean> {
|
||||
if (this.actionPromise != null || s.password == null) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('removePasswordConfirmation'),
|
||||
this.i18nService.t('removePassword'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword'));
|
||||
await this.load();
|
||||
} catch { }
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async delete(s: SendView): Promise<boolean> {
|
||||
if (this.actionPromise != null) {
|
||||
return false;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('deleteSendConfirmation'),
|
||||
this.i18nService.t('deleteSend'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.sendService.deleteWithServer(s.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
|
||||
await this.load();
|
||||
} catch { }
|
||||
this.actionPromise = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
copy(s: SendView) {
|
||||
let webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
if (webVaultUrl == null) {
|
||||
webVaultUrl = 'https://vault.bitwarden.com';
|
||||
}
|
||||
const link = webVaultUrl + '/#/send/' + s.accessId + '/' + s.urlB64Key;
|
||||
this.platformUtilsService.copyToClipboard(link);
|
||||
this.platformUtilsService.showToast('success', null,
|
||||
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.search(200);
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.clearSelections();
|
||||
this.selectedAll = true;
|
||||
this.applyFilter(null);
|
||||
}
|
||||
|
||||
selectType(type: SendType) {
|
||||
this.clearSelections();
|
||||
this.selectedType = type;
|
||||
this.applyFilter((s) => s.type === type);
|
||||
}
|
||||
|
||||
clearSelections() {
|
||||
this.selectedAll = false;
|
||||
this.selectedType = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,13 +155,6 @@ export class EventService {
|
||||
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');
|
||||
|
||||
@@ -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,13 +16,14 @@ 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';
|
||||
@@ -36,7 +37,6 @@ 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';
|
||||
@@ -64,7 +64,6 @@ 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';
|
||||
@@ -107,15 +106,14 @@ 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, fileUploadService, storageService, i18nService, () => searchService);
|
||||
apiService, 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, consoleLogService, i18nService);
|
||||
searchService = new SearchService(cipherService, consoleLogService);
|
||||
const policyService = new PolicyService(userService, storageService);
|
||||
const sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
|
||||
const sendService = new SendService(cryptoService, userService, apiService, storageService,
|
||||
i18nService, cryptoFunctionService);
|
||||
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
|
||||
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
|
||||
@@ -130,33 +128,42 @@ const authService = new AuthService(cryptoService, apiService,
|
||||
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
consoleLogService);
|
||||
const exportService = new ExportService(folderService, cipherService, apiService);
|
||||
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService,
|
||||
platformUtilsService);
|
||||
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
|
||||
const notificationsService = new NotificationsService(userService, syncService, appIdService,
|
||||
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 = 'https://notifications.bitwarden.com';
|
||||
environmentService.enterpriseUrl = 'https://portal.bitwarden.com';
|
||||
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';
|
||||
}
|
||||
|
||||
apiService.setUrls({
|
||||
base: window.location.origin,
|
||||
api: null,
|
||||
identity: null,
|
||||
events: null,
|
||||
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',
|
||||
});
|
||||
setTimeout(() => notificationsService.init(environmentService), 3000);
|
||||
|
||||
@@ -191,7 +198,6 @@ export function initFactory(): Function {
|
||||
UnauthGuardService,
|
||||
RouterService,
|
||||
EventService,
|
||||
LockGuardService,
|
||||
{ provide: AuditServiceAbstraction, useValue: auditService },
|
||||
{ provide: AuthServiceAbstraction, useValue: authService },
|
||||
{ provide: CipherServiceAbstraction, useValue: cipherService },
|
||||
@@ -205,7 +211,6 @@ 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 },
|
||||
|
||||
29
src/app/services/unauth-guard.service.ts
Normal file
29
src/app/services/unauth-guard.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
CanActivate,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
@Injectable()
|
||||
export class UnauthGuardService implements CanActivate {
|
||||
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService,
|
||||
private router: Router) { }
|
||||
|
||||
async canActivate() {
|
||||
const isAuthed = await this.userService.isAuthenticated();
|
||||
if (isAuthed) {
|
||||
const locked = await this.vaultTimeoutService.isLocked();
|
||||
if (locked) {
|
||||
this.router.navigate(['lock']);
|
||||
} else {
|
||||
this.router.navigate(['vault']);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
@@ -46,6 +49,7 @@ export class AddCreditComponent implements OnInit {
|
||||
private email: string;
|
||||
|
||||
constructor(private userService: UserService, private apiService: ApiService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private platformUtilsService: PlatformUtilsService) {
|
||||
if (platformUtilsService.isDev()) {
|
||||
this.ppButtonFormAction = WebConstants.paypal.buttonActionSandbox;
|
||||
@@ -104,6 +108,9 @@ export class AddCreditComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.analytics.eventTrack.next({
|
||||
action: 'Added Credit',
|
||||
});
|
||||
this.onAdded.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -35,12 +36,12 @@ export class AdjustPaymentComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new PaymentRequest();
|
||||
this.formPromise = this.paymentComponent.createPaymentToken().then(result => {
|
||||
this.formPromise = this.paymentComponent.createPaymentToken().then((result) => {
|
||||
request.paymentToken = result[0];
|
||||
request.paymentMethodType = result[1];
|
||||
request.postalCode = this.taxInfoComponent.taxInfo.postalCode;
|
||||
@@ -58,6 +59,9 @@ export class AdjustPaymentComponent {
|
||||
}
|
||||
});
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({
|
||||
action: this.currentType == null ? 'Added Payment Method' : 'Changed Payment Method',
|
||||
});
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('updatedPaymentMethod'));
|
||||
this.onAdjusted.emit();
|
||||
} catch { }
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
@@ -40,8 +41,8 @@ export class AdjustStorageComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private router: Router,
|
||||
private activatedRoute: ActivatedRoute) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -70,6 +71,7 @@ export class AdjustStorageComponent {
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Storage' : 'Removed Storage' });
|
||||
this.onAdjusted.emit(this.storageAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -29,8 +30,8 @@ export class ApiKeyComponent {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
|
||||
constructor(private i18nService: I18nService, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService) { }
|
||||
constructor(private i18nService: I18nService, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
@@ -46,6 +47,7 @@ export class ApiKeyComponent {
|
||||
const response = await this.formPromise;
|
||||
this.clientSecret = response.apiKey;
|
||||
this.clientId = `${this.keyType}.${this.entityId}`;
|
||||
this.analytics.eventTrack.next({ action: `Viewed ${this.keyType} API Key` });
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
@@ -26,8 +27,9 @@ export class ChangeEmailComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService, private userService: UserService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService,
|
||||
private userService: UserService) { }
|
||||
|
||||
async submit() {
|
||||
const hasEncKey = await this.cryptoService.hasEncKey();
|
||||
@@ -61,6 +63,7 @@ export class ChangeEmailComponent {
|
||||
this.formPromise = this.apiService.postEmail(request);
|
||||
await this.formPromise;
|
||||
this.reset();
|
||||
this.analytics.eventTrack.next({ action: 'Changed Email' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('emailChanged'),
|
||||
this.i18nService.t('logBackIn'));
|
||||
this.messagingService.send('logout');
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="col-6">
|
||||
<div class="form-group mb-0">
|
||||
<label for="kdfIterations">{{'kdfIterations' | i18n}}</label>
|
||||
<a class="ml-auto" href="https://bitwarden.com/help/article/what-encryption-is-used/#pbkdf2" target="_blank" rel="noopener"
|
||||
<a class="ml-auto" href="https://en.wikipedia.org/wiki/PBKDF2" target="_blank" rel="noopener"
|
||||
appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -27,8 +28,9 @@ export class ChangeKdfComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService, private userService: UserService) {
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService,
|
||||
private userService: UserService) {
|
||||
this.kdfOptions = [
|
||||
{ name: 'PBKDF2 SHA-256', value: KdfType.PBKDF2_SHA256 },
|
||||
];
|
||||
@@ -58,6 +60,7 @@ export class ChangeKdfComponent implements OnInit {
|
||||
try {
|
||||
this.formPromise = this.apiService.postAccountKdf(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Changed KDF' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('encKeySettingsChanged'),
|
||||
this.i18nService.t('logBackIn'));
|
||||
this.messagingService.send('logout');
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<label class="form-check-label" for="rotateEncKey">
|
||||
{{'rotateAccountEncKey' | i18n}}
|
||||
</label>
|
||||
<a href="https://bitwarden.com/help/article/account-encryption-key/#rotate-your-encryption-key"
|
||||
<a href="https://help.bitwarden.com/article/change-your-master-password/#rotating-your-accounts-encryption-key"
|
||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@@ -19,13 +19,12 @@ import {
|
||||
import { EmergencyAccessStatusType } from 'jslib/enums/emergencyAccessStatusType';
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
import { EncString } from 'jslib/models/domain/encString';
|
||||
import { CipherString } from 'jslib/models/domain/cipherString';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
|
||||
import { CipherWithIdRequest } from 'jslib/models/request/cipherWithIdRequest';
|
||||
import { EmergencyAccessUpdateRequest } from 'jslib/models/request/emergencyAccessUpdateRequest';
|
||||
import { FolderWithIdRequest } from 'jslib/models/request/folderWithIdRequest';
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib/models/request/organizationUserResetPasswordEnrollmentRequest';
|
||||
import { PasswordRequest } from 'jslib/models/request/passwordRequest';
|
||||
import { UpdateKeyRequest } from 'jslib/models/request/updateKeyRequest';
|
||||
|
||||
@@ -42,7 +41,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
userService: UserService, passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
|
||||
private folderService: FolderService, private cipherService: CipherService,
|
||||
private syncService: SyncService, private apiService: ApiService) {
|
||||
private syncService: SyncService, private apiService: ApiService ) {
|
||||
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
|
||||
platformUtilsService, policyService);
|
||||
}
|
||||
@@ -108,7 +107,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
}
|
||||
|
||||
async performSubmitActions(newMasterPasswordHash: string, newKey: SymmetricCryptoKey,
|
||||
newEncKey: [SymmetricCryptoKey, EncString]) {
|
||||
newEncKey: [SymmetricCryptoKey, CipherString]) {
|
||||
const request = new PasswordRequest();
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.currentMasterPassword, null);
|
||||
request.newMasterPasswordHash = newMasterPasswordHash;
|
||||
@@ -136,7 +135,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
private async updateKey(key: SymmetricCryptoKey, masterPasswordHash: string) {
|
||||
const encKey = await this.cryptoService.makeEncKey(key);
|
||||
const privateKey = await this.cryptoService.getPrivateKey();
|
||||
let encPrivateKey: EncString = null;
|
||||
let encPrivateKey: CipherString = null;
|
||||
if (privateKey != null) {
|
||||
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
|
||||
}
|
||||
@@ -167,8 +166,6 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
await this.apiService.postAccountKey(request);
|
||||
|
||||
await this.updateEmergencyAccesses(encKey[0]);
|
||||
|
||||
await this.updateAllResetPasswordKeys(encKey[0]);
|
||||
}
|
||||
|
||||
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
|
||||
@@ -195,25 +192,4 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
await this.apiService.putEmergencyAccess(details.id, updateRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey) {
|
||||
const orgs = await this.userService.getAllOrganizations();
|
||||
|
||||
for (const org of orgs) {
|
||||
// If not already enrolled, skip
|
||||
if (!org.isResetPasswordEnrolled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Re-enroll - encrpyt user's encKey.key with organization key
|
||||
const orgSymKey = await this.cryptoService.getOrgKey(org.id);
|
||||
const encryptedKey = await this.cryptoService.encrypt(encKey.key, orgSymKey);
|
||||
|
||||
// Create/Execute request
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.resetPasswordKey = encryptedKey.encryptedString;
|
||||
|
||||
await this.apiService.putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CreateOrganizationComponent implements OnInit {
|
||||
constructor(private route: ActivatedRoute) { }
|
||||
|
||||
ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
if (qParams.plan === 'families') {
|
||||
this.orgPlansComponent.plan = PlanType.FamiliesAnnually;
|
||||
this.orgPlansComponent.product = ProductType.Families;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } 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';
|
||||
@@ -18,8 +19,8 @@ export class DeauthorizeSessionsComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
@@ -33,6 +34,7 @@ export class DeauthorizeSessionsComponent {
|
||||
try {
|
||||
this.formPromise = this.apiService.postSecurityStamp(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deauthorized Sessions' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('sessionsDeauthorized'),
|
||||
this.i18nService.t('logBackIn'));
|
||||
this.messagingService.send('logout');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } 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';
|
||||
@@ -18,8 +19,8 @@ export class DeleteAccountComponent {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService) { }
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
@@ -33,6 +34,7 @@ export class DeleteAccountComponent {
|
||||
try {
|
||||
this.formPromise = this.apiService.deleteAccount(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Account' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
|
||||
this.i18nService.t('accountDeletedDesc'));
|
||||
this.messagingService.send('logout');
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -21,16 +22,16 @@ export class DomainRulesComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
const response = await this.apiService.getSettingsDomains();
|
||||
this.loading = false;
|
||||
if (response.equivalentDomains != null) {
|
||||
this.custom = response.equivalentDomains.map(d => d.join(', '));
|
||||
this.custom = response.equivalentDomains.map((d) => d.join(', '));
|
||||
}
|
||||
if (response.globalEquivalentDomains != null) {
|
||||
this.global = response.globalEquivalentDomains.map(d => {
|
||||
this.global = response.globalEquivalentDomains.map((d) => {
|
||||
return {
|
||||
domains: d.domains.join(', '),
|
||||
excluded: d.excluded,
|
||||
@@ -59,13 +60,13 @@ export class DomainRulesComponent implements OnInit {
|
||||
|
||||
async submit() {
|
||||
const request = new UpdateDomainsRequest();
|
||||
request.excludedGlobalEquivalentDomains = this.global.filter(d => d.excluded)
|
||||
.map(d => d.key);
|
||||
request.excludedGlobalEquivalentDomains = this.global.filter((d) => d.excluded)
|
||||
.map((d) => d.key);
|
||||
if (request.excludedGlobalEquivalentDomains.length === 0) {
|
||||
request.excludedGlobalEquivalentDomains = null;
|
||||
}
|
||||
request.equivalentDomains = this.custom.filter(d => d != null && d.trim() !== '')
|
||||
.map(d => d.split(',').map(d2 => d2.trim()));
|
||||
request.equivalentDomains = this.custom.filter((d) => d != null && d.trim() !== '')
|
||||
.map((d) => d.split(',').map((d2) => d2.trim()));
|
||||
if (request.equivalentDomains.length === 0) {
|
||||
request.equivalentDomains = null;
|
||||
}
|
||||
@@ -73,6 +74,7 @@ export class DomainRulesComponent implements OnInit {
|
||||
try {
|
||||
this.formPromise = this.apiService.putSettingsDomains(request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Saved Equivalent Domains' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('domainsUpdated'));
|
||||
} catch { }
|
||||
}
|
||||
|
||||
@@ -55,11 +55,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button #submitBtn type="submit" class="btn btn-primary"
|
||||
[disabled]="loading || submitBtn.loading || readOnly" [appApiAction]="formPromise">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"
|
||||
*ngIf="loading || submitBtn.loading"></i>
|
||||
<span *ngIf="!loading && !submitBtn.loading">{{'save' | i18n}}</span>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="loading || readOnly">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="loading"></i>
|
||||
<span *ngIf="!loading">{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user