├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── crowdin-pull.yml │ ├── enforce-labels.yml │ ├── qa-deploy.yml │ ├── release.yml │ ├── version-bump.yml │ └── workflow-linter.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.txt ├── LICENSE_BITWARDEN.txt ├── LICENSE_GPL.txt ├── README.md ├── SECURITY.md ├── bitwarden_license ├── README.md ├── src │ └── app │ │ ├── app-routing.module.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── main.ts │ │ ├── organizations │ │ ├── components │ │ │ ├── base-cva.component.ts │ │ │ ├── input-checkbox.component.html │ │ │ ├── input-checkbox.component.ts │ │ │ ├── input-text-readonly.component.html │ │ │ ├── input-text-readonly.component.ts │ │ │ ├── input-text.component.html │ │ │ ├── input-text.component.ts │ │ │ ├── select.component.html │ │ │ └── select.component.ts │ │ ├── manage │ │ │ ├── sso.component.html │ │ │ └── sso.component.ts │ │ ├── organizations-routing.module.ts │ │ └── organizations.module.ts │ │ ├── policies │ │ ├── disable-personal-vault-export.component.html │ │ ├── disable-personal-vault-export.component.ts │ │ ├── maximum-vault-timeout.component.html │ │ └── maximum-vault-timeout.component.ts │ │ └── providers │ │ ├── clients │ │ ├── add-organization.component.html │ │ ├── add-organization.component.ts │ │ ├── clients.component.html │ │ ├── clients.component.ts │ │ ├── create-organization.component.html │ │ └── create-organization.component.ts │ │ ├── guards │ │ ├── provider-type.guard.ts │ │ └── provider.guard.ts │ │ ├── manage │ │ ├── accept-provider.component.html │ │ ├── accept-provider.component.ts │ │ ├── bulk │ │ │ ├── bulk-confirm.component.ts │ │ │ └── bulk-remove.component.ts │ │ ├── events.component.html │ │ ├── events.component.ts │ │ ├── manage.component.html │ │ ├── manage.component.ts │ │ ├── people.component.html │ │ ├── people.component.ts │ │ ├── user-add-edit.component.html │ │ └── user-add-edit.component.ts │ │ ├── providers-layout.component.html │ │ ├── providers-layout.component.ts │ │ ├── providers-routing.module.ts │ │ ├── providers.module.ts │ │ ├── services │ │ └── webProvider.service.ts │ │ ├── settings │ │ ├── account.component.html │ │ ├── account.component.ts │ │ ├── settings.component.html │ │ └── settings.component.ts │ │ └── setup │ │ ├── setup-provider.component.html │ │ ├── setup-provider.component.ts │ │ ├── setup.component.html │ │ └── setup.component.ts └── webpack.config.js ├── config.js ├── config ├── base.json ├── cloud.json ├── development.json ├── qa.json └── selfhosted.json ├── crowdin.yml ├── dev-server.shared.pem ├── entrypoint.sh ├── package-lock.json ├── package.json ├── postcss.config.js ├── scripts └── optimize.js ├── src ├── 404 │ ├── bootstrap.min.css │ └── styles.css ├── .nojekyll ├── 404.html ├── abstractions │ └── state.service.ts ├── app-id.json ├── app │ ├── accounts │ │ ├── accept-emergency.component.html │ │ ├── accept-emergency.component.ts │ │ ├── accept-organization.component.html │ │ ├── accept-organization.component.ts │ │ ├── hint.component.html │ │ ├── hint.component.ts │ │ ├── lock.component.html │ │ ├── lock.component.ts │ │ ├── login.component.html │ │ ├── login.component.ts │ │ ├── recover-delete.component.html │ │ ├── recover-delete.component.ts │ │ ├── recover-two-factor.component.html │ │ ├── recover-two-factor.component.ts │ │ ├── register.component.html │ │ ├── register.component.ts │ │ ├── remove-password.component.html │ │ ├── remove-password.component.ts │ │ ├── set-password.component.html │ │ ├── set-password.component.ts │ │ ├── sso.component.html │ │ ├── sso.component.ts │ │ ├── two-factor-options.component.html │ │ ├── two-factor-options.component.ts │ │ ├── two-factor.component.html │ │ ├── two-factor.component.ts │ │ ├── update-password.component.html │ │ ├── update-password.component.ts │ │ ├── update-temp-password.component.html │ │ ├── update-temp-password.component.ts │ │ ├── verify-email-token.component.html │ │ ├── verify-email-token.component.ts │ │ ├── verify-recover-delete.component.html │ │ └── verify-recover-delete.component.ts │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── common │ │ ├── base.accept.component.ts │ │ ├── base.events.component.ts │ │ └── base.people.component.ts │ ├── components │ │ ├── nested-checkbox.component.html │ │ ├── nested-checkbox.component.ts │ │ ├── organization-switcher.component.html │ │ ├── organization-switcher.component.ts │ │ ├── password-reprompt.component.html │ │ ├── password-reprompt.component.ts │ │ ├── password-strength.component.html │ │ ├── password-strength.component.ts │ │ └── premium-badge.component.ts │ ├── guards │ │ └── home.guard.ts │ ├── layouts │ │ ├── footer.component.html │ │ ├── footer.component.ts │ │ ├── frontend-layout.component.html │ │ ├── frontend-layout.component.ts │ │ ├── navbar.component.html │ │ ├── navbar.component.ts │ │ ├── user-layout.component.html │ │ └── user-layout.component.ts │ ├── main.ts │ ├── modules │ │ ├── loose-components.module.ts │ │ ├── organizations │ │ │ ├── manage │ │ │ │ ├── entity-users.component.html │ │ │ │ ├── entity-users.component.ts │ │ │ │ └── organization-manage.module.ts │ │ │ └── users │ │ │ │ ├── enroll-master-password-reset.component.html │ │ │ │ ├── enroll-master-password-reset.component.ts │ │ │ │ └── organization-user.module.ts │ │ ├── pipes │ │ │ ├── get-organization-name.pipe.ts │ │ │ └── pipes.module.ts │ │ ├── shared.module.ts │ │ ├── vault-filter │ │ │ ├── components │ │ │ │ ├── collection-filter.component.html │ │ │ │ ├── collection-filter.component.ts │ │ │ │ ├── folder-filter.component.html │ │ │ │ ├── folder-filter.component.ts │ │ │ │ ├── link-sso.component.html │ │ │ │ ├── link-sso.component.ts │ │ │ │ ├── organization-filter.component.html │ │ │ │ ├── organization-filter.component.ts │ │ │ │ ├── organization-options.component.html │ │ │ │ ├── organization-options.component.ts │ │ │ │ ├── status-filter.component.html │ │ │ │ ├── status-filter.component.ts │ │ │ │ ├── type-filter.component.html │ │ │ │ └── type-filter.component.ts │ │ │ ├── organization-vault-filter.component.ts │ │ │ ├── vault-filter.component.html │ │ │ ├── vault-filter.component.ts │ │ │ ├── vault-filter.module.ts │ │ │ └── vault-filter.service.ts │ │ └── vault │ │ │ ├── modules │ │ │ ├── individual-vault │ │ │ │ ├── individual-vault-routing.module.ts │ │ │ │ ├── individual-vault.component.html │ │ │ │ ├── individual-vault.component.ts │ │ │ │ └── individual-vault.module.ts │ │ │ ├── organization-badge │ │ │ │ ├── organization-badge.module.ts │ │ │ │ ├── organization-name-badge.component.html │ │ │ │ └── organization-name-badge.component.ts │ │ │ └── organization-vault │ │ │ │ ├── organization-vault-routing.module.ts │ │ │ │ ├── organization-vault.component.html │ │ │ │ ├── organization-vault.component.ts │ │ │ │ └── organization-vault.module.ts │ │ │ ├── vault.module.ts │ │ │ └── vault.service.ts │ ├── organizations │ │ ├── guards │ │ │ └── permissions.guard.ts │ │ ├── layouts │ │ │ ├── organization-layout.component.html │ │ │ └── organization-layout.component.ts │ │ ├── manage │ │ │ ├── bulk │ │ │ │ ├── bulk-confirm.component.html │ │ │ │ ├── bulk-confirm.component.ts │ │ │ │ ├── bulk-remove.component.html │ │ │ │ ├── bulk-remove.component.ts │ │ │ │ ├── bulk-status.component.html │ │ │ │ └── bulk-status.component.ts │ │ │ ├── collection-add-edit.component.html │ │ │ ├── collection-add-edit.component.ts │ │ │ ├── collections.component.html │ │ │ ├── collections.component.ts │ │ │ ├── entity-events.component.html │ │ │ ├── entity-events.component.ts │ │ │ ├── events.component.html │ │ │ ├── events.component.ts │ │ │ ├── group-add-edit.component.html │ │ │ ├── group-add-edit.component.ts │ │ │ ├── groups.component.html │ │ │ ├── groups.component.ts │ │ │ ├── manage.component.html │ │ │ ├── manage.component.ts │ │ │ ├── people.component.html │ │ │ ├── people.component.ts │ │ │ ├── policies.component.html │ │ │ ├── policies.component.ts │ │ │ ├── policy-edit.component.html │ │ │ ├── policy-edit.component.ts │ │ │ ├── reset-password.component.html │ │ │ ├── reset-password.component.ts │ │ │ ├── user-add-edit.component.html │ │ │ ├── user-add-edit.component.ts │ │ │ ├── user-confirm.component.html │ │ │ ├── user-confirm.component.ts │ │ │ ├── user-groups.component.html │ │ │ └── user-groups.component.ts │ │ ├── organization-routing.module.ts │ │ ├── policies │ │ │ ├── base-policy.component.ts │ │ │ ├── disable-send.component.html │ │ │ ├── disable-send.component.ts │ │ │ ├── master-password.component.html │ │ │ ├── master-password.component.ts │ │ │ ├── password-generator.component.html │ │ │ ├── password-generator.component.ts │ │ │ ├── personal-ownership.component.html │ │ │ ├── personal-ownership.component.ts │ │ │ ├── require-sso.component.html │ │ │ ├── require-sso.component.ts │ │ │ ├── reset-password.component.html │ │ │ ├── reset-password.component.ts │ │ │ ├── send-options.component.html │ │ │ ├── send-options.component.ts │ │ │ ├── single-org.component.html │ │ │ ├── single-org.component.ts │ │ │ ├── two-factor-authentication.component.html │ │ │ └── two-factor-authentication.component.ts │ │ ├── services │ │ │ └── navigation-permissions.service.ts │ │ ├── settings │ │ │ ├── account.component.html │ │ │ ├── account.component.ts │ │ │ ├── adjust-subscription.component.html │ │ │ ├── adjust-subscription.component.ts │ │ │ ├── billing-sync-api-key.component.html │ │ │ ├── billing-sync-api-key.component.ts │ │ │ ├── change-plan.component.html │ │ │ ├── change-plan.component.ts │ │ │ ├── delete-organization.component.html │ │ │ ├── delete-organization.component.ts │ │ │ ├── download-license.component.html │ │ │ ├── download-license.component.ts │ │ │ ├── image-subscription-hidden.component.svg │ │ │ ├── image-subscription-hidden.component.ts │ │ │ ├── organization-billing.component.html │ │ │ ├── organization-billing.component.ts │ │ │ ├── organization-subscription.component.html │ │ │ ├── organization-subscription.component.ts │ │ │ ├── settings.component.html │ │ │ ├── settings.component.ts │ │ │ └── two-factor-setup.component.ts │ │ ├── sponsorships │ │ │ ├── accept-family-sponsorship.component.html │ │ │ ├── accept-family-sponsorship.component.ts │ │ │ ├── families-for-enterprise-setup.component.html │ │ │ └── families-for-enterprise-setup.component.ts │ │ ├── tools │ │ │ ├── export.component.ts │ │ │ ├── exposed-passwords-report.component.ts │ │ │ ├── import.component.ts │ │ │ ├── inactive-two-factor-report.component.ts │ │ │ ├── reused-passwords-report.component.ts │ │ │ ├── tools.component.html │ │ │ ├── tools.component.ts │ │ │ ├── unsecured-websites-report.component.ts │ │ │ └── weak-passwords-report.component.ts │ │ └── vault │ │ │ ├── add-edit.component.ts │ │ │ ├── attachments.component.ts │ │ │ ├── ciphers.component.ts │ │ │ └── collections.component.ts │ ├── oss-routing.module.ts │ ├── oss.module.ts │ ├── polyfills.ts │ ├── providers │ │ ├── providers.component.html │ │ └── providers.component.ts │ ├── reports │ │ ├── breach-report.component.html │ │ ├── breach-report.component.ts │ │ ├── cipher-report.component.ts │ │ ├── exposed-passwords-report.component.html │ │ ├── exposed-passwords-report.component.ts │ │ ├── inactive-two-factor-report.component.html │ │ ├── inactive-two-factor-report.component.ts │ │ ├── report-card.component.html │ │ ├── report-card.component.ts │ │ ├── report-list.component.html │ │ ├── report-list.component.ts │ │ ├── reports-routing.module.ts │ │ ├── reports.component.html │ │ ├── reports.component.ts │ │ ├── reused-passwords-report.component.html │ │ ├── reused-passwords-report.component.ts │ │ ├── unsecured-websites-report.component.html │ │ ├── unsecured-websites-report.component.ts │ │ ├── weak-passwords-report.component.html │ │ └── weak-passwords-report.component.ts │ ├── send │ │ ├── access.component.html │ │ ├── access.component.ts │ │ ├── add-edit.component.html │ │ ├── add-edit.component.ts │ │ ├── efflux-dates.component.html │ │ ├── efflux-dates.component.ts │ │ ├── send.component.html │ │ └── send.component.ts │ ├── services │ │ ├── event.service.ts │ │ ├── init.service.ts │ │ ├── modal.service.ts │ │ ├── policy-list.service.ts │ │ ├── router.service.ts │ │ └── services.module.ts │ ├── settings │ │ ├── account.component.html │ │ ├── account.component.ts │ │ ├── add-credit.component.html │ │ ├── add-credit.component.ts │ │ ├── adjust-payment.component.html │ │ ├── adjust-payment.component.ts │ │ ├── adjust-storage.component.html │ │ ├── adjust-storage.component.ts │ │ ├── api-key.component.html │ │ ├── api-key.component.ts │ │ ├── billing-sync-key.component.html │ │ ├── billing-sync-key.component.ts │ │ ├── change-email.component.html │ │ ├── change-email.component.ts │ │ ├── change-kdf.component.html │ │ ├── change-kdf.component.ts │ │ ├── change-password.component.html │ │ ├── change-password.component.ts │ │ ├── create-organization.component.html │ │ ├── create-organization.component.ts │ │ ├── deauthorize-sessions.component.html │ │ ├── deauthorize-sessions.component.ts │ │ ├── delete-account.component.html │ │ ├── delete-account.component.ts │ │ ├── domain-rules.component.html │ │ ├── domain-rules.component.ts │ │ ├── emergency-access-add-edit.component.html │ │ ├── emergency-access-add-edit.component.ts │ │ ├── emergency-access-attachments.component.ts │ │ ├── emergency-access-confirm.component.html │ │ ├── emergency-access-confirm.component.ts │ │ ├── emergency-access-takeover.component.html │ │ ├── emergency-access-takeover.component.ts │ │ ├── emergency-access-view.component.html │ │ ├── emergency-access-view.component.ts │ │ ├── emergency-access.component.html │ │ ├── emergency-access.component.ts │ │ ├── emergency-add-edit.component.ts │ │ ├── organization-plans.component.html │ │ ├── organization-plans.component.ts │ │ ├── payment-method.component.html │ │ ├── payment-method.component.ts │ │ ├── payment.component.html │ │ ├── payment.component.ts │ │ ├── preferences.component.html │ │ ├── preferences.component.ts │ │ ├── premium.component.html │ │ ├── premium.component.ts │ │ ├── profile.component.html │ │ ├── profile.component.ts │ │ ├── purge-vault.component.html │ │ ├── purge-vault.component.ts │ │ ├── security-keys.component.html │ │ ├── security-keys.component.ts │ │ ├── security-routing.module.ts │ │ ├── security.component.html │ │ ├── security.component.ts │ │ ├── settings.component.html │ │ ├── settings.component.ts │ │ ├── sponsored-families.component.html │ │ ├── sponsored-families.component.ts │ │ ├── sponsoring-org-row.component.html │ │ ├── sponsoring-org-row.component.ts │ │ ├── subscription-routing.module.ts │ │ ├── subscription.component.html │ │ ├── subscription.component.ts │ │ ├── tax-info.component.html │ │ ├── tax-info.component.ts │ │ ├── two-factor-authenticator.component.html │ │ ├── two-factor-authenticator.component.ts │ │ ├── two-factor-base.component.ts │ │ ├── two-factor-duo.component.html │ │ ├── two-factor-duo.component.ts │ │ ├── two-factor-email.component.html │ │ ├── two-factor-email.component.ts │ │ ├── two-factor-recovery.component.html │ │ ├── two-factor-recovery.component.ts │ │ ├── two-factor-setup.component.html │ │ ├── two-factor-setup.component.ts │ │ ├── two-factor-verify.component.html │ │ ├── two-factor-verify.component.ts │ │ ├── two-factor-webauthn.component.html │ │ ├── two-factor-webauthn.component.ts │ │ ├── two-factor-yubikey.component.html │ │ ├── two-factor-yubikey.component.ts │ │ ├── update-key.component.html │ │ ├── update-key.component.ts │ │ ├── update-license.component.html │ │ ├── update-license.component.ts │ │ ├── user-billing-history.component.html │ │ ├── user-billing-history.component.ts │ │ ├── user-subscription.component.html │ │ ├── user-subscription.component.ts │ │ ├── vault-timeout-input.component.html │ │ ├── vault-timeout-input.component.ts │ │ ├── verify-email.component.html │ │ └── verify-email.component.ts │ ├── tools │ │ ├── export.component.html │ │ ├── export.component.ts │ │ ├── generator.component.html │ │ ├── generator.component.ts │ │ ├── import.component.html │ │ ├── import.component.ts │ │ ├── password-generator-history.component.html │ │ ├── password-generator-history.component.ts │ │ ├── tools.component.html │ │ └── tools.component.ts │ ├── vault │ │ ├── add-edit-custom-fields.component.html │ │ ├── add-edit-custom-fields.component.ts │ │ ├── add-edit.component.html │ │ ├── add-edit.component.ts │ │ ├── attachments.component.html │ │ ├── attachments.component.ts │ │ ├── bulk-actions.component.html │ │ ├── bulk-actions.component.ts │ │ ├── bulk-delete.component.html │ │ ├── bulk-delete.component.ts │ │ ├── bulk-move.component.html │ │ ├── bulk-move.component.ts │ │ ├── bulk-restore.component.html │ │ ├── bulk-restore.component.ts │ │ ├── bulk-share.component.html │ │ ├── bulk-share.component.ts │ │ ├── ciphers.component.html │ │ ├── ciphers.component.ts │ │ ├── collections.component.html │ │ ├── collections.component.ts │ │ ├── folder-add-edit.component.html │ │ ├── folder-add-edit.component.ts │ │ ├── share.component.html │ │ └── share.component.ts │ └── wildcard-routing.module.ts ├── browserconfig.xml ├── connectors │ ├── captcha-mobile.html │ ├── captcha-mobile.scss │ ├── captcha.html │ ├── captcha.scss │ ├── captcha.ts │ ├── common-webauthn.ts │ ├── common.ts │ ├── duo.html │ ├── duo.scss │ ├── duo.ts │ ├── sso.html │ ├── sso.scss │ ├── sso.ts │ ├── webauthn-fallback.html │ ├── webauthn-fallback.ts │ ├── webauthn-mobile.html │ ├── webauthn.html │ ├── webauthn.scss │ └── webauthn.ts ├── favicon.ico ├── global.d.ts ├── images │ ├── 404.png │ ├── bwi-globe.png │ ├── cards.png │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg │ ├── loading-white.svg │ ├── loading.svg │ ├── logo-dark@2x.png │ ├── logo-white@2x.png │ ├── register-layout │ │ ├── cnet-logo.svg │ │ ├── forbes-logo.svg │ │ ├── logo-horizontal-white.png │ │ ├── logo-horizontal-white.svg │ │ ├── usnews-360-badge.svg │ │ └── wired-logo.png │ ├── totp-countdown.png │ ├── two-factor │ │ ├── 0.png │ │ ├── 1-w.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 6.png │ │ ├── 7-w.png │ │ ├── 7.png │ │ ├── rc-w.png │ │ └── rc.png │ ├── u2fkey-mobile.avif │ ├── u2fkey-mobile.jpg │ ├── u2fkey-mobile.webp │ ├── u2fkey.avif │ ├── u2fkey.jpg │ ├── u2fkey.webp │ ├── yubikey.avif │ ├── yubikey.jpg │ └── yubikey.webp ├── index.html ├── locales │ ├── af │ │ └── messages.json │ ├── az │ │ └── messages.json │ ├── be │ │ └── messages.json │ ├── bg │ │ └── messages.json │ ├── bn │ │ └── messages.json │ ├── bs │ │ └── messages.json │ ├── ca │ │ └── messages.json │ ├── cs │ │ └── messages.json │ ├── da │ │ └── messages.json │ ├── de │ │ └── messages.json │ ├── el │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── en_GB │ │ └── messages.json │ ├── en_IN │ │ └── messages.json │ ├── eo │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── et │ │ └── messages.json │ ├── fi │ │ └── messages.json │ ├── fil │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── he │ │ └── messages.json │ ├── hi │ │ └── messages.json │ ├── hr │ │ └── messages.json │ ├── hu │ │ └── messages.json │ ├── id │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── ka │ │ └── messages.json │ ├── km │ │ └── messages.json │ ├── kn │ │ └── messages.json │ ├── ko │ │ └── messages.json │ ├── lv │ │ └── messages.json │ ├── ml │ │ └── messages.json │ ├── nb │ │ └── messages.json │ ├── nl │ │ └── messages.json │ ├── nn │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ ├── pt_PT │ │ └── messages.json │ ├── ro │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── si │ │ └── messages.json │ ├── sk │ │ └── messages.json │ ├── sl │ │ └── messages.json │ ├── sr │ │ └── messages.json │ ├── sv │ │ └── messages.json │ ├── tr │ │ └── messages.json │ ├── uk │ │ └── messages.json │ ├── vi │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ └── zh_TW │ │ └── messages.json ├── manifest.json ├── models │ ├── account.ts │ └── globalState.ts ├── scss │ ├── base.scss │ ├── buttons.scss │ ├── callouts.scss │ ├── cards.scss │ ├── export.module.scss │ ├── export.module.scss.d.ts │ ├── forms.scss │ ├── modals.scss │ ├── navigation.scss │ ├── pages.scss │ ├── plugins.scss │ ├── styles.scss │ ├── tables.scss │ ├── tailwind.css │ ├── toasts.scss │ ├── variables.scss │ └── vault-filters.scss ├── services │ ├── broadcasterMessaging.service.ts │ ├── htmlStorage.service.ts │ ├── i18n.service.ts │ ├── memoryStorage.service.ts │ ├── passwordReprompt.service.ts │ ├── state.service.ts │ ├── stateMigration.service.ts │ └── webPlatformUtils.service.ts ├── theme.js └── version.json ├── tailwind.config.js ├── tsconfig.json └── webpack.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !build/* 3 | !entrypoint.sh 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Set default charset 12 | [*.{js,ts,scss,html}] 13 | charset = utf-8 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.{ts}] 18 | quote_type = single 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist 2 | **/build 3 | jslib 4 | webpack.config.js 5 | scripts/optimize.js 6 | config.js 7 | 8 | **/node_modules 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true 5 | }, 6 | "extends": ["./jslib/shared/eslintrc.json"], 7 | "rules": { 8 | "import/order": [ 9 | "error", 10 | { 11 | "alphabetize": { 12 | "order": "asc" 13 | }, 14 | "newlines-between": "always", 15 | "pathGroups": [ 16 | { 17 | "pattern": "jslib-*/**", 18 | "group": "external", 19 | "position": "after" 20 | }, 21 | { 22 | "pattern": "src/**/*", 23 | "group": "parent", 24 | "position": "before" 25 | } 26 | ], 27 | "pathGroupsExcludedImportTypes": ["builtin"] 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Apply Prettier https://github.com/bitwarden/web/pull/1347 2 | 56477eb39cfd8a73c9920577d24d75fed36e2cf5 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Requests 4 | url: https://community.bitwarden.com/c/feature-requests/ 5 | about: Request new features using the Community Forums. Please search existing feature requests before making a new one. 6 | - name: Bitwarden Community Forums 7 | url: https://community.bitwarden.com 8 | about: Please visit the community forums for general community discussion, support and the development roadmap. 9 | - name: Customer Support 10 | url: https://bitwarden.com/contact/ 11 | about: Please contact our customer support for account issues and general customer support. 12 | - name: Security Issues 13 | url: https://hackerone.com/bitwarden 14 | about: We use HackerOne to manage security disclosures. 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Type of change 2 | 3 | - [ ] Bug fix 4 | - [ ] New feature development 5 | - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) 6 | - [ ] Build/deploy pipeline (DevOps) 7 | - [ ] Other 8 | 9 | ## Objective 10 | 11 | 12 | 13 | ## Code changes 14 | 15 | 16 | 17 | 18 | - **file.ext:** Description of what was changed and why 19 | 20 | ## Screenshots 21 | 22 | 23 | 24 | ## Before you submit 25 | 26 | - [ ] I have checked for **linting** errors (`npm run lint`) (required) 27 | - [ ] This change requires a **documentation update** (notify the documentation team) 28 | - [ ] This change has particular **deployment requirements** (notify the DevOps team) 29 | -------------------------------------------------------------------------------- /.github/workflows/enforce-labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enforce PR labels 3 | 4 | on: 5 | pull_request: 6 | types: [labeled, unlabeled, opened, edited, synchronize] 7 | jobs: 8 | enforce-label: 9 | name: EnforceLabel 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - name: Enforce Label 13 | uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb 14 | with: 15 | BANNED_LABELS: "hold" 16 | BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged" 17 | -------------------------------------------------------------------------------- /.github/workflows/workflow-linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Workflow Linter 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - .github/workflows/** 8 | 9 | jobs: 10 | call-workflow: 11 | uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .idea 3 | .DS_Store 4 | node_modules 5 | npm-debug.log 6 | vwd.webinfo 7 | css/ 8 | dist/ 9 | *.pem 10 | *.crx 11 | *.zip 12 | *.swp 13 | build/ 14 | !dev-server.shared.pem 15 | config/local.json 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jslib"] 2 | path = jslib 3 | url = https://github.com/bitwarden/jslib.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build 3 | dist 4 | 5 | jslib 6 | 7 | # External libraries / auto synced locales 8 | src/locales 9 | src/404/*.min.css 10 | 11 | # Github Workflows 12 | .github/workflows 13 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bitwarden/server 2 | 3 | LABEL com.bitwarden.product="bitwarden" 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends \ 7 | gosu \ 8 | curl \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | ENV ASPNETCORE_URLS http://+:5000 12 | WORKDIR /app 13 | EXPOSE 5000 14 | COPY ./build . 15 | COPY entrypoint.sh / 16 | RUN chmod +x /entrypoint.sh 17 | 18 | HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1 19 | 20 | ENTRYPOINT ["/entrypoint.sh"] 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Source code in this repository is covered by one of two licenses: (i) the 2 | GNU General Public License (GPL) v3.0 (ii) the Bitwarden License v1.0. The 3 | default license throughout the repository is GPL v3.0 unless the header 4 | specifies another license. Bitwarden Licensed code is found only in the 5 | /bitwarden_license directory. 6 | 7 | GPL v3.0: 8 | https://github.com/bitwarden/web/blob/master/LICENSE_GPL.txt 9 | 10 | Bitwarden License v1.0: 11 | https://github.com/bitwarden/web/blob/master/LICENSE_BITWARDEN.txt 12 | 13 | No grant of any rights in the trademarks, service marks, or logos of Bitwarden is 14 | made (except as may be necessary to comply with the notice requirements as 15 | applicable), and use of any Bitwarden trademarks must comply with Bitwarden 16 | Trademark Guidelines 17 | . 18 | -------------------------------------------------------------------------------- /bitwarden_license/README.md: -------------------------------------------------------------------------------- 1 | # Bitwarden Licensed Code 2 | 3 | All source code under this directory is licensed under the [Bitwarden License Agreement](https://github.com/bitwarden/web/blob/master/LICENSE_BITWARDEN.txt). 4 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: "providers", 7 | loadChildren: async () => (await import("./providers/providers.module")).ProvidersModule, 8 | }, 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(routes)], 13 | exports: [RouterModule], 14 | }) 15 | export class AppRoutingModule {} 16 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { AppComponent as BaseAppComponent } from "src/app/app.component"; 4 | 5 | import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component"; 6 | import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component"; 7 | 8 | @Component({ 9 | selector: "app-root", 10 | templateUrl: "../../../src/app/app.component.html", 11 | }) 12 | export class AppComponent extends BaseAppComponent { 13 | ngOnInit() { 14 | super.ngOnInit(); 15 | 16 | this.policyListService.addPolicies([ 17 | new MaximumVaultTimeoutPolicy(), 18 | new DisablePersonalVaultExportPolicy(), 19 | ]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from "@angular/core"; 2 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 3 | 4 | import "bootstrap"; 5 | import "jquery"; 6 | import "popper.js"; 7 | 8 | require("src/scss/styles.scss"); 9 | require("src/scss/tailwind.css"); 10 | 11 | import { AppModule } from "./app.module"; 12 | 13 | if (process.env.NODE_ENV === "production") { 14 | enableProdMode(); 15 | } 16 | 17 | platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); 18 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-checkbox.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 | 12 |
13 | {{ 14 | helperText 15 | }} 16 |
17 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { BaseCvaComponent } from "./base-cva.component"; 4 | 5 | /** For use in the SSO Config Form only - will be deprecated by the Component Library */ 6 | @Component({ 7 | selector: "app-input-checkbox", 8 | templateUrl: "input-checkbox.component.html", 9 | }) 10 | export class InputCheckboxComponent extends BaseCvaComponent {} 11 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-text-readonly.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 14 |
15 |
16 | 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-text-readonly.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 4 | 5 | /** For use in the SSO Config Form only - will be deprecated by the Component Library */ 6 | @Component({ 7 | selector: "app-input-text-readonly", 8 | templateUrl: "input-text-readonly.component.html", 9 | }) 10 | export class InputTextReadOnlyComponent { 11 | @Input() controlValue: string; 12 | @Input() label: string; 13 | @Input() showCopy = true; 14 | @Input() showLaunch = false; 15 | 16 | constructor(private platformUtilsService: PlatformUtilsService) {} 17 | 18 | copy(value: string) { 19 | this.platformUtilsService.copyToClipboard(value); 20 | } 21 | 22 | launchUri(url: string) { 23 | this.platformUtilsService.launchUri(url); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-text.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 16 |
17 | 21 | {{ helperText }} 22 | 23 | 24 | 25 | {{ "error" | i18n }}: 26 | {{ 27 | controlDir.control.hasError(helperTextSameAsError) 28 | ? helperText 29 | : ("fieldRequiredError" | i18n: label) 30 | }} 31 | 32 |
33 |
34 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/input-text.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from "@angular/core"; 2 | 3 | import { BaseCvaComponent } from "./base-cva.component"; 4 | 5 | /** For use in the SSO Config Form only - will be deprecated by the Component Library */ 6 | @Component({ 7 | selector: "app-input-text[label][controlId]", 8 | templateUrl: "input-text.component.html", 9 | }) 10 | export class InputTextComponent extends BaseCvaComponent implements OnInit { 11 | @Input() helperTextSameAsError: string; 12 | @Input() requiredErrorMessage: string; 13 | @Input() stripSpaces = false; 14 | 15 | transformValue: (value: string) => string = null; 16 | 17 | ngOnInit() { 18 | super.ngOnInit(); 19 | if (this.stripSpaces) { 20 | this.transformValue = this.doStripSpaces; 21 | } 22 | } 23 | 24 | writeValue(value: string) { 25 | this.internalControl.setValue(value == null ? "" : value); 26 | } 27 | 28 | protected onValueChangesInternal: any = (value: string) => { 29 | let newValue = value; 30 | if (this.transformValue != null) { 31 | newValue = this.transformValue(value); 32 | this.internalControl.setValue(newValue, { emitEvent: false }); 33 | } 34 | this.onChange(newValue); 35 | }; 36 | 37 | protected onValueChangeInternal(value: string) { 38 | let newValue = value; 39 | if (this.transformValue != null) { 40 | newValue = this.transformValue(value); 41 | this.internalControl.setValue(newValue, { emitEvent: false }); 42 | } 43 | } 44 | 45 | private doStripSpaces(value: string) { 46 | return value.replace(/ /g, ""); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/select.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 19 |
20 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/components/select.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { SelectOptions } from "jslib-angular/interfaces/selectOptions"; 4 | 5 | import { BaseCvaComponent } from "./base-cva.component"; 6 | 7 | /** For use in the SSO Config Form only - will be deprecated by the Component Library */ 8 | @Component({ 9 | selector: "app-select", 10 | templateUrl: "select.component.html", 11 | }) 12 | export class SelectComponent extends BaseCvaComponent { 13 | @Input() selectOptions: SelectOptions[]; 14 | } 15 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/organizations-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { AuthGuard } from "jslib-angular/guards/auth.guard"; 5 | import { Permissions } from "jslib-common/enums/permissions"; 6 | 7 | import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard"; 8 | import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component"; 9 | import { ManageComponent } from "src/app/organizations/manage/manage.component"; 10 | import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service"; 11 | 12 | import { SsoComponent } from "./manage/sso.component"; 13 | 14 | const routes: Routes = [ 15 | { 16 | path: "organizations/:organizationId", 17 | component: OrganizationLayoutComponent, 18 | canActivate: [AuthGuard, PermissionsGuard], 19 | children: [ 20 | { 21 | path: "manage", 22 | component: ManageComponent, 23 | canActivate: [PermissionsGuard], 24 | data: { 25 | permissions: NavigationPermissionsService.getPermissions("manage"), 26 | }, 27 | children: [ 28 | { 29 | path: "sso", 30 | component: SsoComponent, 31 | canActivate: [PermissionsGuard], 32 | data: { 33 | permissions: [Permissions.ManageSso], 34 | }, 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | ]; 41 | 42 | @NgModule({ 43 | imports: [RouterModule.forChild(routes)], 44 | exports: [RouterModule], 45 | }) 46 | export class OrganizationsRoutingModule {} 47 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/organizations/organizations.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from "@angular/common"; 2 | import { NgModule } from "@angular/core"; 3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms"; 4 | 5 | import { JslibModule } from "jslib-angular/jslib.module"; 6 | 7 | import { InputCheckboxComponent } from "./components/input-checkbox.component"; 8 | import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component"; 9 | import { InputTextComponent } from "./components/input-text.component"; 10 | import { SelectComponent } from "./components/select.component"; 11 | import { SsoComponent } from "./manage/sso.component"; 12 | import { OrganizationsRoutingModule } from "./organizations-routing.module"; 13 | 14 | // Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere. 15 | // They will be deprecated by the Component Library. 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | ReactiveFormsModule, 21 | JslibModule, 22 | OrganizationsRoutingModule, 23 | ], 24 | declarations: [ 25 | InputCheckboxComponent, 26 | InputTextComponent, 27 | InputTextReadOnlyComponent, 28 | SelectComponent, 29 | SsoComponent, 30 | ], 31 | }) 32 | export class OrganizationsModule {} 33 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/policies/disable-personal-vault-export.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/policies/disable-personal-vault-export.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PolicyType } from "jslib-common/enums/policyType"; 4 | 5 | import { 6 | BasePolicy, 7 | BasePolicyComponent, 8 | } from "src/app/organizations/policies/base-policy.component"; 9 | 10 | export class DisablePersonalVaultExportPolicy extends BasePolicy { 11 | name = "disablePersonalVaultExport"; 12 | description = "disablePersonalVaultExportDesc"; 13 | type = PolicyType.DisablePersonalVaultExport; 14 | component = DisablePersonalVaultExportPolicyComponent; 15 | } 16 | 17 | @Component({ 18 | selector: "policy-disable-personal-vault-export", 19 | templateUrl: "disable-personal-vault-export.component.html", 20 | }) 21 | export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {} 22 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/policies/maximum-vault-timeout.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "requireSsoPolicyReq" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 31 | {{ "hours" | i18n }} 32 |
33 |
34 | 43 | {{ "minutes" | i18n }} 44 |
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/clients/create-organization.component.html: -------------------------------------------------------------------------------- 1 | 4 |

{{ "newClientOrganizationDesc" | i18n }}

5 | 6 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/clients/create-organization.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component"; 5 | 6 | @Component({ 7 | selector: "app-create-organization", 8 | templateUrl: "create-organization.component.html", 9 | }) 10 | export class CreateOrganizationComponent implements OnInit { 11 | @ViewChild(OrganizationPlansComponent, { static: true }) 12 | orgPlansComponent: OrganizationPlansComponent; 13 | 14 | providerId: string; 15 | 16 | constructor(private route: ActivatedRoute) {} 17 | 18 | ngOnInit() { 19 | this.route.parent.params.subscribe(async (params) => { 20 | this.providerId = params.providerId; 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/guards/provider-type.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; 3 | 4 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 5 | import { Permissions } from "jslib-common/enums/permissions"; 6 | 7 | @Injectable() 8 | export class PermissionsGuard implements CanActivate { 9 | constructor(private providerService: ProviderService, private router: Router) {} 10 | 11 | async canActivate(route: ActivatedRouteSnapshot) { 12 | const provider = await this.providerService.get(route.params.providerId); 13 | const permissions = route.data == null ? null : (route.data.permissions as Permissions[]); 14 | 15 | if ( 16 | (permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) || 17 | (permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) || 18 | (permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers) 19 | ) { 20 | return true; 21 | } 22 | 23 | this.router.navigate(["/providers", provider.id]); 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/guards/provider.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; 3 | 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 6 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 7 | 8 | @Injectable() 9 | export class ProviderGuard implements CanActivate { 10 | constructor( 11 | private router: Router, 12 | private platformUtilsService: PlatformUtilsService, 13 | private i18nService: I18nService, 14 | private providerService: ProviderService 15 | ) {} 16 | 17 | async canActivate(route: ActivatedRouteSnapshot) { 18 | const provider = await this.providerService.get(route.params.providerId); 19 | if (provider == null) { 20 | this.router.navigate(["/"]); 21 | return false; 22 | } 23 | if (!provider.isProviderAdmin && !provider.enabled) { 24 | this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled")); 25 | this.router.navigate(["/"]); 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/manage/accept-provider.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 |
15 |
16 |
17 |

{{ "joinProvider" | i18n }}

18 |
19 |
20 |

21 | {{ providerName }} 22 | {{ email }} 23 |

24 |

{{ "joinProviderDesc" | i18n }}

25 |
26 | 42 |
43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/manage/bulk/bulk-confirm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType"; 4 | import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest"; 5 | import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest"; 6 | 7 | import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component"; 8 | import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component"; 9 | 10 | @Component({ 11 | templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-confirm.component.html", 12 | }) 13 | export class BulkConfirmComponent extends OrganizationBulkConfirmComponent { 14 | @Input() providerId: string; 15 | 16 | protected isAccepted(user: BulkUserDetails) { 17 | return user.status === ProviderUserStatusType.Accepted; 18 | } 19 | 20 | protected async getPublicKeys() { 21 | const request = new ProviderUserBulkRequest(this.filteredUsers.map((user) => user.id)); 22 | return await this.apiService.postProviderUsersPublicKey(this.providerId, request); 23 | } 24 | 25 | protected getCryptoKey() { 26 | return this.cryptoService.getProviderKey(this.providerId); 27 | } 28 | 29 | protected async postConfirmRequest(userIdsWithKeys: any[]) { 30 | const request = new ProviderUserBulkConfirmRequest(userIdsWithKeys); 31 | return await this.apiService.postProviderUserBulkConfirm(this.providerId, request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/manage/bulk/bulk-remove.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest"; 4 | 5 | import { BulkRemoveComponent as OrganizationBulkRemoveComponent } from "src/app/organizations/manage/bulk/bulk-remove.component"; 6 | 7 | @Component({ 8 | templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-remove.component.html", 9 | }) 10 | export class BulkRemoveComponent extends OrganizationBulkRemoveComponent { 11 | @Input() providerId: string; 12 | 13 | async deleteUsers() { 14 | const request = new ProviderUserBulkRequest(this.users.map((user) => user.id)); 15 | return await this.apiService.deleteManyProviderUsers(this.providerId, request); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/manage/manage.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
{{ "manage" | i18n }}
6 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/manage/manage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 5 | import { Provider } from "jslib-common/models/domain/provider"; 6 | 7 | @Component({ 8 | selector: "provider-manage", 9 | templateUrl: "manage.component.html", 10 | }) 11 | export class ManageComponent implements OnInit { 12 | provider: Provider; 13 | accessEvents = false; 14 | 15 | constructor(private route: ActivatedRoute, private providerService: ProviderService) {} 16 | 17 | ngOnInit() { 18 | this.route.parent.params.subscribe(async (params) => { 19 | this.provider = await this.providerService.get(params.providerId); 20 | this.accessEvents = this.provider.useEvents; 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/providers-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 5 | import { Provider } from "jslib-common/models/domain/provider"; 6 | 7 | @Component({ 8 | selector: "providers-layout", 9 | templateUrl: "providers-layout.component.html", 10 | }) 11 | export class ProvidersLayoutComponent { 12 | provider: Provider; 13 | private providerId: string; 14 | 15 | constructor(private route: ActivatedRoute, private providerService: ProviderService) {} 16 | 17 | ngOnInit() { 18 | document.body.classList.remove("layout_frontend"); 19 | this.route.params.subscribe(async (params) => { 20 | this.providerId = params.providerId; 21 | await this.load(); 22 | }); 23 | } 24 | 25 | async load() { 26 | this.provider = await this.providerService.get(this.providerId); 27 | } 28 | 29 | get showMenuBar() { 30 | return this.showManageTab || this.showSettingsTab; 31 | } 32 | 33 | get showManageTab() { 34 | return this.provider.canManageUsers || this.provider.canAccessEventLogs; 35 | } 36 | 37 | get showSettingsTab() { 38 | return this.provider.isProviderAdmin; 39 | } 40 | 41 | get manageRoute(): string { 42 | switch (true) { 43 | case this.provider.canManageUsers: 44 | return "manage/people"; 45 | case this.provider.canAccessEventLogs: 46 | return "manage/events"; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/services/webProvider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | import { ApiService } from "jslib-common/abstractions/api.service"; 4 | import { CryptoService } from "jslib-common/abstractions/crypto.service"; 5 | import { SyncService } from "jslib-common/abstractions/sync.service"; 6 | import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest"; 7 | 8 | @Injectable() 9 | export class WebProviderService { 10 | constructor( 11 | private cryptoService: CryptoService, 12 | private syncService: SyncService, 13 | private apiService: ApiService 14 | ) {} 15 | 16 | async addOrganizationToProvider(providerId: string, organizationId: string) { 17 | const orgKey = await this.cryptoService.getOrgKey(organizationId); 18 | const providerKey = await this.cryptoService.getProviderKey(providerId); 19 | 20 | const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey); 21 | 22 | const request = new ProviderAddOrganizationRequest(); 23 | request.organizationId = organizationId; 24 | request.key = encryptedOrgKey.encryptedString; 25 | 26 | const response = await this.apiService.postProviderAddOrganization(providerId, request); 27 | await this.syncService.fullSync(true); 28 | return response; 29 | } 30 | 31 | async detachOrganizastion(providerId: string, organizationId: string): Promise { 32 | await this.apiService.deleteProviderOrganization(providerId, organizationId); 33 | await this.syncService.fullSync(true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/settings/account.component.html: -------------------------------------------------------------------------------- 1 | 4 |
5 | 10 | {{ "loading" | i18n }} 11 |
12 |
19 |
20 |
21 |
22 | 23 | 31 |
32 |
33 | 34 | 42 |
43 |
44 |
45 | 46 |
47 |
48 | 52 |
53 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
{{ "settings" | i18n }}
6 | 11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 5 | 6 | @Component({ 7 | selector: "provider-settings", 8 | templateUrl: "settings.component.html", 9 | }) 10 | export class SettingsComponent { 11 | constructor(private route: ActivatedRoute, private providerService: ProviderService) {} 12 | 13 | ngOnInit() { 14 | this.route.parent.params.subscribe(async (params) => { 15 | await this.providerService.get(params.providerId); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/setup/setup-provider.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 |
15 |
16 |
17 |

{{ "setupProvider" | i18n }}

18 |
19 |
20 |

{{ "setupProviderLoginDesc" | i18n }}

21 |
22 | 31 |
32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/setup/setup-provider.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { BaseAcceptComponent } from "src/app/common/base.accept.component"; 4 | 5 | @Component({ 6 | selector: "app-setup-provider", 7 | templateUrl: "setup-provider.component.html", 8 | }) 9 | export class SetupProviderComponent extends BaseAcceptComponent { 10 | failedShortMessage = "inviteAcceptFailedShort"; 11 | failedMessage = "inviteAcceptFailed"; 12 | 13 | requiredParameters = ["providerId", "email", "token"]; 14 | 15 | async authedHandler(qParams: any) { 16 | this.router.navigate(["/providers/setup"], { queryParams: qParams }); 17 | } 18 | 19 | async unauthedHandler(qParams: any) { 20 | // Empty 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bitwarden_license/src/app/providers/setup/setup.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 6 |

{{ "setupProviderDesc" | i18n }}

7 | 8 |
9 |

{{ "generalInformation" | i18n }}

10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 25 |
26 |
27 | 28 |
29 | 33 | 36 |
37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /bitwarden_license/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { AngularWebpackPlugin } = require("@ngtools/webpack"); 2 | 3 | const webpackConfig = require("../webpack.config"); 4 | 5 | webpackConfig.entry["app/main"] = "./bitwarden_license/src/app/main.ts"; 6 | webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({ 7 | tsConfigPath: "tsconfig.json", 8 | entryModule: "bitwarden_license/src/app/app.module#AppModule", 9 | sourceMap: true, 10 | }); 11 | 12 | module.exports = webpackConfig; 13 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | function load(envName) { 2 | return { 3 | ...require("./config/base.json"), 4 | ...loadConfig(envName), 5 | ...loadConfig("local"), 6 | dev: { 7 | ...require("./config/base.json").dev, 8 | ...loadConfig(envName).dev, 9 | ...loadConfig("local").dev, 10 | }, 11 | }; 12 | } 13 | 14 | function log(configObj) { 15 | const repeatNum = 50; 16 | console.log(`${"=".repeat(repeatNum)}\nenvConfig`); 17 | console.log(JSON.stringify(configObj, null, 2)); 18 | console.log(`${"=".repeat(repeatNum)}`); 19 | } 20 | 21 | function loadConfig(configName) { 22 | try { 23 | return require(`./config/${configName}.json`); 24 | } catch (e) { 25 | if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { 26 | return {}; 27 | } else { 28 | throw e; 29 | } 30 | } 31 | } 32 | 33 | module.exports = { 34 | load, 35 | log, 36 | }; 37 | -------------------------------------------------------------------------------- /config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": {}, 3 | "stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD", 4 | "braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2", 5 | "paypal": { 6 | "businessId": "AD3LAUZSNVPJY", 7 | "buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr" 8 | }, 9 | "dev": { 10 | "port": 8080, 11 | "allowedHosts": "auto" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/cloud.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "icons": "https://icons.bitwarden.net", 4 | "notifications": "https://notifications.bitwarden.com" 5 | }, 6 | "stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk", 7 | "braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd", 8 | "paypal": { 9 | "businessId": "4ZDA7DLUUJGMN", 10 | "buttonAction": "https://www.paypal.com/cgi-bin/webscr" 11 | }, 12 | "dev": { 13 | "proxyApi": "https://api.bitwarden.com", 14 | "proxyIdentity": "https://identity.bitwarden.com", 15 | "proxyEvents": "https://events.bitwarden.com" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "notifications": "http://localhost:61840" 4 | }, 5 | "dev": { 6 | "proxyApi": "http://localhost:4000", 7 | "proxyIdentity": "http://localhost:33656", 8 | "proxyEvents": "http://localhost:46273", 9 | "proxyNotifications": "http://localhost:61840" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/qa.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "icons": "https://icons.qa.bitwarden.pw", 4 | "notifications": "https://notifications.qa.bitwarden.pw" 5 | }, 6 | "dev": { 7 | "proxyApi": "https://api.qa.bitwarden.pw", 8 | "proxyIdentity": "https://identity.qa.bitwarden.pw", 9 | "proxyEvents": "https://events.qa.bitwarden.pw" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/selfhosted.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "proxyApi": "http://localhost:4001", 4 | "proxyIdentity": "http://localhost:33657", 5 | "proxyEvents": "http://localhost:46274", 6 | "proxyNotifications": "http://localhost:61841", 7 | "port": 8081 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | project_id_env: _CROWDIN_PROJECT_ID 2 | api_token_env: CROWDIN_API_TOKEN 3 | preserve_hierarchy: true 4 | files: 5 | - source: /src/locales/en/messages.json 6 | dest: /src/locales/en/%file_name%.%file_extension% 7 | translation: /src/locales/%two_letters_code%/%original_file_name% 8 | update_option: update_as_unapproved 9 | languages_mapping: 10 | two_letters_code: 11 | pt-PT: pt_PT 12 | pt-BR: pt_BR 13 | zh-CN: zh_CN 14 | zh-TW: zh_TW 15 | en-GB: en_GB 16 | en-IN: en_IN 17 | sr-CY: sr_CY 18 | sr-CS: sr_CS 19 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Setup 4 | 5 | GROUPNAME="bitwarden" 6 | USERNAME="bitwarden" 7 | 8 | LUID=${LOCAL_UID:-0} 9 | LGID=${LOCAL_GID:-0} 10 | 11 | # Step down from host root to well-known nobody/nogroup user 12 | 13 | if [ $LUID -eq 0 ] 14 | then 15 | LUID=65534 16 | fi 17 | if [ $LGID -eq 0 ] 18 | then 19 | LGID=65534 20 | fi 21 | 22 | # Create user and group 23 | 24 | groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || 25 | groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 26 | useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || 27 | usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 28 | mkhomedir_helper $USERNAME 29 | 30 | # The rest... 31 | 32 | chown -R $USERNAME:$GROUPNAME /etc/bitwarden 33 | cp /etc/bitwarden/web/app-id.json /app/app-id.json 34 | chown -R $USERNAME:$GROUPNAME /app 35 | chown -R $USERNAME:$GROUPNAME /bitwarden_server 36 | 37 | exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \ 38 | /contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true 39 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], 4 | }; 5 | -------------------------------------------------------------------------------- /scripts/optimize.js: -------------------------------------------------------------------------------- 1 | const child_process = require("child_process"); 2 | const path = require("path"); 3 | 4 | const images = process.argv.slice(2); 5 | 6 | images.forEach((img) => { 7 | switch (img.split(".").pop()) { 8 | case "png": 9 | child_process.execSync( 10 | `npx @squoosh/cli --oxipng {} --output-dir "${path.dirname(img)}" "${img}"` 11 | ); 12 | break; 13 | case "jpg": 14 | child_process.execSync( 15 | `npx @squoosh/cli --mozjpeg {"quality":85,"baseline":false,"arithmetic":false,"progressive":true,"optimize_coding":true,"smoothing":0,"color_space":3,"quant_table":3,"trellis_multipass":false,"trellis_opt_zero":false,"trellis_opt_table":false,"trellis_loops":1,"auto_subsample":true,"chroma_subsample":2,"separate_chroma_quality":false,"chroma_quality":75} --output-dir "${path.dirname( 16 | img 17 | )}" "${img}"` 18 | ); 19 | break; 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/.nojekyll -------------------------------------------------------------------------------- /src/abstractions/state.service.ts: -------------------------------------------------------------------------------- 1 | import { StateService as BaseStateService } from "jslib-common/abstractions/state.service"; 2 | import { StorageOptions } from "jslib-common/models/domain/storageOptions"; 3 | 4 | import { Account } from "src/models/account"; 5 | 6 | export abstract class StateService extends BaseStateService { 7 | getRememberEmail: (options?: StorageOptions) => Promise; 8 | setRememberEmail: (value: boolean, options?: StorageOptions) => Promise; 9 | } 10 | -------------------------------------------------------------------------------- /src/app-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "trustedFacets": [ 3 | { 4 | "version": { 5 | "major": 1, 6 | "minor": 0 7 | }, 8 | "ids": [ 9 | "https://vault.bitwarden.com", 10 | "ios:bundle-id:com.8bit.bitwarden", 11 | "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/app/accounts/accept-emergency.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 |
15 |
16 |
17 |

{{ "emergencyAccess" | i18n }}

18 |
19 |
20 |

21 | {{ name }} 22 |

23 |

{{ "acceptEmergencyAccess" | i18n }}

24 |
25 | 41 |
42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /src/app/accounts/accept-organization.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 |
15 |
16 |
17 |

{{ "joinOrganization" | i18n }}

18 |
19 |
20 |

21 | {{ orgName }} 22 | {{ email }} 23 |

24 |

{{ "joinOrganizationDesc" | i18n }}

25 |
26 | 42 |
43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/app/accounts/hint.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ "passwordHint" | i18n }}

5 |
6 |
7 |
8 | 9 | 20 | {{ "enterEmailToGetHint" | i18n }} 21 |
22 |
23 |
24 | 36 | 37 | {{ "cancel" | i18n }} 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /src/app/accounts/hint.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { Router } from "@angular/router"; 3 | 4 | import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component"; 5 | import { ApiService } from "jslib-common/abstractions/api.service"; 6 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 7 | import { LogService } from "jslib-common/abstractions/log.service"; 8 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 9 | 10 | @Component({ 11 | selector: "app-hint", 12 | templateUrl: "hint.component.html", 13 | }) 14 | export class HintComponent extends BaseHintComponent { 15 | constructor( 16 | router: Router, 17 | i18nService: I18nService, 18 | apiService: ApiService, 19 | platformUtilsService: PlatformUtilsService, 20 | logService: LogService 21 | ) { 22 | super(router, i18nService, apiService, platformUtilsService, logService); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/accounts/recover-delete.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ "deleteAccount" | i18n }}

5 |
6 |
7 |

{{ "deleteRecoverDesc" | i18n }}

8 |
9 | 10 | 21 |
22 |
23 |
24 | 36 | 37 | {{ "cancel" | i18n }} 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /src/app/accounts/recover-delete.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { Router } from "@angular/router"; 3 | 4 | import { ApiService } from "jslib-common/abstractions/api.service"; 5 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 6 | import { LogService } from "jslib-common/abstractions/log.service"; 7 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 8 | import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest"; 9 | 10 | @Component({ 11 | selector: "app-recover-delete", 12 | templateUrl: "recover-delete.component.html", 13 | }) 14 | export class RecoverDeleteComponent { 15 | email: string; 16 | formPromise: Promise; 17 | 18 | constructor( 19 | private router: Router, 20 | private apiService: ApiService, 21 | private platformUtilsService: PlatformUtilsService, 22 | private i18nService: I18nService, 23 | private logService: LogService 24 | ) {} 25 | 26 | async submit() { 27 | try { 28 | const request = new DeleteRecoverRequest(); 29 | request.email = this.email.trim().toLowerCase(); 30 | this.formPromise = this.apiService.postAccountRecoverDelete(request); 31 | await this.formPromise; 32 | this.platformUtilsService.showToast( 33 | "success", 34 | null, 35 | this.i18nService.t("deleteRecoverEmailSent") 36 | ); 37 | this.router.navigate(["/"]); 38 | } catch (e) { 39 | this.logService.error(e); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/accounts/remove-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component"; 4 | 5 | @Component({ 6 | selector: "app-remove-password", 7 | templateUrl: "remove-password.component.html", 8 | }) 9 | export class RemovePasswordComponent extends BaseRemovePasswordComponent {} 10 | -------------------------------------------------------------------------------- /src/app/accounts/two-factor-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { Router } from "@angular/router"; 3 | 4 | import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component"; 5 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 6 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 7 | import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service"; 8 | 9 | @Component({ 10 | selector: "app-two-factor-options", 11 | templateUrl: "two-factor-options.component.html", 12 | }) 13 | export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { 14 | constructor( 15 | twoFactorService: TwoFactorService, 16 | router: Router, 17 | i18nService: I18nService, 18 | platformUtilsService: PlatformUtilsService 19 | ) { 20 | super(twoFactorService, router, i18nService, platformUtilsService, window); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/accounts/verify-email-token.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/accounts/verify-recover-delete.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ "deleteAccount" | i18n }}

5 |
6 |
7 | {{ "deleteAccountWarning" | i18n }} 8 |

9 | {{ email }} 10 |

11 |

{{ "deleteRecoverConfirmDesc" | i18n }}

12 |
13 |
14 | 26 | 27 | {{ "cancel" | i18n }} 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { DragDropModule } from "@angular/cdk/drag-drop"; 2 | import { NgModule } from "@angular/core"; 3 | import { FormsModule } from "@angular/forms"; 4 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 5 | import { InfiniteScrollModule } from "ngx-infinite-scroll"; 6 | 7 | import { AppComponent } from "./app.component"; 8 | import { OssRoutingModule } from "./oss-routing.module"; 9 | import { OssModule } from "./oss.module"; 10 | import { ServicesModule } from "./services/services.module"; 11 | import { WildcardRoutingModule } from "./wildcard-routing.module"; 12 | 13 | @NgModule({ 14 | imports: [ 15 | OssModule, 16 | BrowserAnimationsModule, 17 | FormsModule, 18 | ServicesModule, 19 | InfiniteScrollModule, 20 | DragDropModule, 21 | OssRoutingModule, 22 | WildcardRoutingModule, // Needs to be last to catch all non-existing routes 23 | ], 24 | declarations: [AppComponent], 25 | bootstrap: [AppComponent], 26 | }) 27 | export class AppModule {} 28 | -------------------------------------------------------------------------------- /src/app/components/nested-checkbox.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 | 14 |
15 |
16 |
17 | 25 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /src/app/components/nested-checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from "@angular/core"; 2 | 3 | import { Utils } from "jslib-common/misc/utils"; 4 | 5 | @Component({ 6 | selector: "app-nested-checkbox", 7 | templateUrl: "nested-checkbox.component.html", 8 | }) 9 | export class NestedCheckboxComponent { 10 | @Input() parentId: string; 11 | @Input() checkboxes: { id: string; get: () => boolean; set: (v: boolean) => void }[]; 12 | @Output() onSavedUser = new EventEmitter(); 13 | @Output() onDeletedUser = new EventEmitter(); 14 | 15 | get parentIndeterminate() { 16 | return !this.parentChecked && this.checkboxes.some((c) => c.get()); 17 | } 18 | 19 | get parentChecked() { 20 | return this.checkboxes.every((c) => c.get()); 21 | } 22 | 23 | set parentChecked(value: boolean) { 24 | this.checkboxes.forEach((c) => { 25 | c.set(value); 26 | }); 27 | } 28 | 29 | pascalize(s: string) { 30 | return Utils.camelToPascalCase(s); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/components/organization-switcher.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from "@angular/core"; 2 | 3 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 4 | import { OrganizationService } from "jslib-common/abstractions/organization.service"; 5 | import { Utils } from "jslib-common/misc/utils"; 6 | import { Organization } from "jslib-common/models/domain/organization"; 7 | 8 | import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; 9 | 10 | @Component({ 11 | selector: "app-organization-switcher", 12 | templateUrl: "organization-switcher.component.html", 13 | }) 14 | export class OrganizationSwitcherComponent implements OnInit { 15 | constructor(private organizationService: OrganizationService, private i18nService: I18nService) {} 16 | 17 | @Input() activeOrganization: Organization = null; 18 | organizations: Organization[] = []; 19 | 20 | loaded = false; 21 | 22 | async ngOnInit() { 23 | await this.load(); 24 | } 25 | 26 | async load() { 27 | const orgs = await this.organizationService.getAll(); 28 | this.organizations = orgs 29 | .filter((org) => NavigationPermissionsService.canAccessAdmin(org)) 30 | .sort(Utils.getSortFunction(this.i18nService, "name")); 31 | 32 | this.loaded = true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/password-reprompt.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component"; 4 | 5 | @Component({ 6 | templateUrl: "password-reprompt.component.html", 7 | }) 8 | export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} 9 | -------------------------------------------------------------------------------- /src/app/components/password-strength.component.html: -------------------------------------------------------------------------------- 1 |
2 |
10 | 11 | {{ text }} 12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/app/components/password-strength.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges } from "@angular/core"; 2 | 3 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 4 | 5 | @Component({ 6 | selector: "app-password-strength", 7 | templateUrl: "password-strength.component.html", 8 | }) 9 | export class PasswordStrengthComponent implements OnChanges { 10 | @Input() score?: number; 11 | @Input() showText = false; 12 | 13 | scoreWidth = 0; 14 | color = "bg-danger"; 15 | text: string; 16 | 17 | constructor(private i18nService: I18nService) {} 18 | 19 | ngOnChanges(): void { 20 | this.scoreWidth = this.score == null ? 0 : (this.score + 1) * 20; 21 | switch (this.score) { 22 | case 4: 23 | this.color = "bg-success"; 24 | this.text = this.i18nService.t("strong"); 25 | break; 26 | case 3: 27 | this.color = "bg-primary"; 28 | this.text = this.i18nService.t("good"); 29 | break; 30 | case 2: 31 | this.color = "bg-warning"; 32 | this.text = this.i18nService.t("weak"); 33 | break; 34 | default: 35 | this.color = "bg-danger"; 36 | this.text = this.score != null ? this.i18nService.t("weak") : null; 37 | break; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/premium-badge.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { MessagingService } from "jslib-common/abstractions/messaging.service"; 4 | 5 | @Component({ 6 | selector: "app-premium-badge", 7 | template: ` 8 | 11 | `, 12 | }) 13 | export class PremiumBadgeComponent { 14 | constructor(private messagingService: MessagingService) {} 15 | 16 | premiumRequired() { 17 | this.messagingService.send("premiumRequired"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/guards/home.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; 3 | 4 | import { AuthService } from "jslib-common/abstractions/auth.service"; 5 | import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus"; 6 | 7 | @Injectable() 8 | export class HomeGuard implements CanActivate { 9 | constructor(private router: Router, private authService: AuthService) {} 10 | 11 | async canActivate(route: ActivatedRouteSnapshot) { 12 | const authStatus = await this.authService.getAuthStatus(); 13 | 14 | if (authStatus === AuthenticationStatus.LoggedOut) { 15 | return this.router.createUrlTree(["/login"], { queryParams: route.queryParams }); 16 | } 17 | if (authStatus === AuthenticationStatus.Locked) { 18 | return this.router.createUrlTree(["/lock"], { queryParams: route.queryParams }); 19 | } 20 | return this.router.createUrlTree(["/vault"], { queryParams: route.queryParams }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/layouts/footer.component.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/app/layouts/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 4 | 5 | @Component({ 6 | selector: "app-footer", 7 | templateUrl: "footer.component.html", 8 | }) 9 | export class FooterComponent implements OnInit { 10 | version: string; 11 | year = "2015"; 12 | 13 | constructor(private platformUtilsService: PlatformUtilsService) {} 14 | 15 | async ngOnInit() { 16 | this.year = new Date().getFullYear().toString(); 17 | this.version = await this.platformUtilsService.getApplicationVersion(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/layouts/frontend-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | © {{ year }} Bitwarden Inc.
4 | {{ "versionNumber" | i18n: version }} 5 |
6 | -------------------------------------------------------------------------------- /src/app/layouts/frontend-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from "@angular/core"; 2 | 3 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 4 | 5 | @Component({ 6 | selector: "app-frontend-layout", 7 | templateUrl: "frontend-layout.component.html", 8 | }) 9 | export class FrontendLayoutComponent implements OnInit, OnDestroy { 10 | version: string; 11 | year = "2015"; 12 | 13 | constructor(private platformUtilsService: PlatformUtilsService) {} 14 | 15 | async ngOnInit() { 16 | this.year = new Date().getFullYear().toString(); 17 | this.version = await this.platformUtilsService.getApplicationVersion(); 18 | document.body.classList.add("layout_frontend"); 19 | } 20 | 21 | ngOnDestroy() { 22 | document.body.classList.remove("layout_frontend"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/layouts/user-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/layouts/user-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-user-layout", 5 | templateUrl: "user-layout.component.html", 6 | }) 7 | export class UserLayoutComponent implements OnInit { 8 | ngOnInit() { 9 | document.body.classList.remove("layout_frontend"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from "@angular/core"; 2 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 3 | 4 | import "bootstrap"; 5 | import "jquery"; 6 | import "popper.js"; 7 | 8 | require("../scss/styles.scss"); 9 | require("../scss/tailwind.css"); 10 | 11 | import { AppModule } from "./app.module"; 12 | 13 | if (process.env.NODE_ENV === "production") { 14 | enableProdMode(); 15 | } 16 | 17 | platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); 18 | -------------------------------------------------------------------------------- /src/app/modules/organizations/manage/organization-manage.module.ts: -------------------------------------------------------------------------------- 1 | import { ScrollingModule } from "@angular/cdk/scrolling"; 2 | import { NgModule } from "@angular/core"; 3 | 4 | import { SharedModule } from "../../shared.module"; 5 | 6 | import { EntityUsersComponent } from "./entity-users.component"; 7 | 8 | @NgModule({ 9 | imports: [SharedModule, ScrollingModule], 10 | declarations: [EntityUsersComponent], 11 | exports: [EntityUsersComponent], 12 | }) 13 | export class OrganizationManageModule {} 14 | -------------------------------------------------------------------------------- /src/app/modules/organizations/users/organization-user.module.ts: -------------------------------------------------------------------------------- 1 | import { ScrollingModule } from "@angular/cdk/scrolling"; 2 | import { NgModule } from "@angular/core"; 3 | 4 | import { LooseComponentsModule } from "../../loose-components.module"; 5 | import { SharedModule } from "../../shared.module"; 6 | 7 | import { EnrollMasterPasswordReset } from "./enroll-master-password-reset.component"; 8 | 9 | @NgModule({ 10 | imports: [SharedModule, ScrollingModule, LooseComponentsModule], 11 | declarations: [EnrollMasterPasswordReset], 12 | exports: [EnrollMasterPasswordReset], 13 | }) 14 | export class OrganizationUserModule {} 15 | -------------------------------------------------------------------------------- /src/app/modules/pipes/get-organization-name.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | import { Organization } from "jslib-common/models/domain/organization"; 4 | 5 | @Pipe({ 6 | name: "orgNameFromId", 7 | pure: true, 8 | }) 9 | export class GetOrgNameFromIdPipe implements PipeTransform { 10 | transform(value: string, organizations: Organization[]) { 11 | const orgName = organizations.find((o) => o.id === value)?.name; 12 | return orgName; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/modules/pipes/pipes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe"; 4 | 5 | @NgModule({ 6 | imports: [], 7 | declarations: [GetOrgNameFromIdPipe], 8 | exports: [GetOrgNameFromIdPipe], 9 | }) 10 | export class PipesModule {} 11 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/collection-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component"; 4 | 5 | @Component({ 6 | selector: "app-collection-filter", 7 | templateUrl: "collection-filter.component.html", 8 | }) 9 | export class CollectionFilterComponent extends BaseCollectionFilterComponent {} 10 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/folder-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component"; 4 | 5 | @Component({ 6 | selector: "app-folder-filter", 7 | templateUrl: "folder-filter.component.html", 8 | }) 9 | export class FolderFilterComponent extends BaseFolderFilterComponent {} 10 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/link-sso.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ "linkSso" | i18n }} 4 | 5 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/organization-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component"; 4 | 5 | @Component({ 6 | selector: "app-organization-filter", 7 | templateUrl: "organization-filter.component.html", 8 | }) 9 | export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { 10 | displayText = "allVaults"; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/status-filter.component.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
  • 4 | 5 | 8 | 9 |
  • 10 |
  • 15 | 16 | 19 | 20 |
  • 21 |
  • 26 | 27 | 30 | 31 |
  • 32 |
33 |
34 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/status-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component"; 4 | 5 | @Component({ 6 | selector: "app-status-filter", 7 | templateUrl: "status-filter.component.html", 8 | }) 9 | export class StatusFilterComponent extends BaseStatusFilterComponent {} 10 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/components/type-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component"; 4 | 5 | @Component({ 6 | selector: "app-type-filter", 7 | templateUrl: "type-filter.component.html", 8 | }) 9 | export class TypeFilterComponent extends BaseTypeFilterComponent {} 10 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/organization-vault-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { Organization } from "jslib-common/models/domain/organization"; 4 | 5 | import { VaultFilterComponent } from "./vault-filter.component"; 6 | 7 | @Component({ 8 | selector: "app-organization-vault-filter", 9 | templateUrl: "vault-filter.component.html", 10 | }) 11 | export class OrganizationVaultFilterComponent extends VaultFilterComponent { 12 | hideOrganizations = true; 13 | hideFavorites = true; 14 | hideFolders = true; 15 | 16 | organization: Organization; 17 | 18 | async initCollections() { 19 | if (this.organization.canEditAnyCollection) { 20 | return await this.vaultFilterService.buildAdminCollections(this.organization.id); 21 | } 22 | return await this.vaultFilterService.buildCollections(this.organization.id); 23 | } 24 | 25 | async reloadCollectionsAndFolders() { 26 | this.collections = await this.initCollections(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/vault-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from "@angular/core"; 2 | 3 | import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component"; 4 | 5 | import { VaultFilterService } from "./vault-filter.service"; 6 | 7 | @Component({ 8 | selector: "app-vault-filter", 9 | templateUrl: "vault-filter.component.html", 10 | }) 11 | export class VaultFilterComponent extends BaseVaultFilterComponent { 12 | @Output() onSearchTextChanged = new EventEmitter(); 13 | 14 | searchPlaceholder: string; 15 | searchText = ""; 16 | 17 | constructor(protected vaultFilterService: VaultFilterService) { 18 | // This empty constructor is required to provide the web vaultFilterService subclass to super() 19 | // TODO: refactor this to use proper dependency injection 20 | super(vaultFilterService); 21 | } 22 | 23 | searchTextChanged() { 24 | this.onSearchTextChanged.emit(this.searchText); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/modules/vault-filter/vault-filter.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { SharedModule } from "../shared.module"; 4 | 5 | import { CollectionFilterComponent } from "./components/collection-filter.component"; 6 | import { FolderFilterComponent } from "./components/folder-filter.component"; 7 | import { LinkSsoComponent } from "./components/link-sso.component"; 8 | import { OrganizationFilterComponent } from "./components/organization-filter.component"; 9 | import { OrganizationOptionsComponent } from "./components/organization-options.component"; 10 | import { StatusFilterComponent } from "./components/status-filter.component"; 11 | import { TypeFilterComponent } from "./components/type-filter.component"; 12 | import { OrganizationVaultFilterComponent } from "./organization-vault-filter.component"; 13 | import { VaultFilterComponent } from "./vault-filter.component"; 14 | import { VaultFilterService } from "./vault-filter.service"; 15 | 16 | @NgModule({ 17 | imports: [SharedModule], 18 | declarations: [ 19 | VaultFilterComponent, 20 | CollectionFilterComponent, 21 | FolderFilterComponent, 22 | OrganizationFilterComponent, 23 | OrganizationOptionsComponent, 24 | StatusFilterComponent, 25 | TypeFilterComponent, 26 | OrganizationVaultFilterComponent, 27 | LinkSsoComponent, 28 | ], 29 | exports: [VaultFilterComponent, OrganizationVaultFilterComponent], 30 | providers: [VaultFilterService], 31 | }) 32 | export class VaultFilterModule {} 33 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/individual-vault/individual-vault-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { IndividualVaultComponent } from "./individual-vault.component"; 5 | const routes: Routes = [ 6 | { 7 | path: "", 8 | component: IndividualVaultComponent, 9 | data: { titleId: "vaults" }, 10 | }, 11 | ]; 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule], 15 | }) 16 | export class IndividualVaultRoutingModule {} 17 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/individual-vault/individual-vault.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { VaultModule } from "../../vault.module"; 4 | 5 | import { IndividualVaultRoutingModule } from "./individual-vault-routing.module"; 6 | import { IndividualVaultComponent } from "./individual-vault.component"; 7 | 8 | @NgModule({ 9 | imports: [VaultModule, IndividualVaultRoutingModule], 10 | declarations: [IndividualVaultComponent], 11 | exports: [IndividualVaultComponent], 12 | }) 13 | export class IndividualVaultModule {} 14 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/organization-badge/organization-badge.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { SharedModule } from "../../../shared.module"; 4 | 5 | import { OrganizationNameBadgeComponent } from "./organization-name-badge.component"; 6 | 7 | @NgModule({ 8 | imports: [SharedModule], 9 | declarations: [OrganizationNameBadgeComponent], 10 | exports: [OrganizationNameBadgeComponent], 11 | }) 12 | export class OrganizationBadgeModule {} 13 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/organization-badge/organization-name-badge.component.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/organization-vault/organization-vault-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { OrganizationVaultComponent } from "./organization-vault.component"; 5 | const routes: Routes = [ 6 | { 7 | path: "", 8 | component: OrganizationVaultComponent, 9 | data: { titleId: "vaults" }, 10 | }, 11 | ]; 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule], 15 | }) 16 | export class OrganizationVaultRoutingModule {} 17 | -------------------------------------------------------------------------------- /src/app/modules/vault/modules/organization-vault/organization-vault.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { VaultModule } from "../../vault.module"; 4 | 5 | import { OrganizationVaultRoutingModule } from "./organization-vault-routing.module"; 6 | import { OrganizationVaultComponent } from "./organization-vault.component"; 7 | 8 | @NgModule({ 9 | imports: [VaultModule, OrganizationVaultRoutingModule], 10 | declarations: [OrganizationVaultComponent], 11 | exports: [OrganizationVaultComponent], 12 | }) 13 | export class OrganizationVaultModule {} 14 | -------------------------------------------------------------------------------- /src/app/modules/vault/vault.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { LooseComponentsModule } from "../loose-components.module"; 4 | import { SharedModule } from "../shared.module"; 5 | import { VaultFilterModule } from "../vault-filter/vault-filter.module"; 6 | 7 | import { VaultService } from "./vault.service"; 8 | 9 | @NgModule({ 10 | imports: [SharedModule, VaultFilterModule, LooseComponentsModule], 11 | exports: [SharedModule, VaultFilterModule, LooseComponentsModule], 12 | providers: [ 13 | { 14 | provide: VaultService, 15 | useClass: VaultService, 16 | }, 17 | ], 18 | }) 19 | export class VaultModule {} 20 | -------------------------------------------------------------------------------- /src/app/modules/vault/vault.service.ts: -------------------------------------------------------------------------------- 1 | import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model"; 2 | 3 | export class VaultService { 4 | calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string { 5 | if (vaultFilter.status === "favorites") { 6 | return "searchFavorites"; 7 | } 8 | if (vaultFilter.status === "trash") { 9 | return "searchTrash"; 10 | } 11 | if (vaultFilter.cipherType != null) { 12 | return "searchType"; 13 | } 14 | if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") { 15 | return "searchFolder"; 16 | } 17 | if (vaultFilter.selectedCollectionId != null) { 18 | return "searchCollection"; 19 | } 20 | if (vaultFilter.selectedOrganizationId != null) { 21 | return "searchOrganization"; 22 | } 23 | if (vaultFilter.myVaultOnly) { 24 | return "searchMyVault"; 25 | } 26 | 27 | return "searchVault"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/organizations/layouts/organization-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/organizations/manage/bulk/bulk-remove.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { ApiService } from "jslib-common/abstractions/api.service"; 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { OrganizationUserBulkRequest } from "jslib-common/models/request/organizationUserBulkRequest"; 6 | 7 | import { BulkUserDetails } from "./bulk-status.component"; 8 | 9 | @Component({ 10 | selector: "app-bulk-remove", 11 | templateUrl: "bulk-remove.component.html", 12 | }) 13 | export class BulkRemoveComponent { 14 | @Input() organizationId: string; 15 | @Input() users: BulkUserDetails[]; 16 | 17 | statuses: Map = new Map(); 18 | 19 | loading = false; 20 | done = false; 21 | error: string; 22 | 23 | constructor(protected apiService: ApiService, protected i18nService: I18nService) {} 24 | 25 | async submit() { 26 | this.loading = true; 27 | try { 28 | const response = await this.deleteUsers(); 29 | 30 | response.data.forEach((entry) => { 31 | const error = entry.error !== "" ? entry.error : this.i18nService.t("bulkRemovedMessage"); 32 | this.statuses.set(entry.id, error); 33 | }); 34 | this.done = true; 35 | } catch (e) { 36 | this.error = e.message; 37 | } 38 | 39 | this.loading = false; 40 | } 41 | 42 | protected async deleteUsers() { 43 | const request = new OrganizationUserBulkRequest(this.users.map((user) => user.id)); 44 | return await this.apiService.deleteManyOrganizationUsers(this.organizationId, request); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/organizations/manage/bulk/bulk-status.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType"; 4 | import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType"; 5 | 6 | export interface BulkUserDetails { 7 | id: string; 8 | name: string; 9 | email: string; 10 | status: OrganizationUserStatusType | ProviderUserStatusType; 11 | } 12 | 13 | type BulkStatusEntry = { 14 | user: BulkUserDetails; 15 | error: boolean; 16 | message: string; 17 | }; 18 | 19 | @Component({ 20 | selector: "app-bulk-status", 21 | templateUrl: "bulk-status.component.html", 22 | }) 23 | export class BulkStatusComponent { 24 | users: BulkStatusEntry[]; 25 | loading = false; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/organizations/manage/manage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { OrganizationService } from "jslib-common/abstractions/organization.service"; 5 | import { Organization } from "jslib-common/models/domain/organization"; 6 | 7 | @Component({ 8 | selector: "app-org-manage", 9 | templateUrl: "manage.component.html", 10 | }) 11 | export class ManageComponent implements OnInit { 12 | organization: Organization; 13 | accessPolicies = false; 14 | accessGroups = false; 15 | accessEvents = false; 16 | accessSso = false; 17 | 18 | constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} 19 | 20 | ngOnInit() { 21 | this.route.parent.params.subscribe(async (params) => { 22 | this.organization = await this.organizationService.get(params.organizationId); 23 | this.accessPolicies = this.organization.usePolicies; 24 | this.accessSso = this.organization.useSso; 25 | this.accessEvents = this.organization.useEvents; 26 | this.accessGroups = this.organization.useGroups; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/organizations/manage/policies.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | {{ "loading" | i18n }} 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 |
16 | {{ p.name | i18n }} 17 | {{ 18 | "enabled" | i18n 19 | }} 20 | {{ p.description | i18n }} 21 |
25 | 26 | -------------------------------------------------------------------------------- /src/app/organizations/manage/user-confirm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; 2 | 3 | import { CryptoService } from "jslib-common/abstractions/crypto.service"; 4 | import { LogService } from "jslib-common/abstractions/log.service"; 5 | import { StateService } from "jslib-common/abstractions/state.service"; 6 | 7 | @Component({ 8 | selector: "app-user-confirm", 9 | templateUrl: "user-confirm.component.html", 10 | }) 11 | export class UserConfirmComponent implements OnInit { 12 | @Input() name: string; 13 | @Input() userId: string; 14 | @Input() publicKey: Uint8Array; 15 | @Output() onConfirmedUser = new EventEmitter(); 16 | 17 | dontAskAgain = false; 18 | loading = true; 19 | fingerprint: string; 20 | formPromise: Promise; 21 | 22 | constructor( 23 | private cryptoService: CryptoService, 24 | private logService: LogService, 25 | private stateService: StateService 26 | ) {} 27 | 28 | async ngOnInit() { 29 | try { 30 | if (this.publicKey != null) { 31 | const fingerprint = await this.cryptoService.getFingerprint( 32 | this.userId, 33 | this.publicKey.buffer 34 | ); 35 | if (fingerprint != null) { 36 | this.fingerprint = fingerprint.join("-"); 37 | } 38 | } 39 | } catch (e) { 40 | this.logService.error(e); 41 | } 42 | this.loading = false; 43 | } 44 | 45 | async submit() { 46 | if (this.loading) { 47 | return; 48 | } 49 | 50 | if (this.dontAskAgain) { 51 | await this.stateService.setAutoConfirmFingerprints(true); 52 | } 53 | 54 | this.onConfirmedUser.emit(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/organizations/policies/base-policy.component.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnInit } from "@angular/core"; 2 | import { FormControl, FormGroup } from "@angular/forms"; 3 | 4 | import { PolicyType } from "jslib-common/enums/policyType"; 5 | import { Organization } from "jslib-common/models/domain/organization"; 6 | import { PolicyRequest } from "jslib-common/models/request/policyRequest"; 7 | import { PolicyResponse } from "jslib-common/models/response/policyResponse"; 8 | 9 | export abstract class BasePolicy { 10 | abstract name: string; 11 | abstract description: string; 12 | abstract type: PolicyType; 13 | abstract component: any; 14 | 15 | display(organization: Organization) { 16 | return true; 17 | } 18 | } 19 | 20 | @Directive() 21 | export abstract class BasePolicyComponent implements OnInit { 22 | @Input() policyResponse: PolicyResponse; 23 | @Input() policy: BasePolicy; 24 | 25 | enabled = new FormControl(false); 26 | data: FormGroup = null; 27 | 28 | ngOnInit(): void { 29 | this.enabled.setValue(this.policyResponse.enabled); 30 | 31 | if (this.policyResponse.data != null) { 32 | this.loadData(); 33 | } 34 | } 35 | 36 | loadData() { 37 | this.data.patchValue(this.policyResponse.data ?? {}); 38 | } 39 | 40 | buildRequestData() { 41 | if (this.data != null) { 42 | return this.data.value; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | buildRequest(policiesEnabledMap: Map) { 49 | const request = new PolicyRequest(); 50 | request.enabled = this.enabled.value; 51 | request.type = this.policy.type; 52 | request.data = this.buildRequestData(); 53 | 54 | return Promise.resolve(request); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/organizations/policies/disable-send.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "disableSendExemption" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/organizations/policies/disable-send.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PolicyType } from "jslib-common/enums/policyType"; 4 | 5 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 6 | 7 | export class DisableSendPolicy extends BasePolicy { 8 | name = "disableSend"; 9 | description = "disableSendPolicyDesc"; 10 | type = PolicyType.DisableSend; 11 | component = DisableSendPolicyComponent; 12 | } 13 | 14 | @Component({ 15 | selector: "policy-disable-send", 16 | templateUrl: "disable-send.component.html", 17 | }) 18 | export class DisableSendPolicyComponent extends BasePolicyComponent {} 19 | -------------------------------------------------------------------------------- /src/app/organizations/policies/password-generator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { FormBuilder } from "@angular/forms"; 3 | 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { PolicyType } from "jslib-common/enums/policyType"; 6 | 7 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 8 | 9 | export class PasswordGeneratorPolicy extends BasePolicy { 10 | name = "passwordGenerator"; 11 | description = "passwordGeneratorPolicyDesc"; 12 | type = PolicyType.PasswordGenerator; 13 | component = PasswordGeneratorPolicyComponent; 14 | } 15 | 16 | @Component({ 17 | selector: "policy-password-generator", 18 | templateUrl: "password-generator.component.html", 19 | }) 20 | export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { 21 | data = this.formBuilder.group({ 22 | defaultType: [null], 23 | minLength: [null], 24 | useUpper: [null], 25 | useLower: [null], 26 | useNumbers: [null], 27 | useSpecial: [null], 28 | minNumbers: [null], 29 | minSpecial: [null], 30 | minNumberWords: [null], 31 | capitalize: [null], 32 | includeNumber: [null], 33 | }); 34 | 35 | defaultTypes: { name: string; value: string }[]; 36 | 37 | constructor(private formBuilder: FormBuilder, i18nService: I18nService) { 38 | super(); 39 | 40 | this.defaultTypes = [ 41 | { name: i18nService.t("userPreference"), value: null }, 42 | { name: i18nService.t("password"), value: "password" }, 43 | { name: i18nService.t("passphrase"), value: "passphrase" }, 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/organizations/policies/personal-ownership.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "personalOwnershipExemption" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/app/organizations/policies/personal-ownership.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PolicyType } from "jslib-common/enums/policyType"; 4 | 5 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 6 | 7 | export class PersonalOwnershipPolicy extends BasePolicy { 8 | name = "personalOwnership"; 9 | description = "personalOwnershipPolicyDesc"; 10 | type = PolicyType.PersonalOwnership; 11 | component = PersonalOwnershipPolicyComponent; 12 | } 13 | 14 | @Component({ 15 | selector: "policy-personal-ownership", 16 | templateUrl: "personal-ownership.component.html", 17 | }) 18 | export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {} 19 | -------------------------------------------------------------------------------- /src/app/organizations/policies/require-sso.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "requireSsoPolicyReq" | i18n }} 3 | 4 | 5 | {{ "requireSsoExemption" | i18n }} 6 | 7 | 8 |
9 |
10 | 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/app/organizations/policies/require-sso.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 4 | import { PolicyType } from "jslib-common/enums/policyType"; 5 | import { Organization } from "jslib-common/models/domain/organization"; 6 | import { PolicyRequest } from "jslib-common/models/request/policyRequest"; 7 | 8 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 9 | 10 | export class RequireSsoPolicy extends BasePolicy { 11 | name = "requireSso"; 12 | description = "requireSsoPolicyDesc"; 13 | type = PolicyType.RequireSso; 14 | component = RequireSsoPolicyComponent; 15 | 16 | display(organization: Organization) { 17 | return organization.useSso; 18 | } 19 | } 20 | 21 | @Component({ 22 | selector: "policy-require-sso", 23 | templateUrl: "require-sso.component.html", 24 | }) 25 | export class RequireSsoPolicyComponent extends BasePolicyComponent { 26 | constructor(private i18nService: I18nService) { 27 | super(); 28 | } 29 | 30 | buildRequest(policiesEnabledMap: Map): Promise { 31 | const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false; 32 | if (this.enabled.value && !singleOrgEnabled) { 33 | throw new Error(this.i18nService.t("requireSsoPolicyReqError")); 34 | } 35 | 36 | return super.buildRequest(policiesEnabledMap); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/organizations/policies/reset-password.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "keyConnectorPolicyRestriction" | i18n }} 3 | 4 | 5 | 6 | {{ "resetPasswordPolicyWarning" | i18n }} 7 | 8 | 9 |
10 |
11 | 18 | 19 |
20 |
21 | 22 |
23 |

{{ "resetPasswordPolicyAutoEnroll" | i18n }}

24 |

{{ "resetPasswordPolicyAutoEnrollDescription" | i18n }}

25 | 26 | {{ "resetPasswordPolicyAutoEnrollWarning" | i18n }} 27 | 28 |
29 | 36 | 39 |
40 |
41 | -------------------------------------------------------------------------------- /src/app/organizations/policies/reset-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { FormBuilder } from "@angular/forms"; 3 | 4 | import { OrganizationService } from "jslib-common/abstractions/organization.service"; 5 | import { PolicyType } from "jslib-common/enums/policyType"; 6 | import { Organization } from "jslib-common/models/domain/organization"; 7 | 8 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 9 | 10 | export class ResetPasswordPolicy extends BasePolicy { 11 | name = "resetPasswordPolicy"; 12 | description = "resetPasswordPolicyDescription"; 13 | type = PolicyType.ResetPassword; 14 | component = ResetPasswordPolicyComponent; 15 | 16 | display(organization: Organization) { 17 | return organization.useResetPassword; 18 | } 19 | } 20 | 21 | @Component({ 22 | selector: "policy-reset-password", 23 | templateUrl: "reset-password.component.html", 24 | }) 25 | export class ResetPasswordPolicyComponent extends BasePolicyComponent { 26 | data = this.formBuilder.group({ 27 | autoEnrollEnabled: false, 28 | }); 29 | 30 | defaultTypes: { name: string; value: string }[]; 31 | showKeyConnectorInfo = false; 32 | 33 | constructor(private formBuilder: FormBuilder, private organizationService: OrganizationService) { 34 | super(); 35 | } 36 | 37 | async ngOnInit() { 38 | super.ngOnInit(); 39 | const organization = await this.organizationService.get(this.policyResponse.organizationId); 40 | this.showKeyConnectorInfo = organization.keyConnectorEnabled; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/organizations/policies/send-options.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "sendOptionsExemption" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 15 |
16 |
17 | 18 |
19 |

{{ "options" | i18n }}

20 |
21 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/app/organizations/policies/send-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { FormBuilder } from "@angular/forms"; 3 | 4 | import { PolicyType } from "jslib-common/enums/policyType"; 5 | 6 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 7 | 8 | export class SendOptionsPolicy extends BasePolicy { 9 | name = "sendOptions"; 10 | description = "sendOptionsPolicyDesc"; 11 | type = PolicyType.SendOptions; 12 | component = SendOptionsPolicyComponent; 13 | } 14 | 15 | @Component({ 16 | selector: "policy-send-options", 17 | templateUrl: "send-options.component.html", 18 | }) 19 | export class SendOptionsPolicyComponent extends BasePolicyComponent { 20 | data = this.formBuilder.group({ 21 | disableHideEmail: false, 22 | }); 23 | 24 | constructor(private formBuilder: FormBuilder) { 25 | super(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/organizations/policies/single-org.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "singleOrgPolicyWarning" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/organizations/policies/single-org.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 4 | import { PolicyType } from "jslib-common/enums/policyType"; 5 | import { PolicyRequest } from "jslib-common/models/request/policyRequest"; 6 | 7 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 8 | 9 | export class SingleOrgPolicy extends BasePolicy { 10 | name = "singleOrg"; 11 | description = "singleOrgDesc"; 12 | type = PolicyType.SingleOrg; 13 | component = SingleOrgPolicyComponent; 14 | } 15 | 16 | @Component({ 17 | selector: "policy-single-org", 18 | templateUrl: "single-org.component.html", 19 | }) 20 | export class SingleOrgPolicyComponent extends BasePolicyComponent { 21 | constructor(private i18nService: I18nService) { 22 | super(); 23 | } 24 | 25 | buildRequest(policiesEnabledMap: Map): Promise { 26 | if (!this.enabled.value) { 27 | if (policiesEnabledMap.get(PolicyType.RequireSso) ?? false) { 28 | throw new Error( 29 | this.i18nService.t("disableRequiredError", this.i18nService.t("requireSso")) 30 | ); 31 | } 32 | 33 | if (policiesEnabledMap.get(PolicyType.MaximumVaultTimeout) ?? false) { 34 | throw new Error( 35 | this.i18nService.t("disableRequiredError", this.i18nService.t("maximumVaultTimeoutLabel")) 36 | ); 37 | } 38 | } 39 | 40 | return super.buildRequest(policiesEnabledMap); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/organizations/policies/two-factor-authentication.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "twoStepLoginPolicyWarning" | i18n }} 3 | 4 | 5 |
6 |
7 | 14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/organizations/policies/two-factor-authentication.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PolicyType } from "jslib-common/enums/policyType"; 4 | 5 | import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; 6 | 7 | export class TwoFactorAuthenticationPolicy extends BasePolicy { 8 | name = "twoStepLogin"; 9 | description = "twoStepLoginPolicyDesc"; 10 | type = PolicyType.TwoFactorAuthentication; 11 | component = TwoFactorAuthenticationPolicyComponent; 12 | } 13 | 14 | @Component({ 15 | selector: "policy-two-factor-authentication", 16 | templateUrl: "two-factor-authentication.component.html", 17 | }) 18 | export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {} 19 | -------------------------------------------------------------------------------- /src/app/organizations/settings/change-plan.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |

{{ "changeBillingPlan" | i18n }}

7 |

{{ "changeBillingPlanUpgrade" | i18n }}

8 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/app/organizations/settings/change-plan.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from "@angular/core"; 2 | 3 | import { LogService } from "jslib-common/abstractions/log.service"; 4 | import { PlanType } from "jslib-common/enums/planType"; 5 | import { ProductType } from "jslib-common/enums/productType"; 6 | 7 | @Component({ 8 | selector: "app-change-plan", 9 | templateUrl: "change-plan.component.html", 10 | }) 11 | export class ChangePlanComponent { 12 | @Input() organizationId: string; 13 | @Output() onChanged = new EventEmitter(); 14 | @Output() onCanceled = new EventEmitter(); 15 | 16 | formPromise: Promise; 17 | defaultUpgradePlan: PlanType = PlanType.FamiliesAnnually; 18 | defaultUpgradeProduct: ProductType = ProductType.Families; 19 | 20 | constructor(private logService: LogService) {} 21 | 22 | async submit() { 23 | try { 24 | this.onChanged.emit(); 25 | } catch (e) { 26 | this.logService.error(e); 27 | } 28 | } 29 | 30 | cancel() { 31 | this.onCanceled.emit(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/organizations/settings/download-license.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |

{{ "downloadLicense" | i18n }}

7 |
8 |
9 |
10 | 11 | 18 | 19 | 20 |
21 | 29 |
30 |
31 | 35 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /src/app/organizations/settings/download-license.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from "@angular/core"; 2 | 3 | import { ApiService } from "jslib-common/abstractions/api.service"; 4 | import { LogService } from "jslib-common/abstractions/log.service"; 5 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 6 | 7 | @Component({ 8 | selector: "app-download-license", 9 | templateUrl: "download-license.component.html", 10 | }) 11 | export class DownloadLicenseComponent { 12 | @Input() organizationId: string; 13 | @Output() onDownloaded = new EventEmitter(); 14 | @Output() onCanceled = new EventEmitter(); 15 | 16 | installationId: string; 17 | formPromise: Promise; 18 | 19 | constructor( 20 | private apiService: ApiService, 21 | private platformUtilsService: PlatformUtilsService, 22 | private logService: LogService 23 | ) {} 24 | 25 | async submit() { 26 | if (this.installationId == null || this.installationId === "") { 27 | return; 28 | } 29 | 30 | try { 31 | this.formPromise = this.apiService.getOrganizationLicense( 32 | this.organizationId, 33 | this.installationId 34 | ); 35 | const license = await this.formPromise; 36 | const licenseString = JSON.stringify(license, null, 2); 37 | this.platformUtilsService.saveFile( 38 | window, 39 | licenseString, 40 | null, 41 | "bitwarden_organization_license.json" 42 | ); 43 | this.onDownloaded.emit(); 44 | } catch (e) { 45 | this.logService.error(e); 46 | } 47 | } 48 | 49 | cancel() { 50 | this.onCanceled.emit(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/organizations/settings/image-subscription-hidden.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | // Component is used so that the SVG can embed CSS color variables 4 | @Component({ 5 | selector: "app-image-org-subscription-hidden", 6 | templateUrl: "image-subscription-hidden.component.svg", 7 | }) 8 | export class ImageSubscriptionHiddenComponent {} 9 | -------------------------------------------------------------------------------- /src/app/organizations/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /src/app/organizations/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { OrganizationService } from "jslib-common/abstractions/organization.service"; 5 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 6 | 7 | @Component({ 8 | selector: "app-org-settings", 9 | templateUrl: "settings.component.html", 10 | }) 11 | export class SettingsComponent { 12 | access2fa = false; 13 | showBilling: boolean; 14 | 15 | constructor( 16 | private route: ActivatedRoute, 17 | private organizationService: OrganizationService, 18 | private platformUtilsService: PlatformUtilsService 19 | ) {} 20 | 21 | ngOnInit() { 22 | this.route.parent.params.subscribe(async (params) => { 23 | const organization = await this.organizationService.get(params.organizationId); 24 | this.showBilling = !this.platformUtilsService.isSelfHost() && organization.canManageBilling; 25 | this.access2fa = organization.use2fa; 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/organizations/sponsorships/accept-family-sponsorship.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 10 | {{ "loading" | i18n }} 11 |

12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/organizations/sponsorships/accept-family-sponsorship.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { BaseAcceptComponent } from "src/app/common/base.accept.component"; 4 | 5 | @Component({ 6 | selector: "app-accept-family-sponsorship", 7 | templateUrl: "accept-family-sponsorship.component.html", 8 | }) 9 | export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { 10 | failedShortMessage = "inviteAcceptFailedShort"; 11 | failedMessage = "inviteAcceptFailed"; 12 | 13 | requiredParameters = ["email", "token"]; 14 | 15 | async authedHandler(qParams: any) { 16 | this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams }); 17 | } 18 | 19 | async unauthedHandler(qParams: any) { 20 | if (!qParams.register) { 21 | this.router.navigate(["/login"], { queryParams: { email: qParams.email } }); 22 | } else { 23 | this.router.navigate(["/register"], { queryParams: { email: qParams.email } }); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/organizations/tools/tools.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | 4 | import { MessagingService } from "jslib-common/abstractions/messaging.service"; 5 | import { OrganizationService } from "jslib-common/abstractions/organization.service"; 6 | import { Organization } from "jslib-common/models/domain/organization"; 7 | 8 | @Component({ 9 | selector: "app-org-tools", 10 | templateUrl: "tools.component.html", 11 | }) 12 | export class ToolsComponent { 13 | organization: Organization; 14 | accessReports = false; 15 | loading = true; 16 | 17 | constructor( 18 | private route: ActivatedRoute, 19 | private organizationService: OrganizationService, 20 | private messagingService: MessagingService 21 | ) {} 22 | 23 | ngOnInit() { 24 | this.route.parent.params.subscribe(async (params) => { 25 | this.organization = await this.organizationService.get(params.organizationId); 26 | // TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now 27 | // since all paid plans include useTotp 28 | this.accessReports = this.organization.useTotp; 29 | this.loading = false; 30 | }); 31 | } 32 | 33 | upgradeOrganization() { 34 | this.messagingService.send("upgradeOrganization", { organizationId: this.organization.id }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/oss.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { LooseComponentsModule } from "./modules/loose-components.module"; 4 | import { OrganizationManageModule } from "./modules/organizations/manage/organization-manage.module"; 5 | import { OrganizationUserModule } from "./modules/organizations/users/organization-user.module"; 6 | import { PipesModule } from "./modules/pipes/pipes.module"; 7 | import { SharedModule } from "./modules/shared.module"; 8 | import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module"; 9 | import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module"; 10 | 11 | @NgModule({ 12 | imports: [ 13 | SharedModule, 14 | LooseComponentsModule, 15 | VaultFilterModule, 16 | OrganizationBadgeModule, 17 | PipesModule, 18 | OrganizationManageModule, 19 | OrganizationUserModule, 20 | ], 21 | exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule], 22 | bootstrap: [], 23 | }) 24 | export class OssModule {} 25 | -------------------------------------------------------------------------------- /src/app/polyfills.ts: -------------------------------------------------------------------------------- 1 | import "core-js/stable"; 2 | require("zone.js/dist/zone"); 3 | 4 | if (process.env.NODE_ENV === "production") { 5 | // Production 6 | } else { 7 | // Development and test 8 | Error["stackTraceLimit"] = Infinity; 9 | require("zone.js/dist/long-stack-trace-zone"); 10 | } 11 | 12 | // Other polyfills 13 | require("whatwg-fetch"); 14 | require("webcrypto-shim"); 15 | require("date-input-polyfill"); 16 | -------------------------------------------------------------------------------- /src/app/providers/providers.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 6 |

7 | 8 | {{ "loading" | i18n }} 9 |

10 | 11 | 12 | 13 | 14 | 17 | 28 | 29 | 30 |
15 | 16 | 18 | {{ p.name }} 19 | 20 | 25 | {{ "providerIsDisabled" | i18n }} 26 | 27 |
31 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /src/app/providers/providers.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 4 | import { ProviderService } from "jslib-common/abstractions/provider.service"; 5 | import { Utils } from "jslib-common/misc/utils"; 6 | import { Provider } from "jslib-common/models/domain/provider"; 7 | 8 | @Component({ 9 | selector: "app-providers", 10 | templateUrl: "providers.component.html", 11 | }) 12 | export class ProvidersComponent implements OnInit { 13 | providers: Provider[]; 14 | loaded = false; 15 | actionPromise: Promise; 16 | 17 | constructor(private providerService: ProviderService, private i18nService: I18nService) {} 18 | 19 | async ngOnInit() { 20 | document.body.classList.remove("layout_frontend"); 21 | await this.load(); 22 | } 23 | 24 | async load() { 25 | const providers = await this.providerService.getAll(); 26 | providers.sort(Utils.getSortFunction(this.i18nService, "name")); 27 | this.providers = providers; 28 | this.loaded = true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/reports/breach-report.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | import { AuditService } from "jslib-common/abstractions/audit.service"; 4 | import { StateService } from "jslib-common/abstractions/state.service"; 5 | import { BreachAccountResponse } from "jslib-common/models/response/breachAccountResponse"; 6 | 7 | @Component({ 8 | selector: "app-breach-report", 9 | templateUrl: "breach-report.component.html", 10 | }) 11 | export class BreachReportComponent implements OnInit { 12 | error = false; 13 | username: string; 14 | checkedUsername: string; 15 | breachedAccounts: BreachAccountResponse[] = []; 16 | formPromise: Promise; 17 | 18 | constructor(private auditService: AuditService, private stateService: StateService) {} 19 | 20 | async ngOnInit() { 21 | this.username = await this.stateService.getEmail(); 22 | } 23 | 24 | async submit() { 25 | this.error = false; 26 | this.username = this.username.toLowerCase(); 27 | try { 28 | this.formPromise = this.auditService.breachedAccounts(this.username); 29 | this.breachedAccounts = await this.formPromise; 30 | } catch { 31 | this.error = true; 32 | } 33 | this.checkedUsername = this.username; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/reports/report-card.component.html: -------------------------------------------------------------------------------- 1 | 6 |
7 |
11 |
12 |
13 |
14 |

{{ report.title | i18n }}

15 |

{{ report.description | i18n }}

16 |
17 | {{ "premium" | i18n }} 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/reports/report-list.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |

{{ "reportsDesc" | i18n }}

6 | 7 |
8 |
9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/app/reports/report-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { ReportTypes } from "./report-card.component"; 4 | 5 | @Component({ 6 | selector: "app-report-list", 7 | templateUrl: "report-list.component.html", 8 | }) 9 | export class ReportListComponent { 10 | reports = [ 11 | ReportTypes.exposedPasswords, 12 | ReportTypes.reusedPasswords, 13 | ReportTypes.weakPasswords, 14 | ReportTypes.unsecuredWebsites, 15 | ReportTypes.inactive2fa, 16 | ReportTypes.dataBreach, 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/reports/reports.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 12 |
13 | -------------------------------------------------------------------------------- /src/app/reports/reports.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from "@angular/core"; 2 | import { NavigationEnd, Router } from "@angular/router"; 3 | import { Subscription } from "rxjs"; 4 | import { filter } from "rxjs/operators"; 5 | 6 | @Component({ 7 | selector: "app-reports", 8 | templateUrl: "reports.component.html", 9 | }) 10 | export class ReportsComponent implements OnDestroy { 11 | homepage = true; 12 | subscription: Subscription; 13 | 14 | constructor(router: Router) { 15 | this.subscription = router.events 16 | .pipe(filter((event) => event instanceof NavigationEnd)) 17 | .subscribe((event) => { 18 | this.homepage = (event as NavigationEnd).url == "/reports"; 19 | }); 20 | } 21 | 22 | ngOnDestroy(): void { 23 | this.subscription?.unsubscribe(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/send/efflux-dates.component.ts: -------------------------------------------------------------------------------- 1 | import { DatePipe } from "@angular/common"; 2 | import { Component } from "@angular/core"; 3 | import { ControlContainer, NgForm } from "@angular/forms"; 4 | 5 | import { EffluxDatesComponent as BaseEffluxDatesComponent } from "jslib-angular/components/send/efflux-dates.component"; 6 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 7 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 8 | 9 | @Component({ 10 | selector: "app-send-efflux-dates", 11 | templateUrl: "efflux-dates.component.html", 12 | viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], 13 | }) 14 | export class EffluxDatesComponent extends BaseEffluxDatesComponent { 15 | constructor( 16 | protected i18nService: I18nService, 17 | protected platformUtilsService: PlatformUtilsService, 18 | protected datePipe: DatePipe 19 | ) { 20 | super(i18nService, platformUtilsService, datePipe); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/services/policy-list.service.ts: -------------------------------------------------------------------------------- 1 | import { BasePolicy } from "../organizations/policies/base-policy.component"; 2 | 3 | export class PolicyListService { 4 | private policies: BasePolicy[] = []; 5 | 6 | addPolicies(policies: BasePolicy[]) { 7 | this.policies.push(...policies); 8 | } 9 | 10 | getPolicies(): BasePolicy[] { 11 | return this.policies; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/settings/account.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 |

{{ "changeEmail" | i18n }}

8 |
9 | 10 |
11 |
12 |

{{ "dangerZone" | i18n }}

13 |
14 |
15 |
16 |

{{ "dangerZoneDesc" | i18n }}

17 | 20 | 23 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/app/settings/adjust-payment.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |

7 | {{ (currentType != null ? "changePaymentMethod" : "addPaymentMethod") | i18n }} 8 |

9 | 10 | 11 | 15 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/app/settings/api-key.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { LogService } from "jslib-common/abstractions/log.service"; 4 | import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; 5 | import { SecretVerificationRequest } from "jslib-common/models/request/secretVerificationRequest"; 6 | import { ApiKeyResponse } from "jslib-common/models/response/apiKeyResponse"; 7 | import { Verification } from "jslib-common/types/verification"; 8 | 9 | @Component({ 10 | selector: "app-api-key", 11 | templateUrl: "api-key.component.html", 12 | }) 13 | export class ApiKeyComponent { 14 | keyType: string; 15 | isRotation: boolean; 16 | postKey: (entityId: string, request: SecretVerificationRequest) => Promise; 17 | entityId: string; 18 | scope: string; 19 | grantType: string; 20 | apiKeyTitle: string; 21 | apiKeyWarning: string; 22 | apiKeyDescription: string; 23 | 24 | masterPassword: Verification; 25 | formPromise: Promise; 26 | clientId: string; 27 | clientSecret: string; 28 | 29 | constructor( 30 | private userVerificationService: UserVerificationService, 31 | private logService: LogService 32 | ) {} 33 | 34 | async submit() { 35 | try { 36 | this.formPromise = this.userVerificationService 37 | .buildRequest(this.masterPassword) 38 | .then((request) => this.postKey(this.entityId, request)); 39 | const response = await this.formPromise; 40 | this.clientSecret = response.apiKey; 41 | this.clientId = `${this.keyType}.${this.entityId}`; 42 | } catch (e) { 43 | this.logService.error(e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/settings/create-organization.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |

{{ "newOrganizationDesc" | i18n }}

8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /src/app/settings/create-organization.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from "@angular/core"; 2 | import { ActivatedRoute } from "@angular/router"; 3 | import { first } from "rxjs/operators"; 4 | 5 | import { PlanType } from "jslib-common/enums/planType"; 6 | import { ProductType } from "jslib-common/enums/productType"; 7 | 8 | import { OrganizationPlansComponent } from "./organization-plans.component"; 9 | 10 | @Component({ 11 | selector: "app-create-organization", 12 | templateUrl: "create-organization.component.html", 13 | }) 14 | export class CreateOrganizationComponent implements OnInit { 15 | @ViewChild(OrganizationPlansComponent, { static: true }) 16 | orgPlansComponent: OrganizationPlansComponent; 17 | 18 | constructor(private route: ActivatedRoute) {} 19 | 20 | ngOnInit() { 21 | this.route.queryParams.pipe(first()).subscribe(async (qParams) => { 22 | if (qParams.plan === "families") { 23 | this.orgPlansComponent.plan = PlanType.FamiliesAnnually; 24 | this.orgPlansComponent.product = ProductType.Families; 25 | } else if (qParams.plan === "teams") { 26 | this.orgPlansComponent.plan = PlanType.TeamsAnnually; 27 | this.orgPlansComponent.product = ProductType.Teams; 28 | } else if (qParams.plan === "enterprise") { 29 | this.orgPlansComponent.plan = PlanType.EnterpriseAnnually; 30 | this.orgPlansComponent.product = ProductType.Enterprise; 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/settings/deauthorize-sessions.component.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/app/settings/delete-account.component.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/app/settings/purge-vault.component.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/app/settings/security-keys.component.html: -------------------------------------------------------------------------------- 1 | 2 |
6 |

{{ "apiKey" | i18n }}

7 |
8 |

9 | {{ "userApiKeyDesc" | i18n }} 10 |

11 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/settings/security-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { ChangePasswordComponent } from "./change-password.component"; 5 | import { SecurityKeysComponent } from "./security-keys.component"; 6 | import { SecurityComponent } from "./security.component"; 7 | import { TwoFactorSetupComponent } from "./two-factor-setup.component"; 8 | 9 | const routes: Routes = [ 10 | { 11 | path: "", 12 | component: SecurityComponent, 13 | data: { titleId: "security" }, 14 | children: [ 15 | { path: "", pathMatch: "full", redirectTo: "change-password" }, 16 | { 17 | path: "change-password", 18 | component: ChangePasswordComponent, 19 | data: { titleId: "masterPassword" }, 20 | }, 21 | { 22 | path: "two-factor", 23 | component: TwoFactorSetupComponent, 24 | data: { titleId: "twoStepLogin" }, 25 | }, 26 | { 27 | path: "security-keys", 28 | component: SecurityKeysComponent, 29 | data: { titleId: "keys" }, 30 | }, 31 | ], 32 | }, 33 | ]; 34 | 35 | @NgModule({ 36 | imports: [RouterModule.forChild(routes)], 37 | exports: [RouterModule], 38 | }) 39 | export class SecurityRoutingModule {} 40 | -------------------------------------------------------------------------------- /src/app/settings/security.component.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/settings/security.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; 4 | 5 | @Component({ 6 | selector: "app-security", 7 | templateUrl: "security.component.html", 8 | }) 9 | export class SecurityComponent { 10 | showChangePassword = true; 11 | 12 | constructor(private keyConnectorService: KeyConnectorService) {} 13 | 14 | async ngOnInit() { 15 | this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/settings/subscription-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { PaymentMethodComponent } from "./payment-method.component"; 5 | import { PremiumComponent } from "./premium.component"; 6 | import { SubscriptionComponent } from "./subscription.component"; 7 | import { UserBillingHistoryComponent } from "./user-billing-history.component"; 8 | import { UserSubscriptionComponent } from "./user-subscription.component"; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: "", 13 | component: SubscriptionComponent, 14 | data: { titleId: "subscription" }, 15 | children: [ 16 | { path: "", pathMatch: "full", redirectTo: "premium" }, 17 | { 18 | path: "user-subscription", 19 | component: UserSubscriptionComponent, 20 | data: { titleId: "premiumMembership" }, 21 | }, 22 | { 23 | path: "premium", 24 | component: PremiumComponent, 25 | data: { titleId: "goPremium" }, 26 | }, 27 | { 28 | path: "payment-method", 29 | component: PaymentMethodComponent, 30 | data: { titleId: "paymentMethod" }, 31 | }, 32 | { 33 | path: "billing-history", 34 | component: UserBillingHistoryComponent, 35 | data: { titleId: "billingHistory" }, 36 | }, 37 | ], 38 | }, 39 | ]; 40 | 41 | @NgModule({ 42 | imports: [RouterModule.forChild(routes)], 43 | exports: [RouterModule], 44 | }) 45 | export class SubscriptionRoutingModule {} 46 | -------------------------------------------------------------------------------- /src/app/settings/subscription.component.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | -------------------------------------------------------------------------------- /src/app/settings/subscription.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 4 | import { TokenService } from "jslib-common/abstractions/token.service"; 5 | 6 | @Component({ 7 | selector: "app-subscription", 8 | templateUrl: "subscription.component.html", 9 | }) 10 | export class SubscriptionComponent { 11 | hasPremium: boolean; 12 | selfHosted: boolean; 13 | 14 | constructor( 15 | private tokenService: TokenService, 16 | private platformUtilsService: PlatformUtilsService 17 | ) {} 18 | 19 | async ngOnInit() { 20 | this.hasPremium = await this.tokenService.getPremium(); 21 | this.selfHosted = this.platformUtilsService.isSelfHost(); 22 | } 23 | 24 | get subscriptionRoute(): string { 25 | return this.hasPremium ? "user-subscription" : "premium"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/settings/two-factor-verify.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 16 |
17 | -------------------------------------------------------------------------------- /src/app/settings/update-license.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | {{ 6 | "licenseFileDesc" 7 | | i18n 8 | : (!organizationId 9 | ? "bitwarden_premium_license.json" 10 | : "bitwarden_organization_license.json") 11 | }} 12 |
13 | 17 | 20 |
21 | -------------------------------------------------------------------------------- /src/app/settings/vault-timeout-input.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }} 3 | 4 | 5 |
6 |
7 | 8 | 16 | {{ "vaultTimeoutDesc" | i18n }} 17 |
18 |
19 | 20 |
21 |
22 | 30 | {{ "hours" | i18n }} 31 |
32 |
33 | 41 | {{ "minutes" | i18n }} 42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /src/app/settings/vault-timeout-input.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms"; 3 | 4 | import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component"; 5 | 6 | @Component({ 7 | selector: "app-vault-timeout-input", 8 | templateUrl: "vault-timeout-input.component.html", 9 | providers: [ 10 | { 11 | provide: NG_VALUE_ACCESSOR, 12 | multi: true, 13 | useExisting: VaultTimeoutInputComponent, 14 | }, 15 | { 16 | provide: NG_VALIDATORS, 17 | multi: true, 18 | useExisting: VaultTimeoutInputComponent, 19 | }, 20 | ], 21 | }) 22 | export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} 23 | -------------------------------------------------------------------------------- /src/app/settings/verify-email.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ "verifyEmail" | i18n }} 4 |
5 |
6 |

{{ "verifyEmailDesc" | i18n }}

7 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/settings/verify-email.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { ApiService } from "jslib-common/abstractions/api.service"; 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { LogService } from "jslib-common/abstractions/log.service"; 6 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 7 | 8 | @Component({ 9 | selector: "app-verify-email", 10 | templateUrl: "verify-email.component.html", 11 | }) 12 | export class VerifyEmailComponent { 13 | actionPromise: Promise; 14 | 15 | constructor( 16 | private apiService: ApiService, 17 | private i18nService: I18nService, 18 | private platformUtilsService: PlatformUtilsService, 19 | private logService: LogService 20 | ) {} 21 | 22 | async send() { 23 | if (this.actionPromise != null) { 24 | return; 25 | } 26 | try { 27 | this.actionPromise = this.apiService.postAccountVerifyEmail(); 28 | await this.actionPromise; 29 | this.platformUtilsService.showToast( 30 | "success", 31 | null, 32 | this.i18nService.t("checkInboxForVerification") 33 | ); 34 | } catch (e) { 35 | this.logService.error(e); 36 | } 37 | this.actionPromise = null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/tools/export.component.html: -------------------------------------------------------------------------------- 1 |
8 | 11 | 12 | 13 | {{ "personalVaultExportPolicyInEffect" | i18n }} 14 | 15 | 19 | 20 |
21 |
22 | 23 | 26 |
27 |
28 |
29 |
30 | 31 | 32 |
33 |
34 | 42 |
43 | -------------------------------------------------------------------------------- /src/app/tools/password-generator-history.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component"; 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; 6 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 7 | 8 | @Component({ 9 | selector: "app-password-generator-history", 10 | templateUrl: "password-generator-history.component.html", 11 | }) 12 | export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { 13 | constructor( 14 | passwordGenerationService: PasswordGenerationService, 15 | platformUtilsService: PlatformUtilsService, 16 | i18nService: I18nService 17 | ) { 18 | super(passwordGenerationService, platformUtilsService, i18nService, window); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/tools/tools.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 19 |
20 | 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/app/tools/tools.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | import { MessagingService } from "jslib-common/abstractions/messaging.service"; 4 | import { StateService } from "jslib-common/abstractions/state.service"; 5 | 6 | @Component({ 7 | selector: "app-tools", 8 | templateUrl: "tools.component.html", 9 | }) 10 | export class ToolsComponent implements OnInit { 11 | canAccessPremium = false; 12 | 13 | constructor(private stateService: StateService, private messagingService: MessagingService) {} 14 | 15 | async ngOnInit() { 16 | this.canAccessPremium = await this.stateService.getCanAccessPremium(); 17 | } 18 | 19 | premiumRequired() { 20 | if (!this.canAccessPremium) { 21 | this.messagingService.send("premiumRequired"); 22 | return; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/vault/add-edit-custom-fields.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | 3 | import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "jslib-angular/components/add-edit-custom-fields.component"; 4 | import { EventService } from "jslib-common/abstractions/event.service"; 5 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 6 | 7 | @Component({ 8 | selector: "app-vault-add-edit-custom-fields", 9 | templateUrl: "add-edit-custom-fields.component.html", 10 | }) 11 | export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { 12 | @Input() viewOnly: boolean; 13 | @Input() copy: (value: string, typeI18nKey: string, aType: string) => void; 14 | 15 | constructor(i18nService: I18nService, eventService: EventService) { 16 | super(i18nService, eventService); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/vault/bulk-delete.component.html: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /src/app/vault/bulk-move.component.html: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /src/app/vault/bulk-move.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; 2 | 3 | import { CipherService } from "jslib-common/abstractions/cipher.service"; 4 | import { FolderService } from "jslib-common/abstractions/folder.service"; 5 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 6 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 7 | import { FolderView } from "jslib-common/models/view/folderView"; 8 | 9 | @Component({ 10 | selector: "app-vault-bulk-move", 11 | templateUrl: "bulk-move.component.html", 12 | }) 13 | export class BulkMoveComponent implements OnInit { 14 | @Input() cipherIds: string[] = []; 15 | @Output() onMoved = new EventEmitter(); 16 | 17 | folderId: string = null; 18 | folders: FolderView[] = []; 19 | formPromise: Promise; 20 | 21 | constructor( 22 | private cipherService: CipherService, 23 | private platformUtilsService: PlatformUtilsService, 24 | private i18nService: I18nService, 25 | private folderService: FolderService 26 | ) {} 27 | 28 | async ngOnInit() { 29 | this.folders = await this.folderService.getAllDecrypted(); 30 | this.folderId = this.folders[0].id; 31 | } 32 | 33 | async submit() { 34 | this.formPromise = this.cipherService.moveManyWithServer(this.cipherIds, this.folderId); 35 | await this.formPromise; 36 | this.onMoved.emit(); 37 | this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/vault/bulk-restore.component.html: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /src/app/vault/bulk-restore.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from "@angular/core"; 2 | 3 | import { CipherService } from "jslib-common/abstractions/cipher.service"; 4 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 5 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 6 | 7 | @Component({ 8 | selector: "app-vault-bulk-restore", 9 | templateUrl: "bulk-restore.component.html", 10 | }) 11 | export class BulkRestoreComponent { 12 | @Input() cipherIds: string[] = []; 13 | @Output() onRestored = new EventEmitter(); 14 | 15 | formPromise: Promise; 16 | 17 | constructor( 18 | private cipherService: CipherService, 19 | private platformUtilsService: PlatformUtilsService, 20 | private i18nService: I18nService 21 | ) {} 22 | 23 | async submit() { 24 | this.formPromise = this.cipherService.restoreManyWithServer(this.cipherIds); 25 | await this.formPromise; 26 | this.onRestored.emit(); 27 | this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/vault/collections.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from "@angular/core"; 2 | 3 | import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component"; 4 | import { CipherService } from "jslib-common/abstractions/cipher.service"; 5 | import { CollectionService } from "jslib-common/abstractions/collection.service"; 6 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 7 | import { LogService } from "jslib-common/abstractions/log.service"; 8 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 9 | import { CollectionView } from "jslib-common/models/view/collectionView"; 10 | 11 | @Component({ 12 | selector: "app-vault-collections", 13 | templateUrl: "collections.component.html", 14 | }) 15 | export class CollectionsComponent extends BaseCollectionsComponent implements OnDestroy { 16 | constructor( 17 | collectionService: CollectionService, 18 | platformUtilsService: PlatformUtilsService, 19 | i18nService: I18nService, 20 | cipherService: CipherService, 21 | logService: LogService 22 | ) { 23 | super(collectionService, platformUtilsService, i18nService, cipherService, logService); 24 | } 25 | 26 | ngOnDestroy() { 27 | this.selectAll(false); 28 | } 29 | 30 | check(c: CollectionView, select?: boolean) { 31 | (c as any).checked = select == null ? !(c as any).checked : select; 32 | } 33 | 34 | selectAll(select: boolean) { 35 | this.collections.forEach((c) => this.check(c, select)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/vault/folder-add-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component"; 4 | import { FolderService } from "jslib-common/abstractions/folder.service"; 5 | import { I18nService } from "jslib-common/abstractions/i18n.service"; 6 | import { LogService } from "jslib-common/abstractions/log.service"; 7 | import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; 8 | 9 | @Component({ 10 | selector: "app-folder-add-edit", 11 | templateUrl: "folder-add-edit.component.html", 12 | }) 13 | export class FolderAddEditComponent extends BaseFolderAddEditComponent { 14 | constructor( 15 | folderService: FolderService, 16 | i18nService: I18nService, 17 | platformUtilsService: PlatformUtilsService, 18 | logService: LogService 19 | ) { 20 | super(folderService, i18nService, platformUtilsService, logService); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/wildcard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | const routes: Routes = [{ path: "**", redirectTo: "" }]; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forChild(routes)], 8 | exports: [RouterModule], 9 | }) 10 | export class WildcardRoutingModule {} 11 | -------------------------------------------------------------------------------- /src/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #175DDC 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/connectors/captcha-mobile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Bitwarden Captcha Connector 12 | 13 | 14 | 15 |
16 |
17 | 18 |

Captcha Required

19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/connectors/captcha-mobile.scss: -------------------------------------------------------------------------------- 1 | @import "../scss/styles.scss"; 2 | -------------------------------------------------------------------------------- /src/connectors/captcha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Bitwarden Captcha Connector 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/connectors/captcha.scss: -------------------------------------------------------------------------------- 1 | body { 2 | min-width: 0px !important; 3 | padding: 0; 4 | margin: 0; 5 | background: transparent; 6 | } 7 | -------------------------------------------------------------------------------- /src/connectors/common.ts: -------------------------------------------------------------------------------- 1 | export function getQsParam(name: string) { 2 | const url = window.location.href; 3 | // eslint-disable-next-line 4 | name = name.replace(/[\[\]]/g, "\\$&"); 5 | const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); 6 | const results = regex.exec(url); 7 | 8 | if (!results) { 9 | return null; 10 | } 11 | if (!results[2]) { 12 | return ""; 13 | } 14 | 15 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 16 | } 17 | 18 | export function b64Decode(str: string, spaceAsPlus = false) { 19 | if (spaceAsPlus) { 20 | str = str.replace(/ /g, "+"); 21 | } 22 | 23 | return decodeURIComponent( 24 | Array.prototype.map 25 | .call(atob(str), (c: string) => { 26 | return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); 27 | }) 28 | .join("") 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/connectors/duo.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 9 | Bitwarden Duo Connector 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/connectors/duo.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | background: #efeff4 url("../images/loading.svg") 0 0 no-repeat; 9 | } 10 | 11 | iframe { 12 | display: block; 13 | width: 100%; 14 | height: 400px; 15 | border: none; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | -------------------------------------------------------------------------------- /src/connectors/duo.ts: -------------------------------------------------------------------------------- 1 | import * as DuoWebSDK from "duo_web_sdk"; 2 | 3 | import { getQsParam } from "./common"; 4 | 5 | require("./duo.scss"); 6 | 7 | document.addEventListener("DOMContentLoaded", () => { 8 | const frameElement = document.createElement("iframe"); 9 | frameElement.setAttribute("id", "duo_iframe"); 10 | setFrameHeight(); 11 | document.body.appendChild(frameElement); 12 | 13 | const hostParam = getQsParam("host"); 14 | const requestParam = getQsParam("request"); 15 | 16 | const hostUrl = new URL("https://" + hostParam); 17 | if ( 18 | !hostUrl.hostname.endsWith(".duosecurity.com") && 19 | !hostUrl.hostname.endsWith(".duofederal.com") 20 | ) { 21 | return; 22 | } 23 | 24 | DuoWebSDK.init({ 25 | iframe: "duo_iframe", 26 | host: hostParam, 27 | sig_request: requestParam, 28 | submit_callback: (form: any) => { 29 | invokeCSCode(form.elements.sig_response.value); 30 | }, 31 | }); 32 | 33 | window.onresize = setFrameHeight; 34 | 35 | function setFrameHeight() { 36 | frameElement.style.height = window.innerHeight + "px"; 37 | } 38 | }); 39 | 40 | function invokeCSCode(data: string) { 41 | try { 42 | (window as any).invokeCSharpAction(data); 43 | } catch (err) { 44 | // eslint-disable-next-line 45 | console.log(err); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/connectors/sso.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Bitwarden 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |

23 | 28 |

29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/connectors/sso.scss: -------------------------------------------------------------------------------- 1 | @import "../scss/styles.scss"; 2 | -------------------------------------------------------------------------------- /src/connectors/sso.ts: -------------------------------------------------------------------------------- 1 | import { getQsParam } from "./common"; 2 | 3 | require("./sso.scss"); 4 | 5 | document.addEventListener("DOMContentLoaded", () => { 6 | const code = getQsParam("code"); 7 | const state = getQsParam("state"); 8 | 9 | if (state != null && state.includes(":clientId=browser")) { 10 | initiateBrowserSso(code, state); 11 | } else { 12 | window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; 13 | // Match any characters between "_returnUri='" and the next "'" 14 | const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')"); 15 | if (returnUri) { 16 | window.location.href = window.location.origin + `/#${returnUri}`; 17 | } else { 18 | window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; 19 | } 20 | } 21 | }); 22 | 23 | function initiateBrowserSso(code: string, state: string) { 24 | window.postMessage({ command: "authResult", code: code, state: state }, "*"); 25 | const handOffMessage = ("; " + document.cookie) 26 | .split("; ssoHandOffMessage=") 27 | .pop() 28 | .split(";") 29 | .shift(); 30 | document.cookie = "ssoHandOffMessage=;SameSite=strict;max-age=0"; 31 | const content = document.getElementById("content"); 32 | content.innerHTML = ""; 33 | const p = document.createElement("p"); 34 | p.innerText = handOffMessage; 35 | content.appendChild(p); 36 | } 37 | 38 | function extractFromRegex(s: string, regexString: string) { 39 | const regex = new RegExp(regexString); 40 | const results = regex.exec(s); 41 | 42 | if (!results) { 43 | return null; 44 | } 45 | 46 | return results[0]; 47 | } 48 | -------------------------------------------------------------------------------- /src/connectors/webauthn-fallback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bitwarden WebAuthn Connector 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |

15 | 20 |

21 |
22 |
23 |
24 |

25 |
26 | 27 | 28 |
29 |
30 |

31 | 32 |

33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /src/connectors/webauthn-mobile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | Bitwarden WebAuthn Connector 12 | 13 | 14 | 15 |
16 |
17 | 18 |

19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/connectors/webauthn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bitwarden WebAuthn Connector 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/connectors/webauthn.scss: -------------------------------------------------------------------------------- 1 | @import "../scss/styles.scss"; 2 | 3 | body { 4 | min-width: 0px !important; 5 | } 6 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/favicon.ico -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare function escape(s: string): string; 2 | declare function unescape(s: string): string; 3 | -------------------------------------------------------------------------------- /src/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/404.png -------------------------------------------------------------------------------- /src/images/bwi-globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/bwi-globe.png -------------------------------------------------------------------------------- /src/images/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/cards.png -------------------------------------------------------------------------------- /src/images/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/images/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/images/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/images/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/images/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/images/loading-white.svg: -------------------------------------------------------------------------------- 1 |  2 | 4 | Loading... 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/images/loading.svg: -------------------------------------------------------------------------------- 1 |  2 | 4 | Loading... 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/images/logo-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/logo-dark@2x.png -------------------------------------------------------------------------------- /src/images/logo-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/logo-white@2x.png -------------------------------------------------------------------------------- /src/images/register-layout/cnet-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/register-layout/logo-horizontal-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/register-layout/logo-horizontal-white.png -------------------------------------------------------------------------------- /src/images/register-layout/wired-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/register-layout/wired-logo.png -------------------------------------------------------------------------------- /src/images/totp-countdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/totp-countdown.png -------------------------------------------------------------------------------- /src/images/two-factor/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/0.png -------------------------------------------------------------------------------- /src/images/two-factor/1-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/1-w.png -------------------------------------------------------------------------------- /src/images/two-factor/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/1.png -------------------------------------------------------------------------------- /src/images/two-factor/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/2.png -------------------------------------------------------------------------------- /src/images/two-factor/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/3.png -------------------------------------------------------------------------------- /src/images/two-factor/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/4.png -------------------------------------------------------------------------------- /src/images/two-factor/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/6.png -------------------------------------------------------------------------------- /src/images/two-factor/7-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/7-w.png -------------------------------------------------------------------------------- /src/images/two-factor/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/7.png -------------------------------------------------------------------------------- /src/images/two-factor/rc-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/rc-w.png -------------------------------------------------------------------------------- /src/images/two-factor/rc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/two-factor/rc.png -------------------------------------------------------------------------------- /src/images/u2fkey-mobile.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey-mobile.avif -------------------------------------------------------------------------------- /src/images/u2fkey-mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey-mobile.jpg -------------------------------------------------------------------------------- /src/images/u2fkey-mobile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey-mobile.webp -------------------------------------------------------------------------------- /src/images/u2fkey.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey.avif -------------------------------------------------------------------------------- /src/images/u2fkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey.jpg -------------------------------------------------------------------------------- /src/images/u2fkey.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/u2fkey.webp -------------------------------------------------------------------------------- /src/images/yubikey.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/yubikey.avif -------------------------------------------------------------------------------- /src/images/yubikey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/yubikey.jpg -------------------------------------------------------------------------------- /src/images/yubikey.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwarden/web/ee4afdd0f215ddbfdd0e90bd229dfde6b4a4bffb/src/images/yubikey.webp -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Bitwarden Web Vault 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |

22 | 27 |

28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bitwarden Vault", 3 | "icons": [ 4 | { 5 | "src": "images/icons/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "images/icons/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#175DDC", 16 | "background_color": "#175DDC" 17 | } 18 | -------------------------------------------------------------------------------- /src/models/account.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Account as BaseAccount, 3 | AccountSettings as BaseAccountSettings, 4 | } from "jslib-common/models/domain/account"; 5 | 6 | export class AccountSettings extends BaseAccountSettings { 7 | vaultTimeout: number = process.env.NODE_ENV === "development" ? null : 15; 8 | } 9 | 10 | export class Account extends BaseAccount { 11 | settings?: AccountSettings = new AccountSettings(); 12 | 13 | constructor(init: Partial) { 14 | super(init); 15 | Object.assign(this.settings, { 16 | ...new AccountSettings(), 17 | ...this.settings, 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/models/globalState.ts: -------------------------------------------------------------------------------- 1 | import { ThemeType } from "jslib-common/enums/themeType"; 2 | import { GlobalState as BaseGlobalState } from "jslib-common/models/domain/globalState"; 3 | 4 | export class GlobalState extends BaseGlobalState { 5 | theme?: ThemeType = ThemeType.Light; 6 | rememberEmail = true; 7 | } 8 | -------------------------------------------------------------------------------- /src/scss/export.module.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | :export { 4 | darkInputColor: $darkInputColor; 5 | darkInputPlaceholderColor: $darkInputPlaceholderColor; 6 | lightInputColor: $lightInputColor; 7 | lightInputPlaceholderColor: $lightInputPlaceholderColor; 8 | } 9 | -------------------------------------------------------------------------------- /src/scss/export.module.scss.d.ts: -------------------------------------------------------------------------------- 1 | export interface ThemeVariableExport { 2 | lightInputColor: string; 3 | lightInputPlaceholderColor: string; 4 | darkInputColor: string; 5 | darkInputPlaceholderColor: string; 6 | } 7 | 8 | export const ThemeVariables: ThemeVariableExport; 9 | export default ThemeVariables; 10 | -------------------------------------------------------------------------------- /src/scss/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import "../../jslib/components/src/tw-theme.css"; 6 | -------------------------------------------------------------------------------- /src/services/broadcasterMessaging.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; 4 | import { MessagingService } from "jslib-common/abstractions/messaging.service"; 5 | 6 | @Injectable() 7 | export class BroadcasterMessagingService implements MessagingService { 8 | constructor(private broadcasterService: BroadcasterService) {} 9 | 10 | send(subscriber: string, arg: any = {}) { 11 | const message = Object.assign({}, { command: subscriber }, arg); 12 | this.broadcasterService.send(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/services/i18n.service.ts: -------------------------------------------------------------------------------- 1 | import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service"; 2 | 3 | export class I18nService extends BaseI18nService { 4 | constructor(systemLanguage: string, localesDirectory: string) { 5 | super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => { 6 | const filePath = 7 | this.localesDirectory + 8 | "/" + 9 | formattedLocale + 10 | "/messages.json?cache=" + 11 | process.env.CACHE_TAG; 12 | const localesResult = await fetch(filePath); 13 | const locales = await localesResult.json(); 14 | return locales; 15 | }); 16 | 17 | // Please leave 'en' where it is, as it's our fallback language in case no translation can be found 18 | this.supportedTranslationLocales = [ 19 | "en", 20 | "af", 21 | "az", 22 | "be", 23 | "bg", 24 | "bn", 25 | "bs", 26 | "ca", 27 | "cs", 28 | "da", 29 | "de", 30 | "el", 31 | "en-GB", 32 | "en-IN", 33 | "eo", 34 | "es", 35 | "et", 36 | "fi", 37 | "fil", 38 | "fr", 39 | "he", 40 | "hi", 41 | "hr", 42 | "hu", 43 | "id", 44 | "it", 45 | "ja", 46 | "ka", 47 | "km", 48 | "kn", 49 | "ko", 50 | "lv", 51 | "ml", 52 | "nb", 53 | "nl", 54 | "nn", 55 | "pl", 56 | "pt-PT", 57 | "pt-BR", 58 | "ro", 59 | "ru", 60 | "si", 61 | "sk", 62 | "sl", 63 | "sr", 64 | "sv", 65 | "tr", 66 | "uk", 67 | "vi", 68 | "zh-CN", 69 | "zh-TW", 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/services/memoryStorage.service.ts: -------------------------------------------------------------------------------- 1 | import { StorageService } from "jslib-common/abstractions/storage.service"; 2 | 3 | export class MemoryStorageService implements StorageService { 4 | private store = new Map(); 5 | 6 | get(key: string): Promise { 7 | if (this.store.has(key)) { 8 | const obj = this.store.get(key); 9 | return Promise.resolve(obj as T); 10 | } 11 | return Promise.resolve(null); 12 | } 13 | 14 | async has(key: string): Promise { 15 | return this.get(key) != null; 16 | } 17 | 18 | save(key: string, obj: any): Promise { 19 | if (obj == null) { 20 | return this.remove(key); 21 | } 22 | this.store.set(key, obj); 23 | return Promise.resolve(); 24 | } 25 | 26 | remove(key: string): Promise { 27 | this.store.delete(key); 28 | return Promise.resolve(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/services/passwordReprompt.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | import { PasswordRepromptService as BasePasswordRepromptService } from "jslib-angular/services/passwordReprompt.service"; 4 | 5 | import { PasswordRepromptComponent } from "../app/components/password-reprompt.component"; 6 | 7 | @Injectable() 8 | export class PasswordRepromptService extends BasePasswordRepromptService { 9 | component = PasswordRepromptComponent; 10 | } 11 | -------------------------------------------------------------------------------- /src/services/stateMigration.service.ts: -------------------------------------------------------------------------------- 1 | import { StateMigrationService as BaseStateMigrationService } from "jslib-common/services/stateMigration.service"; 2 | 3 | import { Account } from "../models/account"; 4 | import { GlobalState } from "../models/globalState"; 5 | 6 | export class StateMigrationService extends BaseStateMigrationService { 7 | protected async migrationStateFrom1To2(): Promise { 8 | await super.migrateStateFrom1To2(); 9 | const globals = (await this.get("global")) ?? this.stateFactory.createGlobal(null); 10 | globals.rememberEmail = (await this.get("rememberEmail")) ?? globals.rememberEmail; 11 | await this.set("global", globals); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // Set theme on page load 2 | // This is done outside the Angular app to avoid a flash of unthemed content before it loads 3 | // The defaultTheme is also set in the html itself to make sure that some theming is always applied 4 | (function () { 5 | const defaultTheme = "light"; 6 | const htmlEl = document.documentElement; 7 | let theme = defaultTheme; 8 | 9 | const globalState = window.localStorage.getItem("global"); 10 | if (globalState != null) { 11 | const globalStateJson = JSON.parse(globalState); 12 | if (globalStateJson != null && globalStateJson.theme != null) { 13 | if (globalStateJson.theme.indexOf("system") > -1) { 14 | theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; 15 | } else if (globalStateJson.theme.indexOf("dark") > -1) { 16 | theme = "dark"; 17 | } 18 | } 19 | if (!htmlEl.classList.contains("theme_" + theme)) { 20 | htmlEl.classList.remove("theme_" + defaultTheme); 21 | htmlEl.classList.add("theme_" + theme); 22 | } 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /src/version.json: -------------------------------------------------------------------------------- 1 | { "version": "process.env.APPLICATION_VERSION" } 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef, @typescript-eslint/no-var-requires */ 2 | const config = require("./jslib/components/tailwind.config.base"); 3 | 4 | module.exports = config; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./jslib/shared/tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "tldjs": ["jslib/common/src/misc/tldjs.noop"], 7 | "jslib-common/*": ["jslib/common/src/*"], 8 | "jslib-angular/*": ["jslib/angular/src/*"], 9 | "@bitwarden/components": ["jslib/components/src"], 10 | "src/*": ["src/*"] 11 | } 12 | }, 13 | "angularCompilerOptions": { 14 | "preserveWhitespaces": true 15 | }, 16 | "files": ["src/app/polyfills.ts", "src/app/main.ts", "bitwarden_license/src/app/main.ts"], 17 | "include": [ 18 | "src/connectors/*.ts", 19 | "src/models/*.ts", 20 | "src/services/*.ts", 21 | "src/abstractions/*.ts" 22 | ] 23 | } 24 | --------------------------------------------------------------------------------