mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-02 13:23:29 +01:00
Merge branch 'feature/sm-billing' into AC-1418-add-secrets-manager-manage-subscription-component
This commit is contained in:
commit
1c39938cda
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# The following owners will be the default owners for everything in the repo.
|
# The following owners will be the default owners for everything in the repo.
|
||||||
# Unless a later match takes precedence
|
# Unless a later match takes precedence
|
||||||
@bitwarden/team-leads
|
* @bitwarden/team-leads-eng
|
||||||
|
|
||||||
## Secrets Manager team files ##
|
## Secrets Manager team files ##
|
||||||
bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev
|
bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev
|
||||||
@ -76,3 +76,10 @@ libs/components @bitwarden/team-platform-dev
|
|||||||
|
|
||||||
## Desktop native module ##
|
## Desktop native module ##
|
||||||
apps/desktop/desktop_native @bitwarden/team-platform-dev
|
apps/desktop/desktop_native @bitwarden/team-platform-dev
|
||||||
|
|
||||||
|
## Multiple file owners ##
|
||||||
|
/apps/web/config
|
||||||
|
/apps/web/package.json
|
||||||
|
|
||||||
|
## DevOps team files ##
|
||||||
|
/.github/workflows @bitwarden/dept-devops
|
||||||
|
3
.github/whitelist-capital-letters.txt
vendored
3
.github/whitelist-capital-letters.txt
vendored
@ -4,7 +4,6 @@
|
|||||||
./apps/browser/src/safari/desktop/Base.lproj
|
./apps/browser/src/safari/desktop/Base.lproj
|
||||||
./apps/browser/src/services/vaultTimeout
|
./apps/browser/src/services/vaultTimeout
|
||||||
./apps/browser/store/windows/Assets
|
./apps/browser/store/windows/Assets
|
||||||
./libs/common/src/abstractions/userVerification
|
|
||||||
./libs/common/src/abstractions/vaultTimeout
|
./libs/common/src/abstractions/vaultTimeout
|
||||||
./libs/common/src/services/vaultTimeout
|
./libs/common/src/services/vaultTimeout
|
||||||
./bitwarden_license/README.md
|
./bitwarden_license/README.md
|
||||||
@ -26,8 +25,6 @@
|
|||||||
./libs/common/src/misc/linkedFieldOption.decorator.ts
|
./libs/common/src/misc/linkedFieldOption.decorator.ts
|
||||||
./libs/common/src/misc/serviceUtils.ts
|
./libs/common/src/misc/serviceUtils.ts
|
||||||
./libs/common/src/misc/serviceUtils.spec.ts
|
./libs/common/src/misc/serviceUtils.spec.ts
|
||||||
./libs/common/src/abstractions/userVerification/userVerification.service.abstraction.ts
|
|
||||||
./libs/common/src/abstractions/userVerification/userVerification-api.service.abstraction.ts
|
|
||||||
./libs/common/src/abstractions/vaultTimeout/vaultTimeoutSettings.service.ts
|
./libs/common/src/abstractions/vaultTimeout/vaultTimeoutSettings.service.ts
|
||||||
./libs/common/src/abstractions/vaultTimeout/vaultTimeout.service.ts
|
./libs/common/src/abstractions/vaultTimeout/vaultTimeout.service.ts
|
||||||
./libs/common/src/abstractions/anonymousHub.service.ts
|
./libs/common/src/abstractions/anonymousHub.service.ts
|
||||||
|
2
.github/workflows/build-web.yml
vendored
2
.github/workflows/build-web.yml
vendored
@ -84,6 +84,8 @@ jobs:
|
|||||||
npm_command: "build:bit:ee"
|
npm_command: "build:bit:ee"
|
||||||
- name: "cloud-euprd"
|
- name: "cloud-euprd"
|
||||||
npm_command: "build:bit:euprd"
|
npm_command: "build:bit:euprd"
|
||||||
|
- name: "cloud-euqa"
|
||||||
|
npm_command: "build:bit:euqa"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
60
.github/workflows/deploy-eu-qa-web.yml
vendored
Normal file
60
.github/workflows/deploy-eu-qa-web.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: Deploy Web to EU-QA Cloud
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Branch name to deploy (examples: 'master', 'feature/sm')"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
default: master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
azure-deploy:
|
||||||
|
name: Deploy to Azure
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
_WEB_ARTIFACT: "web-*-cloud-euqa.zip"
|
||||||
|
steps:
|
||||||
|
- name: Login to Azure - EU Subscription
|
||||||
|
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_EU_QA_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
|
- name: Retrieve Storage Account connection string
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
|
||||||
|
with:
|
||||||
|
keyvault: webvaulteu-westeurope-qa
|
||||||
|
secrets: "sa-bitwarden-web-vault-dev-key-temp"
|
||||||
|
|
||||||
|
- name: Download latest cloud asset
|
||||||
|
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
|
||||||
|
with:
|
||||||
|
workflow: build-web.yml
|
||||||
|
path: apps/web
|
||||||
|
workflow_conclusion: success
|
||||||
|
branch: ${{ github.event.inputs.tag }}
|
||||||
|
artifacts: ${{ env._WEB_ARTIFACT }}
|
||||||
|
|
||||||
|
- name: Unzip build asset
|
||||||
|
working-directory: apps/web
|
||||||
|
run: unzip ${{ env._WEB_ARTIFACT }}
|
||||||
|
|
||||||
|
- name: Empty container in Storage Account
|
||||||
|
run: |
|
||||||
|
az storage blob delete-batch \
|
||||||
|
--source '$web' \
|
||||||
|
--pattern '*' \
|
||||||
|
--connection-string "${{ steps.retrieve-secrets.outputs.sa-bitwarden-web-vault-dev-key-temp }}"
|
||||||
|
|
||||||
|
- name: Deploy to Azure Storage Account
|
||||||
|
working-directory: apps/web
|
||||||
|
run: |
|
||||||
|
az storage blob upload-batch \
|
||||||
|
--source "./build" \
|
||||||
|
--destination '$web' \
|
||||||
|
--connection-string "${{ steps.retrieve-secrets.outputs.sa-bitwarden-web-vault-dev-key-temp }}" \
|
||||||
|
--overwrite \
|
||||||
|
--no-progress
|
2
.github/workflows/deploy-non-prod-web.yml
vendored
2
.github/workflows/deploy-non-prod-web.yml
vendored
@ -12,8 +12,6 @@ on:
|
|||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- QA
|
- QA
|
||||||
- POC2
|
|
||||||
- eudevtest
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -101,6 +101,7 @@ const preview: Preview = {
|
|||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
storySort: {
|
storySort: {
|
||||||
|
method: "alphabetical",
|
||||||
order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"],
|
order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -952,7 +952,7 @@
|
|||||||
"message": "Server URL"
|
"message": "Server URL"
|
||||||
},
|
},
|
||||||
"apiUrl": {
|
"apiUrl": {
|
||||||
"message": "API Server URL"
|
"message": "API server URL"
|
||||||
},
|
},
|
||||||
"webVaultUrl": {
|
"webVaultUrl": {
|
||||||
"message": "Web vault server URL"
|
"message": "Web vault server URL"
|
||||||
|
@ -7,8 +7,6 @@ import { NotificationsService as NotificationsServiceAbstraction } from "@bitwar
|
|||||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
|
|
||||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||||
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@ -21,6 +19,8 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab
|
|||||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
|
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
|
||||||
|
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
|
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
||||||
|
@ -15,7 +15,6 @@ import { NotificationsService } from "@bitwarden/common/abstractions/notificatio
|
|||||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@ -31,6 +30,7 @@ import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-con
|
|||||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
@ -5,8 +5,8 @@ import { Router } from "@angular/router";
|
|||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
@ -6,8 +6,8 @@ import { UntypedFormBuilder } from "@angular/forms";
|
|||||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||||
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"urls": {
|
|
||||||
"icons": "https://icons.eudevtest.bitwarden.pw",
|
|
||||||
"notifications": "https://notifications.eudevtest.bitwarden.pw",
|
|
||||||
"scim": "https://scim.eudevtest.bitwarden.pw"
|
|
||||||
},
|
|
||||||
"flags": {
|
|
||||||
"secretsManager": true,
|
|
||||||
"showPasswordless": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"icons": "https://icons.bitwarden.net",
|
"icons": "https://icons.bitwarden.eu",
|
||||||
"notifications": "https://notifications.bitwarden.eu",
|
"notifications": "https://notifications.bitwarden.eu",
|
||||||
"scim": "https://scim.bitwarden.eu"
|
"scim": "https://scim.bitwarden.eu"
|
||||||
},
|
},
|
||||||
|
11
apps/web/config/euqa.json
Normal file
11
apps/web/config/euqa.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"urls": {
|
||||||
|
"icons": "https://icons.euqa.bitwarden.pw",
|
||||||
|
"notifications": "https://notifications.euqa.bitwarden.pw",
|
||||||
|
"scim": "https://scim.euqa.bitwarden.pw"
|
||||||
|
},
|
||||||
|
"flags": {
|
||||||
|
"secretsManager": true,
|
||||||
|
"showPasswordless": true
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
"build:bit:dev:watch": "cross-env ENV=development npm run build:bit:watch",
|
"build:bit:dev:watch": "cross-env ENV=development npm run build:bit:watch",
|
||||||
"build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit",
|
"build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit",
|
||||||
"build:bit:euprd": "cross-env NODE_ENV=production ENV=euprd npm run build:bit",
|
"build:bit:euprd": "cross-env NODE_ENV=production ENV=euprd npm run build:bit",
|
||||||
"build:bit:eudevtest": "cross-env NODE_ENV=production ENV=eudevtest npm run build:bit",
|
"build:bit:euqa": "cross-env NODE_ENV=production ENV=euqa npm run build:bit",
|
||||||
"build:bit:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build:bit",
|
"build:bit:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build:bit",
|
||||||
"build:oss:selfhost:watch": "cross-env ENV=selfhosted npm run build:oss:watch",
|
"build:oss:selfhost:watch": "cross-env ENV=selfhosted npm run build:oss:watch",
|
||||||
"build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch",
|
"build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch",
|
||||||
|
@ -4,10 +4,10 @@ import { FormBuilder, FormControl, Validators } from "@angular/forms";
|
|||||||
import { combineLatest, Subject, takeUntil } from "rxjs";
|
import { combineLatest, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@ -17,7 +17,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
|||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import { UserVerificationModule } from "../../../../shared/components/user-verification";
|
import { UserVerificationModule } from "../../../../auth/shared/components/user-verification";
|
||||||
import { SharedModule } from "../../../../shared/shared.module";
|
import { SharedModule } from "../../../../shared/shared.module";
|
||||||
|
|
||||||
class CountBasedLocalizationKey {
|
class CountBasedLocalizationKey {
|
||||||
|
@ -3,10 +3,9 @@ import { UntypedFormBuilder } from "@angular/forms";
|
|||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
@ -35,7 +34,6 @@ export class OrganizationExportComponent extends ExportComponent {
|
|||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
formBuilder: UntypedFormBuilder,
|
formBuilder: UntypedFormBuilder,
|
||||||
fileDownloadService: FileDownloadService,
|
fileDownloadService: FileDownloadService,
|
||||||
modalService: ModalService,
|
|
||||||
dialogService: DialogServiceAbstraction
|
dialogService: DialogServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -49,7 +47,6 @@ export class OrganizationExportComponent extends ExportComponent {
|
|||||||
userVerificationService,
|
userVerificationService,
|
||||||
formBuilder,
|
formBuilder,
|
||||||
fileDownloadService,
|
fileDownloadService,
|
||||||
modalService,
|
|
||||||
dialogService
|
dialogService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
|||||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { UserVerificationModule } from "../../../auth/shared/components/user-verification";
|
||||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||||
import { UserVerificationModule } from "../../../shared/components/user-verification";
|
|
||||||
|
|
||||||
import { EnrollMasterPasswordReset } from "./enroll-master-password-reset.component";
|
import { EnrollMasterPasswordReset } from "./enroll-master-password-reset.component";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
|
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
|
||||||
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
||||||
|
@ -2,7 +2,7 @@ import { Directive, EventEmitter, Output } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
|
@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request";
|
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request";
|
||||||
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
||||||
|
@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||||
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request";
|
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
|
@ -2,7 +2,7 @@ import { Component, NgZone } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request";
|
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request";
|
||||||
|
@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
|||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubio-otp.request";
|
import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubio-otp.request";
|
||||||
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
|
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export * from "./user-verification.module";
|
export * from "./user-verification.module";
|
||||||
export * from "./user-verification-prompt.component";
|
|
||||||
export * from "./user-verification.component";
|
export * from "./user-verification.component";
|
||||||
|
export * from "./user-verification-prompt.component";
|
@ -0,0 +1,20 @@
|
|||||||
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
|
<bit-dialog>
|
||||||
|
<span bitDialogTitle>{{ modalTitle | i18n }}</span>
|
||||||
|
<ng-container bitDialogContent>
|
||||||
|
<p bitTypography="body1">{{ confirmDescription | i18n }}</p>
|
||||||
|
<app-user-verification
|
||||||
|
[(invalidSecret)]="invalidSecret"
|
||||||
|
formControlName="secret"
|
||||||
|
></app-user-verification>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container bitDialogFooter>
|
||||||
|
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||||
|
{{ confirmButtonText | i18n }}
|
||||||
|
</button>
|
||||||
|
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</bit-dialog>
|
||||||
|
</form>
|
@ -0,0 +1,60 @@
|
|||||||
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
|
import { Component, Inject } from "@angular/core";
|
||||||
|
import { FormBuilder } from "@angular/forms";
|
||||||
|
|
||||||
|
import { UserVerificationPromptComponent as BaseUserVerificationPrompt } from "@bitwarden/angular/auth/components/user-verification-prompt.component";
|
||||||
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
|
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
|
export interface UserVerificationPromptParams {
|
||||||
|
confirmDescription: string;
|
||||||
|
confirmButtonText: string;
|
||||||
|
modalTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "user-verification-prompt.component.html",
|
||||||
|
})
|
||||||
|
export class UserVerificationPromptComponent extends BaseUserVerificationPrompt {
|
||||||
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) data: UserVerificationPromptParams,
|
||||||
|
private dialogRef: DialogRef<boolean>,
|
||||||
|
userVerificationService: UserVerificationService,
|
||||||
|
formBuilder: FormBuilder,
|
||||||
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
i18nService: I18nService
|
||||||
|
) {
|
||||||
|
// TODO: Remove when BaseUserVerificationPrompt has support for CL
|
||||||
|
const modalConfig: ModalConfig = { data };
|
||||||
|
super(
|
||||||
|
null,
|
||||||
|
modalConfig,
|
||||||
|
userVerificationService,
|
||||||
|
formBuilder,
|
||||||
|
platformUtilsService,
|
||||||
|
i18nService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override close(success: boolean) {
|
||||||
|
this.dialogRef.close(success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a UserVerificationPrompt
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Configuration for the dialog
|
||||||
|
*/
|
||||||
|
export const openUserVerificationPrompt = (
|
||||||
|
dialogService: DialogServiceAbstraction,
|
||||||
|
config: DialogConfig<UserVerificationPromptParams>
|
||||||
|
) => {
|
||||||
|
return dialogService.open<boolean, UserVerificationPromptParams>(
|
||||||
|
UserVerificationPromptComponent,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
<ng-container *ngIf="!usesKeyConnector">
|
||||||
|
<bit-form-field disableMargin>
|
||||||
|
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
id="masterPassword"
|
||||||
|
type="password"
|
||||||
|
name="MasterPasswordHash"
|
||||||
|
[formControl]="secret"
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||||
|
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
||||||
|
</bit-form-field>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="usesKeyConnector">
|
||||||
|
<div class="tw-mb-6">
|
||||||
|
<label class="tw-block">{{ "sendVerificationCode" | i18n }}</label>
|
||||||
|
<button type="button" bitButton buttonType="secondary" [bitAction]="requestOTP" appAutofocus>
|
||||||
|
{{ "sendCode" | i18n }}
|
||||||
|
</button>
|
||||||
|
<span class="tw-ml-2 tw-text-success" role="alert" @sent *ngIf="sentCode">
|
||||||
|
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||||
|
{{ "codeSent" | i18n }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bit-form-field disableMargin>
|
||||||
|
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
type="text"
|
||||||
|
id="verificationCode"
|
||||||
|
name="verificationCode"
|
||||||
|
[formControl]="secret"
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
||||||
|
</bit-form-field>
|
||||||
|
</ng-container>
|
@ -1,12 +1,13 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
|
||||||
import { SharedModule } from "../../shared.module";
|
import { SharedModule } from "../../../../shared/shared.module";
|
||||||
|
|
||||||
import { UserVerificationPromptComponent } from "./user-verification-prompt.component";
|
import { UserVerificationPromptComponent } from "./user-verification-prompt.component";
|
||||||
import { UserVerificationComponent } from "./user-verification.component";
|
import { UserVerificationComponent } from "./user-verification.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule],
|
imports: [SharedModule, FormsModule, ReactiveFormsModule],
|
||||||
declarations: [UserVerificationComponent, UserVerificationPromptComponent],
|
declarations: [UserVerificationComponent, UserVerificationPromptComponent],
|
||||||
exports: [UserVerificationComponent, UserVerificationPromptComponent],
|
exports: [UserVerificationComponent, UserVerificationPromptComponent],
|
||||||
})
|
})
|
@ -4,8 +4,8 @@ import { Router } from "@angular/router";
|
|||||||
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
|
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
@ -2,10 +2,10 @@ import { Component } from "@angular/core";
|
|||||||
|
|
||||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { OrganizationApiKeyRequest } from "@bitwarden/common/admin-console/models/request/organization-api-key.request";
|
import { OrganizationApiKeyRequest } from "@bitwarden/common/admin-console/models/request/organization-api-key.request";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { UserVerificationModule } from "../../auth/shared/components/user-verification";
|
||||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||||
import { UserVerificationModule } from "../../shared/components/user-verification";
|
|
||||||
|
|
||||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||||
import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
@ -2,7 +2,7 @@ import { Component, Input } from "@angular/core";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()">
|
|
||||||
<h2 class="tw-my-6 tw-px-3.5 tw-font-semibold" id="modalTitle | i18n ">
|
|
||||||
{{ modalTitle | i18n | uppercase }}
|
|
||||||
</h2>
|
|
||||||
<div class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-p-3.5">
|
|
||||||
{{ confirmDescription | i18n }}
|
|
||||||
</div>
|
|
||||||
<div class="tw-p-3.5">
|
|
||||||
<app-user-verification ngDefaultControl [formControl]="secret" name="secret">
|
|
||||||
</app-user-verification>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-p-3.5"
|
|
||||||
>
|
|
||||||
<button type="button" bitButton buttonType="primary" type="submit" appBlurClick>
|
|
||||||
<span>{{ confirmButtonText | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" bitButton buttonType="secondary" data-dismiss="modal">
|
|
||||||
{{ "cancel" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||||||
import { Component } from "@angular/core";
|
|
||||||
|
|
||||||
import { UserVerificationPromptComponent as BaseUserVerificationPrompt } from "@bitwarden/angular/auth/components/user-verification-prompt.component";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: "user-verification-prompt.component.html",
|
|
||||||
})
|
|
||||||
export class UserVerificationPromptComponent extends BaseUserVerificationPrompt {}
|
|
@ -1,46 +0,0 @@
|
|||||||
<ng-container *ngIf="!usesKeyConnector">
|
|
||||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="masterPassword"
|
|
||||||
type="password"
|
|
||||||
name="MasterPasswordHash"
|
|
||||||
class="form-control"
|
|
||||||
[formControl]="secret"
|
|
||||||
required
|
|
||||||
appAutofocus
|
|
||||||
appInputVerbatim
|
|
||||||
/>
|
|
||||||
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="usesKeyConnector">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline-secondary"
|
|
||||||
(click)="requestOTP()"
|
|
||||||
[disabled]="disableRequestOTP"
|
|
||||||
>
|
|
||||||
{{ "sendCode" | i18n }}
|
|
||||||
</button>
|
|
||||||
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
|
|
||||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
|
||||||
{{ "codeSent" | i18n }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="verificationCode"
|
|
||||||
type="input"
|
|
||||||
name="verificationCode"
|
|
||||||
class="form-control"
|
|
||||||
[formControl]="secret"
|
|
||||||
required
|
|
||||||
appAutofocus
|
|
||||||
appInputVerbatim
|
|
||||||
/>
|
|
||||||
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
@ -43,6 +43,7 @@ import { TwoFactorVerifyComponent } from "../auth/settings/two-factor-verify.com
|
|||||||
import { TwoFactorWebAuthnComponent } from "../auth/settings/two-factor-webauthn.component";
|
import { TwoFactorWebAuthnComponent } from "../auth/settings/two-factor-webauthn.component";
|
||||||
import { TwoFactorYubiKeyComponent } from "../auth/settings/two-factor-yubikey.component";
|
import { TwoFactorYubiKeyComponent } from "../auth/settings/two-factor-yubikey.component";
|
||||||
import { VerifyEmailComponent } from "../auth/settings/verify-email.component";
|
import { VerifyEmailComponent } from "../auth/settings/verify-email.component";
|
||||||
|
import { UserVerificationModule } from "../auth/shared/components/user-verification";
|
||||||
import { SsoComponent } from "../auth/sso.component";
|
import { SsoComponent } from "../auth/sso.component";
|
||||||
import { TwoFactorOptionsComponent } from "../auth/two-factor-options.component";
|
import { TwoFactorOptionsComponent } from "../auth/two-factor-options.component";
|
||||||
import { TwoFactorComponent } from "../auth/two-factor.component";
|
import { TwoFactorComponent } from "../auth/two-factor.component";
|
||||||
@ -109,7 +110,6 @@ import { AttachmentsComponent as OrgAttachmentsComponent } from "../vault/org-va
|
|||||||
import { CollectionsComponent as OrgCollectionsComponent } from "../vault/org-vault/collections.component";
|
import { CollectionsComponent as OrgCollectionsComponent } from "../vault/org-vault/collections.component";
|
||||||
|
|
||||||
import { AccountFingerprintComponent } from "./components/account-fingerprint/account-fingerprint.component";
|
import { AccountFingerprintComponent } from "./components/account-fingerprint/account-fingerprint.component";
|
||||||
import { UserVerificationModule } from "./components/user-verification";
|
|
||||||
import { SharedModule } from "./shared.module";
|
import { SharedModule } from "./shared.module";
|
||||||
|
|
||||||
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
|
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
|
||||||
@ -234,6 +234,7 @@ import { SharedModule } from "./shared.module";
|
|||||||
LowKdfComponent,
|
LowKdfComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
UserVerificationModule,
|
||||||
PremiumBadgeComponent,
|
PremiumBadgeComponent,
|
||||||
AcceptEmergencyComponent,
|
AcceptEmergencyComponent,
|
||||||
AcceptOrganizationComponent,
|
AcceptOrganizationComponent,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { UntypedFormBuilder } from "@angular/forms";
|
import { UntypedFormBuilder } from "@angular/forms";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { EncryptedExportType } from "@bitwarden/common/enums";
|
import { EncryptedExportType } from "@bitwarden/common/enums";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
@ -15,7 +15,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
|
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
|
||||||
|
|
||||||
import { UserVerificationPromptComponent } from "../../shared/components/user-verification";
|
import { openUserVerificationPrompt } from "../../auth/shared/components/user-verification";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-export",
|
selector: "app-export",
|
||||||
@ -37,7 +37,6 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
formBuilder: UntypedFormBuilder,
|
formBuilder: UntypedFormBuilder,
|
||||||
fileDownloadService: FileDownloadService,
|
fileDownloadService: FileDownloadService,
|
||||||
private modalService: ModalService,
|
|
||||||
dialogService: DialogServiceAbstraction
|
dialogService: DialogServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -101,8 +100,7 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
confirmDescription = "encExportKeyWarningDesc";
|
confirmDescription = "encExportKeyWarningDesc";
|
||||||
}
|
}
|
||||||
|
|
||||||
const ref = this.modalService.open(UserVerificationPromptComponent, {
|
const ref = openUserVerificationPrompt(this.dialogService, {
|
||||||
allowMultipleModals: true,
|
|
||||||
data: {
|
data: {
|
||||||
confirmDescription: confirmDescription,
|
confirmDescription: confirmDescription,
|
||||||
confirmButtonText: "exportVault",
|
confirmButtonText: "exportVault",
|
||||||
@ -114,7 +112,7 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref.onClosedPromise();
|
return firstValueFrom(ref.closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFileEncryptedExport() {
|
get isFileEncryptedExport() {
|
||||||
|
@ -22,6 +22,19 @@
|
|||||||
<input bitInput appAutofocus formControlName="name" />
|
<input bitInput appAutofocus formControlName="name" />
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
|
<bit-form-field *ngIf="showOrgSelector">
|
||||||
|
<bit-label>{{ "organization" | i18n }}</bit-label>
|
||||||
|
<bit-select bitInput formControlName="selectedOrg">
|
||||||
|
<bit-option
|
||||||
|
*ngFor="let org of organizations$ | async"
|
||||||
|
icon="bwi-business"
|
||||||
|
[value]="org.id"
|
||||||
|
[label]="org.name"
|
||||||
|
>
|
||||||
|
</bit-option>
|
||||||
|
</bit-select>
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "externalId" | i18n }}</bit-label>
|
<bit-label>{{ "externalId" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="externalId" />
|
<input bitInput formControlName="externalId" />
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
|
import {
|
||||||
|
combineLatest,
|
||||||
|
map,
|
||||||
|
Observable,
|
||||||
|
of,
|
||||||
|
shareReplay,
|
||||||
|
Subject,
|
||||||
|
switchMap,
|
||||||
|
takeUntil,
|
||||||
|
} from "rxjs";
|
||||||
|
|
||||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||||
@ -10,6 +19,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { CollectionResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
import { BitValidators } from "@bitwarden/components";
|
import { BitValidators } from "@bitwarden/components";
|
||||||
|
|
||||||
@ -35,9 +46,16 @@ export interface CollectionDialogParams {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
initialTab?: CollectionDialogTabType;
|
initialTab?: CollectionDialogTabType;
|
||||||
parentCollectionId?: string;
|
parentCollectionId?: string;
|
||||||
|
showOrgSelector?: boolean;
|
||||||
|
collectionIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CollectionDialogResult {
|
export interface CollectionDialogResult {
|
||||||
|
action: CollectionDialogAction;
|
||||||
|
collection: CollectionResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CollectionDialogAction {
|
||||||
Saved = "saved",
|
Saved = "saved",
|
||||||
Canceled = "canceled",
|
Canceled = "canceled",
|
||||||
Deleted = "deleted",
|
Deleted = "deleted",
|
||||||
@ -48,6 +66,7 @@ export enum CollectionDialogResult {
|
|||||||
})
|
})
|
||||||
export class CollectionDialogComponent implements OnInit, OnDestroy {
|
export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
protected organizations$: Observable<Organization[]>;
|
||||||
|
|
||||||
protected tabIndex: CollectionDialogTabType;
|
protected tabIndex: CollectionDialogTabType;
|
||||||
protected loading = true;
|
protected loading = true;
|
||||||
@ -56,11 +75,13 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
protected nestOptions: CollectionView[] = [];
|
protected nestOptions: CollectionView[] = [];
|
||||||
protected accessItems: AccessItemView[] = [];
|
protected accessItems: AccessItemView[] = [];
|
||||||
protected deletedParentName: string | undefined;
|
protected deletedParentName: string | undefined;
|
||||||
|
protected showOrgSelector = false;
|
||||||
protected formGroup = this.formBuilder.group({
|
protected formGroup = this.formBuilder.group({
|
||||||
name: ["", [Validators.required, BitValidators.forbiddenCharacters(["/"])]],
|
name: ["", [Validators.required, BitValidators.forbiddenCharacters(["/"])]],
|
||||||
externalId: "",
|
externalId: "",
|
||||||
parent: undefined as string | undefined,
|
parent: undefined as string | undefined,
|
||||||
access: [[] as AccessItemValue[]],
|
access: [[] as AccessItemValue[]],
|
||||||
|
selectedOrg: "",
|
||||||
});
|
});
|
||||||
protected PermissionMode = PermissionMode;
|
protected PermissionMode = PermissionMode;
|
||||||
|
|
||||||
@ -79,8 +100,31 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info;
|
this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
async ngOnInit() {
|
||||||
const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
|
// Opened from the individual vault
|
||||||
|
if (this.params.showOrgSelector) {
|
||||||
|
this.showOrgSelector = true;
|
||||||
|
this.formGroup.controls.selectedOrg.valueChanges
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((id) => this.loadOrg(id, this.params.collectionIds));
|
||||||
|
this.organizations$ = this.organizationService.organizations$.pipe(
|
||||||
|
map((orgs) =>
|
||||||
|
orgs
|
||||||
|
.filter((o) => o.canCreateNewCollections)
|
||||||
|
.sort(Utils.getSortFunction(this.i18nService, "name"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// patchValue will trigger a call to loadOrg() in this case, so no need to call it again here
|
||||||
|
this.formGroup.patchValue({ selectedOrg: this.params.organizationId });
|
||||||
|
} else {
|
||||||
|
// Opened from the org vault
|
||||||
|
this.formGroup.patchValue({ selectedOrg: this.params.organizationId });
|
||||||
|
this.loadOrg(this.params.organizationId, this.params.collectionIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadOrg(orgId: string, collectionIds: string[]) {
|
||||||
|
const organization$ = of(this.organizationService.get(orgId)).pipe(
|
||||||
shareReplay({ refCount: true, bufferSize: 1 })
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
);
|
);
|
||||||
const groups$ = organization$.pipe(
|
const groups$ = organization$.pipe(
|
||||||
@ -89,20 +133,19 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
return of([] as GroupView[]);
|
return of([] as GroupView[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.groupService.getAll(this.params.organizationId);
|
return this.groupService.getAll(orgId);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
combineLatest({
|
combineLatest({
|
||||||
organization: organization$,
|
organization: organization$,
|
||||||
collections: this.collectionService.getAll(this.params.organizationId),
|
collections: this.collectionService.getAll(orgId),
|
||||||
collectionDetails: this.params.collectionId
|
collectionDetails: this.params.collectionId
|
||||||
? this.collectionService.get(this.params.organizationId, this.params.collectionId)
|
? this.collectionService.get(orgId, this.params.collectionId)
|
||||||
: of(null),
|
: of(null),
|
||||||
groups: groups$,
|
groups: groups$,
|
||||||
users: this.organizationUserService.getAllUsers(this.params.organizationId),
|
users: this.organizationUserService.getAllUsers(orgId),
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$))
|
||||||
.subscribe(({ organization, collections, collectionDetails, groups, users }) => {
|
.subscribe(({ organization, collections, collectionDetails, groups, users }) => {
|
||||||
this.organization = organization;
|
this.organization = organization;
|
||||||
this.accessItems = [].concat(
|
this.accessItems = [].concat(
|
||||||
@ -110,6 +153,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
users.data.map(mapUserToAccessItemView)
|
users.data.map(mapUserToAccessItemView)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (collectionIds) {
|
||||||
|
collections = collections.filter((c) => collectionIds.includes(c.id));
|
||||||
|
}
|
||||||
|
|
||||||
if (this.params.collectionId) {
|
if (this.params.collectionId) {
|
||||||
this.collection = collections.find((c) => c.id === this.collectionId);
|
this.collection = collections.find((c) => c.id === this.collectionId);
|
||||||
this.nestOptions = collections.filter((c) => c.id !== this.collectionId);
|
this.nestOptions = collections.filter((c) => c.id !== this.collectionId);
|
||||||
@ -149,7 +196,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async cancel() {
|
protected async cancel() {
|
||||||
this.close(CollectionDialogResult.Canceled);
|
this.close(CollectionDialogAction.Canceled);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected submit = async () => {
|
protected submit = async () => {
|
||||||
@ -168,7 +215,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const collectionView = new CollectionAdminView();
|
const collectionView = new CollectionAdminView();
|
||||||
collectionView.id = this.params.collectionId;
|
collectionView.id = this.params.collectionId;
|
||||||
collectionView.organizationId = this.params.organizationId;
|
collectionView.organizationId = this.formGroup.controls.selectedOrg.value;
|
||||||
collectionView.externalId = this.formGroup.controls.externalId.value;
|
collectionView.externalId = this.formGroup.controls.externalId.value;
|
||||||
collectionView.groups = this.formGroup.controls.access.value
|
collectionView.groups = this.formGroup.controls.access.value
|
||||||
.filter((v) => v.type === AccessItemType.Group)
|
.filter((v) => v.type === AccessItemType.Group)
|
||||||
@ -184,7 +231,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
collectionView.name = this.formGroup.controls.name.value;
|
collectionView.name = this.formGroup.controls.name.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.collectionService.save(collectionView);
|
const savedCollection = await this.collectionService.save(collectionView);
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
@ -195,7 +242,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.close(CollectionDialogResult.Saved);
|
this.close(CollectionDialogAction.Saved, savedCollection);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected delete = async () => {
|
protected delete = async () => {
|
||||||
@ -217,7 +264,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.i18nService.t("deletedCollectionId", this.collection?.name)
|
this.i18nService.t("deletedCollectionId", this.collection?.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.close(CollectionDialogResult.Deleted);
|
this.close(CollectionDialogAction.Deleted);
|
||||||
};
|
};
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -225,8 +272,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private close(result: CollectionDialogResult) {
|
private close(action: CollectionDialogAction, collection?: CollectionResponse) {
|
||||||
this.dialogRef.close(result);
|
this.dialogRef.close({ action, collection } as CollectionDialogResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export class CollectionAdminService {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(collection: CollectionAdminView): Promise<unknown> {
|
async save(collection: CollectionAdminView): Promise<CollectionResponse> {
|
||||||
const request = await this.encrypt(collection);
|
const request = await this.encrypt(collection);
|
||||||
|
|
||||||
let response: CollectionResponse;
|
let response: CollectionResponse;
|
||||||
@ -61,9 +61,7 @@ export class CollectionAdminService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement upsert when in PS-1083: Collection Service refactors
|
return response;
|
||||||
// await this.collectionService.upsert(data);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(organizationId: string, collectionId: string): Promise<void> {
|
async delete(organizationId: string, collectionId: string): Promise<void> {
|
||||||
|
@ -32,7 +32,6 @@ import { OrganizationOptionsComponent } from "./organization-options.component";
|
|||||||
export class VaultFilterComponent implements OnInit, OnDestroy {
|
export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||||
filters?: VaultFilterList;
|
filters?: VaultFilterList;
|
||||||
@Input() activeFilter: VaultFilter = new VaultFilter();
|
@Input() activeFilter: VaultFilter = new VaultFilter();
|
||||||
@Output() onAddFolder = new EventEmitter<never>();
|
|
||||||
@Output() onEditFolder = new EventEmitter<FolderFilter>();
|
@Output() onEditFolder = new EventEmitter<FolderFilter>();
|
||||||
|
|
||||||
@Input() searchText = "";
|
@Input() searchText = "";
|
||||||
@ -142,10 +141,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
filter.selectedCollectionNode = collectionNode;
|
filter.selectedCollectionNode = collectionNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
addFolder = async (): Promise<void> => {
|
|
||||||
this.onAddFolder.emit();
|
|
||||||
};
|
|
||||||
|
|
||||||
editFolder = async (folder: FolderFilter): Promise<void> => {
|
editFolder = async (folder: FolderFilter): Promise<void> => {
|
||||||
this.onEditFolder.emit(folder);
|
this.onEditFolder.emit(folder);
|
||||||
};
|
};
|
||||||
@ -249,10 +244,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
text: "editFolder",
|
text: "editFolder",
|
||||||
action: this.editFolder,
|
action: this.editFolder,
|
||||||
},
|
},
|
||||||
add: {
|
|
||||||
text: "Add Folder",
|
|
||||||
action: this.addFolder,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return folderFilterSection;
|
return folderFilterSection;
|
||||||
}
|
}
|
||||||
|
@ -34,16 +34,6 @@
|
|||||||
<h3 *ngIf="!headerInfo.isSelectable" class="filter-title">
|
<h3 *ngIf="!headerInfo.isSelectable" class="filter-title">
|
||||||
{{ headerNode.node.name | i18n }}
|
{{ headerNode.node.name | i18n }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
*ngIf="showAddButton"
|
|
||||||
(click)="onAdd()"
|
|
||||||
class="text-muted ml-auto add-button"
|
|
||||||
appA11yTitle="{{ addInfo.text | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
id="{{ headerNode.node.name }}-filters"
|
id="{{ headerNode.node.name }}-filters"
|
||||||
|
@ -87,10 +87,6 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
|||||||
return this.section.add;
|
return this.section.add;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showAddButton() {
|
|
||||||
return this.section.add && !this.section.add.route;
|
|
||||||
}
|
|
||||||
|
|
||||||
get showAddLink() {
|
get showAddLink() {
|
||||||
return this.section.add && this.section.add.route;
|
return this.section.add && this.section.add.route;
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
||||||
<button type="button" bitButton buttonType="primary" (click)="addCipher()">
|
<div appListDropdown>
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
<button
|
||||||
{{ "newItem" | i18n }}
|
bitButton
|
||||||
</button>
|
buttonType="primary"
|
||||||
|
type="button"
|
||||||
|
[bitMenuTriggerFor]="addOptions"
|
||||||
|
id="newItemDropdown"
|
||||||
|
appA11yTitle="{{ 'new' | i18n }}"
|
||||||
|
>
|
||||||
|
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||||
|
<button type="button" bitMenuItem (click)="addCipher()">
|
||||||
|
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
||||||
|
{{ "item" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button type="button" bitMenuItem (click)="addFolder()">
|
||||||
|
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
||||||
|
{{ "folder" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button *ngIf="canCreateCollections" type="button" bitMenuItem (click)="addCollection()">
|
||||||
|
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||||
|
{{ "collection" | i18n }}
|
||||||
|
</button>
|
||||||
|
</bit-menu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,11 +39,26 @@ export class VaultHeaderComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() collection?: TreeNode<CollectionView>;
|
@Input() collection?: TreeNode<CollectionView>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether 'Collection' option is shown in the 'New' dropdown
|
||||||
|
*/
|
||||||
|
@Input() canCreateCollections: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits an event when the new item button is clicked in the header
|
* Emits an event when the new item button is clicked in the header
|
||||||
*/
|
*/
|
||||||
@Output() onAddCipher = new EventEmitter<void>();
|
@Output() onAddCipher = new EventEmitter<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event when the new collection button is clicked in the 'New' dropdown menu
|
||||||
|
*/
|
||||||
|
@Output() onAddCollection = new EventEmitter<null>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event when the new folder button is clicked in the 'New' dropdown menu
|
||||||
|
*/
|
||||||
|
@Output() onAddFolder = new EventEmitter<null>();
|
||||||
|
|
||||||
constructor(private i18nService: I18nService) {}
|
constructor(private i18nService: I18nService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,4 +130,12 @@ export class VaultHeaderComponent {
|
|||||||
protected addCipher() {
|
protected addCipher() {
|
||||||
this.onAddCipher.emit();
|
this.onAddCipher.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addFolder(): Promise<void> {
|
||||||
|
this.onAddFolder.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addCollection(): Promise<void> {
|
||||||
|
this.onAddCollection.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
[searchText]="currentSearchText$ | async"
|
[searchText]="currentSearchText$ | async"
|
||||||
(searchTextChanged)="filterSearchText($event)"
|
(searchTextChanged)="filterSearchText($event)"
|
||||||
(onAddFolder)="addFolder()"
|
|
||||||
(onEditFolder)="editFolder($event)"
|
(onEditFolder)="editFolder($event)"
|
||||||
></app-vault-filter>
|
></app-vault-filter>
|
||||||
</div>
|
</div>
|
||||||
@ -21,8 +20,11 @@
|
|||||||
[filter]="filter"
|
[filter]="filter"
|
||||||
[loading]="refreshing && !performingInitialLoad"
|
[loading]="refreshing && !performingInitialLoad"
|
||||||
[organizations]="allOrganizations"
|
[organizations]="allOrganizations"
|
||||||
|
[canCreateCollections]="canCreateCollections"
|
||||||
[collection]="selectedCollection"
|
[collection]="selectedCollection"
|
||||||
(onAddCipher)="addCipher()"
|
(onAddCipher)="addCipher()"
|
||||||
|
(onAddCollection)="addCollection()"
|
||||||
|
(onAddFolder)="addFolder()"
|
||||||
></app-vault-header>
|
></app-vault-header>
|
||||||
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi-exclamation-triangle">
|
<app-callout type="warning" *ngIf="activeFilter.isDeleted" icon="bwi-exclamation-triangle">
|
||||||
{{ trashCleanupWarning }}
|
{{ trashCleanupWarning }}
|
||||||
|
@ -54,11 +54,14 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
|
|||||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
|
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
|
||||||
|
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
import { Icons } from "@bitwarden/components";
|
import { Icons } from "@bitwarden/components";
|
||||||
|
|
||||||
import { UpdateKeyComponent } from "../../settings/update-key.component";
|
import { UpdateKeyComponent } from "../../settings/update-key.component";
|
||||||
|
import { CollectionDialogAction, openCollectionDialog } from "../components/collection-dialog";
|
||||||
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
||||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||||
|
|
||||||
@ -140,6 +143,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
protected collections: CollectionView[];
|
protected collections: CollectionView[];
|
||||||
protected isEmpty: boolean;
|
protected isEmpty: boolean;
|
||||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||||
|
protected canCreateCollections = false;
|
||||||
protected currentSearchText$: Observable<string>;
|
protected currentSearchText$: Observable<string>;
|
||||||
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
@ -234,12 +238,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
const canAccessPremium$ = Utils.asyncToObservable(() =>
|
const canAccessPremium$ = Utils.asyncToObservable(() =>
|
||||||
this.stateService.getCanAccessPremium()
|
this.stateService.getCanAccessPremium()
|
||||||
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||||
const allCollections$ = Utils.asyncToObservable(() =>
|
const allCollections$ = Utils.asyncToObservable(() => this.collectionService.getAllDecrypted());
|
||||||
this.collectionService.getAllDecrypted()
|
|
||||||
).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
|
||||||
const nestedCollections$ = allCollections$.pipe(
|
const nestedCollections$ = allCollections$.pipe(
|
||||||
map((collections) => getNestedCollectionTree(collections)),
|
map((collections) => getNestedCollectionTree(collections))
|
||||||
shareReplay({ refCount: true, bufferSize: 1 })
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.searchText$
|
this.searchText$
|
||||||
@ -384,6 +385,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.collections = collections;
|
this.collections = collections;
|
||||||
this.selectedCollection = selectedCollection;
|
this.selectedCollection = selectedCollection;
|
||||||
|
|
||||||
|
this.canCreateCollections = allOrganizations?.some((o) => o.canCreateNewCollections);
|
||||||
|
|
||||||
this.showBulkMove =
|
this.showBulkMove =
|
||||||
filter.type !== "trash" &&
|
filter.type !== "trash" &&
|
||||||
(filter.organizationId === undefined || filter.organizationId === Unassigned);
|
(filter.organizationId === undefined || filter.organizationId === Unassigned);
|
||||||
@ -639,6 +642,32 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
return childComponent;
|
return childComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addCollection() {
|
||||||
|
const dialog = openCollectionDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
organizationId: this.allOrganizations
|
||||||
|
.filter((o) => o.canCreateNewCollections)
|
||||||
|
.sort(Utils.getSortFunction(this.i18nService, "name"))[0].id,
|
||||||
|
parentCollectionId: this.filter.collectionId,
|
||||||
|
showOrgSelector: true,
|
||||||
|
collectionIds: this.allCollections.map((c) => c.id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await lastValueFrom(dialog.closed);
|
||||||
|
if (result.action === CollectionDialogAction.Saved) {
|
||||||
|
if (result.collection) {
|
||||||
|
// Update CollectionService with the new collection
|
||||||
|
const c = new CollectionData(result.collection as CollectionDetailsResponse);
|
||||||
|
await this.collectionService.upsert(c);
|
||||||
|
}
|
||||||
|
this.refresh();
|
||||||
|
} else if (result.action === CollectionDialogAction.Deleted) {
|
||||||
|
// TODO: Remove collection from collectionService when collection
|
||||||
|
// deletion is implemented in the individual vault in AC-1347
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async cloneCipher(cipher: CipherView) {
|
async cloneCipher(cipher: CipherView) {
|
||||||
const component = await this.editCipher(cipher);
|
const component = await this.editCipher(cipher);
|
||||||
component.cloneMode = true;
|
component.cloneMode = true;
|
||||||
|
@ -60,7 +60,7 @@ import { openEntityEventsDialog } from "../../admin-console/organizations/manage
|
|||||||
import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||||
import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model";
|
import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model";
|
||||||
import {
|
import {
|
||||||
CollectionDialogResult,
|
CollectionDialogAction,
|
||||||
CollectionDialogTabType,
|
CollectionDialogTabType,
|
||||||
openCollectionDialog,
|
openCollectionDialog,
|
||||||
} from "../components/collection-dialog";
|
} from "../components/collection-dialog";
|
||||||
@ -866,7 +866,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await lastValueFrom(dialog.closed);
|
const result = await lastValueFrom(dialog.closed);
|
||||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
if (
|
||||||
|
result.action === CollectionDialogAction.Saved ||
|
||||||
|
result.action === CollectionDialogAction.Deleted
|
||||||
|
) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,7 +880,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await lastValueFrom(dialog.closed);
|
const result = await lastValueFrom(dialog.closed);
|
||||||
if (result === CollectionDialogResult.Saved || result === CollectionDialogResult.Deleted) {
|
if (
|
||||||
|
result.action === CollectionDialogAction.Saved ||
|
||||||
|
result.action === CollectionDialogAction.Deleted
|
||||||
|
) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -964,8 +964,14 @@
|
|||||||
"confirmVaultExport": {
|
"confirmVaultExport": {
|
||||||
"message": "Confirm vault export"
|
"message": "Confirm vault export"
|
||||||
},
|
},
|
||||||
|
"confirmSecretsExport": {
|
||||||
|
"message": "Confirm secrets export"
|
||||||
|
},
|
||||||
"exportWarningDesc": {
|
"exportWarningDesc": {
|
||||||
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
|
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
|
||||||
|
},
|
||||||
|
"exportSecretsWarningDesc": {
|
||||||
|
"message": "This export contains your secrets data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
|
||||||
},
|
},
|
||||||
"encExportKeyWarningDesc": {
|
"encExportKeyWarningDesc": {
|
||||||
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
|
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file."
|
||||||
@ -979,6 +985,9 @@
|
|||||||
"exportVault": {
|
"exportVault": {
|
||||||
"message": "Export vault"
|
"message": "Export vault"
|
||||||
},
|
},
|
||||||
|
"exportSecrets": {
|
||||||
|
"message": "Export secrets"
|
||||||
|
},
|
||||||
"fileFormat": {
|
"fileFormat": {
|
||||||
"message": "File format"
|
"message": "File format"
|
||||||
},
|
},
|
||||||
@ -2657,6 +2666,12 @@
|
|||||||
"failedLogin2fa": {
|
"failedLogin2fa": {
|
||||||
"message": "Login attempt failed with incorrect two-step login."
|
"message": "Login attempt failed with incorrect two-step login."
|
||||||
},
|
},
|
||||||
|
"incorrectPassword": {
|
||||||
|
"message": "Incorrect password"
|
||||||
|
},
|
||||||
|
"incorrectCode": {
|
||||||
|
"message": "Incorrect code"
|
||||||
|
},
|
||||||
"exportedVault": {
|
"exportedVault": {
|
||||||
"message": "Vault exported"
|
"message": "Vault exported"
|
||||||
},
|
},
|
||||||
@ -6182,10 +6197,6 @@
|
|||||||
"message": "Access token created and copied to clipboard",
|
"message": "Access token created and copied to clipboard",
|
||||||
"description": "Notification to inform the user that the access token has been created and copied to the clipboard."
|
"description": "Notification to inform the user that the access token has been created and copied to the clipboard."
|
||||||
},
|
},
|
||||||
"accessTokenPermissionsBetaNotification": {
|
|
||||||
"message": "Permissions management is unavailable for beta.",
|
|
||||||
"description": "Notification to inform the user that the feature for managing access token permissions is not available in the beta version."
|
|
||||||
},
|
|
||||||
"revokeAccessToken": {
|
"revokeAccessToken": {
|
||||||
"message": "Revoke access token",
|
"message": "Revoke access token",
|
||||||
"description": "Invalidates / cancels an access token and as such removes access to secrets for the client application."
|
"description": "Invalidates / cancels an access token and as such removes access to secrets for the client application."
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
granteeType="people"
|
granteeType="people"
|
||||||
[label]="'people' | i18n"
|
[label]="'people' | i18n"
|
||||||
[hint]="'projectPeopleSelectHint' | i18n"
|
[hint]="'projectPeopleSelectHint' | i18n"
|
||||||
[columnTitle]="'groupSlashUser' | i18n"
|
[columnTitle]="'name' | i18n"
|
||||||
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
||||||
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
||||||
(onDeleteAccessPolicy)="handleDeleteAccessPolicy($event)"
|
(onDeleteAccessPolicy)="handleDeleteAccessPolicy($event)"
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
</label>
|
</label>
|
||||||
</th>
|
</th>
|
||||||
<th bitCell>{{ "name" | i18n }}</th>
|
<th bitCell>{{ "name" | i18n }}</th>
|
||||||
<th bitCell>{{ "permissions" | i18n }}</th>
|
|
||||||
<th bitCell>{{ "expires" | i18n }}</th>
|
<th bitCell>{{ "expires" | i18n }}</th>
|
||||||
<th bitCell>{{ "lastEdited" | i18n }}</th>
|
<th bitCell>{{ "lastEdited" | i18n }}</th>
|
||||||
<th bitCell class="tw-w-0">
|
<th bitCell class="tw-w-0">
|
||||||
@ -57,7 +56,6 @@
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td bitCell>{{ token.name }}</td>
|
<td bitCell>{{ token.name }}</td>
|
||||||
<td bitCell>{{ permission(token) | i18n }}</td>
|
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
{{ token.expireAt === null ? ("never" | i18n) : (token.expireAt | date : "medium") }}
|
{{ token.expireAt === null ? ("never" | i18n) : (token.expireAt | date : "medium") }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -39,8 +39,4 @@ export class AccessListComponent {
|
|||||||
const selected = this.tokens.filter((s) => this.selection.selected.includes(s.id));
|
const selected = this.tokens.filter((s) => this.selection.selected.includes(s.id));
|
||||||
this.revokeAccessTokensEvent.emit(selected);
|
this.revokeAccessTokensEvent.emit(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected permission(token: AccessTokenView) {
|
|
||||||
return "canRead";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,24 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { combineLatestWith, Observable, startWith, switchMap } from "rxjs";
|
import {
|
||||||
|
combineLatestWith,
|
||||||
|
firstValueFrom,
|
||||||
|
Observable,
|
||||||
|
startWith,
|
||||||
|
Subject,
|
||||||
|
switchMap,
|
||||||
|
takeUntil,
|
||||||
|
} from "rxjs";
|
||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/shared/components/user-verification";
|
import { openUserVerificationPrompt } from "@bitwarden/web-vault/app/auth/shared/components/user-verification";
|
||||||
|
|
||||||
|
import { ServiceAccountView } from "../../models/view/service-account.view";
|
||||||
import { AccessTokenView } from "../models/view/access-token.view";
|
import { AccessTokenView } from "../models/view/access-token.view";
|
||||||
|
import { ServiceAccountService } from "../service-account.service";
|
||||||
|
|
||||||
import { AccessService } from "./access.service";
|
import { AccessService } from "./access.service";
|
||||||
import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create-dialog.component";
|
import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create-dialog.component";
|
||||||
@ -17,11 +27,11 @@ import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create-
|
|||||||
selector: "sm-access-tokens",
|
selector: "sm-access-tokens",
|
||||||
templateUrl: "./access-tokens.component.html",
|
templateUrl: "./access-tokens.component.html",
|
||||||
})
|
})
|
||||||
export class AccessTokenComponent implements OnInit {
|
export class AccessTokenComponent implements OnInit, OnDestroy {
|
||||||
accessTokens$: Observable<AccessTokenView[]>;
|
accessTokens$: Observable<AccessTokenView[]>;
|
||||||
|
|
||||||
private serviceAccountId: string;
|
private destroy$ = new Subject<void>();
|
||||||
private organizationId: string;
|
private serviceAccountView: ServiceAccountView;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -29,19 +39,39 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
private dialogService: DialogServiceAbstraction,
|
private dialogService: DialogServiceAbstraction,
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService
|
private i18nService: I18nService,
|
||||||
|
private serviceAccountService: ServiceAccountService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accessTokens$ = this.accessService.accessToken$.pipe(
|
this.accessTokens$ = this.accessService.accessToken$.pipe(
|
||||||
startWith(null),
|
startWith(null),
|
||||||
combineLatestWith(this.route.params),
|
combineLatestWith(this.route.params),
|
||||||
switchMap(async ([_, params]) => {
|
switchMap(async ([_, params]) =>
|
||||||
this.organizationId = params.organizationId;
|
this.accessService.getAccessTokens(params.organizationId, params.serviceAccountId)
|
||||||
this.serviceAccountId = params.serviceAccountId;
|
)
|
||||||
return await this.getAccessTokens();
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.serviceAccountService.serviceAccount$
|
||||||
|
.pipe(
|
||||||
|
startWith(null),
|
||||||
|
combineLatestWith(this.route.params),
|
||||||
|
switchMap(([_, params]) =>
|
||||||
|
this.serviceAccountService.getByServiceAccountId(
|
||||||
|
params.serviceAccountId,
|
||||||
|
params.organizationId
|
||||||
|
)
|
||||||
|
),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((serviceAccountView) => {
|
||||||
|
this.serviceAccountView = serviceAccountView;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async revoke(tokens: AccessTokenView[]) {
|
protected async revoke(tokens: AccessTokenView[]) {
|
||||||
@ -59,7 +89,7 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.accessService.revokeAccessTokens(
|
await this.accessService.revokeAccessTokens(
|
||||||
this.serviceAccountId,
|
this.serviceAccountView.id,
|
||||||
tokens.map((t) => t.id)
|
tokens.map((t) => t.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -69,14 +99,12 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
protected openNewAccessTokenDialog() {
|
protected openNewAccessTokenDialog() {
|
||||||
AccessTokenCreateDialogComponent.openNewAccessTokenDialog(
|
AccessTokenCreateDialogComponent.openNewAccessTokenDialog(
|
||||||
this.dialogService,
|
this.dialogService,
|
||||||
this.serviceAccountId,
|
this.serviceAccountView
|
||||||
this.organizationId
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private verifyUser() {
|
private verifyUser() {
|
||||||
const ref = this.modalService.open(UserVerificationPromptComponent, {
|
const ref = openUserVerificationPrompt(this.dialogService, {
|
||||||
allowMultipleModals: true,
|
|
||||||
data: {
|
data: {
|
||||||
confirmDescription: "revokeAccessTokenDesc",
|
confirmDescription: "revokeAccessTokenDesc",
|
||||||
confirmButtonText: "revokeAccessToken",
|
confirmButtonText: "revokeAccessToken",
|
||||||
@ -88,10 +116,6 @@ export class AccessTokenComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref.onClosedPromise();
|
return firstValueFrom(ref.closed);
|
||||||
}
|
|
||||||
|
|
||||||
private async getAccessTokens(): Promise<AccessTokenView[]> {
|
|
||||||
return await this.accessService.getAccessTokens(this.organizationId, this.serviceAccountId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,6 @@
|
|||||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="name" />
|
<input bitInput formControlName="name" />
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
<div class="tw-mb-6">
|
|
||||||
<bit-form-field class="tw-mb-0">
|
|
||||||
<bit-label>{{ "permissions" | i18n }}</bit-label>
|
|
||||||
<select bitInput disabled>
|
|
||||||
<option selected value="canRead">
|
|
||||||
{{ "canRead" | i18n }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</bit-form-field>
|
|
||||||
<span class="tw-text-sm tw-text-muted">
|
|
||||||
{{ "accessTokenPermissionsBetaNotification" | i18n }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<sm-expiration-options
|
<sm-expiration-options
|
||||||
formControlName="expirationDateControl"
|
formControlName="expirationDateControl"
|
||||||
[expirationDayOptions]="expirationDayOptions"
|
[expirationDayOptions]="expirationDayOptions"
|
||||||
|
@ -3,7 +3,6 @@ import { Component, Inject, OnInit } from "@angular/core";
|
|||||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
|
|
||||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
|
||||||
import { BitValidators } from "@bitwarden/components";
|
import { BitValidators } from "@bitwarden/components";
|
||||||
|
|
||||||
import { ServiceAccountView } from "../../../models/view/service-account.view";
|
import { ServiceAccountView } from "../../../models/view/service-account.view";
|
||||||
@ -13,7 +12,6 @@ import { AccessService } from "../access.service";
|
|||||||
import { AccessTokenDetails, AccessTokenDialogComponent } from "./access-token-dialog.component";
|
import { AccessTokenDetails, AccessTokenDialogComponent } from "./access-token-dialog.component";
|
||||||
|
|
||||||
export interface AccessTokenOperation {
|
export interface AccessTokenOperation {
|
||||||
organizationId: string;
|
|
||||||
serviceAccountView: ServiceAccountView;
|
serviceAccountView: ServiceAccountView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,17 +33,12 @@ export class AccessTokenCreateDialogComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
public dialogRef: DialogRef,
|
public dialogRef: DialogRef,
|
||||||
@Inject(DIALOG_DATA) public data: AccessTokenOperation,
|
@Inject(DIALOG_DATA) public data: AccessTokenOperation,
|
||||||
private i18nService: I18nService,
|
|
||||||
private dialogService: DialogServiceAbstraction,
|
private dialogService: DialogServiceAbstraction,
|
||||||
private accessService: AccessService
|
private accessService: AccessService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (
|
if (!this.data.serviceAccountView) {
|
||||||
!this.data.organizationId ||
|
|
||||||
!this.data.serviceAccountView?.id ||
|
|
||||||
!this.data.serviceAccountView?.name
|
|
||||||
) {
|
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The access token create dialog was not called with the appropriate operation values.`
|
`The access token create dialog was not called with the appropriate operation values.`
|
||||||
@ -62,7 +55,7 @@ export class AccessTokenCreateDialogComponent implements OnInit {
|
|||||||
accessTokenView.name = this.formGroup.value.name;
|
accessTokenView.name = this.formGroup.value.name;
|
||||||
accessTokenView.expireAt = this.formGroup.value.expirationDateControl;
|
accessTokenView.expireAt = this.formGroup.value.expirationDateControl;
|
||||||
const accessToken = await this.accessService.createAccessToken(
|
const accessToken = await this.accessService.createAccessToken(
|
||||||
this.data.organizationId,
|
this.data.serviceAccountView.organizationId,
|
||||||
this.data.serviceAccountView.id,
|
this.data.serviceAccountView.id,
|
||||||
accessTokenView
|
accessTokenView
|
||||||
);
|
);
|
||||||
@ -90,18 +83,11 @@ export class AccessTokenCreateDialogComponent implements OnInit {
|
|||||||
|
|
||||||
static openNewAccessTokenDialog(
|
static openNewAccessTokenDialog(
|
||||||
dialogService: DialogServiceAbstraction,
|
dialogService: DialogServiceAbstraction,
|
||||||
serviceAccountId: string,
|
serviceAccountView: ServiceAccountView
|
||||||
organizationId: string
|
|
||||||
) {
|
) {
|
||||||
// TODO once service account names are implemented in service account contents page pass in here.
|
|
||||||
const serviceAccountView = new ServiceAccountView();
|
|
||||||
serviceAccountView.id = serviceAccountId;
|
|
||||||
serviceAccountView.name = "placeholder";
|
|
||||||
|
|
||||||
return dialogService.open<unknown, AccessTokenOperation>(AccessTokenCreateDialogComponent, {
|
return dialogService.open<unknown, AccessTokenOperation>(AccessTokenCreateDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
organizationId: organizationId,
|
serviceAccountView,
|
||||||
serviceAccountView: serviceAccountView,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
granteeType="people"
|
granteeType="people"
|
||||||
[label]="'people' | i18n"
|
[label]="'people' | i18n"
|
||||||
[hint]="'projectPeopleSelectHint' | i18n"
|
[hint]="'projectPeopleSelectHint' | i18n"
|
||||||
[columnTitle]="'groupSlashUser' | i18n"
|
[columnTitle]="'name' | i18n"
|
||||||
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
[emptyMessage]="'projectEmptyPeopleAccessPolicies' | i18n"
|
||||||
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
(onCreateAccessPolicies)="handleCreateAccessPolicies($event)"
|
||||||
(onDeleteAccessPolicy)="handleDeleteAccessPolicy($event)"
|
(onDeleteAccessPolicy)="handleDeleteAccessPolicy($event)"
|
||||||
|
@ -15,6 +15,8 @@ import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
|
import { ServiceAccountView } from "../models/view/service-account.view";
|
||||||
|
|
||||||
import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component";
|
import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component";
|
||||||
import { ServiceAccountService } from "./service-account.service";
|
import { ServiceAccountService } from "./service-account.service";
|
||||||
|
|
||||||
@ -32,6 +34,7 @@ export class ServiceAccountComponent implements OnInit, OnDestroy {
|
|||||||
startWith(null)
|
startWith(null)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private serviceAccountView: ServiceAccountView;
|
||||||
protected serviceAccount$ = combineLatest([this.route.params, this.onChange$]).pipe(
|
protected serviceAccount$ = combineLatest([this.route.params, this.onChange$]).pipe(
|
||||||
switchMap(([params, _]) =>
|
switchMap(([params, _]) =>
|
||||||
this.serviceAccountService.getByServiceAccountId(
|
this.serviceAccountService.getByServiceAccountId(
|
||||||
@ -61,9 +64,8 @@ export class ServiceAccountComponent implements OnInit, OnDestroy {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
this.serviceAccount$.pipe(takeUntil(this.destroy$)).subscribe((serviceAccountView) => {
|
||||||
this.serviceAccountId = params.serviceAccountId;
|
this.serviceAccountView = serviceAccountView;
|
||||||
this.organizationId = params.organizationId;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,8 +77,7 @@ export class ServiceAccountComponent implements OnInit, OnDestroy {
|
|||||||
protected openNewAccessTokenDialog() {
|
protected openNewAccessTokenDialog() {
|
||||||
AccessTokenCreateDialogComponent.openNewAccessTokenDialog(
|
AccessTokenCreateDialogComponent.openNewAccessTokenDialog(
|
||||||
this.dialogService,
|
this.dialogService,
|
||||||
this.serviceAccountId,
|
this.serviceAccountView
|
||||||
this.organizationId
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Subject, switchMap, takeUntil } from "rxjs";
|
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/shared/components/user-verification";
|
import { openUserVerificationPrompt } from "@bitwarden/web-vault/app/auth/shared/components/user-verification";
|
||||||
|
|
||||||
import { SecretsManagerPortingApiService } from "../services/sm-porting-api.service";
|
import { SecretsManagerPortingApiService } from "../services/sm-porting-api.service";
|
||||||
import { SecretsManagerPortingService } from "../services/sm-porting.service";
|
import { SecretsManagerPortingService } from "../services/sm-porting.service";
|
||||||
@ -42,7 +42,7 @@ export class SecretsManagerExportComponent implements OnInit, OnDestroy {
|
|||||||
private smPortingService: SecretsManagerPortingService,
|
private smPortingService: SecretsManagerPortingService,
|
||||||
private fileDownloadService: FileDownloadService,
|
private fileDownloadService: FileDownloadService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private modalService: ModalService,
|
private dialogService: DialogServiceAbstraction,
|
||||||
private secretsManagerApiService: SecretsManagerPortingApiService
|
private secretsManagerApiService: SecretsManagerPortingApiService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -98,12 +98,11 @@ export class SecretsManagerExportComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private verifyUser() {
|
private verifyUser() {
|
||||||
const ref = this.modalService.open(UserVerificationPromptComponent, {
|
const ref = openUserVerificationPrompt(this.dialogService, {
|
||||||
allowMultipleModals: true,
|
|
||||||
data: {
|
data: {
|
||||||
confirmDescription: "exportWarningDesc",
|
confirmDescription: "exportSecretsWarningDesc",
|
||||||
confirmButtonText: "exportVault",
|
confirmButtonText: "exportSecrets",
|
||||||
modalTitle: "confirmVaultExport",
|
modalTitle: "confirmSecretsExport",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -111,6 +110,6 @@ export class SecretsManagerExportComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref.onClosedPromise();
|
return firstValueFrom(ref.closed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
|
@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
||||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Directive } from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
import { FormBuilder, FormControl } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
|
|
||||||
import { ModalRef } from "../../components/modal/modal.ref";
|
import { ModalRef } from "../../components/modal/modal.ref";
|
||||||
import { ModalConfig } from "../../services/modal.service";
|
import { ModalConfig } from "../../services/modal.service";
|
||||||
@ -16,7 +17,12 @@ export class UserVerificationPromptComponent {
|
|||||||
confirmDescription = this.config.data.confirmDescription;
|
confirmDescription = this.config.data.confirmDescription;
|
||||||
confirmButtonText = this.config.data.confirmButtonText;
|
confirmButtonText = this.config.data.confirmButtonText;
|
||||||
modalTitle = this.config.data.modalTitle;
|
modalTitle = this.config.data.modalTitle;
|
||||||
secret = new FormControl();
|
|
||||||
|
formGroup = this.formBuilder.group({
|
||||||
|
secret: this.formBuilder.control<Verification | null>(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected invalidSecret = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private modalRef: ModalRef,
|
private modalRef: ModalRef,
|
||||||
@ -27,19 +33,31 @@ export class UserVerificationPromptComponent {
|
|||||||
private i18nService: I18nService
|
private i18nService: I18nService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async submit() {
|
get secret() {
|
||||||
try {
|
return this.formGroup.controls.secret;
|
||||||
//Incorrect secret will throw an invalid password error.
|
}
|
||||||
await this.userVerificationService.verifyUser(this.secret.value);
|
|
||||||
} catch (e) {
|
submit = async () => {
|
||||||
this.platformUtilsService.showToast(
|
this.formGroup.markAllAsTouched();
|
||||||
"error",
|
|
||||||
this.i18nService.t("error"),
|
if (this.formGroup.invalid) {
|
||||||
this.i18nService.t("invalidMasterPassword")
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modalRef.close(true);
|
try {
|
||||||
|
//Incorrect secret will throw an invalid password error.
|
||||||
|
await this.userVerificationService.verifyUser(this.secret.value);
|
||||||
|
this.invalidSecret = false;
|
||||||
|
} catch (e) {
|
||||||
|
this.invalidSecret = true;
|
||||||
|
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
close(success: boolean) {
|
||||||
|
this.modalRef.close(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Directive, OnInit } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
import { ControlValueAccessor, FormControl } from "@angular/forms";
|
import { ControlValueAccessor, FormControl, Validators } from "@angular/forms";
|
||||||
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { Verification } from "@bitwarden/common/types/verification";
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
|
|
||||||
@ -17,29 +19,65 @@ import { Verification } from "@bitwarden/common/types/verification";
|
|||||||
selector: "app-user-verification",
|
selector: "app-user-verification",
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||||
usesKeyConnector = false;
|
private _invalidSecret = false;
|
||||||
|
@Input()
|
||||||
|
get invalidSecret() {
|
||||||
|
return this._invalidSecret;
|
||||||
|
}
|
||||||
|
set invalidSecret(value: boolean) {
|
||||||
|
this._invalidSecret = value;
|
||||||
|
this.invalidSecretChange.emit(value);
|
||||||
|
|
||||||
|
// ISSUE: This is pretty hacky but unfortunately there is no way of knowing if the parent
|
||||||
|
// control has been marked as touched, see: https://github.com/angular/angular/issues/10887
|
||||||
|
// When that functionality has been added we should also look into forwarding reactive form
|
||||||
|
// controls errors so that we don't need a separate input/output `invalidSecret`.
|
||||||
|
if (value) {
|
||||||
|
this.secret.markAsTouched();
|
||||||
|
}
|
||||||
|
this.secret.updateValueAndValidity({ emitEvent: false });
|
||||||
|
}
|
||||||
|
@Output() invalidSecretChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
usesKeyConnector = true;
|
||||||
disableRequestOTP = false;
|
disableRequestOTP = false;
|
||||||
sentCode = false;
|
sentCode = false;
|
||||||
|
|
||||||
secret = new FormControl("");
|
secret = new FormControl("", [
|
||||||
|
Validators.required,
|
||||||
|
() => {
|
||||||
|
if (this.invalidSecret) {
|
||||||
|
return {
|
||||||
|
invalidSecret: {
|
||||||
|
message: this.usesKeyConnector
|
||||||
|
? this.i18nService.t("incorrectCode")
|
||||||
|
: this.i18nService.t("incorrectPassword"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
private onChange: (value: Verification) => void;
|
private onChange: (value: Verification) => void;
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService,
|
||||||
private userVerificationService: UserVerificationService
|
private userVerificationService: UserVerificationService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||||
this.processChanges(this.secret.value);
|
this.processChanges(this.secret.value);
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
this.secret.valueChanges
|
||||||
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((secret: string) => this.processChanges(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestOTP() {
|
requestOTP = async () => {
|
||||||
if (this.usesKeyConnector) {
|
if (this.usesKeyConnector) {
|
||||||
this.disableRequestOTP = true;
|
this.disableRequestOTP = true;
|
||||||
try {
|
try {
|
||||||
@ -49,7 +87,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
|||||||
this.disableRequestOTP = false;
|
this.disableRequestOTP = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
writeValue(obj: any): void {
|
writeValue(obj: any): void {
|
||||||
this.secret.setValue(obj);
|
this.secret.setValue(obj);
|
||||||
@ -72,7 +110,14 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private processChanges(secret: string) {
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processChanges(secret: string) {
|
||||||
|
this.invalidSecret = false;
|
||||||
|
|
||||||
if (this.onChange == null) {
|
if (this.onChange == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,6 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
|
|||||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
|
|
||||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
@ -48,6 +46,8 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
|
|||||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
|
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
|
||||||
|
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
|
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
|
||||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
|
@ -3,9 +3,9 @@ import { UntypedFormBuilder, Validators } from "@angular/forms";
|
|||||||
import { merge, startWith, Subject, takeUntil } from "rxjs";
|
import { merge, startWith, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
|
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { VerifyOTPRequest } from "../../auth/models/request/verify-otp.request";
|
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||||
|
|
||||||
export abstract class UserVerificationApiServiceAbstraction {
|
export abstract class UserVerificationApiServiceAbstraction {
|
||||||
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
|
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
|
@ -1,5 +1,5 @@
|
|||||||
import { SecretVerificationRequest } from "../../auth/models/request/secret-verification.request";
|
import { Verification } from "../../../types/verification";
|
||||||
import { Verification } from "../../types/verification";
|
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||||
|
|
||||||
export abstract class UserVerificationService {
|
export abstract class UserVerificationService {
|
||||||
buildRequest: <T extends SecretVerificationRequest>(
|
buildRequest: <T extends SecretVerificationRequest>(
|
@ -1,9 +1,9 @@
|
|||||||
import { ApiService } from "../../abstractions/api.service";
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { LogService } from "../../platform/abstractions/log.service";
|
import { LogService } from "../../platform/abstractions/log.service";
|
||||||
import { Verification } from "../../types/verification";
|
import { Verification } from "../../types/verification";
|
||||||
import { AccountApiService } from "../abstractions/account-api.service";
|
import { AccountApiService } from "../abstractions/account-api.service";
|
||||||
import { InternalAccountService } from "../abstractions/account.service";
|
import { InternalAccountService } from "../abstractions/account.service";
|
||||||
|
import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction";
|
||||||
|
|
||||||
export class AccountApiServiceImplementation implements AccountApiService {
|
export class AccountApiServiceImplementation implements AccountApiService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ApiService } from "../../../abstractions/api.service";
|
import { ApiService } from "../../../abstractions/api.service";
|
||||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||||
|
|
||||||
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {
|
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
|
||||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../../abstractions/userVerification/userVerification.service.abstraction";
|
|
||||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||||
import { Verification } from "../../../types/verification";
|
import { Verification } from "../../../types/verification";
|
||||||
|
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||||
|
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { VerificationType } from "../../enums/verification-type";
|
import { VerificationType } from "../../enums/verification-type";
|
||||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||||
|
67
libs/components/src/avatar/avatar.mdx
Normal file
67
libs/components/src/avatar/avatar.mdx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./avatar.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Avatar
|
||||||
|
|
||||||
|
Avatars display a unique color that helps a user visually recognize their logged in account.
|
||||||
|
|
||||||
|
A variance in color across the avatar component is important as it is used in Account Switching as a
|
||||||
|
visual indicator to recognize which of a personal or work account a user is logged into.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Size
|
||||||
|
|
||||||
|
### Large: 64px
|
||||||
|
|
||||||
|
<Story of={stories.Large} />
|
||||||
|
|
||||||
|
### Default: 48px
|
||||||
|
|
||||||
|
<Story of={stories.Default} />
|
||||||
|
|
||||||
|
### Small 28px
|
||||||
|
|
||||||
|
<Story of={stories.Small} />
|
||||||
|
|
||||||
|
## Background color
|
||||||
|
|
||||||
|
The Background color can be set 3 ways. The color is generated using the following order of
|
||||||
|
priority:
|
||||||
|
|
||||||
|
- Color
|
||||||
|
- ID
|
||||||
|
- Text, usually set to the user's Name field
|
||||||
|
|
||||||
|
<Story of={stories.ColorByText} />
|
||||||
|
Use the user 'ID' field if `Name` is not defined.
|
||||||
|
<Story of={stories.ColorByID} />
|
||||||
|
|
||||||
|
## Outline
|
||||||
|
|
||||||
|
If the avatar is displayed on one of the theme's `background` color variables or is interactive,
|
||||||
|
display the avatar with a 1 pixel `secondary-500` border to meet WCAG AA graphic contrast guidelines
|
||||||
|
for interactive elements.
|
||||||
|
|
||||||
|
<Story of={stories.Border} />
|
||||||
|
|
||||||
|
## Avatar as a button
|
||||||
|
|
||||||
|
The Avatar can be used as a button.
|
||||||
|
|
||||||
|
Typically this is only in the navigation on client apps where account switching is used and in the
|
||||||
|
web app for the account menu indicator.
|
||||||
|
|
||||||
|
When the avatar is used as a button, the following states should be used:
|
||||||
|
|
||||||
|
`TODO:` [Jira add stories](https://bitwarden.atlassian.net/browse/CL-101) for button avatars.
|
||||||
|
[See Figma](https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?type=design&node-id=9730-31746&mode=design&t=IjDIHDb6FZl6bUQW-4)
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Avatar background color should have 3.1:1 contrast with it’s background; or include the
|
||||||
|
`secondary-500` border Avatar text should have 4.5:1 contrast with the avatar background color
|
67
libs/components/src/badge/badge.mdx
Normal file
67
libs/components/src/badge/badge.mdx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./badge.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Badge
|
||||||
|
|
||||||
|
The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag
|
||||||
|
for interactive events. The Focus and Hover states only apply to badges used for interactive events.
|
||||||
|
|
||||||
|
Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the
|
||||||
|
component configurations may be reviewed and adjusted.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Styles
|
||||||
|
|
||||||
|
### Primary
|
||||||
|
|
||||||
|
The primary badge is used to indicate an active status (example: device management page) or provide
|
||||||
|
additional information (example: type of emergency access granted).
|
||||||
|
|
||||||
|
<Story of={stories.Primary} />
|
||||||
|
|
||||||
|
### Secondary
|
||||||
|
|
||||||
|
The secondary badge style is typically is a default badge style. It is often used to indicate
|
||||||
|
neutral information.
|
||||||
|
|
||||||
|
<Story of={stories.Secondary} />
|
||||||
|
|
||||||
|
### Success
|
||||||
|
|
||||||
|
The success badge is used to indicate a positive status, OR to indicate a feature requires a Premium
|
||||||
|
subscription. See [Premium Badge](?path=/docs/web-premium-badge--docs)
|
||||||
|
|
||||||
|
<Story of={stories.Success} />
|
||||||
|
|
||||||
|
### Danger
|
||||||
|
|
||||||
|
The danger badge is used to indicate a negative status.
|
||||||
|
|
||||||
|
<Story of={stories.Danger} />
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
|
||||||
|
The warning badge is used to indicate a status waiting on an additional action from the active user.
|
||||||
|
|
||||||
|
<Story of={stories.Warning} />
|
||||||
|
|
||||||
|
### Info
|
||||||
|
|
||||||
|
The info badge is used to indicate a low emphasis informative information.
|
||||||
|
|
||||||
|
<Story of={stories.Info} />
|
||||||
|
|
||||||
|
## Badges as counters
|
||||||
|
|
||||||
|
Badges can be used as part of links or buttons to provide a counter. See the
|
||||||
|
[Toggle Group](?path=/docs/component-library-toggle-group--docs) component.
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Be sure to use the correct html tag based on the purpose or function of the badge. Follow color WCAG
|
||||||
|
color contrast guidelines for small text.
|
@ -10,7 +10,7 @@ Banners are used for important communication with the user that needs to be seen
|
|||||||
little effect on the experience. Banners appear at the top of the user's screen on page load and
|
little effect on the experience. Banners appear at the top of the user's screen on page load and
|
||||||
persist across all pages a user navigates to.
|
persist across all pages a user navigates to.
|
||||||
|
|
||||||
- They should always be dismissable and never use a timeout. If a user dismisses a banner, it should
|
- They should always be dismissible and never use a timeout. If a user dismisses a banner, it should
|
||||||
not reappear during that same active session.
|
not reappear during that same active session.
|
||||||
- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their
|
- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their
|
||||||
effectiveness may decrease if too many are used.
|
effectiveness may decrease if too many are used.
|
||||||
|
54
libs/components/src/breadcrumbs/breadcrumbs.mdx
Normal file
54
libs/components/src/breadcrumbs/breadcrumbs.mdx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./breadcrumbs.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Breadcrumbs
|
||||||
|
|
||||||
|
Breadcrumbs are used to help users understand where they are in a products navigation. Typically
|
||||||
|
Bitwarden uses this component to indicate the user's current location in a set of data organized in
|
||||||
|
containers (Collections, Folders, or Projects).
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Display
|
||||||
|
|
||||||
|
Breadcrumbs display above the page title. The current page should not appear as a breadcrumb link.
|
||||||
|
See [Header with Breadcrumbs](?path=/story/web-header--with-breadcrumbs).
|
||||||
|
|
||||||
|
### Top Level
|
||||||
|
|
||||||
|
When a user is 1 level deep into a tree, the top level is displayed as a single link above the page
|
||||||
|
title.
|
||||||
|
|
||||||
|
<Story of={stories.TopLevel} />
|
||||||
|
|
||||||
|
### Second Level
|
||||||
|
|
||||||
|
When a user is 2 or more levels deep into a tree, the top level is displayed followed by an
|
||||||
|
|
||||||
|
<i class="bwi bwi-angle-right"></i> icon, and the following pages.
|
||||||
|
|
||||||
|
<Story of={stories.SecondLevel} />
|
||||||
|
|
||||||
|
### Overflow
|
||||||
|
|
||||||
|
When a user is several levels deep into a tree, the top level or 2 are displayed followed by an
|
||||||
|
|
||||||
|
<i class="bwi bwi-ellipsis-h"> </i> icon button, and then the page directly above the current page.
|
||||||
|
|
||||||
|
When the user selects the <i class="bwi bwi-ellipsis-h"></i> icon button, a menu opens displaying
|
||||||
|
the pages between the top level and the previous page.
|
||||||
|
|
||||||
|
<Story of={stories.Overflow} />
|
||||||
|
|
||||||
|
### Small screens
|
||||||
|
|
||||||
|
If a screen's width is not large enough to display the full breadcrumb path, display a link to the
|
||||||
|
previous page and an <i class="bwi bwi-angle-right"></i> icon to take the user back to the previous
|
||||||
|
page.
|
||||||
|
|
||||||
|
`TODO:` [Jira add stories](https://bitwarden.atlassian.net/browse/CL-102) for responsive screen
|
||||||
|
width/small screens
|
@ -33,31 +33,6 @@ Groups within page content, dialog footers or forms should have the `primary` ca
|
|||||||
to left. Groups in headers and navigational areas should have the `primary` call to action on the
|
to left. Groups in headers and navigational areas should have the `primary` call to action on the
|
||||||
right.
|
right.
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
Please follow these guidelines to ensure that buttons are accessible to all users.
|
|
||||||
|
|
||||||
### Color contrast
|
|
||||||
|
|
||||||
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To
|
|
||||||
use a button on a different background, double check that the color contrast is sufficient in both
|
|
||||||
the light and dark themes.
|
|
||||||
|
|
||||||
### Loading Buttons
|
|
||||||
|
|
||||||
Include an `aria-label` attribute that defaults to “loading” but can be configurable per
|
|
||||||
implementation. On click, the screen reader should announce the `aria-label`. Once the action is
|
|
||||||
compelted, use another messaging pattern to alert the user that the action is complete (example:
|
|
||||||
success toast).
|
|
||||||
|
|
||||||
### Submit and async actions
|
|
||||||
|
|
||||||
Both submit and async action buttons use a loading button state while an action is taken. If your
|
|
||||||
button is preforming a long running task in the background like a server API call, be sure to review
|
|
||||||
the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
|
|
||||||
|
|
||||||
<Story of={stories.Loading} />
|
|
||||||
|
|
||||||
## Styles
|
## Styles
|
||||||
|
|
||||||
There are 3 main styles for the button: Primary, Secondary, and Danger.
|
There are 3 main styles for the button: Primary, Secondary, and Danger.
|
||||||
@ -96,3 +71,39 @@ Typically button widths expand with their text. In some causes though buttons ma
|
|||||||
where the width is fixed and the text wraps to 2 lines if exceeding the button’s width.
|
where the width is fixed and the text wraps to 2 lines if exceeding the button’s width.
|
||||||
|
|
||||||
<Story of={stories.Block} />
|
<Story of={stories.Block} />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Please follow these guidelines to ensure that buttons are accessible to all users.
|
||||||
|
|
||||||
|
### Color contrast
|
||||||
|
|
||||||
|
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To
|
||||||
|
use a button on a different background, double check that the color contrast is sufficient in both
|
||||||
|
the light and dark themes.
|
||||||
|
|
||||||
|
### Loading Buttons
|
||||||
|
|
||||||
|
Include an `aria-label` attribute that defaults to "loading" but can be configurable per
|
||||||
|
implementation. On click, the screen reader should announce the `aria-label`. Once the action is
|
||||||
|
completed, use another messaging pattern to alert the user that the action is complete (example:
|
||||||
|
success toast).
|
||||||
|
|
||||||
|
### Submit and async actions
|
||||||
|
|
||||||
|
Both submit and async action buttons use a loading button state while an action is taken. If your
|
||||||
|
button is preforming a long running task in the background like a server API call, be sure to review
|
||||||
|
the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
|
||||||
|
|
||||||
|
<Story of={stories.Loading} />
|
||||||
|
|
||||||
|
### appA11yTitle
|
||||||
|
|
||||||
|
`appA11yTitle` is a directive that auto assigns the same string to the `title` and `aria-label`
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
When a button uses accessible content (e.i. actual text), DO NOT include this as it adds redundant
|
||||||
|
content for someone using assistive technology.
|
||||||
|
|
||||||
|
`appA11yTitle` should only be used if the element it applies to does not include accessible text,
|
||||||
|
e.i. an icon.
|
||||||
|
66
libs/components/src/callout/callout.mdx
Normal file
66
libs/components/src/callout/callout.mdx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./callout.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Callouts
|
||||||
|
|
||||||
|
Callouts are used to communicate important information to the user. Callouts should be used
|
||||||
|
sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in
|
||||||
|
the same location.
|
||||||
|
|
||||||
|
## Styles
|
||||||
|
|
||||||
|
Icons should remain consistent across these types. Do not change the icon without consulting a
|
||||||
|
designer. Use the following guidelines to help choose the correct type of callout.
|
||||||
|
|
||||||
|
### Success
|
||||||
|
|
||||||
|
Use the success callout to communicate a positive messaging to the user.
|
||||||
|
|
||||||
|
**Example:** a positive report results shows a success callout.
|
||||||
|
|
||||||
|
The success callout may also be used for the information related to a premium membership. In this
|
||||||
|
case, replace the icon with <i class="bwi bwi-star" title="bwi-star" aria-label="bwi-star"></i>
|
||||||
|
|
||||||
|
<Story of={stories.Success} />
|
||||||
|
|
||||||
|
### Info
|
||||||
|
|
||||||
|
Use an info callout to call attention to important information the user should be aware of, but has
|
||||||
|
low risk of the user receiving and unintended or irreversible results if they do not read the
|
||||||
|
information.
|
||||||
|
|
||||||
|
**Example:** in the Domain Claiming modal, an info callout is used to tell the user the domain will
|
||||||
|
automatically be checked.
|
||||||
|
|
||||||
|
<Story of={stories.Info} />
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
|
||||||
|
Use a warning callout if the user is about to perform an action that may have unintended or
|
||||||
|
irreversible results.
|
||||||
|
|
||||||
|
**Example:** the warning callout is used before the change master password and encryption key form
|
||||||
|
to alert the user that they will be logged out.
|
||||||
|
|
||||||
|
<Story of={stories.Warning} />
|
||||||
|
|
||||||
|
### Danger
|
||||||
|
|
||||||
|
Use the danger callout to communicate an action the user is about to take is dangerous and typically
|
||||||
|
not reversible.
|
||||||
|
|
||||||
|
The danger callout can also be used to alert the user of an error or errors, such as a server side
|
||||||
|
errors after form submit or failed communication request.
|
||||||
|
|
||||||
|
<Story of={stories.Danger} />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Use the `role=”alert”` only if the callout is appearing on a page after the user takes an action. If
|
||||||
|
the content is static, do not use the alert role. This will cause a screen reader to announce the
|
||||||
|
callout content on page load.
|
||||||
|
|
||||||
|
Ensure the title's color contrast remains WCAG compliant with the callout's background.
|
34
libs/components/src/color-password/color-password.mdx
Normal file
34
libs/components/src/color-password/color-password.mdx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./color-password.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Color password
|
||||||
|
|
||||||
|
The color password is used primarily in the Generator pages and in the Login type form. It includes
|
||||||
|
the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as
|
||||||
|
`danger`.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Password Count
|
||||||
|
|
||||||
|
The password count option is used in the Login type form. It is used to highlight each character's
|
||||||
|
position in the password string.
|
||||||
|
|
||||||
|
<Story of={stories.ColorPasswordCount} />
|
||||||
|
|
||||||
|
## Wrapped Password
|
||||||
|
|
||||||
|
When the password length is longer than the container's width, it should wrap as shown below.
|
||||||
|
|
||||||
|
<Story of={stories.WrappedColorPassword} />
|
||||||
|
|
||||||
|
<Story of={stories.WrappedColorPasswordCount} />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
The colors used in the colored password should maintain WCAG compliant contrast with theme
|
||||||
|
`background` and `background-alt` colors.
|
73
libs/components/src/dialog/dialog/dialog.mdx
Normal file
73
libs/components/src/dialog/dialog/dialog.mdx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./dialog.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Dialog
|
||||||
|
|
||||||
|
Dialogs are used throughout the app to help the user focus on a specific action. Use this dialog
|
||||||
|
component when content exceeds 384px width or there are a high number of interactive elements
|
||||||
|
needed. **Example:** The web app's edit vault item form dialog
|
||||||
|
|
||||||
|
For alerts or simple confirmation actions, like speedbumps, use the
|
||||||
|
[Simple Dialog](?path=/docs/component-library-dialogs-simple-dialog--docs).
|
||||||
|
|
||||||
|
Dialogs's should be used sparingly as they do call extra attention to themselves and can be
|
||||||
|
interruptive if overused.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Size
|
||||||
|
|
||||||
|
There are 3 main dialog sizes:
|
||||||
|
|
||||||
|
### Large
|
||||||
|
|
||||||
|
Use the large size for dialogs that have many interactive elements or tabbed content.
|
||||||
|
|
||||||
|
**Tailwind styling:**
|
||||||
|
|
||||||
|
`max-w-3xl` 48rem
|
||||||
|
|
||||||
|
<Story of={stories.Large} />
|
||||||
|
|
||||||
|
### Default
|
||||||
|
|
||||||
|
Use the Default size for most dialogs that require some content and a few interactive elements.
|
||||||
|
**Example:** master password confirmation dialog
|
||||||
|
|
||||||
|
**Tailwind styling:**
|
||||||
|
|
||||||
|
`max-w-xl` 36rem
|
||||||
|
|
||||||
|
<Story of={stories.Default} />
|
||||||
|
|
||||||
|
### Small
|
||||||
|
|
||||||
|
**Tailwind styling:**
|
||||||
|
|
||||||
|
`max-w-sm` 24rem
|
||||||
|
|
||||||
|
<Story of={stories.Small} />
|
||||||
|
|
||||||
|
## Long Title
|
||||||
|
|
||||||
|
If a dialog's title is too long to fully display. It should be truncated and on hover shown in a
|
||||||
|
tooltip.
|
||||||
|
|
||||||
|
<Story of={stories.LongTitle} />
|
||||||
|
|
||||||
|
## Loading
|
||||||
|
|
||||||
|
Similar to a page loading state, a Dialog that takes more than a few seconds to load should use a
|
||||||
|
loading state.
|
||||||
|
|
||||||
|
<Story of={stories.Loading} />
|
||||||
|
|
||||||
|
## Tab Content
|
||||||
|
|
||||||
|
Use tabs to separate related content within a dialog.
|
||||||
|
|
||||||
|
<Story of={stories.TabContent} />
|
56
libs/components/src/dialog/dialogs.mdx
Normal file
56
libs/components/src/dialog/dialogs.mdx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
<Meta title="Component Library/Dialogs" />
|
||||||
|
|
||||||
|
# Dialog
|
||||||
|
|
||||||
|
Dialogs are used throughout the app to help the user focus on a specific action.
|
||||||
|
|
||||||
|
Use the main [Dialog Component](?path=/docs/component-library-dialogs-dialog--docs). when content
|
||||||
|
exceeds 384px width or there are a high number of interactive elements needed. **Example:** The web
|
||||||
|
app's edit vault item form dialog
|
||||||
|
|
||||||
|
For alerts or simple confirmation actions, like speedbumps, use the
|
||||||
|
[Simple Dialog](?path=/docs/component-library-dialogs-simple-dialog--docs).
|
||||||
|
|
||||||
|
Dialogs's should be used sparingly as they do call extra attention to themselves and can be
|
||||||
|
interruptive if overused.
|
||||||
|
|
||||||
|
## Placement
|
||||||
|
|
||||||
|
Dialogs should be centered vertically and horizontally on screen. Dialogs height should expand to
|
||||||
|
fit its content until there are 2rems of margin on the top/bottom of the dialog; in this case, the
|
||||||
|
dialog should become scrollable.
|
||||||
|
|
||||||
|
A backdrop should be used to hide the content below the dialog. Use `#000000` with `30% opacity`.
|
||||||
|
|
||||||
|
<Story id="component-library-dialogs-service--default" />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
### Component behavior
|
||||||
|
|
||||||
|
- Dialog include `role="dialog"`
|
||||||
|
- The Dialog title is an `<h1>`
|
||||||
|
- A user should not be able to tab focus outside of the Dialog until it has been closed.
|
||||||
|
- Clicking outside the dialog or clicking escape should close the dialog (this prevents a keyboard
|
||||||
|
trap)
|
||||||
|
|
||||||
|
### Required per implementation
|
||||||
|
|
||||||
|
The triggering button should indicate to assistive technology that additional content will open or
|
||||||
|
appear when the trigger is selected. Consider using `aria-haspopup="true"`
|
||||||
|
|
||||||
|
Dialog title should be announced by a screen reader when launched. Consider using `aria-labelledby`
|
||||||
|
or `aria-label`
|
||||||
|
|
||||||
|
When opened, focus should follow the visual order of the popover’s focusable content. Typically
|
||||||
|
focus is moved to the close button, but it is acceptable to move focus to the first interactive
|
||||||
|
element after close since a user may not want to close the dialog immediately if there are
|
||||||
|
additional interactive elements. See
|
||||||
|
[WCAG Focus Order success criteria](https://www.w3.org/WAI/WCAG21/Understanding/focus-order.html)
|
||||||
|
|
||||||
|
Once closed, focus should remain on the the element which triggered the Dialog.
|
||||||
|
|
||||||
|
**Note:** If a Simple Dialog is triggered from a main Dialog, be sure to make sure focus is moved to
|
||||||
|
the Simple Dialog.
|
47
libs/components/src/dialog/simple-dialog/simple-dialog.mdx
Normal file
47
libs/components/src/dialog/simple-dialog/simple-dialog.mdx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./simple-dialog.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Simple Dialogs
|
||||||
|
|
||||||
|
Simple Dialogs are used throughout the app for simple alert or confirmation actions such as
|
||||||
|
speedbumps.
|
||||||
|
|
||||||
|
For dialogs with a high number of interactive elements such as a form or content exceeding 384px,
|
||||||
|
use the [Dialog component](?path=/docs/component-library-dialogs-dialog--docs).
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Configurable Simple Dialog
|
||||||
|
|
||||||
|
The Simple Dialog contains the following configuration points:
|
||||||
|
|
||||||
|
- `title`: string
|
||||||
|
- `content`: string
|
||||||
|
- `type`: SimpleDialogType
|
||||||
|
- `icon`: string – if empty, infer from type
|
||||||
|
- `acceptButtonText`: string – if empty, default to "Yes"
|
||||||
|
- `cancelButtonText`: string – if empty, default to "No", unless acceptButtonText is overridden, in
|
||||||
|
which case default to "Cancel"
|
||||||
|
|
||||||
|
To increase consistency, the simple dialog service supports some automation for setting the `icon`
|
||||||
|
and `color` based on the defined type. See the following for how properties will be configured when
|
||||||
|
the simple dialog's type is specified.
|
||||||
|
|
||||||
|
| type | icon name | icon | color |
|
||||||
|
| ------- | ------------------------ | -------------------------------------------- | ----------- |
|
||||||
|
| primary | bwi-business | <i class="bwi bwi-business"></i> | primary-500 |
|
||||||
|
| success | bwi-star | <i class="bwi bwi-star"></i> | success-500 |
|
||||||
|
| info | bwi-info-circle | <i class="bwi bwi-info-circle"></i> | info-500 |
|
||||||
|
| warning | bwi-exclamation-triangle | <i class="bwi bwi-exclamation-triangle"></i> | warning-500 |
|
||||||
|
| danger | bwi-error | <i class="bwi bwi-error"></i> | danger-500 |
|
||||||
|
|
||||||
|
## Scrolling Content
|
||||||
|
|
||||||
|
Simple dialogs can support scrolling content if necessary, but typically with larger quantities of
|
||||||
|
content a [Dialog component](?path=/docs/component-library-dialogs-dialog--docs).
|
||||||
|
|
||||||
|
<Story of={stories.ScrollingContent} />
|
@ -1,8 +1,11 @@
|
|||||||
|
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||||
import {
|
import {
|
||||||
AfterContentChecked,
|
AfterContentChecked,
|
||||||
Component,
|
Component,
|
||||||
ContentChild,
|
ContentChild,
|
||||||
ContentChildren,
|
ContentChildren,
|
||||||
|
HostBinding,
|
||||||
|
Input,
|
||||||
QueryList,
|
QueryList,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
@ -17,9 +20,6 @@ import { BitSuffixDirective } from "./suffix.directive";
|
|||||||
@Component({
|
@Component({
|
||||||
selector: "bit-form-field",
|
selector: "bit-form-field",
|
||||||
templateUrl: "./form-field.component.html",
|
templateUrl: "./form-field.component.html",
|
||||||
host: {
|
|
||||||
class: "tw-mb-6 tw-block",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export class BitFormFieldComponent implements AfterContentChecked {
|
export class BitFormFieldComponent implements AfterContentChecked {
|
||||||
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
|
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
|
||||||
@ -30,6 +30,19 @@ export class BitFormFieldComponent implements AfterContentChecked {
|
|||||||
@ContentChildren(BitPrefixDirective) prefixChildren: QueryList<BitPrefixDirective>;
|
@ContentChildren(BitPrefixDirective) prefixChildren: QueryList<BitPrefixDirective>;
|
||||||
@ContentChildren(BitSuffixDirective) suffixChildren: QueryList<BitSuffixDirective>;
|
@ContentChildren(BitSuffixDirective) suffixChildren: QueryList<BitSuffixDirective>;
|
||||||
|
|
||||||
|
private _disableMargin = false;
|
||||||
|
@Input() set disableMargin(value: boolean | "") {
|
||||||
|
this._disableMargin = coerceBooleanProperty(value);
|
||||||
|
}
|
||||||
|
get disableMargin() {
|
||||||
|
return this._disableMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding("class")
|
||||||
|
get classList() {
|
||||||
|
return ["tw-block"].concat(this.disableMargin ? [] : ["tw-mb-6"]);
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterContentChecked(): void {
|
ngAfterContentChecked(): void {
|
||||||
if (this.error) {
|
if (this.error) {
|
||||||
this.input.ariaDescribedBy = this.error.id;
|
this.input.ariaDescribedBy = this.error.id;
|
||||||
|
178
libs/components/src/form/forms.mdx
Normal file
178
libs/components/src/form/forms.mdx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
<Meta title="Component Library/Form" />
|
||||||
|
|
||||||
|
# Forms
|
||||||
|
|
||||||
|
Component Library forms should always be built using [Angular Reactive Forms][reactive]. Please read
|
||||||
|
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
||||||
|
always use the native `form` element and bind a `formGroup`.
|
||||||
|
|
||||||
|
<Story id="component-library-form--full-example" />
|
||||||
|
|
||||||
|
<Source id="component-library-form--full-example" />
|
||||||
|
|
||||||
|
## Form spacing and sections
|
||||||
|
|
||||||
|
Forms consists of 1 or more inputs, and ends with 1 or 2 buttons.
|
||||||
|
|
||||||
|
If there are many inputs in a form, they should should be organized into sections as content
|
||||||
|
relates. **Example:** Item type form
|
||||||
|
|
||||||
|
Each input within a section should follow the following spacing guidelines (see
|
||||||
|
[Tailwind CSS spacing documentation](https://tailwindcss.com/docs/customizing-spacing)):
|
||||||
|
|
||||||
|
- 1.5rem of vertical spacing between form elements: `mb-6`
|
||||||
|
- 1.5rem of horizontal spacing between form elements: `mr-6`
|
||||||
|
- 3rem of vertical spacing below a form section: `mb-12`
|
||||||
|
- 1rem of vertical spacing between a form group divider and the group's title; so title tag has:
|
||||||
|
`my-4`
|
||||||
|
- Form section titles should be styled using `text-lg`
|
||||||
|
- Each form sections may have a single column, double or triple column layout. No form should have
|
||||||
|
more than 3 columns. Do NOT use different column layouts within the same form section. Choose the
|
||||||
|
best layout based on the number of fields and type of fields included.
|
||||||
|
|
||||||
|
## Input Types
|
||||||
|
|
||||||
|
### Field
|
||||||
|
|
||||||
|
A form field is the most common input in a form. It consists of a label, control and an optional
|
||||||
|
hint.
|
||||||
|
|
||||||
|
The styling of form fields applies to all field types: `text`, `number`, `select`, `text-area`,
|
||||||
|
`date`, etc.
|
||||||
|
|
||||||
|
Be sure to use an appropriate type attribute on fields when defining new field components (e.g.
|
||||||
|
`email` for email address or `number` for numerical information) to take advantage of newer input
|
||||||
|
controls like email verification, number selection, and more.
|
||||||
|
|
||||||
|
#### Default with required attribute
|
||||||
|
|
||||||
|
<Story id="component-library-form-field--default" />
|
||||||
|
|
||||||
|
#### Password Toggle
|
||||||
|
|
||||||
|
<Story id="component-library-form-password-toggle--default" />
|
||||||
|
|
||||||
|
#### Search
|
||||||
|
|
||||||
|
<Story id="component-library-form-search--default" />
|
||||||
|
|
||||||
|
### Selects
|
||||||
|
|
||||||
|
#### Searchable single select (default)
|
||||||
|
|
||||||
|
<Story id="component-library-form-select--default" />
|
||||||
|
|
||||||
|
#### Multi-select
|
||||||
|
|
||||||
|
<Story id="component-library-form-multi-select--members" />
|
||||||
|
|
||||||
|
### Radio group
|
||||||
|
|
||||||
|
Radio buttons should always be in radio groups.
|
||||||
|
|
||||||
|
Radio groups are form fields that consists of a main label and multiple radio buttons. Each radio
|
||||||
|
button consists of a label and a radio input.
|
||||||
|
|
||||||
|
The full form control + label should be selectable to allow the user a larger click target.
|
||||||
|
|
||||||
|
Radio groups should always have a default selected value.
|
||||||
|
|
||||||
|
Radio groups may optionally include extra helper text below each radio button.
|
||||||
|
|
||||||
|
If a radio group has more than 4 options and the options do not need helper text, a
|
||||||
|
[select menu](?path=/docs/component-library-form-multi-select--docs) should be used instead. Avoid
|
||||||
|
using a radio group for more than 5 options even if the options require additional explanation text.
|
||||||
|
|
||||||
|
`TODO: extend the select component to support a dropdown menu with descriptions below each option`
|
||||||
|
|
||||||
|
#### Block
|
||||||
|
|
||||||
|
<Story id="component-library-form-radio-button--block" />
|
||||||
|
|
||||||
|
#### Inline
|
||||||
|
|
||||||
|
<Story id="component-library-form-radio-button--inline" />
|
||||||
|
|
||||||
|
[reactive]: https://angular.io/guide/reactive-forms
|
||||||
|
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||||
|
|
||||||
|
### Checkbox
|
||||||
|
|
||||||
|
The checkbox input is used to toggle an action on/off.
|
||||||
|
|
||||||
|
Checkboxes can be displayed on their own or in a group (select multiple form question). When
|
||||||
|
displayed in a group, include an input Label and any associated required/validation logic for the
|
||||||
|
field.
|
||||||
|
|
||||||
|
Unlike radio groups, checkbox groups are not required to have a default selected value.
|
||||||
|
|
||||||
|
Checkbox groups can include extra explanation text below each radio button or just the checkbox
|
||||||
|
button itself.
|
||||||
|
|
||||||
|
If a checkbox group has more than 4 options a
|
||||||
|
[multi-select components](?path=/docs/component-library-form-multi-select--docs) should be used.
|
||||||
|
|
||||||
|
#### Single checkbox
|
||||||
|
|
||||||
|
<Story id="component-library-form-checkbox--default" />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
### Required Fields
|
||||||
|
|
||||||
|
- Use "(required)" in the label of each required form field styled the same as the field's helper
|
||||||
|
text (`.muted-text`).
|
||||||
|
- If whether or not a form field is required depends on another field, add this to the field's
|
||||||
|
helper text.
|
||||||
|
- **Example:** "Billing Email is required if owned by a business".
|
||||||
|
|
||||||
|
### Form Field Errors
|
||||||
|
|
||||||
|
- When a resting field is filled out, validation is triggered when the user de-focuses the field
|
||||||
|
(`onblur`). If the control is invalid, assistive technology should announce the error (consider
|
||||||
|
using `role="alert"` or an `aria-live="assertive"`).
|
||||||
|
- Validation should not be triggered if the control is left untouched; this allows a user of
|
||||||
|
assistive technology to read the entire form if they wish without triggering validation that could
|
||||||
|
interrupt them. - **TODO:** research how we might implement this behavior; as previous research
|
||||||
|
has shown Angular may not allow both validation when `dirty` `onblur` AND validation on Submit
|
||||||
|
which is a requirement
|
||||||
|
- A form control with an error should change to the error UI and the error text should be displayed
|
||||||
|
below the element and be associated to their respective fields (consider using `aria-describedby`)
|
||||||
|
- When a field with an error is focused, assistive technology should announce the label and
|
||||||
|
elements' invalid state and then the error text.
|
||||||
|
- **Example:** "URL required, Error, URL format is not acceptable."
|
||||||
|
- Once the user has re-focused the field, and starts typing. The error will disappear. Validation
|
||||||
|
should not occur when typing in most cases. Once th user unfocuses the field, validation triggers
|
||||||
|
again.
|
||||||
|
|
||||||
|
### Validation on Submit
|
||||||
|
|
||||||
|
- Validation must also occur on submit. A user may select the submit button directly without
|
||||||
|
changing focus from a form input. Or a user may disable their browser's javascript which is what
|
||||||
|
supports the inline onblur validation. Finally, there may be a server side error that can only be
|
||||||
|
checked on submit.
|
||||||
|
- On submit, a summary error should appear near the submit button or at the top of the form alerting
|
||||||
|
the user of what errors need to be addressed. This summary should be read out by assistive
|
||||||
|
technology after submit regardless of whether or not it was already on screen.
|
||||||
|
- Any invalid form control will display an inline error following the field's helper text (or in
|
||||||
|
place of)
|
||||||
|
- If submit is successful, use a success toast to alert the user of the successful action.
|
||||||
|
- For any server side errors, the Danger toast may still be used. Be sure to adjust the toast's
|
||||||
|
timeout to follow the 6 second
|
||||||
|
|
||||||
|
* 1 second for each additional 120 words rule.
|
||||||
|
|
||||||
|
### Helper Text
|
||||||
|
|
||||||
|
Similar to a field error, helper text should be associated to a field using `aria-describedby`. This
|
||||||
|
allows assistive technology to read out the instructional text and field requirements in addition to
|
||||||
|
the field’s label.
|
||||||
|
|
||||||
|
### Visual style
|
||||||
|
|
||||||
|
- All field inputs are interactive elements that must follow the WCAG graphic contrast guidelines.
|
||||||
|
Maintain a ratio of 3:1 with the form's background.
|
||||||
|
- Error styling should not rely only on using the `danger-500`color change. Use
|
||||||
|
<i class="bwi bwi-error"></i> as a prefix to highlight the text as error text versus helper
|
55
libs/components/src/icon-button/icon-button.mdx
Normal file
55
libs/components/src/icon-button/icon-button.mdx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./icon-button.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Icon Button
|
||||||
|
|
||||||
|
Icon buttons are used when no text accompanies the button. It consists of an icon that may be
|
||||||
|
updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`.
|
||||||
|
|
||||||
|
There are 3 common styles for button main, contrast, and danger. The main style is used on the
|
||||||
|
theme’s main `background`; and the contrast style is used on a theme’s colored or contrasting
|
||||||
|
backgrounds; danger is used for “trash” actions throughout the experience. The other styles are used
|
||||||
|
sparingly.
|
||||||
|
|
||||||
|
The most common use of the icon button is in the banner, toast, and modal components as a close
|
||||||
|
button. It can also be found in tables as the 3 dot option menu, or on navigation list items when
|
||||||
|
there are options that need to be collapsed into a menu.
|
||||||
|
|
||||||
|
Similar to the main button components, spacing between icon buttons should be .5rem.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
**Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus
|
||||||
|
indicator does not meet WCAG graphic contrast guidelines.
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
There are 2 sizes for the icon button: `small` and `default`.
|
||||||
|
|
||||||
|
Default is typically used for most instances. Small is used if the implementation needs a variant
|
||||||
|
with less padding around the icon, such as in the navigation component.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Icon buttons can be found in other components such as: the
|
||||||
|
[banner](?path=/docs/component-library-banner--docs)
|
||||||
|
[dialog](?path=/docs/component-library-dialogs--docs), and
|
||||||
|
[table](?path=/docs/component-library-table--docs).
|
||||||
|
|
||||||
|
<Story id="component-library-banner--premium" />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Follow guidelines outlined in the [Button docs](?path=/docs/component-library-button--doc)
|
||||||
|
|
||||||
|
Always use the `appA11yTitle` directive set to a string that describes the action of the
|
||||||
|
icon-button. This will auto assign the same string to the `title` and `aria-label` attributes.
|
||||||
|
|
||||||
|
`aria-label` allows assistive technology to announce the action the button takes to the users.
|
||||||
|
|
||||||
|
`title` attribute provides a user with the browser tool tip if they do not understand what the icon
|
||||||
|
is indicating.
|
@ -1,6 +1,6 @@
|
|||||||
import { Meta } from "@storybook/addon-docs";
|
import { Meta } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Component Library/Form/Input" />
|
<Meta title="Component Library/Form/Input Directive" />
|
||||||
|
|
||||||
# Input
|
# Input
|
||||||
|
|
39
libs/components/src/link/link.mdx
Normal file
39
libs/components/src/link/link.mdx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./link.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Link / Text button
|
||||||
|
|
||||||
|
Text Links and Buttons use the `primary-500` color and can use either the `<a>` or `<button>` tags.
|
||||||
|
Choose which based on the action the button takes:
|
||||||
|
|
||||||
|
- if navigating to a new page, use a `<a>`
|
||||||
|
- if taking an action on the current page use a `<button>`
|
||||||
|
|
||||||
|
Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions
|
||||||
|
or show/hide additional form options.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
There are 2 sizes for the component: default and small.
|
||||||
|
|
||||||
|
Default uses `text-base` and small uses `text-xs`
|
||||||
|
|
||||||
|
## With icons
|
||||||
|
|
||||||
|
Text Links/buttons can have icons on left or the right.
|
||||||
|
|
||||||
|
To indicate a new or add action, the <i class="bwi bwi-plus-circle"></i> icon on is used on the
|
||||||
|
left.
|
||||||
|
|
||||||
|
An angle icon, <i class="bwi bwi-angle-right"></i>, is used on the left to indicate an expand to
|
||||||
|
show/hide additional content.
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Make sure to only use the Link on backgrounds that maintain the WCAG color contrast ratios.
|
21
libs/components/src/menu/menu.mdx
Normal file
21
libs/components/src/menu/menu.mdx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./menu.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Menu
|
||||||
|
|
||||||
|
Menus are used to help organize related options. Menus are most often used for item options in
|
||||||
|
tables.
|
||||||
|
|
||||||
|
<Story of={stories.ClosedMenu} />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Follow WCAG AA best practices. Example: Insure the triggering element has `aria-haspopup="true"`
|
||||||
|
prior to being clicked and `aria-expanded="true"` after the user clicks the element.
|
||||||
|
|
||||||
|
User should be able to navigate the opened menu via the up and down arrow keys and close the menu
|
||||||
|
using the escape key or clicking out of the menu.
|
48
libs/components/src/progress/progress.mdx
Normal file
48
libs/components/src/progress/progress.mdx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./progress.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Progress
|
||||||
|
|
||||||
|
Progress indicators may be used to visually indicate progress or to visually measure some other
|
||||||
|
value, such as a password strength indicator.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Labels
|
||||||
|
|
||||||
|
Always display a label to provide a text based description of what the indicator is measuring. This
|
||||||
|
allows those who may not be familiar with the pattern to be able to read and digest the information.
|
||||||
|
It also allows assistive technology to accurately describe the indicator to those who may be unable
|
||||||
|
to see part or all of the indicator.
|
||||||
|
|
||||||
|
<Story of={stories.Full} />
|
||||||
|
|
||||||
|
## Text label
|
||||||
|
|
||||||
|
When measuring something other than progress, such as password strength, update the label to fit the
|
||||||
|
context of the implementation.
|
||||||
|
|
||||||
|
<Story of={stories.CustomText} />
|
||||||
|
|
||||||
|
### Strength indicator styles
|
||||||
|
|
||||||
|
For a strength indicator use the following styles for fill:
|
||||||
|
|
||||||
|
- **Weak:** `danger-500`
|
||||||
|
- **Weak2:** `warning-500`
|
||||||
|
- **Good:** `primary-500`
|
||||||
|
- **Strong:** `success-500`
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Be sure to include the proper `aria-valuemin`, `aria-valuemax`, and `aria-valuenow` attributes. An
|
||||||
|
a`ria-valuetext` should also be configurable to include the text a screen reader should read to the
|
||||||
|
user.
|
||||||
|
|
||||||
|
In the case of a password strength indicator; `aria-describedby` is used on the password field and
|
||||||
|
points to the `id` of the progress bar. This results in the screen reader reading the password
|
||||||
|
strength to the user after they finish typing.
|
@ -1,51 +0,0 @@
|
|||||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
|
||||||
|
|
||||||
<Meta title="Component Library/Form" />
|
|
||||||
|
|
||||||
# Forms
|
|
||||||
|
|
||||||
Examples and usage guidelines for form control styles, layout options, and custom components for
|
|
||||||
creating a wide variety of forms.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Component Library forms should always be built using [Angular Reactive Forms][reactive]. Please read
|
|
||||||
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
|
||||||
always use the native `form` element and bind a `formGroup`.
|
|
||||||
|
|
||||||
Forms consists of one or multiple sections, and ends with one or multiple buttons.
|
|
||||||
|
|
||||||
### Form Field
|
|
||||||
|
|
||||||
A form field is the most common section in a form. It consists of a label, control and a optional
|
|
||||||
hint.
|
|
||||||
|
|
||||||
<Story id="component-library-form-field--default" />
|
|
||||||
|
|
||||||
<Source id="component-library-form-field--default" />
|
|
||||||
|
|
||||||
### Radio group
|
|
||||||
|
|
||||||
A radio group is a form field that consists of a main label and multiple radio groups. Each group
|
|
||||||
consists of a label and a radio input.
|
|
||||||
|
|
||||||
#### Block
|
|
||||||
|
|
||||||
<Story id="component-library-form-radio-button--block" />
|
|
||||||
|
|
||||||
<Source id="component-library-form-radio-button--block" />
|
|
||||||
|
|
||||||
#### Inline
|
|
||||||
|
|
||||||
<Story id="component-library-form-radio-button--inline" />
|
|
||||||
|
|
||||||
<Source id="component-library-form-radio-button--inline" />
|
|
||||||
|
|
||||||
## Full Example
|
|
||||||
|
|
||||||
<Story id="component-library-form--full-example" />
|
|
||||||
|
|
||||||
<Source id="component-library-form--full-example" />
|
|
||||||
|
|
||||||
[reactive]: https://angular.io/guide/reactive-forms
|
|
||||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
|
@ -26,12 +26,18 @@ The UI component consists of a couple of elements.
|
|||||||
### Guidelines
|
### Guidelines
|
||||||
|
|
||||||
- Always include a row or column header with your table; this allows screen readers to better
|
- Always include a row or column header with your table; this allows screen readers to better
|
||||||
contextualize the data
|
contextualize the data.
|
||||||
- Avoid spanning data across cells.
|
- Avoid spanning data across cells.
|
||||||
- Be sure to make repeating actions unique by associating them with the object they relate to.
|
- Be sure to make repeating actions unique by associating them with the object they relate to.
|
||||||
Example: if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
Example: if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
||||||
Netflix” for an edit option for a Netflix item.
|
Netflix” for an edit option for a Netflix item.
|
||||||
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
|
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
|
||||||
|
- For bulk menu options, display a 3 dot menu in the header. When multiple items are selected, the
|
||||||
|
bulk menu will contain actions that can be completed in bulk for the selected items.
|
||||||
|
- Note, this may result in some menu actions being hidden at times if they are not applicable to
|
||||||
|
the selected item
|
||||||
|
- Clicking on a row’s 3 dot menu will continue to result in actions specific to just that row's
|
||||||
|
item
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@ -147,3 +153,12 @@ specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*c
|
|||||||
</bit-table>
|
</bit-table>
|
||||||
</cdk-virtual-scroll-viewport>
|
</cdk-virtual-scroll-viewport>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- Always include a row or column header with your table; this allows assistive technology to better
|
||||||
|
contextualize the data
|
||||||
|
- Avoid spanning data across cells
|
||||||
|
- Be sure to make repeating actions unique by associating them with the object they relate to
|
||||||
|
- **Example:** if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
||||||
|
Netflix” for an edit option for a Netflix item.
|
||||||
|
40
libs/components/src/tabs/tabs.mdx
Normal file
40
libs/components/src/tabs/tabs.mdx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./tabs.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Tabs
|
||||||
|
|
||||||
|
The tab navigation and content tabs share the same styling. The tab navigation uses links to
|
||||||
|
navigate between pages, whereas the tab list uses `<buttons>` to toggle content on a single page.
|
||||||
|
|
||||||
|
Tabs should be displayed on the `background-alt` color, with their content area set to background
|
||||||
|
and 1rem of padding on the left and right.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Content Tabs
|
||||||
|
|
||||||
|
<Story of={stories.ContentTabs} />
|
||||||
|
|
||||||
|
## Navigation Tabs
|
||||||
|
|
||||||
|
<Story of={stories.NavigationTabs} />
|
||||||
|
|
||||||
|
## Content tabs in dialogs
|
||||||
|
|
||||||
|
Tabs can be used in dialogs to separate related content.
|
||||||
|
|
||||||
|
<Story id="component-library-dialogs-dialog--tab-content" />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
**Navigation tabs** are implemented using the `<nav>` element and `<a>` for each tab.
|
||||||
|
|
||||||
|
**Content tabs** should be implemented with the `tablist` role and:
|
||||||
|
|
||||||
|
- Use `<button>` for the tab elements
|
||||||
|
- Set `aria-selected` for each tab; “true” for selected and “false” for unselected
|
||||||
|
- See WCAG for more: https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html
|
36
libs/components/src/toggle-group/toggle-group.mdx
Normal file
36
libs/components/src/toggle-group/toggle-group.mdx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./toggle-group.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Toggle Group
|
||||||
|
|
||||||
|
Toggle groups are used for quick filters for a data set. **Example:** the Member’s page of the Admin
|
||||||
|
Console uses a toggle group to filter members by their organization status: all, invited, needs
|
||||||
|
confirmation, revoked.
|
||||||
|
|
||||||
|
Toggle groups function as radio buttons and a radio group under the hood.
|
||||||
|
|
||||||
|
A button in a toggle group can have a badge counter added to show the number of items existing
|
||||||
|
within that filter.
|
||||||
|
|
||||||
|
For focus states, use `focus-visible`.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- Follow contrast rules for the main button styles.
|
||||||
|
- Focus:
|
||||||
|
- Implement as a radio group with button styling and a context label (context label can be screen
|
||||||
|
reader only depending on use case).
|
||||||
|
- Since only 1 button can be selected at a time to filter the toggle group acts similarly to a
|
||||||
|
radio group.
|
||||||
|
- When moving focus to a button group, the focus should always move to the selected button. The
|
||||||
|
screen reader should then announce the button group: example “[context label], [button content]
|
||||||
|
selected, of [# of buttons]”), the number of buttons and the currently selected button. The user
|
||||||
|
may navigate the options then via left/right arrow keys.
|
||||||
|
|
||||||
|
See WCAG for more: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
|
Loading…
Reference in New Issue
Block a user