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

Compare commits

..

3 Commits

Author SHA1 Message Date
Carlos J. Muentes
a001ac72b5 Adding IIFE for obtaining master password 2022-04-06 10:46:35 -04:00
Carlos J. Muentes
356bd14bb4 Update jslib 2022-04-05 19:43:50 -04:00
Carlos J. Muentes
66046660d1 CSA-2 - capturing user master password for password change enrollment 2022-04-05 19:42:38 -04:00
221 changed files with 7294 additions and 7517 deletions

View File

@@ -21,6 +21,10 @@
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)

View File

@@ -11,7 +11,6 @@ on:
branches-ignore:
- "l10n_master"
- "gh-pages"
- "deploy"
paths-ignore:
- '.github/workflows/**'

View File

@@ -158,7 +158,7 @@ jobs:
ghpages-deploy:
name: Deploy Web Vault to GitHub Pages
name: Deploy Web Vault
runs-on: ubuntu-20.04
needs:
- setup
@@ -172,10 +172,10 @@ jobs:
with:
ref: gh-pages
- name: Create gh-pages-deploy branch
- name: Create deploy branch
run: |
git switch -c gh-pages-deploy-$_TAG_VERSION
git push -u origin gh-pages-deploy-$_TAG_VERSION
git switch -c deploy-$_TAG_VERSION
git push -u origin deploy-$_TAG_VERSION
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
@@ -188,7 +188,7 @@ jobs:
git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success
@@ -204,88 +204,24 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
target_branch: gh-pages-deploy-${{ needs.setup.outputs.tag_version }}
target_branch: deploy-${{ needs.setup.outputs.tag_version }}
build_dir: build
keep_history: true
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
- name: Create GitHub Pages Deploy PR
- name: Create Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: gh-pages-deploy-${{ env._TAG_VERSION }}
PR_BRANCH: deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION to GitHub Pages" \
gh pr create --title "Deploy $_RELEASE_VERSION" \
--body "Deploying $_RELEASE_VERSION" \
--base gh-pages \
--head "$PR_BRANCH"
cfpages-deploy:
name: Deploy Web Vault to CloudFlare Pages branch
runs-on: ubuntu-20.04
needs:
- setup
- self-host
env:
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
steps:
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.setup.outputs.branch_name }}
artifacts: web-*-cloud-COMMERCIAL.zip
# This should result in a build directory in the current working directory
- name: Unzip build asset
run: unzip web-*-cloud-COMMERCIAL.zip
- name: Checkout Repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
with:
ref: deploy
path: deployment
- name: Setup git config
run: |
git config --global user.name = "GitHub Action Bot"
git config --global user.email = "<>"
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
git config --global url."https://".insteadOf ssh://
- name: Deploy CloudFlare Pages
run: |
rm -rf ./*
cp -R ../build/* .
working-directory: deployment
- name: Create cf-pages-deploy branch
run: |
git switch -c cf-pages-deploy-$_TAG_VERSION
git add .
git commit -m "Staging deploy ${{ needs.setup.outputs.release_version }}"
git push -u origin cf-pages-deploy-$_TAG_VERSION
working-directory: deployment
- name: Create CloudFlare Pages Deploy PR
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PR_BRANCH: cf-pages-deploy-${{ env._TAG_VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create --title "Deploy $_RELEASE_VERSION to CloudFlare Pages" \
--body "Deploying $_RELEASE_VERSION" \
--base deploy \
--head "$PR_BRANCH"
release:
name: Create GitHub Release
runs-on: ubuntu-20.04
@@ -293,7 +229,6 @@ jobs:
- setup
- self-host
- ghpages-deploy
- cfpages-deploy
steps:
- name: Download latest build artifacts
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
@@ -336,8 +271,5 @@ jobs:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
- name: Remove gh-pages-deploy branch
run: git push origin --delete gh-pages-deploy-$_TAG_VERSION
- name: Remove cf-pages-deploy branch
run: git push origin --delete cf-pages-deploy-$_TAG_VERSION
- name: Remove deploy branch
run: git push origin --delete deploy-$_TAG_VERSION

View File

@@ -10,7 +10,7 @@ Here is how you can get involved:
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
## Contributor Agreement
@@ -31,6 +31,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o
If you are interested in helping translate the Bitwarden web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -1,9 +1,3 @@
> **Repository Reorganization in Progress**
>
> We are currently migrating some projects over to a mono repository. For existing PR's we will be providing documentation on how to move/migrate them. To minimize the overhead we are actively reviewing open PRs. If possible please ensure any pending comments are resolved as soon as possible.
>
> New pull requests created during this transition period may not get addressed —if needed, please create a new PR after the reorganization is complete.
<p align="center">
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
</p>

View File

@@ -1,5 +1,4 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
@@ -9,7 +8,6 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { JslibModule } from "jslib-angular/jslib.module";
import { OssRoutingModule } from "src/app/oss-routing.module";
import { OssModule } from "src/app/oss.module";
import { ServicesModule } from "src/app/services/services.module";
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
@@ -21,8 +19,6 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
@NgModule({
imports: [
OverlayModule,
OssModule,
JslibModule,
BrowserAnimationsModule,
FormsModule,

View File

@@ -1,13 +1,13 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
import { ManageComponent } from "src/app/organizations/manage/manage.component";
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
import { SsoComponent } from "./manage/sso.component";
@@ -15,16 +15,25 @@ const routes: Routes = [
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard],
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{
path: "manage",
component: ManageComponent,
canActivate: [PermissionsGuard],
canActivate: [OrganizationTypeGuardService],
data: {
permissions: NavigationPermissionsService.getPermissions("manage").concat(
Permissions.ManageSso
),
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
Permissions.ManageSso,
],
},
children: [
{

View File

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

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
@@ -9,13 +9,13 @@ import { ProvidersComponent } from "src/app/providers/providers.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
@@ -24,7 +24,7 @@ import { SetupComponent } from "./setup/setup.component";
const routes: Routes = [
{
path: "",
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
component: ProvidersComponent,
},
{
@@ -45,7 +45,7 @@ const routes: Routes = [
},
{
path: "",
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{
path: "setup",
@@ -54,7 +54,7 @@ const routes: Routes = [
{
path: ":providerId",
component: ProvidersLayoutComponent,
canActivate: [ProviderGuard],
canActivate: [ProviderGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "clients" },
{ path: "clients/create", component: CreateOrganizationComponent },
@@ -71,7 +71,7 @@ const routes: Routes = [
{
path: "people",
component: PeopleComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers],
@@ -80,7 +80,7 @@ const routes: Routes = [
{
path: "events",
component: EventsComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
@@ -100,7 +100,7 @@ const routes: Routes = [
{
path: "account",
component: AccountComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "myProvider",
permissions: [Permissions.ManageProvider],

View File

@@ -10,8 +10,6 @@ import { OssModule } from "src/app/oss.module";
import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@@ -21,6 +19,8 @@ import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
@@ -46,7 +46,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupProviderComponent,
UserAddEditComponent,
],
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
})
export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

View File

@@ -6,7 +6,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ProviderService } from "jslib-common/abstractions/provider.service";
@Injectable()
export class ProviderGuard implements CanActivate {
export class ProviderGuardService implements CanActivate {
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,

View File

@@ -5,7 +5,7 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class PermissionsGuard implements CanActivate {
export class ProviderTypeGuardService implements CanActivate {
constructor(private providerService: ProviderService, private router: Router) {}
async canActivate(route: ActivatedRouteSnapshot) {

View File

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

2
jslib

Submodule jslib updated: 3bf25edd3e...dc7efa4970

6016
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2.28.1",
"version": "2.27.0",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -90,7 +90,7 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.33.1",
"braintree-web-drop-in": "1.30.1",
"browser-hrtime": "^1.1.8",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",
@@ -98,7 +98,6 @@
"jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4",
"node-forge": "^1.3.1",
"popper.js": "1.16.1",
"qrious": "4.0.2",
"rxjs": "^7.4.0",

View File

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

View File

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

View File

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

View File

@@ -55,10 +55,10 @@ export class LockComponent extends BaseLockComponent {
await super.ngOnInit();
this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl && previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl;
}
this.router.navigateByUrl(this.successRoute);
this.router.navigate([this.successRoute]);
};
}
}

View File

@@ -20,7 +20,6 @@ import { ListResponse } from "jslib-common/models/response/listResponse";
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { StateService } from "../../abstractions/state.service";
import { RouterService } from "../services/router.service";
@Component({
selector: "app-login",
@@ -45,8 +44,7 @@ export class LoginComponent extends BaseLoginComponent {
logService: LogService,
ngZone: NgZone,
protected stateService: StateService,
private messagingService: MessagingService,
private routerService: RouterService
private messagingService: MessagingService
) {
super(
authService,
@@ -72,20 +70,21 @@ export class LoginComponent extends BaseLoginComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { token: qParams.sponsorshipToken },
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
await super.ngOnInit();
this.rememberEmail = await this.stateService.getRememberEmail();
@@ -146,9 +145,10 @@ export class LoginComponent extends BaseLoginComponent {
}
}
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute]);
}

View File

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

View File

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

View File

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

View File

@@ -18,8 +18,6 @@ import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPa
import { Policy } from "jslib-common/models/domain/policy";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
import { RouterService } from "../services/router.service";
@Component({
selector: "app-register",
templateUrl: "register.component.html",
@@ -43,8 +41,7 @@ export class RegisterComponent extends BaseRegisterComponent {
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService,
private routerService: RouterService
logService: LogService
) {
super(
authService,
@@ -67,14 +64,14 @@ export class RegisterComponent extends BaseRegisterComponent {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
@@ -91,10 +88,10 @@ export class RegisterComponent extends BaseRegisterComponent {
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { plan: qParams.sponsorshipToken },
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
if (this.referenceData.id === "") {
this.referenceData.id = null;

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -14,8 +13,6 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { RouterService } from "../services/router.service";
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({
@@ -37,9 +34,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService,
twoFactorService: TwoFactorService,
appIdService: AppIdService,
private routerService: RouterService
twoFactorService: TwoFactorService
) {
super(
authService,
@@ -52,8 +47,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
stateService,
route,
logService,
twoFactorService,
appIdService
twoFactorService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
@@ -76,9 +70,10 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
async goAfterLogIn() {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
this.router.navigateByUrl(previousUrl);
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute], {
queryParams: {

View File

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

View File

@@ -91,17 +91,11 @@ export class AppComponent implements OnDestroy, OnInit {
this.ngZone.run(async () => {
switch (message.command) {
case "loggedIn":
this.notificationsService.updateConnection(false);
break;
case "loggedOut":
this.routerService.setPreviousUrl(null);
this.notificationsService.updateConnection(false);
break;
case "unlocked":
this.notificationsService.updateConnection(false);
break;
case "authBlocked":
this.routerService.setPreviousUrl(message.url);
this.router.navigate(["/"]);
break;
case "logout":
@@ -115,7 +109,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(["lock"]);
break;
case "lockedUrl":
this.routerService.setPreviousUrl(message.url);
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
break;
case "syncStarted":
break;

View File

@@ -30,6 +30,7 @@ export abstract class BaseAcceptComponent implements OnInit {
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
let errorMessage: string = null;
if (!error) {
@@ -43,6 +44,11 @@ export abstract class BaseAcceptComponent implements OnInit {
errorMessage = e.message;
}
} else {
await this.stateService.setLoginRedirect({
route: this.getRedirectRoute(),
qParams: qParams,
});
this.email = qParams.email;
await this.unauthedHandler(qParams);
}
@@ -60,4 +66,10 @@ export abstract class BaseAcceptComponent implements OnInit {
this.loading = false;
});
}
getRedirectRoute() {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
return urlTree.toString();
}
}

View File

@@ -1,68 +0,0 @@
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
<button
class="tw-flex tw-items-center tw-bg-background-alt tw-border-none"
type="button"
id="pickerButton"
[appA11yTitle]="'organizationPicker' | i18n"
[bitMenuTriggerFor]="orgPickerMenu"
>
<app-avatar
[data]="activeOrganization.name"
size="45"
[circle]="true"
[dynamic]="true"
></app-avatar>
<div class="tw-flex">
<div class="org-name tw-ml-3">
<span>{{ activeOrganization.name }}</span>
<small class="tw-text-muted">{{ "organization" | i18n }}</small>
</div>
<div class="tw-ml-3">
<i class="bwi bwi-angle-down tw-text-main" aria-hidden="true"></i>
</div>
</div>
</button>
<div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-danger-500 tw-text-danger"
*ngIf="!activeOrganization.enabled"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-info-500 tw-text-info"
*ngIf="activeOrganization.isProviderUser"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: activeOrganization.providerName }}
</div>
</div>
</div>
<bit-menu #orgPickerMenu>
<ul aria-labelledby="pickerButton" class="tw-p-0 tw-m-0">
<li *ngFor="let org of organizations" class="tw-list-none tw-flex tw-flex-col" role="none">
<a bit-menu-item [routerLink]="['/organizations', org.id]">
<i
class="bwi bwi-check mr-2"
[ngClass]="org.id === activeOrganization.id ? 'visible' : 'invisible'"
>
<span class="tw-sr-only">{{ "currentOrganization" | i18n }}</span>
</i>
{{ org.name }}
</a>
</li>
<bit-menu-divider></bit-menu-divider>
<li class="tw-list-none" role="none">
<a bit-menu-item routerLink="/create-organization">
<i class="bwi bwi-plus mr-2"></i>
{{ "newOrganization" | i18n }}</a
>
</li>
</ul>
</bit-menu>
</div>

View File

@@ -1,34 +0,0 @@
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-organization-switcher",
templateUrl: "organization-switcher.component.html",
})
export class OrganizationSwitcherComponent implements OnInit {
constructor(private organizationService: OrganizationService, private i18nService: I18nService) {}
@Input() activeOrganization: Organization = null;
organizations: Organization[] = [];
loaded = false;
async ngOnInit() {
await this.load();
}
async load() {
const orgs = await this.organizationService.getAll();
this.organizations = orgs
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
this.loaded = true;
}
}

View File

@@ -1,22 +0,0 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
@Injectable()
export class HomeGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
async canActivate(route: ActivatedRouteSnapshot) {
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
return this.router.createUrlTree(["/login"], { queryParams: route.queryParams });
}
if (authStatus === AuthenticationStatus.Locked) {
return this.router.createUrlTree(["/lock"], { queryParams: route.queryParams });
}
return this.router.createUrlTree(["/vault"], { queryParams: route.queryParams });
}
}

View File

@@ -6,22 +6,11 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
"organizations" | i18n
}}</a>
</li>
<ng-container *ngIf="providers.length >= 1">
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{
@@ -32,62 +21,76 @@
<a class="nav-link" routerLink="/providers">{{ "provider" | i18n }}</a>
</li>
</ng-container>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<li>
<button
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-text-alt2 tw-opacity-70 hover:tw-opacity-90"
<li class="nav-item dropdown">
<a
class="nav-item nav-link dropdown-toggle"
href="#"
id="nav-profile"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i class="bwi bwi-user-circle bwi-lg" aria-hidden="true"></i>
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #accountMenu>
<div class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<div
class="tw-flex tw-items-center tw-leading-tight tw-text-info tw-py-1 tw-px-4"
*ngIf="name"
appStopProp
>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="tw-text-muted tw-block tw-overflow-hidden tw-whitespace-nowrap">{{
name
}}</small>
</div>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="ml-2 overflow-hidden">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="text-muted">{{ name }}</small>
</div>
<bit-menu-divider></bit-menu-divider>
<a bit-menu-item routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "accountSettings" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/help/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/download/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider>
<button bit-menu-item type="button" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button bit-menu-item type="button" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</bit-menu>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myAccount" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/help/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/download/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button type="button" class="dropdown-item" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</li>
</ul>
</div>

View File

@@ -1,19 +1,12 @@
import { Component, NgZone, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider";
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-navbar",
templateUrl: "navbar.component.html",
@@ -23,18 +16,13 @@ export class NavbarComponent implements OnInit {
name: string;
email: string;
providers: Provider[] = [];
organizations: Organization[] = [];
constructor(
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private tokenService: TokenService,
private providerService: ProviderService,
private syncService: SyncService,
private organizationService: OrganizationService,
private i18nService: I18nService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone
private syncService: SyncService
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
@@ -46,32 +34,11 @@ export class NavbarComponent implements OnInit {
this.name = this.email;
}
// Ensure providers and organizations are loaded
// Ensure provides are loaded
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
this.providers = await this.providerService.getAll();
this.organizations = await this.buildOrganizations();
this.broadcasterService.subscribe(this.constructor.name, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "organizationCreated":
if (this.organizations.length < 1) {
this.organizations = await this.buildOrganizations();
}
break;
}
});
});
}
async buildOrganizations() {
const allOrgs = await this.organizationService.getAll();
return allOrgs
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
}
lock() {

View File

@@ -2,11 +2,32 @@
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
<app-organization-switcher
class="my-auto pl-1"
[activeOrganization]="organization"
></app-organization-switcher>
<ul class="nav nav-tabs">
<div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3">
<span>{{ organization.name }}</span>
<small class="text-muted">{{ "organization" | i18n }}</small>
</div>
<div
class="ml-3 card border-danger text-danger bg-transparent"
*ngIf="!organization.enabled"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="ml-3 card border-info text-info bg-transparent"
*ngIf="organization.isProviderUser"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: organization.providerName }}
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="bwi bwi-lock" aria-hidden="true"></i>
@@ -25,7 +46,7 @@
{{ "tools" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showSettingsTab">
<li class="nav-item" *ngIf="organization.isOwner">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="bwi bwi-cogs" aria-hidden="true"></i>
{{ "settings" | i18n }}

View File

@@ -5,8 +5,6 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
@Component({
@@ -27,7 +25,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
ngOnInit() {
document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async (params: any) => {
this.route.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
});
@@ -50,16 +48,23 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
this.organization = await this.organizationService.get(this.organizationId);
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return NavigationPermissionsService.canAccessManage(this.organization);
return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
}
get showToolsTab(): boolean {
return NavigationPermissionsService.canAccessTools(this.organization);
}
get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization);
return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get toolsRoute(): string {

View File

@@ -1,496 +0,0 @@
import { NgModule } from "@angular/core";
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
import { LockComponent } from "../accounts/lock.component";
import { LoginComponent } from "../accounts/login.component";
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.component";
import { RegisterComponent } from "../accounts/register.component";
import { RemovePasswordComponent } from "../accounts/remove-password.component";
import { SetPasswordComponent } from "../accounts/set-password.component";
import { SsoComponent } from "../accounts/sso.component";
import { TwoFactorOptionsComponent } from "../accounts/two-factor-options.component";
import { TwoFactorComponent } from "../accounts/two-factor.component";
import { UpdatePasswordComponent } from "../accounts/update-password.component";
import { UpdateTempPasswordComponent } from "../accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { PasswordStrengthComponent } from "../components/password-strength.component";
import { PremiumBadgeComponent } from "../components/premium-badge.component";
import { FooterComponent } from "../layouts/footer.component";
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
import { NavbarComponent } from "../layouts/navbar.component";
import { UserLayoutComponent } from "../layouts/user-layout.component";
import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "../organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "../organizations/manage/entity-events.component";
import { EventsComponent as OrgEventsComponent } from "../organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "../organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "../organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "../organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "../organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "../organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "../organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "../organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "../organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "../organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "../organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "../organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "../organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "../organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "../organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "../organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "../organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "../organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "../organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "../organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "../organizations/settings/account.component";
import { AdjustSubscription } from "../organizations/settings/adjust-subscription.component";
import { BillingSyncApiKeyComponent } from "../organizations/settings/billing-sync-api-key.component";
import { ChangePlanComponent } from "../organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "../organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "../organizations/settings/download-license.component";
import { ImageSubscriptionHiddenComponent as OrgSubscriptionHiddenComponent } from "../organizations/settings/image-subscription-hidden.component";
import { OrganizationBillingComponent } from "../organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "../organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "../organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "../organizations/settings/two-factor-setup.component";
import { AcceptFamilySponsorshipComponent } from "../organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "../organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "../organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "../organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "../organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
import { ProvidersComponent } from "../providers/providers.component";
import { BreachReportComponent } from "../reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "../reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "../reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "../reports/report-card.component";
import { ReportListComponent } from "../reports/report-list.component";
import { ReportsComponent } from "../reports/reports.component";
import { ReusedPasswordsReportComponent } from "../reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "../reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "../reports/weak-passwords-report.component";
import { AccessComponent } from "../send/access.component";
import { AddEditComponent as SendAddEditComponent } from "../send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../send/efflux-dates.component";
import { SendComponent } from "../send/send.component";
import { AccountComponent } from "../settings/account.component";
import { AddCreditComponent } from "../settings/add-credit.component";
import { AdjustPaymentComponent } from "../settings/adjust-payment.component";
import { AdjustStorageComponent } from "../settings/adjust-storage.component";
import { ApiKeyComponent } from "../settings/api-key.component";
import { BillingSyncKeyComponent } from "../settings/billing-sync-key.component";
import { ChangeEmailComponent } from "../settings/change-email.component";
import { ChangeKdfComponent } from "../settings/change-kdf.component";
import { ChangePasswordComponent } from "../settings/change-password.component";
import { CreateOrganizationComponent } from "../settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "../settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "../settings/delete-account.component";
import { DomainRulesComponent } from "../settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "../settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "../settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "../settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "../settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "../settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "../settings/emergency-access.component";
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
import { LinkSsoComponent } from "../settings/link-sso.component";
import { OrganizationPlansComponent } from "../settings/organization-plans.component";
import { PaymentMethodComponent } from "../settings/payment-method.component";
import { PaymentComponent } from "../settings/payment.component";
import { PreferencesComponent } from "../settings/preferences.component";
import { PremiumComponent } from "../settings/premium.component";
import { ProfileComponent } from "../settings/profile.component";
import { PurgeVaultComponent } from "../settings/purge-vault.component";
import { SecurityKeysComponent } from "../settings/security-keys.component";
import { SecurityComponent } from "../settings/security.component";
import { SettingsComponent } from "../settings/settings.component";
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
import { SubscriptionComponent } from "../settings/subscription.component";
import { TaxInfoComponent } from "../settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "../settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "../settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "../settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "../settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "../settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { UpdateLicenseComponent } from "../settings/update-license.component";
import { UserBillingHistoryComponent } from "../settings/user-billing-history.component";
import { UserSubscriptionComponent } from "../settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "../settings/verify-email.component";
import { ExportComponent } from "../tools/export.component";
import { GeneratorComponent } from "../tools/generator.component";
import { ImportComponent } from "../tools/import.component";
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
import { ToolsComponent } from "../tools/tools.component";
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../vault/add-edit.component";
import { AttachmentsComponent } from "../vault/attachments.component";
import { BulkActionsComponent } from "../vault/bulk-actions.component";
import { BulkDeleteComponent } from "../vault/bulk-delete.component";
import { BulkMoveComponent } from "../vault/bulk-move.component";
import { BulkRestoreComponent } from "../vault/bulk-restore.component";
import { BulkShareComponent } from "../vault/bulk-share.component";
import { CiphersComponent } from "../vault/ciphers.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { ShareComponent } from "../vault/share.component";
import { PipesModule } from "./pipes/pipes.module";
import { SharedModule } from "./shared.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
// If you are building new functionality, please create or extend a feature module instead.
@NgModule({
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptFamilySponsorshipComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BillingSyncApiKeyComponent,
BillingSyncKeyComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgSubscriptionHiddenComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
})
export class LooseComponentsModule {}

View File

@@ -1,13 +0,0 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { NgModule } from "@angular/core";
import { SharedModule } from "../../shared.module";
import { EntityUsersComponent } from "./entity-users.component";
@NgModule({
imports: [SharedModule, ScrollingModule],
declarations: [EntityUsersComponent],
exports: [EntityUsersComponent],
})
export class OrganizationManageModule {}

View File

@@ -1,14 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import { Organization } from "jslib-common/models/domain/organization";
@Pipe({
name: "orgNameFromId",
pure: true,
})
export class GetOrgNameFromIdPipe implements PipeTransform {
transform(value: string, organizations: Organization[]) {
const orgName = organizations.find((o) => o.id === value)?.name;
return orgName;
}
}

View File

@@ -1,10 +0,0 @@
import { NgModule } from "@angular/core";
import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe";
@NgModule({
imports: [],
declarations: [GetOrgNameFromIdPipe],
exports: [GetOrgNameFromIdPipe],
})
export class PipesModule {}

View File

@@ -1,157 +0,0 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import {
BadgeModule,
ButtonModule,
CalloutModule,
FormFieldModule,
MenuModule,
} from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { JslibModule } from "jslib-angular/jslib.module";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
FormFieldModule,
],
exports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
FormFieldModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
],
providers: [DatePipe],
bootstrap: [],
})
export class SharedModule {}

View File

@@ -1,74 +0,0 @@
<ng-container *ngIf="show">
<div class="filter-heading">
<button
(click)="toggleCollapse(collectionsGrouping)"
[attr.aria-expanded]="!isCollapsed(collectionsGrouping)"
aria-controls="collection-filters"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(collectionsGrouping),
'bwi-angle-down': !isCollapsed(collectionsGrouping)
}"
aria-hidden="true"
></i>
</button>
<h3 class="filter-title">{{ collectionsGrouping.name | i18n }}</h3>
</div>
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{
active: c.node.id === activeFilter.selectedCollectionId
}"
class="filter-option"
>
<span class="filter-buttons">
<button
class="toggle-button"
*ngIf="c.children.length"
(click)="collapse(c.node)"
title="{{ 'toggleCollapse' | i18n }}"
[attr.aria-expanded]="!isCollapsed(c.node)"
[attr.aria-controls]="c.node.name + '_children'"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(c.node)">
<i
*ngIf="c.children.length === 0"
class="bwi bwi-collection bwi-fw"
aria-hidden="true"
></i
>{{ c.node.name }}
</button>
</span>
<ul
[id]="c.node.name + '_children'"
class="nested-filter-options"
*ngIf="c.children.length && !isCollapsed(c.node)"
>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</ng-container>

View File

@@ -1,9 +0,0 @@
import { Component } from "@angular/core";
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component";
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",
})
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}

View File

@@ -1,84 +0,0 @@
<ng-container *ngIf="!hide">
<div class="filter-heading">
<button
class="toggle-button"
(click)="toggleCollapse(foldersGrouping)"
[attr.aria-expanded]="!isCollapsed(foldersGrouping)"
aria-controls="folder-filters"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(foldersGrouping),
'bwi-angle-down': !isCollapsed(foldersGrouping)
}"
></i>
</button>
<h3 class="filter-title">
{{ "folders" | i18n }}
</h3>
<button
class="text-muted ml-auto add-button"
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
<ul id="folder-filters" *ngIf="!isCollapsed(foldersGrouping)" class="filter-options">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
}"
class="filter-option"
>
<span class="filter-buttons">
<button
*ngIf="f.children.length"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse(f.node)"
[attr.aria-expanded]="!isCollapsed(f.node)"
[attr.aria-controls]="f.node.name + '_children'"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(f.node)">
<i *ngIf="f.children.length === 0" class="bwi bwi-fw bwi-folder" aria-hidden="true"></i
>{{ f.node.name }}
</button>
<button
class="edit-button"
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</button>
</span>
<ul
[id]="f.node.name + '_children'"
class="nested-filter-options"
*ngIf="f.children.length && !isCollapsed(f.node)"
>
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
</ng-container>

View File

@@ -1,9 +0,0 @@
import { Component } from "@angular/core";
import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component";
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",
})
export class FolderFilterComponent extends BaseFolderFilterComponent {}

View File

@@ -1,156 +0,0 @@
<ng-container *ngIf="!hide">
<ng-container [ngSwitch]="displayMode">
<ng-container *ngSwitchCase="'noOrganizations'">
<ul class="filter-options">
<li class="filter-option active">
<span class="filter-buttons">
<button class="filter-button">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li class="filter-option">
<span class="filter-buttons">
<a href="#" routerLink="/create-organization" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newOrganization" | i18n }}
</a>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'personalOwnershipPolicy'">
<div class="filter-heading">
<button
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
<a
href="#"
routerLink="/create-organization"
class="text-muted ml-auto create-organization-link"
appA11yTitle="{{ 'newOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'singleOrganizationAndPersonalOwnershipPolicies'">
<div class="filter-heading">
<button class="filter-button active">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organizations[0].name }}
</button>
</div>
</ng-container>
<ng-container *ngSwitchDefault>
<div class="filter-heading">
<button
class="toggle-button"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
<a
href="#"
routerLink="/create-organization"
class="text-muted ml-auto create-organization-link"
appA11yTitle="{{ 'newOrganization' | i18n }}"
*ngIf="!(displayMode === 'singleOrganizationPolicy')"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyMyVaultFilter()">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container>
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
</ul>
</ng-container>
</ng-container>
<hr />
</ng-container>

View File

@@ -1,11 +0,0 @@
import { Component } from "@angular/core";
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component";
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",
})
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
displayText = "allVaults";
}

View File

@@ -1,43 +0,0 @@
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin text-muted tw-m-2"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<div *ngIf="loaded" class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<button
*ngIf="allowEnrollmentChanges(organization) && !organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | i18n }}
</button>
<button
*ngIf="allowEnrollmentChanges(organization) && organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "withdrawPasswordReset" | i18n }}
</button>
<ng-container *ngIf="organization.useSso && organization.identifier">
<button
*ngIf="organization.ssoBound; else linkSso"
class="dropdown-item"
(click)="unlinkSso(organization)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
</button>
<ng-template #linkSso>
<app-link-sso [organization]="organization"> </app-link-sso>
</ng-template>
</ng-container>
<button class="dropdown-item text-danger" (click)="leave(organization)">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "leave" | i18n }}
</button>
</div>

View File

@@ -1,33 +0,0 @@
<ng-container *ngIf="show">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.status === 'all' }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('all')">
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideFavorites"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'favorites' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('favorites')">
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideTrash"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'trash' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('trash')">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</button>
</span>
</li>
</ul>
</ng-container>

View File

@@ -1,9 +0,0 @@
import { Component } from "@angular/core";
import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component";
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",
})
export class StatusFilterComponent extends BaseStatusFilterComponent {}

View File

@@ -1,60 +0,0 @@
<div class="filter-heading">
<button
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="type-filters"
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<h3>
{{ "types" | i18n }}
</h3>
</div>
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Login }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Login)">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>{{ "typeLogin" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Card }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Card)">
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>{{ "typeCard" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Identity }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Identity)">
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>{{ "typeIdentity" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SecureNote }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.SecureNote)">
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>{{ "typeSecureNote" | i18n }}
</button>
</span>
</li>
</ul>

View File

@@ -1,9 +0,0 @@
import { Component } from "@angular/core";
import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component";
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
})
export class TypeFilterComponent extends BaseTypeFilterComponent {}

View File

@@ -1,80 +0,0 @@
<div class="card vault-filters">
<div class="container loading-spinner" *ngIf="!isLoaded">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="isLoaded">
<div class="card-header d-flex">
{{ "filters" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/searching-vault/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<input
type="search"
placeholder="{{ (searchPlaceholder | i18n) || ('searchVault' | i18n) }}"
id="search"
class="form-control"
[(ngModel)]="searchText"
(input)="searchTextChanged()"
autocomplete="off"
appAutofocus
/>
<app-organization-filter
*ngIf="showOrgFilter"
[hide]="hideOrganizations"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations"
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-organization-filter>
<div class="filter">
<app-status-filter
[hideFavorites]="!showFavorites"
[hideTrash]="hideTrash"
[activeFilter]="activeFilter"
(onFilterChange)="applyFilter($event)"
></app-status-filter>
</div>
<div class="filter">
<app-type-filter
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-type-filter>
</div>
<div class="filter">
<app-folder-filter
[hide]="!showFolders"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event)"
></app-folder-filter>
</div>
<div class="filter">
<app-collection-filter
[hide]="hideCollections"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[collectionNodes]="collections"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-collection-filter>
</div>
</div>
</div>
</div>

View File

@@ -1,44 +0,0 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { Organization } from "jslib-common/models/domain/organization";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent {
@Input() showOrgFilter = true;
@Input() showFolders = true;
@Input() showFavorites = true;
@Output() onSearchTextChanged = new EventEmitter<string>();
searchPlaceholder: string;
searchText = "";
organization: Organization;
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
// This method exists because the vault component gets its data mixed up during the initial sync on first login. It looks for data before the sync is complete.
// It should be removed as soon as doing so makes sense.
async reloadOrganizations() {
this.organizations = await this.vaultFilterService.buildOrganizations();
this.activePersonalOwnershipPolicy =
await this.vaultFilterService.checkForPersonalOwnershipPolicy();
this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy();
}
async initCollections() {
return await this.vaultFilterService.buildCollections(this.organization?.id);
}
}

View File

@@ -1,48 +0,0 @@
import { NgModule } from "@angular/core";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SharedModule } from "../shared.module";
import { CollectionFilterComponent } from "./components/collection-filter.component";
import { FolderFilterComponent } from "./components/folder-filter.component";
import { OrganizationFilterComponent } from "./components/organization-filter.component";
import { OrganizationOptionsComponent } from "./components/organization-options.component";
import { StatusFilterComponent } from "./components/status-filter.component";
import { TypeFilterComponent } from "./components/type-filter.component";
import { VaultFilterComponent } from "./vault-filter.component";
@NgModule({
imports: [SharedModule],
declarations: [
VaultFilterComponent,
CollectionFilterComponent,
FolderFilterComponent,
OrganizationFilterComponent,
OrganizationOptionsComponent,
StatusFilterComponent,
TypeFilterComponent,
],
exports: [VaultFilterComponent],
providers: [
{
provide: VaultFilterService,
useClass: VaultFilterService,
deps: [
StateService,
OrganizationService,
FolderService,
CipherService,
CollectionService,
PolicyService,
],
},
],
})
export class VaultFilterModule {}

View File

@@ -1,3 +0,0 @@
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
export class VaultFilterService extends BaseVaultFilterService {}

View File

@@ -1,16 +0,0 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { IndividualVaultComponent } from "./individual-vault.component";
const routes: Routes = [
{
path: "",
component: IndividualVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IndividualVaultRoutingModule {}

View File

@@ -1,13 +0,0 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { IndividualVaultRoutingModule } from "./individual-vault-routing.module";
import { IndividualVaultComponent } from "./individual-vault.component";
@NgModule({
imports: [VaultModule, IndividualVaultRoutingModule],
declarations: [IndividualVaultComponent],
exports: [IndividualVaultComponent],
})
export class IndividualVaultModule {}

View File

@@ -1,12 +0,0 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../../shared.module";
import { OrganizationNameBadgeComponent } from "./organization-name-badge.component";
@NgModule({
imports: [SharedModule],
declarations: [OrganizationNameBadgeComponent],
exports: [OrganizationNameBadgeComponent],
})
export class OrganizationBadgeModule {}

View File

@@ -1,9 +0,0 @@
<button
bit-badge
[style.color]="textColor"
[style.background-color]="color"
appA11yTitle="{{ organizationName }}"
(click)="emitOnOrganizationClicked()"
>
{{ organizationName | ellipsis: 13 }}
</button>

View File

@@ -1,59 +0,0 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: "app-org-badge",
templateUrl: "organization-name-badge.component.html",
})
export class OrganizationNameBadgeComponent implements OnInit {
@Input() organizationName: string;
@Input() profileName: string;
@Output() onOrganizationClicked = new EventEmitter<string>();
color: string;
textColor: string;
constructor(private i18nService: I18nService) {}
ngOnInit(): void {
if (this.organizationName == null || this.organizationName === "") {
this.organizationName = this.i18nService.t("me");
this.color = this.stringToColor(this.profileName.toUpperCase());
}
if (this.color == null) {
this.color = this.stringToColor(this.organizationName.toUpperCase());
}
this.textColor = this.pickTextColorBasedOnBgColor();
}
// This value currently isn't stored anywhere, only calculated in the app-avatar component
// Once we are allowing org colors to be changed and saved, change this out
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ("00" + value.toString(16)).substr(-2);
}
return color;
}
// There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
// https://stackoverflow.com/a/3943023/6869691
private pickTextColorBasedOnBgColor() {
const color = this.color.charAt(0) === "#" ? this.color.substring(1, 7) : this.color;
const r = parseInt(color.substring(0, 2), 16); // hexToR
const g = parseInt(color.substring(2, 4), 16); // hexToG
const b = parseInt(color.substring(4, 6), 16); // hexToB
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? "black !important" : "white !important";
}
emitOnOrganizationClicked() {
this.onOrganizationClicked.emit();
}
}

View File

@@ -1,16 +0,0 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { OrganizationVaultComponent } from "./organization-vault.component";
const routes: Routes = [
{
path: "",
component: OrganizationVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationVaultRoutingModule {}

View File

@@ -1,13 +0,0 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { OrganizationVaultRoutingModule } from "./organization-vault-routing.module";
import { OrganizationVaultComponent } from "./organization-vault.component";
@NgModule({
imports: [VaultModule, OrganizationVaultRoutingModule],
declarations: [OrganizationVaultComponent],
exports: [OrganizationVaultComponent],
})
export class OrganizationVaultModule {}

View File

@@ -1,19 +0,0 @@
import { NgModule } from "@angular/core";
import { LooseComponentsModule } from "../loose-components.module";
import { SharedModule } from "../shared.module";
import { VaultFilterModule } from "../vault-filter/vault-filter.module";
import { VaultService } from "./vault.service";
@NgModule({
imports: [SharedModule, VaultFilterModule, LooseComponentsModule],
exports: [SharedModule, VaultFilterModule, LooseComponentsModule],
providers: [
{
provide: VaultService,
useClass: VaultService,
},
],
})
export class VaultModule {}

View File

@@ -1,29 +0,0 @@
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
export class VaultService {
calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
if (vaultFilter.status === "favorites") {
return "searchFavorites";
}
if (vaultFilter.status === "trash") {
return "searchTrash";
}
if (vaultFilter.cipherType != null) {
return "searchType";
}
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
return "searchFolder";
}
if (vaultFilter.selectedCollectionId != null) {
return "searchCollection";
}
if (vaultFilter.selectedOrganizationId != null) {
return "searchOrganization";
}
if (vaultFilter.myVaultOnly) {
return "searchMyVault";
}
return "searchVault";
}
}

View File

@@ -1,58 +0,0 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(
private router: Router,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private syncService: SyncService
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
// TODO: We need to fix this issue once and for all.
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
const org = await this.organizationService.get(route.params.organizationId);
if (org == null) {
return this.router.createUrlTree(["/"]);
}
if (!org.isOwner && !org.enabled) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("organizationIsDisabled")
);
return this.router.createUrlTree(["/"]);
}
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
if (permissions != null && !org.hasAnyPermission(permissions)) {
// Handle linkable ciphers for organizations the user only has view access to
// https://bitwarden.atlassian.net/browse/EC-203
if (state.root.queryParamMap.has("cipherId")) {
return this.router.createUrlTree(["/vault"], {
queryParams: {
cipherId: state.root.queryParamMap.get("cipherId"),
},
});
}
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
return this.router.createUrlTree(["/"]);
}
return true;
}
}

View File

@@ -20,9 +20,8 @@ import {
import { ListResponse } from "jslib-common/models/response/listResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { EntityUsersComponent } from "../../modules/organizations/manage/entity-users.component";
import { CollectionAddEditComponent } from "./collection-add-edit.component";
import { EntityUsersComponent } from "./entity-users.component";
@Component({
selector: "app-org-manage-collections",

View File

@@ -29,52 +29,52 @@
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div>
<cdk-virtual-scroll-viewport
itemSize="46"
minBufferPx="600"
maxBufferPx="1200"
[style]="scrollViewportStyle"
<div
class="modal-body"
*ngIf="
!loading && users && (users | search: searchText:'name':'email':'id') as searchedUsers
"
>
<div class="modal-body" *ngIf="!loading && users && searchedUsers">
<div class="d-flex">
<div class="mr-3">
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
name="SearchText"
[(ngModel)]="searchText"
/>
</div>
<div class="btn-group btn-group-sm" role="group">
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: !showSelected }"
(click)="filterSelected(false)"
>
{{ "all" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: showSelected }"
(click)="filterSelected(true)"
>
{{ "selected" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="selectedCount">{{
selectedCount
}}</span>
</button>
</div>
<div class="d-flex">
<div class="mr-3">
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
name="SearchText"
[(ngModel)]="searchText"
/>
</div>
<ng-container *ngIf="!searchedUsers.length">
<hr />
{{ "noUsersInList" | i18n }}
</ng-container>
<table class="table table-hover table-list mb-0" [hidden]="!searchedUsers.length">
<div class="btn-group btn-group-sm" role="group">
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: !showSelected }"
(click)="filterSelected(false)"
>
{{ "all" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: showSelected }"
(click)="filterSelected(true)"
>
{{ "selected" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="selectedCount">{{
selectedCount
}}</span>
</button>
</div>
</div>
<ng-container *ngIf="!searchedUsers.length">
<hr />
{{ "noUsersInList" | i18n }}
</ng-container>
<ng-container *ngIf="searchedUsers.length">
<table class="table table-hover table-list mb-0">
<thead>
<tr>
<th>&nbsp;</th>
@@ -91,7 +91,7 @@
</tr>
</thead>
<tbody>
<tr *cdkVirtualFor="let u of searchedUsers" class="">
<tr *ngFor="let u of searchedUsers">
<td class="table-list-checkbox" (click)="check(u)">
<input
type="checkbox"
@@ -164,8 +164,8 @@
</tr>
</tbody>
</table>
</div>
</cdk-virtual-scroll-viewport>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>

View File

@@ -1,6 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
@@ -14,7 +13,6 @@ import { OrganizationUserUserDetailsResponse } from "jslib-common/models/respons
@Component({
selector: "app-entity-users",
templateUrl: "entity-users.component.html",
providers: [SearchPipe],
})
export class EntityUsersComponent implements OnInit {
@Input() entity: "group" | "collection";
@@ -35,7 +33,6 @@ export class EntityUsersComponent implements OnInit {
private allUsers: OrganizationUserUserDetailsResponse[] = [];
constructor(
private search: SearchPipe,
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
@@ -55,14 +52,6 @@ export class EntityUsersComponent implements OnInit {
}
}
get searchedUsers() {
return this.search.transform(this.users, this.searchText, "name", "email", "id");
}
get scrollViewportStyle() {
return `min-height: 120px; height: ${120 + this.searchedUsers.length * 46}px`;
}
async loadUsers() {
const users = await this.apiService.getOrganizationUsers(this.organizationId);
this.allUsers = users.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, "email"));

View File

@@ -12,8 +12,7 @@ import { SearchService } from "jslib-common/abstractions/search.service";
import { Utils } from "jslib-common/misc/utils";
import { GroupResponse } from "jslib-common/models/response/groupResponse";
import { EntityUsersComponent } from "../../modules/organizations/manage/entity-users.component";
import { EntityUsersComponent } from "./entity-users.component";
import { GroupAddEditComponent } from "./group-add-edit.component";
@Component({

View File

@@ -1,222 +0,0 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { PermissionsGuard } from "./guards/permissions.guard";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { CollectionsComponent } from "./manage/collections.component";
import { EventsComponent } from "./manage/events.component";
import { GroupsComponent } from "./manage/groups.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { PoliciesComponent } from "./manage/policies.component";
import { NavigationPermissionsService } from "./services/navigation-permissions.service";
import { AccountComponent } from "./settings/account.component";
import { OrganizationBillingComponent } from "./settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
import { SettingsComponent } from "./settings/settings.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { ExportComponent } from "./tools/export.component";
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
const routes: Routes = [
{
path: ":organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("admin"),
},
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{
path: "vault",
loadChildren: async () =>
(await import("../modules/vault/modules/organization-vault/organization-vault.module"))
.OrganizationVaultModule,
},
{
path: "tools",
component: ToolsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("tools") },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: ImportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: ExportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: ManageComponent,
canActivate: [PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("manage"),
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: CollectionsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: EventsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: GroupsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: PeopleComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: PoliciesComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: SettingsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("settings") },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
canActivate: [PermissionsGuard],
data: { titleId: "billing", permissions: [Permissions.ManageBilling] },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationsRoutingModule {}

View File

@@ -1,48 +0,0 @@
import { Permissions } from "jslib-common/enums/permissions";
import { Organization } from "jslib-common/models/domain/organization";
const permissions = {
manage: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
settings: [Permissions.ManageOrganization],
};
export class NavigationPermissionsService {
static getPermissions(route: keyof typeof permissions | "admin") {
if (route === "admin") {
return Object.values(permissions).reduce((previous, current) => previous.concat(current), []);
}
return permissions[route];
}
static canAccessAdmin(organization: Organization): boolean {
return (
this.canAccessTools(organization) ||
this.canAccessSettings(organization) ||
this.canAccessManage(organization)
);
}
static canAccessTools(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools"));
}
static canAccessSettings(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings"));
}
static canAccessManage(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage"));
}
}

View File

@@ -14,50 +14,62 @@
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="formData"
ngNativeValidate
>
<div class="row">
<div class="col-6">
<bit-form-field>
<bit-label>{{ "organizationName" | i18n }}</bit-label>
<input bitInput type="text" formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
<input bitInput type="email" formControlName="billingEmail" />
</bit-form-field>
<bit-form-field>
<bit-label>{{ "businessName" | i18n }}</bit-label>
<input bitInput type="text" formControlName="businessName" />
</bit-form-field>
<bit-form-field>
<bit-label>{{ "identifier" | i18n }}</bit-label>
<input bitInput type="text" formControlName="identifier" />
</bit-form-field>
<div class="form-group">
<label for="name">{{ "organizationName" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="org.name"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
<input
id="billingEmail"
class="form-control"
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="businessName">{{ "businessName" | i18n }}</label>
<input
id="businessName"
class="form-control"
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
[disabled]="selfHosted"
/>
</div>
<div class="form-group">
<label for="identifier">{{ "identifier" | i18n }}</label>
<input
id="identifier"
class="form-control"
type="text"
name="Identifier"
[(ngModel)]="org.identifier"
/>
</div>
</div>
<div class="col-6">
<app-avatar
[data]="formData.get('name').value"
dynamic="true"
size="75"
fontSize="35"
></app-avatar>
<app-avatar data="{{ org.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div>
</div>
<button
type="submit"
bit-button
buttonType="primary"
class="btn-submit"
[disabled]="form.loading"
>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formData"></bit-error-summary>
</form>
<ng-container *ngIf="canUseApi">
<div class="secondary-header border-0 mb-0">

View File

@@ -1,5 +1,4 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { ModalService } from "jslib-angular/services/modal.service";
@@ -7,7 +6,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { OrganizationKeysRequest } from "jslib-common/models/request/organizationKeysRequest";
@@ -36,25 +34,15 @@ export class AccountComponent {
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
selfHosted = false;
canManageBilling = true;
loading = true;
canUseApi = false;
org: OrganizationResponse;
formPromise: Promise<any>;
taxFormPromise: Promise<any>;
showErrorSummary = false;
private organizationId: string;
formData = this.formBuilder.group({
name: ["", [Validators.required]],
billingEmail: ["", [Validators.required, Validators.email]],
businessName: [],
identifier: [],
});
constructor(
private formBuilder: FormBuilder,
private modalService: ModalService,
private apiService: ApiService,
private i18nService: I18nService,
@@ -63,34 +51,16 @@ export class AccountComponent {
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private router: Router,
private organizationService: OrganizationService
private router: Router
) {}
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
if (this.selfHosted) {
this.formData.disable();
} else {
this.formData.enable();
}
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.canManageBilling = (
await this.organizationService.get(this.organizationId)
).canManageBilling;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi;
this.formData.setValue({
name: this.org.name,
billingEmail: this.org.billingEmail,
businessName: this.org.businessName,
identifier: this.org.identifier,
});
} catch (e) {
this.logService.error(e);
}
@@ -99,19 +69,12 @@ export class AccountComponent {
}
async submit() {
this.formData.markAllAsTouched();
this.showErrorSummary = true;
if (!this.formData.valid) {
return;
}
try {
const request = new OrganizationUpdateRequest();
request.name = this.formData.get("name").value;
request.businessName = this.formData.get("businessName").value;
request.billingEmail = this.formData.get("billingEmail").value;
request.identifier = this.formData.get("identifier").value;
request.name = this.org.name;
request.businessName = this.org.businessName;
request.billingEmail = this.org.billingEmail;
request.identifier = this.org.identifier;
// Backfill pub/priv key if necessary
if (!this.org.hasPublicAndPrivateKeys) {

View File

@@ -1,117 +0,0 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="billingSyncApiKeyTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="billingSyncApiKeyTitle">
{{ (hasBillingToken ? "viewBillingSyncToken" : "generateBillingSyncToken") | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-user-verification
[(ngModel)]="masterPassword"
ngDefaultControl
name="secret"
*ngIf="!clientSecret"
>
</app-user-verification>
<ng-container *ngIf="clientSecret && showRotateScreen">
<p>{{ "rotateBillingSyncTokenTitle" | i18n }}</p>
<app-callout type="warning">
{{ "rotateBillingSyncTokenWarning" | i18n }}
</app-callout>
</ng-container>
<div *ngIf="clientSecret && !showRotateScreen">
<p>{{ "copyPasteBillingSync" | i18n }}</p>
<label for="clientSecret">Billing Sync Key</label>
<div class="input-group">
<input
id="clientSecret"
class="form-control text-monospace"
type="text"
[(ngModel)]="clientSecret"
name="clientSecret"
disabled
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
(click)="copy()"
[appA11yTitle]="'copy' | i18n"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="small text-muted mt-2" *ngIf="showLastSyncText">
<b class="font-weight-semibold">{{ "lastSync" | i18n }}:</b>
{{ lastSyncDate | date: "medium" }}
</div>
<div class="small text-danger mt-2" *ngIf="showAwaitingSyncText">
<i class="bwi bwi-error"></i>
{{
(daysBetween === 1 ? "awaitingSyncSingular" : "awaitingSyncPlural")
| i18n: daysBetween
}}
</div>
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading"
*ngIf="!clientSecret || showRotateScreen"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
*ngIf="form.loading"
></i>
<span>
{{ submitButtonText }}
</span>
</button>
<button
type="button"
class="btn btn-outline-secondary"
data-dismiss="modal"
*ngIf="!showRotateScreen"
>
{{ "close" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
*ngIf="showRotateScreen"
(click)="cancelRotate()"
>
{{ "cancel" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
*ngIf="clientSecret && !showRotateScreen"
(click)="rotateToken()"
>
{{ "rotateToken" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,108 +0,0 @@
import { Component } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { OrganizationApiKeyType } from "jslib-common/enums/organizationApiKeyType";
import { OrganizationApiKeyRequest } from "jslib-common/models/request/organizationApiKeyRequest";
import { ApiKeyResponse } from "jslib-common/models/response/apiKeyResponse";
import { Verification } from "jslib-common/types/verification";
@Component({
selector: "app-billing-sync-api-key",
templateUrl: "billing-sync-api-key.component.html",
})
export class BillingSyncApiKeyComponent {
organizationId: string;
hasBillingToken: boolean;
showRotateScreen: boolean;
masterPassword: Verification;
formPromise: Promise<ApiKeyResponse>;
clientSecret?: string;
keyRevisionDate?: Date;
lastSyncDate?: Date = null;
constructor(
private userVerificationService: UserVerificationService,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
copy() {
this.platformUtilsService.copyToClipboard(this.clientSecret);
}
async submit() {
if (this.showRotateScreen) {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest)
.then((request) => {
request.type = OrganizationApiKeyType.BillingSync;
return this.apiService.postOrganizationRotateApiKey(this.organizationId, request);
});
const response = await this.formPromise;
await this.load(response);
this.showRotateScreen = false;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("billingSyncApiKeyRotated")
);
} else {
this.formPromise = this.userVerificationService
.buildRequest(this.masterPassword, OrganizationApiKeyRequest)
.then((request) => {
request.type = OrganizationApiKeyType.BillingSync;
return this.apiService.postOrganizationApiKey(this.organizationId, request);
});
const response = await this.formPromise;
await this.load(response);
}
}
async load(response: ApiKeyResponse) {
this.clientSecret = response.apiKey;
this.keyRevisionDate = response.revisionDate;
this.hasBillingToken = true;
const syncStatus = await this.apiService.getSponsorshipSyncStatus(this.organizationId);
this.lastSyncDate = syncStatus.lastSyncDate;
}
cancelRotate() {
this.showRotateScreen = false;
}
rotateToken() {
this.showRotateScreen = true;
}
private dayDiff(date1: Date, date2: Date): number {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
return Math.round(diffTime / (1000 * 60 * 60 * 24));
}
get submitButtonText(): string {
if (this.showRotateScreen) {
return this.i18nService.t("rotateToken");
}
return this.i18nService.t(this.hasBillingToken ? "continue" : "generateToken");
}
get showLastSyncText(): boolean {
// If the keyRevisionDate is later than the lastSyncDate we need to show
// a warning that they need to put the billing sync key in their self hosted install
return this.lastSyncDate && this.lastSyncDate > this.keyRevisionDate;
}
get showAwaitingSyncText(): boolean {
return this.lastSyncDate && this.lastSyncDate <= this.keyRevisionDate;
}
get daysBetween(): number {
return this.dayDiff(this.keyRevisionDate, new Date());
}
}

View File

@@ -44,8 +44,8 @@
</ng-container>
</ng-template>
</p>
<app-user-verification [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-user-verification>
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
</app-verify-master-password>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">

View File

@@ -1,20 +0,0 @@
<svg width="216" height="231" viewBox="0 0 216 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M133.356 85.6618C133.136 85.43 132.871 85.2457 132.577 85.1198C132.283 84.9939 131.968 84.93 131.648 84.9318H87.8482C87.5289 84.93 87.2113 84.9939 86.9175 85.1198C86.6237 85.2457 86.359 85.43 86.14 85.6618C85.9083 85.8808 85.7239 86.1473 85.598 86.4411C85.4721 86.7349 85.4082 87.0506 85.41 87.37V116.57C85.4192 118.793 85.8499 120.994 86.6802 123.056C87.4705 125.091 88.5326 127.011 89.8375 128.761C91.1789 130.515 92.6808 132.137 94.3233 133.612C95.8472 135.01 97.4532 136.318 99.1304 137.528C100.59 138.565 102.123 139.547 103.729 140.474C105.335 141.401 106.469 142.027 107.131 142.354C107.799 142.682 108.339 142.941 108.741 143.113C109.055 143.264 109.4 143.339 109.748 143.332C110.091 143.337 110.431 143.257 110.737 143.102C111.146 142.923 111.679 142.671 112.354 142.343C113.03 142.014 114.179 141.386 115.756 140.463C117.333 139.539 118.884 138.554 120.355 137.517C122.034 136.306 123.642 134.999 125.169 133.601C126.814 132.128 128.316 130.504 129.655 128.75C130.958 126.998 132.021 125.08 132.813 123.045C133.645 120.983 134.075 118.782 134.083 116.559V87.3591C134.085 87.0415 134.021 86.7276 133.895 86.4356C133.769 86.1436 133.586 85.8808 133.356 85.6618ZM127.71 116.836C127.71 127.421 109.748 136.514 109.748 136.514V91.1879H127.71V116.836Z" fill="var(--color-secondary-700)"/>
<path d="M24.6216 122.3C24.7144 123.4 25.6819 124.217 26.7825 124.125C27.8832 124.032 28.7002 123.064 28.6074 121.964L24.6216 122.3ZM151.501 45.7445C152.57 45.4679 153.213 44.3768 152.936 43.3074L148.429 25.8809C148.152 24.8115 147.061 24.1688 145.992 24.4454C144.922 24.722 144.28 25.8131 144.556 26.8825L148.563 42.3728L133.073 46.3793C132.003 46.6559 131.361 47.747 131.637 48.8164C131.914 49.8858 133.005 50.5285 134.074 50.2519L151.501 45.7445ZM28.6074 121.964C26.6788 99.0874 34.4658 75.5543 51.9661 58.054L49.1377 55.2256C30.7695 73.5938 22.5982 98.2999 24.6216 122.3L28.6074 121.964ZM51.9661 58.054C78.5404 31.4797 119.036 27.3026 149.985 45.5315L152.015 42.0849C119.534 22.9534 77.0327 27.3306 49.1377 55.2256L51.9661 58.054Z" fill="var(--color-secondary-700)"/>
<path d="M67.4747 185.062C66.4089 185.352 65.7801 186.451 66.0701 187.517L70.797 204.885C71.0871 205.951 72.1862 206.58 73.252 206.29C74.3179 205.999 74.9467 204.9 74.6567 203.834L70.455 188.396L85.8934 184.194C86.9592 183.904 87.5881 182.805 87.298 181.739C87.008 180.674 85.9088 180.045 84.843 180.335L67.4747 185.062ZM192.478 100.283C192.286 99.1952 191.248 98.4697 190.16 98.6625C189.072 98.8552 188.347 99.8931 188.54 100.981L192.478 100.283ZM166.138 172.225C139.813 198.55 99.8271 202.897 68.9932 185.255L67.0068 188.727C99.3669 207.242 141.333 202.687 168.966 175.054L166.138 172.225ZM188.54 100.981C192.985 126.07 185.516 152.847 166.138 172.225L168.966 175.054C189.307 154.713 197.142 126.603 192.478 100.283L188.54 100.981Z" fill="var(--color-secondary-700)"/>
<path d="M37.726 108.132C39.283 92.1931 46.1655 76.6765 58.3734 64.4686C75.2893 47.5527 98.5583 40.8618 120.5 44.396" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<path d="M180.595 119.132C179.653 136.119 172.693 152.834 159.717 165.811C144.44 181.088 123.982 188.025 104 186.623" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<rect x="153.5" y="56.1317" width="49" height="34" rx="2.5" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M153.5 63.6317V63.6317C153.5 66.9454 156.186 69.6317 159.5 69.6317H172.509M202.5 63.6317V63.6317C202.5 66.9454 199.814 69.6317 196.5 69.6317H183.491" stroke="var(--color-secondary-700)" stroke-width="3"/>
<mask id="path-8-inside-1_1000_26057" fill="white">
<rect x="172" y="65.6317" width="12" height="9" rx="1.25"/>
</mask>
<rect x="172" y="65.6317" width="12" height="9" rx="1.25" stroke="var(--color-secondary-700)" stroke-width="6" mask="url(#path-8-inside-1_1000_26057)"/>
<path d="M187 54.6317C187 54.6317 187 53.6317 187 52.6317C187 51.6317 186.053 50.6317 185.105 50.6317C184.158 50.6317 171.842 50.6317 170.895 50.6317C169.947 50.6317 169 51.6317 169 52.6317C169 53.6317 169 54.6317 169 54.6317" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="48" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M64.9935 168.5H64.9126H64.8318H64.7512H64.6708H64.5905H64.5104H64.4304H64.3506H64.2709H64.1914H64.1121H64.0329H63.9538H63.8749H63.7962H63.7176H63.6392H63.5609H63.4828H63.4048H63.3269H63.2492H63.1717H63.0943H63.017H62.9399H62.8629H62.7861H62.7094H62.6329H62.5565H62.4802H62.4041H62.3281H62.2523H62.1766H62.101H62.0256H61.9503H61.8751H61.8001H61.7252H61.6504H61.5758H61.5013H61.4269H61.3527H61.2786H61.2046H61.1308H61.0571H60.9835H60.91H60.8367H60.7635H60.6904H60.6175H60.5446H60.4719H60.3993H60.3269H60.2545H60.1823H60.1102H60.0382H59.9664H59.8946H59.823H59.7515H59.6801H59.6088H59.5376H59.4666H59.3956H59.3248H59.2541H59.1835H59.113H59.0426H58.9724H58.9022H58.8322H58.7622H58.6924H58.6226H58.553H58.4835H58.4141H58.3448H58.2756H58.2065H58.1375H58.0686H57.9998H57.9311H57.8625H57.794H57.7256H57.6572H57.589H57.5209H57.4529H57.385H57.3172H57.2494H57.1818H57.1142H57.0468H56.9794H56.9121H56.8449H56.7779H56.7108H56.6439H56.5771H56.5103H56.4437H56.3771H56.3106H56.2442H56.1779H56.1117H56.0455H55.9794H55.9134H55.8475H55.7817H55.7159H55.6502H55.5846H55.5191H55.4537H55.3883H55.323H55.2578H55.1926H55.1275H55.0625H54.9976H54.9328H54.868H54.8032H54.7386H54.674H54.6095H54.5451H54.4807H54.4164H54.3521H54.2879H54.2238H54.1598H54.0958H54.0318H53.968H53.9042H53.8404H53.7767H53.7131H53.6495H53.586H53.5226H53.4592H53.3958H53.3325H53.2693H53.2061H53.143H53.0799H53.0169H52.9539H52.891H52.8281H52.7653H52.7025H52.6398H52.5771H52.5145H52.4519H52.3894H52.3269H52.2645H52.202H52.1397H52.0774H52.0151H51.9528H51.8907H51.8285H51.7664H51.7043H51.6423H51.5803H51.5183H51.4564H51.3945H51.3326H51.2708H51.209H51.1472H51.0855H51.0238H50.9621H50.9005H50.8389H50.7773H50.7157H50.6542H50.5927H50.5312H50.4698H50.4084H50.347H50.2856H50.2243H50.1629H50.1016H50.0404H49.9791H49.9178H49.8566H49.7954H49.7342H49.6731H49.6119H49.5508H49.4896H49.4285H49.3674H49.3064H49.2453H49.1842H49.1232H49.0622H49.0011H48.9401H48.8791H48.8181H48.7571H48.6962H48.6352H48.5742H48.5133H48.4523H48.3913H48.3304H48.2694H48.2085H48.1475H48.0866H48.0257H47.9647H47.9038H47.8428H47.7819H47.7209H47.6599H47.599H47.538H47.477H47.416H47.3551H47.2941H47.2331H47.172H47.111H47.05H46.9889H46.9279H46.8668H46.8057H46.7446H46.6835H46.6224H46.5612H46.5001H46.4389H46.3777H46.3165H46.2553H46.194H46.1328H46.0715H46.0102H45.9489H45.8875H45.8261H45.7647H45.7033H45.6418H45.5804H45.5189H45.4573H45.3958H45.3342H45.2726H45.2109H45.1493H45.0876H45.0258H44.9641H44.9023H44.8404H44.7786H44.7166H44.6547H44.5927H44.5307H44.4687H44.4066H44.3445H44.2823H44.2201H44.1578H44.0956H44.0332H43.9709H43.9084H43.846H43.7835H43.7209H43.6583H43.5957H43.533H43.4703H43.4075H43.3447H43.2818H43.2189H43.1559H43.0929H43.0298H42.9666H42.9034H42.8402H42.7769H42.7135H42.6501H42.5867H42.5231H42.4596H42.3959H42.3322H42.2685H42.2046H42.1408H42.0768H42.0128H41.9487H41.8846H41.8204H41.7562H41.6918H41.6274H41.563H41.4985H41.4339H41.3692H41.3045H41.2397H41.1748H41.1098H41.0448H40.9797H40.9146H40.8493H40.784H40.7186H40.6532H40.5876H40.522H40.4563H40.3905H40.3247H40.2588H40.1928H40.1267H40.0605H39.9942H39.9279H39.8615H39.795H39.7284H39.6617H39.5949H39.5281H39.4611H39.3941H39.327H39.2598H39.1925H39.1251H39.0576H38.9901H38.9224H38.8547H38.7868H38.7189H38.6508H38.5827H38.5145H38.4461H38.3777H38.3092H38.2406H38.1719H38.103H38.0341H37.9651H37.896H37.8267H37.7574H37.688H37.6184H37.5488H37.479H37.4092H37.3392H37.2691H37.199H37.1287H37.0583H36.9878H36.9171H36.8464H36.7755H36.7046H36.6335H36.5623H36.491H36.4196H36.3481H36.2764H36.2046H36.1328H36.0607H35.9886H35.9164H35.844H35.7715H35.6989H35.6262H35.5533H35.4803H35.4072H35.334H35.2606H35.1872H35.1135H35.0398H34.9659H34.8919H34.8178H34.7436H34.6692H34.5947H34.52H34.4452H34.3703H34.2952H34.2201H34.1447H34.0693H33.9937H33.9179H33.8421H33.7661H33.6899H33.6136H33.5372H33.4606H33.3839H33.3071H33.2301H33.1529H33.0756H32.9982H32.9206H32.8429H32.765H32.687H32.6088H32.5305H32.452H32.3734H32.2946H32.2157H32.1367H32.0574H31.978H31.8985H31.8188H31.739H31.659H31.5788H31.4985H31.418H31.3374H31.2566H31.1757H31.0946H31.0133H30.9318H30.8503H30.7685H30.6866H30.6045H30.5222H30.4398H30.3572H30.2745H30.1915C30.0489 168.5 29.9693 168.466 29.9164 168.433C29.8553 168.394 29.7779 168.322 29.702 168.192C29.5361 167.906 29.4608 167.48 29.5197 167.111C30.9235 158.322 38.6474 151.574 47.9925 151.574C57.3375 151.574 65.0615 158.322 66.4652 167.111C66.5751 167.799 66.4037 168.054 66.3124 168.149C66.1888 168.278 65.8364 168.5 64.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="20" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M36.9935 168.5H36.9126H36.8318H36.7512H36.6708H36.5905H36.5104H36.4304H36.3506H36.2709H36.1914H36.1121H36.0329H35.9538H35.8749H35.7962H35.7176H35.6392H35.5609H35.4828H35.4048H35.3269H35.2492H35.1717H35.0943H35.017H34.9399H34.8629H34.7861H34.7094H34.6329H34.5565H34.4802H34.4041H34.3281H34.2523H34.1766H34.101H34.0256H33.9503H33.8751H33.8001H33.7252H33.6504H33.5758H33.5013H33.4269H33.3527H33.2786H33.2046H33.1308H33.0571H32.9835H32.91H32.8367H32.7635H32.6904H32.6175H32.5446H32.4719H32.3993H32.3269H32.2545H32.1823H32.1102H32.0382H31.9664H31.8946H31.823H31.7515H31.6801H31.6088H31.5376H31.4666H31.3956H31.3248H31.2541H31.1835H31.113H31.0426H30.9724H30.9022H30.8322H30.7622H30.6924H30.6226H30.553H30.4835H30.4141H30.3448H30.2756H30.2065H30.1375H30.0686H29.9998H29.9311H29.8625H29.794H29.7256H29.6572H29.589H29.5209H29.4529H29.385H29.3172H29.2494H29.1818H29.1142H29.0468H28.9794H28.9121H28.8449H28.7779H28.7108H28.6439H28.5771H28.5103H28.4437H28.3771H28.3106H28.2442H28.1779H28.1117H28.0455H27.9794H27.9134H27.8475H27.7817H27.7159H27.6502H27.5846H27.5191H27.4537H27.3883H27.323H27.2578H27.1926H27.1275H27.0625H26.9976H26.9328H26.868H26.8032H26.7386H26.674H26.6095H26.5451H26.4807H26.4164H26.3521H26.2879H26.2238H26.1598H26.0958H26.0318H25.968H25.9042H25.8404H25.7767H25.7131H25.6495H25.586H25.5226H25.4592H25.3958H25.3325H25.2693H25.2061H25.143H25.0799H25.0169H24.9539H24.891H24.8281H24.7653H24.7025H24.6398H24.5771H24.5145H24.4519H24.3894H24.3269H24.2645H24.202H24.1397H24.0774H24.0151H23.9528H23.8907H23.8285H23.7664H23.7043H23.6423H23.5803H23.5183H23.4564H23.3945H23.3326H23.2708H23.209H23.1472H23.0855H23.0238H22.9621H22.9005H22.8389H22.7773H22.7157H22.6542H22.5927H22.5312H22.4698H22.4084H22.347H22.2856H22.2243H22.1629H22.1016H22.0404H21.9791H21.9178H21.8566H21.7954H21.7342H21.6731H21.6119H21.5508H21.4896H21.4285H21.3674H21.3064H21.2453H21.1842H21.1232H21.0622H21.0011H20.9401H20.8791H20.8181H20.7571H20.6962H20.6352H20.5742H20.5133H20.4523H20.3913H20.3304H20.2694H20.2085H20.1475H20.0866H20.0257H19.9647H19.9038H19.8428H19.7819H19.7209H19.6599H19.599H19.538H19.477H19.416H19.3551H19.2941H19.2331H19.172H19.111H19.05H18.9889H18.9279H18.8668H18.8057H18.7446H18.6835H18.6224H18.5612H18.5001H18.4389H18.3777H18.3165H18.2553H18.194H18.1328H18.0715H18.0102H17.9489H17.8875H17.8261H17.7647H17.7033H17.6418H17.5804H17.5189H17.4573H17.3958H17.3342H17.2726H17.2109H17.1493H17.0876H17.0258H16.9641H16.9023H16.8404H16.7786H16.7166H16.6547H16.5927H16.5307H16.4687H16.4066H16.3445H16.2823H16.2201H16.1578H16.0956H16.0332H15.9709H15.9084H15.846H15.7835H15.7209H15.6583H15.5957H15.533H15.4703H15.4075H15.3447H15.2818H15.2189H15.1559H15.0929H15.0298H14.9666H14.9034H14.8402H14.7769H14.7135H14.6501H14.5867H14.5231H14.4596H14.3959H14.3322H14.2685H14.2046H14.1408H14.0768H14.0128H13.9487H13.8846H13.8204H13.7562H13.6918H13.6274H13.563H13.4985H13.4339H13.3692H13.3045H13.2397H13.1748H13.1098H13.0448H12.9797H12.9146H12.8493H12.784H12.7186H12.6532H12.5876H12.522H12.4563H12.3905H12.3247H12.2588H12.1928H12.1267H12.0605H11.9942H11.9279H11.8615H11.795H11.7284H11.6617H11.5949H11.5281H11.4611H11.3941H11.327H11.2598H11.1925H11.1251H11.0576H10.9901H10.9224H10.8547H10.7868H10.7189H10.6508H10.5827H10.5145H10.4461H10.3777H10.3092H10.2406H10.1719H10.103H10.0341H9.9651H9.89597H9.82674H9.75741H9.68798H9.61843H9.54879H9.47904H9.40918H9.33921H9.26914H9.19896H9.12867H9.05826H8.98775H8.91713H8.8464H8.77555H8.70459H8.63351H8.56232H8.49102H8.4196H8.34807H8.27641H8.20464H8.13276H8.06075H7.98862H7.91638H7.84401H7.77152H7.69891H7.62617H7.55332H7.48034H7.40723H7.334H7.26064H7.18716H7.11355H7.03981H6.96594H6.89195H6.81782H6.74356H6.66918H6.59466H6.52H6.44522H6.3703H6.29525H6.22006H6.14474H6.06928H5.99368H5.91795H5.84208H5.76607H5.68992H5.61363H5.5372H5.46062H5.38391H5.30705H5.23005H5.15291H5.07562H4.99819H4.92061H4.84288H4.76501H4.68699H4.60882H4.5305H4.45203H4.37342H4.29465H4.21573H4.13665H4.05743H3.97805H3.89851H3.81882H3.73898H3.65898H3.57882H3.49851H3.41804H3.33741H3.25662H3.17566H3.09455H3.01328H2.93185H2.85025H2.76849H2.68657H2.60448H2.52223H2.43981H2.35722H2.27447H2.19155C2.04893 168.5 1.96927 168.466 1.91645 168.433C1.85532 168.394 1.77792 168.322 1.702 168.192C1.53613 167.906 1.46078 167.48 1.51975 167.111C2.92347 158.322 10.6474 151.574 19.9925 151.574C29.3375 151.574 37.0615 158.322 38.4652 167.111C38.5751 167.799 38.4037 168.054 38.3124 168.149C38.1888 168.278 37.8364 168.5 36.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="34" cy="154" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M50.9935 181.5H50.9126H50.8318H50.7512H50.6708H50.5905H50.5104H50.4304H50.3506H50.2709H50.1914H50.1121H50.0329H49.9538H49.8749H49.7962H49.7176H49.6392H49.5609H49.4828H49.4048H49.3269H49.2492H49.1717H49.0943H49.017H48.9399H48.8629H48.7861H48.7094H48.6329H48.5565H48.4802H48.4041H48.3281H48.2523H48.1766H48.101H48.0256H47.9503H47.8751H47.8001H47.7252H47.6504H47.5758H47.5013H47.4269H47.3527H47.2786H47.2046H47.1308H47.0571H46.9835H46.91H46.8367H46.7635H46.6904H46.6175H46.5446H46.4719H46.3993H46.3269H46.2545H46.1823H46.1102H46.0382H45.9664H45.8946H45.823H45.7515H45.6801H45.6088H45.5376H45.4666H45.3956H45.3248H45.2541H45.1835H45.113H45.0426H44.9724H44.9022H44.8322H44.7622H44.6924H44.6226H44.553H44.4835H44.4141H44.3448H44.2756H44.2065H44.1375H44.0686H43.9998H43.9311H43.8625H43.794H43.7256H43.6572H43.589H43.5209H43.4529H43.385H43.3172H43.2494H43.1818H43.1142H43.0468H42.9794H42.9121H42.8449H42.7779H42.7108H42.6439H42.5771H42.5103H42.4437H42.3771H42.3106H42.2442H42.1779H42.1117H42.0455H41.9794H41.9134H41.8475H41.7817H41.7159H41.6502H41.5846H41.5191H41.4537H41.3883H41.323H41.2578H41.1926H41.1275H41.0625H40.9976H40.9328H40.868H40.8032H40.7386H40.674H40.6095H40.5451H40.4807H40.4164H40.3521H40.2879H40.2238H40.1598H40.0958H40.0318H39.968H39.9042H39.8404H39.7767H39.7131H39.6495H39.586H39.5226H39.4592H39.3958H39.3325H39.2693H39.2061H39.143H39.0799H39.0169H38.9539H38.891H38.8281H38.7653H38.7025H38.6398H38.5771H38.5145H38.4519H38.3894H38.3269H38.2645H38.202H38.1397H38.0774H38.0151H37.9528H37.8907H37.8285H37.7664H37.7043H37.6423H37.5803H37.5183H37.4564H37.3945H37.3326H37.2708H37.209H37.1472H37.0855H37.0238H36.9621H36.9005H36.8389H36.7773H36.7157H36.6542H36.5927H36.5312H36.4698H36.4084H36.347H36.2856H36.2243H36.1629H36.1016H36.0404H35.9791H35.9178H35.8566H35.7954H35.7342H35.6731H35.6119H35.5508H35.4896H35.4285H35.3674H35.3064H35.2453H35.1842H35.1232H35.0622H35.0011H34.9401H34.8791H34.8181H34.7571H34.6962H34.6352H34.5742H34.5133H34.4523H34.3913H34.3304H34.2694H34.2085H34.1475H34.0866H34.0257H33.9647H33.9038H33.8428H33.7819H33.7209H33.6599H33.599H33.538H33.477H33.416H33.3551H33.2941H33.2331H33.172H33.111H33.05H32.9889H32.9279H32.8668H32.8057H32.7446H32.6835H32.6224H32.5612H32.5001H32.4389H32.3777H32.3165H32.2553H32.194H32.1328H32.0715H32.0102H31.9489H31.8875H31.8261H31.7647H31.7033H31.6418H31.5804H31.5189H31.4573H31.3958H31.3342H31.2726H31.2109H31.1493H31.0876H31.0258H30.9641H30.9023H30.8404H30.7786H30.7166H30.6547H30.5927H30.5307H30.4687H30.4066H30.3445H30.2823H30.2201H30.1578H30.0956H30.0332H29.9709H29.9084H29.846H29.7835H29.7209H29.6583H29.5957H29.533H29.4703H29.4075H29.3447H29.2818H29.2189H29.1559H29.0929H29.0298H28.9666H28.9034H28.8402H28.7769H28.7135H28.6501H28.5867H28.5231H28.4596H28.3959H28.3322H28.2685H28.2046H28.1408H28.0768H28.0128H27.9487H27.8846H27.8204H27.7562H27.6918H27.6274H27.563H27.4985H27.4339H27.3692H27.3045H27.2397H27.1748H27.1098H27.0448H26.9797H26.9146H26.8493H26.784H26.7186H26.6532H26.5876H26.522H26.4563H26.3905H26.3247H26.2588H26.1928H26.1267H26.0605H25.9942H25.9279H25.8615H25.795H25.7284H25.6617H25.5949H25.5281H25.4611H25.3941H25.327H25.2598H25.1925H25.1251H25.0576H24.9901H24.9224H24.8547H24.7868H24.7189H24.6508H24.5827H24.5145H24.4461H24.3777H24.3092H24.2406H24.1719H24.103H24.0341H23.9651H23.896H23.8267H23.7574H23.688H23.6184H23.5488H23.479H23.4092H23.3392H23.2691H23.199H23.1287H23.0583H22.9878H22.9171H22.8464H22.7755H22.7046H22.6335H22.5623H22.491H22.4196H22.3481H22.2764H22.2046H22.1328H22.0607H21.9886H21.9164H21.844H21.7715H21.6989H21.6262H21.5533H21.4803H21.4072H21.334H21.2606H21.1872H21.1135H21.0398H20.9659H20.8919H20.8178H20.7436H20.6692H20.5947H20.52H20.4452H20.3703H20.2952H20.2201H20.1447H20.0693H19.9937H19.9179H19.8421H19.7661H19.6899H19.6136H19.5372H19.4606H19.3839H19.3071H19.2301H19.1529H19.0756H18.9982H18.9206H18.8429H18.765H18.687H18.6088H18.5305H18.452H18.3734H18.2946H18.2157H18.1367H18.0574H17.978H17.8985H17.8188H17.739H17.659H17.5788H17.4985H17.418H17.3374H17.2566H17.1757H17.0946H17.0133H16.9318H16.8503H16.7685H16.6866H16.6045H16.5222H16.4398H16.3572H16.2745H16.1915C16.045 181.5 15.9628 181.465 15.9092 181.432C15.8479 181.394 15.772 181.324 15.6978 181.198C15.5354 180.922 15.4617 180.509 15.5193 180.153C16.9196 171.496 24.6325 164.823 33.9925 164.823C43.3524 164.823 51.0654 171.496 52.4657 180.153C52.574 180.823 52.4052 181.064 52.319 181.152C52.1962 181.279 51.8413 181.5 50.9935 181.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,8 +0,0 @@
import { Component } from "@angular/core";
// Component is used so that the SVG can embed CSS color variables
@Component({
selector: "app-image-org-subscription-hidden",
templateUrl: "image-subscription-hidden.component.svg",
})
export class ImageSubscriptionHiddenComponent {}

View File

@@ -5,38 +5,23 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
import { TransactionType } from "jslib-common/enums/transactionType";
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
import { BillingResponse } from "jslib-common/models/response/billingResponse";
import { UserBillingComponent } from "../../settings/user-billing.component";
@Component({
selector: "app-org-billing",
templateUrl: "./organization-billing.component.html",
templateUrl: "../../settings/user-billing.component.html",
})
export class OrganizationBillingComponent implements OnInit {
loading = false;
firstLoaded = false;
showAdjustPayment = false;
showAddCredit = false;
billing: BillingResponse;
paymentMethodType = PaymentMethodType;
transactionType = TransactionType;
organizationId: string;
verifyAmount1: number;
verifyAmount2: number;
verifyBankPromise: Promise<any>;
// TODO - Make sure to properly split out the billing/invoice and payment method/account during org admin refresh
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
constructor(
private apiService: ApiService,
private i18nService: I18nService,
apiService: ApiService,
i18nService: I18nService,
private route: ActivatedRoute,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(apiService, i18nService, platformUtilsService, logService);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
@@ -45,110 +30,4 @@ export class OrganizationBillingComponent implements OnInit {
this.firstLoaded = true;
});
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
}
this.loading = false;
}
async verifyBank() {
if (this.loading) {
return;
}
try {
const request = new VerifyBankRequest();
request.amount1 = this.verifyAmount1;
request.amount2 = this.verifyAmount2;
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
this.organizationId,
request
);
await this.verifyBankPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verifiedBankAccount")
);
this.load();
} catch (e) {
this.logService.error(e);
}
}
addCredit() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("addCredit"),
null,
null,
"warning"
);
return;
}
this.showAddCredit = true;
}
closeAddCredit(load: boolean) {
this.showAddCredit = false;
if (load) {
this.load();
}
}
changePayment() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("changePaymentMethod"),
null,
null,
"warning"
);
return;
}
this.showAdjustPayment = true;
}
closePayment(load: boolean) {
this.showAdjustPayment = false;
if (load) {
this.load();
}
}
get isCreditBalance() {
return this.billing == null || this.billing.balance <= 0;
}
get creditOrBalance() {
return Math.abs(this.billing != null ? this.billing.balance : 0);
}
get paymentSource() {
return this.billing != null ? this.billing.paymentSource : null;
}
get paymentSourceInApp() {
return (
this.paymentSource != null &&
(this.paymentSource.type === PaymentMethodType.AppleInApp ||
this.paymentSource.type === PaymentMethodType.GoogleInApp)
);
}
get invoices() {
return this.billing != null ? this.billing.invoices : null;
}
get transactions() {
return this.billing != null ? this.billing.transactions : null;
}
}

View File

@@ -16,14 +16,6 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="firstLoaded && !userOrg.canManageBilling">
<div class="tw-flex tw-flex-col tw-items-center tw-text-info">
<app-image-org-subscription-hidden></app-image-org-subscription-hidden>
<p class="tw-font-bold">{{ "billingManagedByProvider" | i18n: this.userOrg.providerName }}</p>
<p>{{ "billingContactProviderForAssistance" | i18n }}</p>
</div>
</ng-container>
<ng-container *ngIf="sub">
<app-callout
type="warning"
@@ -188,10 +180,10 @@
></app-adjust-storage>
</div>
</ng-container>
<!--Switch to i18n-->
<h2 class="spaced-header">{{ "selfHostingTitle" | i18n }}</h2>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "selfHostingEnterpriseOrganizationSectionCopy" | i18n }}
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
@@ -203,27 +195,6 @@
>
{{ "downloadLicense" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary ml-1"
(click)="manageBillingSync()"
*ngIf="canManageBillingSync"
>
{{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license
[organizationId]="organizationId"
(onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
#cancelBtn
type="button"
@@ -237,6 +208,13 @@
<span>{{ "cancelSubscription" | i18n }}</span>
</button>
</div>
<div class="mt-3" *ngIf="showDownloadLicense">
<app-download-license
[organizationId]="organizationId"
(onDownloaded)="closeDownloadLicense()"
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
</ng-container>
<ng-container *ngIf="selfHosted">
<dl>
@@ -283,31 +261,5 @@
></app-update-license>
</div>
</div>
<div *ngIf="showBillingSyncKey">
<h2 class="mt-5">
{{ "billingSync" | i18n }}
</h2>
<p>
{{ "billingSyncDesc" | i18n }}
</p>
<button
type="button"
class="btn btn-outline-secondary"
(click)="manageBillingSyncSelfHosted()"
>
{{ "manageBillingSync" | i18n }}
</button>
<small class="form-text text-muted" *ngIf="billingSyncSetUp">
{{ "lastSync" | i18n }}:
<span *ngIf="userOrg.familySponsorshipLastSyncDate != null">
{{ userOrg.familySponsorshipLastSyncDate | date: "medium" }}
</span>
<span *ngIf="userOrg.familySponsorshipLastSyncDate == null">
{{ "never" | i18n | lowercase }}
</span>
</small>
</div>
</ng-container>
</ng-container>
<ng-template #setupBillingSyncTemplate></ng-template>
<ng-template #rotateBillingSyncKeyTemplate></ng-template>

View File

@@ -1,34 +1,21 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalRef } from "jslib-angular/components/modal/modal.ref";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { OrganizationApiKeyType } from "jslib-common/enums/organizationApiKeyType";
import { OrganizationConnectionType } from "jslib-common/enums/organizationConnectionType";
import { PlanType } from "jslib-common/enums/planType";
import { BillingSyncConfigApi } from "jslib-common/models/api/billingSyncConfigApi";
import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationConnectionResponse } from "jslib-common/models/response/organizationConnectionResponse";
import { OrganizationSubscriptionResponse } from "jslib-common/models/response/organizationSubscriptionResponse";
import { BillingSyncKeyComponent } from "src/app/settings/billing-sync-key.component";
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
@Component({
selector: "app-org-subscription",
templateUrl: "organization-subscription.component.html",
})
export class OrganizationSubscriptionComponent implements OnInit {
@ViewChild("setupBillingSyncTemplate", { read: ViewContainerRef, static: true })
setupBillingSyncModalRef: ViewContainerRef;
loading = false;
firstLoaded = false;
organizationId: string;
@@ -38,24 +25,17 @@ export class OrganizationSubscriptionComponent implements OnInit {
adjustStorageAdd = true;
showAdjustStorage = false;
showUpdateLicense = false;
showBillingSyncKey = false;
showDownloadLicense = false;
showChangePlan = false;
sub: OrganizationSubscriptionResponse;
selfHosted = false;
hasBillingSyncToken: boolean;
userOrg: Organization;
existingBillingSyncConnection: OrganizationConnectionResponse<BillingSyncConfigApi>;
removeSponsorshipPromise: Promise<any>;
cancelPromise: Promise<any>;
reinstatePromise: Promise<any>;
@ViewChild("rotateBillingSyncKeyTemplate", { read: ViewContainerRef, static: true })
billingSyncKeyViewContainerRef: ViewContainerRef;
billingSyncKeyRef: [ModalRef, BillingSyncKeyComponent];
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
@@ -63,8 +43,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
private messagingService: MessagingService,
private route: ActivatedRoute,
private organizationService: OrganizationService,
private logService: LogService,
private modalService: ModalService
private logService: LogService
) {
this.selfHosted = platformUtilsService.isSelfHost();
}
@@ -84,28 +63,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
if (this.userOrg.canManageBilling) {
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
}
const apiKeyResponse = await this.apiService.getOrganizationApiKeyInformation(
this.organizationId
);
this.hasBillingSyncToken = apiKeyResponse.data.some(
(i) => i.keyType === OrganizationApiKeyType.BillingSync
);
if (this.selfHosted) {
this.showBillingSyncKey = await this.apiService.getCloudCommunicationsEnabled();
}
if (this.showBillingSyncKey) {
this.existingBillingSyncConnection = await this.apiService.getOrganizationConnection(
this.organizationId,
OrganizationConnectionType.CloudBillingSync,
BillingSyncConfigApi
);
}
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
this.loading = false;
}
@@ -176,20 +134,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.showDownloadLicense = !this.showDownloadLicense;
}
async manageBillingSync() {
const [ref] = await this.modalService.openViewRef(
BillingSyncApiKeyComponent,
this.setupBillingSyncModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.hasBillingToken = this.hasBillingSyncToken;
}
);
ref.onClosed.subscribe(async () => {
await this.load();
});
}
closeDownloadLicense() {
this.showDownloadLicense = false;
}
@@ -252,24 +196,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
}
}
async manageBillingSyncSelfHosted() {
this.billingSyncKeyRef = await this.modalService.openViewRef(
BillingSyncKeyComponent,
this.billingSyncKeyViewContainerRef,
(comp) => {
comp.entityId = this.organizationId;
comp.existingConnectionId = this.existingBillingSyncConnection?.id;
comp.billingSyncKey = this.existingBillingSyncConnection?.config?.billingSyncKey;
comp.setParentConnection = (
connection: OrganizationConnectionResponse<BillingSyncConfigApi>
) => {
this.existingBillingSyncConnection = connection;
this.billingSyncKeyRef[0].close();
};
}
);
}
get isExpired() {
return (
this.sub != null && this.sub.expiration != null && new Date(this.sub.expiration) < new Date()
@@ -336,16 +262,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
);
}
get canManageBillingSync() {
return (
!this.selfHosted &&
(this.sub.planType === PlanType.EnterpriseAnnually ||
this.sub.planType === PlanType.EnterpriseMonthly ||
this.sub.planType === PlanType.EnterpriseAnnually2019 ||
this.sub.planType === PlanType.EnterpriseMonthly2019)
);
}
get subscriptionDesc() {
if (this.sub.planType === PlanType.Free) {
return this.i18nService.t("subscriptionFreePlan", this.sub.seats.toString());
@@ -373,8 +289,4 @@ export class OrganizationSubscriptionComponent implements OnInit {
get showChangePlanButton() {
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
}
get billingSyncSetUp() {
return this.existingBillingSyncConnection?.id != null;
}
}

View File

@@ -14,7 +14,7 @@
routerLink="billing"
class="list-group-item"
routerLinkActive="active"
*ngIf="showBilling"
*ngIf="!selfHosted"
>
{{ "billing" | i18n }}
</a>

View File

@@ -10,7 +10,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
})
export class SettingsComponent {
access2fa = false;
showBilling: boolean;
selfHosted: boolean;
constructor(
private route: ActivatedRoute,
@@ -20,8 +20,8 @@ export class SettingsComponent {
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.organizationService.get(params.organizationId);
this.showBilling = !this.platformUtilsService.isSelfHost() && organization.canManageBilling;
this.access2fa = organization.use2fa;
});
}

View File

@@ -1,13 +0,0 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<i
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
</div>
</div>

View File

@@ -1,26 +0,0 @@
import { Component } from "@angular/core";
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
@Component({
selector: "app-accept-family-sponsorship",
templateUrl: "accept-family-sponsorship.component.html",
})
export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent {
failedShortMessage = "inviteAcceptFailedShort";
failedMessage = "inviteAcceptFailed";
requiredParameters = ["email", "token"];
async authedHandler(qParams: any) {
this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams });
}
async unauthedHandler(qParams: any) {
if (!qParams.register) {
this.router.navigate(["/login"], { queryParams: { email: qParams.email } });
} else {
this.router.navigate(["/register"], { queryParams: { email: qParams.email } });
}
}
}

View File

@@ -14,7 +14,7 @@ import { CipherView } from "jslib-common/models/view/cipherView";
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component";
@Component({
selector: "app-org-exposed-passwords-report",
selector: "app-exposed-passwords-report",
templateUrl: "../../reports/exposed-passwords-report.component.html",
})
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
@@ -41,10 +41,12 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
}
ngOnInit() {
const dynamicSuper = Object.getPrototypeOf(this.constructor.prototype);
this.route.parent.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
await this.checkAccess();
// TODO: We should do something about this, calling super in an async function is bad
dynamicSuper.ngOnInit();
});
}

View File

@@ -5,12 +5,10 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
@@ -34,14 +32,12 @@ export class CiphersComponent extends BaseCiphersComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
cipherService: CipherService,
private apiService: ApiService,
eventService: EventService,
totpService: TotpService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
stateService: StateService,
organizationService: OrganizationService,
tokenService: TokenService,
private apiService: ApiService
stateService: StateService
) {
super(
searchService,
@@ -52,14 +48,11 @@ export class CiphersComponent extends BaseCiphersComponent {
totpService,
stateService,
passwordRepromptService,
logService,
organizationService,
tokenService
logService
);
}
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
this.deleted = deleted || false;
async load(filter: (cipher: CipherView) => boolean = null) {
if (this.organization.canEditAnyCollection) {
this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);

View File

@@ -0,0 +1,65 @@
import { Component } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { Organization } from "jslib-common/models/domain/organization";
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { GroupingsComponent as BaseGroupingsComponent } from "../../vault/groupings.component";
@Component({
selector: "app-org-vault-groupings",
templateUrl: "../../vault/groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
organization: Organization;
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService,
private apiService: ApiService,
private i18nService: I18nService
) {
super(collectionService, folderService, stateService);
}
async loadCollections() {
if (!this.organization.canEditAnyCollection) {
await super.loadCollections(this.organization.id);
return;
}
const collections = await this.apiService.getCollections(this.organization.id);
if (collections != null && collections.data != null && collections.data.length) {
const collectionDomains = collections.data.map(
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
);
this.collections = await this.collectionService.decryptMany(collectionDomains);
} else {
this.collections = [];
}
const unassignedCollection = new CollectionView();
unassignedCollection.name = this.i18nService.t("unassigned");
unassignedCollection.id = "unassigned";
unassignedCollection.organizationId = this.organization.id;
unassignedCollection.readOnly = true;
this.collections.push(unassignedCollection);
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
async collapse(grouping: CollectionView) {
await super.collapse(grouping, "org_");
}
isCollapsed(grouping: CollectionView) {
return super.isCollapsed(grouping, "org_");
}
}

View File

@@ -1,26 +1,22 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-vault-filter
#vaultFilter
[showFolders]="false"
[showFavorites]="false"
[activeFilter]="activeFilter"
[showOrgFilter]="false"
(onFilterChange)="applyVaultFilter($event)"
(onSearchTextChanged)="filterSearchText($event)"
></app-vault-filter>
</div>
</div>
</div>
<app-org-vault-groupings
[showFolders]="false"
[showFavorites]="false"
[showTrash]="true"
(onAllClicked)="clearGroupingFilters()"
(onCipherTypeClicked)="filterCipherType($event)"
(onCollectionClicked)="filterCollection($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
(onTrashClicked)="filterDeleted()"
>
</app-org-vault-groupings>
</div>
<div class="col-9">
<div class="page-header d-flex">
<h1>
{{ "vaultItems" | i18n }}
{{ "vault" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i

View File

@@ -10,36 +10,33 @@ import {
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
import { EntityEventsComponent } from "../../../../organizations/manage/entity-events.component";
import { AddEditComponent } from "../../../../organizations/vault/add-edit.component";
import { AttachmentsComponent } from "../../../../organizations/vault/attachments.component";
import { CiphersComponent } from "../../../../organizations/vault/ciphers.component";
import { CollectionsComponent } from "../../../../organizations/vault/collections.component";
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
import { VaultService } from "../../vault.service";
import { EntityEventsComponent } from "../manage/entity-events.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { GroupingsComponent } from "./groupings.component";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@Component({
selector: "app-org-vault",
templateUrl: "organization-vault.component.html",
templateUrl: "vault.component.html",
})
export class OrganizationVaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@@ -55,7 +52,6 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private route: ActivatedRoute,
@@ -68,10 +64,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
private messagingService: MessagingService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
@@ -80,13 +73,13 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning"
);
this.route.parent.params.subscribe(async (params: any) => {
this.route.parent.params.pipe(first()).subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
this.vaultFilterComponent.organization = this.organization;
this.groupingsComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.ciphersComponent.searchText = this.vaultFilterComponent.searchText = qParams.search;
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -95,11 +88,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
} as Partial<VaultFilter>)
),
this.groupingsComponent.load(),
this.ciphersComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
@@ -109,10 +98,27 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
});
});
}
await this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
);
await this.ciphersComponent.reload();
await this.groupingsComponent.load();
if (qParams == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (qParams.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted(true);
} else if (qParams.type) {
const t = parseInt(qParams.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t, true);
} else if (qParams.collectionId) {
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
await this.filterCollection(qParams.collectionId, true);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
}
}
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
@@ -120,28 +126,6 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
this.viewEvents(cipher[0]);
}
}
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
if (
// Handle users with implicit collection access since they use the admin endpoint
this.organization.canEditAnyCollection ||
(await this.cipherService.get(params.cipherId)) != null
) {
this.editCipherId(params.cipherId);
} else {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("unknownCipher")
);
this.router.navigate([], {
queryParams: { cipherId: null },
queryParamsHandling: "merge",
});
}
}
});
});
});
}
@@ -150,47 +134,63 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.vaultFilterComponent.searchPlaceholder =
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.applyFilter();
this.clearFilters();
this.go();
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
async filterCipherType(type: CipherType, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
const filter = (c: CipherView) => c.type === type;
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.type = type;
this.go();
}
async filterCollection(collectionId: string, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
const filter = (c: CipherView) => {
if (collectionId === "unassigned") {
return c.collectionIds == null || c.collectionIds.length === 0;
} else {
return c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolder != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async filterDeleted(load = false) {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
if (load) {
await this.ciphersComponent.reload(null, true);
} else {
await this.ciphersComponent.applyFilter(null);
}
this.clearFilters();
this.deleted = true;
this.go();
}
filterSearchText(searchText: string) {
@@ -232,9 +232,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
(comp) => {
if (this.organization.canEditAnyCollection) {
comp.collectionIds = cipher.collectionIds;
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
comp.organization = this.organization;
comp.cipherId = cipher.id;
@@ -251,9 +249,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.canEditAnyCollection) {
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
if (this.collectionId != null) {
component.collectionIds = [this.collectionId];
@@ -261,24 +257,12 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
}
async editCipher(cipher: CipherView) {
return this.editCipherId(cipher?.id);
}
async editCipherId(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
(comp) => {
comp.organization = this.organization;
comp.cipherId = cipherId;
comp.cipherId = cipher == null ? null : cipher.id;
comp.onSavedCipher.subscribe(async () => {
modal.close();
await this.ciphersComponent.refresh();
@@ -294,10 +278,6 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
}
);
modal.onClosedPromise().then(() => {
this.go({ cipherId: null });
});
return childComponent;
}
@@ -306,9 +286,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.canEditAnyCollection) {
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
// in the add-edit componenet
@@ -343,7 +321,6 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
this.router.navigate([], {
relativeTo: this.route,
queryParams: queryParams,
queryParamsHandling: "merge",
replaceUrl: true,
});
}

View File

@@ -1,9 +1,10 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { LockGuard } from "jslib-angular/guards/lock.guard";
import { UnauthGuard } from "jslib-angular/guards/unauth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
@@ -21,50 +22,70 @@ import { UpdatePasswordComponent } from "./accounts/update-password.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { HomeGuard } from "./guards/home.guard";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingsComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { AccessComponent } from "./send/access.component";
import { SendComponent } from "./send/send.component";
import { OrganizationGuardService } from "./services/organization-guard.service";
import { OrganizationTypeGuardService } from "./services/organization-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { PreferencesComponent } from "./settings/preferences.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PremiumComponent } from "./settings/premium.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { ToolsComponent } from "./tools/tools.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{
path: "",
component: FrontendLayoutComponent,
data: { doNotSaveUrl: true },
children: [
{
path: "",
pathMatch: "full",
children: [], // Children lets us have an empty component.
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
},
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{
path: "register",
component: RegisterComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "createAccount" },
},
{
path: "sso",
component: SsoComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "enterpriseSingleSignOn" },
},
{
@@ -75,47 +96,42 @@ const routes: Routes = [
{
path: "hint",
component: HintComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "passwordHint" },
},
{
path: "lock",
component: LockComponent,
canActivate: [LockGuard],
canActivate: [LockGuardService],
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
path: "accept-organization",
component: AcceptOrganizationComponent,
data: { titleId: "joinOrganization", doNotSaveUrl: false },
data: { titleId: "joinOrganization" },
},
{
path: "accept-emergency",
component: AcceptEmergencyComponent,
data: { titleId: "acceptEmergency", doNotSaveUrl: false },
},
{
path: "accept-families-for-enterprise",
component: AcceptFamilySponsorshipComponent,
data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false },
data: { titleId: "acceptEmergency" },
},
{ path: "recover", pathMatch: "full", redirectTo: "recover-2fa" },
{
path: "recover-2fa",
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "recoverAccountTwoStep" },
},
{
path: "recover-delete",
component: RecoverDeleteComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" },
},
{
path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" },
},
{
@@ -126,19 +142,19 @@ const routes: Routes = [
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "updateTempPassword" },
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "updatePassword" },
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
],
@@ -146,45 +162,43 @@ const routes: Routes = [
{
path: "",
component: UserLayoutComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{
path: "vault",
loadChildren: async () =>
(await import("./modules/vault/modules/individual-vault/individual-vault.module"))
.IndividualVaultModule,
},
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
{ path: "sends", component: SendComponent, data: { title: "Send" } },
{
path: "create-organization",
component: CreateOrganizationComponent,
data: { titleId: "newOrganization" },
},
{
path: "settings",
component: SettingsComponent,
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
{
path: "preferences",
component: PreferencesComponent,
data: { titleId: "preferences" },
},
{
path: "security",
loadChildren: async () =>
(await import("./settings/security-routing.module")).SecurityRoutingModule,
},
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
{
path: "domain-rules",
component: DomainRulesComponent,
data: { titleId: "domainRules" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
{
path: "subscription",
loadChildren: async () =>
(await import("./settings/subscription-routing.module")).SubscriptionRoutingModule,
component: UserSubscriptionComponent,
data: { titleId: "premiumMembership" },
},
{
path: "organizations",
component: OrganizationsComponent,
data: { titleId: "organizations" },
},
{
path: "create-organization",
component: CreateOrganizationComponent,
data: { titleId: "newOrganization" },
},
{
path: "emergency-access",
@@ -211,7 +225,7 @@ const routes: Routes = [
{
path: "tools",
component: ToolsComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "generator" },
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
@@ -225,18 +239,196 @@ const routes: Routes = [
},
{
path: "reports",
loadChildren: async () =>
(await import("./reports/reports-routing.module")).ReportsRoutingModule,
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
},
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
],
},
{
path: "organizations",
loadChildren: () =>
import("./organizations/organization-routing.module").then(
(m) => m.OrganizationsRoutingModule
),
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
{
path: "tools",
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: OrgImportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: OrgExportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: OrgExposedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: OrgInactiveTwoFactorReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: OrgReusedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: OrgUnsecuredWebsitesReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: OrgWeakPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: OrgManageComponent,
canActivate: [OrganizationTypeGuardService],
data: {
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: OrgManageCollectionsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: OrgEventsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: OrgGroupsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: OrgPeopleComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: OrgPoliciesComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: OrgSettingsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.ManageOrganization] },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: OrgTwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
data: { titleId: "billing" },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
},
];
@@ -245,7 +437,7 @@ const routes: Routes = [
RouterModule.forRoot(routes, {
useHash: true,
paramsInheritanceStrategy: "always",
// enableTracing: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],

View File

@@ -1,22 +1,448 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { LooseComponentsModule } from "./modules/loose-components.module";
import { OrganizationManageModule } from "./modules/organizations/manage/organization-manage.module";
import { PipesModule } from "./modules/pipes/pipes.module";
import { SharedModule } from "./modules/shared.module";
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component";
import { JslibModule } from "jslib-angular/jslib.module";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RecoverDeleteComponent } from "./accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "./accounts/recover-two-factor.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdatePasswordComponent } from "./accounts/update-password.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PasswordStrengthComponent } from "./components/password-strength.component";
import { PremiumBadgeComponent } from "./components/premium-badge.component";
import { FooterComponent } from "./layouts/footer.component";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { NavbarComponent } from "./layouts/navbar.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "./organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "./organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "./organizations/manage/entity-events.component";
import { EntityUsersComponent as OrgEntityUsersComponent } from "./organizations/manage/entity-users.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "./organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "./organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "./organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "./organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "./organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "./organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "./organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "./organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "./organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "./organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "./organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "./organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { AdjustSubscription } from "./organizations/settings/adjust-subscription.component";
import { ChangePlanComponent } from "./organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "./organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "./organizations/settings/download-license.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { ProvidersComponent } from "./providers/providers.component";
import { BreachReportComponent } from "./reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "./reports/report-card.component";
import { ReportListComponent } from "./reports/report-list.component";
import { ReportsComponent } from "./reports/reports.component";
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
import { AccessComponent } from "./send/access.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { AccountComponent } from "./settings/account.component";
import { AddCreditComponent } from "./settings/add-credit.component";
import { AdjustPaymentComponent } from "./settings/adjust-payment.component";
import { AdjustStorageComponent } from "./settings/adjust-storage.component";
import { ApiKeyComponent } from "./settings/api-key.component";
import { ChangeEmailComponent } from "./settings/change-email.component";
import { ChangeKdfComponent } from "./settings/change-kdf.component";
import { ChangePasswordComponent } from "./settings/change-password.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "./settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "./settings/delete-account.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "./settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "./settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "./settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "./settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
import { LinkSsoComponent } from "./settings/link-sso.component";
import { MasterPasswordEnrollmentComponent } from "./settings/master-password-enrollment.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PaymentComponent } from "./settings/payment.component";
import { PremiumComponent } from "./settings/premium.component";
import { ProfileComponent } from "./settings/profile.component";
import { PurgeVaultComponent } from "./settings/purge-vault.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
import { TaxInfoComponent } from "./settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "./settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "./settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "./settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "./settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "./settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "./settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "./settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "./settings/update-key.component";
import { UpdateLicenseComponent } from "./settings/update-license.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "./settings/verify-email.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
import { ToolsComponent } from "./tools/tools.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { BulkActionsComponent } from "./vault/bulk-actions.component";
import { BulkDeleteComponent } from "./vault/bulk-delete.component";
import { BulkMoveComponent } from "./vault/bulk-move.component";
import { BulkRestoreComponent } from "./vault/bulk-restore.component";
import { BulkShareComponent } from "./vault/bulk-share.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
SharedModule,
LooseComponentsModule,
VaultFilterModule,
OrganizationBadgeModule,
PipesModule,
OrganizationManageModule,
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
ToastrModule,
BadgeModule,
ButtonModule,
],
exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
MasterPasswordEnrollmentComponent,
NavbarComponent,
NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationsComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupingsComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgVaultComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PersonalOwnershipPolicyComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyMasterPasswordComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent],
providers: [DatePipe],
bootstrap: [],
})
export class OssModule {}

View File

@@ -1,33 +1,58 @@
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<ng-container *ngIf="vault">
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
<ul class="bwi-ul card-ul carets" *ngIf="providers && providers.length">
<li *ngFor="let p of providers">
<a [routerLink]="['/providers', p.id]" class="text-body">
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ p.name }}
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</a>
</li>
</ul>
</ng-container>
</div>
<app-footer></app-footer>
</ng-container>
<ng-container *ngIf="!vault">
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<app-footer></app-footer>
</ng-container>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core";
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
@@ -10,6 +10,8 @@ import { Provider } from "jslib-common/models/domain/provider";
templateUrl: "providers.component.html",
})
export class ProvidersComponent implements OnInit {
@Input() vault = false;
providers: Provider[];
loaded = false;
actionPromise: Promise<any>;

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
@@ -16,7 +16,7 @@ const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{
@@ -57,4 +57,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ReportsRoutingModule {}
export class ReportsModule {}

View File

@@ -19,48 +19,26 @@
autocomplete="off"
appAutofocus
/>
<div class="filter">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedAll }">
<span class="filter-buttons">
<button bit-button class="filter-button" appStopClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</button>
</span>
</li>
</ul>
</div>
<div class="filter">
<div class="filter-heading">
<h3>{{ "types" | i18n }}</h3>
</div>
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
<span class="filter-buttons">
<button
bit-button
class="filter-button"
appStopClick
(click)="selectType(sendType.Text)"
>
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
<span class="filter-buttons">
<button
bit-button
class="filter-button"
appStopClick
(click)="selectType(sendType.File)"
>
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</button>
</span>
</li>
</ul>
</div>
<ul class="bwi-ul card-ul">
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick (click)="selectAll()">
<i class="bwi bwi-li bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</a>
</li>
</ul>
<h3>{{ "types" | i18n }}</h3>
<ul class="bwi-ul card-ul">
<li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick (click)="selectType(sendType.Text)">
<i class="bwi bwi-li bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === sendType.File }">
<a href="#" appStopClick (click)="selectType(sendType.File)">
<i class="bwi bwi-li bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</a>
</li>
</ul>
</div>
</div>
</div>
@@ -151,34 +129,39 @@
<small appStopProp>{{ s.deletionDate | date: "medium" }}</small>
</td>
<td class="table-list-options">
<button
[bitMenuTriggerFor]="sendOptions"
class="tw-border-none tw-bg-transparent tw-text-main"
type="button"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
</button>
<bit-menu #sendOptions>
<button bit-menu-item (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</button>
<div class="dropdown" appListDropdown>
<button
bit-menu-item
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
class="btn btn-outline-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
</button>
<button bit-menu-item (click)="delete(s)">
<span class="tw-text-danger">
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" appStopClick (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(s)">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</a>
</div>
</div>
</td>
</tr>
</tbody>

View File

@@ -1,69 +0,0 @@
import { Inject, Injectable } from "@angular/core";
import { WINDOW } from "jslib-angular/services/jslib-services.module";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import {
EnvironmentService as EnvironmentServiceAbstraction,
Urls,
} from "jslib-common/abstractions/environment.service";
import { EventService as EventLoggingServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { ThemeType } from "jslib-common/enums/themeType";
import { ContainerService } from "jslib-common/services/container.service";
import { EventService as EventLoggingService } from "jslib-common/services/event.service";
import { VaultTimeoutService as VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { I18nService as I18nService } from "../../services/i18n.service";
@Injectable()
export class InitService {
constructor(
@Inject(WINDOW) private win: Window,
private environmentService: EnvironmentServiceAbstraction,
private notificationsService: NotificationsServiceAbstraction,
private vaultTimeoutService: VaultTimeoutServiceAbstraction,
private i18nService: I18nServiceAbstraction,
private eventLoggingService: EventLoggingServiceAbstraction,
private twoFactorService: TwoFactorServiceAbstraction,
private stateService: StateServiceAbstraction,
private platformUtilsService: PlatformUtilsServiceAbstraction,
private cryptoService: CryptoServiceAbstraction
) {}
init() {
return async () => {
await this.stateService.init();
const urls = process.env.URLS as Urls;
urls.base ??= this.win.location.origin;
this.environmentService.setUrls(urls);
setTimeout(() => this.notificationsService.init(), 3000);
(this.vaultTimeoutService as VaultTimeoutService).init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
(this.eventLoggingService as EventLoggingService).init(true);
this.twoFactorService.init();
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
// Initial theme is set in index.html which must be updated if there are any changes to theming logic
this.platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await this.stateService.getTheme();
if (bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
const containerService = new ContainerService(this.cryptoService);
containerService.attachToWindow(this.win);
};
}
}

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