mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-22 07:50:04 +02:00
Merge branch 'main' into tools/PM-8280/email-forwarders
This commit is contained in:
commit
7461ce04f3
@ -34,7 +34,7 @@ export class HintComponent extends BaseHintComponent {
|
||||
toastService,
|
||||
);
|
||||
|
||||
super.onSuccessfulSubmit = async () => {
|
||||
this.onSuccessfulSubmit = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([this.successRoute]);
|
||||
|
@ -105,7 +105,7 @@ export class LockComponent extends BaseLockComponent implements OnInit {
|
||||
this.successRoute = "/tabs/current";
|
||||
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
||||
|
||||
super.onSuccessfulSubmit = async () => {
|
||||
this.onSuccessfulSubmit = async () => {
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
|
@ -74,7 +74,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
|
||||
loginStrategyService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
await syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
||||
registerRouteService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
await syncService.fullSync(true);
|
||||
};
|
||||
super.successRoute = "/tabs/vault";
|
||||
this.successRoute = "/tabs/vault";
|
||||
this.showPasswordless = flagEnabled("showPasswordless");
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
});
|
||||
this.clientId = "browser";
|
||||
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
@ -92,13 +92,13 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
this.win.close();
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTde = async () => {
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.win.close();
|
||||
};
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ export class TwoFactorAuthComponent
|
||||
win,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.win.close();
|
||||
};
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
@ -131,7 +131,7 @@ export class TwoFactorAuthComponent
|
||||
// WebAuthn fallback response
|
||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.syncService.fullSync(true);
|
||||
|
@ -87,23 +87,23 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTde = async () => {
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.win.close();
|
||||
};
|
||||
|
||||
super.successRoute = "/tabs/vault";
|
||||
this.successRoute = "/tabs/vault";
|
||||
// FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe
|
||||
this.webAuthnNewTab = true;
|
||||
}
|
||||
@ -113,7 +113,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit
|
||||
// WebAuthn fallback response
|
||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.syncService.fullSync(true);
|
||||
@ -155,7 +155,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.sso === "true") {
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// This is not awaited so we don't pause the application while the sync is happening.
|
||||
// This call is executed by the service that lives in the background script so it will continue
|
||||
// the sync even if this tab closes.
|
||||
|
@ -1158,7 +1158,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
if (
|
||||
this.showInlineMenuIdentities &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
|
@ -422,8 +422,12 @@ const routes: Routes = [
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn(unauthRouteOverrides)],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageTitle: {
|
||||
key: "requestPasswordHint",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
},
|
||||
pageIcon: UserLockIcon,
|
||||
showBackButton: true,
|
||||
state: "hint",
|
||||
|
@ -57,7 +57,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "12.0.1",
|
||||
"@koa/router": "13.1.0",
|
||||
"argon2": "0.40.1",
|
||||
"big-integer": "1.6.52",
|
||||
"browser-hrtime": "1.1.8",
|
||||
@ -68,7 +68,7 @@
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "25.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.15.0",
|
||||
"koa": "2.15.3",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-json": "2.0.2",
|
||||
"lowdb": "1.0.0",
|
||||
@ -80,7 +80,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.51",
|
||||
"tldts": "6.1.52",
|
||||
"zxcvbn": "4.4.2"
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
|
||||
toastService,
|
||||
);
|
||||
|
||||
super.onSuccessfulLogin = () => {
|
||||
this.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
||||
registerRouteService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = () => {
|
||||
this.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTde = async () => {
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
|
@ -89,13 +89,13 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
super.onSuccessfulLoginTde = async () => {
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
|
@ -2,15 +2,7 @@
|
||||
{{ "personalOwnershipExemption" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />
|
||||
<bit-label>{{ "turnOn" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
@ -3,6 +3,8 @@ import { Component } from "@angular/core";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
@ -23,11 +25,19 @@ export class RequireSsoPolicy extends BasePolicy {
|
||||
templateUrl: "require-sso.component.html",
|
||||
})
|
||||
export class RequireSsoPolicyComponent extends BasePolicyComponent {
|
||||
constructor(private i18nService: I18nService) {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
|
||||
// We are now relying on server-side validation only
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
|
||||
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
||||
if (this.enabled.value && !singleOrgEnabled) {
|
||||
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
|
||||
|
@ -2,6 +2,8 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
@ -18,11 +20,19 @@ export class SingleOrgPolicy extends BasePolicy {
|
||||
templateUrl: "single-org.component.html",
|
||||
})
|
||||
export class SingleOrgPolicyComponent extends BasePolicyComponent {
|
||||
constructor(private i18nService: I18nService) {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
|
||||
// We are now relying on server-side validation only
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
|
||||
if (!this.enabled.value) {
|
||||
if (policiesEnabledMap.get(PolicyType.RequireSso) ?? false) {
|
||||
throw new Error(
|
||||
|
@ -71,7 +71,7 @@ export class RegisterFormComponent extends BaseRegisterComponent implements OnIn
|
||||
dialogService,
|
||||
toastService,
|
||||
);
|
||||
super.modifyRegisterRequest = async (request: RegisterRequest) => {
|
||||
this.modifyRegisterRequest = async (request: RegisterRequest) => {
|
||||
// Org invites are deep linked. Non-existent accounts are redirected to the register page.
|
||||
// Org user id and token are included here only for validation and two factor purposes.
|
||||
const orgInvite = await acceptOrgInviteService.getOrganizationInvite();
|
||||
|
@ -184,7 +184,9 @@ const routes: Routes = [
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "passwordHint",
|
||||
pageTitle: {
|
||||
key: "passwordHint",
|
||||
},
|
||||
titleId: "passwordHint",
|
||||
},
|
||||
children: [
|
||||
@ -203,8 +205,12 @@ const routes: Routes = [
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageTitle: {
|
||||
key: "requestPasswordHint",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
},
|
||||
pageIcon: UserLockIcon,
|
||||
state: "hint",
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { unauthGuardFn } from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@ -11,7 +10,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: AccessIntelligenceComponent,
|
||||
canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence), unauthGuardFn()],
|
||||
canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)],
|
||||
data: {
|
||||
titleId: "accessIntelligence",
|
||||
},
|
||||
|
@ -1,6 +1,9 @@
|
||||
<app-header></app-header>
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex">
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||
<bit-tab label="Raw Data">
|
||||
<tools-password-health></tools-password-health>
|
||||
</bit-tab>
|
||||
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||
<tools-application-table></tools-application-table>
|
||||
</bit-tab>
|
||||
@ -19,5 +22,5 @@
|
||||
</ng-template>
|
||||
<h2 bitTypography="h2">{{ "notifiedMembers" | i18n }}</h2>
|
||||
<tools-notified-members-table></tools-notified-members-table>
|
||||
</bit-tab>
|
||||
</bit-tab> -->
|
||||
</bit-tab-group>
|
||||
|
@ -11,6 +11,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
|
||||
|
||||
import { ApplicationTableComponent } from "./application-table.component";
|
||||
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
||||
import { PasswordHealthComponent } from "./password-health.component";
|
||||
|
||||
export enum AccessIntelligenceTabType {
|
||||
AllApps = 0,
|
||||
@ -26,6 +27,7 @@ export enum AccessIntelligenceTabType {
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
HeaderModule,
|
||||
PasswordHealthComponent,
|
||||
NotifiedMembersTableComponent,
|
||||
TabsModule,
|
||||
],
|
||||
|
@ -0,0 +1,57 @@
|
||||
<bit-container>
|
||||
<p>{{ "passwordsReportDesc" | i18n }}</p>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="tw-mt-4" *ngIf="!loading">
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr bitRow>
|
||||
<th bitCell></th>
|
||||
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let r of rows$ | async">
|
||||
<td bitCell>
|
||||
<app-vault-icon [cipher]="r"></app-vault-icon>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<ng-container>
|
||||
<span>{{ r.name }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ r.subTitle }}</small>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span
|
||||
bitBadge
|
||||
*ngIf="passwordStrengthMap.has(r.id)"
|
||||
[variant]="passwordStrengthMap.get(r.id)[1]"
|
||||
>
|
||||
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
|
||||
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</div>
|
||||
</bit-container>
|
@ -0,0 +1,114 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, convertToParamMap } from "@angular/router";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TableModule } from "@bitwarden/components";
|
||||
import { TableBodyDirective } from "@bitwarden/components/src/table/table.component";
|
||||
|
||||
import { LooseComponentsModule } from "../../shared";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { cipherData } from "../reports/pages/reports-ciphers.mock";
|
||||
|
||||
import { PasswordHealthComponent } from "./password-health.component";
|
||||
|
||||
describe("PasswordHealthComponent", () => {
|
||||
let component: PasswordHealthComponent;
|
||||
let fixture: ComponentFixture<PasswordHealthComponent>;
|
||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let cipherServiceMock: MockProxy<CipherService>;
|
||||
let auditServiceMock: MockProxy<AuditService>;
|
||||
const activeRouteParams = convertToParamMap({ organizationId: "orgId" });
|
||||
|
||||
beforeEach(async () => {
|
||||
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
||||
auditServiceMock = mock<AuditService>();
|
||||
organizationService = mock<OrganizationService>({
|
||||
get: jest.fn().mockResolvedValue({ id: "orgId" } as Organization),
|
||||
});
|
||||
cipherServiceMock = mock<CipherService>({
|
||||
getAllFromApiForOrganization: jest.fn().mockResolvedValue(cipherData),
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PasswordHealthComponent, PipesModule, TableModule, LooseComponentsModule],
|
||||
declarations: [TableBodyDirective],
|
||||
providers: [
|
||||
{ provide: CipherService, useValue: cipherServiceMock },
|
||||
{ provide: PasswordStrengthServiceAbstraction, useValue: passwordStrengthService },
|
||||
{ provide: OrganizationService, useValue: organizationService },
|
||||
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||
{ provide: AuditService, useValue: auditServiceMock },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
paramMap: of(activeRouteParams),
|
||||
url: of([]),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PasswordHealthComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should initialize component", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should populate reportCiphers with ciphers that have password issues", async () => {
|
||||
passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 1 } as any);
|
||||
|
||||
auditServiceMock.passwordLeaked.mockResolvedValue(5);
|
||||
|
||||
await component.setCiphers();
|
||||
|
||||
const cipherIds = component.reportCiphers.map((c) => c.id);
|
||||
|
||||
expect(cipherIds).toEqual([
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
]);
|
||||
expect(component.reportCiphers.length).toEqual(3);
|
||||
});
|
||||
|
||||
it("should correctly populate passwordStrengthMap", async () => {
|
||||
passwordStrengthService.getPasswordStrength.mockImplementation((password) => {
|
||||
let score = 0;
|
||||
if (password === "123") {
|
||||
score = 1;
|
||||
} else {
|
||||
score = 4;
|
||||
}
|
||||
return { score } as any;
|
||||
});
|
||||
|
||||
auditServiceMock.passwordLeaked.mockResolvedValue(0);
|
||||
|
||||
await component.setCiphers();
|
||||
|
||||
expect(component.passwordStrengthMap.size).toBeGreaterThan(0);
|
||||
expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toEqual([
|
||||
"veryWeak",
|
||||
"danger",
|
||||
]);
|
||||
expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toEqual([
|
||||
"veryWeak",
|
||||
"danger",
|
||||
]);
|
||||
});
|
||||
});
|
@ -0,0 +1,229 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { from, map, switchMap, tap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
BadgeModule,
|
||||
BadgeVariant,
|
||||
ContainerComponent,
|
||||
TableDataSource,
|
||||
TableModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "tools-password-health",
|
||||
templateUrl: "password-health.component.html",
|
||||
imports: [
|
||||
BadgeModule,
|
||||
OrganizationBadgeModule,
|
||||
CommonModule,
|
||||
ContainerComponent,
|
||||
PipesModule,
|
||||
JslibModule,
|
||||
HeaderModule,
|
||||
TableModule,
|
||||
],
|
||||
})
|
||||
export class PasswordHealthComponent implements OnInit {
|
||||
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
|
||||
|
||||
weakPasswordCiphers: CipherView[] = [];
|
||||
|
||||
passwordUseMap = new Map<string, number>();
|
||||
|
||||
exposedPasswordMap = new Map<string, number>();
|
||||
|
||||
dataSource = new TableDataSource<CipherView>();
|
||||
|
||||
reportCiphers: CipherView[] = [];
|
||||
reportCipherIds: string[] = [];
|
||||
|
||||
organization: Organization;
|
||||
|
||||
loading = true;
|
||||
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||
protected organizationService: OrganizationService,
|
||||
protected auditService: AuditService,
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.activatedRoute.paramMap
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((params) => params.get("organizationId")),
|
||||
switchMap((organizationId) => {
|
||||
return from(this.organizationService.get(organizationId));
|
||||
}),
|
||||
tap((organization) => {
|
||||
this.organization = organization;
|
||||
}),
|
||||
switchMap(() => from(this.setCiphers())),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async setCiphers() {
|
||||
const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
allCiphers.forEach(async (cipher) => {
|
||||
this.findWeakPassword(cipher);
|
||||
this.findReusedPassword(cipher);
|
||||
await this.findExposedPassword(cipher);
|
||||
});
|
||||
this.dataSource.data = this.reportCiphers;
|
||||
this.loading = false;
|
||||
|
||||
// const reportIssues = allCiphers.map((c) => {
|
||||
// if (this.passwordStrengthMap.has(c.id)) {
|
||||
// return c;
|
||||
// }
|
||||
|
||||
// if (this.passwordUseMap.has(c.id)) {
|
||||
// return c;
|
||||
// }
|
||||
|
||||
// if (this.exposedPasswordMap.has(c.id)) {
|
||||
// return c;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
protected checkForExistingCipher(ciph: CipherView) {
|
||||
if (!this.reportCipherIds.includes(ciph.id)) {
|
||||
this.reportCipherIds.push(ciph.id);
|
||||
this.reportCiphers.push(ciph);
|
||||
}
|
||||
}
|
||||
|
||||
protected async findExposedPassword(cipher: CipherView) {
|
||||
const { type, login, isDeleted, edit, viewPassword, id } = cipher;
|
||||
if (
|
||||
type !== CipherType.Login ||
|
||||
login.password == null ||
|
||||
login.password === "" ||
|
||||
isDeleted ||
|
||||
(!this.organization && !edit) ||
|
||||
!viewPassword
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exposedCount = await this.auditService.passwordLeaked(login.password);
|
||||
if (exposedCount > 0) {
|
||||
this.exposedPasswordMap.set(id, exposedCount);
|
||||
this.checkForExistingCipher(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
protected findReusedPassword(cipher: CipherView) {
|
||||
const { type, login, isDeleted, edit, viewPassword } = cipher;
|
||||
if (
|
||||
type !== CipherType.Login ||
|
||||
login.password == null ||
|
||||
login.password === "" ||
|
||||
isDeleted ||
|
||||
(!this.organization && !edit) ||
|
||||
!viewPassword
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.passwordUseMap.has(login.password)) {
|
||||
this.passwordUseMap.set(login.password, this.passwordUseMap.get(login.password) || 0 + 1);
|
||||
} else {
|
||||
this.passwordUseMap.set(login.password, 1);
|
||||
}
|
||||
|
||||
this.checkForExistingCipher(cipher);
|
||||
}
|
||||
|
||||
protected findWeakPassword(cipher: CipherView): void {
|
||||
const { type, login, isDeleted, edit, viewPassword } = cipher;
|
||||
if (
|
||||
type !== CipherType.Login ||
|
||||
login.password == null ||
|
||||
login.password === "" ||
|
||||
isDeleted ||
|
||||
(!this.organization && !edit) ||
|
||||
!viewPassword
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasUserName = this.isUserNameNotEmpty(cipher);
|
||||
let userInput: string[] = [];
|
||||
if (hasUserName) {
|
||||
const atPosition = login.username.indexOf("@");
|
||||
if (atPosition > -1) {
|
||||
userInput = userInput
|
||||
.concat(
|
||||
login.username
|
||||
.substring(0, atPosition)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(/[^A-Za-z0-9]/),
|
||||
)
|
||||
.filter((i) => i.length >= 3);
|
||||
} else {
|
||||
userInput = login.username
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(/[^A-Za-z0-9]/)
|
||||
.filter((i) => i.length >= 3);
|
||||
}
|
||||
}
|
||||
const { score } = this.passwordStrengthService.getPasswordStrength(
|
||||
login.password,
|
||||
null,
|
||||
userInput.length > 0 ? userInput : null,
|
||||
);
|
||||
|
||||
if (score != null && score <= 2) {
|
||||
this.passwordStrengthMap.set(cipher.id, this.scoreKey(score));
|
||||
this.checkForExistingCipher(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
private isUserNameNotEmpty(c: CipherView): boolean {
|
||||
return !Utils.isNullOrWhitespace(c.login.username);
|
||||
}
|
||||
|
||||
private scoreKey(score: number): [string, BadgeVariant] {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return ["strong", "success"];
|
||||
case 3:
|
||||
return ["good", "primary"];
|
||||
case 2:
|
||||
return ["weak", "warning"];
|
||||
default:
|
||||
return ["veryWeak", "danger"];
|
||||
}
|
||||
}
|
||||
}
|
@ -288,6 +288,7 @@ function createCollectionView(i: number): CollectionAdminView {
|
||||
view.id = `collection-${i}`;
|
||||
view.name = `Collection ${i}`;
|
||||
view.organizationId = organization?.id;
|
||||
view.manage = true;
|
||||
|
||||
if (group !== undefined) {
|
||||
view.groups = [
|
||||
|
@ -9377,6 +9377,21 @@
|
||||
"editAccess": {
|
||||
"message": "Edit access"
|
||||
},
|
||||
"textHelpText": {
|
||||
"message": "Use text fields for data like security questions"
|
||||
},
|
||||
"hiddenHelpText": {
|
||||
"message": "Use hidden fields for sensitive data like a password"
|
||||
},
|
||||
"checkBoxHelpText": {
|
||||
"message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email"
|
||||
},
|
||||
"linkedHelpText": {
|
||||
"message": "Use a linked field when you are experiencing autofill issues for a specific website."
|
||||
},
|
||||
"linkedLabelHelpText": {
|
||||
"message": "Enter the the field's html id, name, aria-label, or placeholder."
|
||||
},
|
||||
"uppercaseDescription": {
|
||||
"message": "Include uppercase characters",
|
||||
"description": "Tooltip for the password generator uppercase character checkbox"
|
||||
|
@ -105,6 +105,11 @@ export class DomainAddEditDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
copyDnsTxt(): void {
|
||||
this.orgDomainService.copyDnsTxt(this.txtCtrl.value);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord")),
|
||||
});
|
||||
}
|
||||
|
||||
// End Form methods
|
||||
|
@ -101,6 +101,11 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
|
||||
|
||||
copyDnsTxt(dnsTxt: string): void {
|
||||
this.orgDomainService.copyDnsTxt(dnsTxt);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord")),
|
||||
});
|
||||
}
|
||||
|
||||
async verifyDomain(orgDomainId: string, domainName: string): Promise<void> {
|
||||
|
@ -62,7 +62,9 @@ const routes: Routes = [
|
||||
path: "accept-provider",
|
||||
component: AcceptProviderComponent,
|
||||
data: {
|
||||
pageTitle: "joinProvider",
|
||||
pageTitle: {
|
||||
key: "joinProvider",
|
||||
},
|
||||
titleId: "acceptProvider",
|
||||
},
|
||||
},
|
||||
|
@ -180,6 +180,5 @@ describe("Org Domain Service", () => {
|
||||
it("copyDnsTxt copies DNS TXT to clipboard and shows toast", () => {
|
||||
orgDomainService.copyDnsTxt("fakeTxt");
|
||||
expect(jest.spyOn(platformUtilService, "copyToClipboard")).toHaveBeenCalled();
|
||||
expect(jest.spyOn(platformUtilService, "showToast")).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -23,11 +23,6 @@ export class OrgDomainService implements OrgDomainInternalServiceAbstraction {
|
||||
|
||||
copyDnsTxt(dnsTxt: string): void {
|
||||
this.platformUtilsService.copyToClipboard(dnsTxt);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord")),
|
||||
);
|
||||
}
|
||||
|
||||
upsert(orgDomains: OrganizationDomainResponse[]): void {
|
||||
|
@ -35,6 +35,7 @@ export enum FeatureFlag {
|
||||
PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader",
|
||||
Pm3478RefactorOrganizationUserApi = "pm-3478-refactor-organizationuser-api",
|
||||
AccessIntelligence = "pm-13227-access-intelligence",
|
||||
Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions",
|
||||
}
|
||||
|
||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||
@ -80,6 +81,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE,
|
||||
[FeatureFlag.Pm3478RefactorOrganizationUserApi]: FALSE,
|
||||
[FeatureFlag.AccessIntelligence]: FALSE,
|
||||
[FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE,
|
||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||
|
||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||
|
@ -43,8 +43,13 @@
|
||||
></button>
|
||||
<bit-hint>{{ "sendPasswordDescV2" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-control *ngIf="!disableHideEmail">
|
||||
<input bitCheckbox type="checkbox" formControlName="hideEmail" />
|
||||
<bit-form-control *ngIf="!disableHideEmail || originalSendView?.hideEmail">
|
||||
<input
|
||||
[disabled]="disableHideEmail && !sendOptionsForm.get('hideEmail').value"
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="hideEmail"
|
||||
/>
|
||||
<bit-label>{{ "hideYourEmail" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-field disableMargin>
|
||||
|
@ -58,6 +58,13 @@ describe("UriOptionComponent", () => {
|
||||
expect(component["uriMatchOptions"][0].label).toBe("default");
|
||||
});
|
||||
|
||||
it("should update the default uri match strategy label when it is domain", () => {
|
||||
component.defaultMatchDetection = UriMatchStrategy.Domain;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component["uriMatchOptions"][0].label).toBe("defaultLabel baseDomain");
|
||||
});
|
||||
|
||||
it("should update the default uri match strategy label", () => {
|
||||
component.defaultMatchDetection = UriMatchStrategy.Exact;
|
||||
fixture.detectChanges();
|
||||
|
@ -84,7 +84,7 @@ export class UriOptionComponent implements ControlValueAccessor {
|
||||
@Input({ required: true })
|
||||
set defaultMatchDetection(value: UriMatchStrategySetting) {
|
||||
// The default selection has a value of `null` avoid showing "Default (Default)"
|
||||
if (!value) {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
54
package-lock.json
generated
54
package-lock.json
generated
@ -27,7 +27,7 @@
|
||||
"@bitwarden/sdk-internal": "0.1.3",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "12.0.1",
|
||||
"@koa/router": "13.1.0",
|
||||
"@microsoft/signalr": "8.0.7",
|
||||
"@microsoft/signalr-protocol-msgpack": "8.0.7",
|
||||
"@ng-select/ng-select": "11.2.0",
|
||||
@ -47,7 +47,7 @@
|
||||
"jquery": "3.7.1",
|
||||
"jsdom": "25.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.15.0",
|
||||
"koa": "2.15.3",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-json": "2.0.2",
|
||||
"lowdb": "1.0.0",
|
||||
@ -68,7 +68,7 @@
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tabbable": "6.2.0",
|
||||
"tldts": "6.1.51",
|
||||
"tldts": "6.1.52",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"zone.js": "0.13.3",
|
||||
"zxcvbn": "4.4.2"
|
||||
@ -103,7 +103,7 @@
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/jquery": "3.5.30",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/koa": "2.14.0",
|
||||
"@types/koa": "2.15.0",
|
||||
"@types/koa__multer": "2.0.7",
|
||||
"@types/koa__router": "12.0.4",
|
||||
"@types/koa-bodyparser": "4.3.7",
|
||||
@ -202,7 +202,7 @@
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"dependencies": {
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "12.0.1",
|
||||
"@koa/router": "13.1.0",
|
||||
"argon2": "0.40.1",
|
||||
"big-integer": "1.6.52",
|
||||
"browser-hrtime": "1.1.8",
|
||||
@ -213,7 +213,7 @@
|
||||
"inquirer": "8.2.6",
|
||||
"jsdom": "25.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.15.0",
|
||||
"koa": "2.15.3",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-json": "2.0.2",
|
||||
"lowdb": "1.0.0",
|
||||
@ -225,7 +225,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.51",
|
||||
"tldts": "6.1.52",
|
||||
"zxcvbn": "4.4.2"
|
||||
},
|
||||
"bin": {
|
||||
@ -7021,20 +7021,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@koa/router": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz",
|
||||
"integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==",
|
||||
"deprecated": "Use v12.0.2 or higher to fix the vulnerability issue",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz",
|
||||
"integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"http-errors": "^2.0.0",
|
||||
"koa-compose": "^4.1.0",
|
||||
"methods": "^1.1.2",
|
||||
"path-to-regexp": "^6.2.1"
|
||||
"path-to-regexp": "^6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@leichtgewicht/ip-codec": {
|
||||
@ -9524,9 +9521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/koa": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.14.0.tgz",
|
||||
"integrity": "sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==",
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz",
|
||||
"integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -25243,9 +25240,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/koa": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.0.tgz",
|
||||
"integrity": "sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==",
|
||||
"version": "2.15.3",
|
||||
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz",
|
||||
"integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.5",
|
||||
@ -27277,6 +27274,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@ -36519,21 +36517,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.51",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.51.tgz",
|
||||
"integrity": "sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA==",
|
||||
"version": "6.1.52",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.52.tgz",
|
||||
"integrity": "sha512-fgrDJXDjbAverY6XnIt0lNfv8A0cf7maTEaZxNykLGsLG7XP+5xhjBTrt/ieAsFjAlZ+G5nmXomLcZDkxXnDzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.51"
|
||||
"tldts-core": "^6.1.52"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "6.1.51",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.51.tgz",
|
||||
"integrity": "sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg==",
|
||||
"version": "6.1.52",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz",
|
||||
"integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
|
@ -64,7 +64,7 @@
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/jquery": "3.5.30",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/koa": "2.14.0",
|
||||
"@types/koa": "2.15.0",
|
||||
"@types/koa__multer": "2.0.7",
|
||||
"@types/koa__router": "12.0.4",
|
||||
"@types/koa-bodyparser": "4.3.7",
|
||||
@ -161,7 +161,7 @@
|
||||
"@bitwarden/sdk-internal": "0.1.3",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "12.0.1",
|
||||
"@koa/router": "13.1.0",
|
||||
"@microsoft/signalr": "8.0.7",
|
||||
"@microsoft/signalr-protocol-msgpack": "8.0.7",
|
||||
"@ng-select/ng-select": "11.2.0",
|
||||
@ -181,7 +181,7 @@
|
||||
"jquery": "3.7.1",
|
||||
"jsdom": "25.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"koa": "2.15.0",
|
||||
"koa": "2.15.3",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-json": "2.0.2",
|
||||
"lowdb": "1.0.0",
|
||||
@ -202,7 +202,7 @@
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tabbable": "6.2.0",
|
||||
"tldts": "6.1.51",
|
||||
"tldts": "6.1.52",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"zone.js": "0.13.3",
|
||||
"zxcvbn": "4.4.2"
|
||||
|
Loading…
Reference in New Issue
Block a user