Merge branch 'master' of github.com:bitwarden/desktop into hollow-frenk/master

This commit is contained in:
Hinton 2022-03-26 17:55:42 +01:00
commit f5ecfbf361
255 changed files with 57834 additions and 24785 deletions

View File

@ -12,4 +12,4 @@ insert_final_newline = true
[*.{js,ts,scss,html}]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2

8
.eslintignore Normal file
View File

@ -0,0 +1,8 @@
dist
build
jslib
webpack.main.js
webpack.renderer.js
src/scripts/duo.js
**/node_modules

32
.eslintrc.json Normal file
View File

@ -0,0 +1,32 @@
{
"root": true,
"env": {
"browser": true,
"node": true
},
"extends": ["./jslib/shared/eslintrc.json"],
"rules": {
"import/order": [
"error",
{
"alphabetize": {
"order": "asc"
},
"newlines-between": "always",
"pathGroups": [
{
"pattern": "jslib-*/**",
"group": "external",
"position": "after"
},
{
"pattern": "src/**/*",
"group": "parent",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"]
}
]
}
}

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# Apply Prettier https://github.com/bitwarden/desktop/pull/1202
521feae535d83166e620c3c28dfc3e7b0314a00e

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

85
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@ -0,0 +1,85 @@
name: Bug Report
description: File a bug report
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce
attributes:
label: Steps To Reproduce
description: How can we reproduce the behavior.
value: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. Click on '...'
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Result
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Result
description: A clear and concise description of what is happening.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots or Videos
description: If applicable, add screenshots and/or a short video to help explain your problem.
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you seeing the problem on?
multiple: true
options:
- Windows
- macOS
- Linux
validations:
required: true
- type: input
id: os-version
attributes:
label: Operating System Version
description: What version of the operating system(s) are you seeing the problem on?
- type: dropdown
id: install-method
attributes:
label: Installation method
multiple: true
options:
- Direct Download (from bitwarden.com)
- Mac App Store
- Microsoft Store
- Homebrew
- Chocolatey
- Snap
- Other
validations:
required: true
- type: input
id: version
attributes:
label: Build Version
description: What version of our software are you running? (go to "Help" → "About Bitwarden" in the app)
validations:
required: true

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Feature Requests
url: https://community.bitwarden.com/c/feature-requests/
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
- name: Bitwarden Community Forums
url: https://community.bitwarden.com
about: Please visit the community forums for general community discussion, support and the development roadmap.
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.
- name: Security Issues
url: https://hackerone.com/bitwarden
about: We use HackerOne to manage security disclosures.

32
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,32 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
- **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@ -1,29 +0,0 @@
param (
[Parameter(Mandatory=$true)]
[string] $filename,
[string] $output
)
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$rootPath = $env:GITHUB_WORKSPACE
$secretInputPath = $rootPath + "/.github/secrets"
$input = $secretInputPath + "/" + $filename
$passphrase = $env:DECRYPT_FILE_PASSWORD
$secretOutputPath = $homePath + "/secrets"
if ([string]::IsNullOrEmpty($output)) {
if ($filename.EndsWith(".gpg")) {
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
} else {
$output = $secretOutputPath + "/" + $filename + ".plaintext"
}
}
if (!(Test-Path -Path $secretOutputPath))
{
New-Item -ItemType Directory -Path $secretOutputPath
}
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input

View File

@ -1,5 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$packageVersion = (Get-Content -Raw -Path $rootPath\src\package.json | ConvertFrom-Json).version;
Write-Output "Setting package version to $packageVersion";
Write-Output "PACKAGE_VERSION=$packageVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;

View File

@ -1,11 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden-desktop-key.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename appstore-app-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename appstore-installer-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename devid-app-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename devid-installer-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename macdev-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden_desktop_appstore.provisionprofile.gpg"

View File

@ -1,8 +0,0 @@
$rootPath = $env:GITHUB_WORKSPACE;
$packagePath = "$rootPath\package.json";
$buildNumber = 500 + [int]$env:GITHUB_RUN_NUMBER;
Write-Output "Setting build number to $buildNumber";
Write-Output "BUILD_NUMBER=$buildNumber" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;
$package = Get-Content -Raw -Path $packagePath | ConvertFrom-Json;
$package.build | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$buildNumber";
$package | ConvertTo-Json -Depth 32 | Set-Content $packagePath;

View File

@ -1,21 +0,0 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$desktopKeyPath = $($secretsPath + "/bitwarden-desktop-key.p12");
$devidAppCertPath = $($secretsPath + "/devid-app-cert.p12");
$devidInstallerCertPath = $($secretsPath + "/devid-installer-cert.p12");
$appstoreAppCertPath = $($secretsPath + "/appstore-app-cert.p12");
$appstoreInstallerCertPath = $($secretsPath + "/appstore-installer-cert.p12");
$macdevCertPath = $($secretsPath + "/macdev-cert.p12");
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $desktopKeyPath -k build.keychain -P $env:DESKTOP_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import $devidAppCertPath -k build.keychain -P $env:DEVID_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import $devidInstallerCertPath -k build.keychain -P $env:DEVID_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import $appstoreAppCertPath -k build.keychain -P $env:APPSTORE_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import $appstoreInstallerCertPath -k build.keychain -P $env:APPSTORE_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security import $macdevCertPath -k build.keychain -P $env:MACDEV_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain

View File

@ -1,6 +0,0 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$rootPath = $env:GITHUB_WORKSPACE
$pprofile = "bitwarden_desktop_appstore.provisionprofile"
Copy-Item "$secretsPath/$pprofile" -destination "$rootPath/$pprofile"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

49
.github/workflows/crowndin-pull.yml vendored Normal file
View File

@ -0,0 +1,49 @@
---
name: Crowdin Sync
on:
workflow_dispatch:
inputs: {}
schedule:
- cron: "0 0 * * 5"
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-20.04
env:
_CROWDIN_PROJECT_ID: "299360"
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- 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: "crowdin-api-token"
- name: Download translations
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml
crowdin_branch_name: master
upload_sources: false
upload_translations: false
download_translations: true
github_user_name: "github-actions"
github_user_email: "<>"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"

View File

@ -1,157 +0,0 @@
name: Deploy
on:
workflow_dispatch:
inputs:
release_tag_name_input:
description: "Release Tag Name <X.X.X>"
required: true
release:
types:
- published
jobs:
setup:
runs-on: ubuntu-latest
outputs:
package_version: ${{ steps.create_tags.outputs.package_version }}
tag_version: ${{ steps.create_tags.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Create Deploy version vars
id: create_tags
run: |
if ! [[ "${{ github.event_name }}" -eq "release" ]]; then
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
else
TAG_VERSION=$(echo ${{ github.ref }} | cut -d "/" -f 3)
PKG_VERSION=${TAG_VERSION:1}
echo "::set-output name=package_version::$PKG_VERSION"
echo "::set-output name=tag_version::$TAG_VERSION"
fi
env:
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
snap:
name: Deploy Snap
runs-on: ubuntu-latest
needs: setup
env:
PKG_VERSION: ${{ needs.setup.outputs.package_version }}
TAG_VERSION: ${{ needs.setup.ouputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Install Snap
uses: samuelmeuli/action-snapcraft@v1
with:
snapcraft_token: ${{ secrets.SNAP_TOKEN }}
- name: setup
run: mkdir dist
- name: get snap package
uses: Xotl/cool-github-releases@v1
with:
mode: download
tag_name: ${{ env.TAG_VERSION }}
assets: bitwarden_${{ env.PKG_VERSION }}_amd64.snap|./dist/bitwarden_${{ env.PKG_VERSION }}_amd64.snap
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: test
run: ls -alht dist
- name: Deploy to Snap Store
run: |
snapcraft upload dist/bitwarden_${{ env.PACKAGE_VERSION }}_amd64.snap --release stable
snapcraft logout
choco:
name: Deploy Choco
runs-on: windows-latest
needs: setup
env:
PKG_VERSION: ${{ needs.setup.outputs.package_version }}
TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Get choco release asset
uses: dsaltares/fetch-gh-release-asset@0.0.5
with:
version: tags/${{ env.TAG_VERSION }}
file: bitwarden.${{ env.PKG_VERSION }}.nupkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Chocolatey
run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
- name: make dist dir
shell: pwsh
run: New-Item -ItemType directory -Path ./dist
- name: Get nupkg
uses: Xotl/cool-github-releases@v1
with:
mode: download
tag_name: ${{ env.TAG_VERSION }}
assets: bitwarden.${{ env.PKG_VERSION }}.nupkg|./dist/bitwarden.${{ env.PKG_VERSION }}.nupkg
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Push to Chocolatey
shell: pwsh
run: |
cd dist
choco push
macos:
runs-on: macos-latest
needs: setup
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: make target directory
run: mkdir -p dist/mas
- name: Get mac release asset
uses: Xotl/cool-github-releases@v1
with:
mode: download
tag_name: ${{ env.TAG_VERSION }}
assets: Bitwarden-${{ env.PACKAGE_VERSION }}.pkg|./dist/mas/Bitwarden-${{ env.PACKAGE_VERSION }}.pkg
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to App Store
run: npm run upload:mas
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}

16
.github/workflows/enforce-labels.yml vendored Normal file
View File

@ -0,0 +1,16 @@
---
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@ -1,371 +1,195 @@
---
name: Release
on:
workflow_dispatch:
inputs:
release_tag_name_input:
description: 'Release Tag Name <X.X.X>'
release_type:
description: 'Release Options'
required: true
browser_extension_ref:
description: 'Browser Extension ref (defaults to `master`):'
default: master
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
- Dry Run
jobs:
setup:
runs-on: ubuntu-latest
name: Setup
runs-on: ubuntu-20.04
outputs:
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
package_version: ${{ steps.retrieve-version.outputs.package_version }}
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Create Release Vars
id: create_tags
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
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
;;
[0-9])
echo "RELEASE_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
echo "RELEASE_TAG_NAME=v$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
;;
*)
exit 1
;;
esac
env:
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "==================================="
exit 1
fi
- name: Create Draft Release
id: create_release
uses: actions/create-release@v1
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Get Package Version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version src/package.json)
echo "::set-output name=package_version::$PKG_VERSION"
- name: Check to make sure Desktop release version has been bumped
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-version.outputs.package_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ] && \
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
echo "Version has not been bumped!"
exit 1
fi
- name: Get branch name
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
tag_name: ${{ env.RELEASE_TAG_NAME }}
release_name: ${{ env.RELEASE_NAME }}
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- name: Rename .pkg to .pkg.archive
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "Bitwarden-${{ env.PKG_VERSION }}-amd64.deb,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm,
Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd,
bitwarden_${{ env.PKG_VERSION }}_amd64.snap,
Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage,
latest-linux.yml,
Bitwarden-Portable-${{ env.PKG_VERSION }}.exe,
Bitwarden-Installer-${{ env.PKG_VERSION }}.exe,
Bitwarden-${{ env.PKG_VERSION }}-ia32-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.appx,
Bitwarden-${{ env.PKG_VERSION }}-ia32.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-x64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.appx,
Bitwarden-${{ env.PKG_VERSION }}-x64.nsis.7z,
Bitwarden-${{ env.PKG_VERSION }}-arm64-store.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.appx,
Bitwarden-${{ env.PKG_VERSION }}-arm64.nsis.7z,
bitwarden.${{ env.PKG_VERSION }}.nupkg,
latest.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal-mac.zip,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg,
Bitwarden-${{ env.PKG_VERSION }}-universal.dmg.blockmap,
latest-mac.yml,
Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
prerelease: false
linux:
runs-on: ubuntu-latest
snap:
name: Deploy Snap
runs-on: ubuntu-20.04
needs: setup
env:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Set up Node
uses: actions/setup-node@v1
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
node-version: '10.x'
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "snapcraft-store-token"
- name: Set up environment
- name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
with:
snapcraft_token: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }}
- name: Setup
run: mkdir dist
- name: Download Snap artifact
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: bitwarden_${{ env._PKG_VERSION }}_amd64.snap
path: ./dist
- name: Test
run: ls -alht dist
- name: Deploy to Snap Store
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm
snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout
- name: Print environment
run: |
node --version
npm --version
- name: Checkout repo
uses: actions/checkout@v2
- name: Load package version
run: ./.github/scripts/load-version.ps1
shell: pwsh
- name: Install Node dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Build & Publish
run: npm run publish:lin
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows-signed:
runs-on: windows-latest
choco:
name: Deploy Choco
runs-on: windows-2019
needs: setup
env:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Set up dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
- name: Install AST
shell: pwsh
run: |
cd $HOME
git clone https://github.com/vcsjones/AzureSignTool.git
cd AzureSignTool
$latest_head = $(git rev-parse HEAD)[0..9] -join ""
$latest_version = "0.0.0-g$latest_head"
Write-Host "--------"
Write-Host "git commit - $(git rev-parse HEAD)"
Write-Host "latest_head - $latest_head"
Write-Host "PACKAGE VERSION TO BUILD - $latest_version"
Write-Host "--------"
dotnet restore
dotnet pack --output ./nupkg
dotnet tool install --global --ignore-failed-sources --add-source ./nupkg --version $latest_version azuresigntool
- name: Set up environment
shell: pwsh
run: |
choco install checksum --no-progress
choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/
- name: Setup Chocolatey
run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
- name: Print environment
run: |
node --version
npm --version
choco --version
- name: Checkout repo
uses: actions/checkout@v2
- name: Load package version
run: ./.github/scripts/load-version.ps1
- name: Make dist dir
shell: pwsh
run: New-Item -ItemType directory -Path ./dist
- name: Install Node dependencies
run: npm install
- name: Download choco artifact
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: bitwarden.${{ env._PKG_VERSION }}.nupkg
path: ./dist
- name: Run linter
run: npm run lint
- name: Build, Sign & Release
run: npm run publish:win
env:
ELECTRON_BUILDER_SIGN: 1
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package Chocolatey
- name: Push to Chocolatey
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
shell: pwsh
run: |
Copy-Item -Path ./stores/chocolatey -Destination ./dist/chocolatey -Recurse
Copy-Item -Path ./dist/nsis-web/Bitwarden-Installer-${{ env.PACKAGE_VERSION }}.exe -Destination ./dist/chocolatey
$checksum = checksum -t sha256 ./dist/chocolatey/Bitwarden-Installer-${{ env.PACKAGE_VERSION }}.exe
$chocoInstall = "./dist/chocolatey/tools/chocolateyinstall.ps1"
(Get-Content $chocoInstall).replace('__version__', "$env:PACKAGE_VERSION").replace('__checksum__', $checksum) | Set-Content $chocoInstall
ls -alht dist/chocolatey
choco pack ./dist/chocolatey/bitwarden.nuspec --version "$env:PACKAGE_VERSION" --out ./dist/chocolatey
cd ./dist/chocolatey
ls -alht dist/chocolatey
- name: Upload Chocolatey nupkg release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.setup.outputs.release_upload_url }}
asset_name: bitwarden.${{ env.PACKAGE_VERSION }}.nupkg
asset_path: ./dist/chocolatey/bitwarden.${{ env.PACKAGE_VERSION }}.nupkg
asset_content_type: application
windows-store:
runs-on: windows-latest
needs: setup
steps:
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh
- name: Set up environment
shell: pwsh
run: |
choco install checksum --no-progress
- name: Print environment
run: |
node --version
npm --version
choco --version
- name: Checkout repo
uses: actions/checkout@v2
- name: Load package version
run: ./.github/scripts/load-version.ps1
shell: pwsh
- name: Install Node dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Build, Sign & Release
run: npm run dist:win:ci
- name: Upload unsigned ia32 Windows Store release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.setup.outputs.release_upload_url }}
asset_name: Bitwarden-${{ env.PACKAGE_VERSION }}-ia32-store.appx
asset_path: ./dist/Bitwarden-${{ env.PACKAGE_VERSION }}-ia32.appx
asset_content_type: application
- name: Upload unsigned x64 Windows Store release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.setup.outputs.release_upload_url }}
asset_name: Bitwarden-${{ env.PACKAGE_VERSION }}-x64-store.appx
asset_path: ./dist/Bitwarden-${{ env.PACKAGE_VERSION }}-x64.appx
asset_content_type: application
- name: Upload unsigned ARM64 Windows Store release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.setup.outputs.release_upload_url }}
asset_name: Bitwarden-${{ env.PACKAGE_VERSION }}-arm64-store.appx
asset_path: ./dist/Bitwarden-${{ env.PACKAGE_VERSION }}-arm64.appx
asset_content_type: application
macos:
runs-on: macos-latest
needs: setup
steps:
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Set Node options
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
- name: Print environment
run: |
node --version
npm --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
- name: Decrypt secrets
run: ./.github/scripts/macos/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Set up keychain
run: ./.github/scripts/macos/setup-keychain.ps1
shell: pwsh
env:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
DESKTOP_KEY_PASSWORD: ${{ secrets.DESKTOP_KEY_PASSWORD }}
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
APPSTORE_CERT_PASSWORD: ${{ secrets.APPSTORE_CERT_PASSWORD }}
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Set up provisioning profiles
run: ./.github/scripts/macos/setup-profiles.ps1
shell: pwsh
- name: Increment version
run: ./.github/scripts/macos/increment-version.ps1
shell: pwsh
- name: Load package version
run: ./.github/scripts/load-version.ps1
shell: pwsh
- name: Install Node dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Create Safari directory
shell: pwsh
run: New-Item ./dist-safari -ItemType Directory -ea 0
- name: Checkout browser extension
uses: actions/checkout@v2
with:
repository: 'bitwarden/browser'
ref: ${{ github.event.inputs.browser_extension_ref }}
path: 'dist-safari/browser'
- name: Build Safari extension
shell: pwsh
run: ./scripts/safari-build.ps1 -skipcheckout -skipoutcopy
- name: Load Safari extension for .dmg
shell: pwsh
run: ./scripts/safari-build.ps1 -copyonly
- name: Build application (dist)
run: npm run publish:mac
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Load Safari extension for App Store
shell: pwsh
run: ./scripts/safari-build.ps1 -mas -copyonly
- name: Build application for App Store
run: npm run dist:mac:mas
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Upload Apple Store release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.setup.outputs.release_upload_url }}
asset_name: Bitwarden-${{ env.PACKAGE_VERSION }}.pkg
asset_path: ./dist/mas/Bitwarden-${{ env.PACKAGE_VERSION }}.pkg
asset_content_type: application
cd dist
choco push

