1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Joseph Flinn
f244f4ffde More work on the decoupling. It is not currently in a working state 2022-01-28 22:41:45 +00:00
Joseph Flinn
9d393224da switching to an nginx image instead of a bitwarden/server one 2022-01-28 12:01:47 -08:00
362 changed files with 8114 additions and 24898 deletions

View File

@@ -1,3 +1,4 @@
*
!docker/*
!build/*
!entrypoint.sh

View File

@@ -1,8 +0,0 @@
**/dist
**/build
jslib
webpack.config.js
scripts/optimize.js
config.js
**/node_modules

View File

@@ -1,31 +0,0 @@
{
"root": true,
"env": {
"browser": 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"]
}
]
}
}

View File

@@ -1,354 +0,0 @@
---
name: Build CLI
on:
push:
branches-ignore:
- 'l10n_master'
paths:
- 'apps/cli/**'
workflow_dispatch: {}
defaults:
run:
working-directory: apps/cli
jobs:
cloc:
name: CLOC
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up cloc
run: |
sudo apt update
sudo apt -y install cloc
- name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript --vcs git
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Get Package Version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version package.json)
echo "::set-output name=package_version::$PKG_VERSION"
cli:
name: Build CLI
runs-on: windows-2019
needs:
- setup
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
_WIN_PKG_FETCH_VERSION: 16.14.2
_WIN_PKG_VERSION: 3.3
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Setup Windows builder
run: |
choco install checksum --no-progress
choco install reshack --no-progress
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/cli/**/package-lock.json'
node-version: '16'
- name: Get pkg-fetch
shell: pwsh
run: |
cd $HOME
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
New-Item -ItemType directory -Path .\.pkg-cache
New-Item -ItemType directory -Path .\.pkg-cache\v$env:_WIN_PKG_VERSION
Invoke-RestMethod -Uri $fetchedUrl `
-OutFile ".\.pkg-cache\v$env:_WIN_PKG_VERSION\fetched-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
- name: Setup Version Info
shell: pwsh
run: |
$major,$minor,$patch = $env:_PACKAGE_VERSION.split('.')
$versionInfo = @"
1 VERSIONINFO
FILEVERSION $major,$minor,$patch,0
PRODUCTVERSION $major,$minor,$patch,0
FILEOS 0x40004
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "040904b0"
{
VALUE "CompanyName", "Bitwarden Inc."
VALUE "ProductName", "Bitwarden"
VALUE "FileDescription", "Bitwarden CLI"
VALUE "FileVersion", "$env:_PACKAGE_VERSION"
VALUE "ProductVersion", "$env:_PACKAGE_VERSION"
VALUE "OriginalFilename", "bw.exe"
VALUE "InternalName", "bw"
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409 0x04B0
}
}
"@
$versionInfo | Out-File ./version-info.rc
# https://github.com/vercel/pkg-fetch/issues/188
- name: Resource Hacker
shell: cmd
run: |
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
set WIN_PKG=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\fetched-v%_WIN_PKG_FETCH_VERSION%-win-x64
set WIN_PKG_BUILT=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\built-v%_WIN_PKG_FETCH_VERSION%-win-x64
copy %WIN_PKG% %WIN_PKG_BUILT%
ResourceHacker -open %WIN_PKG_BUILT% -save %WIN_PKG_BUILT% -action delete -mask ICONGROUP,1,
ResourceHacker -open version-info.rc -save version-info.res -action compile
ResourceHacker -open %WIN_PKG_BUILT% -save %WIN_PKG_BUILT% -action addoverwrite -resource version-info.res
- name: Setup sub-module
run: npm run sub:init
- name: Install
run: npm ci
- name: Run tests
run: npm run test
- name: Build & Package
run: npm run dist
- name: Package Chocolatey
shell: pwsh
run: |
Copy-Item -Path stores/chocolatey -Destination dist/chocolatey -Recurse
Copy-Item dist/windows/bw.exe -Destination dist/chocolatey/tools
Copy-Item LICENSE.txt -Destination dist/chocolatey/tools
choco pack dist/chocolatey/bitwarden-cli.nuspec --version ${{ env._PACKAGE_VERSION }} --out dist/chocolatey
- name: Zip
shell: cmd
run: |
7z a ./dist/bw-windows-%_PACKAGE_VERSION%.zip ./dist/windows/bw.exe
7z a ./dist/bw-macos-%_PACKAGE_VERSION%.zip ./dist/macos/bw
7z a ./dist/bw-linux-%_PACKAGE_VERSION%.zip ./dist/linux/bw
- name: Version Test
run: |
dir ./dist/
Expand-Archive -Path "./dist/bw-windows-${env:_PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
$testVersion = Invoke-Expression '& ./test/windows/bw.exe -v'
echo "version: $env:_PACKAGE_VERSION"
echo "testVersion: $testVersion"
if($testVersion -ne $env:_PACKAGE_VERSION) {
Throw "Version test failed."
}
- name: Create checksums
run: |
checksum -f="./dist/bw-windows-${env:_PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-windows-sha256-${env:_PACKAGE_VERSION}.txt
checksum -f="./dist/bw-macos-${env:_PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-macos-sha256-${env:_PACKAGE_VERSION}.txt
checksum -f="./dist/bw-linux-${env:_PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-linux-sha256-${env:_PACKAGE_VERSION}.txt
- name: Upload windows zip asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-windows-${{ env._PACKAGE_VERSION }}.zip
path: apps/cli/dist/bw-windows-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload windows checksum asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
path: apps/cli/dist/bw-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
- name: Upload macos zip asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-macos-${{ env._PACKAGE_VERSION }}.zip
path: apps/cli/dist/bw-macos-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload macos checksum asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
path: apps/cli/dist/bw-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
- name: Upload linux zip asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-linux-${{ env._PACKAGE_VERSION }}.zip
path: apps/cli/dist/bw-linux-${{ env._PACKAGE_VERSION }}.zip
if-no-files-found: error
- name: Upload linux checksum asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
path: apps/cli/dist/bw-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
- name: Upload Chocolatey asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg
path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg
if-no-files-found: error
- name: Upload NPM Build Directory asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip
path: apps/cli/build
if-no-files-found: error
snap:
name: Build Snap
runs-on: ubuntu-20.04
needs: [setup, cli]
env:
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Print environment
run: |
whoami
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
echo "BW Package Version: $_PACKAGE_VERSION"
- name: Get bw linux cli
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741
with:
name: bw-linux-${{ env._PACKAGE_VERSION }}.zip
path: apps/cli/dist/snap
- name: Setup Snap Package
run: |
cp -r stores/snap/* -t dist/snap
sed -i s/__version__/${{ env._PACKAGE_VERSION }}/g dist/snap/snapcraft.yaml
cd dist/snap
ls -alth
- name: Build snap
uses: snapcore/action-build@ea14cdeb353272f75977040488ca191880509a8c # v1.1.0
with:
path: apps/cli/dist/snap
- name: Create checksum
run: |
cd dist/snap
ls -alth
sha256sum bw_${{ env._PACKAGE_VERSION }}_amd64.snap \
| awk '{split($0, a); print a[1]}' > bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt
- name: Install Snap
run: sudo snap install dist/snap/bw*.snap --dangerous
- name: Test Snap
shell: pwsh
run: |
$testVersion = Invoke-Expression '& bw -v'
if($testVersion -ne $env:_PACKAGE_VERSION) {
Throw "Version test failed."
}
env:
BITWARDENCLI_APPDATA_DIR: "/home/runner/snap/bw/x1/.config/Bitwarden CLI"
- name: Cleanup Test & Update Snap for Publish
shell: pwsh
run: sudo snap remove bw
- name: Upload snap asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap
path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap
if-no-files-found: error
- name: Upload snap checksum asset
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt
path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt
if-no-files-found: error
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-20.04
needs:
- cloc
- setup
- cli
- snap
steps:
- name: Check if any job failed
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
CLI_STATUS: ${{ needs.cli.result }}
SNAP_STATUS: ${{ needs.snap.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$CLI_STATUS" = "failure" ]; then
exit 1
elif [ "$SNAP_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -1,497 +0,0 @@
---
name: Build Web
on:
push:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
paths:
- 'apps/web/**'
workflow_dispatch:
inputs:
custom_tag_extension:
description: "Custom image tag extension"
required: false
defaults:
run:
working-directory: apps/web
jobs:
cloc:
name: CLOC
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up cloc
run: |
sudo apt update
sudo apt -y install cloc
- name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
version: ${{ steps.version.outputs.value }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Get GitHub sha as version
id: version
run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
build-oss-selfhost:
name: Build OSS zip
runs-on: ubuntu-20.04
needs:
- setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/web/**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Install dependencies
run: npm ci
- name: Build OSS selfhost
run: |
npm run dist:oss:selfhost
zip -r web-$_VERSION-selfhosted-open-source.zip build
- name: Upload build artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: web-${{ env._VERSION }}-selfhosted-open-source.zip
path: apps/web/web-${{ env._VERSION }}-selfhosted-open-source.zip
if-no-files-found: error
build-cloud:
name: Build Cloud zip
runs-on: ubuntu-20.04
needs:
- setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/web/**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Install dependencies
run: npm ci
- name: Build Cloud
run: |
npm run dist:bit:cloud
zip -r web-$_VERSION-cloud-COMMERCIAL.zip build
- name: Upload build artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
path: apps/web/web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
if-no-files-found: error
build-commercial-selfhost:
name: Build SelfHost Docker image
runs-on: ubuntu-20.04
needs:
- setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/web/**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Setup DCT
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
with:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv"
- name: Install dependencies
run: npm ci
- name: Build
run: |
echo -e "# Building Web\n"
echo "Building app"
echo "npm version $(npm --version)"
npm run dist:bit:selfhost
zip -r web-$_VERSION-selfhosted-COMMERCIAL.zip build
- name: Upload build artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
path: apps/web/web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
if-no-files-found: error
- name: Build Docker image
run: |
echo -e "\nBuilding Docker image"
docker --version
docker build -t bitwarden/web .
- name: Tag rc branch
if: github.ref == 'refs/heads/rc'
run: docker tag bitwarden/web bitwarden/web:rc
- name: Tag dev
if: github.ref == 'refs/heads/master'
run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag hotfix branch
if: github.ref == 'refs/heads/hotfix-rc'
run: docker tag bitwarden/web bitwarden/web:hotfix-rc
- name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: docker images
- name: Push rc image
if: github.ref == 'refs/heads/rc'
run: docker push bitwarden/web:rc
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Push dev image
if: github.ref == 'refs/heads/master'
run: docker push bitwarden/web:dev
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Push hotfix image
if: github.ref == 'refs/heads/hotfix-rc'
run: docker push bitwarden/web:hotfix-rc
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
- name: Login to Azure - QA Subscription
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag and Push RC to Azure ACR QA registry
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag bitwarden/web \
$REGISTRY/web-sh:$IMAGE_TAG
docker push $REGISTRY/web-sh:$IMAGE_TAG
- name: Log out of Docker
run: docker logout
build-qa:
name: Build Docker images for QA environment
runs-on: ubuntu-20.04
needs:
- setup
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/web/**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Log into container registry
run: az acr login -n bitwardenqa
- name: Install dependencies
run: npm ci
- name: Build
run: |
echo -e "# Building Web\n"
echo "Building app"
echo "npm version $(npm --version)"
VERSION=$( jq -r ".version" package.json)
jq --arg version "$VERSION - ${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
mv package.json.tmp package.json
npm run build:bit:qa
echo "{\"commit_hash\": \"$GITHUB_SHA\", \"ref\": \"$GITHUB_REF\"}" | jq . > build/info.json
echo -e "\nBuilding Docker image"
docker --version
docker build -t bitwardenqa.azurecr.io/web .
- name: Get image tag
id: image-tag
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }}
if [[ $TAG_EXTENSION ]]; then
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
fi
echo "::set-output name=value::$IMAGE_TAG"
- name: Tag image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
run: docker tag bitwardenqa.azurecr.io/web "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
- name: Tag dev
if: github.ref == 'refs/heads/master'
run: docker tag bitwardenqa.azurecr.io/web bitwardenqa.azurecr.io/web:dev
- name: List Docker images
run: docker images
- name: Push image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
run: docker push "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
- name: Push dev images
if: github.ref == 'refs/heads/master'
run: docker push bitwardenqa.azurecr.io/web:dev
- name: Log out of Docker
run: docker logout
windows:
name: Test code on Windows
runs-on: windows-2019
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Set up NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: "latest"
- name: Set up Node
uses: actions/setup-node@56337c425554a6be30cdef71bf441f15be286854 # v3.1.1
with:
cache: 'npm'
cache-dependency-path: 'apps/web/**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
nuget help | grep Version
node --version
npm --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Install dependencies
run: npm ci
- name: NPM build
run: npm run build:bit:cloud
crowdin-push:
name: Crowdin Push
if: github.ref == 'refs/heads/master'
needs:
- build-oss-selfhost
- build-cloud
- build-commercial-selfhost
- build-qa
runs-on: ubuntu-20.04
env:
_CROWDIN_PROJECT_ID: "308189"
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 # v2.3.4
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f # v1.0.0
with:
keyvault: "bitwarden-prod-kv"
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@a3160b9e5a9e00739392c23da5e580c6cabe526d # v1.4.8
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: true
upload_translations: false
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-20.04
needs:
- cloc
- setup
- build-oss-selfhost
- build-cloud
- build-commercial-selfhost
- build-qa
- crowdin-push
- windows
steps:
- name: Check if any job failed
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
SETUP_STATUS: ${{ needs.setup.result }}
BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }}
BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }}
BUILD_COMMERCIAL_SELFHOST_STATUS: ${{ needs.build-commercial-selfhost.result }}
BUILD_QA_STATUS: ${{ needs.build-qa.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
WINDOWS_STATUS: ${{ needs.windows.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_CLOUD_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_COMMERCIAL_SELFHOST_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_QA_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1
elif [ "$WINDOWS_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f # v1.0.0
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33 # v1.5.1
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -11,9 +11,6 @@ on:
branches-ignore:
- "l10n_master"
- "gh-pages"
paths-ignore:
- '.github/workflows/**'
jobs:
cloc:
@@ -31,28 +28,6 @@ jobs:
- name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
lint:
name: Lint
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-lint-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
setup:
name: Setup
runs-on: ubuntu-20.04
@@ -66,26 +41,25 @@ jobs:
id: version
run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
build-oss-selfhost:
name: Build OSS zip
runs-on: ubuntu-20.04
needs:
- setup
- lint
needs: setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment
run: |
whoami
@@ -96,6 +70,9 @@ jobs:
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies
run: npm ci
@@ -111,26 +88,25 @@ jobs:
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
if-no-files-found: error
build-cloud:
name: Build Cloud zip
runs-on: ubuntu-20.04
needs:
- setup
- lint
needs: setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment
run: |
whoami
@@ -141,6 +117,9 @@ jobs:
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies
run: npm ci
@@ -156,26 +135,25 @@ jobs:
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
if-no-files-found: error
build-commercial-selfhost:
name: Build SelfHost Docker image
runs-on: ubuntu-20.04
needs:
- setup
- lint
needs: setup
env:
_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment
run: |
whoami
@@ -187,13 +165,19 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Setup DCT
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
with:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
- name: Install dependencies
run: npm ci
@@ -228,11 +212,11 @@ jobs:
run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag hotfix branch
if: github.ref == 'refs/heads/hotfix-rc'
run: docker tag bitwarden/web bitwarden/web:hotfix-rc
if: github.ref == 'refs/heads/hotfix'
run: docker tag bitwarden/web bitwarden/web:hotfix
- name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
run: docker images
- name: Push rc image
@@ -250,59 +234,32 @@ jobs:
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Push hotfix image
if: github.ref == 'refs/heads/hotfix-rc'
run: docker push bitwarden/web:hotfix-rc
if: github.ref == 'refs/heads/hotfix'
run: docker push bitwarden/web:hotfix
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag and Push RC to Azure ACR QA registry
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag bitwarden/web \
$REGISTRY/web-sh:$IMAGE_TAG
docker push $REGISTRY/web-sh:$IMAGE_TAG
- name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
run: docker logout
build-qa:
name: Build Docker images for QA environment
runs-on: ubuntu-20.04
needs:
- setup
- lint
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment
run: |
whoami
@@ -321,6 +278,12 @@ jobs:
- name: Log into container registry
run: az acr login -n bitwardenqa
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
- name: Install dependencies
run: npm ci
@@ -376,29 +339,35 @@ jobs:
- name: Log out of Docker
run: docker logout
windows:
name: Test code on Windows
runs-on: windows-2019
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up NuGet
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
with:
nuget-version: "latest"
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16"
- name: Print environment
run: |
nuget help | grep Version
msbuild -version
dotnet --info
node --version
npm --version
echo "GitHub ref: $GITHUB_REF"
@@ -407,13 +376,18 @@ jobs:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: NPM build
run: npm run build:bit:cloud
crowdin-push:
name: Crowdin Push
if: github.ref == 'refs/heads/master'
@@ -452,7 +426,6 @@ jobs:
upload_sources: true
upload_translations: false
check-failures:
name: Check for failures
if: always()
@@ -460,7 +433,6 @@ jobs:
needs:
- cloc
- setup
- lint
- build-oss-selfhost
- build-cloud
- build-commercial-selfhost
@@ -472,7 +444,6 @@ jobs:
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
env:
CLOC_STATUS: ${{ needs.cloc.result }}
LINT_STATUS: ${{ needs.lint.result }}
SETUP_STATUS: ${{ needs.setup.result }}
BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }}
BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }}
@@ -483,8 +454,6 @@ jobs:
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$LINT_STATUS" = "failure" ]; then
exit 1
elif [ "$SETUP_STATUS" = "failure" ]; then
exit 1
elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then

View File

@@ -1,16 +0,0 @@
---
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,211 +0,0 @@
---
name: Release CLI
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
- Dry Run
defaults:
run:
working-directory: apps/cli
jobs:
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
package_version: ${{ steps.retrieve-version.outputs.package_version }}
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
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: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Retrieve CLI release version
id: retrieve-version
run: |
PKG_VERSION=$(jq -r .version package.json)
echo "::set-output name=package_version::$PKG_VERSION"
- name: Check to make sure CLI release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases | jq -r 'first(.[] | select(.tag_name | startswith("cli"))).tag_name'
)
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-version.outputs.package_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- 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@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-ci.yml
path: apps/cli
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0
env:
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
with:
artifacts: "bw-windows-${{ env.PKG_VERSION }}.zip,
bw-windows-sha256-${{ env.PKG_VERSION }}.txt,
bw-macos-${{ env.PKG_VERSION }}.zip,
bw-macos-sha256-${{ env.PKG_VERSION }}.txt,
bw-linux-${{ env.PKG_VERSION }}.zip,
bw-linux-sha256-${{ env.PKG_VERSION }}.txt,
bitwarden-cli.${{ env.PKG_VERSION }}.nupkg,
bw_${{ env.PKG_VERSION }}_amd64.snap,
bw-snap-sha256-${{ env.PKG_VERSION }}.txt"
commit: ${{ github.sha }}
tag: v${{ env.PKG_VERSION }}
name: Version ${{ env.PKG_VERSION }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
snap:
name: Deploy Snap
runs-on: ubuntu-20.04
needs: setup
env:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
with:
keyvault: "bitwarden-prod-kv"
secrets: "snapcraft-store-token"
- name: Install Snap
uses: samuelmeuli/action-snapcraft@10d7d0a84d9d86098b19f872257df314b0bd8e2d # v1.2.0
with:
snapcraft_token: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }}
- name: Download artifacts
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-cli.yml
path: apps/cli
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: bw_${{ env._PKG_VERSION }}_amd64.snap
- name: Publish Snap & logout
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
snapcraft push bw_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout
choco:
name: Deploy Choco
runs-on: windows-2019
needs: setup
env:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- 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: Download artifacts
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-cli.yml
path: apps/cli
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: bitwarden-cli.${{ env._PKG_VERSION }}.nupkg
- name: Push to Chocolatey
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
shell: pwsh
run: |
cd dist
choco push
npm:
name: Publish NPM
runs-on: ubuntu-20.04
needs: setup
env:
_PKG_VERSION: ${{ needs.setup.outputs.package_version }}
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Download artifacts
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-cli.yml
path: apps/cli
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip
- name: Setup NPM
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Install Husky
run: npm install -g husky
- name: Publish NPM
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm publish --access public

View File

@@ -1,69 +0,0 @@
---
name: Web QA Release
on:
workflow_dispatch:
inputs:
image_extension:
description: "Image tag extension"
required: false
env:
_QA_CLUSTER_RESOURCE_GROUP: "bw-env-qa"
_QA_CLUSTER_NAME: "bw-aks-qa"
_QA_K8S_NAMESPACE: "bw-qa"
_QA_K8S_APP_NAME: "bw-web"
jobs:
deploy:
name: Deploy QA Web
runs-on: ubuntu-20.04
steps:
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Setup
run: export PATH=$PATH:~/work/web/web
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f # v1
with:
keyvault: "bitwarden-qa-kv"
secrets: "qa-aks-kubectl-credentials"
- name: Login with qa-aks-kubectl-credentials SP
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ env.qa-aks-kubectl-credentials }}
- name: Setup AKS access
run: |
echo "---az install---"
az aks install-cli --install-location ./kubectl --kubelogin-install-location ./kubelogin
echo "---az get-creds---"
az aks get-credentials -n $_QA_CLUSTER_NAME -g $_QA_CLUSTER_RESOURCE_GROUP
- name: Get image tag
id: image_tag
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
TAG_EXTENSION=${{ github.event.inputs.image_extension }}
if [[ $TAG_EXTENSION ]]; then
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
fi
echo "::set-output name=value::$IMAGE_TAG"
- name: Deploy Web image
env:
IMAGE_TAG: ${{ steps.image_tag.outputs.value }}
run: |
kubectl set image -n $_QA_K8S_NAMESPACE deployment/web web=bitwardenqa.azurecr.io/web:$IMAGE_TAG --record
kubectl rollout restart -n $_QA_K8S_NAMESPACE deployment/web
kubectl rollout status deployment/web -n $_QA_K8S_NAMESPACE

View File

@@ -1,350 +0,0 @@
---
name: Release Web
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
- Dry Run
defaults:
run:
working-directory: apps/web
jobs:
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
release_version: ${{ steps.version.outputs.package }}
tag_version: ${{ steps.version.outputs.tag }}
branch_name: ${{ steps.branch.outputs.branch_name }}
steps:
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
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: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Check Release Version
id: version
run: |
version=$( jq -r ".version" package.json)
previous_release_tag_version=$(
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name"
)
if [ "v$version" == "$previous_release_tag_version" ] && \
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
echo "[!] Already released v$version. Please bump version to continue"
exit 1
fi
echo "::set-output name=package::$version"
echo "::set-output name=tag::v$version"
- name: Get branch name
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch_name::$BRANCH_NAME"
self-host:
name: Release self-host docker
runs-on: ubuntu-20.04
needs: setup
env:
_BRANCH_NAME: ${{ needs.setup.outputs.branch_name }}
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_RELEASE_OPTION: ${{ github.event.inputs.release_type }}
steps:
- name: Print environment
run: |
whoami
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
echo "Github Release Option: $_RELEASE_OPTION"
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
########## DockerHub ##########
- name: Setup DCT
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
with:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv"
- name: Pull latest selfhost image
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker pull bitwarden/web:latest
else
docker pull bitwarden/web:$_BRANCH_NAME
fi
- name: Tag version and latest
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest bitwarden/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
fi
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
run: |
docker push bitwarden/web:$_RELEASE_VERSION
docker push bitwarden/web:latest
- name: Log out of Docker and disable Docker Notary
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
########## ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010 # v1.1
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag version and latest
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest $REGISTRY/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:latest
fi
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
docker push $REGISTRY/web:$_RELEASE_VERSION
docker push $REGISTRY/web:latest
docker push $REGISTRY/web-sh:$_RELEASE_VERSION
docker push $REGISTRY/web-sh:latest
- name: Log out of Docker
run: docker logout
ghpages-deploy:
name: Deploy Web Vault to GitHub Pages
runs-on: ubuntu-20.04
needs:
- setup
- self-host
env:
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 # v2.4.0
with:
ref: gh-pages
- name: Create gh-pages-deploy branch
run: |
git switch -c gh-pages-deploy-$_TAG_VERSION
git push -u origin gh-pages-deploy-$_TAG_VERSION
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 # v2.4.0
- name: Setup git config
run: |
git config user.name = "GitHub Action Bot"
git config user.email = "<>"
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-web.yml
path: apps/web
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory
- name: Unzip build asset
run: unzip web-*-cloud-COMMERCIAL.zip
- name: Deploy GitHub Pages
uses: crazy-max/ghaction-github-pages@a117e4aa1fb4854d021546d2abdfac95be568a3a # v2.6.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
target_branch: gh-pages-deploy-${{ needs.setup.outputs.tag_version }}
build_dir: build
keep_history: true
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
- name: Create GitHub Pages Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: gh-pages-deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION to GitHub Pages" \
--body "Deploying $_RELEASE_VERSION" \
--base gh-pages \
--head "$PR_BRANCH"
cfpages-deploy:
name: Deploy Web Vault to CloudFlare Pages branch
runs-on: ubuntu-20.04
needs:
- setup
- self-host
env:
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 # v2.4.0
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-web.yml
path: apps/web
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory
- name: Unzip build asset
run: unzip web-*-cloud-COMMERCIAL.zip
- name: Checkout Repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 # v2.4.0
with:
ref: deploy
path: deployment
- name: Setup git config
run: |
git config --global user.name = "GitHub Action Bot"
git config --global user.email = "<>"
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
git config --global url."https://".insteadOf ssh://
- name: Deploy CloudFlare Pages
run: |
rm -rf ./*
cp -R ../build/* .
working-directory: deployment
- name: Create cf-pages-deploy branch
run: |
git switch -c cf-pages-deploy-$_TAG_VERSION
git add .
git commit -m "Staging deploy ${{ needs.setup.outputs.release_version }}"
git push -u origin cf-pages-deploy-$_TAG_VERSION
working-directory: deployment
- name: Create CloudFlare Pages Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: cf-pages-deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION to CloudFlare Pages" \
--body "Deploying $_RELEASE_VERSION" \
--base deploy \
--head "$PR_BRANCH"
release:
name: Create GitHub Release
runs-on: ubuntu-20.04
needs:
- setup
- self-host
- ghpages-deploy
- cfpages-deploy
steps:
- name: Download latest build artifacts
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build-web.yml
path: apps/web
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
web-*-selfhosted-open-source.zip"
- name: Rename assets
run: |
mv web-*-selfhosted-COMMERCIAL.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip
mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0
with:
name: "Version ${{ needs.setup.outputs.release_version }}"
commit: ${{ github.sha }}
tag: "${{ needs.setup.outputs.tag_version }}"
body: "<insert release notes here>"
artifacts: "web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip,
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
dry-run:
name: Dry Run Cleanup
runs-on: ubuntu-20.04
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
env:
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
needs:
- setup
- release
steps:
- name: Checkout repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Remove gh-pages-deploy branch
run: git push origin --delete gh-pages-deploy-$_TAG_VERSION
- name: Remove cf-pages-deploy branch
run: git push origin --delete cf-pages-deploy-$_TAG_VERSION

View File

@@ -12,7 +12,6 @@ on:
options:
- Initial Release
- Redeploy
- Dry Run
jobs:
setup:
@@ -21,20 +20,19 @@ jobs:
outputs:
release_version: ${{ steps.version.outputs.package }}
tag_version: ${{ steps.version.outputs.tag }}
branch_name: ${{ steps.branch.outputs.branch_name }}
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
echo "==================================="
exit 1
fi
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
- name: Check Release Version
id: version
@@ -57,17 +55,15 @@ jobs:
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch_name::$BRANCH_NAME"
echo "::set-output name=branch-name::$BRANCH_NAME"
self-host:
name: Release self-host docker
runs-on: ubuntu-20.04
needs: setup
env:
_BRANCH_NAME: ${{ needs.setup.outputs.branch_name }}
_BRANCH_NAME: ${{ needs.setup.outputs.branch-name }}
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_RELEASE_OPTION: ${{ github.event.inputs.release_type }}
steps:
- name: Print environment
run: |
@@ -75,12 +71,7 @@ jobs:
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
echo "Github Release Option: $_RELEASE_OPTION"
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
########## DockerHub ##########
- name: Setup DCT
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
@@ -88,25 +79,21 @@ jobs:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Pull latest selfhost image
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker pull bitwarden/web:latest
else
docker pull bitwarden/web:$_BRANCH_NAME
fi
run: docker pull bitwarden/web:$_BRANCH_NAME
- name: Tag version and latest
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest bitwarden/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
fi
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
- name: List Docker images
run: docker images
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
@@ -114,49 +101,9 @@ jobs:
docker push bitwarden/web:$_RELEASE_VERSION
docker push bitwarden/web:latest
- name: Log out of Docker and disable Docker Notary
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
########## ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag version and latest
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag bitwarden/web:latest $REGISTRY/web:dryrun
else
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:latest
fi
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
docker push $REGISTRY/web:$_RELEASE_VERSION
docker push $REGISTRY/web:latest
docker push $REGISTRY/web-sh:$_RELEASE_VERSION
docker push $REGISTRY/web-sh:latest
- name: Log out of Docker
run: docker logout
ghpages-deploy:
name: Deploy Web Vault
runs-on: ubuntu-20.04
@@ -168,7 +115,7 @@ jobs:
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: gh-pages
@@ -178,7 +125,7 @@ jobs:
git push -u origin deploy-$_TAG_VERSION
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup git config
run: |
@@ -188,11 +135,11 @@ jobs:
git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory
@@ -200,7 +147,7 @@ jobs:
run: unzip web-*-cloud-COMMERCIAL.zip
- name: Deploy GitHub Pages
uses: crazy-max/ghaction-github-pages@a117e4aa1fb4854d021546d2abdfac95be568a3a # v2.6.0
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -208,10 +155,8 @@ jobs:
build_dir: build
keep_history: true
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
- name: Create Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -221,7 +166,6 @@ jobs:
--base gh-pages \
--head "$PR_BRANCH"
release:
name: Create GitHub Release
runs-on: ubuntu-20.04
@@ -235,7 +179,7 @@ jobs:
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
branch: ${{ needs.setup.outputs.branch-name }}
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
web-*-selfhosted-open-source.zip"
@@ -245,8 +189,7 @@ jobs:
mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09
with:
name: "Version ${{ needs.setup.outputs.release_version }}"
commit: ${{ github.sha }}
@@ -256,20 +199,3 @@ jobs:
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
dry-run:
name: Dry Run Cleanup
runs-on: ubuntu-20.04
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
env:
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
needs:
- setup
- release
steps:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
- name: Remove deploy branch
run: git push origin --delete deploy-$_TAG_VERSION

View File

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

0
.husky/pre-commit Executable file → Normal file
View File

View File

@@ -10,7 +10,7 @@ Here is how you can get involved:
- **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 [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
- **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
@@ -31,6 +31,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o
If you are interested in helping translate the Bitwarden web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -1,4 +1,4 @@
FROM bitwarden/server
FROM nginx:stable
LABEL com.bitwarden.product="bitwarden"
@@ -8,13 +8,29 @@ RUN apt-get update \
curl \
&& rm -rf /var/lib/apt/lists/*
ENV ASPNETCORE_URLS http://+:5000
COPY docker/nginx.conf /etc/nginx
COPY docker/nginx-web.conf /etc/nginx
COPY docker/mime.types /etc/nginx
COPY docker/security-headers.conf /etc/nginx
WORKDIR /app
EXPOSE 5000
COPY ./build .
COPY entrypoint.sh /
COPY docker/entrypoint.sh /
RUN chmod +x /entrypoint.sh
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
RUN bash /entrypoint.sh
RUN chown -R bitwarden:bitwarden /app && chmod -R 755 /app && \
chown -R bitwarden:bitwarden /var/cache/nginx && \
chown -R bitwarden:bitwarden /var/log/nginx && \
chown -R bitwarden:bitwarden /etc/nginx/conf.d
RUN touch /var/run/nginx.pid && \
chown -R bitwarden:bitwarden /var/run/nginx.pid
ENTRYPOINT ["/entrypoint.sh"]
USER bitwarden
EXPOSE 8080
HEALTHCHECK CMD curl -f http://localhost:8080 || exit 1
#ENTRYPOINT ["/entrypoint.sh"]
#CMD ["tail", "-f", "/dev/null"]
CMD nginx -g 'daemon off;'

View File

@@ -1,9 +1,3 @@
> **Repository Reorganization in Progress**
>
> We are currently migrating some projects over to a mono repository. For existing PR's we will be providing documentation on how to move/migrate them. To minimize the overhead we are actively reviewing open PRs. If possible please ensure any pending comments are resolved as soon as possible.
>
> New pull requests created during this transition period may not get addressed —if needed, please create a new PR after the reorganization is complete.
<p align="center">
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
</p>
@@ -67,10 +61,6 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts).
## 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.

View File

@@ -1,11 +1,39 @@
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!
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!
# 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).
- 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
While researching, we'd like to ask you to refrain from:
@@ -14,8 +42,4 @@ 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,7 +1,6 @@
import { Component } from "@angular/core";
import { AppComponent as BaseAppComponent } from "src/app/app.component";
import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component";

View File

@@ -5,12 +5,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule } from "@angular/router";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { JslibModule } from "jslib-angular/jslib.module";
import { OssRoutingModule } from "src/app/oss-routing.module";
import { OssModule } from "src/app/oss.module";
import { ServicesModule } from "src/app/services/services.module";
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
@@ -18,26 +13,35 @@ import { OrganizationsModule } from "./organizations/organizations.module";
import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component";
import { OssRoutingModule } from "src/app/oss-routing.module";
import { OssModule } from "src/app/oss.module";
import { ServicesModule } from "src/app/services/services.module";
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
@NgModule({
imports: [
OssModule,
JslibModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule,
DragDropModule,
AppRoutingModule,
OssRoutingModule,
OrganizationsModule, // Must be after OssRoutingModule for competing routes to resolve properly
OrganizationsModule,
RouterModule,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
],
declarations: [
AppComponent,
DisablePersonalVaultExportPolicyComponent,
MaximumVaultTimeoutPolicyComponent,
DisablePersonalVaultExportPolicyComponent,
],
bootstrap: [AppComponent],
})

View File

@@ -5,8 +5,8 @@ import "bootstrap";
import "jquery";
import "popper.js";
// tslint:disable-next-line
require("src/scss/styles.scss");
require("src/scss/tailwind.css");
import { AppModule } from "./app.module";

View File

@@ -1,68 +0,0 @@
import { Directive, Input, OnInit, Self } from "@angular/core";
import { ControlValueAccessor, FormControl, NgControl, Validators } from "@angular/forms";
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Directive()
export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
get describedById() {
return this.showDescribedBy ? this.controlId + "Desc" : null;
}
get showDescribedBy() {
return this.helperText != null || this.controlDir.control.hasError("required");
}
get isRequired() {
return (
this.controlDir.control.hasValidator(Validators.required) ||
this.controlDir.control.hasValidator(dirtyRequired)
);
}
@Input() label: string;
@Input() controlId: string;
@Input() helperText: string;
internalControl = new FormControl("");
protected onChange: any;
protected onTouched: any;
constructor(@Self() public controlDir: NgControl) {
this.controlDir.valueAccessor = this;
}
ngOnInit() {
this.internalControl.valueChanges.subscribe(this.onValueChangesInternal);
}
onBlurInternal() {
this.onTouched();
}
// CVA interfaces
writeValue(value: string) {
this.internalControl.setValue(value);
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.internalControl.disable();
} else {
this.internalControl.enable();
}
}
protected onValueChangesInternal: any = (value: string) => this.onChange(value);
// End CVA interfaces
}

View File

@@ -1,16 +0,0 @@
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
[attr.id]="controlId"
[attr.aria-describedby]="describedById"
[formControl]="internalControl"
(blur)="onBlurInternal()"
/>
<label class="form-check-label" [attr.for]="controlId">{{ label }}</label>
</div>
<small *ngIf="showDescribedBy" [attr.id]="describedById" class="form-text text-muted">{{
helperText
}}</small>
</div>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-checkbox",
templateUrl: "input-checkbox.component.html",
})
export class InputCheckboxComponent extends BaseCvaComponent {}

View File

@@ -1,26 +0,0 @@
<div class="form-group">
<label>{{ label }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="controlValue" />
<div class="input-group-append" *ngIf="showLaunch">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchUri(controlValue)"
>
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
</button>
</div>
<div class="input-group-append" *ngIf="showCopy">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(controlValue)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@@ -1,25 +0,0 @@
import { Component, Input } from "@angular/core";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-text-readonly",
templateUrl: "input-text-readonly.component.html",
})
export class InputTextReadOnlyComponent {
@Input() controlValue: string;
@Input() label: string;
@Input() showCopy = true;
@Input() showLaunch = false;
constructor(private platformUtilsService: PlatformUtilsService) {}
copy(value: string) {
this.platformUtilsService.copyToClipboard(value);
}
launchUri(url: string) {
this.platformUtilsService.launchUri(url);
}
}

View File

@@ -1,33 +0,0 @@
<div class="form-group">
<label [attr.for]="controlId">
{{ label }}
<small *ngIf="isRequired" class="text-muted form-text d-inline"
>({{ "required" | i18n }})</small
>
</label>
<input
[formControl]="internalControl"
class="form-control"
[attr.id]="controlId"
[attr.aria-describedby]="describedById"
[attr.aria-invalid]="controlDir.control.invalid"
(blur)="onBlurInternal()"
/>
<div *ngIf="showDescribedBy" [attr.id]="describedById">
<small
*ngIf="helperText != null && !controlDir.control.hasError(helperTextSameAsError)"
class="form-text text-muted"
>
{{ helperText }}
</small>
<small class="error-inline" *ngIf="controlDir.control.hasError('required')" role="alert">
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{
controlDir.control.hasError(helperTextSameAsError)
? helperText
: ("fieldRequiredError" | i18n: label)
}}
</small>
</div>
</div>

View File

@@ -1,48 +0,0 @@
import { Component, Input, OnInit } from "@angular/core";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-input-text[label][controlId]",
templateUrl: "input-text.component.html",
})
export class InputTextComponent extends BaseCvaComponent implements OnInit {
@Input() helperTextSameAsError: string;
@Input() requiredErrorMessage: string;
@Input() stripSpaces = false;
transformValue: (value: string) => string = null;
ngOnInit() {
super.ngOnInit();
if (this.stripSpaces) {
this.transformValue = this.doStripSpaces;
}
}
writeValue(value: string) {
this.internalControl.setValue(value == null ? "" : value);
}
protected onValueChangesInternal: any = (value: string) => {
let newValue = value;
if (this.transformValue != null) {
newValue = this.transformValue(value);
this.internalControl.setValue(newValue, { emitEvent: false });
}
this.onChange(newValue);
};
protected onValueChangeInternal(value: string) {
let newValue = value;
if (this.transformValue != null) {
newValue = this.transformValue(value);
this.internalControl.setValue(newValue, { emitEvent: false });
}
}
private doStripSpaces(value: string) {
return value.replace(/ /g, "");
}
}

View File

@@ -1,19 +0,0 @@
<div class="form-group">
<label [attr.for]="controlId">
{{ label }}
<small *ngIf="isRequired" class="text-muted form-text d-inline"
>({{ "required" | i18n }})</small
>
</label>
<select
class="form-control"
[attr.id]="controlId"
[attr.aria-invalid]="controlDir.control.invalid"
[formControl]="internalControl"
(blur)="onBlurInternal()"
>
<option *ngFor="let o of selectOptions" [ngValue]="o.value" disabled="{{ o.disabled }}">
{{ o.name }}
</option>
</select>
</div>

View File

@@ -1,14 +0,0 @@
import { Component, Input } from "@angular/core";
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
import { BaseCvaComponent } from "./base-cva.component";
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
@Component({
selector: "app-select",
templateUrl: "select.component.html",
})
export class SelectComponent extends BaseCvaComponent {
@Input() selectOptions: SelectOptions[];
}

View File

@@ -14,9 +14,10 @@
<form
#form
(ngSubmit)="submit()"
[formGroup]="ssoConfigForm"
[formGroup]="data"
[appApiAction]="formPromise"
*ngIf="!loading"
ngNativeValidate
>
<p>
{{ "ssoPolicyHelpStart" | i18n }}
@@ -26,407 +27,461 @@
{{ "ssoPolicyHelpKeyConnector" | i18n }}
</p>
<!-- Root form -->
<ng-container>
<app-input-checkbox
controlId="enabled"
[formControl]="enabled"
[label]="'allowSso' | i18n"
[helperText]="'allowSsoDesc' | i18n"
></app-input-checkbox>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "allowSso" | i18n }}</label>
</div>
<small class="form-text text-muted">{{ "allowSsoDesc" | i18n }}</small>
</div>
<div class="form-group">
<label>{{ "memberDecryptionOption" | i18n }}</label>
<div class="form-check form-check-block">
<input
class="form-check-input"
type="radio"
id="memberDecryptionPass"
[value]="false"
formControlName="keyConnectorEnabled"
/>
<label class="form-check-label" for="memberDecryptionPass">
{{ "masterPass" | i18n }}
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
id="memberDecryptionKey"
[value]="true"
formControlName="keyConnectorEnabled"
[attr.disabled]="!organization.useKeyConnector || null"
/>
<label class="form-check-label" for="memberDecryptionKey">
{{ "keyConnector" | i18n }}
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/about-key-connector/"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
</label>
</div>
</div>
<ng-container *ngIf="data.value.keyConnectorEnabled">
<app-callout type="warning" [useAlertRole]="true">
{{ "keyConnectorWarning" | i18n }}
</app-callout>
<div class="form-group">
<label>{{ "memberDecryptionOption" | i18n }}</label>
<div class="form-check form-check-block">
<label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label>
<div class="input-group">
<input
class="form-check-input"
type="radio"
id="memberDecryptionPass"
[value]="false"
formControlName="keyConnectorEnabled"
class="form-control"
formControlName="keyConnectorUrl"
id="keyConnectorUrl"
required
/>
<label class="form-check-label" for="memberDecryptionPass">
{{ "masterPass" | i18n }}
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
id="memberDecryptionKey"
[value]="true"
formControlName="keyConnectorEnabled"
[attr.disabled]="!organization.useKeyConnector || null"
/>
<label class="form-check-label" for="memberDecryptionKey">
{{ "keyConnector" | i18n }}
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/about-key-connector/"
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
(click)="validateKeyConnectorUrl()"
[disabled]="!enableTestKeyConnector"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
</label>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="keyConnectorUrl.pending"
></i>
<span *ngIf="!keyConnectorUrl.pending">
{{ "keyConnectorTest" | i18n }}
</span>
</button>
</div>
</div>
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
{{ "keyConnectorTestFail" | i18n }}
</div>
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "keyConnectorTestSuccess" | i18n }}
</div>
</ng-container>
</div>
</ng-container>
<!-- Key Connector -->
<ng-container *ngIf="ssoConfigForm.get('keyConnectorEnabled').value">
<app-callout type="warning" [useAlertRole]="true">
{{ "keyConnectorWarning" | i18n }}
</app-callout>
<div class="form-group">
<label for="type">{{ "type" | i18n }}</label>
<select class="form-control" id="type" formControlName="configType">
<option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option>
<option [ngValue]="1">OpenID Connect</option>
<option [ngValue]="2">SAML 2.0</option>
</select>
</div>
<!-- OIDC -->
<div *ngIf="data.value.configType == 1">
<div class="config-section">
<h2>{{ "openIdConnectConfig" | i18n }}</h2>
<div class="form-group">
<label for="keyConnectorUrl">
{{ "keyConnectorUrl" | i18n }}
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
</label>
<label>{{ "callbackPath" | i18n }}</label>
<div class="input-group">
<input
class="form-control"
formControlName="keyConnectorUrl"
id="keyConnectorUrl"
aria-describedby="keyConnectorUrlDesc"
(change)="haveTestedKeyConnector = false"
appInputStripSpaces
appA11yInvalid
/>
<input class="form-control" readonly [value]="callbackPath" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
(click)="validateKeyConnectorUrl()"
[disabled]="!enableTestKeyConnector"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(callbackPath)"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="keyConnectorUrl.pending"
></i>
<span *ngIf="!keyConnectorUrl.pending">
{{ "keyConnectorTest" | i18n }}
</span>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<div *ngIf="haveTestedKeyConnector" id="keyConnectorUrlDesc" aria-live="polite">
<small
class="error-inline"
*ngIf="keyConnectorUrl.hasError('invalidUrl'); else keyConnectorSuccess"
>
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{ "keyConnectorTestFail" | i18n }}
</small>
<ng-template #keyConnectorSuccess>
<small class="text-success">
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "keyConnectorTestSuccess" | i18n }}
</small>
</ng-template>
</div>
<div class="form-group">
<label>{{ "signedOutCallbackPath" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="signedOutCallbackPath" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(signedOutCallbackPath)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</ng-container>
<app-select
controlId="type"
[label]="'type' | i18n"
[selectOptions]="ssoTypeOptions"
formControlName="configType"
>
</app-select>
</ng-container>
<!-- OIDC -->
<div
*ngIf="ssoConfigForm.get('configType').value === ssoType.OpenIdConnect"
[formGroup]="openIdForm"
>
<div class="config-section">
<h2 class="secondary-header">{{ "openIdConnectConfig" | i18n }}</h2>
<app-input-text-readonly
[label]="'callbackPath' | i18n"
[controlValue]="callbackPath"
></app-input-text-readonly>
<app-input-text-readonly
[label]="'signedOutCallbackPath' | i18n"
[controlValue]="signedOutCallbackPath"
></app-input-text-readonly>
<app-input-text
[label]="'authority' | i18n"
controlId="authority"
[stripSpaces]="true"
formControlName="authority"
></app-input-text>
<app-input-text
[label]="'clientId' | i18n"
controlId="clientId"
[stripSpaces]="true"
formControlName="clientId"
></app-input-text>
<app-input-text
[label]="'clientSecret' | i18n"
controlId="clientSecret"
[stripSpaces]="true"
formControlName="clientSecret"
></app-input-text>
<app-input-text
[label]="'metadataAddress' | i18n"
controlId="metadataAddress"
[stripSpaces]="true"
[helperText]="'openIdAuthorityRequired' | i18n"
formControlName="metadataAddress"
></app-input-text>
<app-select
controlId="redirectBehavior"
[label]="'oidcRedirectBehavior' | i18n"
[selectOptions]="connectRedirectOptions"
formControlName="redirectBehavior"
>
</app-select>
<app-input-checkbox
controlId="getClaimsFromUserInfoEndpoint"
formControlName="getClaimsFromUserInfoEndpoint"
[label]="'getClaimsFromUserInfoEndpoint' | i18n"
></app-input-checkbox>
<!-- Optional customizations -->
<div
class="section-header d-flex flex-row align-items-center mt-3 mb-3"
(click)="toggleOpenIdCustomizations()"
>
<h3 class="mb-0 mr-2" id="customizations-header">
{{ "openIdOptionalCustomizations" | i18n }}
</h3>
<button
class="mb-1 btn btn-link"
type="button"
appStopClick
role="button"
aria-controls="customizations"
[attr.aria-expanded]="showOpenIdCustomizations"
aria-labelledby="customizations-header"
>
<i
class="bwi"
aria-hidden="true"
[ngClass]="{
'bwi-angle-down': !showOpenIdCustomizations,
'bwi-chevron-up': showOpenIdCustomizations
}"
></i>
</button>
<div class="form-group">
<label for="authority">{{ "authority" | i18n }}</label>
<input class="form-control" formControlName="authority" id="authority" />
</div>
<div id="customizations" [hidden]="!showOpenIdCustomizations">
<app-input-text
[label]="'additionalScopes' | i18n"
controlId="additionalScopes"
[helperText]="'separateMultipleWithComma' | i18n"
formControlName="additionalScopes"
></app-input-text>
<app-input-text
[label]="'additionalUserIdClaimTypes' | i18n"
controlId="additionalUserIdClaimTypes"
[helperText]="'separateMultipleWithComma' | i18n"
<div class="form-group">
<label for="clientId">{{ "clientId" | i18n }}</label>
<input class="form-control" formControlName="clientId" id="clientId" />
</div>
<div class="form-group">
<label for="clientSecret">{{ "clientSecret" | i18n }}</label>
<input class="form-control" formControlName="clientSecret" id="clientSecret" />
</div>
<div class="form-group">
<label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
<input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
</div>
<div class="form-group">
<label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
<option [ngValue]="0">Redirect GET</option>
<option [ngValue]="1">Form POST</option>
</select>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="getClaimsFromUserInfoEndpoint"
formControlName="getClaimsFromUserInfoEndpoint"
/>
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
{{ "getClaimsFromUserInfoEndpoint" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<label for="additionalScopes">{{ "additionalScopes" | i18n }}</label>
<input class="form-control" formControlName="additionalScopes" id="additionalScopes" />
</div>
<div class="form-group">
<label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label>
<input
class="form-control"
formControlName="additionalUserIdClaimTypes"
></app-input-text>
<app-input-text
[label]="'additionalEmailClaimTypes' | i18n"
controlId="additionalEmailClaimTypes"
[helperText]="'separateMultipleWithComma' | i18n"
id="additionalUserIdClaimTypes"
/>
</div>
<div class="form-group">
<label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label>
<input
class="form-control"
formControlName="additionalEmailClaimTypes"
></app-input-text>
<app-input-text
[label]="'additionalNameClaimTypes' | i18n"
controlId="additionalNameClaimTypes"
[helperText]="'separateMultipleWithComma' | i18n"
id="additionalEmailClaimTypes"
/>
</div>
<div class="form-group">
<label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label>
<input
class="form-control"
formControlName="additionalNameClaimTypes"
></app-input-text>
<app-input-text
[label]="'acrValues' | i18n"
controlId="acrValues"
helperText="acr_values"
formControlName="acrValues"
></app-input-text>
<app-input-text
[label]="'expectedReturnAcrValue' | i18n"
controlId="expectedReturnAcrValue"
helperText="acr_validation"
id="additionalNameClaimTypes"
/>
</div>
<div class="form-group">
<label for="acrValues">{{ "acrValues" | i18n }}</label>
<input class="form-control" formControlName="acrValues" id="acrValues" />
</div>
<div class="form-group">
<label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
<input
class="form-control"
formControlName="expectedReturnAcrValue"
></app-input-text>
id="expectedReturnAcrValue"
/>
</div>
</div>
</div>
<!-- SAML2 SP -->
<div *ngIf="ssoConfigForm.get('configType').value === ssoType.Saml2" [formGroup]="samlForm">
<div *ngIf="data.value.configType == 2">
<!-- SAML2 SP -->
<div class="config-section">
<h2 class="secondary-header">{{ "samlSpConfig" | i18n }}</h2>
<app-input-text-readonly
[label]="'spEntityId' | i18n"
[controlValue]="spEntityId"
></app-input-text-readonly>
<app-input-text-readonly
[label]="'spMetadataUrl' | i18n"
[controlValue]="spMetadataUrl"
[showLaunch]="true"
></app-input-text-readonly>
<app-input-text-readonly
[label]="'spAcsUrl' | i18n"
[controlValue]="spAcsUrl"
></app-input-text-readonly>
<app-select
controlId="spNameIdFormat"
[label]="'spNameIdFormat' | i18n"
[selectOptions]="saml2NameIdFormatOptions"
formControlName="spNameIdFormat"
>
</app-select>
<app-select
controlId="spOutboundSigningAlgorithm"
[label]="'spOutboundSigningAlgorithm' | i18n"
[selectOptions]="samlSigningAlgorithmOptions"
formControlName="spOutboundSigningAlgorithm"
>
</app-select>
<app-select
controlId="spSigningBehavior"
[label]="'spSigningBehavior' | i18n"
[selectOptions]="saml2SigningBehaviourOptions"
formControlName="spSigningBehavior"
>
</app-select>
<app-select
controlId="spMinIncomingSigningAlgorithm"
[label]="'spMinIncomingSigningAlgorithm' | i18n"
[selectOptions]="samlSigningAlgorithmOptions"
formControlName="spMinIncomingSigningAlgorithm"
>
</app-select>
<app-input-checkbox
controlId="spWantAssertionsSigned"
formControlName="spWantAssertionsSigned"
[label]="'spWantAssertionsSigned' | i18n"
></app-input-checkbox>
<app-input-checkbox
controlId="spValidateCertificates"
formControlName="spValidateCertificates"
[label]="'spValidateCertificates' | i18n"
></app-input-checkbox>
<h2>{{ "samlSpConfig" | i18n }}</h2>
<div class="form-group">
<label>{{ "spEntityId" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="spEntityId" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spEntityId)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label>{{ "spMetadataUrl" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="spMetadataUrl" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchUri(spMetadataUrl)"
>
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spMetadataUrl)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label>{{ "spAcsUrl" | i18n }}</label>
<div class="input-group">
<input class="form-control" readonly [value]="spAcsUrl" />
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spAcsUrl)"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
<option value="0">Not Configured</option>
<option value="1">Unspecified</option>
<option value="2">Email Address</option>
<option value="3">X.509 Subject Name</option>
<option value="4">Windows Domain Qualified Name</option>
<option value="5">Kerberos Principal Name</option>
<option value="6">Entity Identifier</option>
<option value="7">Persistent</option>
<option value="8">Transient</option>
</select>
</div>
<div class="form-group">
<label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
<select
class="form-control"
formControlName="spOutboundSigningAlgorithm"
id="spOutboundSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select>
</div>
<div class="form-group">
<label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label>
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
<option value="0">If IdP Wants Authn Requests Signed</option>
<option value="1">Always</option>
<option value="3">Never</option>
</select>
</div>
<div class="form-group">
<label for="spMinIncomingSigningAlgorithm">{{
"spMinIncomingSigningAlgorithm" | i18n
}}</label>
<select
class="form-control"
formControlName="spMinIncomingSigningAlgorithm"
id="spMinIncomingSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="spWantAssertionsSigned"
formControlName="spWantAssertionsSigned"
/>
<label class="form-check-label" for="spWantAssertionsSigned">
{{ "spWantAssertionsSigned" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="spValidateCertificates"
formControlName="spValidateCertificates"
/>
<label class="form-check-label" for="spValidateCertificates">
{{ "spValidateCertificates" | i18n }}
</label>
</div>
</div>
</div>
<!-- SAML2 IDP -->
<div class="config-section">
<h2 class="secondary-header">{{ "samlIdpConfig" | i18n }}</h2>
<app-input-text
[label]="'idpEntityId' | i18n"
controlId="idpEntityId"
formControlName="idpEntityId"
></app-input-text>
<app-select
controlId="idpBindingType"
[label]="'idpBindingType' | i18n"
[selectOptions]="saml2BindingTypeOptions"
formControlName="idpBindingType"
>
</app-select>
<app-input-text
[label]="'idpSingleSignOnServiceUrl' | i18n"
controlId="idpSingleSignOnServiceUrl"
[helperText]="'idpSingleSignOnServiceUrlRequired' | i18n"
[stripSpaces]="true"
formControlName="idpSingleSignOnServiceUrl"
></app-input-text>
<app-input-text
[label]="'idpSingleLogoutServiceUrl' | i18n"
controlId="idpSingleLogoutServiceUrl"
[stripSpaces]="true"
formControlName="idpSingleLogoutServiceUrl"
></app-input-text>
<h2>{{ "samlIdpConfig" | i18n }}</h2>
<div class="form-group">
<label for="idpX509PublicCert">
{{ "idpX509PublicCert" | i18n }}
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
</label>
<label for="idpEntityId">{{ "idpEntityId" | i18n }}</label>
<input class="form-control" formControlName="idpEntityId" id="idpEntityId" />
</div>
<div class="form-group">
<label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
<option value="1">Redirect</option>
<option value="2">HTTP POST</option>
<option value="4">Artifact</option>
</select>
</div>
<div class="form-group">
<label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
<input
class="form-control"
formControlName="idpSingleSignOnServiceUrl"
id="idpSingleSignOnServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
<input
class="form-control"
formControlName="idpSingleLogoutServiceUrl"
id="idpSingleLogoutServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpArtifactResolutionServiceUrl">{{
"idpArtifactResolutionServiceUrl" | i18n
}}</label>
<input
class="form-control"
formControlName="idpArtifactResolutionServiceUrl"
id="idpArtifactResolutionServiceUrl"
/>
</div>
<div class="form-group">
<label for="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
<textarea
formControlName="idpX509PublicCert"
class="form-control form-control-sm text-monospace"
rows="6"
id="idpX509PublicCert"
appA11yInvalid
aria-describedby="idpX509PublicCertDesc"
></textarea>
<small
id="idpX509PublicCertDesc"
class="error-inline"
role="alert"
*ngIf="samlForm.get('idpX509PublicCert').hasError('required')"
>
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{ "fieldRequiredError" | i18n: ("idpX509PublicCert" | i18n) }}
</small>
</div>
<app-select
controlId="idpOutboundSigningAlgorithm"
[label]="'idpOutboundSigningAlgorithm' | i18n"
[selectOptions]="samlSigningAlgorithmOptions"
formControlName="idpOutboundSigningAlgorithm"
>
</app-select>
<!--TODO: Uncomment once Unsolicited IdP Response is supported-->
<!-- <app-input-checkbox
controlId="idpAllowUnsolicitedAuthnResponse"
formControlName="idpAllowUnsolicitedAuthnResponse"
[label]="'idpAllowUnsolicitedAuthnResponse' | i18n"
></app-input-checkbox> -->
<app-input-checkbox
controlId="idpAllowOutboundLogoutRequests"
formControlName="idpAllowOutboundLogoutRequests"
[label]="'idpAllowOutboundLogoutRequests' | i18n"
></app-input-checkbox>
<app-input-checkbox
controlId="idpWantAuthnRequestsSigned"
formControlName="idpWantAuthnRequestsSigned"
[label]="'idpSignAuthenticationRequests' | i18n"
></app-input-checkbox>
<div class="form-group">
<label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label>
<select
class="form-control"
formControlName="idpOutboundSigningAlgorithm"
id="idpOutboundSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="idpAllowUnsolicitedAuthnResponse"
formControlName="idpAllowUnsolicitedAuthnResponse"
/>
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
{{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="idpDisableOutboundLogoutRequests"
formControlName="idpDisableOutboundLogoutRequests"
/>
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
{{ "idpDisableOutboundLogoutRequests" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="idpWantAuthnRequestsSigned"
formControlName="idpWantAuthnRequestsSigned"
/>
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
{{ "idpWantAuthnRequestsSigned" | i18n }}
</label>
</div>
</div>
</div>
</div>
@@ -434,15 +489,4 @@
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<div
id="errorSummary"
class="error-summary text-danger"
*ngIf="this.getErrorCount(ssoConfigForm) as errorCount"
>
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
<span class="sr-only">{{ "error" | i18n }}:</span>
{{
(errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural") | i18n: errorCount
}}
</div>
</form>

View File

@@ -1,82 +1,29 @@
import { Component, OnInit } from "@angular/core";
import { AbstractControl, FormBuilder, FormGroup } from "@angular/forms";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
OpenIdConnectRedirectBehavior,
Saml2BindingType,
Saml2NameIdFormat,
Saml2SigningBehavior,
SsoType,
} from "jslib-common/enums/ssoEnums";
import { Utils } from "jslib-common/misc/utils";
import { SsoConfigApi } from "jslib-common/models/api/ssoConfigApi";
import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
import { OrganizationSsoResponse } from "jslib-common/models/response/organization/organizationSsoResponse";
import { SsoConfigView } from "jslib-common/models/view/ssoConfigView";
const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
@Component({
selector: "app-org-manage-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent implements OnInit {
readonly ssoType = SsoType;
readonly ssoTypeOptions: SelectOptions[] = [
{ name: this.i18nService.t("selectType"), value: SsoType.None, disabled: true },
{ name: "OpenID Connect", value: SsoType.OpenIdConnect },
{ name: "SAML 2.0", value: SsoType.Saml2 },
];
readonly samlSigningAlgorithms = [
samlSigningAlgorithms = [
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"http://www.w3.org/2000/09/xmldsig#rsa-sha384",
"http://www.w3.org/2000/09/xmldsig#rsa-sha512",
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
];
readonly saml2SigningBehaviourOptions: SelectOptions[] = [
{
name: "If IdP Wants Authn Requests Signed",
value: Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned,
},
{ name: "Always", value: Saml2SigningBehavior.Always },
{ name: "Never", value: Saml2SigningBehavior.Never },
];
readonly saml2BindingTypeOptions: SelectOptions[] = [
{ name: "Redirect", value: Saml2BindingType.HttpRedirect },
{ name: "HTTP POST", value: Saml2BindingType.HttpPost },
];
readonly saml2NameIdFormatOptions: SelectOptions[] = [
{ name: "Not Configured", value: Saml2NameIdFormat.NotConfigured },
{ name: "Unspecified", value: Saml2NameIdFormat.Unspecified },
{ name: "Email Address", value: Saml2NameIdFormat.EmailAddress },
{ name: "X.509 Subject Name", value: Saml2NameIdFormat.X509SubjectName },
{ name: "Windows Domain Qualified Name", value: Saml2NameIdFormat.WindowsDomainQualifiedName },
{ name: "Kerberos Principal Name", value: Saml2NameIdFormat.KerberosPrincipalName },
{ name: "Entity Identifier", value: Saml2NameIdFormat.EntityIdentifier },
{ name: "Persistent", value: Saml2NameIdFormat.Persistent },
{ name: "Transient", value: Saml2NameIdFormat.Transient },
];
readonly connectRedirectOptions: SelectOptions[] = [
{ name: "Redirect GET", value: OpenIdConnectRedirectBehavior.RedirectGet },
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
];
showOpenIdCustomizations = false;
loading = true;
haveTestedKeyConnector = false;
organizationId: string;
organization: Organization;
formPromise: Promise<any>;
@@ -88,57 +35,44 @@ export class SsoComponent implements OnInit {
spAcsUrl: string;
enabled = this.formBuilder.control(false);
data = this.formBuilder.group({
configType: [],
openIdForm = this.formBuilder.group(
{
authority: ["", dirtyRequired],
clientId: ["", dirtyRequired],
clientSecret: ["", dirtyRequired],
metadataAddress: [],
redirectBehavior: [OpenIdConnectRedirectBehavior.RedirectGet, dirtyRequired],
getClaimsFromUserInfoEndpoint: [],
additionalScopes: [],
additionalUserIdClaimTypes: [],
additionalEmailClaimTypes: [],
additionalNameClaimTypes: [],
acrValues: [],
expectedReturnAcrValue: [],
},
{
updateOn: "blur",
}
);
keyConnectorEnabled: [],
keyConnectorUrl: [],
samlForm = this.formBuilder.group(
{
spNameIdFormat: [Saml2NameIdFormat.NotConfigured],
spOutboundSigningAlgorithm: [defaultSigningAlgorithm],
spSigningBehavior: [Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned],
spMinIncomingSigningAlgorithm: [defaultSigningAlgorithm],
spWantAssertionsSigned: [],
spValidateCertificates: [],
// OpenId
authority: [],
clientId: [],
clientSecret: [],
metadataAddress: [],
redirectBehavior: [],
getClaimsFromUserInfoEndpoint: [],
additionalScopes: [],
additionalUserIdClaimTypes: [],
additionalEmailClaimTypes: [],
additionalNameClaimTypes: [],
acrValues: [],
expectedReturnAcrValue: [],
idpEntityId: ["", dirtyRequired],
idpBindingType: [Saml2BindingType.HttpRedirect],
idpSingleSignOnServiceUrl: [],
idpSingleLogoutServiceUrl: [],
idpX509PublicCert: ["", dirtyRequired],
idpOutboundSigningAlgorithm: [defaultSigningAlgorithm],
idpAllowUnsolicitedAuthnResponse: [],
idpAllowOutboundLogoutRequests: [true],
idpWantAuthnRequestsSigned: [],
},
{
updateOn: "blur",
}
);
// SAML
spNameIdFormat: [],
spOutboundSigningAlgorithm: [],
spSigningBehavior: [],
spMinIncomingSigningAlgorithm: [],
spWantAssertionsSigned: [],
spValidateCertificates: [],
ssoConfigForm = this.formBuilder.group({
configType: [SsoType.None],
keyConnectorEnabled: [false],
keyConnectorUrl: [""],
openId: this.openIdForm,
saml: this.samlForm,
idpEntityId: [],
idpBindingType: [],
idpSingleSignOnServiceUrl: [],
idpSingleLogoutServiceUrl: [],
idpArtifactResolutionServiceUrl: [],
idpX509PublicCert: [],
idpOutboundSigningAlgorithm: [],
idpAllowUnsolicitedAuthnResponse: [],
idpDisableOutboundLogoutRequests: [],
idpWantAuthnRequestsSigned: [],
});
constructor(
@@ -151,25 +85,6 @@ export class SsoComponent implements OnInit {
) {}
async ngOnInit() {
this.ssoConfigForm.get("configType").valueChanges.subscribe((newType: SsoType) => {
if (newType === SsoType.OpenIdConnect) {
this.openIdForm.enable();
this.samlForm.disable();
} else if (newType === SsoType.Saml2) {
this.openIdForm.disable();
this.samlForm.enable();
} else {
this.openIdForm.disable();
this.samlForm.disable();
}
});
this.samlForm
.get("spSigningBehavior")
.valueChanges.subscribe(() =>
this.samlForm.get("idpX509PublicCert").updateValueAndValidity()
);
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
@@ -179,7 +94,9 @@ export class SsoComponent implements OnInit {
async load() {
this.organization = await this.organizationService.get(this.organizationId);
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
this.populateForm(ssoSettings);
this.data.patchValue(ssoSettings.data);
this.enabled.setValue(ssoSettings.enabled);
this.callbackPath = ssoSettings.urls.callbackPath;
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
@@ -187,30 +104,28 @@ export class SsoComponent implements OnInit {
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
this.keyConnectorUrl.markAsDirty();
this.loading = false;
}
copy(value: string) {
this.platformUtilsService.copyToClipboard(value);
}
launchUri(url: string) {
this.platformUtilsService.launchUri(url);
}
async submit() {
this.validateForm(this.ssoConfigForm);
if (this.ssoConfigForm.get("keyConnectorEnabled").value) {
await this.validateKeyConnectorUrl();
}
if (!this.ssoConfigForm.valid) {
this.readOutErrors();
return;
}
const request = new OrganizationSsoRequest();
request.enabled = this.enabled.value;
request.data = SsoConfigApi.fromView(this.ssoConfigForm.value as SsoConfigView);
this.formPromise = this.apiService.postOrganizationSso(this.organizationId, request);
this.formPromise = this.postData();
try {
const response = await this.formPromise;
this.populateForm(response);
this.data.patchValue(response.data);
this.enabled.setValue(response.enabled);
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
} catch {
// Logged by appApiAction, do nothing
@@ -219,8 +134,24 @@ export class SsoComponent implements OnInit {
this.formPromise = null;
}
async postData() {
if (this.data.get("keyConnectorEnabled").value) {
await this.validateKeyConnectorUrl();
if (this.keyConnectorUrl.hasError("invalidUrl")) {
throw new Error(this.i18nService.t("keyConnectorTestFail"));
}
}
const request = new OrganizationSsoRequest();
request.enabled = this.enabled.value;
request.data = this.data.value;
return this.apiService.postOrganizationSso(this.organizationId, request);
}
async validateKeyConnectorUrl() {
if (this.haveTestedKeyConnector) {
if (this.keyConnectorUrl.pristine) {
return;
}
@@ -235,84 +166,18 @@ export class SsoComponent implements OnInit {
});
}
this.haveTestedKeyConnector = true;
}
toggleOpenIdCustomizations() {
this.showOpenIdCustomizations = !this.showOpenIdCustomizations;
}
getErrorCount(form: FormGroup): number {
return Object.values(form.controls).reduce((acc: number, control: AbstractControl) => {
if (control instanceof FormGroup) {
return acc + this.getErrorCount(control);
}
if (control.errors == null) {
return acc;
}
return acc + Object.keys(control.errors).length;
}, 0);
this.keyConnectorUrl.markAsPristine();
}
get enableTestKeyConnector() {
return (
this.ssoConfigForm.get("keyConnectorEnabled").value &&
!Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
this.data.get("keyConnectorEnabled").value &&
this.keyConnectorUrl != null &&
this.keyConnectorUrl.value !== ""
);
}
get keyConnectorUrl() {
return this.ssoConfigForm.get("keyConnectorUrl");
}
get samlSigningAlgorithmOptions(): SelectOptions[] {
return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm }));
}
private validateForm(form: FormGroup) {
Object.values(form.controls).forEach((control: AbstractControl) => {
if (control.disabled) {
return;
}
if (control instanceof FormGroup) {
this.validateForm(control);
} else {
control.markAsDirty();
control.markAsTouched();
control.updateValueAndValidity();
}
});
}
private populateForm(ssoSettings: OrganizationSsoResponse) {
this.enabled.setValue(ssoSettings.enabled);
if (ssoSettings.data != null) {
const ssoConfigView = new SsoConfigView(ssoSettings.data);
this.ssoConfigForm.patchValue(ssoConfigView);
}
}
private readOutErrors() {
const errorText = this.i18nService.t("error");
const errorCount = this.getErrorCount(this.ssoConfigForm);
const errorCountText = this.i18nService.t(
errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural",
errorCount.toString()
);
const div = document.createElement("div");
div.className = "sr-only";
div.id = "srErrorCount";
div.setAttribute("aria-live", "polite");
div.innerText = errorText + ": " + errorCountText;
const existing = document.getElementById("srErrorCount");
if (existing != null) {
existing.remove();
}
document.body.append(div);
return this.data.get("keyConnectorUrl");
}
}

View File

@@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";

View File

@@ -2,31 +2,13 @@ import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "jslib-angular/jslib.module";
import { OssModule } from "src/app/oss.module";
import { InputCheckboxComponent } from "./components/input-checkbox.component";
import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component";
import { InputTextComponent } from "./components/input-text.component";
import { SelectComponent } from "./components/select.component";
import { SsoComponent } from "./manage/sso.component";
import { OrganizationsRoutingModule } from "./organizations-routing.module";
// Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere.
// They will be deprecated by the Component Library.
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
JslibModule,
OrganizationsRoutingModule,
],
declarations: [
InputCheckboxComponent,
InputTextComponent,
InputTextReadOnlyComponent,
SelectComponent,
SsoComponent,
],
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
declarations: [SsoComponent],
})
export class OrganizationsModule {}

View File

@@ -1,7 +1,12 @@
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import {
BasePolicy,
BasePolicyComponent,

View File

@@ -2,7 +2,9 @@ import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import {

View File

@@ -1,14 +1,16 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ValidationService } from "jslib-angular/services/validation.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider";
import { ValidationService } from "jslib-angular/services/validation.service";
import { WebProviderService } from "../services/webProvider.service";
import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider";
@Component({
selector: "provider-add-organization",
templateUrl: "add-organization.component.html",

View File

@@ -1,9 +1,8 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
@@ -11,8 +10,13 @@ import { OrganizationService } from "jslib-common/abstractions/organization.serv
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { PlanType } from "jslib-common/enums/planType";
import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { Organization } from "jslib-common/models/domain/organization";
import { ProviderOrganizationOrganizationDetailsResponse } from "jslib-common/models/response/provider/providerOrganizationResponse";

View File

@@ -24,11 +24,7 @@
<p>{{ "joinProviderDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -1,14 +1,15 @@
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ProviderUserAcceptRequest } from "jslib-common/models/request/provider/providerUserAcceptRequest";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
@Component({
selector: "app-accept-provider",
templateUrl: "accept-provider.component.html",

View File

@@ -1,9 +1,10 @@
import { Component, Input } from "@angular/core";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest";
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component";
import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component";

View File

@@ -1,24 +1,27 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ApiService } from "jslib-common/abstractions/api.service";
import { ExportService } from "jslib-common/abstractions/export.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 { ProviderService } from "jslib-common/abstractions/provider.service";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { EventResponse } from "jslib-common/models/response/eventResponse";
import { BaseEventsComponent } from "src/app/common/base.events.component";
import { EventService } from "src/app/services/event.service";
import { BaseEventsComponent } from "src/app/common/base.events.component";
@Component({
selector: "provider-events",
templateUrl: "events.component.html",
})
export class EventsComponent extends BaseEventsComponent implements OnInit {
exportFileName = "provider-events";
exportFileName: string = "provider-events";
providerId: string;
private providerUsersUserIdMap = new Map<string, any>();

View File

@@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from "jslib-common/models/domain/provider";
@Component({

View File

@@ -214,7 +214,7 @@
{{ "eventLogs" | i18n }}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
<i class="bwi bwi-fw bwi-remove" aria-hidden="true"></i>
{{ "remove" | i18n }}
</a>
</div>

View File

@@ -1,11 +1,8 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -14,18 +11,26 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserConfirmRequest } from "jslib-common/models/request/provider/providerUserConfirmRequest";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { ProviderUserBulkResponse } from "jslib-common/models/response/provider/providerUserBulkResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { BasePeopleComponent } from "src/app/common/base.people.component";
import { BulkStatusComponent } from "src/app/organizations/manage/bulk/bulk-status.component";
import { EntityEventsComponent } from "src/app/organizations/manage/entity-events.component";
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
import { UserAddEditComponent } from "./user-add-edit.component";
@@ -153,13 +158,17 @@ export class PeopleComponent
}
async events(user: ProviderUserUserDetailsResponse) {
await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, (comp) => {
comp.name = this.userNamePipe.transform(user);
comp.providerId = this.providerId;
comp.entityId = user.id;
comp.showUser = false;
comp.entity = "user";
});
const [modal] = await this.modalService.openViewRef(
EntityEventsComponent,
this.eventsModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.providerId = this.providerId;
comp.entityId = user.id;
comp.showUser = false;
comp.entity = "user";
}
);
}
async bulkRemove() {
@@ -263,14 +272,13 @@ export class PeopleComponent
childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id), // eslint-disable-line
error: keyedErrors.hasOwnProperty(user.id),
message: message,
};
});

View File

@@ -52,7 +52,7 @@
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/provider-users/"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>

View File

@@ -4,9 +4,12 @@ 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 { ProviderUserType } from "jslib-common/enums/providerUserType";
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
import { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest";
@Component({
@@ -21,7 +24,7 @@ export class UserAddEditComponent implements OnInit {
@Output() onDeletedUser = new EventEmitter();
loading = true;
editMode = false;
editMode: boolean = false;
title: string;
emails: string;
type: ProviderUserType = ProviderUserType.ServiceUser;

View File

@@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from "jslib-common/models/domain/provider";
@Component({

View File

@@ -4,9 +4,7 @@ import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from "src/app/providers/providers.component";
import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
@@ -14,13 +12,17 @@ import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from "src/app/providers/providers.component";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component";
const routes: Routes = [
{
path: "",

View File

@@ -1,15 +1,21 @@
import { CommonModule } from "@angular/common";
import { ComponentFactoryResolver, NgModule } from "@angular/core";
import { ComponentFactoryResolver } from "@angular/core";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { JslibModule } from "jslib-angular/jslib.module";
import { ModalService } from "jslib-angular/services/modal.service";
import { OssModule } from "src/app/oss.module";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@@ -17,18 +23,17 @@ import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component";
import { OssModule } from "src/app/oss.module";
@NgModule({
imports: [CommonModule, FormsModule, OssModule, JslibModule, ProvidersRoutingModule],
imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
declarations: [
AcceptProviderComponent,
AccountComponent,

View File

@@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()

View File

@@ -3,6 +3,7 @@ import { Injectable } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest";
@Injectable()

View File

@@ -6,7 +6,9 @@ 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 { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderUpdateRequest } from "jslib-common/models/request/provider/providerUpdateRequest";
import { ProviderResponse } from "jslib-common/models/response/provider/providerResponse";
@Component({

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
@Component({
@@ -8,11 +9,15 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
templateUrl: "settings.component.html",
})
export class SettingsComponent {
constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
constructor(
private route: ActivatedRoute,
private providerService: ProviderService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
await this.providerService.get(params.providerId);
const provider = await this.providerService.get(params.providerId);
});
}
}

View File

@@ -20,11 +20,7 @@
<p>{{ "setupProviderLoginDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
</div>

View File

@@ -16,7 +16,6 @@ export class SetupProviderComponent extends BaseAcceptComponent {
this.router.navigate(["/providers/setup"], { queryParams: qParams });
}
async unauthedHandler(qParams: any) {
// Empty
}
// tslint:disable-next-line
async unauthedHandler(qParams: any) {}
}

View File

@@ -1,10 +1,12 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";

View File

@@ -7,7 +7,6 @@
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
},
"dev": {
"port": 8080,
"allowedHosts": "auto"
}
}

View File

@@ -1,9 +1 @@
{
"dev": {
"proxyApi": "http://localhost:4001",
"proxyIdentity": "http://localhost:33657",
"proxyEvents": "http://localhost:46274",
"proxyNotifications": "http://localhost:61841",
"port": 8081
}
}
{}

View File

@@ -32,7 +32,6 @@ mkhomedir_helper $USERNAME
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
cp /etc/bitwarden/web/app-id.json /app/app-id.json
chown -R $USERNAME:$GROUPNAME /app
chown -R $USERNAME:$GROUPNAME /bitwarden_server
#chown -R $USERNAME:$GROUPNAME /bitwarden_server
exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \
/contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true
#exec nginx -g 'daemon off;'

138
docker/mime.types Normal file
View File

@@ -0,0 +1,138 @@
types {
# Data interchange
application/atom+xml atom;
application/json json map topojson;
application/ld+json jsonld;
application/rss+xml rss;
application/vnd.geo+json geojson;
application/xml rdf xml;
# JavaScript
# Normalize to standard type.
# https://tools.ietf.org/html/rfc4329#section-7.2
application/javascript js;
# Manifest files
application/manifest+json webmanifest;
application/x-web-app-manifest+json webapp;
text/cache-manifest appcache;
# Media files
audio/midi mid midi kar;
audio/mp4 aac f4a f4b m4a;
audio/mpeg mp3;
audio/ogg oga ogg opus;
audio/x-realaudio ra;
audio/x-wav wav;
image/bmp bmp;
image/gif gif;
image/jpeg jpeg jpg;
image/jxr jxr hdp wdp;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-jng jng;
video/3gpp 3gp 3gpp;
video/mp4 f4p f4v m4v mp4;
video/mpeg mpeg mpg;
video/ogg ogv;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-mng mng;
video/x-ms-asf asf asx;
video/x-ms-wmv wmv;
video/x-msvideo avi;
# Serving `.ico` image files with a different media type
# prevents Internet Explorer from displaying then as images:
# https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
image/x-icon cur ico;
# Microsoft Office
application/msword doc;
application/vnd.ms-excel xls;
application/vnd.ms-powerpoint ppt;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
# Web fonts
application/font-woff woff;
application/font-woff2 woff2;
application/vnd.ms-fontobject eot;
# Browsers usually ignore the font media types and simply sniff
# the bytes to figure out the font type.
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
#
# However, Blink and WebKit based browsers will show a warning
# in the console if the following font types are served with any
# other media types.
application/x-font-ttf ttc ttf;
font/opentype otf;
# Other
application/java-archive ear jar war;
application/mac-binhex40 hqx;
application/octet-stream bin deb dll dmg exe img iso msi msm msp safariextz;
application/pdf pdf;
application/postscript ai eps ps;
application/rtf rtf;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-bb-appworld bbaw;
application/x-bittorrent torrent;
application/x-chrome-extension crx;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-opera-extension oex;
application/x-perl pl pm;
application/x-pilot pdb prc;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert crt der pem;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xslt+xml xsl;
application/zip zip;
text/css css;
text/csv csv;
text/html htm html shtml;
text/markdown md;
text/mathml mml;
text/plain txt;
text/vcard vcard vcf;
text/vnd.rim.location.xloc xloc;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/vtt vtt;
text/x-component htc;
}

25
docker/nginx-web.conf Normal file
View File

@@ -0,0 +1,25 @@
#######################################################################
# WARNING: This file is generated. Do not make changes to this file. #
# They will be overwritten on update. You can manage various settings #
# used in this file from the ./bwdata/config.yml file for your #
# installation. #
#######################################################################
server {
listen 8080 default_server;
listen [::]:8080 default_server;
include /etc/nginx/security-headers.conf;
location / {
root /app;
index index.html index.htm;
include /etc/nginx/security-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Robots-Tag "noindex, nofollow";
}
location /alive {
return 200 'alive';
add_header Content-Type text/plain;
}
}

147
docker/nginx.conf Normal file
View File

@@ -0,0 +1,147 @@
# nginx Configuration File
# http://wiki.nginx.org/Configuration
# Run as a less privileged user for security reasons.
# user www www;
# How many worker threads to run;
# "auto" sets it to the number of CPU cores available in the system, and
# offers the best performance. Don't set it higher than the number of CPU
# cores if changing this parameter.
# The maximum number of connections for Nginx is calculated by:
# max_clients = worker_processes * worker_connections
worker_processes auto;
# Maximum open file descriptors per process;
# should be > worker_connections.
worker_rlimit_nofile 8192;
events {
# When you need > 8000 * cpu_cores connections, you start optimizing your OS,
# and this is probably the point at which you hire people who are smarter than
# you, as this is *a lot* of requests.
worker_connections 8000;
}
# Default error log file
# (this is only used when you don't override error_log on a server{} level)
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
http {
# Hide nginx version information.
server_tokens off;
# Define the MIME types for files.
include mime.types;
default_type application/octet-stream;
# Update charset_types to match updated mime.types.
# text/html is always included by charset module.
# Default: text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml
charset_types
text/css
text/plain
text/vnd.wap.wml
application/javascript
application/json
application/rss+xml
application/xml;
# Format to use in log files
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Default log file
# (this is only used when you don't override access_log on a server{} level)
access_log /var/log/nginx/access.log main;
# How long to allow each connection to stay idle; longer values are better
# for each individual client, particularly for SSL, but means that worker
# connections are tied up longer. (Default: 65)
keepalive_timeout 20;
# Speed up file transfers by using sendfile() to copy directly
# between descriptors rather than using read()/write().
# For performance reasons, on FreeBSD systems w/ ZFS
# this option should be disabled as ZFS's ARC caches
# frequently used files in RAM by default.
sendfile on;
# Tell Nginx not to send out partial frames; this increases throughput
# since TCP frames are filled up before being sent out. (adds TCP_CORK)
tcp_nopush on;
# Compression
# Enable Gzip compressed.
gzip on;
# Compression level (1-9).
# 5 is a perfect compromise between size and cpu usage, offering about
# 75% reduction for most ascii files (almost identical to level 9).
gzip_comp_level 5;
# Don't compress anything that's already small and unlikely to shrink much
# if at all (the default is 20 bytes, which is bad as that usually leads to
# larger files after gzipping).
gzip_min_length 256;
# Compress data even for clients that are connecting to us via proxies,
# identified by the "Via" header (required for CloudFront).
gzip_proxied any;
# Tell proxies to cache both the gzipped and regular version of a resource
# whenever the client's Accept-Encoding capabilities header varies;
# Avoids the issue where a non-gzip capable client (which is extremely rare
# today) would display gibberish if their proxy gave them the gzipped version.
gzip_vary on;
# Compress all output labeled with one of the following MIME-types.
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# text/html is always compressed by HttpGzipModule
# This should be turned on if you are going to have pre-compressed copies (.gz) of
# static files available. If not it should be left off as it will cause extra I/O
# for the check. It is best if you enable this in a location{} block for
# a specific directory, or on an individual server{} level.
# gzip_static on;
# Content type for FIDO U2F facets
map $uri $fido_content_type {
default "application/fido.trusted-apps+json";
}
# Include files in the sites-enabled folder. server{} configuration files should be
# placed in the sites-available folder, and then the configuration should be enabled
# by creating a symlink to it in the sites-enabled folder.
# See doc/sites-enabled.md for more info.
include conf.d/*.conf;
}

View File

@@ -0,0 +1,3 @@
add_header Referrer-Policy same-origin;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

2
jslib

Submodule jslib updated: 2e2849b4de...e372bf242b

7996
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2.28.1",
"version": "2.25.1",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -29,8 +29,8 @@
"dist:bit:selfhost": "npm run build:bit:selfhost:prod",
"deploy": "npm run dist:bit && gh-pages -d build",
"deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
"lint": "eslint . && prettier --check .",
"lint:fix": "eslint . --fix",
"lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' && prettier --check .",
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix",
"prettier": "prettier --write .",
"prepare": "husky install"
},
@@ -41,18 +41,11 @@
"@types/node": "^16.11.12",
"@types/webcrypto": "^0.0.28",
"@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"autoprefixer": "^10.4.2",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.4",
"gh-pages": "^3.1.0",
"html-loader": "^3.0.1",
"html-webpack-injector": "1.1.4",
@@ -60,17 +53,15 @@
"husky": "^7.0.4",
"lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.6",
"postcss-loader": "^6.2.1",
"prettier": "2.5.1",
"process": "^0.11.10",
"rimraf": "^3.0.2",
"sass": "^1.32.10",
"sass-loader": "^12.4.0",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.18",
"terser-webpack-plugin": "^5.2.5",
"ts-loader": "^9.2.5",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "4.3.5",
"util": "^0.12.4",
"webpack": "^5.64.4",
@@ -90,12 +81,12 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.33.1",
"braintree-web-drop-in": "1.30.1",
"browser-hrtime": "^1.1.8",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",
"font-awesome": "4.7.0",
"jquery": "3.6.0",
"jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4",
"popper.js": "1.16.1",
@@ -103,16 +94,14 @@
"rxjs": "^7.4.0",
"sweetalert2": "^10.16.6",
"webcrypto-shim": "0.1.7",
"whatwg-fetch": "3.6.2",
"zone.js": "0.11.4"
"whatwg-fetch": "3.6.2"
},
"engines": {
"node": "~16",
"npm": "~8"
},
"lint-staged": {
"./!(jslib)**": "prettier --ignore-unknown --write",
"*.ts": "eslint --fix",
"*": "prettier --ignore-unknown --write",
"*.png": "node scripts/optimize.js"
}
}

View File

@@ -1,4 +0,0 @@
/* eslint-disable no-undef */
module.exports = {
plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")],
};

View File

@@ -1,9 +0,0 @@
import { StateService as BaseStateService } from "jslib-common/abstractions/state.service";
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
import { Account } from "src/models/account";
export abstract class StateService extends BaseStateService<Account> {
getRememberEmail: (options?: StorageOptions) => Promise<boolean>;
setRememberEmail: (value: boolean, options?: StorageOptions) => Promise<void>;
}

View File

@@ -23,11 +23,7 @@
<p>{{ "acceptEmergencyAccess" | i18n }}</p>
<hr />
<div class="d-flex">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -6,7 +6,6 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({

View File

@@ -24,11 +24,7 @@
<p>{{ "joinOrganizationDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
<a

View File

@@ -8,11 +8,12 @@ import { LogService } from "jslib-common/abstractions/log.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 { Utils } from "jslib-common/misc/utils";
import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { Utils } from "jslib-common/misc/utils";
import { Policy } from "jslib-common/models/domain/policy";
import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({

View File

@@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -1,12 +1,13 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
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";
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({
selector: "app-hint",
templateUrl: "hint.component.html",

View File

@@ -1,7 +1,6 @@
import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
@@ -15,6 +14,8 @@ import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.serv
import { RouterService } from "../services/router.service";
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@Component({
selector: "app-lock",
templateUrl: "lock.component.html",
@@ -58,7 +59,7 @@ export class LockComponent extends BaseLockComponent {
if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl;
}
this.router.navigateByUrl(this.successRoute);
this.router.navigate([this.successRoute]);
};
}
}

View File

@@ -86,7 +86,7 @@
[queryParams]="{ email: email }"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
>
<i class="bwi bwi-pencil-square" aria-hidden="true"></i>
<i class="bwi bwi-pencil-square-o" aria-hidden="true"></i>
{{ "createAccount" | i18n }}
</a>
</div>

View File

@@ -1,26 +1,22 @@
import { Component, NgZone } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.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 { 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 { PolicyData } from "jslib-common/models/data/policyData";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "jslib-common/models/domain/policy";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { StateService } from "jslib-common/abstractions/state.service";
import { StateService } from "../../abstractions/state.service";
import { RouterService } from "../services/router.service";
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { Policy } from "jslib-common/models/domain/policy";
@Component({
selector: "app-login",
@@ -28,14 +24,13 @@ import { RouterService } from "../services/router.service";
})
export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false;
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
policies: ListResponse<PolicyResponse>;
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
private route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
@@ -43,10 +38,7 @@ export class LoginComponent extends BaseLoginComponent {
private apiService: ApiService,
private policyService: PolicyService,
logService: LogService,
ngZone: NgZone,
protected stateService: StateService,
private messagingService: MessagingService,
private routerService: RouterService
ngZone: NgZone
) {
super(
authService,
@@ -60,9 +52,6 @@ export class LoginComponent extends BaseLoginComponent {
logService,
ngZone
);
this.onSuccessfulLogin = async () => {
this.messagingService.send("setFullWidth");
};
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
@@ -72,108 +61,58 @@ export class LoginComponent extends BaseLoginComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
const route = this.router.createUrlTree(["settings/create-organization"], {
queryParams: { plan: qParams.org },
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { token: qParams.sponsorshipToken },
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
await super.ngOnInit();
this.rememberEmail = await this.stateService.getRememberEmail();
});
const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) {
let policyList: Policy[] = null;
try {
this.policies = await this.apiService.getPoliciesByToken(
const policies = await this.apiService.getPoliciesByToken(
invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(this.policies);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) {
this.logService.error(e);
}
if (policyList != null) {
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
invite.organizationId
);
// Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning =
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
this.enforcedPasswordPolicyOptions =
await this.policyService.getMasterPasswordPolicyOptions(policyList);
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
}
}
}
async goAfterLogIn() {
// Check master password against policy
if (this.enforcedPasswordPolicyOptions != null) {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
this.getPasswordStrengthUserInput()
);
const masterPasswordScore = strengthResult == null ? null : strengthResult.score;
// If invalid, save policies and require update
if (
!this.policyService.evaluateMasterPassword(
masterPasswordScore,
this.masterPassword,
this.enforcedPasswordPolicyOptions
)
) {
const policiesData: { [id: string]: PolicyData } = {};
this.policies.data.map((p) => (policiesData[p.id] = new PolicyData(p)));
await this.policyService.replace(policiesData);
this.router.navigate(["update-password"]);
return;
}
}
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute]);
}
}
async submit() {
await this.stateService.setRememberEmail(this.rememberEmail);
if (!this.rememberEmail) {
await this.stateService.setRememberedEmail(null);
}
await super.submit();
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
}
}

View File

@@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -5,6 +5,7 @@ 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 { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
@Component({

View File

@@ -7,7 +7,7 @@
<p>
{{ "recoverAccountTwoStepDesc" | i18n }}
<a
href="https://bitwarden.com/help/lost-two-step-device/"
href="https://help.bitwarden.com/article/lost-two-step-device/"
target="_blank"
rel="noopener"
>{{ "learnMore" | i18n }}</a
@@ -65,7 +65,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -7,6 +7,7 @@ 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 { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
@Component({

View File

@@ -1,100 +1,44 @@
<div class="layout" [ngClass]="['layout', layout]">
<!-- TEAMS 1 Header -->
<header
class="header"
*ngIf="
layout === 'default' ||
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'cnetcmpgnent' ||
layout === 'cnetcmpgnteams' ||
layout === 'cnetcmpgnind'
"
>
<header class="header" *ngIf="layout === 'enterprise2'">
<div class="container">
<div class="row">
<div class="col-7">
<img
alt="Bitwarden"
class="logo mb-2"
src="../../images/register-layout/logo-horizontal-white.svg"
src="../../images/register-layout/logo-horizontal-white.png"
/>
</div>
</div>
</div>
</header>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row">
<div class="col-7" *ngIf="layout">
<div class="mt-5">
<!-- Default Body -->
<div
*ngIf="
layout === 'teams' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'default'
"
>
<h1>The Bitwarden Password Manager</h1>
<h2>
Trusted by millions of individuals, teams, and organizations worldwide for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<div *ngIf="layout === 'enterprise2'">
<h2>Companies globally trust Bitwarden for password management.</h2>
<p>Start your 7-day free trial!</p>
<p class="highlight">Quickly deploy your <b>organization</b></p>
<p>Use Bitwarden across all platforms</p>
<p>Collaborate and share securely</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
<figure>
<figcaption>
<cite>
<img src="../../images/register-layout/wired-logo.png" alt="Wired" />
</cite>
</figcaption>
<blockquote>
"Bitwarden has become a popular choice among open-source software advocates. After
using it for a few months, I can see why." - February 2020
</blockquote>
</figure>
</div>
<!-- Teams & Enterprise Body -->
<div *ngIf="layout === 'teams1' || layout === 'teams2' || layout === 'enterprise2'">
<h1>
Start Your <span *ngIf="layout === 'teams1' || layout === 'teams1'">Teams<br /></span
><span *ngIf="layout === 'enterprise2'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
<div *ngIf="layout === 'enterprise3'">
<p>Enterprise 3 layout</p>
</div>
<!-- CNET Campaign Teams & Enterprise Body -->
<div *ngIf="layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnent'">
<h1>
Start Your <span *ngIf="layout === 'cnetcmpgnteams'">Teams<br /></span
><span *ngIf="layout === 'cnetcmpgnent'">Enterprise</span> Free Trial Now
</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Collaborate and share securely</p>
<p>Deploy and manage quickly and easily</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
</div>
<!-- CNET Campaign Premium Body -->
<div *ngIf="layout === 'cnetcmpgnind'">
<h1>Start Your Premium Account Now</h1>
<h2>
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
password storage and sharing.
</h2>
<p>Store logins, secure notes, and more</p>
<p>Secure your account with advanced two-step login</p>
<p>Access anywhere on any device</p>
<p>Create your account to get started</p>
<div *ngIf="layout === 'enterprise4'">
<p>Enterprise 4 layout</p>
</div>
</div>
</div>
@@ -258,7 +202,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
@@ -268,88 +212,5 @@
</div>
</div>
</div>
<div class="row">
<div class="col-7 d-flex align-items-center">
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/cnet-logo.svg"
class="w-25 d-block mx-auto"
alt="cnet logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
"No more excuses; start using Bitwarden today. The identity you save could be your
own. The money definitely will be."
</blockquote>
</figure>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
>
<figure>
<figcaption>
<cite>
<img
src="../../images/register-layout/forbes-logo.svg"
class="w-25 d-block mx-auto"
alt="Forbes Logo"
/>
</cite>
</figcaption>
<blockquote class="mx-auto text-center px-4">
“Bitwarden boasts the backing of some of the world's best security experts and an
attractive, easy-to-use interface”
</blockquote>
</figure>
</div>
</div>
<div
*ngIf="
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
<div
*ngIf="
layout === 'teams' ||
layout === 'teams1' ||
layout === 'teams2' ||
layout === 'enterprise' ||
layout === 'enterprise1' ||
layout === 'enterprise2' ||
layout === 'default'
"
class="col-5 d-flex align-items-center justify-content-center"
>
<img
src="../../images/register-layout/usnews-360-badge.svg"
class="w-50 d-block"
alt="US News 360 Reviews Best Password Manager"
/>
</div>
</div>
</form>
</div>

View File

@@ -1,8 +1,8 @@
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
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 { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -13,12 +13,14 @@ import { PasswordGenerationService } from "jslib-common/abstractions/passwordGen
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 { PolicyData } from "jslib-common/models/data/policyData";
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "jslib-common/models/domain/policy";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
import { RouterService } from "../services/router.service";
import { PolicyData } from "jslib-common/models/data/policyData";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
@Component({
selector: "app-register",
@@ -43,8 +45,7 @@ export class RegisterComponent extends BaseRegisterComponent {
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService,
private routerService: RouterService
logService: LogService
) {
super(
authService,
@@ -67,14 +68,14 @@ export class RegisterComponent extends BaseRegisterComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
const route = this.router.createUrlTree(["settings/create-organization"], {
queryParams: { plan: qParams.org },
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
@@ -91,10 +92,10 @@ export class RegisterComponent extends BaseRegisterComponent {
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { plan: qParams.sponsorshipToken },
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
if (this.referenceData.id === "") {
this.referenceData.id = null;

View File

@@ -1,7 +1,6 @@
import { Component } from "@angular/core";
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 { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -12,6 +11,8 @@ 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 { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",

View File

@@ -41,7 +41,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -1,8 +1,8 @@
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
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";
@@ -13,6 +13,8 @@ import { PasswordGenerationService } from "jslib-common/abstractions/passwordGen
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({
selector: "app-sso",
templateUrl: "sso.component.html",

View File

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

View File

@@ -114,9 +114,6 @@
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container>
<hr />
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="d-flex mb-3">
<button
type="submit"
@@ -138,7 +135,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -1,20 +1,20 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
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 { AppIdService } from "jslib-common/abstractions/appId.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 { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { RouterService } from "../services/router.service";
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@@ -36,10 +36,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
environmentService: EnvironmentService,
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService,
appIdService: AppIdService,
private routerService: RouterService
logService: LogService
) {
super(
authService,
@@ -51,9 +48,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
environmentService,
stateService,
route,
logService,
twoFactorService,
appIdService
logService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
@@ -76,9 +71,10 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
async goAfterLogIn() {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute], {
queryParams: {

View File

@@ -1,90 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{ "masterPasswordInvalidWarning" | i18n }} </app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
></app-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
autocomplete="new-password"
/>
<app-password-strength
[score]="masterPasswordScore"
[showText]="true"
></app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>
<button (click)="cancel()" type="button" class="btn btn-outline-secondary">
<span>{{ "cancel" | i18n }}</span>
</button>
</form>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,48 +0,0 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "jslib-angular/components/update-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 { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
@Component({
selector: "app-update-password",
templateUrl: "update-password.component.html",
})
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
logService: LogService,
stateService: StateService,
userVerificationService: UserVerificationService
) {
super(
router,
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
userVerificationService,
logService
);
}
}

View File

@@ -1,6 +1,5 @@
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";
@@ -9,9 +8,11 @@ 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 { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",

View File

@@ -1,5 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { ApiService } from "jslib-common/abstractions/api.service";
@@ -7,6 +8,7 @@ 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 { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
@Component({

View File

@@ -23,7 +23,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@@ -1,11 +1,13 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
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 { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest";
@Component({

View File

@@ -21,10 +21,14 @@ import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SettingsService } from "jslib-common/abstractions/settings.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from "./services/router.service";
import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
@@ -34,8 +38,6 @@ import { ResetPasswordPolicy } from "./organizations/policies/reset-password.com
import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from "./services/router.service";
const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes
@@ -91,17 +93,11 @@ export class AppComponent implements OnDestroy, OnInit {
this.ngZone.run(async () => {
switch (message.command) {
case "loggedIn":
this.notificationsService.updateConnection(false);
break;
case "loggedOut":
this.routerService.setPreviousUrl(null);
this.notificationsService.updateConnection(false);
break;
case "unlocked":
this.notificationsService.updateConnection(false);
break;
case "authBlocked":
this.routerService.setPreviousUrl(message.url);
this.router.navigate(["/"]);
break;
case "logout":
@@ -115,13 +111,13 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(["lock"]);
break;
case "lockedUrl":
this.routerService.setPreviousUrl(message.url);
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
break;
case "syncStarted":
break;
case "syncCompleted":
break;
case "upgradeOrganization": {
case "upgradeOrganization":
const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("upgradeOrganizationDesc"),
this.i18nService.t("upgradeOrganization"),
@@ -137,8 +133,7 @@ export class AppComponent implements OnDestroy, OnInit {
]);
}
break;
}
case "premiumRequired": {
case "premiumRequired":
const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"),
@@ -149,8 +144,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(["settings/premium"]);
}
break;
}
case "emailVerificationRequired": {
case "emailVerificationRequired":
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("emailVerificationRequiredDesc"),
this.i18nService.t("emailVerificationRequired"),
@@ -159,11 +153,10 @@ export class AppComponent implements OnDestroy, OnInit {
);
if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/create-bitwarden-account/"
"https://bitwarden.com/help/article/create-bitwarden-account/"
);
}
break;
}
case "showToast":
this.showToast(message);
break;
@@ -217,6 +210,7 @@ export class AppComponent implements OnDestroy, OnInit {
await Promise.all([
this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),

View File

@@ -1,8 +1,11 @@
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { AppComponent } from "./app.component";
import { OssRoutingModule } from "./oss-routing.module";
@@ -16,6 +19,11 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
BrowserAnimationsModule,
FormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule,
DragDropModule,
OssRoutingModule,

View File

@@ -1,5 +1,6 @@
import { Directive, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -30,6 +31,7 @@ export abstract class BaseAcceptComponent implements OnInit {
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
let errorMessage: string = null;
if (!error) {
@@ -43,6 +45,11 @@ export abstract class BaseAcceptComponent implements OnInit {
errorMessage = e.message;
}
} else {
await this.stateService.setLoginRedirect({
route: this.getRedirectRoute(),
qParams: qParams,
});
this.email = qParams.email;
await this.unauthedHandler(qParams);
}
@@ -60,4 +67,10 @@ export abstract class BaseAcceptComponent implements OnInit {
this.loading = false;
});
}
getRedirectRoute() {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
return urlTree.toString();
}
}

View File

@@ -4,9 +4,11 @@ import { ExportService } from "jslib-common/abstractions/export.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 { EventView } from "jslib-common/models/view/eventView";
import { EventResponse } from "jslib-common/models/response/eventResponse";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { EventView } from "jslib-common/models/view/eventView";
import { EventService } from "src/app/services/event.service";
@@ -17,7 +19,7 @@ export abstract class BaseEventsComponent {
events: EventView[];
start: string;
end: string;
dirtyDates = true;
dirtyDates: boolean = true;
continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;

View File

@@ -1,9 +1,5 @@
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -11,15 +7,24 @@ import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { Utils } from "jslib-common/misc/utils";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { Utils } from "jslib-common/misc/utils";
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;

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