mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-24 02:41:54 +01:00
Merge branch 'main' into autofill/pm-6426-create-alarms-manager-and-update-usage-of-long-lived-timeouts-rework
This commit is contained in:
commit
bd886cab49
5
.github/workflows/scan.yml
vendored
5
.github/workflows/scan.yml
vendored
@ -40,7 +40,10 @@ jobs:
|
||||
base_uri: https://ast.checkmarx.net/
|
||||
cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }}
|
||||
cx_client_secret: ${{ secrets.CHECKMARX_SECRET }}
|
||||
additional_params: --report-format sarif --output-path . ${{ env.INCREMENTAL }}
|
||||
additional_params: |
|
||||
--report-format sarif \
|
||||
--filter "state=TO_VERIFY;PROPOSED_NOT_EXPLOITABLE;CONFIRMED;URGENT" \
|
||||
--output-path . ${{ env.INCREMENTAL }}
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
|
||||
|
@ -604,9 +604,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
* @param sender - The sender of the port message
|
||||
*/
|
||||
private getNewVaultItemDetails({ sender }: chrome.runtime.Port) {
|
||||
// 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
|
||||
BrowserApi.tabSendMessage(sender.tab, { command: "addNewVaultItemFromOverlay" });
|
||||
void BrowserApi.tabSendMessage(sender.tab, { command: "addNewVaultItemFromOverlay" });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -643,8 +641,8 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
|
||||
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
||||
await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id });
|
||||
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,10 +16,6 @@ import {
|
||||
logServiceFactory,
|
||||
LogServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
stateServiceFactory,
|
||||
StateServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
import {
|
||||
cipherServiceFactory,
|
||||
CipherServiceInitOptions,
|
||||
@ -44,7 +40,6 @@ type AutoFillServiceOptions = FactoryOptions;
|
||||
|
||||
export type AutoFillServiceInitOptions = AutoFillServiceOptions &
|
||||
CipherServiceInitOptions &
|
||||
StateServiceInitOptions &
|
||||
AutofillSettingsServiceInitOptions &
|
||||
TotpServiceInitOptions &
|
||||
EventCollectionServiceInitOptions &
|
||||
@ -63,7 +58,6 @@ export function autofillServiceFactory(
|
||||
async () =>
|
||||
new AutofillService(
|
||||
await cipherServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await autofillSettingsServiceFactory(cache, opts),
|
||||
await totpServiceFactory(cache, opts),
|
||||
await eventCollectionServiceFactory(cache, opts),
|
||||
|
@ -99,9 +99,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
return pageDetails;
|
||||
}
|
||||
|
||||
// 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
|
||||
chrome.runtime.sendMessage({
|
||||
void chrome.runtime.sendMessage({
|
||||
command: "collectPageDetailsResponse",
|
||||
tab: message.tab,
|
||||
details: pageDetails,
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
exports[`AutofillOverlayList initAutofillOverlayList the list of ciphers for an authenticated user creates the view for a list of ciphers 1`] = `
|
||||
<div
|
||||
aria-modal="true"
|
||||
class="overlay-list-container theme_light"
|
||||
role="dialog"
|
||||
>
|
||||
<ul
|
||||
class="overlay-actions-list"
|
||||
@ -436,9 +434,7 @@ exports[`AutofillOverlayList initAutofillOverlayList the list of ciphers for an
|
||||
|
||||
exports[`AutofillOverlayList initAutofillOverlayList the locked overlay for an unauthenticated user creates the views for the locked overlay 1`] = `
|
||||
<div
|
||||
aria-modal="true"
|
||||
class="overlay-list-container theme_light"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="locked-overlay overlay-list-message"
|
||||
@ -490,9 +486,7 @@ exports[`AutofillOverlayList initAutofillOverlayList the locked overlay for an u
|
||||
|
||||
exports[`AutofillOverlayList initAutofillOverlayList the overlay with an empty list of ciphers creates the views for the no results overlay 1`] = `
|
||||
<div
|
||||
aria-modal="true"
|
||||
class="overlay-list-container theme_light"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="no-items overlay-list-message"
|
||||
|
@ -312,6 +312,24 @@ describe("AutofillOverlayList", () => {
|
||||
});
|
||||
|
||||
describe("directing user focus into the overlay list", () => {
|
||||
it("sets ARIA attributes that define the list as a `dialog` to screen reader users", () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
authStatus: AuthenticationStatus.Locked,
|
||||
cipherList: [],
|
||||
}),
|
||||
);
|
||||
const overlayContainerSetAttributeSpy = jest.spyOn(
|
||||
autofillOverlayList["overlayListContainer"],
|
||||
"setAttribute",
|
||||
);
|
||||
|
||||
postWindowMessage({ command: "focusOverlayList" });
|
||||
|
||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog");
|
||||
expect(overlayContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true");
|
||||
});
|
||||
|
||||
it("focuses the unlock button element if the user is not authenticated", () => {
|
||||
postWindowMessage(
|
||||
createInitAutofillOverlayListMessageMock({
|
||||
|
@ -59,8 +59,6 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
|
||||
|
||||
this.overlayListContainer = globalThis.document.createElement("div");
|
||||
this.overlayListContainer.classList.add("overlay-list-container", themeClass);
|
||||
this.overlayListContainer.setAttribute("role", "dialog");
|
||||
this.overlayListContainer.setAttribute("aria-modal", "true");
|
||||
this.resizeObserver.observe(this.overlayListContainer);
|
||||
|
||||
this.shadowDom.append(linkElement, this.overlayListContainer);
|
||||
@ -487,6 +485,9 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
|
||||
* the first cipher button.
|
||||
*/
|
||||
private focusOverlayList() {
|
||||
this.overlayListContainer.setAttribute("role", "dialog");
|
||||
this.overlayListContainer.setAttribute("aria-modal", "true");
|
||||
|
||||
const unlockButtonElement = this.overlayListContainer.querySelector(
|
||||
"#unlock-button",
|
||||
) as HTMLElement;
|
||||
|
@ -32,7 +32,6 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
||||
import { AutofillPort } from "../enums/autofill-port.enums";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
@ -63,7 +62,6 @@ const mockEquivalentDomains = [
|
||||
describe("AutofillService", () => {
|
||||
let autofillService: AutofillService;
|
||||
const cipherService = mock<CipherService>();
|
||||
const stateService = mock<BrowserStateService>();
|
||||
const autofillSettingsService = mock<AutofillSettingsService>();
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||
@ -78,7 +76,6 @@ describe("AutofillService", () => {
|
||||
beforeEach(() => {
|
||||
autofillService = new AutofillService(
|
||||
cipherService,
|
||||
stateService,
|
||||
autofillSettingsService,
|
||||
totpService,
|
||||
eventCollectionService,
|
||||
|
@ -20,7 +20,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
|
||||
import { openVaultItemPasswordRepromptPopout } from "../../vault/popup/utils/vault-popout-window";
|
||||
import { AutofillPort } from "../enums/autofill-port.enums";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
@ -49,7 +48,6 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private stateService: BrowserStateService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private totpService: TotpService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
|
@ -807,6 +807,36 @@ describe("CollectAutofillContentService", () => {
|
||||
});
|
||||
|
||||
describe("buildAutofillFieldItem", () => {
|
||||
it("returns a `null` value if the field is a child of a `button[type='submit']`", async () => {
|
||||
const usernameField = {
|
||||
labelText: "Username",
|
||||
id: "username-id",
|
||||
type: "text",
|
||||
};
|
||||
document.body.innerHTML = `
|
||||
<form>
|
||||
<div>
|
||||
<div>
|
||||
<label for="${usernameField.id}">${usernameField.labelText}</label>
|
||||
<button type="submit">
|
||||
<input id="${usernameField.id}" type="${usernameField.type}" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
const usernameInput = document.getElementById(
|
||||
usernameField.id,
|
||||
) as ElementWithOpId<FillableFormFieldElement>;
|
||||
|
||||
const autofillFieldItem = await collectAutofillContentService["buildAutofillFieldItem"](
|
||||
usernameInput,
|
||||
0,
|
||||
);
|
||||
|
||||
expect(autofillFieldItem).toBeNull();
|
||||
});
|
||||
|
||||
it("returns an existing autofill field item if it exists", async () => {
|
||||
const index = 0;
|
||||
const usernameField = {
|
||||
@ -847,27 +877,6 @@ describe("CollectAutofillContentService", () => {
|
||||
/>
|
||||
</form>
|
||||
`;
|
||||
document.body.innerHTML = `
|
||||
<form>
|
||||
<label for="${usernameField.id}">${usernameField.labelText}</label>
|
||||
<input
|
||||
id="${usernameField.id}"
|
||||
class="${usernameField.classes}"
|
||||
name="${usernameField.name}"
|
||||
type="${usernameField.type}"
|
||||
maxlength="${usernameField.maxLength}"
|
||||
tabindex="${usernameField.tabIndex}"
|
||||
title="${usernameField.title}"
|
||||
autocomplete="${usernameField.autocomplete}"
|
||||
data-label="${usernameField.dataLabel}"
|
||||
aria-label="${usernameField.ariaLabel}"
|
||||
placeholder="${usernameField.placeholder}"
|
||||
rel="${usernameField.rel}"
|
||||
value="${usernameField.value}"
|
||||
data-stripe="${usernameField.dataStripe}"
|
||||
/>
|
||||
</form>
|
||||
`;
|
||||
const existingFieldData: AutofillField = {
|
||||
elementNumber: index,
|
||||
htmlClass: usernameField.classes,
|
||||
|
@ -92,9 +92,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||
const { formElements, formFieldElements } = this.queryAutofillFormAndFieldElements();
|
||||
const autofillFormsData: Record<string, AutofillForm> =
|
||||
this.buildAutofillFormsData(formElements);
|
||||
const autofillFieldsData: AutofillField[] = await this.buildAutofillFieldsData(
|
||||
formFieldElements as FormFieldElement[],
|
||||
);
|
||||
const autofillFieldsData: AutofillField[] = (
|
||||
await this.buildAutofillFieldsData(formFieldElements as FormFieldElement[])
|
||||
).filter((field) => !!field);
|
||||
this.sortAutofillFieldElementsMap();
|
||||
|
||||
if (!autofillFieldsData.length) {
|
||||
@ -333,15 +333,18 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||
* Builds an AutofillField object from the given form element. Will only return
|
||||
* shared field values if the element is a span element. Will not return any label
|
||||
* values if the element is a hidden input element.
|
||||
* @param {ElementWithOpId<FormFieldElement>} element
|
||||
* @param {number} index
|
||||
* @returns {Promise<AutofillField>}
|
||||
* @private
|
||||
*
|
||||
* @param element - The form field element to build the AutofillField object from
|
||||
* @param index - The index of the form field element
|
||||
*/
|
||||
private buildAutofillFieldItem = async (
|
||||
element: ElementWithOpId<FormFieldElement>,
|
||||
index: number,
|
||||
): Promise<AutofillField> => {
|
||||
): Promise<AutofillField | null> => {
|
||||
if (element.closest("button[type='submit']")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
element.opid = `__${index}`;
|
||||
|
||||
const existingAutofillField = this.autofillFieldElements.get(element);
|
||||
|
@ -98,7 +98,7 @@ describe("InsertAutofillContentService", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
windowLocationSpy.mockRestore();
|
||||
confirmSpy.mockRestore();
|
||||
document.body.innerHTML = "";
|
||||
|
@ -776,7 +776,6 @@ export default class MainBackground {
|
||||
|
||||
this.autofillService = new AutofillService(
|
||||
this.cipherService,
|
||||
this.stateService,
|
||||
this.autofillSettingsService,
|
||||
this.totpService,
|
||||
this.eventCollectionService,
|
||||
|
@ -172,7 +172,7 @@ describe("Browser Utils Service", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("sends a copy to clipboard message to the desktop application if a user is using the safari browser", async () => {
|
||||
@ -264,7 +264,7 @@ describe("Browser Utils Service", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("sends a ready from clipboard message to the desktop application if a user is using the safari browser", async () => {
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
AuthRequestServiceAbstraction,
|
||||
LoginStrategyServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
@ -47,6 +48,7 @@ import {
|
||||
UserNotificationSettingsService,
|
||||
UserNotificationSettingsServiceAbstraction,
|
||||
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
@ -86,7 +88,8 @@ import { DialogService } from "@bitwarden/components";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||
|
||||
import { UnauthGuardService } from "../../auth/popup/services";
|
||||
import { AutofillService } from "../../autofill/services/abstractions/autofill.service";
|
||||
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
||||
import AutofillService from "../../autofill/services/autofill.service";
|
||||
import MainBackground from "../../background/main.background";
|
||||
import { Account } from "../../models/account";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@ -314,10 +317,22 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: BrowserLocalStorageService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AutofillServiceAbstraction,
|
||||
useExisting: AutofillService,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AutofillService,
|
||||
useFactory: getBgService<AutofillService>("autofillService"),
|
||||
deps: [],
|
||||
deps: [
|
||||
CipherService,
|
||||
AutofillSettingsServiceAbstraction,
|
||||
TotpService,
|
||||
EventCollectionServiceAbstraction,
|
||||
LogService,
|
||||
DomainSettingsService,
|
||||
UserVerificationService,
|
||||
BillingAccountProfileStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: VaultExportServiceAbstraction,
|
||||
|
403
apps/desktop/desktop_native/Cargo.lock
generated
403
apps/desktop/desktop_native/Cargo.lock
generated
@ -30,9 +30,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -62,15 +62,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
@ -95,9 +95,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
@ -123,12 +123,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
@ -140,18 +134,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.15.5"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3"
|
||||
checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
@ -163,6 +154,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@ -219,9 +216,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -238,19 +235,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.5"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
|
||||
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.110"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7129e341034ecb940c9072817cd9007974ea696844fc4dd582dc1653a7fbe2e8"
|
||||
checksum = "ff4dc7287237dd438b926a81a1a5605dad33d286870e5eee2db17bf2bcd9e92a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
@ -260,9 +257,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.110"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a24f3f5f8eed71936f21e570436f024f5c2e25628f7496aa7ccd03b90109d5"
|
||||
checksum = "f47c6c8ad7c1a10d3ef0fe3ff6733f4db0d78f08ef0b13121543163ef327058b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
@ -270,35 +267,35 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.110"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06fdd177fc61050d63f67f5bd6351fac6ab5526694ea8e359cd9cd3b75857f44"
|
||||
checksum = "701a1ac7a697e249cdd8dc026d7a7dafbfd0dbcd8bd24ec55889f2bc13dd6287"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.110"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f"
|
||||
checksum = "b404f596046b0bb2d903a9c786b875a126261b52b7c3a64bbb66382c41c771df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-new"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535"
|
||||
checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -378,9 +375,9 @@ checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
@ -396,24 +393,24 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
|
||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -422,32 +419,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
@ -479,9 +476,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@ -490,9 +487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.0"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
@ -527,11 +524,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.19.2"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab9e86540b5d8402e905ad4ce7d6aa544092131ab564f3102175af176b90a053"
|
||||
checksum = "01e191cc1af1f35b9699213107068cd3fe05d9816275ac118dc785a0dd8faebf"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.5.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
@ -549,15 +546,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.19.2"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f5897ca27a83e4cdc7b4666850bade0a2e73e17689aabafcc9acddad9d823b8"
|
||||
checksum = "9972bb91643d589c889654693a4f1d07697fdcb5d104b5c44fb68649ba1bf68d"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -583,27 +580,36 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.2"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@ -640,17 +646,11 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -699,9 +699,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@ -715,9 +715,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
@ -730,18 +730,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
@ -751,9 +742,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
@ -764,7 +755,7 @@ version = "2.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.5.0",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
@ -789,14 +780,14 @@ dependencies = [
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.62"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4"
|
||||
checksum = "ce5126b64f6ad9e28e30e6d15213dd378626b38f556454afebc42f7f02a90902"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
@ -804,7 +795,7 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -818,15 +809,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -880,9 +870,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.1"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -938,9 +928,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@ -950,9 +940,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@ -971,27 +961,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -1037,9 +1027,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1049,9 +1039,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1060,9 +1050,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "retry"
|
||||
@ -1081,11 +1071,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
version = "0.38.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@ -1135,35 +1125,35 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.190"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.190"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -1190,26 +1180,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1218,9 +1197,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.1.2"
|
||||
version = "6.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6"
|
||||
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
@ -1231,28 +1210,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.12"
|
||||
version = "0.12.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
|
||||
checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.9.0"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.3.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@ -1274,14 +1252,14 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.36.0"
|
||||
version = "1.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"num_cpus",
|
||||
@ -1290,14 +1268,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.6"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.7",
|
||||
"toml_edit 0.22.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1309,19 +1287,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
@ -1330,18 +1295,31 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.0.3"
|
||||
version = "3.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91adfd0607cacf6e4babdb870e9bec4037c1c4b151cfd279ccefc5e0c7feaa6d"
|
||||
checksum = "77ee137597cdb361b55a4746983e4ac1b35ab6024396a419944ad473bb915265"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"home",
|
||||
"memchr",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"petgraph",
|
||||
@ -1361,9 +1339,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
@ -1373,9 +1351,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@ -1391,13 +1369,13 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
|
||||
checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"nix",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
@ -1405,23 +1383,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.1"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
|
||||
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"nix",
|
||||
"bitflags 2.5.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.31.0"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
|
||||
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.5.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@ -1433,7 +1411,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.5.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@ -1442,9 +1420,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
|
||||
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
@ -1653,18 +1631,27 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.17"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57af79e973eadf08627115c73847392e6b766856ab8e3844a59245354b23d2fa"
|
||||
checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb"
|
||||
dependencies = [
|
||||
"derive-new",
|
||||
"libc",
|
||||
|
@ -19,18 +19,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node16": "1.0.4",
|
||||
"@types/node": "18.19.19",
|
||||
"@types/node": "18.19.29",
|
||||
"@types/node-ipc": "9.2.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
},
|
||||
"../../../libs/common": {
|
||||
"name": "@bitwarden/common",
|
||||
"version": "0.0.0",
|
||||
"license": "GPL-3.0"
|
||||
},
|
||||
"../../../libs/node": {
|
||||
"name": "@bitwarden/node",
|
||||
"version": "0.0.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
@ -57,9 +55,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@ -79,9 +77,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.10.tgz",
|
||||
"integrity": "sha512-PiaIWIoPvO6qm6t114ropMCagj6YAF24j9OkCA2mJDXFnlionEwhsBCJ8yek4aib575BI3OkART/90WsgHgLWw=="
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
@ -99,9 +97,9 @@
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.19.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.19.tgz",
|
||||
"integrity": "sha512-qqV6hSy9zACEhQUy5CEGeuXAZN0fNjqLWRIvOXOwdFYhFoKBiY08VKR5kgchr90+TitLVhpUEb54hk4bYaArUw==",
|
||||
"version": "18.19.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz",
|
||||
"integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
@ -116,9 +114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -127,9 +125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@ -217,9 +215,9 @@
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node16": "1.0.4",
|
||||
"@types/node": "18.19.19",
|
||||
"@types/node": "18.19.29",
|
||||
"@types/node-ipc": "9.2.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
|
592
apps/desktop/src/package-lock.json
generated
592
apps/desktop/src/package-lock.json
generated
@ -9,19 +9,607 @@
|
||||
"version": "2024.3.2",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@bitwarden/desktop-native": "file:../desktop_native"
|
||||
"@bitwarden/desktop-native": "file:../desktop_native",
|
||||
"argon2": "0.31.0"
|
||||
}
|
||||
},
|
||||
"../desktop_native": {
|
||||
"version": "0.1.0",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "2.14.8"
|
||||
"@napi-rs/cli": "2.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@bitwarden/desktop-native": {
|
||||
"resolved": "../desktop_native",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@phc/format": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
||||
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/argon2": {
|
||||
"version": "0.31.0",
|
||||
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.31.0.tgz",
|
||||
"integrity": "sha512-r56NWwlE3tjD/FIqL1T+V4Ka+Mb5yMF35w1YWHpwpEjeONXBUbxmjhWkWqY63mse8lpcZ+ZZIGpKL+s+qXhyfg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"@phc/format": "^1.0.0",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
|
||||
"integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
|
||||
"engines": {
|
||||
"node": "^16 || ^18 || >= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<a routerLink="." class="tw-m-5 tw-mt-7 tw-block" [appA11yTitle]="'adminConsole' | i18n">
|
||||
<bit-icon [icon]="logo"></bit-icon>
|
||||
</a>
|
||||
<org-switcher [filter]="orgFilter"></org-switcher>
|
||||
<org-switcher [filter]="orgFilter" [hideNewButton]="hideNewOrgButton$ | async"></org-switcher>
|
||||
|
||||
<bit-nav-item
|
||||
icon="bwi-collection"
|
||||
@ -105,6 +105,8 @@
|
||||
*ngIf="organization.canManageScim"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
|
||||
<app-toggle-width></app-toggle-width>
|
||||
</nav>
|
||||
|
||||
<ng-container *ngIf="organization$ | async as organization">
|
||||
|
@ -15,6 +15,8 @@ import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@ -23,6 +25,7 @@ import { BannerModule, IconModule, LayoutComponent, NavigationModule } from "@bi
|
||||
|
||||
import { PaymentMethodWarningsModule } from "../../../billing/shared";
|
||||
import { OrgSwitcherComponent } from "../../../layouts/org-switcher/org-switcher.component";
|
||||
import { ToggleWidthComponent } from "../../../layouts/toggle-width.component";
|
||||
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||
|
||||
@Component({
|
||||
@ -39,6 +42,7 @@ import { AdminConsoleLogo } from "../../icons/admin-console-logo";
|
||||
OrgSwitcherComponent,
|
||||
BannerModule,
|
||||
PaymentMethodWarningsModule,
|
||||
ToggleWidthComponent,
|
||||
],
|
||||
})
|
||||
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
@ -48,6 +52,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
organization$: Observable<Organization>;
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
@ -61,6 +66,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private configService: ConfigService,
|
||||
private policyService: PolicyService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -85,6 +91,8 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
org?.canEditPaymentMethods,
|
||||
),
|
||||
);
|
||||
|
||||
this.hideNewOrgButton$ = this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -124,10 +124,7 @@ export class PremiumComponent implements OnInit {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated"));
|
||||
this.messagingService.send("purchasedPremium");
|
||||
// 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(["/settings/subscription/user-subscription"]);
|
||||
await this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
}
|
||||
|
||||
get additionalStorageTotal(): number {
|
||||
|
@ -46,7 +46,6 @@ export class OrgSwitcherComponent {
|
||||
|
||||
/**
|
||||
* Visibility of the New Organization button
|
||||
* (Temporary; will be removed when ability to create organizations is added to SM.)
|
||||
*/
|
||||
@Input()
|
||||
hideNewButton = false;
|
||||
|
33
apps/web/src/app/layouts/toggle-width.component.ts
Normal file
33
apps/web/src/app/layouts/toggle-width.component.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { NavigationModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "app-toggle-width",
|
||||
template: `<bit-nav-item
|
||||
text="Toggle Width"
|
||||
icon="bwi-bug"
|
||||
*ngIf="isDev"
|
||||
class="tw-absolute tw-bottom-0 tw-w-full"
|
||||
(click)="toggleWidth()"
|
||||
></bit-nav-item>`,
|
||||
standalone: true,
|
||||
imports: [CommonModule, NavigationModule],
|
||||
})
|
||||
export class ToggleWidthComponent {
|
||||
protected isDev: boolean;
|
||||
|
||||
constructor(platformUtilsService: PlatformUtilsService) {
|
||||
this.isDev = platformUtilsService.isDev();
|
||||
}
|
||||
|
||||
protected toggleWidth() {
|
||||
if (document.body.style.minWidth === "unset") {
|
||||
document.body.style.minWidth = "";
|
||||
} else {
|
||||
document.body.style.minWidth = "unset";
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
<bit-nav-item
|
||||
[text]="'subscription' | i18n"
|
||||
route="settings/subscription"
|
||||
*ngIf="!hideSubscription"
|
||||
*ngIf="showSubscription$ | async"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item [text]="'domainRules' | i18n" route="settings/domain-rules"></bit-nav-item>
|
||||
<bit-nav-item
|
||||
@ -29,9 +29,11 @@
|
||||
<bit-nav-item
|
||||
[text]="'sponsoredFamilies' | i18n"
|
||||
route="settings/sponsored-families"
|
||||
*ngIf="hasFamilySponsorshipAvailable"
|
||||
*ngIf="hasFamilySponsorshipAvailable$ | async"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
|
||||
<app-toggle-width></app-toggle-width>
|
||||
</nav>
|
||||
<app-payment-method-warnings
|
||||
*ngIf="showPaymentMethodWarningBanners$ | async"
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { Observable, combineLatest, concatMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
@ -17,8 +16,7 @@ import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/compon
|
||||
import { PaymentMethodWarningsModule } from "../billing/shared";
|
||||
|
||||
import { PasswordManagerLogo } from "./password-manager-logo";
|
||||
|
||||
const BroadcasterSubscriptionId = "UserLayoutComponent";
|
||||
import { ToggleWidthComponent } from "./toggle-width.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-layout",
|
||||
@ -32,12 +30,13 @@ const BroadcasterSubscriptionId = "UserLayoutComponent";
|
||||
IconModule,
|
||||
NavigationModule,
|
||||
PaymentMethodWarningsModule,
|
||||
ToggleWidthComponent,
|
||||
],
|
||||
})
|
||||
export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
export class UserLayoutComponent implements OnInit {
|
||||
protected readonly logo = PasswordManagerLogo;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
protected hasFamilySponsorshipAvailable$: Observable<boolean>;
|
||||
protected showSubscription$: Observable<boolean>;
|
||||
|
||||
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.ShowPaymentMethodWarningBanners,
|
||||
@ -45,8 +44,6 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
@ -58,43 +55,28 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
// 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.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "purchasedPremium":
|
||||
await this.load();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await this.syncService.fullSync(false);
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
this.hasFamilySponsorshipAvailable$ = this.organizationService.canManageSponsorships$;
|
||||
|
||||
async load() {
|
||||
const hasPremiumPersonally = await firstValueFrom(
|
||||
// We want to hide the subscription menu for organizations that provide premium.
|
||||
// Except if the user has premium personally or has a billing history.
|
||||
this.showSubscription$ = combineLatest([
|
||||
this.billingAccountProfileStateService.hasPremiumPersonally$,
|
||||
);
|
||||
const hasPremiumFromOrg = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$,
|
||||
);
|
||||
const selfHosted = this.platformUtilsService.isSelfHost();
|
||||
]).pipe(
|
||||
concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => {
|
||||
const isCloud = !this.platformUtilsService.isSelfHost();
|
||||
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
let billing = null;
|
||||
if (!selfHosted) {
|
||||
// TODO: We should remove the need to call this!
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
this.hideSubscription =
|
||||
!hasPremiumPersonally && hasPremiumFromOrg && (selfHosted || billing?.hasNoHistory);
|
||||
let billing = null;
|
||||
if (isCloud) {
|
||||
// TODO: We should remove the need to call this!
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
|
||||
const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory;
|
||||
return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "SettingsComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
premium: boolean;
|
||||
selfHosted: boolean;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
private billingAccountProfileStateServiceAbstraction: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
// 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.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "purchasedPremium":
|
||||
await this.load();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.selfHosted = await this.platformUtilsService.isSelfHost();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.premium = await firstValueFrom(
|
||||
this.billingAccountProfileStateServiceAbstraction.hasPremiumPersonally$,
|
||||
);
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
const hasPremiumFromOrg = await firstValueFrom(
|
||||
this.billingAccountProfileStateServiceAbstraction.hasPremiumFromAnyOrganization$,
|
||||
);
|
||||
let billing = null;
|
||||
if (!this.selfHosted) {
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
this.hideSubscription =
|
||||
!this.premium && hasPremiumFromOrg && (this.selfHosted || billing?.hasNoHistory);
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ export class VaultItemsComponent {
|
||||
@Input() showBulkAddToCollections = false;
|
||||
@Input() showPermissionsColumn = false;
|
||||
@Input() viewingOrgVault: boolean;
|
||||
@Input({ required: true }) flexibleCollectionsV1Enabled = false;
|
||||
|
||||
private _ciphers?: CipherView[] = [];
|
||||
@Input() get ciphers(): CipherView[] {
|
||||
@ -101,7 +102,7 @@ export class VaultItemsComponent {
|
||||
}
|
||||
|
||||
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
|
||||
return collection.canEdit(organization);
|
||||
return collection.canEdit(organization, this.flexibleCollectionsV1Enabled);
|
||||
}
|
||||
|
||||
protected canDeleteCollection(collection: CollectionView): boolean {
|
||||
|
@ -31,10 +31,11 @@ export class CollectionAdminView extends CollectionView {
|
||||
this.assigned = response.assigned;
|
||||
}
|
||||
|
||||
override canEdit(org: Organization): boolean {
|
||||
override canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
return org?.flexibleCollections
|
||||
? org?.canEditAnyCollection || this.manage
|
||||
: org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned);
|
||||
? org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage
|
||||
: org?.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
|
||||
(org?.canEditAssignedCollections && this.assigned);
|
||||
}
|
||||
|
||||
override canDelete(org: Organization): boolean {
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@ -49,6 +52,11 @@ export class BulkDeleteDialogComponent {
|
||||
organizations: Organization[];
|
||||
collections: CollectionView[];
|
||||
|
||||
private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.FlexibleCollectionsV1,
|
||||
false,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
|
||||
private dialogRef: DialogRef<BulkDeleteDialogResult>,
|
||||
@ -57,6 +65,7 @@ export class BulkDeleteDialogComponent {
|
||||
private i18nService: I18nService,
|
||||
private apiService: ApiService,
|
||||
private collectionService: CollectionService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.cipherIds = params.cipherIds ?? [];
|
||||
this.permanent = params.permanent;
|
||||
@ -72,7 +81,12 @@ export class BulkDeleteDialogComponent {
|
||||
protected submit = async () => {
|
||||
const deletePromises: Promise<void>[] = [];
|
||||
if (this.cipherIds.length) {
|
||||
if (!this.organization || !this.organization.canEditAnyCollection) {
|
||||
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||
|
||||
if (
|
||||
!this.organization ||
|
||||
!this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled)
|
||||
) {
|
||||
deletePromises.push(this.deleteCiphers());
|
||||
} else {
|
||||
deletePromises.push(this.deleteCiphersAdmin());
|
||||
@ -104,7 +118,8 @@ export class BulkDeleteDialogComponent {
|
||||
};
|
||||
|
||||
private async deleteCiphers(): Promise<any> {
|
||||
const asAdmin = this.organization?.canEditAnyCollection;
|
||||
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||
const asAdmin = this.organization?.canEditAllCiphers(flexibleCollectionsV1Enabled);
|
||||
if (this.permanent) {
|
||||
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
|
||||
} else {
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
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 { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
@ -17,7 +27,7 @@ import {
|
||||
templateUrl: "./vault-header.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class VaultHeaderComponent {
|
||||
export class VaultHeaderComponent implements OnInit {
|
||||
protected Unassigned = Unassigned;
|
||||
protected All = All;
|
||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||
@ -55,7 +65,18 @@ export class VaultHeaderComponent {
|
||||
/** Emits an event when the delete collection button is clicked in the header */
|
||||
@Output() onDeleteCollection = new EventEmitter<void>();
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
private flexibleCollectionsV1Enabled = false;
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of the organization that is currently being filtered on.
|
||||
@ -137,7 +158,7 @@ export class VaultHeaderComponent {
|
||||
const organization = this.organizations.find(
|
||||
(o) => o.id === this.collection?.node.organizationId,
|
||||
);
|
||||
return this.collection.node.canEdit(organization);
|
||||
return this.collection.node.canEdit(organization, this.flexibleCollectionsV1Enabled);
|
||||
}
|
||||
|
||||
async editCollection(tab: CollectionDialogTabType): Promise<void> {
|
||||
|
@ -50,6 +50,7 @@
|
||||
[cloneableOrganizationCiphers]="false"
|
||||
[showAdminActions]="false"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled$ | async"
|
||||
>
|
||||
</app-vault-items>
|
||||
<div
|
||||
|
@ -39,6 +39,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -144,6 +145,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||
protected canCreateCollections = false;
|
||||
protected currentSearchText$: Observable<string>;
|
||||
protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.FlexibleCollectionsV1,
|
||||
false,
|
||||
);
|
||||
|
||||
private searchText$ = new Subject<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -21,10 +24,12 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "../individual-
|
||||
selector: "app-org-vault-attachments",
|
||||
templateUrl: "../individual-vault/attachments.component.html",
|
||||
})
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent implements OnInit {
|
||||
viewOnly = false;
|
||||
organization: Organization;
|
||||
|
||||
private flexibleCollectionsV1Enabled = false;
|
||||
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
i18nService: I18nService,
|
||||
@ -36,6 +41,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
fileDownloadService: FileDownloadService,
|
||||
dialogService: DialogService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
@ -51,14 +57,24 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1, false),
|
||||
);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) {
|
||||
if (
|
||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
||||
this.showFixOldAttachments(attachment)
|
||||
) {
|
||||
await super.reuploadCipherAttachment(attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -69,18 +85,21 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
return this.cipherService.saveAttachmentWithServer(
|
||||
this.cipherDomain,
|
||||
file,
|
||||
this.organization.canEditAnyCollection,
|
||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled),
|
||||
);
|
||||
}
|
||||
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
return super.deleteCipherAttachment(attachmentId);
|
||||
}
|
||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.organization.canEditAnyCollection;
|
||||
return (
|
||||
attachment.key == null &&
|
||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
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 { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
||||
@ -22,7 +24,7 @@ import {
|
||||
selector: "app-org-vault-header",
|
||||
templateUrl: "./vault-header.component.html",
|
||||
})
|
||||
export class VaultHeaderComponent {
|
||||
export class VaultHeaderComponent implements OnInit {
|
||||
protected All = All;
|
||||
protected Unassigned = Unassigned;
|
||||
|
||||
@ -56,14 +58,23 @@ export class VaultHeaderComponent {
|
||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||
protected organizations$ = this.organizationService.organizations$;
|
||||
|
||||
private flexibleCollectionsV1Enabled = false;
|
||||
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private i18nService: I18nService,
|
||||
private dialogService: DialogService,
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
const headerType = this.organization?.flexibleCollections
|
||||
? this.i18nService.t("collections").toLowerCase()
|
||||
@ -153,7 +164,7 @@ export class VaultHeaderComponent {
|
||||
}
|
||||
|
||||
// Otherwise, check if we can edit the specified collection
|
||||
return this.collection.node.canEdit(this.organization);
|
||||
return this.collection.node.canEdit(this.organization, this.flexibleCollectionsV1Enabled);
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
|
@ -54,6 +54,7 @@
|
||||
[showBulkEditCollectionAccess]="organization?.flexibleCollections"
|
||||
[showBulkAddToCollections]="organization?.flexibleCollections"
|
||||
[viewingOrgVault]="true"
|
||||
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
|
||||
>
|
||||
</app-vault-items>
|
||||
<ng-container *ngIf="!flexibleCollectionsV1Enabled">
|
||||
@ -98,7 +99,10 @@
|
||||
</bit-no-items>
|
||||
<collection-access-restricted
|
||||
*ngIf="showCollectionAccessRestricted"
|
||||
[canEdit]="selectedCollection != null && selectedCollection.node.canEdit(organization)"
|
||||
[canEdit]="
|
||||
selectedCollection != null &&
|
||||
selectedCollection.node.canEdit(organization, flexibleCollectionsV1Enabled)
|
||||
"
|
||||
(editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)"
|
||||
>
|
||||
</collection-access-restricted>
|
||||
|
@ -213,7 +213,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
switchMap(async ([organization]) => {
|
||||
this.organization = organization;
|
||||
|
||||
if (!organization.canUseAdminCollections) {
|
||||
if (!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) {
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
|
||||
@ -322,7 +322,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
} else {
|
||||
// Pre-flexible collections logic, to be removed after flexible collections is fully released
|
||||
if (organization.canEditAnyCollection) {
|
||||
if (organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
||||
} else {
|
||||
ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||
@ -407,7 +407,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
]).pipe(
|
||||
map(([filter, collection, organization]) => {
|
||||
return (
|
||||
(filter.collectionId === Unassigned && !organization.canUseAdminCollections) ||
|
||||
(filter.collectionId === Unassigned &&
|
||||
!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) ||
|
||||
(!organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
||||
collection != undefined &&
|
||||
!collection.node.assigned)
|
||||
@ -453,11 +454,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
map(([filter, collection, organization]) => {
|
||||
return (
|
||||
// Filtering by unassigned, show message if not admin
|
||||
(filter.collectionId === Unassigned && !organization.canUseAdminCollections) ||
|
||||
(filter.collectionId === Unassigned &&
|
||||
!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) ||
|
||||
// Filtering by a collection, so show message if user is not assigned
|
||||
(collection != undefined &&
|
||||
!collection.node.assigned &&
|
||||
!organization.canUseAdminCollections)
|
||||
!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled))
|
||||
);
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
@ -480,7 +482,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
|
||||
} else {
|
||||
canEditCipher =
|
||||
organization.canUseAdminCollections ||
|
||||
organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled) ||
|
||||
(await this.cipherService.get(cipherId)) != null;
|
||||
}
|
||||
|
||||
@ -856,7 +858,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
try {
|
||||
const asAdmin = this.organization?.canEditAnyCollection;
|
||||
const asAdmin = this.organization?.canEditAnyCollection(this.flexibleCollectionsV1Enabled);
|
||||
await this.cipherService.restoreWithServer(c.id, asAdmin);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
|
||||
this.refresh();
|
||||
@ -1143,7 +1145,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
||||
const asAdmin = this.organization?.canEditAnyCollection;
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
||||
return permanent
|
||||
? this.cipherService.deleteWithServer(id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(id, asAdmin);
|
||||
|
@ -27,6 +27,8 @@
|
||||
route="settings"
|
||||
*ngIf="showSettingsTab"
|
||||
></bit-nav-item>
|
||||
|
||||
<app-toggle-width></app-toggle-width>
|
||||
</nav>
|
||||
<app-payment-method-warnings
|
||||
*ngIf="showPaymentMethodWarningBanners$ | async"
|
||||
|
@ -10,6 +10,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo";
|
||||
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
|
||||
import { ToggleWidthComponent } from "@bitwarden/web-vault/app/layouts/toggle-width.component";
|
||||
|
||||
@Component({
|
||||
selector: "providers-layout",
|
||||
@ -23,6 +24,7 @@ import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/sh
|
||||
IconModule,
|
||||
NavigationModule,
|
||||
PaymentMethodWarningsModule,
|
||||
ToggleWidthComponent,
|
||||
],
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,20 @@ import { NgModule } from "@angular/core";
|
||||
|
||||
import { LayoutComponent as BitLayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
import { OrgSwitcherComponent } from "@bitwarden/web-vault/app/layouts/org-switcher/org-switcher.component";
|
||||
import { ToggleWidthComponent } from "@bitwarden/web-vault/app/layouts/toggle-width.component";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||
|
||||
import { LayoutComponent } from "./layout.component";
|
||||
import { NavigationComponent } from "./navigation.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, NavigationModule, BitLayoutComponent, OrgSwitcherComponent],
|
||||
imports: [
|
||||
SharedModule,
|
||||
NavigationModule,
|
||||
BitLayoutComponent,
|
||||
OrgSwitcherComponent,
|
||||
ToggleWidthComponent,
|
||||
],
|
||||
declarations: [LayoutComponent, NavigationComponent],
|
||||
})
|
||||
export class LayoutModule {}
|
||||
|
@ -41,4 +41,6 @@
|
||||
[relativeTo]="route.parent"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
|
||||
<app-toggle-width></app-toggle-width>
|
||||
</nav>
|
||||
|
@ -33,7 +33,7 @@ export class GeneratorComponent implements OnInit {
|
||||
subaddressOptions: any[];
|
||||
catchallOptions: any[];
|
||||
forwardOptions: EmailForwarderOptions[];
|
||||
usernameOptions: UsernameGeneratorOptions = {};
|
||||
usernameOptions: UsernameGeneratorOptions = { website: null };
|
||||
passwordOptions: PasswordGeneratorOptions = {};
|
||||
username = "-";
|
||||
password = "-";
|
||||
@ -199,12 +199,12 @@ export class GeneratorComponent implements OnInit {
|
||||
}
|
||||
|
||||
async sliderInput() {
|
||||
this.normalizePasswordOptions();
|
||||
await this.normalizePasswordOptions();
|
||||
this.password = await this.passwordGenerationService.generatePassword(this.passwordOptions);
|
||||
}
|
||||
|
||||
async savePasswordOptions(regenerate = true) {
|
||||
this.normalizePasswordOptions();
|
||||
await this.normalizePasswordOptions();
|
||||
await this.passwordGenerationService.saveOptions(this.passwordOptions);
|
||||
|
||||
if (regenerate && this.regenerateWithoutButtonPress()) {
|
||||
@ -271,7 +271,7 @@ export class GeneratorComponent implements OnInit {
|
||||
return this.type !== "username" || this.usernameOptions.type !== "forwarded";
|
||||
}
|
||||
|
||||
private normalizePasswordOptions() {
|
||||
private async normalizePasswordOptions() {
|
||||
// Application level normalize options dependent on class variables
|
||||
this.passwordOptions.ambiguous = !this.avoidAmbiguous;
|
||||
|
||||
@ -290,9 +290,8 @@ export class GeneratorComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.passwordGenerationService.normalizeOptions(
|
||||
await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(
|
||||
this.passwordOptions,
|
||||
this.enforcedPasswordPolicyOptions,
|
||||
);
|
||||
|
||||
this._passwordOptionsMinLengthForReader.next(this.passwordOptions.minLength);
|
||||
|
@ -662,7 +662,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
|
||||
// if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection
|
||||
if (!cipher.collectionIds) {
|
||||
orgAdmin = this.organization?.canEditAnyCollection;
|
||||
orgAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
||||
}
|
||||
|
||||
return this.cipher.id == null
|
||||
@ -671,14 +671,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
const asAdmin = this.organization?.canEditAnyCollection;
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
||||
}
|
||||
|
||||
protected restoreCipher() {
|
||||
const asAdmin = this.organization?.canEditAnyCollection;
|
||||
const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
||||
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ export abstract class OrganizationService {
|
||||
* https://bitwarden.atlassian.net/browse/AC-2252.
|
||||
*/
|
||||
getFromState: (id: string) => Promise<Organization>;
|
||||
canManageSponsorships: () => Promise<boolean>;
|
||||
canManageSponsorships$: Observable<boolean>;
|
||||
hasOrganizations: () => Promise<boolean>;
|
||||
get$: (id: string) => Observable<Organization | undefined>;
|
||||
get: (id: string) => Promise<Organization>;
|
||||
|
@ -188,18 +188,29 @@ export class Organization {
|
||||
return this.isManager || this.permissions.createNewCollections;
|
||||
}
|
||||
|
||||
get canEditAnyCollection() {
|
||||
return this.isAdmin || this.permissions.editAnyCollection;
|
||||
canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {
|
||||
if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {
|
||||
// Pre-Flexible Collections v1 logic
|
||||
return this.isAdmin || this.permissions.editAnyCollection;
|
||||
}
|
||||
|
||||
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
|
||||
// Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
|
||||
return (
|
||||
this.isProviderUser ||
|
||||
(this.type === OrganizationUserType.Custom && this.permissions.editAnyCollection) ||
|
||||
(this.allowAdminAccessToAllCollectionItems && this.isAdmin)
|
||||
);
|
||||
}
|
||||
|
||||
get canUseAdminCollections() {
|
||||
return this.canEditAnyCollection;
|
||||
canUseAdminCollections(flexibleCollectionsV1Enabled: boolean) {
|
||||
return this.canEditAnyCollection(flexibleCollectionsV1Enabled);
|
||||
}
|
||||
|
||||
canEditAllCiphers(flexibleCollectionsV1Enabled: boolean) {
|
||||
// Before Flexible Collections, anyone with editAnyCollection permission could edit all ciphers
|
||||
if (!flexibleCollectionsV1Enabled) {
|
||||
return this.canEditAnyCollection;
|
||||
// Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers
|
||||
if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {
|
||||
return this.isAdmin || this.permissions.editAnyCollection;
|
||||
}
|
||||
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
|
||||
// Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
|
||||
@ -214,8 +225,13 @@ export class Organization {
|
||||
return this.isAdmin || this.permissions.deleteAnyCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user can view all collection information, such as collection name and access.
|
||||
* This does not indicate that the user can view items inside any collection - for that, see {@link canEditAllCiphers}
|
||||
*/
|
||||
get canViewAllCollections() {
|
||||
return this.canEditAnyCollection || this.canDeleteAnyCollection;
|
||||
// Admins can always see all collections even if collection management settings prevent them from editing them or seeing items
|
||||
return this.isAdmin || this.permissions.editAnyCollection || this.canDeleteAnyCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +121,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipAvailable = true;
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
@ -129,7 +129,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipFriendlyName = "Something";
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
@ -137,7 +137,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipFriendlyName = null;
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -77,14 +77,10 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
|
||||
return await firstValueFrom(this.getOrganizationsFromState$(userId as UserId));
|
||||
}
|
||||
|
||||
async canManageSponsorships(): Promise<boolean> {
|
||||
return await firstValueFrom(
|
||||
this.organizations$.pipe(
|
||||
mapToExcludeOrganizationsWithoutFamilySponsorshipSupport(),
|
||||
mapToBooleanHasAnyOrganizations(),
|
||||
),
|
||||
);
|
||||
}
|
||||
canManageSponsorships$ = this.organizations$.pipe(
|
||||
mapToExcludeOrganizationsWithoutFamilySponsorshipSupport(),
|
||||
mapToBooleanHasAnyOrganizations(),
|
||||
);
|
||||
|
||||
async hasOrganizations(): Promise<boolean> {
|
||||
return await firstValueFrom(this.organizations$.pipe(mapToBooleanHasAnyOrganizations()));
|
||||
|
@ -9,40 +9,46 @@ import {
|
||||
GlobalState,
|
||||
KeyDefinition,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "../../platform/state";
|
||||
import { ClearClipboardDelay, AutofillOverlayVisibility } from "../constants";
|
||||
import { ClearClipboardDelaySetting, InlineMenuVisibilitySetting } from "../types";
|
||||
|
||||
const AUTOFILL_ON_PAGE_LOAD = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autofillOnPageLoad", {
|
||||
const AUTOFILL_ON_PAGE_LOAD = new UserKeyDefinition(AUTOFILL_SETTINGS_DISK, "autofillOnPageLoad", {
|
||||
deserializer: (value: boolean) => value ?? false,
|
||||
clearOn: [],
|
||||
});
|
||||
|
||||
const AUTOFILL_ON_PAGE_LOAD_DEFAULT = new KeyDefinition(
|
||||
const AUTOFILL_ON_PAGE_LOAD_DEFAULT = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK,
|
||||
"autofillOnPageLoadDefault",
|
||||
{
|
||||
deserializer: (value: boolean) => value ?? false,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED = new KeyDefinition(
|
||||
const AUTOFILL_ON_PAGE_LOAD_CALLOUT_DISMISSED = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK,
|
||||
"autofillOnPageLoadCalloutIsDismissed",
|
||||
{
|
||||
deserializer: (value: boolean) => value ?? false,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED = new KeyDefinition(
|
||||
const AUTOFILL_ON_PAGE_LOAD_POLICY_TOAST_HAS_DISPLAYED = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK,
|
||||
"autofillOnPageLoadPolicyToastHasDisplayed",
|
||||
{
|
||||
deserializer: (value: boolean) => value ?? false,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const AUTO_COPY_TOTP = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
|
||||
const AUTO_COPY_TOTP = new UserKeyDefinition(AUTOFILL_SETTINGS_DISK, "autoCopyTotp", {
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
clearOn: [],
|
||||
});
|
||||
|
||||
const INLINE_MENU_VISIBILITY = new KeyDefinition(
|
||||
@ -57,11 +63,12 @@ const ENABLE_CONTEXT_MENU = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "enableCon
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
});
|
||||
|
||||
const CLEAR_CLIPBOARD_DELAY = new KeyDefinition(
|
||||
const CLEAR_CLIPBOARD_DELAY = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK_LOCAL,
|
||||
"clearClipboardDelay",
|
||||
{
|
||||
deserializer: (value: ClearClipboardDelaySetting) => value ?? ClearClipboardDelay.Never,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -3,12 +3,13 @@ import { map, Observable } from "rxjs";
|
||||
import {
|
||||
BADGE_SETTINGS_DISK,
|
||||
ActiveUserState,
|
||||
KeyDefinition,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "../../platform/state";
|
||||
|
||||
const ENABLE_BADGE_COUNTER = new KeyDefinition(BADGE_SETTINGS_DISK, "enableBadgeCounter", {
|
||||
const ENABLE_BADGE_COUNTER = new UserKeyDefinition(BADGE_SETTINGS_DISK, "enableBadgeCounter", {
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
clearOn: [],
|
||||
});
|
||||
|
||||
export abstract class BadgeSettingsServiceAbstraction {
|
||||
|
@ -29,11 +29,12 @@ const EQUIVALENT_DOMAINS = new UserKeyDefinition(DOMAIN_SETTINGS_DISK, "equivale
|
||||
clearOn: ["logout"],
|
||||
});
|
||||
|
||||
const DEFAULT_URI_MATCH_STRATEGY = new KeyDefinition(
|
||||
const DEFAULT_URI_MATCH_STRATEGY = new UserKeyDefinition(
|
||||
DOMAIN_SETTINGS_DISK,
|
||||
"defaultUriMatchStrategy",
|
||||
{
|
||||
deserializer: (value: UriMatchStrategySetting) => value ?? UriMatchStrategy.Domain,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BILLING_DISK, KeyDefinition } from "../../platform/state";
|
||||
import { BILLING_DISK, UserKeyDefinition } from "../../platform/state";
|
||||
import { PaymentMethodWarning } from "../models/domain/payment-method-warning";
|
||||
|
||||
export const PAYMENT_METHOD_WARNINGS_KEY = KeyDefinition.record<PaymentMethodWarning>(
|
||||
export const PAYMENT_METHOD_WARNINGS_KEY = UserKeyDefinition.record<PaymentMethodWarning>(
|
||||
BILLING_DISK,
|
||||
"paymentMethodWarnings",
|
||||
{
|
||||
@ -9,5 +9,6 @@ export const PAYMENT_METHOD_WARNINGS_KEY = KeyDefinition.record<PaymentMethodWar
|
||||
...warnings,
|
||||
savedAt: new Date(warnings.savedAt),
|
||||
}),
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
@ -3,19 +3,20 @@ import { map, Observable, of, switchMap } from "rxjs";
|
||||
import {
|
||||
ActiveUserState,
|
||||
BILLING_DISK,
|
||||
KeyDefinition,
|
||||
StateProvider,
|
||||
UserKeyDefinition,
|
||||
} from "../../../platform/state";
|
||||
import {
|
||||
BillingAccountProfile,
|
||||
BillingAccountProfileStateService,
|
||||
} from "../../abstractions/account/billing-account-profile-state.service";
|
||||
|
||||
export const BILLING_ACCOUNT_PROFILE_KEY_DEFINITION = new KeyDefinition<BillingAccountProfile>(
|
||||
export const BILLING_ACCOUNT_PROFILE_KEY_DEFINITION = new UserKeyDefinition<BillingAccountProfile>(
|
||||
BILLING_DISK,
|
||||
"accountProfile",
|
||||
{
|
||||
deserializer: (billingAccountProfile) => billingAccountProfile,
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -14,7 +14,7 @@ describe("AppIdService", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("getAppId", () => {
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorNavigation } from "../navigation/generator-navigation";
|
||||
import { GeneratorNavigationPolicy } from "../navigation/generator-navigation-policy";
|
||||
|
||||
import { PolicyEvaluator } from "./policy-evaluator.abstraction";
|
||||
|
||||
/** Loads and stores generator navigational data
|
||||
*/
|
||||
export abstract class GeneratorNavigationService {
|
||||
/** An observable monitoring the options saved to disk.
|
||||
* The observable updates when the options are saved.
|
||||
* @param userId: Identifies the user making the request
|
||||
*/
|
||||
options$: (userId: UserId) => Observable<GeneratorNavigation>;
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$: (userId: UserId) => Observable<GeneratorNavigation>;
|
||||
|
||||
/** An observable monitoring the options used to enforce policy.
|
||||
* The observable updates when the policy changes.
|
||||
* @param userId: Identifies the user making the request
|
||||
*/
|
||||
evaluator$: (
|
||||
userId: UserId,
|
||||
) => Observable<PolicyEvaluator<GeneratorNavigationPolicy, GeneratorNavigation>>;
|
||||
|
||||
/** Enforces the policy on the given options
|
||||
* @param userId: Identifies the user making the request
|
||||
* @param options the options to enforce the policy on
|
||||
* @returns a new instance of the options with the policy enforced
|
||||
*/
|
||||
enforcePolicy: (userId: UserId, options: GeneratorNavigation) => Promise<GeneratorNavigation>;
|
||||
|
||||
/** Saves the navigation options to disk.
|
||||
* @param userId: Identifies the user making the request
|
||||
* @param options the options to save
|
||||
* @returns a promise that resolves when the options are saved
|
||||
*/
|
||||
saveOptions: (userId: UserId, options: GeneratorNavigation) => Promise<void>;
|
||||
}
|
@ -17,6 +17,9 @@ export abstract class GeneratorStrategy<Options, Policy> {
|
||||
*/
|
||||
durableState: (userId: UserId) => SingleUserState<Options>;
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$: (userId: UserId) => Observable<Options>;
|
||||
|
||||
/** Identifies the policy enforced by the generator. */
|
||||
policy: PolicyType;
|
||||
|
||||
|
@ -21,6 +21,9 @@ export abstract class GeneratorService<Options, Policy> {
|
||||
*/
|
||||
evaluator$: (userId: UserId) => Observable<PolicyEvaluator<Policy, Options>>;
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$: (userId: UserId) => Observable<Options>;
|
||||
|
||||
/** Enforces the policy on the given options
|
||||
* @param userId: Identifies the user making the request
|
||||
* @param options the options to enforce the policy on
|
||||
|
@ -1,3 +1,4 @@
|
||||
export { GeneratorNavigationService } from "./generator-navigation.service.abstraction";
|
||||
export { GeneratorService } from "./generator.service.abstraction";
|
||||
export { GeneratorStrategy } from "./generator-strategy.abstraction";
|
||||
export { PolicyEvaluator } from "./policy-evaluator.abstraction";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { PasswordGeneratorPolicyOptions } from "../../../admin-console/models/domain/password-generator-policy-options";
|
||||
import { GeneratedPasswordHistory } from "../password/generated-password-history";
|
||||
import { PasswordGeneratorOptions } from "../password/password-generator-options";
|
||||
|
||||
import { GeneratedPasswordHistory } from "./generated-password-history";
|
||||
import { PasswordGeneratorOptions } from "./password-generator-options";
|
||||
|
||||
/** @deprecated Use {@link GeneratorService} with a password or passphrase {@link GeneratorStrategy} instead. */
|
||||
export abstract class PasswordGenerationServiceAbstraction {
|
||||
generatePassword: (options: PasswordGeneratorOptions) => Promise<string>;
|
||||
generatePassphrase: (options: PasswordGeneratorOptions) => Promise<string>;
|
||||
@ -10,13 +10,8 @@ export abstract class PasswordGenerationServiceAbstraction {
|
||||
enforcePasswordGeneratorPoliciesOnOptions: (
|
||||
options: PasswordGeneratorOptions,
|
||||
) => Promise<[PasswordGeneratorOptions, PasswordGeneratorPolicyOptions]>;
|
||||
getPasswordGeneratorPolicyOptions: () => Promise<PasswordGeneratorPolicyOptions>;
|
||||
saveOptions: (options: PasswordGeneratorOptions) => Promise<void>;
|
||||
getHistory: () => Promise<GeneratedPasswordHistory[]>;
|
||||
addHistory: (password: string) => Promise<void>;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
normalizeOptions: (
|
||||
options: PasswordGeneratorOptions,
|
||||
enforcedPolicyOptions: PasswordGeneratorPolicyOptions,
|
||||
) => void;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { UsernameGeneratorOptions } from "./username-generation-options";
|
||||
import { UsernameGeneratorOptions } from "../username/username-generation-options";
|
||||
|
||||
/** @deprecated Use {@link GeneratorService} with a username {@link GeneratorStrategy} instead. */
|
||||
export abstract class UsernameGenerationServiceAbstraction {
|
||||
generateUsername: (options: UsernameGeneratorOptions) => Promise<string>;
|
||||
generateWord: (options: UsernameGeneratorOptions) => Promise<string>;
|
@ -37,6 +37,7 @@ function mockGeneratorStrategy(config?: {
|
||||
userState?: SingleUserState<any>;
|
||||
policy?: PolicyType;
|
||||
evaluator?: any;
|
||||
defaults?: any;
|
||||
}) {
|
||||
const durableState =
|
||||
config?.userState ?? new FakeSingleUserState<PasswordGenerationOptions>(SomeUser);
|
||||
@ -45,6 +46,7 @@ function mockGeneratorStrategy(config?: {
|
||||
// whether they're used properly are guaranteed to test
|
||||
// the value from `config`.
|
||||
durableState: jest.fn(() => durableState),
|
||||
defaults$: jest.fn(() => new BehaviorSubject(config?.defaults)),
|
||||
policy: config?.policy ?? PolicyType.DisableSend,
|
||||
toEvaluator: jest.fn(() =>
|
||||
pipe(map(() => config?.evaluator ?? mock<PolicyEvaluator<any, any>>())),
|
||||
@ -72,6 +74,20 @@ describe("Password generator service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should retrieve default state from the service", async () => {
|
||||
const policy = mockPolicyService();
|
||||
const defaults = {};
|
||||
const strategy = mockGeneratorStrategy({ defaults });
|
||||
const service = new DefaultGeneratorService(strategy, policy);
|
||||
|
||||
const result = await firstValueFrom(service.defaults$(SomeUser));
|
||||
|
||||
expect(strategy.defaults$).toHaveBeenCalledWith(SomeUser);
|
||||
expect(result).toBe(defaults);
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveOptions()", () => {
|
||||
it("should trigger an options$ update", async () => {
|
||||
const policy = mockPolicyService();
|
||||
|
@ -21,17 +21,22 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
|
||||
|
||||
private _evaluators$ = new Map<UserId, Observable<PolicyEvaluator<Policy, Options>>>();
|
||||
|
||||
/** {@link GeneratorService.options$()} */
|
||||
/** {@link GeneratorService.options$} */
|
||||
options$(userId: UserId) {
|
||||
return this.strategy.durableState(userId).state$;
|
||||
}
|
||||
|
||||
/** {@link GeneratorService.defaults$} */
|
||||
defaults$(userId: UserId) {
|
||||
return this.strategy.defaults$(userId);
|
||||
}
|
||||
|
||||
/** {@link GeneratorService.saveOptions} */
|
||||
async saveOptions(userId: UserId, options: Options): Promise<void> {
|
||||
await this.strategy.durableState(userId).update(() => options);
|
||||
}
|
||||
|
||||
/** {@link GeneratorService.evaluator$()} */
|
||||
/** {@link GeneratorService.evaluator$} */
|
||||
evaluator$(userId: UserId) {
|
||||
let evaluator$ = this._evaluators$.get(userId);
|
||||
|
||||
@ -59,7 +64,7 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
|
||||
return evaluator$;
|
||||
}
|
||||
|
||||
/** {@link GeneratorService.enforcePolicy()} */
|
||||
/** {@link GeneratorService.enforcePolicy} */
|
||||
async enforcePolicy(userId: UserId, options: Options): Promise<Options> {
|
||||
const policy = await firstValueFrom(this.evaluator$(userId));
|
||||
const evaluated = policy.applyPolicy(options);
|
||||
|
@ -1,3 +1,5 @@
|
||||
export type GeneratorOptions = {
|
||||
type?: "password" | "username";
|
||||
};
|
||||
// this export provided solely for backwards compatibility
|
||||
export {
|
||||
/** @deprecated use `GeneratorNavigation` from './navigation' instead. */
|
||||
GeneratorNavigation as GeneratorOptions,
|
||||
} from "./navigation/generator-navigation";
|
||||
|
2
libs/common/src/tools/generator/generator-type.ts
Normal file
2
libs/common/src/tools/generator/generator-type.ts
Normal file
@ -0,0 +1,2 @@
|
||||
/** The kind of credential being generated. */
|
||||
export type GeneratorType = "password" | "passphrase" | "username";
|
@ -10,9 +10,18 @@ import {
|
||||
FASTMAIL_FORWARDER,
|
||||
DUCK_DUCK_GO_FORWARDER,
|
||||
ADDY_IO_FORWARDER,
|
||||
GENERATOR_SETTINGS,
|
||||
} from "./key-definitions";
|
||||
|
||||
describe("Key definitions", () => {
|
||||
describe("GENERATOR_SETTINGS", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
const result = GENERATOR_SETTINGS.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PASSWORD_SETTINGS", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
@ -31,7 +40,7 @@ describe("Key definitions", () => {
|
||||
|
||||
describe("EFF_USERNAME_SETTINGS", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
const value = { website: null as string };
|
||||
const result = EFF_USERNAME_SETTINGS.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
@ -39,7 +48,7 @@ describe("Key definitions", () => {
|
||||
|
||||
describe("CATCHALL_SETTINGS", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
const value = { website: null as string };
|
||||
const result = CATCHALL_SETTINGS.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
@ -47,7 +56,7 @@ describe("Key definitions", () => {
|
||||
|
||||
describe("SUBADDRESS_SETTINGS", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
const value = { website: null as string };
|
||||
const result = SUBADDRESS_SETTINGS.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { GENERATOR_DISK, KeyDefinition } from "../../platform/state";
|
||||
import { GENERATOR_DISK, GENERATOR_MEMORY, KeyDefinition } from "../../platform/state";
|
||||
|
||||
import { GeneratedCredential } from "./history/generated-credential";
|
||||
import { GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { PassphraseGenerationOptions } from "./passphrase/passphrase-generation-options";
|
||||
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
||||
import { SecretClassifier } from "./state/secret-classifier";
|
||||
@ -15,6 +16,15 @@ import {
|
||||
} from "./username/options/forwarder-options";
|
||||
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
|
||||
|
||||
/** plaintext password generation options */
|
||||
export const GENERATOR_SETTINGS = new KeyDefinition<GeneratorNavigation>(
|
||||
GENERATOR_MEMORY,
|
||||
"generatorSettings",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
/** plaintext password generation options */
|
||||
export const PASSWORD_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
|
||||
GENERATOR_DISK,
|
||||
@ -42,7 +52,7 @@ export const EFF_USERNAME_SETTINGS = new KeyDefinition<EffUsernameGenerationOpti
|
||||
},
|
||||
);
|
||||
|
||||
/** catchall email generation options */
|
||||
/** plaintext configuration for a domain catch-all address. */
|
||||
export const CATCHALL_SETTINGS = new KeyDefinition<CatchallGenerationOptions>(
|
||||
GENERATOR_DISK,
|
||||
"catchallGeneratorSettings",
|
||||
@ -51,7 +61,7 @@ export const CATCHALL_SETTINGS = new KeyDefinition<CatchallGenerationOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
/** email subaddress generation options */
|
||||
/** plaintext configuration for an email subaddress. */
|
||||
export const SUBADDRESS_SETTINGS = new KeyDefinition<SubaddressGenerationOptions>(
|
||||
GENERATOR_DISK,
|
||||
"subaddressGeneratorSettings",
|
||||
@ -60,6 +70,7 @@ export const SUBADDRESS_SETTINGS = new KeyDefinition<SubaddressGenerationOptions
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link Forwarders.AddyIo} */
|
||||
export const ADDY_IO_FORWARDER = new KeyDefinition<SelfHostedApiOptions & EmailDomainOptions>(
|
||||
GENERATOR_DISK,
|
||||
"addyIoForwarder",
|
||||
@ -68,6 +79,7 @@ export const ADDY_IO_FORWARDER = new KeyDefinition<SelfHostedApiOptions & EmailD
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link Forwarders.DuckDuckGo} */
|
||||
export const DUCK_DUCK_GO_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"duckDuckGoForwarder",
|
||||
@ -76,6 +88,7 @@ export const DUCK_DUCK_GO_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link Forwarders.FastMail} */
|
||||
export const FASTMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailPrefixOptions>(
|
||||
GENERATOR_DISK,
|
||||
"fastmailForwarder",
|
||||
@ -84,6 +97,7 @@ export const FASTMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailPrefixOpti
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link Forwarders.FireFoxRelay} */
|
||||
export const FIREFOX_RELAY_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"firefoxRelayForwarder",
|
||||
@ -92,6 +106,7 @@ export const FIREFOX_RELAY_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link Forwarders.ForwardEmail} */
|
||||
export const FORWARD_EMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailDomainOptions>(
|
||||
GENERATOR_DISK,
|
||||
"forwardEmailForwarder",
|
||||
@ -100,6 +115,7 @@ export const FORWARD_EMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailDomai
|
||||
},
|
||||
);
|
||||
|
||||
/** backing store configuration for {@link forwarders.SimpleLogin} */
|
||||
export const SIMPLE_LOGIN_FORWARDER = new KeyDefinition<SelfHostedApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"simpleLoginForwarder",
|
||||
|
@ -0,0 +1,470 @@
|
||||
/**
|
||||
* include structuredClone in test environment.
|
||||
* @jest-environment ../../../shared/test.environment.ts
|
||||
*/
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { mockAccountServiceWith } from "../../../spec";
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
import { GeneratorNavigationService, GeneratorService } from "./abstractions";
|
||||
import { LegacyPasswordGenerationService } from "./legacy-password-generation.service";
|
||||
import { DefaultGeneratorNavigation, GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { GeneratorNavigationEvaluator } from "./navigation/generator-navigation-evaluator";
|
||||
import { GeneratorNavigationPolicy } from "./navigation/generator-navigation-policy";
|
||||
import {
|
||||
DefaultPassphraseGenerationOptions,
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorOptionsEvaluator,
|
||||
PassphraseGeneratorPolicy,
|
||||
} from "./passphrase";
|
||||
import { DisabledPassphraseGeneratorPolicy } from "./passphrase/passphrase-generator-policy";
|
||||
import {
|
||||
DefaultPasswordGenerationOptions,
|
||||
PasswordGenerationOptions,
|
||||
PasswordGeneratorOptions,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
PasswordGeneratorPolicy,
|
||||
} from "./password";
|
||||
import { DisabledPasswordGeneratorPolicy } from "./password/password-generator-policy";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
function createPassphraseGenerator(
|
||||
options: PassphraseGenerationOptions = {},
|
||||
policy: PassphraseGeneratorPolicy = DisabledPassphraseGeneratorPolicy,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PassphraseGenerationOptions, PassphraseGeneratorPolicy>>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
return of(savedOptions);
|
||||
},
|
||||
defaults$(id: UserId) {
|
||||
return of(DefaultPassphraseGenerationOptions);
|
||||
},
|
||||
saveOptions(userId, options) {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
function createPasswordGenerator(
|
||||
options: PasswordGenerationOptions = {},
|
||||
policy: PasswordGeneratorPolicy = DisabledPasswordGeneratorPolicy,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PasswordGenerationOptions, PasswordGeneratorPolicy>>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
return of(savedOptions);
|
||||
},
|
||||
defaults$(id: UserId) {
|
||||
return of(DefaultPasswordGenerationOptions);
|
||||
},
|
||||
saveOptions(userId, options) {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
function createNavigationGenerator(
|
||||
options: GeneratorNavigation = {},
|
||||
policy: GeneratorNavigationPolicy = {},
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorNavigationService>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new GeneratorNavigationEvaluator(policy);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
return of(savedOptions);
|
||||
},
|
||||
defaults$(id: UserId) {
|
||||
return of(DefaultGeneratorNavigation);
|
||||
},
|
||||
saveOptions(userId, options) {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
describe("LegacyPasswordGenerationService", () => {
|
||||
// NOTE: in all tests, `null` constructor arguments are not used by the test.
|
||||
// They're set to `null` to avoid setting up unnecessary mocks.
|
||||
|
||||
describe("generatePassword", () => {
|
||||
it("invokes the inner password generator to generate passwords", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null);
|
||||
const options = { type: "password" } as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassword(options);
|
||||
|
||||
expect(innerPassword.generate).toHaveBeenCalledWith(options);
|
||||
});
|
||||
|
||||
it("invokes the inner passphrase generator to generate passphrases", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, null, innerPassphrase);
|
||||
const options = { type: "passphrase" } as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassword(options);
|
||||
|
||||
expect(innerPassphrase.generate).toHaveBeenCalledWith(options);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generatePassphrase", () => {
|
||||
it("invokes the inner passphrase generator", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, null, innerPassphrase);
|
||||
const options = {} as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassphrase(options);
|
||||
|
||||
expect(innerPassphrase.generate).toHaveBeenCalledWith(options);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOptions", () => {
|
||||
it("combines options from its inner services", async () => {
|
||||
const innerPassword = createPasswordGenerator({
|
||||
length: 29,
|
||||
minLength: 20,
|
||||
ambiguous: false,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: false,
|
||||
minLowercase: 2,
|
||||
number: true,
|
||||
minNumber: 3,
|
||||
special: false,
|
||||
minSpecial: 4,
|
||||
});
|
||||
const innerPassphrase = createPassphraseGenerator({
|
||||
numWords: 10,
|
||||
wordSeparator: "-",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "passphrase",
|
||||
username: "word",
|
||||
forwarder: "simplelogin",
|
||||
});
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "passphrase",
|
||||
username: "word",
|
||||
forwarder: "simplelogin",
|
||||
length: 29,
|
||||
minLength: 20,
|
||||
ambiguous: false,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: false,
|
||||
minLowercase: 2,
|
||||
number: true,
|
||||
minNumber: 3,
|
||||
special: false,
|
||||
minSpecial: 4,
|
||||
numWords: 10,
|
||||
wordSeparator: "-",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("sets default options when an inner service lacks a value", async () => {
|
||||
const innerPassword = createPasswordGenerator(null);
|
||||
const innerPassphrase = createPassphraseGenerator(null);
|
||||
const navigation = createNavigationGenerator(null);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
...DefaultGeneratorNavigation,
|
||||
...DefaultPassphraseGenerationOptions,
|
||||
...DefaultPasswordGenerationOptions,
|
||||
});
|
||||
});
|
||||
|
||||
it("combines policies from its inner services", async () => {
|
||||
const innerPassword = createPasswordGenerator(
|
||||
{},
|
||||
{
|
||||
minLength: 20,
|
||||
numberCount: 10,
|
||||
specialCount: 11,
|
||||
useUppercase: true,
|
||||
useLowercase: false,
|
||||
useNumbers: true,
|
||||
useSpecial: false,
|
||||
},
|
||||
);
|
||||
const innerPassphrase = createPassphraseGenerator(
|
||||
{},
|
||||
{
|
||||
minNumberWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
},
|
||||
);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator(
|
||||
{},
|
||||
{
|
||||
defaultType: "password",
|
||||
},
|
||||
);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.getOptions();
|
||||
|
||||
expect(policy).toEqual({
|
||||
defaultType: "password",
|
||||
minLength: 20,
|
||||
numberCount: 10,
|
||||
specialCount: 11,
|
||||
useUppercase: true,
|
||||
useLowercase: false,
|
||||
useNumbers: true,
|
||||
useSpecial: false,
|
||||
minNumberWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("enforcePasswordGeneratorPoliciesOnOptions", () => {
|
||||
it("returns its options parameter with password policy applied", async () => {
|
||||
const innerPassword = createPasswordGenerator(
|
||||
{},
|
||||
{
|
||||
minLength: 15,
|
||||
numberCount: 5,
|
||||
specialCount: 5,
|
||||
useUppercase: true,
|
||||
useLowercase: true,
|
||||
useNumbers: true,
|
||||
useSpecial: true,
|
||||
},
|
||||
);
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator();
|
||||
const options = {
|
||||
type: "password" as const,
|
||||
};
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
|
||||
expect(result).toBe(options);
|
||||
expect(result).toMatchObject({
|
||||
length: 15,
|
||||
minLength: 15,
|
||||
minLowercase: 1,
|
||||
minNumber: 5,
|
||||
minUppercase: 1,
|
||||
minSpecial: 5,
|
||||
uppercase: true,
|
||||
lowercase: true,
|
||||
number: true,
|
||||
special: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns its options parameter with passphrase policy applied", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const innerPassphrase = createPassphraseGenerator(
|
||||
{},
|
||||
{
|
||||
minNumberWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: true,
|
||||
},
|
||||
);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator();
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
};
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
|
||||
expect(result).toBe(options);
|
||||
expect(result).toMatchObject({
|
||||
numWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the applied policy", async () => {
|
||||
const innerPassword = createPasswordGenerator(
|
||||
{},
|
||||
{
|
||||
minLength: 20,
|
||||
numberCount: 10,
|
||||
specialCount: 11,
|
||||
useUppercase: true,
|
||||
useLowercase: false,
|
||||
useNumbers: true,
|
||||
useSpecial: false,
|
||||
},
|
||||
);
|
||||
const innerPassphrase = createPassphraseGenerator(
|
||||
{},
|
||||
{
|
||||
minNumberWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
},
|
||||
);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator(
|
||||
{},
|
||||
{
|
||||
defaultType: "password",
|
||||
},
|
||||
);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({});
|
||||
|
||||
expect(policy).toEqual({
|
||||
defaultType: "password",
|
||||
minLength: 20,
|
||||
numberCount: 10,
|
||||
specialCount: 11,
|
||||
useUppercase: true,
|
||||
useLowercase: false,
|
||||
useNumbers: true,
|
||||
useSpecial: false,
|
||||
minNumberWords: 5,
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveOptions", () => {
|
||||
it("loads saved password options", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const navigation = createNavigationGenerator();
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
const options = {
|
||||
type: "password" as const,
|
||||
username: "word" as const,
|
||||
forwarder: "simplelogin" as const,
|
||||
length: 29,
|
||||
minLength: 20,
|
||||
ambiguous: false,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: false,
|
||||
minLowercase: 2,
|
||||
number: true,
|
||||
minNumber: 3,
|
||||
special: false,
|
||||
minSpecial: 4,
|
||||
};
|
||||
await generator.saveOptions(options);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toMatchObject(options);
|
||||
});
|
||||
|
||||
it("loads saved passphrase options", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const navigation = createNavigationGenerator();
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
);
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
username: "word" as const,
|
||||
forwarder: "simplelogin" as const,
|
||||
numWords: 10,
|
||||
wordSeparator: "-",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
};
|
||||
await generator.saveOptions(options);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toMatchObject(options);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,184 @@
|
||||
import { concatMap, zip, map, firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PasswordGeneratorPolicyOptions } from "../../admin-console/models/domain/password-generator-policy-options";
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { StateProvider } from "../../platform/state";
|
||||
|
||||
import { GeneratorService, GeneratorNavigationService } from "./abstractions";
|
||||
import { PasswordGenerationServiceAbstraction } from "./abstractions/password-generation.service.abstraction";
|
||||
import { DefaultGeneratorService } from "./default-generator.service";
|
||||
import { DefaultGeneratorNavigationService } from "./navigation/default-generator-navigation.service";
|
||||
import {
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy,
|
||||
PassphraseGeneratorStrategy,
|
||||
} from "./passphrase";
|
||||
import {
|
||||
PasswordGenerationOptions,
|
||||
PasswordGenerationService,
|
||||
PasswordGeneratorOptions,
|
||||
PasswordGeneratorPolicy,
|
||||
PasswordGeneratorStrategy,
|
||||
} from "./password";
|
||||
|
||||
export function legacyPasswordGenerationServiceFactory(
|
||||
cryptoService: CryptoService,
|
||||
policyService: PolicyService,
|
||||
accountService: AccountService,
|
||||
stateProvider: StateProvider,
|
||||
): PasswordGenerationServiceAbstraction {
|
||||
// FIXME: Once the password generation service is replaced with this service
|
||||
// in the clients, factor out the deprecated service in its entirety.
|
||||
const deprecatedService = new PasswordGenerationService(cryptoService, null, null);
|
||||
|
||||
const passwords = new DefaultGeneratorService(
|
||||
new PasswordGeneratorStrategy(deprecatedService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const passphrases = new DefaultGeneratorService(
|
||||
new PassphraseGeneratorStrategy(deprecatedService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const navigation = new DefaultGeneratorNavigationService(stateProvider, policyService);
|
||||
|
||||
return new LegacyPasswordGenerationService(accountService, navigation, passwords, passphrases);
|
||||
}
|
||||
|
||||
/** Adapts the generator 2.0 design to 1.0 angular services. */
|
||||
export class LegacyPasswordGenerationService implements PasswordGenerationServiceAbstraction {
|
||||
constructor(
|
||||
private readonly accountService: AccountService,
|
||||
private readonly navigation: GeneratorNavigationService,
|
||||
private readonly passwords: GeneratorService<
|
||||
PasswordGenerationOptions,
|
||||
PasswordGeneratorPolicy
|
||||
>,
|
||||
private readonly passphrases: GeneratorService<
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy
|
||||
>,
|
||||
) {}
|
||||
|
||||
generatePassword(options: PasswordGeneratorOptions) {
|
||||
if (options.type === "password") {
|
||||
return this.passwords.generate(options);
|
||||
} else {
|
||||
return this.passphrases.generate(options);
|
||||
}
|
||||
}
|
||||
|
||||
generatePassphrase(options: PasswordGeneratorOptions) {
|
||||
return this.passphrases.generate(options);
|
||||
}
|
||||
|
||||
async getOptions() {
|
||||
const options$ = this.accountService.activeAccount$.pipe(
|
||||
concatMap((activeUser) =>
|
||||
zip(
|
||||
this.passwords.options$(activeUser.id),
|
||||
this.passwords.defaults$(activeUser.id),
|
||||
this.passwords.evaluator$(activeUser.id),
|
||||
this.passphrases.options$(activeUser.id),
|
||||
this.passphrases.defaults$(activeUser.id),
|
||||
this.passphrases.evaluator$(activeUser.id),
|
||||
this.navigation.options$(activeUser.id),
|
||||
this.navigation.defaults$(activeUser.id),
|
||||
this.navigation.evaluator$(activeUser.id),
|
||||
),
|
||||
),
|
||||
map(
|
||||
([
|
||||
passwordOptions,
|
||||
passwordDefaults,
|
||||
passwordEvaluator,
|
||||
passphraseOptions,
|
||||
passphraseDefaults,
|
||||
passphraseEvaluator,
|
||||
generatorOptions,
|
||||
generatorDefaults,
|
||||
generatorEvaluator,
|
||||
]) => {
|
||||
const options: PasswordGeneratorOptions = Object.assign(
|
||||
{},
|
||||
passwordOptions ?? passwordDefaults,
|
||||
passphraseOptions ?? passphraseDefaults,
|
||||
generatorOptions ?? generatorDefaults,
|
||||
);
|
||||
|
||||
const policy = Object.assign(
|
||||
new PasswordGeneratorPolicyOptions(),
|
||||
passwordEvaluator.policy,
|
||||
passphraseEvaluator.policy,
|
||||
generatorEvaluator.policy,
|
||||
);
|
||||
|
||||
return [options, policy] as [PasswordGenerationOptions, PasswordGeneratorPolicyOptions];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const options = await firstValueFrom(options$);
|
||||
return options;
|
||||
}
|
||||
|
||||
async enforcePasswordGeneratorPoliciesOnOptions(options: PasswordGeneratorOptions) {
|
||||
const options$ = this.accountService.activeAccount$.pipe(
|
||||
concatMap((activeUser) =>
|
||||
zip(
|
||||
this.passwords.evaluator$(activeUser.id),
|
||||
this.passphrases.evaluator$(activeUser.id),
|
||||
this.navigation.evaluator$(activeUser.id),
|
||||
),
|
||||
),
|
||||
map(([passwordEvaluator, passphraseEvaluator, navigationEvaluator]) => {
|
||||
const policy = Object.assign(
|
||||
new PasswordGeneratorPolicyOptions(),
|
||||
passwordEvaluator.policy,
|
||||
passphraseEvaluator.policy,
|
||||
navigationEvaluator.policy,
|
||||
);
|
||||
|
||||
const navigationApplied = navigationEvaluator.applyPolicy(options);
|
||||
const navigationSanitized = {
|
||||
...options,
|
||||
...navigationEvaluator.sanitize(navigationApplied),
|
||||
};
|
||||
if (options.type === "password") {
|
||||
const applied = passwordEvaluator.applyPolicy(navigationSanitized);
|
||||
const sanitized = passwordEvaluator.sanitize(applied);
|
||||
return [sanitized, policy];
|
||||
} else {
|
||||
const applied = passphraseEvaluator.applyPolicy(navigationSanitized);
|
||||
const sanitized = passphraseEvaluator.sanitize(applied);
|
||||
return [sanitized, policy];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const [sanitized, policy] = await firstValueFrom(options$);
|
||||
return [
|
||||
// callers assume this function updates the options parameter
|
||||
Object.assign(options, sanitized),
|
||||
policy,
|
||||
] as [PasswordGenerationOptions, PasswordGeneratorPolicyOptions];
|
||||
}
|
||||
|
||||
async saveOptions(options: PasswordGeneratorOptions) {
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
|
||||
await this.navigation.saveOptions(activeAccount.id, options);
|
||||
if (options.type === "password") {
|
||||
await this.passwords.saveOptions(activeAccount.id, options);
|
||||
} else {
|
||||
await this.passphrases.saveOptions(activeAccount.id, options);
|
||||
}
|
||||
}
|
||||
|
||||
getHistory: () => Promise<any[]>;
|
||||
addHistory: (password: string) => Promise<void>;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
}
|
@ -0,0 +1,748 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { mockAccountServiceWith } from "../../../spec";
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
import { GeneratorNavigationService, GeneratorService } from "./abstractions";
|
||||
import { DefaultPolicyEvaluator } from "./default-policy-evaluator";
|
||||
import { LegacyUsernameGenerationService } from "./legacy-username-generation.service";
|
||||
import { DefaultGeneratorNavigation, GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { GeneratorNavigationEvaluator } from "./navigation/generator-navigation-evaluator";
|
||||
import { GeneratorNavigationPolicy } from "./navigation/generator-navigation-policy";
|
||||
import { NoPolicy } from "./no-policy";
|
||||
import { UsernameGeneratorOptions } from "./username";
|
||||
import {
|
||||
CatchallGenerationOptions,
|
||||
DefaultCatchallOptions,
|
||||
} from "./username/catchall-generator-options";
|
||||
import {
|
||||
DefaultEffUsernameOptions,
|
||||
EffUsernameGenerationOptions,
|
||||
} from "./username/eff-username-generator-options";
|
||||
import { DefaultAddyIoOptions } from "./username/forwarders/addy-io";
|
||||
import { DefaultDuckDuckGoOptions } from "./username/forwarders/duck-duck-go";
|
||||
import { DefaultFastmailOptions } from "./username/forwarders/fastmail";
|
||||
import { DefaultFirefoxRelayOptions } from "./username/forwarders/firefox-relay";
|
||||
import { DefaultForwardEmailOptions } from "./username/forwarders/forward-email";
|
||||
import { DefaultSimpleLoginOptions } from "./username/forwarders/simple-login";
|
||||
import { Forwarders } from "./username/options/constants";
|
||||
import {
|
||||
ApiOptions,
|
||||
EmailDomainOptions,
|
||||
EmailPrefixOptions,
|
||||
SelfHostedApiOptions,
|
||||
} from "./username/options/forwarder-options";
|
||||
import {
|
||||
DefaultSubaddressOptions,
|
||||
SubaddressGenerationOptions,
|
||||
} from "./username/subaddress-generator-options";
|
||||
|
||||
const SomeUser = "userId" as UserId;
|
||||
|
||||
function createGenerator<Options>(options: Options, defaults: Options) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<Options, NoPolicy>>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new DefaultPolicyEvaluator<Options>();
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
return of(savedOptions);
|
||||
},
|
||||
defaults$(id: UserId) {
|
||||
return of(defaults);
|
||||
},
|
||||
saveOptions: jest.fn((userId, options) => {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
}),
|
||||
});
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
function createNavigationGenerator(
|
||||
options: GeneratorNavigation = {},
|
||||
policy: GeneratorNavigationPolicy = {},
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorNavigationService>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new GeneratorNavigationEvaluator(policy);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
return of(savedOptions);
|
||||
},
|
||||
defaults$(id: UserId) {
|
||||
return of(DefaultGeneratorNavigation);
|
||||
},
|
||||
saveOptions: jest.fn((userId, options) => {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
}),
|
||||
});
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
describe("LegacyUsernameGenerationService", () => {
|
||||
// NOTE: in all tests, `null` constructor arguments are not used by the test.
|
||||
// They're set to `null` to avoid setting up unnecessary mocks.
|
||||
describe("generateUserName", () => {
|
||||
it("should generate a catchall username", async () => {
|
||||
const options = { type: "catchall" } as UsernameGeneratorOptions;
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(null, null);
|
||||
catchall.generate.mockReturnValue(Promise.resolve("catchall@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
catchall,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateUsername(options);
|
||||
|
||||
expect(catchall.generate).toHaveBeenCalledWith(options);
|
||||
expect(result).toBe("catchall@example.com");
|
||||
});
|
||||
|
||||
it("should generate an EFF word username", async () => {
|
||||
const options = { type: "word" } as UsernameGeneratorOptions;
|
||||
const effWord = createGenerator<EffUsernameGenerationOptions>(null, null);
|
||||
effWord.generate.mockReturnValue(Promise.resolve("eff word"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
effWord,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateUsername(options);
|
||||
|
||||
expect(effWord.generate).toHaveBeenCalledWith(options);
|
||||
expect(result).toBe("eff word");
|
||||
});
|
||||
|
||||
it("should generate a subaddress username", async () => {
|
||||
const options = { type: "subaddress" } as UsernameGeneratorOptions;
|
||||
const subaddress = createGenerator<SubaddressGenerationOptions>(null, null);
|
||||
subaddress.generate.mockReturnValue(Promise.resolve("subaddress@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
subaddress,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateUsername(options);
|
||||
|
||||
expect(subaddress.generate).toHaveBeenCalledWith(options);
|
||||
expect(result).toBe("subaddress@example.com");
|
||||
});
|
||||
|
||||
it("should generate a forwarder username", async () => {
|
||||
// set up an arbitrary forwarder for the username test; all forwarders tested in their own tests
|
||||
const options = {
|
||||
type: "forwarded",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
} as UsernameGeneratorOptions;
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(null, null);
|
||||
addyIo.generate.mockReturnValue(Promise.resolve("addyio@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
addyIo,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateUsername(options);
|
||||
|
||||
expect(addyIo.generate).toHaveBeenCalledWith({});
|
||||
expect(result).toBe("addyio@example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateCatchall", () => {
|
||||
it("should generate a catchall username", async () => {
|
||||
const options = { type: "catchall" } as UsernameGeneratorOptions;
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(null, null);
|
||||
catchall.generate.mockReturnValue(Promise.resolve("catchall@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
catchall,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateCatchall(options);
|
||||
|
||||
expect(catchall.generate).toHaveBeenCalledWith(options);
|
||||
expect(result).toBe("catchall@example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSubaddress", () => {
|
||||
it("should generate a subaddress username", async () => {
|
||||
const options = { type: "subaddress" } as UsernameGeneratorOptions;
|
||||
const subaddress = createGenerator<SubaddressGenerationOptions>(null, null);
|
||||
subaddress.generate.mockReturnValue(Promise.resolve("subaddress@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
subaddress,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateSubaddress(options);
|
||||
|
||||
expect(subaddress.generate).toHaveBeenCalledWith(options);
|
||||
expect(result).toBe("subaddress@example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateForwarded", () => {
|
||||
it("should generate a AddyIo username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedAnonAddyApiToken: "token",
|
||||
forwardedAnonAddyBaseUrl: "https://example.com",
|
||||
forwardedAnonAddyDomain: "example.com",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(null, null);
|
||||
addyIo.generate.mockReturnValue(Promise.resolve("addyio@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
addyIo,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(addyIo.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
baseUrl: "https://example.com",
|
||||
domain: "example.com",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("addyio@example.com");
|
||||
});
|
||||
|
||||
it("should generate a DuckDuckGo username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.DuckDuckGo.id,
|
||||
forwardedDuckDuckGoToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const duckDuckGo = createGenerator<ApiOptions>(null, null);
|
||||
duckDuckGo.generate.mockReturnValue(Promise.resolve("ddg@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
duckDuckGo,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(duckDuckGo.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("ddg@example.com");
|
||||
});
|
||||
|
||||
it("should generate a Fastmail username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.Fastmail.id,
|
||||
forwardedFastmailApiToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(null, null);
|
||||
fastmail.generate.mockReturnValue(Promise.resolve("fastmail@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
fastmail,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(fastmail.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("fastmail@example.com");
|
||||
});
|
||||
|
||||
it("should generate a FirefoxRelay username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.FirefoxRelay.id,
|
||||
forwardedFirefoxApiToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const firefoxRelay = createGenerator<ApiOptions>(null, null);
|
||||
firefoxRelay.generate.mockReturnValue(Promise.resolve("firefoxrelay@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
firefoxRelay,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(firefoxRelay.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("firefoxrelay@example.com");
|
||||
});
|
||||
|
||||
it("should generate a ForwardEmail username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.ForwardEmail.id,
|
||||
forwardedForwardEmailApiToken: "token",
|
||||
forwardedForwardEmailDomain: "example.com",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(null, null);
|
||||
forwardEmail.generate.mockReturnValue(Promise.resolve("forwardemail@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
forwardEmail,
|
||||
null,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(forwardEmail.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("forwardemail@example.com");
|
||||
});
|
||||
|
||||
it("should generate a SimpleLogin username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.SimpleLogin.id,
|
||||
forwardedSimpleLoginApiKey: "token",
|
||||
forwardedSimpleLoginBaseUrl: "https://example.com",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(null, null);
|
||||
simpleLogin.generate.mockReturnValue(Promise.resolve("simplelogin@example.com"));
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
simpleLogin,
|
||||
);
|
||||
|
||||
const result = await generator.generateForwarded(options);
|
||||
|
||||
expect(simpleLogin.generate).toHaveBeenCalledWith({
|
||||
token: "token",
|
||||
baseUrl: "https://example.com",
|
||||
website: "example.com",
|
||||
});
|
||||
expect(result).toBe("simplelogin@example.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOptions", () => {
|
||||
it("combines options from its inner generators", async () => {
|
||||
const account = mockAccountServiceWith(SomeUser);
|
||||
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "username",
|
||||
username: "catchall",
|
||||
forwarder: Forwarders.AddyIo.id,
|
||||
});
|
||||
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(
|
||||
{
|
||||
catchallDomain: "example.com",
|
||||
catchallType: "random",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const effUsername = createGenerator<EffUsernameGenerationOptions>(
|
||||
{
|
||||
wordCapitalize: true,
|
||||
wordIncludeNumber: false,
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const subaddress = createGenerator<SubaddressGenerationOptions>(
|
||||
{
|
||||
subaddressType: "random",
|
||||
subaddressEmail: "foo@example.com",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(
|
||||
{
|
||||
token: "addyIoToken",
|
||||
domain: "addyio.example.com",
|
||||
baseUrl: "https://addyio.api.example.com",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const duckDuckGo = createGenerator<ApiOptions>(
|
||||
{
|
||||
token: "ddgToken",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(
|
||||
{
|
||||
token: "fastmailToken",
|
||||
domain: "fastmail.example.com",
|
||||
prefix: "foo",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const firefoxRelay = createGenerator<ApiOptions>(
|
||||
{
|
||||
token: "firefoxToken",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(
|
||||
{
|
||||
token: "forwardEmailToken",
|
||||
domain: "example.com",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(
|
||||
{
|
||||
token: "simpleLoginToken",
|
||||
baseUrl: "https://simplelogin.api.example.com",
|
||||
website: null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
account,
|
||||
navigation,
|
||||
catchall,
|
||||
effUsername,
|
||||
subaddress,
|
||||
addyIo,
|
||||
duckDuckGo,
|
||||
fastmail,
|
||||
firefoxRelay,
|
||||
forwardEmail,
|
||||
simpleLogin,
|
||||
);
|
||||
|
||||
const result = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "catchall",
|
||||
wordCapitalize: true,
|
||||
wordIncludeNumber: false,
|
||||
subaddressType: "random",
|
||||
subaddressEmail: "foo@example.com",
|
||||
catchallType: "random",
|
||||
catchallDomain: "example.com",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedAnonAddyApiToken: "addyIoToken",
|
||||
forwardedAnonAddyDomain: "addyio.example.com",
|
||||
forwardedAnonAddyBaseUrl: "https://addyio.api.example.com",
|
||||
forwardedDuckDuckGoToken: "ddgToken",
|
||||
forwardedFirefoxApiToken: "firefoxToken",
|
||||
forwardedFastmailApiToken: "fastmailToken",
|
||||
forwardedForwardEmailApiToken: "forwardEmailToken",
|
||||
forwardedForwardEmailDomain: "example.com",
|
||||
forwardedSimpleLoginApiKey: "simpleLoginToken",
|
||||
forwardedSimpleLoginBaseUrl: "https://simplelogin.api.example.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("sets default options when an inner service lacks a value", async () => {
|
||||
const account = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator(null);
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(null, DefaultCatchallOptions);
|
||||
const effUsername = createGenerator<EffUsernameGenerationOptions>(
|
||||
null,
|
||||
DefaultEffUsernameOptions,
|
||||
);
|
||||
const subaddress = createGenerator<SubaddressGenerationOptions>(
|
||||
null,
|
||||
DefaultSubaddressOptions,
|
||||
);
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(
|
||||
null,
|
||||
DefaultAddyIoOptions,
|
||||
);
|
||||
const duckDuckGo = createGenerator<ApiOptions>(null, DefaultDuckDuckGoOptions);
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(
|
||||
null,
|
||||
DefaultFastmailOptions,
|
||||
);
|
||||
const firefoxRelay = createGenerator<ApiOptions>(null, DefaultFirefoxRelayOptions);
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(
|
||||
null,
|
||||
DefaultForwardEmailOptions,
|
||||
);
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(null, DefaultSimpleLoginOptions);
|
||||
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
account,
|
||||
navigation,
|
||||
catchall,
|
||||
effUsername,
|
||||
subaddress,
|
||||
addyIo,
|
||||
duckDuckGo,
|
||||
fastmail,
|
||||
firefoxRelay,
|
||||
forwardEmail,
|
||||
simpleLogin,
|
||||
);
|
||||
|
||||
const result = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
type: DefaultGeneratorNavigation.username,
|
||||
catchallType: DefaultCatchallOptions.catchallType,
|
||||
catchallDomain: DefaultCatchallOptions.catchallDomain,
|
||||
wordCapitalize: DefaultEffUsernameOptions.wordCapitalize,
|
||||
wordIncludeNumber: DefaultEffUsernameOptions.wordIncludeNumber,
|
||||
subaddressType: DefaultSubaddressOptions.subaddressType,
|
||||
subaddressEmail: DefaultSubaddressOptions.subaddressEmail,
|
||||
forwardedService: DefaultGeneratorNavigation.forwarder,
|
||||
forwardedAnonAddyApiToken: DefaultAddyIoOptions.token,
|
||||
forwardedAnonAddyDomain: DefaultAddyIoOptions.domain,
|
||||
forwardedAnonAddyBaseUrl: DefaultAddyIoOptions.baseUrl,
|
||||
forwardedDuckDuckGoToken: DefaultDuckDuckGoOptions.token,
|
||||
forwardedFastmailApiToken: DefaultFastmailOptions.token,
|
||||
forwardedFirefoxApiToken: DefaultFirefoxRelayOptions.token,
|
||||
forwardedForwardEmailApiToken: DefaultForwardEmailOptions.token,
|
||||
forwardedForwardEmailDomain: DefaultForwardEmailOptions.domain,
|
||||
forwardedSimpleLoginApiKey: DefaultSimpleLoginOptions.token,
|
||||
forwardedSimpleLoginBaseUrl: DefaultSimpleLoginOptions.baseUrl,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveOptions", () => {
|
||||
it("saves option sets to its inner generators", async () => {
|
||||
const account = mockAccountServiceWith(SomeUser);
|
||||
const navigation = createNavigationGenerator({ type: "password" });
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(null, null);
|
||||
const effUsername = createGenerator<EffUsernameGenerationOptions>(null, null);
|
||||
const subaddress = createGenerator<SubaddressGenerationOptions>(null, null);
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(null, null);
|
||||
const duckDuckGo = createGenerator<ApiOptions>(null, null);
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(null, null);
|
||||
const firefoxRelay = createGenerator<ApiOptions>(null, null);
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(null, null);
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(null, null);
|
||||
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
account,
|
||||
navigation,
|
||||
catchall,
|
||||
effUsername,
|
||||
subaddress,
|
||||
addyIo,
|
||||
duckDuckGo,
|
||||
fastmail,
|
||||
firefoxRelay,
|
||||
forwardEmail,
|
||||
simpleLogin,
|
||||
);
|
||||
|
||||
await generator.saveOptions({
|
||||
type: "catchall",
|
||||
wordCapitalize: true,
|
||||
wordIncludeNumber: false,
|
||||
subaddressType: "random",
|
||||
subaddressEmail: "foo@example.com",
|
||||
catchallType: "random",
|
||||
catchallDomain: "example.com",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedAnonAddyApiToken: "addyIoToken",
|
||||
forwardedAnonAddyDomain: "addyio.example.com",
|
||||
forwardedAnonAddyBaseUrl: "https://addyio.api.example.com",
|
||||
forwardedDuckDuckGoToken: "ddgToken",
|
||||
forwardedFirefoxApiToken: "firefoxToken",
|
||||
forwardedFastmailApiToken: "fastmailToken",
|
||||
forwardedForwardEmailApiToken: "forwardEmailToken",
|
||||
forwardedForwardEmailDomain: "example.com",
|
||||
forwardedSimpleLoginApiKey: "simpleLoginToken",
|
||||
forwardedSimpleLoginBaseUrl: "https://simplelogin.api.example.com",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
type: "password",
|
||||
username: "catchall",
|
||||
forwarder: Forwarders.AddyIo.id,
|
||||
});
|
||||
|
||||
expect(catchall.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
catchallDomain: "example.com",
|
||||
catchallType: "random",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(effUsername.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
wordCapitalize: true,
|
||||
wordIncludeNumber: false,
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(subaddress.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
subaddressType: "random",
|
||||
subaddressEmail: "foo@example.com",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(addyIo.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "addyIoToken",
|
||||
domain: "addyio.example.com",
|
||||
baseUrl: "https://addyio.api.example.com",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(duckDuckGo.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "ddgToken",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(fastmail.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "fastmailToken",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(firefoxRelay.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "firefoxToken",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(forwardEmail.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "forwardEmailToken",
|
||||
domain: "example.com",
|
||||
website: null,
|
||||
});
|
||||
|
||||
expect(simpleLogin.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
token: "simpleLoginToken",
|
||||
baseUrl: "https://simplelogin.api.example.com",
|
||||
website: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,383 @@
|
||||
import { zip, firstValueFrom, map, concatMap } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../platform/state";
|
||||
|
||||
import { GeneratorService, GeneratorNavigationService } from "./abstractions";
|
||||
import { UsernameGenerationServiceAbstraction } from "./abstractions/username-generation.service.abstraction";
|
||||
import { DefaultGeneratorService } from "./default-generator.service";
|
||||
import { DefaultGeneratorNavigationService } from "./navigation/default-generator-navigation.service";
|
||||
import { GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { NoPolicy } from "./no-policy";
|
||||
import {
|
||||
CatchallGeneratorStrategy,
|
||||
SubaddressGeneratorStrategy,
|
||||
EffUsernameGeneratorStrategy,
|
||||
} from "./username";
|
||||
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
|
||||
import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options";
|
||||
import { AddyIoForwarder } from "./username/forwarders/addy-io";
|
||||
import { DuckDuckGoForwarder } from "./username/forwarders/duck-duck-go";
|
||||
import { FastmailForwarder } from "./username/forwarders/fastmail";
|
||||
import { FirefoxRelayForwarder } from "./username/forwarders/firefox-relay";
|
||||
import { ForwardEmailForwarder } from "./username/forwarders/forward-email";
|
||||
import { SimpleLoginForwarder } from "./username/forwarders/simple-login";
|
||||
import { Forwarders } from "./username/options/constants";
|
||||
import {
|
||||
ApiOptions,
|
||||
EmailDomainOptions,
|
||||
EmailPrefixOptions,
|
||||
RequestOptions,
|
||||
SelfHostedApiOptions,
|
||||
} from "./username/options/forwarder-options";
|
||||
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
|
||||
import { UsernameGeneratorOptions } from "./username/username-generation-options";
|
||||
import { UsernameGenerationService } from "./username/username-generation.service";
|
||||
|
||||
type MappedOptions = {
|
||||
generator: GeneratorNavigation;
|
||||
algorithms: {
|
||||
catchall: CatchallGenerationOptions;
|
||||
effUsername: EffUsernameGenerationOptions;
|
||||
subaddress: SubaddressGenerationOptions;
|
||||
};
|
||||
forwarders: {
|
||||
addyIo: SelfHostedApiOptions & EmailDomainOptions & RequestOptions;
|
||||
duckDuckGo: ApiOptions & RequestOptions;
|
||||
fastmail: ApiOptions & EmailPrefixOptions & RequestOptions;
|
||||
firefoxRelay: ApiOptions & RequestOptions;
|
||||
forwardEmail: ApiOptions & EmailDomainOptions & RequestOptions;
|
||||
simpleLogin: SelfHostedApiOptions & RequestOptions;
|
||||
};
|
||||
};
|
||||
|
||||
export function legacyPasswordGenerationServiceFactory(
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
encryptService: EncryptService,
|
||||
policyService: PolicyService,
|
||||
accountService: AccountService,
|
||||
stateProvider: StateProvider,
|
||||
): UsernameGenerationServiceAbstraction {
|
||||
// FIXME: Once the username generation service is replaced with this service
|
||||
// in the clients, factor out the deprecated service in its entirety.
|
||||
const deprecatedService = new UsernameGenerationService(cryptoService, null, null);
|
||||
|
||||
const effUsername = new DefaultGeneratorService(
|
||||
new EffUsernameGeneratorStrategy(deprecatedService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const subaddress = new DefaultGeneratorService(
|
||||
new SubaddressGeneratorStrategy(deprecatedService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const catchall = new DefaultGeneratorService(
|
||||
new CatchallGeneratorStrategy(deprecatedService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const addyIo = new DefaultGeneratorService(
|
||||
new AddyIoForwarder(apiService, i18nService, encryptService, cryptoService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const duckDuckGo = new DefaultGeneratorService(
|
||||
new DuckDuckGoForwarder(apiService, i18nService, encryptService, cryptoService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const fastmail = new DefaultGeneratorService(
|
||||
new FastmailForwarder(apiService, i18nService, encryptService, cryptoService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const firefoxRelay = new DefaultGeneratorService(
|
||||
new FirefoxRelayForwarder(
|
||||
apiService,
|
||||
i18nService,
|
||||
encryptService,
|
||||
cryptoService,
|
||||
stateProvider,
|
||||
),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const forwardEmail = new DefaultGeneratorService(
|
||||
new ForwardEmailForwarder(
|
||||
apiService,
|
||||
i18nService,
|
||||
encryptService,
|
||||
cryptoService,
|
||||
stateProvider,
|
||||
),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const simpleLogin = new DefaultGeneratorService(
|
||||
new SimpleLoginForwarder(apiService, i18nService, encryptService, cryptoService, stateProvider),
|
||||
policyService,
|
||||
);
|
||||
|
||||
const navigation = new DefaultGeneratorNavigationService(stateProvider, policyService);
|
||||
|
||||
return new LegacyUsernameGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
catchall,
|
||||
effUsername,
|
||||
subaddress,
|
||||
addyIo,
|
||||
duckDuckGo,
|
||||
fastmail,
|
||||
firefoxRelay,
|
||||
forwardEmail,
|
||||
simpleLogin,
|
||||
);
|
||||
}
|
||||
|
||||
/** Adapts the generator 2.0 design to 1.0 angular services. */
|
||||
export class LegacyUsernameGenerationService implements UsernameGenerationServiceAbstraction {
|
||||
constructor(
|
||||
private readonly accountService: AccountService,
|
||||
private readonly navigation: GeneratorNavigationService,
|
||||
private readonly catchall: GeneratorService<CatchallGenerationOptions, NoPolicy>,
|
||||
private readonly effUsername: GeneratorService<EffUsernameGenerationOptions, NoPolicy>,
|
||||
private readonly subaddress: GeneratorService<SubaddressGenerationOptions, NoPolicy>,
|
||||
private readonly addyIo: GeneratorService<SelfHostedApiOptions & EmailDomainOptions, NoPolicy>,
|
||||
private readonly duckDuckGo: GeneratorService<ApiOptions, NoPolicy>,
|
||||
private readonly fastmail: GeneratorService<ApiOptions & EmailPrefixOptions, NoPolicy>,
|
||||
private readonly firefoxRelay: GeneratorService<ApiOptions, NoPolicy>,
|
||||
private readonly forwardEmail: GeneratorService<ApiOptions & EmailDomainOptions, NoPolicy>,
|
||||
private readonly simpleLogin: GeneratorService<SelfHostedApiOptions, NoPolicy>,
|
||||
) {}
|
||||
|
||||
generateUsername(options: UsernameGeneratorOptions) {
|
||||
if (options.type === "catchall") {
|
||||
return this.generateCatchall(options);
|
||||
} else if (options.type === "subaddress") {
|
||||
return this.generateSubaddress(options);
|
||||
} else if (options.type === "forwarded") {
|
||||
return this.generateForwarded(options);
|
||||
} else {
|
||||
return this.generateWord(options);
|
||||
}
|
||||
}
|
||||
|
||||
generateWord(options: UsernameGeneratorOptions) {
|
||||
return this.effUsername.generate(options);
|
||||
}
|
||||
|
||||
generateSubaddress(options: UsernameGeneratorOptions) {
|
||||
return this.subaddress.generate(options);
|
||||
}
|
||||
|
||||
generateCatchall(options: UsernameGeneratorOptions) {
|
||||
return this.catchall.generate(options);
|
||||
}
|
||||
|
||||
generateForwarded(options: UsernameGeneratorOptions) {
|
||||
if (!options.forwardedService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stored = this.toStoredOptions(options);
|
||||
switch (options.forwardedService) {
|
||||
case Forwarders.AddyIo.id:
|
||||
return this.addyIo.generate(stored.forwarders.addyIo);
|
||||
case Forwarders.DuckDuckGo.id:
|
||||
return this.duckDuckGo.generate(stored.forwarders.duckDuckGo);
|
||||
case Forwarders.Fastmail.id:
|
||||
return this.fastmail.generate(stored.forwarders.fastmail);
|
||||
case Forwarders.FirefoxRelay.id:
|
||||
return this.firefoxRelay.generate(stored.forwarders.firefoxRelay);
|
||||
case Forwarders.ForwardEmail.id:
|
||||
return this.forwardEmail.generate(stored.forwarders.forwardEmail);
|
||||
case Forwarders.SimpleLogin.id:
|
||||
return this.simpleLogin.generate(stored.forwarders.simpleLogin);
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
const options$ = this.accountService.activeAccount$.pipe(
|
||||
concatMap((account) =>
|
||||
zip(
|
||||
this.navigation.options$(account.id),
|
||||
this.navigation.defaults$(account.id),
|
||||
this.catchall.options$(account.id),
|
||||
this.catchall.defaults$(account.id),
|
||||
this.effUsername.options$(account.id),
|
||||
this.effUsername.defaults$(account.id),
|
||||
this.subaddress.options$(account.id),
|
||||
this.subaddress.defaults$(account.id),
|
||||
this.addyIo.options$(account.id),
|
||||
this.addyIo.defaults$(account.id),
|
||||
this.duckDuckGo.options$(account.id),
|
||||
this.duckDuckGo.defaults$(account.id),
|
||||
this.fastmail.options$(account.id),
|
||||
this.fastmail.defaults$(account.id),
|
||||
this.firefoxRelay.options$(account.id),
|
||||
this.firefoxRelay.defaults$(account.id),
|
||||
this.forwardEmail.options$(account.id),
|
||||
this.forwardEmail.defaults$(account.id),
|
||||
this.simpleLogin.options$(account.id),
|
||||
this.simpleLogin.defaults$(account.id),
|
||||
),
|
||||
),
|
||||
map(
|
||||
([
|
||||
generatorOptions,
|
||||
generatorDefaults,
|
||||
catchallOptions,
|
||||
catchallDefaults,
|
||||
effUsernameOptions,
|
||||
effUsernameDefaults,
|
||||
subaddressOptions,
|
||||
subaddressDefaults,
|
||||
addyIoOptions,
|
||||
addyIoDefaults,
|
||||
duckDuckGoOptions,
|
||||
duckDuckGoDefaults,
|
||||
fastmailOptions,
|
||||
fastmailDefaults,
|
||||
firefoxRelayOptions,
|
||||
firefoxRelayDefaults,
|
||||
forwardEmailOptions,
|
||||
forwardEmailDefaults,
|
||||
simpleLoginOptions,
|
||||
simpleLoginDefaults,
|
||||
]) =>
|
||||
this.toUsernameOptions({
|
||||
generator: generatorOptions ?? generatorDefaults,
|
||||
algorithms: {
|
||||
catchall: catchallOptions ?? catchallDefaults,
|
||||
effUsername: effUsernameOptions ?? effUsernameDefaults,
|
||||
subaddress: subaddressOptions ?? subaddressDefaults,
|
||||
},
|
||||
forwarders: {
|
||||
addyIo: addyIoOptions ?? addyIoDefaults,
|
||||
duckDuckGo: duckDuckGoOptions ?? duckDuckGoDefaults,
|
||||
fastmail: fastmailOptions ?? fastmailDefaults,
|
||||
firefoxRelay: firefoxRelayOptions ?? firefoxRelayDefaults,
|
||||
forwardEmail: forwardEmailOptions ?? forwardEmailDefaults,
|
||||
simpleLogin: simpleLoginOptions ?? simpleLoginDefaults,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return firstValueFrom(options$);
|
||||
}
|
||||
|
||||
async saveOptions(options: UsernameGeneratorOptions) {
|
||||
const stored = this.toStoredOptions(options);
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a.id)));
|
||||
|
||||
// generator settings needs to preserve whether password or passphrase is selected,
|
||||
// so `navigationOptions` is mutated.
|
||||
let navigationOptions = await firstValueFrom(this.navigation.options$(userId));
|
||||
navigationOptions = Object.assign(navigationOptions, stored.generator);
|
||||
await this.navigation.saveOptions(userId, navigationOptions);
|
||||
|
||||
// overwrite all other settings with latest values
|
||||
await Promise.all([
|
||||
this.catchall.saveOptions(userId, stored.algorithms.catchall),
|
||||
this.effUsername.saveOptions(userId, stored.algorithms.effUsername),
|
||||
this.subaddress.saveOptions(userId, stored.algorithms.subaddress),
|
||||
this.addyIo.saveOptions(userId, stored.forwarders.addyIo),
|
||||
this.duckDuckGo.saveOptions(userId, stored.forwarders.duckDuckGo),
|
||||
this.fastmail.saveOptions(userId, stored.forwarders.fastmail),
|
||||
this.firefoxRelay.saveOptions(userId, stored.forwarders.firefoxRelay),
|
||||
this.forwardEmail.saveOptions(userId, stored.forwarders.forwardEmail),
|
||||
this.simpleLogin.saveOptions(userId, stored.forwarders.simpleLogin),
|
||||
]);
|
||||
}
|
||||
|
||||
private toStoredOptions(options: UsernameGeneratorOptions) {
|
||||
const forwarders = {
|
||||
addyIo: {
|
||||
baseUrl: options.forwardedAnonAddyBaseUrl,
|
||||
token: options.forwardedAnonAddyApiToken,
|
||||
domain: options.forwardedAnonAddyDomain,
|
||||
website: options.website,
|
||||
},
|
||||
duckDuckGo: {
|
||||
token: options.forwardedDuckDuckGoToken,
|
||||
website: options.website,
|
||||
},
|
||||
fastmail: {
|
||||
token: options.forwardedFastmailApiToken,
|
||||
website: options.website,
|
||||
},
|
||||
firefoxRelay: {
|
||||
token: options.forwardedFirefoxApiToken,
|
||||
website: options.website,
|
||||
},
|
||||
forwardEmail: {
|
||||
token: options.forwardedForwardEmailApiToken,
|
||||
domain: options.forwardedForwardEmailDomain,
|
||||
website: options.website,
|
||||
},
|
||||
simpleLogin: {
|
||||
token: options.forwardedSimpleLoginApiKey,
|
||||
baseUrl: options.forwardedSimpleLoginBaseUrl,
|
||||
website: options.website,
|
||||
},
|
||||
};
|
||||
|
||||
const generator = {
|
||||
username: options.type,
|
||||
forwarder: options.forwardedService,
|
||||
};
|
||||
|
||||
const algorithms = {
|
||||
effUsername: {
|
||||
wordCapitalize: options.wordCapitalize,
|
||||
wordIncludeNumber: options.wordIncludeNumber,
|
||||
website: options.website,
|
||||
},
|
||||
subaddress: {
|
||||
subaddressType: options.subaddressType,
|
||||
subaddressEmail: options.subaddressEmail,
|
||||
website: options.website,
|
||||
},
|
||||
catchall: {
|
||||
catchallType: options.catchallType,
|
||||
catchallDomain: options.catchallDomain,
|
||||
website: options.website,
|
||||
},
|
||||
};
|
||||
|
||||
return { generator, algorithms, forwarders } as MappedOptions;
|
||||
}
|
||||
|
||||
private toUsernameOptions(options: MappedOptions) {
|
||||
return {
|
||||
type: options.generator.username,
|
||||
wordCapitalize: options.algorithms.effUsername.wordCapitalize,
|
||||
wordIncludeNumber: options.algorithms.effUsername.wordIncludeNumber,
|
||||
subaddressType: options.algorithms.subaddress.subaddressType,
|
||||
subaddressEmail: options.algorithms.subaddress.subaddressEmail,
|
||||
catchallType: options.algorithms.catchall.catchallType,
|
||||
catchallDomain: options.algorithms.catchall.catchallDomain,
|
||||
forwardedService: options.generator.forwarder,
|
||||
forwardedAnonAddyApiToken: options.forwarders.addyIo.token,
|
||||
forwardedAnonAddyDomain: options.forwarders.addyIo.domain,
|
||||
forwardedAnonAddyBaseUrl: options.forwarders.addyIo.baseUrl,
|
||||
forwardedDuckDuckGoToken: options.forwarders.duckDuckGo.token,
|
||||
forwardedFirefoxApiToken: options.forwarders.firefoxRelay.token,
|
||||
forwardedFastmailApiToken: options.forwarders.fastmail.token,
|
||||
forwardedForwardEmailApiToken: options.forwarders.forwardEmail.token,
|
||||
forwardedForwardEmailDomain: options.forwarders.forwardEmail.domain,
|
||||
forwardedSimpleLoginApiKey: options.forwarders.simpleLogin.token,
|
||||
forwardedSimpleLoginBaseUrl: options.forwarders.simpleLogin.baseUrl,
|
||||
} as UsernameGeneratorOptions;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* include structuredClone in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
|
||||
import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GENERATOR_SETTINGS } from "../key-definitions";
|
||||
|
||||
import {
|
||||
GeneratorNavigationEvaluator,
|
||||
DefaultGeneratorNavigationService,
|
||||
DefaultGeneratorNavigation,
|
||||
} from "./";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
describe("DefaultGeneratorNavigationService", () => {
|
||||
describe("options$", () => {
|
||||
it("emits options", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const settings = { type: "password" as const };
|
||||
await stateProvider.setUserState(GENERATOR_SETTINGS, settings, SomeUser);
|
||||
const navigation = new DefaultGeneratorNavigationService(stateProvider, null);
|
||||
|
||||
const result = await firstValueFrom(navigation.options$(SomeUser));
|
||||
|
||||
expect(result).toEqual(settings);
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("emits default options", async () => {
|
||||
const navigation = new DefaultGeneratorNavigationService(null, null);
|
||||
|
||||
const result = await firstValueFrom(navigation.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultGeneratorNavigation);
|
||||
});
|
||||
});
|
||||
|
||||
describe("evaluator$", () => {
|
||||
it("emits a GeneratorNavigationEvaluator", async () => {
|
||||
const policyService = mock<PolicyService>({
|
||||
getAll$() {
|
||||
return of([]);
|
||||
},
|
||||
});
|
||||
const navigation = new DefaultGeneratorNavigationService(null, policyService);
|
||||
|
||||
const result = await firstValueFrom(navigation.evaluator$(SomeUser));
|
||||
|
||||
expect(result).toBeInstanceOf(GeneratorNavigationEvaluator);
|
||||
});
|
||||
});
|
||||
|
||||
describe("enforcePolicy", () => {
|
||||
it("applies policy", async () => {
|
||||
const policyService = mock<PolicyService>({
|
||||
getAll$(_type: PolicyType, _user: UserId) {
|
||||
return of([
|
||||
new Policy({
|
||||
id: "" as any,
|
||||
organizationId: "" as any,
|
||||
enabled: true,
|
||||
type: PolicyType.PasswordGenerator,
|
||||
data: { defaultType: "password" },
|
||||
}),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const navigation = new DefaultGeneratorNavigationService(null, policyService);
|
||||
const options = {};
|
||||
|
||||
const result = await navigation.enforcePolicy(SomeUser, options);
|
||||
|
||||
expect(result).toMatchObject({ type: "password" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveOptions", () => {
|
||||
it("updates options$", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const navigation = new DefaultGeneratorNavigationService(stateProvider, null);
|
||||
const settings = { type: "password" as const };
|
||||
|
||||
await navigation.saveOptions(SomeUser, settings);
|
||||
const result = await firstValueFrom(navigation.options$(SomeUser));
|
||||
|
||||
expect(result).toEqual(settings);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,71 @@
|
||||
import { BehaviorSubject, Observable, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorNavigationService } from "../abstractions/generator-navigation.service.abstraction";
|
||||
import { GENERATOR_SETTINGS } from "../key-definitions";
|
||||
import { reduceCollection } from "../reduce-collection.operator";
|
||||
|
||||
import { DefaultGeneratorNavigation, GeneratorNavigation } from "./generator-navigation";
|
||||
import { GeneratorNavigationEvaluator } from "./generator-navigation-evaluator";
|
||||
import { DisabledGeneratorNavigationPolicy, preferPassword } from "./generator-navigation-policy";
|
||||
|
||||
export class DefaultGeneratorNavigationService implements GeneratorNavigationService {
|
||||
/** instantiates the password generator strategy.
|
||||
* @param stateProvider provides durable state
|
||||
* @param policy provides the policy to enforce
|
||||
*/
|
||||
constructor(
|
||||
private readonly stateProvider: StateProvider,
|
||||
private readonly policy: PolicyService,
|
||||
) {}
|
||||
|
||||
/** An observable monitoring the options saved to disk.
|
||||
* The observable updates when the options are saved.
|
||||
* @param userId: Identifies the user making the request
|
||||
*/
|
||||
options$(userId: UserId): Observable<GeneratorNavigation> {
|
||||
return this.stateProvider.getUserState$(GENERATOR_SETTINGS, userId);
|
||||
}
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$(userId: UserId): Observable<GeneratorNavigation> {
|
||||
return new BehaviorSubject({ ...DefaultGeneratorNavigation });
|
||||
}
|
||||
|
||||
/** An observable monitoring the options used to enforce policy.
|
||||
* The observable updates when the policy changes.
|
||||
* @param userId: Identifies the user making the request
|
||||
*/
|
||||
evaluator$(userId: UserId) {
|
||||
const evaluator$ = this.policy.getAll$(PolicyType.PasswordGenerator, userId).pipe(
|
||||
reduceCollection(preferPassword, DisabledGeneratorNavigationPolicy),
|
||||
map((policy) => new GeneratorNavigationEvaluator(policy)),
|
||||
);
|
||||
|
||||
return evaluator$;
|
||||
}
|
||||
|
||||
/** Enforces the policy on the given options
|
||||
* @param userId: Identifies the user making the request
|
||||
* @param options the options to enforce the policy on
|
||||
* @returns a new instance of the options with the policy enforced
|
||||
*/
|
||||
async enforcePolicy(userId: UserId, options: GeneratorNavigation) {
|
||||
const evaluator = await firstValueFrom(this.evaluator$(userId));
|
||||
const applied = evaluator.applyPolicy(options);
|
||||
const sanitized = evaluator.sanitize(applied);
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
/** Saves the navigation options to disk.
|
||||
* @param userId: Identifies the user making the request
|
||||
* @param options the options to save
|
||||
* @returns a promise that resolves when the options are saved
|
||||
*/
|
||||
async saveOptions(userId: UserId, options: GeneratorNavigation): Promise<void> {
|
||||
await this.stateProvider.setUserState(GENERATOR_SETTINGS, options, userId);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import { DefaultGeneratorNavigation } from "./generator-navigation";
|
||||
import { GeneratorNavigationEvaluator } from "./generator-navigation-evaluator";
|
||||
|
||||
describe("GeneratorNavigationEvaluator", () => {
|
||||
describe("policyInEffect", () => {
|
||||
it.each([["passphrase"], ["password"]] as const)(
|
||||
"returns true if the policy has a defaultType (= %p)",
|
||||
(defaultType) => {
|
||||
const evaluator = new GeneratorNavigationEvaluator({ defaultType });
|
||||
|
||||
expect(evaluator.policyInEffect).toEqual(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([[undefined], [null], ["" as any]])(
|
||||
"returns false if the policy has a falsy defaultType (= %p)",
|
||||
(defaultType) => {
|
||||
const evaluator = new GeneratorNavigationEvaluator({ defaultType });
|
||||
|
||||
expect(evaluator.policyInEffect).toEqual(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("applyPolicy", () => {
|
||||
it("returns the input options", () => {
|
||||
const evaluator = new GeneratorNavigationEvaluator(null);
|
||||
const options = { type: "password" as const };
|
||||
|
||||
const result = evaluator.applyPolicy(options);
|
||||
|
||||
expect(result).toEqual(options);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitize", () => {
|
||||
it.each([["passphrase"], ["password"]] as const)(
|
||||
"defaults options to the policy's default type (= %p) when a policy is in effect",
|
||||
(defaultType) => {
|
||||
const evaluator = new GeneratorNavigationEvaluator({ defaultType });
|
||||
|
||||
const result = evaluator.sanitize({});
|
||||
|
||||
expect(result).toEqual({ type: defaultType });
|
||||
},
|
||||
);
|
||||
|
||||
it("defaults options to the default generator navigation type when a policy is not in effect", () => {
|
||||
const evaluator = new GeneratorNavigationEvaluator(null);
|
||||
|
||||
const result = evaluator.sanitize({});
|
||||
|
||||
expect(result.type).toEqual(DefaultGeneratorNavigation.type);
|
||||
});
|
||||
|
||||
it("retains the options type when it is set", () => {
|
||||
const evaluator = new GeneratorNavigationEvaluator({ defaultType: "passphrase" });
|
||||
|
||||
const result = evaluator.sanitize({ type: "password" });
|
||||
|
||||
expect(result).toEqual({ type: "password" });
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
import { PolicyEvaluator } from "../abstractions";
|
||||
|
||||
import { DefaultGeneratorNavigation, GeneratorNavigation } from "./generator-navigation";
|
||||
import { GeneratorNavigationPolicy } from "./generator-navigation-policy";
|
||||
|
||||
/** Enforces policy for generator navigation options.
|
||||
*/
|
||||
export class GeneratorNavigationEvaluator
|
||||
implements PolicyEvaluator<GeneratorNavigationPolicy, GeneratorNavigation>
|
||||
{
|
||||
/** Instantiates the evaluator.
|
||||
* @param policy The policy applied by the evaluator. When this conflicts with
|
||||
* the defaults, the policy takes precedence.
|
||||
*/
|
||||
constructor(readonly policy: GeneratorNavigationPolicy) {}
|
||||
|
||||
/** {@link PolicyEvaluator.policyInEffect} */
|
||||
get policyInEffect(): boolean {
|
||||
return this.policy?.defaultType ? true : false;
|
||||
}
|
||||
|
||||
/** Apply policy to the input options.
|
||||
* @param options The options to build from. These options are not altered.
|
||||
* @returns A new password generation request with policy applied.
|
||||
*/
|
||||
applyPolicy(options: GeneratorNavigation): GeneratorNavigation {
|
||||
return options;
|
||||
}
|
||||
|
||||
/** Ensures internal options consistency.
|
||||
* @param options The options to cascade. These options are not altered.
|
||||
* @returns A passphrase generation request with cascade applied.
|
||||
*/
|
||||
sanitize(options: GeneratorNavigation): GeneratorNavigation {
|
||||
const defaultType = this.policyInEffect
|
||||
? this.policy.defaultType
|
||||
: DefaultGeneratorNavigation.type;
|
||||
return {
|
||||
...options,
|
||||
type: options.type ?? defaultType,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { PolicyId } from "../../../types/guid";
|
||||
|
||||
import { DisabledGeneratorNavigationPolicy, preferPassword } from "./generator-navigation-policy";
|
||||
|
||||
function createPolicy(
|
||||
data: any,
|
||||
type: PolicyType = PolicyType.PasswordGenerator,
|
||||
enabled: boolean = true,
|
||||
) {
|
||||
return new Policy({
|
||||
id: "id" as PolicyId,
|
||||
organizationId: "organizationId",
|
||||
data,
|
||||
enabled,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
describe("leastPrivilege", () => {
|
||||
it("should return the accumulator when the policy type does not apply", () => {
|
||||
const policy = createPolicy({}, PolicyType.RequireSso);
|
||||
|
||||
const result = preferPassword(DisabledGeneratorNavigationPolicy, policy);
|
||||
|
||||
expect(result).toEqual(DisabledGeneratorNavigationPolicy);
|
||||
});
|
||||
|
||||
it("should return the accumulator when the policy is not enabled", () => {
|
||||
const policy = createPolicy({}, PolicyType.PasswordGenerator, false);
|
||||
|
||||
const result = preferPassword(DisabledGeneratorNavigationPolicy, policy);
|
||||
|
||||
expect(result).toEqual(DisabledGeneratorNavigationPolicy);
|
||||
});
|
||||
|
||||
it("should take the %p from the policy", () => {
|
||||
const policy = createPolicy({ defaultType: "passphrase" });
|
||||
|
||||
const result = preferPassword({ ...DisabledGeneratorNavigationPolicy }, policy);
|
||||
|
||||
expect(result).toEqual({ defaultType: "passphrase" });
|
||||
});
|
||||
|
||||
it("should override passphrase with password", () => {
|
||||
const policy = createPolicy({ defaultType: "password" });
|
||||
|
||||
const result = preferPassword({ defaultType: "passphrase" }, policy);
|
||||
|
||||
expect(result).toEqual({ defaultType: "password" });
|
||||
});
|
||||
|
||||
it("should not override password", () => {
|
||||
const policy = createPolicy({ defaultType: "passphrase" });
|
||||
|
||||
const result = preferPassword({ defaultType: "password" }, policy);
|
||||
|
||||
expect(result).toEqual({ defaultType: "password" });
|
||||
});
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { GeneratorType } from "../generator-type";
|
||||
|
||||
/** Policy settings affecting password generator navigation */
|
||||
export type GeneratorNavigationPolicy = {
|
||||
/** The type of generator that should be shown by default when opening
|
||||
* the password generator.
|
||||
*/
|
||||
defaultType?: GeneratorType;
|
||||
};
|
||||
|
||||
/** Reduces a policy into an accumulator by preferring the password generator
|
||||
* type to other generator types.
|
||||
* @param acc the accumulator
|
||||
* @param policy the policy to reduce
|
||||
* @returns the resulting `GeneratorNavigationPolicy`
|
||||
*/
|
||||
export function preferPassword(
|
||||
acc: GeneratorNavigationPolicy,
|
||||
policy: Policy,
|
||||
): GeneratorNavigationPolicy {
|
||||
const isEnabled = policy.type === PolicyType.PasswordGenerator && policy.enabled;
|
||||
if (!isEnabled) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const isOverridable = acc.defaultType !== "password" && policy.data.defaultType;
|
||||
const result = isOverridable ? { ...acc, defaultType: policy.data.defaultType } : acc;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The default options for password generation policy. */
|
||||
export const DisabledGeneratorNavigationPolicy: GeneratorNavigationPolicy = Object.freeze({
|
||||
defaultType: undefined,
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { GeneratorType } from "../generator-type";
|
||||
import { ForwarderId } from "../username/options";
|
||||
import { UsernameGeneratorType } from "../username/options/generator-options";
|
||||
|
||||
/** Stores credential generator UI state. */
|
||||
|
||||
export type GeneratorNavigation = {
|
||||
/** The kind of credential being generated.
|
||||
* @remarks The legacy generator only supports "password" and "passphrase".
|
||||
* The componentized generator supports all values.
|
||||
*/
|
||||
type?: GeneratorType;
|
||||
|
||||
/** When `type === "username"`, this stores the username algorithm. */
|
||||
username?: UsernameGeneratorType;
|
||||
|
||||
/** When `username === "forwarded"`, this stores the forwarder implementation. */
|
||||
forwarder?: ForwarderId | "";
|
||||
};
|
||||
/** The default options for password generation. */
|
||||
|
||||
export const DefaultGeneratorNavigation: Partial<GeneratorNavigation> = Object.freeze({
|
||||
type: "password",
|
||||
username: "word",
|
||||
forwarder: "",
|
||||
});
|
3
libs/common/src/tools/generator/navigation/index.ts
Normal file
3
libs/common/src/tools/generator/navigation/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { GeneratorNavigationEvaluator } from "./generator-navigation-evaluator";
|
||||
export { DefaultGeneratorNavigationService } from "./default-generator-navigation.service";
|
||||
export { GeneratorNavigation, DefaultGeneratorNavigation } from "./generator-navigation";
|
@ -2,4 +2,7 @@
|
||||
export { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
|
||||
export { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
|
||||
export { PassphraseGeneratorStrategy } from "./passphrase-generator-strategy";
|
||||
export { DefaultPassphraseGenerationOptions } from "./passphrase-generation-options";
|
||||
export {
|
||||
DefaultPassphraseGenerationOptions,
|
||||
PassphraseGenerationOptions,
|
||||
} from "./passphrase-generation-options";
|
||||
|
@ -2,7 +2,6 @@
|
||||
* include structuredClone in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of, firstValueFrom } from "rxjs";
|
||||
|
||||
@ -12,12 +11,16 @@ import { PolicyType } from "../../../admin-console/enums";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PasswordGenerationServiceAbstraction } from "../abstractions/password-generation.service.abstraction";
|
||||
import { PASSPHRASE_SETTINGS } from "../key-definitions";
|
||||
import { PasswordGenerationServiceAbstraction } from "../password/password-generation.service.abstraction";
|
||||
|
||||
import { DisabledPassphraseGeneratorPolicy } from "./passphrase-generator-policy";
|
||||
|
||||
import { PassphraseGeneratorOptionsEvaluator, PassphraseGeneratorStrategy } from ".";
|
||||
import {
|
||||
DefaultPassphraseGenerationOptions,
|
||||
PassphraseGeneratorOptionsEvaluator,
|
||||
PassphraseGeneratorStrategy,
|
||||
} from ".";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
@ -71,6 +74,16 @@ describe("Password generation strategy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new PassphraseGeneratorStrategy(null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultPassphraseGenerationOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
import { BehaviorSubject, map, pipe } from "rxjs";
|
||||
|
||||
import { GeneratorStrategy } from "..";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PasswordGenerationServiceAbstraction } from "../abstractions/password-generation.service.abstraction";
|
||||
import { PASSPHRASE_SETTINGS } from "../key-definitions";
|
||||
import { PasswordGenerationServiceAbstraction } from "../password/password-generation.service.abstraction";
|
||||
import { reduceCollection } from "../reduce-collection.operator";
|
||||
|
||||
import { PassphraseGenerationOptions } from "./passphrase-generation-options";
|
||||
import {
|
||||
PassphraseGenerationOptions,
|
||||
DefaultPassphraseGenerationOptions,
|
||||
} from "./passphrase-generation-options";
|
||||
import { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
|
||||
import {
|
||||
DisabledPassphraseGeneratorPolicy,
|
||||
@ -36,6 +39,11 @@ export class PassphraseGeneratorStrategy
|
||||
return this.stateProvider.getUser(id, PASSPHRASE_SETTINGS);
|
||||
}
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$(_: UserId) {
|
||||
return new BehaviorSubject({ ...DefaultPassphraseGenerationOptions }).asObservable();
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.policy} */
|
||||
get policy() {
|
||||
return PolicyType.PasswordGenerator;
|
||||
|
@ -6,6 +6,6 @@ export { PasswordGeneratorStrategy } from "./password-generator-strategy";
|
||||
|
||||
// legacy interfaces
|
||||
export { PasswordGeneratorOptions } from "./password-generator-options";
|
||||
export { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
export { PasswordGenerationServiceAbstraction } from "../abstractions/password-generation.service.abstraction";
|
||||
export { PasswordGenerationService } from "./password-generation.service";
|
||||
export { GeneratedPasswordHistory } from "./generated-password-history";
|
||||
|
@ -5,10 +5,10 @@ import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import { EFFLongWordList } from "../../../platform/misc/wordlist";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { PasswordGenerationServiceAbstraction } from "../abstractions/password-generation.service.abstraction";
|
||||
import { PassphraseGeneratorOptionsEvaluator } from "../passphrase/passphrase-generator-options-evaluator";
|
||||
|
||||
import { GeneratedPasswordHistory } from "./generated-password-history";
|
||||
import { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
import { PasswordGeneratorOptions } from "./password-generator-options";
|
||||
import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator";
|
||||
|
||||
@ -341,24 +341,6 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId });
|
||||
}
|
||||
|
||||
normalizeOptions(
|
||||
options: PasswordGeneratorOptions,
|
||||
enforcedPolicyOptions: PasswordGeneratorPolicyOptions,
|
||||
) {
|
||||
const evaluator =
|
||||
options.type == "password"
|
||||
? new PasswordGeneratorOptionsEvaluator(enforcedPolicyOptions)
|
||||
: new PassphraseGeneratorOptionsEvaluator(enforcedPolicyOptions);
|
||||
|
||||
const evaluatedOptions = evaluator.applyPolicy(options);
|
||||
const santizedOptions = evaluator.sanitize(evaluatedOptions);
|
||||
|
||||
// callers assume this function updates the options parameter
|
||||
Object.assign(options, santizedOptions);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private capitalize(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { GeneratorNavigation } from "../navigation/generator-navigation";
|
||||
import { PassphraseGenerationOptions } from "../passphrase/passphrase-generation-options";
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
@ -6,12 +7,5 @@ import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
* This type includes all properties suitable for reactive data binding.
|
||||
*/
|
||||
export type PasswordGeneratorOptions = PasswordGenerationOptions &
|
||||
PassphraseGenerationOptions & {
|
||||
/** The algorithm to use for credential generation.
|
||||
* Properties on @see PasswordGenerationOptions should be processed
|
||||
* only when `type === "password"`.
|
||||
* Properties on @see PassphraseGenerationOptions should be processed
|
||||
* only when `type === "passphrase"`.
|
||||
*/
|
||||
type?: "password" | "passphrase";
|
||||
};
|
||||
PassphraseGenerationOptions &
|
||||
GeneratorNavigation;
|
||||
|
@ -17,6 +17,7 @@ import { PASSWORD_SETTINGS } from "../key-definitions";
|
||||
import { DisabledPasswordGeneratorPolicy } from "./password-generator-policy";
|
||||
|
||||
import {
|
||||
DefaultPasswordGenerationOptions,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
PasswordGeneratorStrategy,
|
||||
@ -82,6 +83,16 @@ describe("Password generation strategy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultPasswordGenerationOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
import { BehaviorSubject, map, pipe } from "rxjs";
|
||||
|
||||
import { GeneratorStrategy } from "..";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PasswordGenerationServiceAbstraction } from "../abstractions/password-generation.service.abstraction";
|
||||
import { PASSWORD_SETTINGS } from "../key-definitions";
|
||||
import { reduceCollection } from "../reduce-collection.operator";
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
import { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
import {
|
||||
DefaultPasswordGenerationOptions,
|
||||
PasswordGenerationOptions,
|
||||
} from "./password-generation-options";
|
||||
import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator";
|
||||
import {
|
||||
DisabledPasswordGeneratorPolicy,
|
||||
@ -35,6 +38,11 @@ export class PasswordGeneratorStrategy
|
||||
return this.stateProvider.getUser(id, PASSWORD_SETTINGS);
|
||||
}
|
||||
|
||||
/** Gets the default options. */
|
||||
defaults$(_: UserId) {
|
||||
return new BehaviorSubject({ ...DefaultPasswordGenerationOptions }).asObservable();
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.policy} */
|
||||
get policy() {
|
||||
return PolicyType.PasswordGenerator;
|
||||
|
@ -1,10 +1,21 @@
|
||||
import { RequestOptions } from "./options/forwarder-options";
|
||||
import { UsernameGenerationMode } from "./options/generator-options";
|
||||
|
||||
/** Settings supported when generating an email subaddress */
|
||||
export type CatchallGenerationOptions = {
|
||||
type?: "random" | "website-name";
|
||||
domain?: string;
|
||||
};
|
||||
/** selects the generation algorithm for the catchall email address. */
|
||||
catchallType?: UsernameGenerationMode;
|
||||
|
||||
/** The default options for email subaddress generation. */
|
||||
export const DefaultCatchallOptions: Partial<CatchallGenerationOptions> = Object.freeze({
|
||||
type: "random",
|
||||
/** The domain part of the generated email address.
|
||||
* @example If the domain is `domain.io` and the generated username
|
||||
* is `jd`, then the generated email address will be `jd@mydomain.io`
|
||||
*/
|
||||
catchallDomain?: string;
|
||||
} & RequestOptions;
|
||||
|
||||
/** The default options for catchall address generation. */
|
||||
export const DefaultCatchallOptions: CatchallGenerationOptions = Object.freeze({
|
||||
catchallType: "random",
|
||||
catchallDomain: "",
|
||||
website: null,
|
||||
});
|
||||
|
@ -10,6 +10,8 @@ import { UserId } from "../../../types/guid";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { CATCHALL_SETTINGS } from "../key-definitions";
|
||||
|
||||
import { CatchallGenerationOptions, DefaultCatchallOptions } from "./catchall-generator-options";
|
||||
|
||||
import { CatchallGeneratorStrategy, UsernameGenerationServiceAbstraction } from ".";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
@ -47,6 +49,16 @@ describe("Email subaddress list generation strategy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new CatchallGeneratorStrategy(null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultCatchallOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
@ -70,16 +82,14 @@ describe("Email subaddress list generation strategy", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
const strategy = new CatchallGeneratorStrategy(legacy, null);
|
||||
const options = {
|
||||
type: "website-name" as const,
|
||||
domain: "example.com",
|
||||
};
|
||||
catchallType: "website-name",
|
||||
catchallDomain: "example.com",
|
||||
website: "foo.com",
|
||||
} as CatchallGenerationOptions;
|
||||
|
||||
await strategy.generate(options);
|
||||
|
||||
expect(legacy.generateCatchall).toHaveBeenCalledWith({
|
||||
catchallType: "website-name" as const,
|
||||
catchallDomain: "example.com",
|
||||
});
|
||||
expect(legacy.generateCatchall).toHaveBeenCalledWith(options);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
import { BehaviorSubject, map, pipe } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorStrategy } from "../abstractions";
|
||||
import { UsernameGenerationServiceAbstraction } from "../abstractions/username-generation.service.abstraction";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { CATCHALL_SETTINGS } from "../key-definitions";
|
||||
import { NoPolicy } from "../no-policy";
|
||||
|
||||
import { CatchallGenerationOptions } from "./catchall-generator-options";
|
||||
import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
||||
import { CatchallGenerationOptions, DefaultCatchallOptions } from "./catchall-generator-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
@ -30,6 +30,11 @@ export class CatchallGeneratorStrategy
|
||||
return this.stateProvider.getUser(id, CATCHALL_SETTINGS);
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.defaults$} */
|
||||
defaults$(userId: UserId) {
|
||||
return new BehaviorSubject({ ...DefaultCatchallOptions }).asObservable();
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.policy} */
|
||||
get policy() {
|
||||
// Uses password generator since there aren't policies
|
||||
@ -49,9 +54,6 @@ export class CatchallGeneratorStrategy
|
||||
|
||||
/** {@link GeneratorStrategy.generate} */
|
||||
generate(options: CatchallGenerationOptions) {
|
||||
return this.usernameService.generateCatchall({
|
||||
catchallDomain: options.domain,
|
||||
catchallType: options.type,
|
||||
});
|
||||
return this.usernameService.generateCatchall(options);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
/** Settings supported when generating an ASCII username */
|
||||
import { RequestOptions } from "./options/forwarder-options";
|
||||
|
||||
/** Settings supported when generating a username using the EFF word list */
|
||||
export type EffUsernameGenerationOptions = {
|
||||
/** when true, the word is capitalized */
|
||||
wordCapitalize?: boolean;
|
||||
|
||||
/** when true, a random number is appended to the username */
|
||||
wordIncludeNumber?: boolean;
|
||||
};
|
||||
} & RequestOptions;
|
||||
|
||||
/** The default options for EFF long word generation. */
|
||||
export const DefaultEffUsernameOptions: Partial<EffUsernameGenerationOptions> = Object.freeze({
|
||||
export const DefaultEffUsernameOptions: EffUsernameGenerationOptions = Object.freeze({
|
||||
wordCapitalize: false,
|
||||
wordIncludeNumber: false,
|
||||
website: null,
|
||||
});
|
||||
|
@ -10,6 +10,8 @@ import { UserId } from "../../../types/guid";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { EFF_USERNAME_SETTINGS } from "../key-definitions";
|
||||
|
||||
import { DefaultEffUsernameOptions } from "./eff-username-generator-options";
|
||||
|
||||
import { EffUsernameGeneratorStrategy, UsernameGenerationServiceAbstraction } from ".";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
@ -47,6 +49,16 @@ describe("EFF long word list generation strategy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new EffUsernameGeneratorStrategy(null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultEffUsernameOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
@ -72,6 +84,7 @@ describe("EFF long word list generation strategy", () => {
|
||||
const options = {
|
||||
wordCapitalize: false,
|
||||
wordIncludeNumber: false,
|
||||
website: null as string,
|
||||
};
|
||||
|
||||
await strategy.generate(options);
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
import { BehaviorSubject, map, pipe } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorStrategy } from "../abstractions";
|
||||
import { UsernameGenerationServiceAbstraction } from "../abstractions/username-generation.service.abstraction";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { EFF_USERNAME_SETTINGS } from "../key-definitions";
|
||||
import { NoPolicy } from "../no-policy";
|
||||
|
||||
import { EffUsernameGenerationOptions } from "./eff-username-generator-options";
|
||||
import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
||||
import {
|
||||
DefaultEffUsernameOptions,
|
||||
EffUsernameGenerationOptions,
|
||||
} from "./eff-username-generator-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
@ -30,6 +33,11 @@ export class EffUsernameGeneratorStrategy
|
||||
return this.stateProvider.getUser(id, EFF_USERNAME_SETTINGS);
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.defaults$} */
|
||||
defaults$(userId: UserId) {
|
||||
return new BehaviorSubject({ ...DefaultEffUsernameOptions }).asObservable();
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.policy} */
|
||||
get policy() {
|
||||
// Uses password generator since there aren't policies
|
||||
|
@ -15,6 +15,7 @@ import { DUCK_DUCK_GO_FORWARDER } from "../key-definitions";
|
||||
import { SecretState } from "../state/secret-state";
|
||||
|
||||
import { ForwarderGeneratorStrategy } from "./forwarder-generator-strategy";
|
||||
import { DefaultDuckDuckGoOptions } from "./forwarders/duck-duck-go";
|
||||
import { ApiOptions } from "./options/forwarder-options";
|
||||
|
||||
class TestForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
@ -30,6 +31,10 @@ class TestForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
// arbitrary.
|
||||
return DUCK_DUCK_GO_FORWARDER;
|
||||
}
|
||||
|
||||
defaults$ = (userId: UserId) => {
|
||||
return of(DefaultDuckDuckGoOptions);
|
||||
};
|
||||
}
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { map, pipe } from "rxjs";
|
||||
import { Observable, map, pipe } from "rxjs";
|
||||
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
@ -79,6 +79,9 @@ export abstract class ForwarderGeneratorStrategy<
|
||||
return new UserKeyEncryptor(this.encryptService, this.keyService, packer);
|
||||
}
|
||||
|
||||
/** Gets the default options. */
|
||||
abstract defaults$: (userId: UserId) => Observable<Options>;
|
||||
|
||||
/** Determine where forwarder configuration is stored */
|
||||
protected abstract readonly key: KeyDefinition<Options>;
|
||||
|
||||
|
@ -2,12 +2,17 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { ADDY_IO_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { AddyIoForwarder } from "./addy-io";
|
||||
import { AddyIoForwarder, DefaultAddyIoOptions } from "./addy-io";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
describe("Addy.io Forwarder", () => {
|
||||
it("key returns the Addy IO forwarder key", () => {
|
||||
const forwarder = new AddyIoForwarder(null, null, null, null, null);
|
||||
@ -15,6 +20,16 @@ describe("Addy.io Forwarder", () => {
|
||||
expect(forwarder.key).toBe(ADDY_IO_FORWARDER);
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new AddyIoForwarder(null, null, null, null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultAddyIoOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||
const apiService = mockApiService(200, {});
|
||||
|
@ -1,13 +1,23 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { ADDY_IO_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { EmailDomainOptions, SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
|
||||
export const DefaultAddyIoOptions: SelfHostedApiOptions & EmailDomainOptions = Object.freeze({
|
||||
website: null,
|
||||
baseUrl: "https://app.addy.io",
|
||||
token: "",
|
||||
domain: "",
|
||||
});
|
||||
|
||||
/** Generates a forwarding address for addy.io (formerly anon addy) */
|
||||
export class AddyIoForwarder extends ForwarderGeneratorStrategy<
|
||||
SelfHostedApiOptions & EmailDomainOptions
|
||||
@ -34,6 +44,11 @@ export class AddyIoForwarder extends ForwarderGeneratorStrategy<
|
||||
return ADDY_IO_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.defaults$} */
|
||||
defaults$ = (userId: UserId) => {
|
||||
return new BehaviorSubject({ ...DefaultAddyIoOptions });
|
||||
};
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: SelfHostedApiOptions & EmailDomainOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
@ -91,3 +106,10 @@ export class AddyIoForwarder extends ForwarderGeneratorStrategy<
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const DefaultOptions = Object.freeze({
|
||||
website: null,
|
||||
baseUrl: "https://app.addy.io",
|
||||
domain: "",
|
||||
token: "",
|
||||
});
|
||||
|
@ -2,12 +2,17 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { DuckDuckGoForwarder } from "./duck-duck-go";
|
||||
import { DuckDuckGoForwarder, DefaultDuckDuckGoOptions } from "./duck-duck-go";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
describe("DuckDuckGo Forwarder", () => {
|
||||
it("key returns the Duck Duck Go forwarder key", () => {
|
||||
const forwarder = new DuckDuckGoForwarder(null, null, null, null, null);
|
||||
@ -15,6 +20,16 @@ describe("DuckDuckGo Forwarder", () => {
|
||||
expect(forwarder.key).toBe(DUCK_DUCK_GO_FORWARDER);
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new DuckDuckGoForwarder(null, null, null, null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultDuckDuckGoOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||
const apiService = mockApiService(200, {});
|
||||
|
@ -1,13 +1,21 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
export const DefaultDuckDuckGoOptions: ApiOptions = Object.freeze({
|
||||
website: null,
|
||||
token: "",
|
||||
});
|
||||
|
||||
/** Generates a forwarding address for DuckDuckGo */
|
||||
export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
/** Instantiates the forwarder
|
||||
@ -32,6 +40,11 @@ export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions>
|
||||
return DUCK_DUCK_GO_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.defaults$} */
|
||||
defaults$ = (userId: UserId) => {
|
||||
return new BehaviorSubject({ ...DefaultDuckDuckGoOptions });
|
||||
};
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions): Promise<string> => {
|
||||
if (!options.token || options.token === "") {
|
||||
@ -68,3 +81,8 @@ export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions>
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const DefaultOptions = Object.freeze({
|
||||
website: null,
|
||||
token: "",
|
||||
});
|
||||
|
@ -2,13 +2,18 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { FASTMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { FastmailForwarder } from "./fastmail";
|
||||
import { FastmailForwarder, DefaultFastmailOptions } from "./fastmail";
|
||||
import { mockI18nService } from "./mocks.jest";
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
|
||||
type MockResponse = { status: number; body: any };
|
||||
|
||||
// fastmail calls nativeFetch first to resolve the accountId,
|
||||
@ -52,6 +57,16 @@ describe("Fastmail Forwarder", () => {
|
||||
expect(forwarder.key).toBe(FASTMAIL_FORWARDER);
|
||||
});
|
||||
|
||||
describe("defaults$", () => {
|
||||
it("should return the default subaddress options", async () => {
|
||||
const strategy = new FastmailForwarder(null, null, null, null, null);
|
||||
|
||||
const result = await firstValueFrom(strategy.defaults$(SomeUser));
|
||||
|
||||
expect(result).toEqual(DefaultFastmailOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||
const apiService = mockApiService(AccountIdSuccess, EmptyResponse);
|
||||
|
@ -1,13 +1,23 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { UserId } from "../../../../types/guid";
|
||||
import { FASTMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { EmailPrefixOptions, ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
export const DefaultFastmailOptions: ApiOptions & EmailPrefixOptions = Object.freeze({
|
||||
website: null,
|
||||
domain: "",
|
||||
prefix: "",
|
||||
token: "",
|
||||
});
|
||||
|
||||
/** Generates a forwarding address for Fastmail */
|
||||
export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & EmailPrefixOptions> {
|
||||
/** Instantiates the forwarder
|
||||
@ -32,6 +42,11 @@ export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & E
|
||||
return FASTMAIL_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.defaults$} */
|
||||
defaults$ = (userId: UserId) => {
|
||||
return new BehaviorSubject({ ...DefaultFastmailOptions });
|
||||
};
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions & EmailPrefixOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
@ -141,3 +156,10 @@ export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & E
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultOptions = Object.freeze({
|
||||
website: null,
|
||||
domain: "",
|
||||
prefix: "",
|
||||
token: "",
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user