65
.github/workflows/version-bump.yml vendored Normal file
View File

@ -0,0 +1,65 @@
---
name: Version Bump
on:
workflow_dispatch:
inputs:
version_number:
description: "New Version"
required: true
jobs:
bump_version:
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Package
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/package.json"
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
--body "
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ github.event.inputs.version_number }}"

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

2
.nvmrc
View File

@ -1 +1 @@
10.13.0
v16.13.1

13
.prettierignore Normal file
View File

@ -0,0 +1,13 @@
# Build directories
build
dist
dist-safari
jslib
# External libraries / auto synced locales
src/locales
src/scripts/duo.js
# Github Workflows
.github/workflows

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"printWidth": 100
}

4
.vscode/launch.json vendored
View File

@ -10,9 +10,7 @@
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args": [
"."
]
"args": ["."]
}
]
}

View File

@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (l10n) section below
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/deskt
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- include a link to your Community Forums post
# Localization (l10n)

View File

@ -1,52 +0,0 @@
<!--
Please do not submit feature requests. The [Community Forums][1] has a
section for submitting, voting for, and discussing product feature requests.
[1]: https://community.bitwarden.com
-->
## Describe the Bug
<!-- Comment:
A clear and concise description of what the bug is.
-->
## Steps To Reproduce
<!-- Comment:
How can we reproduce the behavior:
-->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. Click on '...'
## Expected Result
<!-- Comment:
A clear and concise description of what you expected to happen.
-->
## Actual Result
<!-- Comment:
A clear and concise description of what is happening.
-->
## Screenshots or Videos
<!-- Comment:
If applicable, add screenshots and/or a short video to help explain your problem.
-->
## Environment
- Operating system: [e.g. Windows 10, Mac OS Catalina]
- Installation method: [e.g. Downloaded from Bitwarden.com, Mac App Store / Microsoft Store, Homebrew, Snap]
- Build Version (go to "Settings" → "About"): [e.g. 1.16.10]
## Additional Context
<!-- Comment:
Add any other context about the problem here.
-->

View File

@ -8,15 +8,15 @@
The Bitwarden desktop app is written using Electron and Angular. The application installs on Windows, macOS, and Linux distributions.
![Desktop Vault](https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/desktop-macos-vault.png "My Vault")
![Desktop Vault](https://github.com/bitwarden/brand/blob/f09f2fa594c8a020c315296074f18ce0a7b3f171/screenshots/desktop-macos-vault.png "My Vault")
# Build/Run
**Requirements**
- [Node.js](https://nodejs.org/)
- Windows users: To compile the native node modules used in the app you will need the *Visual C++ toolset*, available through the standard Visual Studio installer. You will also need to install the *Microsoft Build Tools 2015* and *Windows 10 SDK 17134* as additional dependencies in the Visual Studio installer.
- [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8
- Windows users: To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
**Run the app**
@ -30,15 +30,39 @@ npm run electron
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests
to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manifest
format, and the exact locations for the different platforms. *Note* that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
format, and the exact locations for the different platforms. _Note_ that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.
It will then be possible to run the desktop application as usual using `npm run electron` and communicate with the browser.
# We're Hiring!
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
# 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.
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.
## Prettier
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge b4df834b16d4f5d4162a926a5a308bdb3ebc718b`
3. Resolve any merge conflicts, commit.
4. Run `npm run prettier`
5. Commit
6. Run `git merge -Xours 521feae535d83166e620c3c28dfc3e7b0314a00e`
7. Push
### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@ -1,39 +1,11 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
While researching, we'd like to ask you to refrain from:
@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe!

View File

@ -1,5 +1,9 @@
project_id_env: _CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
preserve_hierarchy: true
files:
- source: /src/locales/en/messages.json
dest: /src/locales/en/%original_file_name%
translation: /src/locales/%two_letters_code%/%original_file_name%
update_option: update_as_unapproved
languages_mapping:

View File

@ -1,35 +0,0 @@
const gulp = require('gulp');
const googleWebFonts = require('gulp-google-webfonts');
const del = require('del');
const fs = require('fs');
const paths = {
cssDir: './src/css/',
node_modules: './node_modules/',
dist: './dist/',
resources: './resources/',
};
function clean() {
return del([paths.cssDir]);
}
function webfonts() {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
fontsDir: 'webfonts',
cssFilename: 'webfonts.css',
format: 'woff',
}))
.pipe(gulp.dest(paths.cssDir));
}
// ref: https://github.com/angular/angular/issues/22524
function cleanupAotIssue() {
return del(['./node_modules/@types/uglify-js/node_modules/source-map/source-map.d.ts']);
}
exports.clean = clean;
exports.cleanupAotIssue = cleanupAotIssue;
exports.webfonts = gulp.series(clean, webfonts);
exports['prebuild:renderer'] = gulp.parallel(webfonts, cleanupAotIssue);

2
jslib

@ -1 +1 @@
Subproject commit c832728b6dfcab3a75cad6badfdff464257971d2
Subproject commit fa73c13b8c9ed35cbb9909e342b143fa8a57f1a0

28406
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
{
"name": "bitwarden",
"productName": "Bitwarden",
"name": "@bitwarden/desktop",
"description": "A secure and free password manager for all of your devices.",
"version": "0.0.0",
"keywords": [
@ -22,22 +21,26 @@
"sub:update": "git submodule update --remote",
"sub:pull": "git submodule foreach git pull origin master",
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
"postinstall": "./node_modules/.bin/electron-rebuild && npm run sub:init && patch-package",
"preinstall": "npm run sub:init",
"postinstall": "electron-rebuild",
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
"lint": "tslint 'src/**/*.ts'",
"lint:fix": "tslint 'src/**/*.ts' --fix",
"lint": "eslint . && prettier --check .",
"lint:fix": "eslint . --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
"build:main": "webpack --config webpack.main.js",
"build:renderer": "gulp prebuild:renderer && webpack --config webpack.renderer.js",
"build:renderer:watch": "gulp prebuild:renderer && webpack --config webpack.renderer.js --watch",
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
"build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"",
"build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js",
"build:main:dev": "cross-env NODE_ENV=development webpack --config webpack.main.js",
"build:renderer": "cross-env NODE_ENV=production webpack --config webpack.renderer.js",
"build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.renderer.js",
"build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.renderer.js --watch",
"electron": "npm run build:main:dev && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
"clean:dist": "rimraf ./dist/*",
"clean:l10n": "git push origin --delete l10n_master",
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
"pack:mac": "npm run clean:dist && electron-builder --mac -p never",
"pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never",
"pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never",
"pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never",
"pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never",
@ -54,24 +57,31 @@
"publish:mac": "npm run build && npm run clean:dist && electron-builder --mac -p always",
"publish:mac:mas": "npm run dist:mac:mas && npm run upload:mas",
"publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas/Bitwarden*.pkg)\" --username $APPLE_ID_USERNAME --password $APPLE_ID_PASSWORD"
"publish:win:dev": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always",
"upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --username $APPLE_ID_USERNAME --password $APPLE_ID_PASSWORD",
"prettier": "prettier --write .",
"prepare": "husky install"
},
"build": {
"extraMetadata": {
"name": "bitwarden"
},
"productName": "Bitwarden",
"appId": "com.bitwarden.desktop",
"buildDependenciesFromSource": true,
"copyright": "Copyright © 2015-2021 Bitwarden Inc.",
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
"directories": {
"buildResources": "resources",
"output": "dist",
"app": "build"
},
"afterSign": "scripts/after-sign.js",
"asarUnpack": [
"**/*.node"
],
"mac": {
"electronUpdaterCompatibility": ">=0.0.1",
"category": "public.app-category.productivity",
"extraFiles": [
"PlugIns/"
],
"darkModeSupport": true,
"gatekeeperAssess": false,
"hardenedRuntime": true,
@ -177,14 +187,14 @@
"entitlements": "resources/entitlements.mas.plist",
"entitlementsInherit": "resources/entitlements.mas.inherit.plist",
"hardenedRuntime": false,
"asarUnpack": [
"node_modules/keytar"
]
"extendInfo": {
"LSMinimumSystemVersion": "10.14.0"
}
},
"nsisWeb": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"perMachine": false,
"allowToChangeInstallationDirectory": false,
"artifactName": "${productName}-Installer-${version}.${ext}",
"uninstallDisplayName": "${productName}",
"deleteAppDataOnUninstall": true
@ -246,85 +256,69 @@
]
},
"devDependencies": {
"@angular/compiler-cli": "^9.1.12",
"@ngtools/webpack": "^9.1.12",
"@types/lunr": "^2.3.3",
"@types/node": "^10.17.28",
"@types/node-forge": "^0.9.7",
"@types/papaparse": "^5.2.0",
"@types/semver": "^5.5.0",
"@types/webcrypto": "^0.0.28",
"@types/webpack": "^4.4.11",
"@types/zxcvbn": "4.4.0",
"clean-webpack-plugin": "^0.1.19",
"concurrently": "^4.0.1",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"del": "^3.0.0",
"electron": "11.3.0",
"electron-builder": "22.10.5",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.5",
"@angular/compiler-cli": "^12.2.13",
"@ngtools/webpack": "^12.2.13",
"@types/node": "^16.11.12",
"@types/node-ipc": "^9.1.4",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"clean-webpack-plugin": "^4.0.0",
"concurrently": "^6.0.2",
"copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"electron-builder": "22.11.7",
"electron-notarize": "^1.1.1",
"electron-rebuild": "^3.2.5",
"electron-reload": "^1.5.0",
"file-loader": "^2.0.0",
"font-awesome": "4.7.0",
"gulp": "^4.0.0",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"ngx-infinite-scroll": "7.0.1",
"node-abi": "^2.9.0",
"node-loader": "^0.6.0",
"node-sass": "^4.13.1",
"patch-package": "^6.4.7",
"rimraf": "^2.6.2",
"sass-loader": "^7.1.0",
"ts-loader": "^8.0.2",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "3.8.3",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-merge": "^4.2.1",
"webpack-node-externals": "^1.7.2"
"eslint": "^8.9.0",
"eslint-config-prettier": "^8.4.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.4",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"mini-css-extract-plugin": "^2.4.5",
"node-loader": "^2.0.0",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"sass": "^1.32.11",
"sass-loader": "^12.4.0",
"tapable": "^1.1.3",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "4.3.5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@angular/animations": "9.1.12",
"@angular/cdk": "9.2.4",
"@angular/common": "9.1.12",
"@angular/compiler": "9.1.12",
"@angular/core": "9.1.12",
"@angular/forms": "9.1.12",
"@angular/platform-browser": "9.1.12",
"@angular/platform-browser-dynamic": "9.1.12",
"@angular/router": "9.1.12",
"@angular/upgrade": "9.1.12",
"@microsoft/signalr": "3.1.13",
"@microsoft/signalr-protocol-msgpack": "3.1.13",
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"angular2-toaster": "8.0.0",
"angulartics2": "9.1.0",
"big-integer": "1.6.48",
"browser-hrtime": "^1.1.8",
"core-js": "2.6.2",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"electron-log": "4.3.0",
"electron-store": "6.0.1",
"electron-updater": "4.3.5",
"forcefocus": "^1.1.0",
"keytar": "7.3.0",
"lunr": "2.3.3",
"node-forge": "0.10.0",
"node-ipc": "^9.1.1",
"nord": "0.2.1",
"papaparse": "5.2.0",
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"@bitwarden/jslib-electron": "file:jslib/electron",
"ngx-toastr": "14.1.4",
"node-ipc": "^9.1.4",
"nord": "^0.2.1",
"regedit": "^3.0.3",
"rxjs": "6.6.2",
"sweetalert2": "10.15.4",
"tslib": "^2.0.1",
"zone.js": "0.10.3",
"zxcvbn": "4.4.2"
"rxjs": "^7.4.0",
"sweetalert2": "^10.16.6"
},
"engines": {
"node": "~16",
"npm": "~8"
},
"lint-staged": {
"./!(jslib)**": "prettier --ignore-unknown --write",
"*.ts": "eslint --fix"
}
}

View File

@ -1,54 +0,0 @@
diff --git a/node_modules/app-builder-lib/out/macPackager.js b/node_modules/app-builder-lib/out/macPackager.js
index 41e067c..cd97293 100644
--- a/node_modules/app-builder-lib/out/macPackager.js
+++ b/node_modules/app-builder-lib/out/macPackager.js
@@ -292,6 +292,23 @@ class MacPackager extends _platformPackager().PlatformPackager {
const appFile = `${this.appInfo.productFilename}.app`;
+ // Bitwarden Patch: Electron-Builder currently does not support including our Safari extension which
+ // is already cross-compiled. Hence we remove it prior to making the universal package, and re-add
+ // it afterwards
+ // https://github.com/electron-userland/electron-builder/issues/5552
+ const rmdir = (0, _fsExtra().remove);
+ try {
+ await rmdir(`${x64AppOutDir}/Bitwarden.app/Contents/PlugIns`, {
+ recursive: true
+ });
+ await rmdir(`${arm64AppOutPath}/Bitwarden.app/Contents/PlugIns`, {
+ recursive: true
+ });
+ } catch (e) {
+ // Catches errors where PlugIns does not exist
+ console.log(e);
+ }
+
const {
makeUniversalApp
} = require('@electron/universal');
@@ -302,7 +319,15 @@ class MacPackager extends _platformPackager().PlatformPackager {
outAppPath: path.join(appOutDir, appFile),
force: true
});
- const rmdir = (0, _util().promisify)(require('fs').rmdir);
+
+ // Bitwarden Patch: Re-add PlugIns dir to Universal binary
+ try {
+ await ((0, _fsExtra().copy)(path.join(this.projectDir, 'PlugIns'), `${path.join(appOutDir, appFile)}/Contents/PlugIns`));
+ } catch (e) {
+ // Catches errors where PlugIns does not exist
+ console.log(e);
+ }
+
await rmdir(x64AppOutDir, {
recursive: true
});
@@ -611,7 +636,7 @@ exports.default = MacPackager;
function getCertificateType(isMas, isDevelopment) {
if (isDevelopment) {
- return "Mac Developer";
+ return "Apple Development";
}
return isMas ? "3rd Party Mac Developer Application" : "Developer ID Application";

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>

View File

@ -15,6 +15,7 @@
<string>/Library/Application Support/Google/Chrome Beta/NativeMessagingHosts/</string>
<string>/Library/Application Support/Google/Chrome Dev/NativeMessagingHosts/</string>
<string>/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/</string>
<string>/Library/Application Support/Chromium/NativeMessagingHosts/</string>
<string>/Library/Application Support/Microsoft Edge/NativeMessagingHosts/</string>
<string>/Library/Application Support/Microsoft Edge Beta/NativeMessagingHosts/</string>
<string>/Library/Application Support/Microsoft Edge Dev/NativeMessagingHosts/</string>

View File

@ -1,25 +1,62 @@
require('dotenv').config();
const { notarize } = require('electron-notarize');
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
require("dotenv").config();
const path = require("path");
const { deepAssign } = require("builder-util");
const { notarize } = require("electron-notarize");
const fse = require("fs-extra");
exports.default = run;
async function run(context) {
console.log('## After sign');
// console.log(context);
console.log("## After sign");
// console.log(context);
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === 'darwin';
const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === "darwin";
const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
if (macBuild) {
console.log('### Notarizing ' + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: 'com.bitwarden.desktop',
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, "../PlugIns");
if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
fse.copySync(
path.join(plugIn, "safari.appex"),
path.join(appPath, "Contents/PlugIns/safari.appex")
);
// Resign to sign safari extension
if (context.electronPlatformName === "mas") {
const masBuildOptions = deepAssign(
{},
context.packager.platformSpecificBuildOptions,
context.packager.config.mas
);
if (context.targets.some((e) => e.name === "mas-dev")) {
deepAssign(masBuildOptions, {
type: "development",
});
}
if (context.packager.packagerOptions.prepackaged == null) {
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
}
} else {
await context.packager.signApp(context, true);
}
}
}
if (macBuild) {
console.log("### Notarizing " + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({
appBundleId: "com.bitwarden.desktop",
appPath: appPath,
appleId: appleId,
appleIdPassword: appleIdPassword,
});
}
}

1
scripts/dev/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
data

View File

@ -0,0 +1,19 @@
version: "3"
services:
minio:
image: minio/minio
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
# environment:
# MINIO_ROOT_USER: minioadmin
# MINIO_ROOT_PASSWORD: minioadmin
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
- ./data:/data

33
sign.js
View File

@ -1,22 +1,21 @@
exports.default = async function(configuration) {
if (
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
exports.default = async function (configuration) {
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
console.log(`[*] Signing file: ${configuration.path}`);
require("child_process").execSync(
`azuresigntool sign ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
`azuresigntool sign -v ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
`-fd ${configuration.hash} ` +
`-du ${configuration.site} ` +
`-tr http://timestamp.digicert.com ` +
`${configuration.path}`,
{
stdio: "inherit"
stdio: "inherit",
}
);
}

View File

@ -1,64 +1,97 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="{{'ex' | i18n}} https://bitwarden.company.com">
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{'customEnvironment' | i18n}}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl"
[(ngModel)]="notificationsUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl">
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{ "selfHostedEnvironment" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
appInputVerbatim
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="box-footer">
{{ "selfHostedEnvironmentFooter" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleCustom()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="bwi bwi-plus-square" [hidden]="showCustom" aria-hidden="true"></i>
<i class="bwi bwi-minus-square" [hidden]="!showCustom" aria-hidden="true"></i>
{{ "customEnvironment" | i18n }}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
appInputVerbatim
/>
</div>
</form>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim />
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input
id="notificationsUrl"
type="text"
name="NotificationsUrl"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
appInputVerbatim
/>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{ "customEnvironmentFooter" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@ -1,18 +1,20 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib/angular/components/environment.component';
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-environment',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
i18nService: I18nService) {
super(platformUtilsService, environmentService, i18nService);
}
constructor(
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService);
}
}

View File

@ -1,23 +1,31 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'passwordHint' | i18n}}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
<div class="content">
<h1>{{ "passwordHint" | i18n }}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
appInputVerbatim
/>
</div>
</div>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -1,19 +1,24 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { HintComponent as BaseHintComponent } from 'jslib/angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, apiService: ApiService) {
super(router, i18nService, apiService, platformUtilsService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@ -1,47 +1,77 @@
<form id="lock-page" (ngSubmit)="submit()">
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{(pinLock ? 'yourVaultIsLockedPinCode' : 'yourVaultIsLocked') | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
[(ngModel)]="pin" required appAutofocus>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'loggedInAsOn' | i18n : email : webVaultHostname}}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick>
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{'unlock' | i18n}}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<a class="btn block" appBlurClick (click)="unlockBiometric()">
{{biometricText | i18n}}
</a>
</div>
<div class="content">
<p aria-hidden="true"><i class="bwi bwi-lock bwi-4x text-muted"></i></p>
<p>{{ "yourVaultIsLocked" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button
type="button"
class="btn block"
[ngClass]="{ 'primary font-weight-bold': hideInput }"
appBlurClick
(click)="unlockBiometric()"
>
{{ biometricText | i18n }}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="bwi bwi-unlock" aria-hidden="true"></i> <b>{{ "unlock" | i18n }}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</form>

View File

@ -1,70 +1,107 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ipcRenderer } from "electron";
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
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 { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { LockComponent as BaseLockComponent } from 'jslib/angular/components/lock.component';
const BroadcasterSubscriptionId = 'LockComponent';
const BroadcasterSubscriptionId = "LockComponent";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent implements OnDestroy {
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, vaultTimeoutService, environmentService, stateService, apiService);
}
private deferFocus: boolean = null;
async ngOnInit() {
await super.ngOnInit();
this.route.queryParams.subscribe(params => {
if (this.supportsBiometric && params.promptBiometric) {
setTimeout(() => this.unlockBiometric(), 1000);
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
private route: ActivatedRoute,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
logService: LogService,
keyConnectorService: KeyConnectorService
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics());
this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async () => {
if (await ipcRenderer.invoke("windowVisible")) {
this.unlockBiometric();
}
}, 1000);
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
private focusInput() {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
}
}

View File

@ -1,53 +1,101 @@
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div id="login-page">
<div class="login-header">
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
attr.aria-label="{{ 'settings' | i18n }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
{{ "settings" | i18n }}
</a>
</div>
<form
id="login-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appInputVerbatim="false"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</a>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</div>
<div class="box last" [hidden]="!showCaptcha()">
<div class="box-content">
<div class="box-content-row">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<a href="#" appStopClick (click)="settings()" class="settings-icon" attr.aria-label="{{'settings' | i18n}}">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span
aria-hidden="true">&nbsp;{{'settings' | i18n}}</span>
</a>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
</form>
</form>
</div>
<ng-template #environment></ng-template>

View File

@ -1,93 +1,128 @@
import {
Component,
ComponentFactoryResolver,
NgZone,
OnDestroy,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
import { Router } from "@angular/router";
import { Router } from '@angular/router';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { EnvironmentComponent } from './environment.component';
import { EnvironmentComponent } from "./environment.component";
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
import { ModalComponent } from 'jslib/angular/components/modal.component';
const BroadcasterSubscriptionId = 'LoginComponent';
const BroadcasterSubscriptionId = "LoginComponent";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
showingModal = false;
showingModal = false;
constructor(authService: AuthService, router: Router, i18nService: I18nService,
syncService: SyncService, private componentFactoryResolver: ComponentFactoryResolver,
platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, storageService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
settings() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
const modal = this.environmentModal.createComponent(factory).instance;
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
modal.onShown.unsubscribe();
modal.onClosed.unsubscribe();
});
const childComponent = modal.show<EnvironmentComponent>(EnvironmentComponent,
this.environmentModal);
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
protected alwaysRememberEmail = true;
private deferFocus: boolean = null;
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
private modalService: ModalService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@ -1,70 +1,91 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}}
</li>
</ul>
<p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}}
</p>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p>
</div>
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{ "premiumMembership" | i18n }}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="bwi-ul">
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpStorage" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTwoStep" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpReports" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTotp" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpSupport" | i18n }}
</li>
<li>
<i class="bwi bwi-li bwi-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpFuture" | i18n }}
</li>
</ul>
<p class="text-center lead no-margin">
{{ "premiumPrice" | i18n: (price | currency: "$") }}
</p>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{'premiumManage' | i18n}}</b>
</button>
<button #purchaseBtn type="button" class="primary" appBlurClick (click)="purchase()" *ngIf="!isPremium"
[disabled]="purchaseBtn.loading">
<b>{{'premiumPurchase' | i18n}}</b>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right" *ngIf="!isPremium">
<button #refreshBtn type="button" appBlurClick (click)="refresh()" [disabled]="refreshBtn.loading"
appA11yTitle="{{'premiumRefresh' | i18n}}" [appApiAction]="refreshPromise">
<i class="fa fa-refresh fa-lg fa-fw" [hidden]="refreshBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!refreshBtn.loading"
aria-hidden="true"></i>
</button>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{ "premiumManage" | i18n }}</b>
</button>
<button
#purchaseBtn
type="button"
class="primary"
appBlurClick
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
>
<b>{{ "premiumPurchase" | i18n }}</b>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium">
<button
#refreshBtn
type="button"
appBlurClick
(click)="refresh()"
[disabled]="refreshBtn.loading"
appA11yTitle="{{ 'premiumRefresh' | i18n }}"
[appApiAction]="refreshPromise"
>
<i
class="bwi bwi-refresh bwi-lg bwi-fw"
[hidden]="refreshBtn.loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!refreshBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,26 +1,24 @@
import {
Component,
NgZone,
} from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib/abstractions/api.service';
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';
import { PremiumComponent as BasePremiumComponent } from 'jslib/angular/components/premium.component';
import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: 'app-premium',
templateUrl: 'premium.component.html',
selector: "app-premium",
templateUrl: "premium.component.html",
})
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, userService: UserService,
private ngZone: NgZone, private messagingService: MessagingService,
private syncService: SyncService) {
super(i18nService, platformUtilsService, apiService, userService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@ -1,89 +1,150 @@
<form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'createAccount' | i18n}}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''">
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="email !== ''" (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<div class="content">
<h1>{{ "createAccount" | i18n }}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
appInputVerbatim
/>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@ -1,57 +1,73 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component';
const BroadcasterSubscriptionId = 'RegisterComponent';
const BroadcasterSubscriptionId = "RegisterComponent";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, stateService: StateService,
platformUtilsService: PlatformUtilsService, passwordGenerationService: PasswordGenerationService,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
super.ngOnInit();
}
onWindowHidden() {
this.showPassword = false;
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -0,0 +1,28 @@
<div id="remove-password-page" *ngIf="!loading">
<div class="content">
<h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="actionPromise"
appBlurClick
(click)="convert()"
>
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn secondary block"
[disabled]="actionPromise"
appBlurClick
(click)="leave()"
>
<b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@ -1,112 +1,159 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'setMasterPassword' | i18n}}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="info" *ngIf="enforcedPolicyOptions">
{{'masterPasswordPolicyInEffect' | i18n}}
<ul>
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">{{'policyInEffectUppercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">{{'policyInEffectLowercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">{{'policyInEffectNumbers' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}
</li>
</ul>
</app-callout>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
(input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}">
</div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i *ngIf="form.loading" class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{'logOut' | i18n}}</span>
</button>
</div>
</form>
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@ -1,96 +1,104 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { UserService } from 'jslib/abstractions/user.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
const BroadcasterSubscriptionId = 'SetPasswordComponent';
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib/angular/components/set-password.component';
const BroadcasterSubscriptionId = "SetPasswordComponent";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
}
});
});
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@ -1,193 +1,352 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{'security' | i18n}}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="vaultTimeouts">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeouts" name="VaultTimeouts" [(ngModel)]="vaultTimeout"
(change)="saveVaultTimeoutOptions()">
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'vaultTimeoutDesc' | i18n}}</small>
</div>
<div class="form-group">
<label>{{'vaultTimeoutAction' | i18n}}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLock"
value="lock" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'lock' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLockDesc' | i18n}}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLogOut"
value="logOut" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'logOut' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLogOutDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input id="pin" type="checkbox" name="PIN" [(ngModel)]="pin" (change)="updatePin()">
{{'unlockWithPin' | i18n}}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input id="biometric" type="checkbox" name="biometric" [checked]="biometric" (change)="updateBiometric()">
{{biometricText | i18n}}
</label>
</div>
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<label class="settingsTitle">{{ "settingsTitle" | i18n: currentUserEmail }} </label>
<div class="box-content box-content-padded">
<h2>
<button
id="app-settings"
type="button"
class="box-header-expandable"
(click)="showSecurity = !showSecurity"
[attr.aria-expanded]="showSecurity"
appAutofocus
>
{{ "security" | i18n }}
<i
*ngIf="!showSecurity"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showSecurity"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showSecurity">
<app-vault-timeout-input
[vaultTimeouts]="vaultTimeouts"
[formControl]="vaultTimeout"
ngDefaultControl
></app-vault-timeout-input>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLock"
value="lock"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "lock" | i18n }}
</label>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="clearClipboard">{{'clearClipboard' | i18n}}</label>
<select id="clearClipboard" name="ClearClipboard" [(ngModel)]="clearClipboard"
(change)="saveClearClipboard()">
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'clearClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input id="minimizeOnCopyToClipboard" type="checkbox"
name="MinimizeOnCopyToClipboard" [(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()">
{{'minimizeOnCopyToClipboard' | i18n}}
</label>
</div>
<small class="help-block">{{'minimizeOnCopyToClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input id="disableFavicons" type="checkbox" name="DisableFavicons"
[(ngModel)]="disableFavicons" (change)="saveFavicons()">
{{'disableFavicon' | i18n}}
</label>
</div>
<small class="help-block">{{'disableFaviconDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input id="enableBrowserIntegration" type="checkbox" name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration" (change)="saveBrowserIntegration()">
{{'enableBrowserIntegration' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration">
{{'enableBrowserIntegrationFingerprint' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationFingerprintDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input id="enableTray" type="checkbox" name="EnableTray" [(ngModel)]="enableTray"
(change)="saveTray()">
{{enableTrayText}}
</label>
</div>
<small class="help-block">{{enableTrayDescText}}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input id="enableMinToTray" type="checkbox" name="EnableMinToTray"
[(ngModel)]="enableMinToTray" (change)="saveMinToTray()">
{{enableMinToTrayText}}
</label>
</div>
<small class="help-block">{{enableMinToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input id="enableCloseToTray" type="checkbox" name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray" (change)="saveCloseToTray()">
{{enableCloseToTrayText}}
</label>
</div>
<small class="help-block">{{enableCloseToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input id="startToTray" type="checkbox" name="StartToTray" [(ngModel)]="startToTray"
(change)="saveStartToTray()">
{{startToTrayText}}
</label>
</div>
<small class="help-block">{{startToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input id="openAtLogin" type="checkbox" name="OpenAtLogin" [(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()">
{{'openAtLogin' | i18n}}
</label>
</div>
<small class="help-block">{{'openAtLoginDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input id="alwaysShowDock" type="checkbox" name="AlwaysShowDock" [(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()">
{{'alwaysShowDock' | i18n}}
</label>
</div>
<small class="help-block">{{'alwaysShowDockDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'themeDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="locale">{{'language' | i18n}}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'languageDesc' | i18n}}</small>
</div>
</div>
<small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="logOut"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "logOut" | i18n }}
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input
id="biometric"
type="checkbox"
name="biometric"
[checked]="biometric"
(change)="updateBiometric()"
/>
{{ biometricText | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input
id="noAutoPromptBiometrics"
type="checkbox"
name="noAutoPromptBiometrics"
[(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric"
(change)="updateNoAutoPromptBiometrics()"
/>
{{ noAutoPromptBiometricsText | i18n }}
</label>
</div>
</div>
</ng-container>
</div>
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showAccountPreferences = !showAccountPreferences"
[attr.aria-expanded]="showAccountPreferences"
>
{{ "accountPreferences" | i18n }}
<i
*ngIf="!showAccountPreferences"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAccountPreferences"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showAccountPreferences">
<div class="form-group">
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<small class="help-block">{{ "clearClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input
id="minimizeOnCopyToClipboard"
type="checkbox"
name="MinimizeOnCopyToClipboard"
[(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()"
/>
{{ "minimizeOnCopyToClipboard" | i18n }}
</label>
</div>
<small class="help-block">{{ "minimizeOnCopyToClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input
id="disableFavicons"
type="checkbox"
name="DisableFavicons"
[(ngModel)]="disableFavicons"
(change)="saveFavicons()"
/>
{{ "disableFavicon" | i18n }}
</label>
</div>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
</ng-container>
</div>
</div>
<div class="box">
<div class="box-content box-content-padded">
<h2>
<button
type="button"
class="box-header-expandable"
(click)="showAppPreferences = !showAppPreferences"
[attr.aria-expanded]="showAppPreferences"
>
{{ "appPreferences" | i18n }}
<i
*ngIf="!showAppPreferences"
class="bwi bwi-angle-down bwi-sm icon"
aria-hidden="true"
></i>
<i
*ngIf="showAppPreferences"
class="bwi bwi-chevron-up bwi-sm icon"
aria-hidden="true"
></i>
</button>
</h2>
<ng-container *ngIf="showAppPreferences">
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input
id="enableTray"
type="checkbox"
name="EnableTray"
[(ngModel)]="enableTray"
(change)="saveTray()"
/>
{{ enableTrayText }}
</label>
</div>
<small class="help-block">{{ enableTrayDescText }}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input
id="enableMinToTray"
type="checkbox"
name="EnableMinToTray"
[(ngModel)]="enableMinToTray"
(change)="saveMinToTray()"
/>
{{ enableMinToTrayText }}
</label>
</div>
<small class="help-block">{{ enableMinToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input
id="enableCloseToTray"
type="checkbox"
name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray"
(change)="saveCloseToTray()"
/>
{{ enableCloseToTrayText }}
</label>
</div>
<small class="help-block">{{ enableCloseToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input
id="startToTray"
type="checkbox"
name="StartToTray"
[(ngModel)]="startToTray"
(change)="saveStartToTray()"
/>
{{ startToTrayText }}
</label>
</div>
<small class="help-block">{{ startToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input
id="openAtLogin"
type="checkbox"
name="OpenAtLogin"
[(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()"
/>
{{ "openAtLogin" | i18n }}
</label>
</div>
<small class="help-block">{{ "openAtLoginDesc" | i18n }}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input
id="alwaysShowDock"
type="checkbox"
name="AlwaysShowDock"
[(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()"
/>
{{ "alwaysShowDock" | i18n }}
</label>
</div>
<small class="help-block">{{ "alwaysShowDockDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input
id="enableBrowserIntegration"
type="checkbox"
name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration"
(change)="saveBrowserIntegration()"
/>
{{ "enableBrowserIntegration" | i18n }}
</label>
</div>
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input
id="enableBrowserIntegrationFingerprint"
type="checkbox"
name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint"
(change)="saveBrowserIntegrationFingerprint()"
[disabled]="!enableBrowserIntegration"
/>
{{ "enableBrowserIntegrationFingerprint" | i18n }}
</label>
</div>
<small class="help-block">{{
"enableBrowserIntegrationFingerprintDesc" | i18n
}}</small>
</div>
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "themeDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="locale">{{ "language" | i18n }}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "languageDesc" | i18n }}</small>
</div>
</ng-container>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,389 +1,407 @@
import {
Component,
OnInit,
} from '@angular/core';
import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { debounceTime } from "rxjs/operators";
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import Swal from 'sweetalert2/src/sweetalert2.js';
import { ModalService } from "jslib-angular/services/modal.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { DeviceType } from "jslib-common/enums/deviceType";
import { StorageLocation } from "jslib-common/enums/storageLocation";
import { ThemeType } from "jslib-common/enums/themeType";
import { Utils } from "jslib-common/misc/utils";
import { isWindowsStore } from "jslib-electron/utils";
import { DeviceType } from 'jslib/enums/deviceType';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { ElectronConstants } from 'jslib/electron/electronConstants';
import { isWindowsStore } from 'jslib/electron/utils';
import { Utils } from 'jslib/misc/utils';
import { SetPinComponent } from "../components/set-pin.component";
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
selector: "app-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent implements OnInit {
vaultTimeout: number = null;
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons = false;
enableBrowserIntegration = false;
enableBrowserIntegrationFingerprint = false;
enableMinToTray = false;
enableCloseToTray = false;
enableTray = false;
showMinToTray = false;
startToTray = false;
minimizeOnCopyToClipboard = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: ThemeType;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock = false;
openAtLogin: boolean;
requireEnableTray = false;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
constructor(private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService, private messagingService: MessagingService,
private userService: UserService, private cryptoService: CryptoService) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
vaultTimeout: FormControl = new FormControl(null);
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
showSecurity = true;
showAccountPreferences = true;
showAppPreferences = true;
const trayKey = isMac ? 'enableMenuBar' : 'enableTray';
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc');
currentUserEmail: string;
const minToTrayKey = isMac ? 'enableMinToMenuBar' : 'enableMinToTray';
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + 'Desc');
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService,
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService
) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
const closeToTrayKey = isMac ? 'enableCloseToMenuBar' : 'enableCloseToTray';
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + 'Desc');
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
const startToTrayKey = isMac ? 'startToMenuBar' : 'startToTray';
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + 'Desc');
const trayKey = isMac ? "enableMenuBar" : "enableTray";
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t('oneMinute'), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 },
{ name: i18nService.t('thirtyMinutes'), value: 30 },
{ name: i18nService.t('oneHour'), value: 60 },
{ name: i18nService.t('fourHours'), value: 240 },
{ name: i18nService.t('onIdle'), value: -4 },
{ name: i18nService.t('onSleep'), value: -3 },
];
const minToTrayKey = isMac ? "enableMinToMenuBar" : "enableMinToTray";
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 });
}
const closeToTrayKey = isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t('onRestart'), value: -1 },
{ name: i18nService.t('never'), value: null },
]);
const startToTrayKey = isMac ? "startToMenuBar" : "startToTray";
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach(locale => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += (' - ' + i18nService.localeNames.get(locale));
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
this.localeOptions = localeOptions;
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t("oneMinute"), value: 1 },
{ name: i18nService.t("fiveMinutes"), value: 5 },
{ name: i18nService.t("fifteenMinutes"), value: 15 },
{ name: i18nService.t("thirtyMinutes"), value: 30 },
{ name: i18nService.t("oneHour"), value: 60 },
{ name: i18nService.t("fourHours"), value: 240 },
{ name: i18nService.t("onIdle"), value: -4 },
{ name: i18nService.t("onSleep"), value: -3 },
];
this.themeOptions = [
{ name: i18nService.t('default'), value: null },
{ name: i18nService.t('light'), value: 'light' },
{ name: i18nService.t('dark'), value: 'dark' },
{ name: 'Nord', value: 'nord' },
];
this.clearClipboardOptions = [
{ name: i18nService.t('never'), value: null },
{ name: i18nService.t('tenSeconds'), value: 10 },
{ name: i18nService.t('twentySeconds'), value: 20 },
{ name: i18nService.t('thirtySeconds'), value: 30 },
{ name: i18nService.t('oneMinute'), value: 60 },
{ name: i18nService.t('twoMinutes'), value: 120 },
{ name: i18nService.t('fiveMinutes'), value: 300 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableBrowserIntegration = await this.storageService.get<boolean>(
ElectronConstants.enableBrowserIntegration);
this.enableBrowserIntegrationFingerprint = await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint);
this.enableMinToTray = await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey);
this.enableCloseToTray = await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey);
this.enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
this.startToTray = await this.storageService.get<boolean>(ElectronConstants.enableStartToTrayKey);
this.locale = await this.storageService.get<string>(ConstantsService.localeKey);
this.theme = await this.storageService.get<string>(ConstantsService.themeKey);
this.clearClipboard = await this.storageService.get<number>(ConstantsService.clearClipboardKey);
this.minimizeOnCopyToClipboard = await this.storageService.get<boolean>(
ElectronConstants.minimizeOnCopyToClipboardKey);
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.storageService.get<string>(ConstantsService.biometricText);
this.alwaysShowDock = await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock);
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.storageService.get<boolean>(ElectronConstants.openAtLogin);
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t("onRestart"), value: -1 },
{ name: i18nService.t("never"), value: null },
]);
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach((locale) => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += " - " + i18nService.localeNames.get(locale);
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t("default"), value: ThemeType.System },
{ name: i18nService.t("light"), value: ThemeType.Light },
{ name: i18nService.t("dark"), value: ThemeType.Dark },
{ name: "Nord", value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
];
}
async ngOnInit() {
// App preferences
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
if ((await this.stateService.getUserId()) == null) {
return;
}
this.currentUserEmail = await this.stateService.getEmail();
// Security
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
// Account preferences
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint =
await this.stateService.getEnableBrowserIntegrationFingerprint();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === "logOut") {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
this.i18nService.t("yes"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
this.vaultTimeoutAction = "lock";
return;
}
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === 'logOut') {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
if (!confirmed) {
this.vaultTimeoutAction = 'lock';
return;
}
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null,
this.vaultTimeoutAction);
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
async updatePin() {
if (this.pin) {
const div = document.createElement('div');
const label = document.createElement('label');
label.className = 'checkbox';
const checkboxText = document.createElement('span');
const restartText = document.createTextNode(this.i18nService.t('lockWithMasterPassOnRestart'));
checkboxText.appendChild(restartText);
label.innerHTML = '<input type="checkbox" id="master-pass-restart" checked>';
label.appendChild(checkboxText);
div.innerHTML =
`<div class="swal2-text">${this.i18nService.t('setYourPinCode')}</div>` +
'<input type="text" class="swal2-input" id="pin-val" autocomplete="off" ' +
'autocapitalize="none" autocorrect="none" spellcheck="false" inputmode="verbatim">';
(div.querySelector('#pin-val') as HTMLInputElement).placeholder = this.i18nService.t('pin');
div.appendChild(label);
const submitted = await Swal.fire({
heightAuto: false,
buttonsStyling: false,
html: div,
showCancelButton: true,
cancelButtonText: this.i18nService.t('cancel'),
showConfirmButton: true,
confirmButtonText: this.i18nService.t('submit'),
});
let pin: string = null;
let masterPassOnRestart: boolean = null;
if (submitted.value) {
pin = (document.getElementById('pin-val') as HTMLInputElement).value;
masterPassOnRestart = (document.getElementById('master-pass-restart') as HTMLInputElement).checked;
}
if (pin != null && pin.trim() !== '') {
const kdf = await this.userService.getKdf();
const kdfIterations = await this.userService.getKdfIterations();
const email = await this.userService.getEmail();
const pinKey = await this.cryptoService.makePinKey(pin, email, kdf, kdfIterations);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
if (masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(pin);
await this.storageService.save(ConstantsService.protectedPin, encPin.encryptedString);
this.vaultTimeoutService.pinProtectedKey = pinProtectedKey;
} else {
await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString);
}
} else {
this.pin = false;
}
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("vaultTimeoutTooLarge")
);
return;
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
} else {
await this.storageService.remove(ConstantsService.biometricUnlockKey);
}
this.vaultTimeoutService.biometricLocked = false;
await this.cryptoService.toggleKey();
await this.vaultTimeoutService.setVaultTimeoutOptions(
this.vaultTimeout.value,
this.vaultTimeoutAction
);
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
async saveFavicons() {
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
this.messagingService.send('refreshCiphers');
this.callAnalytics('Favicons', !this.disableFavicons);
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, {
storageLocation: StorageLocation.Disk,
});
this.messagingService.send("refreshCiphers");
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveMinToTray() {
await this.storageService.save(ElectronConstants.enableMinimizeToTrayKey, this.enableMinToTray);
this.callAnalytics('MinimizeToTray', this.enableMinToTray);
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveTray() {
if (
this.requireEnableTray &&
!this.enableTray &&
(this.startToTray || this.enableCloseToTray)
) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t("confirmTrayDesc"),
this.i18nService.t("confirmTrayTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
}
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
}
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
this.callAnalytics('CloseToTray', this.enableCloseToTray);
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveTray() {
if (this.requireEnableTray && !this.enableTray && (this.startToTray || this.enableCloseToTray)) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t('confirmTrayDesc'), this.i18nService.t('confirmTrayTitle'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
await this.stateService.setEnableStartToTray(this.startToTray);
}
if (confirm) {
this.startToTray = false;
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
this.enableCloseToTray = false;
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
} else {
this.enableTray = true;
}
async saveLocale() {
await this.stateService.setLocale(this.locale);
}
return;
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
this.callAnalytics('Tray', this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
}
async saveBrowserIntegration() {
if (process.platform === "darwin" && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationMasOnlyDesc"),
this.i18nService.t("browserIntegrationMasOnlyTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationWindowsStoreDesc"),
this.i18nService.t("browserIntegrationWindowsStoreTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
}
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
);
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
this.callAnalytics('StartToTray', this.startToTray);
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveLocale() {
await this.storageService.save(ConstantsService.localeKey, this.locale);
this.analytics.eventTrack.next({ action: 'Set Locale ' + this.locale });
}
async saveTheme() {
await this.storageService.save(ConstantsService.themeKey, this.theme);
this.analytics.eventTrack.next({ action: 'Set Theme ' + this.theme });
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.storageService.save(ElectronConstants.minimizeOnCopyToClipboardKey, this.minimizeOnCopyToClipboard);
this.callAnalytics('MinOnCopyToClipboard', this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard);
this.analytics.eventTrack.next({
action: 'Set Clear Clipboard ' + (this.clearClipboard == null ? 'Disabled' : this.clearClipboard),
});
}
async saveAlwaysShowDock() {
await this.storageService.save(ElectronConstants.alwaysShowDock, this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.storageService.save(ElectronConstants.openAtLogin, this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
async saveBrowserIntegration() {
if (process.platform === 'darwin' && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationMasOnlyDesc'),
this.i18nService.t('browserIntegrationMasOnlyTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationWindowsStoreDesc'),
this.i18nService.t('browserIntegrationWindowsStoreTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
}
await this.storageService.save(ElectronConstants.enableBrowserIntegration, this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveBrowserIntegrationFingerprint() {
await this.storageService.save(ElectronConstants.enableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
}
private callAnalytics(name: string, enabled: boolean) {
const status = enabled ? 'Enabled' : 'Disabled';
this.analytics.eventTrack.next({ action: `${status} ${name}` });
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(
this.enableBrowserIntegrationFingerprint
);
}
}

View File

@ -1,9 +1,9 @@
<form id="sso-page" (ngSubmit)="submit()">
<div class="content">
<img class="logo-image" alt="Bitwarden">
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<div class="box">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
</div>
</form>

View File

@ -1,39 +1,54 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
passwordGenerationService: PasswordGenerationService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, passwordGenerationService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = 'bitwarden://sso-callback';
this.clientId = 'desktop';
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = "bitwarden://sso-callback";
this.clientId = "desktop";
}
}

View File

@ -1,28 +1,33 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}}
</div>
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{ "twoStepOptions" | i18n }}
</div>
<div class="box-content">
<a
href="#"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@ -1,21 +1,22 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib/angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
twoFactorService: TwoFactorService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(twoFactorService, router, i18nService, platformUtilsService, window);
}
}

View File

@ -1,88 +1,146 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div class="content">
<h1>{{title}}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="box last"
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
<form
id="two-factor-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content">
<h1>{{ title }}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<div
class="box last"
*ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{'insertYubiKey' | i18n}}</p>
<img src="../../images/yubikey.jpg" alt="">
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i>
{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{'useAnotherTwoStepMethod' | i18n}}</a>
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise" role="button"
*ngIf="selectedProviderType === providerType.Email">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" alt="" />
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</div>
</div>
</div>
<div class="box last" [hidden]="!showCaptcha()">
<div class="box-content">
<div class="box-content-row">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
</div>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="form.loading"
appBlurClick
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
>
<span [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}</span
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
<a
href="#"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
role="button"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a>
</div>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@ -1,73 +1,91 @@
import {
Component,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { ModalComponent } from 'jslib/angular/components/modal.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
showingModal = false;
showingModal = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, syncService: SyncService,
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver,
stateService: StateService, storageService: StorageService, route: ActivatedRoute) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
anotherMethod() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
modal.onShown.unsubscribe();
modal.onClosed.unsubscribe();
});
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
this.twoFactorOptionsModal);
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
syncService: SyncService,
environmentService: EnvironmentService,
private modalService: ModalService,
stateService: StateService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService,
twoFactorService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@ -0,0 +1,124 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ "updateMasterPasswordWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
*ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</a>
</div>
</div>
</form>

View File

@ -0,0 +1,80 @@
import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
}
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: "bg-success",
Text: "strong",
Width: scoreWidth,
};
case 3:
return {
Color: "bg-primary",
Text: "good",
Width: scoreWidth,
};
case 2:
return {
Color: "bg-warning",
Text: "weak",
Width: scoreWidth,
};
default:
return {
Color: "bg-danger",
Text: "weak",
Width: scoreWidth,
};
}
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService,
stateService: StateService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
}
}

View File

@ -0,0 +1,45 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select
id="vaultTimeout"
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{ "hours" | i18n }}</label>
<input
id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
</div>
<div class="col">
<label for="minutes">{{ "minutes" | i18n }}</label>
<input
id="minutes"
class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
</div>
</div>
<div class="form-group"></div>
<!-- Styling fix -->
</div>

View File

@ -0,0 +1,22 @@
import { Component } from "@angular/core";
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms";
import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component";
@Component({
selector: "app-vault-timeout-input",
templateUrl: "vault-timeout-input.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@ -1,49 +1,70 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { LoginGuardService } from "../services/loginGuard.service";
import { SendComponent } from './send/send.component';
import { VaultComponent } from './vault/vault.component';
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { SendComponent } from "./send/send.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{ path: '', redirectTo: '/vault', pathMatch: 'full' },
{ path: 'lock', component: LockComponent },
{ path: 'login', component: LoginComponent },
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },
{
path: 'vault',
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: 'hint', component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent },
{
path: 'send',
component: SendComponent,
canActivate: [AuthGuardService],
},
{ path: "", redirectTo: "/vault", pathMatch: "full" },
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: "login",
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: "2fa", component: TwoFactorComponent },
{ path: "register", component: RegisterComponent },
{
path: "vault",
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: "hint", component: HintComponent },
{ path: "set-password", component: SetPasswordComponent },
{ path: "sso", component: SsoComponent },
{
path: "send",
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

File diff suppressed because it is too large Load Diff

View File

@ -1,227 +1,224 @@
import 'core-js';
import 'zone.js/dist/zone';
import "zone.js/dist/zone";
import { ToasterModule } from 'angular2-toaster';
import { Angulartics2Module } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { DatePipe, registerLocaleData } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeMe from "@angular/common/locales/sr-Latn-ME";
import localeSv from "@angular/common/locales/sv";
import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { JslibModule } from "jslib-angular/jslib.module";
import { DragDropModule } from '@angular/cdk/drag-drop';
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { PremiumComponent } from "./accounts/premium.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SettingsComponent } from "./accounts/settings.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { AccountSwitcherComponent } from "./layout/account-switcher.component";
import { HeaderComponent } from "./layout/header.component";
import { NavComponent } from "./layout/nav.component";
import { SearchComponent } from "./layout/search/search.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { ServicesModule } from "./services.module";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from "./vault/view.component";
import { AppComponent } from './app.component';
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { PremiumComponent } from './accounts/premium.component';
import { RegisterComponent } from './accounts/register.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SettingsComponent } from './accounts/settings.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { CalloutComponent } from 'jslib/angular/components/callout.component';
import { IconComponent } from 'jslib/angular/components/icon.component';
import { ModalComponent } from 'jslib/angular/components/modal.component';
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
import { SelectCopyDirective } from 'jslib/angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-value.directive';
import { ColorPasswordPipe } from 'jslib/angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { ExportComponent } from './vault/export.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
import { PasswordGeneratorComponent } from './vault/password-generator.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { ViewComponent } from './vault/view.component';
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component';
import { SendComponent } from './send/send.component';
import { NavComponent } from './layout/nav.component';
import { registerLocaleData } from '@angular/common';
import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localEnGb from '@angular/common/locales/en-GB';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFa from '@angular/common/locales/fa';
import localeFi from '@angular/common/locales/fi';
import localeFr from '@angular/common/locales/fr';
import localeHr from '@angular/common/locales/hr';
import localeHu from '@angular/common/locales/hu';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKo from '@angular/common/locales/ko';
import localeMl from '@angular/common/locales/ml';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSr from '@angular/common/locales/sr';
import localeSv from '@angular/common/locales/sv';
import localeTh from '@angular/common/locales/th';
import localeTr from '@angular/common/locales/tr';
import localeUk from '@angular/common/locales/uk';
import localeVi from '@angular/common/locales/vi';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
registerLocaleData(localeBe, 'be');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localEnGb, 'en-GB');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFa, 'fa');
registerLocaleData(localeFi, 'fi');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeHr, 'hr');
registerLocaleData(localeHu, 'hu');
registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKo, 'ko');
registerLocaleData(localeMl, 'ml');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRo, 'ro');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSr, 'sr');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeTh, 'th');
registerLocaleData(localeTr, 'tr');
registerLocaleData(localeUk, 'uk');
registerLocaleData(localeVi, 'vi');
registerLocaleData(localeZhCn, 'zh-CN');
registerLocaleData(localeZhTw, 'zh-TW');
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMe, "me");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot({
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,
],
declarations: [
A11yTitleDirective,
AddEditComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HintComponent,
I18nPipe,
IconComponent,
LockComponent,
LoginComponent,
ModalComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PremiumComponent,
RegisterComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
VaultComponent,
ViewComponent,
],
entryComponents: [
AttachmentsComponent,
CollectionsComponent,
EnvironmentComponent,
ExportComponent,
FolderAddEditComponent,
ModalComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PremiumComponent,
SettingsComponent,
ShareComponent,
SendAddEditComponent,
TwoFactorOptionsComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
imports: [
A11yModule,
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
JslibModule,
OverlayModule,
ReactiveFormsModule,
ScrollingModule,
ServicesModule,
],
declarations: [
AccountSwitcherComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AppComponent,
AttachmentsComponent,
CiphersComponent,
CollectionsComponent,
EnvironmentComponent,
ExportComponent,
FolderAddEditComponent,
GroupingsComponent,
HeaderComponent,
HintComponent,
LockComponent,
LoginComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@ -0,0 +1,55 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">{{ "passwordConfirmation" | i18n }}</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "passwordConfirmationDesc" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,8 @@
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@ -0,0 +1,65 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{ "setYourPinCode" | i18n }}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPin ? 'text' : 'password' }}"
name="Pin"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleVisibility()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPin, 'bwi-eye-slash': showPin }"
></i>
</a>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,8 @@
import { Component } from "@angular/core";
import { SetPinComponent as BaseSetPinComponent } from "jslib-angular/components/set-pin.component";
@Component({
templateUrl: "set-pin.component.html",
})
export class SetPinComponent extends BaseSetPinComponent {}

View File

@ -0,0 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>

View File

@ -0,0 +1,23 @@
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@ -1,14 +0,0 @@
import { NgModule } from '@angular/core';
import { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.directive';
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
@NgModule({
imports: [],
declarations: [
InputVerbatimDirective,
SearchPipe,
],
})
export class DummyModule {
}

View File

@ -0,0 +1,101 @@
<button
class="account-switcher"
(click)="toggle()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
[hidden]="!showSwitcher"
aria-haspopup="menu"
aria-controls="cdk-overlay-container"
[attr.aria-expanded]="isOpen"
>
<ng-container *ngIf="activeAccountEmail != null; else noActiveAccount">
<app-avatar
[data]="activeAccountEmail"
size="25"
[circle]="true"
[fontSize]="14"
[dynamic]="true"
*ngIf="activeAccountEmail != null"
aria-hidden="true"
></app-avatar>
<span>{{ activeAccountEmail }}</span>
</ng-container>
<ng-template #noActiveAccount>
<span>{{ "switchAccount" | i18n }}</span>
</ng-template>
<i
class="bwi"
aria-hidden="true"
[ngClass]="{ 'bwi-angle-down': !isOpen, 'bwi-chevron-up': isOpen }"
></i>
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
(backdropClick)="close()"
(detach)="close()"
[cdkConnectedOverlayOpen]="showSwitcher && isOpen"
[cdkConnectedOverlayPositions]="overlayPostition"
cdkConnectedOverlayMinWidth="250px"
>
<div
class="account-switcher-dropdown"
[@transformPanel]="'open'"
cdkTrapFocus
cdkTrapFocusAutoCapture
role="dialog"
aria-modal="true"
>
<div class="accounts" *ngIf="numberOfAccounts > 0">
<button
*ngFor="let a of accounts | keyvalue"
class="account"
[ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
(click)="switch(a.key)"
appA11yTitle="{{ 'loggedInAsOn' | i18n: a.value.profile.email:a.value.serverUrl }}"
attr.aria-label="{{ 'switchAccount' | i18n }}"
>
<app-avatar
[data]="a.value.profile.email"
size="25"
[circle]="true"
[fontSize]="14"
[dynamic]="true"
*ngIf="a.value.profile.email != null"
aria-hidden="true"
></app-avatar>
<div class="accountInfo">
<span class="email" aria-hidden="true">{{ a.value.profile.email }}</span>
<span class="server" aria-hidden="true" *ngIf="a.value.serverUrl != 'bitwarden.com'">{{
a.value.serverUrl
}}</span>
<span class="status" aria-hidden="true">{{ a.value.profile.authenticationStatus }}</span>
</div>
<i
class="bwi bwi-unlock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'unlocked'"
></i>
<i
class="bwi bwi-lock bwi-2x text-muted"
aria-hidden="true"
*ngIf="a.value.profile.authenticationStatus == 'locked'"
></i>
</button>
</div>
<ng-container *ngIf="activeAccountEmail != null">
<div class="border" *ngIf="numberOfAccounts > 0"></div>
<ng-container *ngIf="numberOfAccounts < 4">
<button class="add" routerLink="/login" (click)="addAccount()">
<i class="bwi bwi-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="numberOfAccounts == 4">
<span class="accountLimitReached">{{ "accountSwitcherLimitReached" | i18n }} </span>
</ng-container>
</ng-container>
</div>
</ng-template>

View File

@ -0,0 +1,140 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, OnInit } from "@angular/core";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
import { Utils } from "jslib-common/misc/utils";
import { Account } from "jslib-common/models/domain/account";
export class SwitcherAccount extends Account {
get serverUrl() {
return this.removeWebProtocolFromString(
this.settings?.environmentUrls?.base ??
this.settings?.environmentUrls.api ??
"https://bitwarden.com"
);
}
private removeWebProtocolFromString(urlString: string) {
const regex = /http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g;
return urlString.replace(regex, "");
}
}
@Component({
selector: "app-account-switcher",
templateUrl: "account-switcher.component.html",
animations: [
trigger("transformPanel", [
state(
"void",
style({
opacity: 0,
})
),
transition(
"void => open",
animate(
"100ms linear",
style({
opacity: 1,
})
)
),
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]),
],
})
export class AccountSwitcherComponent implements OnInit {
isOpen = false;
accounts: { [userId: string]: SwitcherAccount } = {};
activeAccountEmail: string;
serverUrl: string;
overlayPostition: ConnectedPosition[] = [
{
originX: "end",
originY: "bottom",
overlayX: "end",
overlayY: "top",
},
];
get showSwitcher() {
const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccountEmail);
const userIsAddingAnAdditionalAccount = Object.keys(this.accounts).length > 0;
return userIsInAVault || userIsAddingAnAdditionalAccount;
}
get numberOfAccounts() {
if (this.accounts == null) {
this.isOpen = false;
return 0;
}
return Object.keys(this.accounts).length;
}
constructor(
private stateService: StateService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService
) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async (accounts) => {
for (const userId in accounts) {
if (userId === (await this.stateService.getUserId())) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
accounts[userId].profile.authenticationStatus = (await this.vaultTimeoutService.isLocked(
userId
))
? AuthenticationStatus.Locked
: AuthenticationStatus.Unlocked;
}
}
this.accounts = await this.createSwitcherAccounts(accounts);
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
close() {
this.isOpen = false;
}
async switch(userId: string) {
this.close();
this.messagingService.send("switchAccount", { userId: userId });
}
async addAccount() {
this.close();
await this.stateService.setActiveUser(null);
}
private async createSwitcherAccounts(baseAccounts: {
[userId: string]: Account;
}): Promise<{ [userId: string]: SwitcherAccount }> {
const switcherAccounts: { [userId: string]: SwitcherAccount } = {};
for (const userId in baseAccounts) {
if (userId == null || userId === (await this.stateService.getUserId())) {
continue;
}
// environmentUrls are stored on disk and must be retrieved seperatly from the in memory state offered from subscribing to accounts
baseAccounts[userId].settings.environmentUrls = await this.stateService.getEnvironmentUrls({
userId: userId,
});
switcherAccounts[userId] = new SwitcherAccount(baseAccounts[userId]);
}
return switcherAccounts;
}
}

View File

@ -0,0 +1,4 @@
<div class="header">
<app-search></app-search>
<app-account-switcher></app-account-switcher>
</div>

View File

@ -0,0 +1,7 @@
import { Component } from "@angular/core";
@Component({
selector: "app-header",
templateUrl: "header.component.html",
})
export class HeaderComponent {}

View File

@ -1,5 +1,5 @@
<ng-container *ngFor="let item of items">
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="fa" [ngClass]="item.icon"></i>{{ item.label }}
</a>
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="bwi" [ngClass]="item.icon"></i>{{ item.label }}
</a>
</ng-container>

View File

@ -1,23 +1,24 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { Component } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: 'app-nav',
templateUrl: 'nav.component.html',
selector: "app-nav",
templateUrl: "nav.component.html",
})
export class NavComponent {
items: any[] = [
{
link: '/vault',
icon: 'fa-lock',
label: this.i18nService.translate('myVault'),
},
{
link: '/send',
icon: 'fa-paper-plane',
label: 'Send',
},
];
items: any[] = [
{
link: "/vault",
icon: "bwi-lock-f",
label: this.i18nService.translate("myVault"),
},
{
link: "/send",
icon: "bwi-send-f",
label: "Send",
},
];
constructor(private i18nService: I18nService) {}
constructor(private i18nService: I18nService) {}
}

View File

@ -0,0 +1,38 @@
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
export type SearchBarState = {
enabled: boolean;
placeholderText: string;
};
@Injectable()
export class SearchBarService {
searchText = new BehaviorSubject<string>(null);
private _state = {
enabled: false,
placeholderText: "",
};
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setSearchText(value: string) {
this.searchText.next(value);
}
private updateState() {
this.state.next(this._state);
}
}

View File

@ -0,0 +1,11 @@
<div class="search" *ngIf="state.enabled">
<input
type="search"
[placeholder]="state.placeholderText"
id="search"
autocomplete="off"
[formControl]="searchText"
appAutofocus
/>
<i class="bwi bwi-search" aria-hidden="true"></i>
</div>

View File

@ -0,0 +1,23 @@
import { Component } from "@angular/core";
import { FormControl } from "@angular/forms";
import { SearchBarService, SearchBarState } from "./search-bar.service";
@Component({
selector: "app-search",
templateUrl: "search.component.html",
})
export class SearchComponent {
state: SearchBarState;
searchText: FormControl = new FormControl(null);
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe((state) => {
this.state = state;
});
this.searchText.valueChanges.subscribe((value) => {
this.searchBarService.setSearchText(value);
});
}
}

View File

@ -1,19 +1,19 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { isDev } from 'jslib/electron/utils';
import { isDev } from "jslib-electron/utils";
// tslint:disable-next-line
require('../scss/styles.scss');
require("../scss/styles.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (!isDev()) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
// Disable drag and drop to prevent malicious links from executing in the context of the app
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
document.addEventListener("dragover", (event) => event.preventDefault());
document.addEventListener("drop", (event) => event.preventDefault());

View File

@ -1,202 +1,297 @@
<form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus [readOnly]="disableSend">
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<label class="radio-header">{{'whatTypeOfSend' | i18n}}</label>
<div class="item" *ngFor="let o of typeOptions">
<input type="radio" class="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
[checked]="send.type === o.value" [disabled]="disableSend">
<label class="unstyled" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required [disabled]="disableSend">
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label>
<div class="row-main">{{send.file.fileName}} ({{send.file.sizeName}})</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{'text' | i18n}}</label>
<textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{'sendTextDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label>
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden" [disabled]="disableSend">
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
<a class="toggle" href="#" appStopClick appBlurClick role="button" (click)="toggleOptions()">
<i class="fa fa-lg" aria-hidden="true" [ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i>
</a>
</div>
</div>
<div [hidden]="!showOptions">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect" required>
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small class="help-block">{{'deletionDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" *ngIf="deletionDateSelect === 0 || editMode">
<label *ngIf="editMode" for="deletionDateCustom">{{'deletionDate' | i18n}}</label>
<input id="deletionDateCustom" type="datetime-local" name="deletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
<small class="help-block" *ngIf="editMode">{{'deletionDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<select id="expirationDate" name="expirationDateSelect" [(ngModel)]="expirationDateSelect" required>
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small class="help-block">{{'expirationDateDesc' | i18n}}</small>
</div>
<div class="box-content-row" *ngIf="expirationDateSelect === 0 || editMode">
<label *ngIf="editMode" for="expirationDateCustom">{{'expirationDate' | i18n}}</label>
<input id="expirationDateCustom" type="datetime-local" name="expirationDate"
[(ngModel)]="expirationDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
<small *ngIf="editMode" class="help-block">{{'expirationDateDesc' | i18n}}</small>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount" [readOnly]="disableSend">
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{'maxAccessCountDesc' | i18n}}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{'maxAccessCountDesc' | i18n}}</p>
{{'currentAccessCount' | i18n}}: <strong>{{send.accessCount}}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
<input id="password" name="password" type="{{showPassword ? 'text' : 'password'}}" [(ngModel)]="password" [readOnly]="disableSend">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()" [disabled]="disableSend">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'sendPasswordDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
{{'notes' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer">
{{'sendNotesDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{'hideEmail' | i18n}}</label>
<input id="hideEmail" type="checkbox" name="HideEmail" [(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend">
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disabled">{{'disableSend' | i18n}}</label>
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled" [disabled]="disableSend">
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'share' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="link">{{'sendLinkLabel' | i18n}}</label>
<input id="link" name="link" [ngModel]="link" readonly>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyLink">{{'copySendLinkOnSave' | i18n}}</label>
<input id="copyLink" name="copyLink" [(ngModel)]="copyLink" type="checkbox" [disabled]="disableSend">
</div>
</div>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{ "sendDisabledWarning" | i18n }}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{ title }}
</div>
</div>
<div class="footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" *ngIf="!disableSend">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{'cancel' | i18n}}
</button>
<div class="right">
<button appBlurClick type="button" (click)="copyLinkToClipboard(link)" appA11yTitle="{{'copySendLinkToClipboard' | i18n}}" *ngIf="editMode">
<i class="fa fa-copy fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i>
</button>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="send.name"
appAutofocus
[readOnly]="disableSend"
/>
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<label class="radio-header">{{ "whatTypeOfSend" | i18n }}</label>
<div class="item" *ngFor="let o of typeOptions">
<input
type="radio"
class="radio"
[(ngModel)]="send.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged(o)"
[checked]="send.type === o.value"
[disabled]="disableSend"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
<label for="file">{{ "file" | i18n }}</label>
<input
type="file"
id="file"
class="form-control-file"
name="file"
required
[disabled]="disableSend"
/>
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
<label for="file">{{ "file" | i18n }}</label>
<div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{ "text" | i18n }}</label>
<textarea
id="text"
name="text"
[(ngModel)]="send.text.text"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{ "sendTextDesc" | i18n }}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{ "textHiddenByDefault" | i18n }}</label>
<input
id="hideText"
name="hideText"
type="checkbox"
[(ngModel)]="send.text.hidden"
[disabled]="disableSend"
/>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "options" | i18n }}
<a
class="toggle"
href="#"
appStopClick
appBlurClick
role="button"
(click)="toggleOptions()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-angle-down': !showOptions, 'bwi-chevron-up': showOptions }"
></i>
</a>
</div>
</div>
<div [hidden]="!showOptions">
<app-send-efflux-dates
[initialDeletionDate]="send.deletionDate"
[initialExpirationDate]="send.expirationDate"
[editMode]="editMode"
[disabled]="disableSend"
(datesChanged)="setDates($event)"
>
</app-send-efflux-dates>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
<input
id="maxAccessCount"
type="number"
name="maxAccessCount"
[(ngModel)]="send.maxAccessCount"
[readOnly]="disableSend"
/>
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{ "maxAccessCountDesc" | i18n }}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{ "maxAccessCountDesc" | i18n }}</p>
{{ "currentAccessCount" | i18n }}: <strong>{{ send.accessCount }}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{
(hasPassword ? "newPassword" : "password") | i18n
}}</label>
<input
id="password"
name="password"
type="{{ showPassword ? 'text' : 'password' }}"
[(ngModel)]="password"
[readOnly]="disableSend"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePasswordVisible()"
[disabled]="disableSend"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "sendPasswordDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
{{ "notes" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea
id="notes"
name="notes"
[(ngModel)]="send.notes"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer">
{{ "sendNotesDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{ "hideEmail" | i18n }}</label>
<input
id="hideEmail"
type="checkbox"
name="HideEmail"
[(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
/>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disabled">{{ "disableSend" | i18n }}</label>
<input
id="disabled"
type="checkbox"
name="disabled"
[(ngModel)]="send.disabled"
[disabled]="disableSend"
/>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "share" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
<input id="link" name="link" [ngModel]="link" readonly />
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyLink">{{ "copySendLinkOnSave" | i18n }}</label>
<input
id="copyLink"
name="copyLink"
[(ngModel)]="copyLink"
type="checkbox"
[disabled]="disableSend"
/>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary btn-submit"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
*ngIf="!disableSend"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span><i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i></span>
</button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="copyLinkToClipboard(link)"
appA11yTitle="{{ 'copySendLinkToClipboard' | i18n }}"
*ngIf="editMode"
>
<i class="bwi bwi-clone bwi-lg bwi-fw" aria-hidden="true"></i>
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
>
<i class="bwi bwi-trash bwi-lg bwi-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>

View File

@ -1,47 +1,62 @@
import { DatePipe } from '@angular/common';
import { DatePipe } from "@angular/common";
import { Component } from "@angular/core";
import { Component } from '@angular/core';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SendService } from 'jslib/abstractions/send.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/send/add-edit.component";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
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, tokenService: TokenService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, userService, messagingService, policyService, tokenService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
datePipe: DatePipe,
sendService: SendService,
stateService: StateService,
messagingService: MessagingService,
policyService: PolicyService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
environmentService,
datePipe,
sendService,
messagingService,
policyService,
logService,
stateService
);
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
this.deletionDate = this.dateToString(this.send.deletionDate);
this.expirationDate = this.dateToString(this.send.expirationDate);
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
cancel() {
this.onCancelled.emit(this.send);
}
cancel() {
this.onCancelled.emit(this.send);
}
copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
}

View File

@ -0,0 +1,55 @@
<ng-container [formGroup]="datesForm">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<select
id="deletionDate"
name="DeletionDateSelect"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "deletionDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="deletionDateCustom">{{ "deletionDate" | i18n }}</label>
<input
id="deletionDateCustom"
type="datetime-local"
name="deletionDate"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
<small class="help-block" *ngIf="editMode">{{ "deletionDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
<select
id="expirationDate"
name="expirationDateSelect"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div>
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="expirationDateCustom">{{ "expirationDate" | i18n }}</label>
<input
id="expirationDateCustom"
type="datetime-local"
name="expirationDate"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disableSend"
/>
<small *ngIf="editMode" class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div>
</div>
</div>
</ng-container>

View File

@ -0,0 +1,38 @@
import { DatePipe } from "@angular/common";
import { Component, OnChanges } from "@angular/core";
import { ControlContainer, NgForm } from "@angular/forms";
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "jslib-angular/components/send/efflux-dates.component";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: "app-send-efflux-dates",
templateUrl: "efflux-dates.component.html",
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges {
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {
super(i18nService, platformUtilsService, datePipe);
}
// We reuse the same form on desktop and just swap content, so need to watch these to maintin proper values.
ngOnChanges() {
this.selectedExpirationDatePreset.setValue(0);
this.selectedDeletionDatePreset.setValue(0);
this.defaultDeletionDateTime.setValue(
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
);
if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
);
} else {
this.defaultExpirationDateTime.setValue(null);
}
}
}

Some files were not shown because too many files have changed in this diff Show More