mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 17:13:24 +00:00
Merge branch 'main' into feature/passkey-provider
This commit is contained in:
@@ -20,10 +20,8 @@ export default class TabsBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.updateCurrentTabData();
|
||||
this.setupTabEventListeners();
|
||||
void this.updateCurrentTabData();
|
||||
void this.setupTabEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { EVENTS } from "@bitwarden/common/autofill/constants";
|
||||
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
@@ -122,7 +120,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
* @param {AutofillExtensionMessage} message
|
||||
*/
|
||||
private async fillForm({ fillScript, pageDetailsUrl }: AutofillExtensionMessage) {
|
||||
if ((document.defaultView || window).location.href !== pageDetailsUrl) {
|
||||
if ((document.defaultView || window).location.href !== pageDetailsUrl || !fillScript) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,7 +175,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||
message: AutofillExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
sendResponse: (response?: any) => void,
|
||||
): boolean => {
|
||||
): boolean | null => {
|
||||
const command: string = message.command;
|
||||
const handler: CallableFunction | undefined = this.getExtensionMessageHandler(command);
|
||||
if (!handler) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { setupExtensionDisconnectAction } from "../utils";
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
@@ -9,7 +7,7 @@ if (document.readyState === "loading") {
|
||||
}
|
||||
|
||||
function loadAutofiller() {
|
||||
let pageHref: string = null;
|
||||
let pageHref: null | string = null;
|
||||
let filledThisHref = false;
|
||||
let delayFillTimeout: number;
|
||||
let doFillInterval: number | NodeJS.Timeout;
|
||||
@@ -51,9 +49,7 @@ function loadAutofiller() {
|
||||
sender: "autofiller",
|
||||
};
|
||||
|
||||
// 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(msg);
|
||||
void chrome.runtime.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
@@ -11,7 +9,7 @@ import AutofillInit from "./autofill-init";
|
||||
|
||||
(function (windowContext) {
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
let inlineMenuContentService: AutofillInlineMenuContentService;
|
||||
let inlineMenuContentService: undefined | AutofillInlineMenuContentService;
|
||||
if (globalThis.self === globalThis.top) {
|
||||
inlineMenuContentService = new AutofillInlineMenuContentService();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
@@ -20,7 +18,7 @@ import AutofillInit from "./autofill-init";
|
||||
inlineMenuFieldQualificationService,
|
||||
);
|
||||
|
||||
let overlayNotificationsContentService: OverlayNotificationsContentService;
|
||||
let overlayNotificationsContentService: undefined | OverlayNotificationsContentService;
|
||||
if (globalThis.self === globalThis.top) {
|
||||
overlayNotificationsContentService = new OverlayNotificationsContentService();
|
||||
}
|
||||
@@ -29,7 +27,7 @@ import AutofillInit from "./autofill-init";
|
||||
domQueryService,
|
||||
domElementVisibilityService,
|
||||
autofillOverlayContentService,
|
||||
null,
|
||||
undefined,
|
||||
overlayNotificationsContentService,
|
||||
);
|
||||
setupAutofillInitDisconnectAction(windowContext);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
|
||||
import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
@@ -12,8 +10,8 @@ import AutofillInit from "./autofill-init";
|
||||
|
||||
(function (windowContext) {
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
let inlineMenuContentService: AutofillInlineMenuContentService;
|
||||
let overlayNotificationsContentService: OverlayNotificationsContentService;
|
||||
let inlineMenuContentService: undefined | AutofillInlineMenuContentService;
|
||||
let overlayNotificationsContentService: undefined | OverlayNotificationsContentService;
|
||||
if (globalThis.self === globalThis.top) {
|
||||
inlineMenuContentService = new AutofillInlineMenuContentService();
|
||||
overlayNotificationsContentService = new OverlayNotificationsContentService();
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import path, { dirname, join } from "path";
|
||||
import { createRequire } from "module";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
import type { StorybookConfig } from "@storybook/web-components-webpack5";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
const currentDirectory = dirname(currentFile);
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const getAbsolutePath = (value: string): string =>
|
||||
dirname(require.resolve(join(value, "package.json")));
|
||||
|
||||
@@ -43,7 +50,7 @@ const config: StorybookConfig = {
|
||||
if (config.resolve) {
|
||||
config.resolve.plugins = [
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: path.resolve(__dirname, "../../../../../tsconfig.json"),
|
||||
configFile: resolve(currentDirectory, "../../../../../tsconfig.json"),
|
||||
}),
|
||||
] as any;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Spinner } from "../icons";
|
||||
|
||||
export type ActionButtonProps = {
|
||||
buttonText: string | TemplateResult;
|
||||
dataTestId?: string;
|
||||
disabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
theme: Theme;
|
||||
@@ -17,6 +18,7 @@ export type ActionButtonProps = {
|
||||
|
||||
export function ActionButton({
|
||||
buttonText,
|
||||
dataTestId,
|
||||
disabled = false,
|
||||
isLoading = false,
|
||||
theme,
|
||||
@@ -32,6 +34,7 @@ export function ActionButton({
|
||||
return html`
|
||||
<button
|
||||
class=${actionButtonStyles({ disabled, fullWidth, isLoading, theme })}
|
||||
data-testid="${dataTestId}"
|
||||
title=${buttonText}
|
||||
type="button"
|
||||
@click=${handleButtonClick}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Meta, StoryObj } from "@storybook/web-components";
|
||||
|
||||
import { ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||
|
||||
import { NotificationTypes } from "../../../../../notification/abstractions/notification-bar";
|
||||
import { getNotificationTestId } from "../../../../../notification/bar";
|
||||
import {
|
||||
AtRiskNotification,
|
||||
AtRiskNotificationProps,
|
||||
@@ -30,8 +32,10 @@ export default {
|
||||
},
|
||||
} as Meta<AtRiskNotificationProps>;
|
||||
|
||||
const Template = (args: AtRiskNotificationProps) => AtRiskNotification({ ...args });
|
||||
|
||||
const Template = (args: AtRiskNotificationProps) => {
|
||||
const notificationTestId = getNotificationTestId(NotificationTypes.AtRiskPassword);
|
||||
return AtRiskNotification({ ...args, notificationTestId });
|
||||
};
|
||||
export const Default: StoryObj<AtRiskNotificationProps> = {
|
||||
render: Template,
|
||||
};
|
||||
|
||||
@@ -18,11 +18,13 @@ export type AtRiskNotificationProps = NotificationBarIframeInitData & {
|
||||
handleCloseNotification: (e: Event) => void;
|
||||
} & {
|
||||
i18n: I18n;
|
||||
notificationTestId: string;
|
||||
};
|
||||
|
||||
export function AtRiskNotification({
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
notificationTestId,
|
||||
theme = ThemeTypes.Light,
|
||||
params,
|
||||
}: AtRiskNotificationProps) {
|
||||
@@ -33,7 +35,7 @@ export function AtRiskNotification({
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class=${atRiskNotificationContainerStyles(theme)}>
|
||||
<div data-testid="${notificationTestId}" class=${atRiskNotificationContainerStyles(theme)}>
|
||||
${NotificationHeader({
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
|
||||
@@ -26,6 +26,7 @@ export function AtRiskNotificationFooter({
|
||||
open(passwordChangeUri, "_blank");
|
||||
},
|
||||
buttonText: AdditionalTasksButtonContent({ buttonText: i18n.changePassword, theme }),
|
||||
dataTestId: "change-password-button",
|
||||
theme,
|
||||
fullWidth: false,
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
(function () {
|
||||
// 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({ command: "triggerAutofillScriptInjection" });
|
||||
void chrome.runtime.sendMessage({ command: "triggerAutofillScriptInjection" });
|
||||
})();
|
||||
|
||||
@@ -200,7 +200,7 @@ export function getNotificationTestId(
|
||||
[NotificationTypes.Unlock]: "unlock-notification-bar",
|
||||
[NotificationTypes.Add]: "save-notification-bar",
|
||||
[NotificationTypes.Change]: "update-notification-bar",
|
||||
[NotificationTypes.AtRiskPassword]: "at-risk-password-notification-bar",
|
||||
[NotificationTypes.AtRiskPassword]: "at-risk-notification-bar",
|
||||
}[notificationType];
|
||||
}
|
||||
|
||||
@@ -287,6 +287,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
type: notificationBarIframeInitData.type as NotificationType,
|
||||
theme: resolvedTheme,
|
||||
i18n,
|
||||
notificationTestId,
|
||||
params: initData.params,
|
||||
handleCloseNotification,
|
||||
}),
|
||||
|
||||
@@ -213,9 +213,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
this.autofillScriptPortsSet.delete(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
|
||||
this.injectAutofillScriptsInAllTabs();
|
||||
void this.injectAutofillScriptsInAllTabs();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,9 +468,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id);
|
||||
}
|
||||
|
||||
// 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(
|
||||
void BrowserApi.tabSendMessage(
|
||||
tab,
|
||||
{
|
||||
command: options.autoSubmitLogin ? "triggerAutoSubmitLogin" : "fillForm",
|
||||
@@ -502,9 +498,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
);
|
||||
|
||||
if (didAutofill) {
|
||||
// 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.eventCollectionService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id);
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientAutofilled,
|
||||
options.cipher.id,
|
||||
);
|
||||
if (totp !== null) {
|
||||
return totp;
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -15,7 +13,7 @@ const IdleInterval = 60 * 5; // 5 minutes
|
||||
|
||||
export default class IdleBackground {
|
||||
private idle: typeof chrome.idle | typeof browser.idle | null;
|
||||
private idleTimer: number | NodeJS.Timeout = null;
|
||||
private idleTimer: null | number | NodeJS.Timeout = null;
|
||||
private idleState = "active";
|
||||
|
||||
constructor(
|
||||
@@ -80,9 +78,8 @@ export default class IdleBackground {
|
||||
globalThis.clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
// 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.idle.queryState(IdleInterval, (state: string) => {
|
||||
|
||||
void this.idle?.queryState(IdleInterval, (state: string) => {
|
||||
if (state !== this.idleState) {
|
||||
this.idleState = state;
|
||||
handler(state);
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class RuntimeBackground {
|
||||
private platformUtilsService: BrowserPlatformUtilsService,
|
||||
private notificationsService: NotificationsService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private processReloadSerivce: ProcessReloadServiceAbstraction,
|
||||
private processReloadService: ProcessReloadServiceAbstraction,
|
||||
private environmentService: BrowserEnvironmentService,
|
||||
private messagingService: MessagingService,
|
||||
private logService: LogService,
|
||||
@@ -241,7 +241,7 @@ export default class RuntimeBackground {
|
||||
await closeUnlockPopout();
|
||||
}
|
||||
|
||||
this.processReloadSerivce.cancelProcessReload();
|
||||
this.processReloadService.cancelProcessReload();
|
||||
|
||||
if (item) {
|
||||
await BrowserApi.focusWindow(item.commandToRetry.sender.tab.windowId);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
03100CAF291891F4008E14EF /* encrypt-worker.js in Resources */ = {isa = PBXBuildFile; fileRef = 03100CAE291891F4008E14EF /* encrypt-worker.js */; };
|
||||
55BC93932CB4268A008CA4C6 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 55BC93922CB4268A008CA4C6 /* assets */; };
|
||||
55E0374D2577FA6B00979016 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E0374C2577FA6B00979016 /* AppDelegate.swift */; };
|
||||
55E037502577FA6B00979016 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55E0374E2577FA6B00979016 /* Main.storyboard */; };
|
||||
@@ -53,7 +52,6 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
03100CAE291891F4008E14EF /* encrypt-worker.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = "encrypt-worker.js"; path = "../../../build/encrypt-worker.js"; sourceTree = "<group>"; };
|
||||
5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftAppKit.tbd; path = usr/lib/swift/libswiftAppKit.tbd; sourceTree = SDKROOT; };
|
||||
55BC93922CB4268A008CA4C6 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../../build/assets; sourceTree = "<group>"; };
|
||||
55E037482577FA6B00979016 /* desktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -155,7 +153,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
55BC93922CB4268A008CA4C6 /* assets */,
|
||||
03100CAE291891F4008E14EF /* encrypt-worker.js */,
|
||||
55E037702577FA6F00979016 /* popup */,
|
||||
55E037712577FA6F00979016 /* background.js */,
|
||||
55E037722577FA6F00979016 /* images */,
|
||||
@@ -272,7 +269,6 @@
|
||||
55E037802577FA6F00979016 /* background.html in Resources */,
|
||||
55E0377A2577FA6F00979016 /* background.js in Resources */,
|
||||
55E037792577FA6F00979016 /* popup in Resources */,
|
||||
03100CAF291891F4008E14EF /* encrypt-worker.js in Resources */,
|
||||
55BC93932CB4268A008CA4C6 /* assets in Resources */,
|
||||
55E0377C2577FA6F00979016 /* notification in Resources */,
|
||||
55E0377E2577FA6F00979016 /* vendor.js in Resources */,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -163,7 +163,7 @@ describe("OpenAttachmentsComponent", () => {
|
||||
it("sets `cipherIsAPartOfFreeOrg` to true when the cipher is a part of a free organization", async () => {
|
||||
cipherView.organizationId = "888-333-333";
|
||||
org.productTierType = ProductTierType.Free;
|
||||
org.id = cipherView.organizationId;
|
||||
org.id = cipherView.organizationId as OrganizationId;
|
||||
|
||||
await component.ngOnInit();
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("OpenAttachmentsComponent", () => {
|
||||
it("sets `cipherIsAPartOfFreeOrg` to false when the organization is not free", async () => {
|
||||
cipherView.organizationId = "888-333-333";
|
||||
org.productTierType = ProductTierType.Families;
|
||||
org.id = cipherView.organizationId;
|
||||
org.id = cipherView.organizationId as OrganizationId;
|
||||
|
||||
await component.ngOnInit();
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { SelectionReadOnly } from "../selection-read-only";
|
||||
|
||||
export class OrganizationCollectionRequest extends CollectionExport {
|
||||
static template(): OrganizationCollectionRequest {
|
||||
const req = new OrganizationCollectionRequest();
|
||||
req.organizationId = "00000000-0000-0000-0000-000000000000";
|
||||
req.organizationId = "00000000-0000-0000-0000-000000000000" as OrganizationId;
|
||||
req.name = "Collection name";
|
||||
req.externalId = null;
|
||||
req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()];
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::os::windows::ffi::OsStringExt;
|
||||
|
||||
use windows::Win32::Foundation::{GetLastError, HWND};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
BlockInput, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP,
|
||||
KEYEVENTF_UNICODE,
|
||||
SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_UNICODE,
|
||||
VIRTUAL_KEY,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
GetForegroundWindow, GetWindowTextLengthW, GetWindowTextW,
|
||||
@@ -28,21 +28,31 @@ pub fn get_foreground_window_title() -> std::result::Result<String, ()> {
|
||||
///
|
||||
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
|
||||
pub fn type_input(input: Vec<u16>) -> Result<(), ()> {
|
||||
const TAB_KEY: u16 = 9;
|
||||
let mut keyboard_inputs: Vec<INPUT> = Vec::new();
|
||||
|
||||
// Release hotkeys
|
||||
keyboard_inputs.push(build_virtual_key_input(InputKeyPress::Up, 0x12)); // alt
|
||||
keyboard_inputs.push(build_virtual_key_input(InputKeyPress::Up, 0x11)); // ctrl
|
||||
keyboard_inputs.push(build_unicode_input(InputKeyPress::Up, 105)); // i
|
||||
|
||||
for i in input {
|
||||
let next_down_input = build_input(InputKeyPress::Down, i);
|
||||
let next_up_input = build_input(InputKeyPress::Up, i);
|
||||
let next_down_input = if i == TAB_KEY {
|
||||
build_virtual_key_input(InputKeyPress::Down, i as u8)
|
||||
} else {
|
||||
build_unicode_input(InputKeyPress::Down, i)
|
||||
};
|
||||
let next_up_input = if i == TAB_KEY {
|
||||
build_virtual_key_input(InputKeyPress::Up, i as u8)
|
||||
} else {
|
||||
build_unicode_input(InputKeyPress::Up, i)
|
||||
};
|
||||
|
||||
keyboard_inputs.push(next_down_input);
|
||||
keyboard_inputs.push(next_up_input);
|
||||
}
|
||||
|
||||
let _ = block_input(true);
|
||||
let result = send_input(keyboard_inputs);
|
||||
let _ = block_input(false);
|
||||
|
||||
result
|
||||
send_input(keyboard_inputs)
|
||||
}
|
||||
|
||||
/// Gets the foreground window handle.
|
||||
@@ -103,11 +113,11 @@ enum InputKeyPress {
|
||||
Up,
|
||||
}
|
||||
|
||||
/// A function for easily building keyboard INPUT structs used in SendInput().
|
||||
/// A function for easily building keyboard unicode INPUT structs used in SendInput().
|
||||
///
|
||||
/// Before modifying this function, make sure you read the SendInput() documentation:
|
||||
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
|
||||
fn build_input(key_press: InputKeyPress, character: u16) -> INPUT {
|
||||
fn build_unicode_input(key_press: InputKeyPress, character: u16) -> INPUT {
|
||||
match key_press {
|
||||
InputKeyPress::Down => INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
@@ -136,14 +146,37 @@ fn build_input(key_press: InputKeyPress, character: u16) -> INPUT {
|
||||
}
|
||||
}
|
||||
|
||||
/// Block keyboard and mouse input events. This prevents the hotkey
|
||||
/// key presses from interfering with the input sent via SendInput().
|
||||
/// A function for easily building keyboard virtual-key INPUT structs used in SendInput().
|
||||
///
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-blockinput
|
||||
fn block_input(block: bool) -> Result<(), ()> {
|
||||
match unsafe { BlockInput(block) } {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(()),
|
||||
/// Before modifying this function, make sure you read the SendInput() documentation:
|
||||
/// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
fn build_virtual_key_input(key_press: InputKeyPress, virtual_key: u8) -> INPUT {
|
||||
match key_press {
|
||||
InputKeyPress::Down => INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 {
|
||||
ki: KEYBDINPUT {
|
||||
wVk: VIRTUAL_KEY(virtual_key as u16),
|
||||
wScan: Default::default(),
|
||||
dwFlags: Default::default(),
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
InputKeyPress::Up => INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 {
|
||||
ki: KEYBDINPUT {
|
||||
wVk: VIRTUAL_KEY(virtual_key as u16),
|
||||
wScan: Default::default(),
|
||||
dwFlags: KEYEVENTF_KEYUP,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
{{ trashCleanupWarning }}
|
||||
</bit-callout>
|
||||
<app-vault-items
|
||||
#vaultItems
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
} from "@bitwarden/vault";
|
||||
import { OrganizationResellerRenewalWarningComponent } from "@bitwarden/web-vault/app/billing/warnings/components/organization-reseller-renewal-warning.component";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/warnings/services/organization-warnings.service";
|
||||
import { VaultItemsComponent } from "@bitwarden/web-vault/app/vault/components/vault-items/vault-items.component";
|
||||
|
||||
import { BillingNotificationService } from "../../../billing/services/billing-notification.service";
|
||||
import {
|
||||
@@ -204,6 +205,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
protected addAccessStatus$ = new BehaviorSubject<AddAccessStatusType>(0);
|
||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||
|
||||
@ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent<CipherView>;
|
||||
|
||||
private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
|
||||
map((account) => account?.id),
|
||||
switchMap((id) =>
|
||||
@@ -278,9 +281,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
const filter$ = this.routedVaultFilterService.filter$;
|
||||
|
||||
// FIXME: The RoutedVaultFilterModel uses `organizationId: Unassigned` to represent the individual vault,
|
||||
// but that is never used in Admin Console. This function narrows the type so it doesn't pollute our code here,
|
||||
// but really we should change to using our own vault filter model that only represents valid states in AC.
|
||||
const isOrganizationId = (value: OrganizationId | Unassigned): value is OrganizationId =>
|
||||
value !== Unassigned;
|
||||
const organizationId$ = filter$.pipe(
|
||||
map((filter) => filter.organizationId),
|
||||
filter((filter) => filter !== undefined),
|
||||
filter(isOrganizationId),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
@@ -373,9 +383,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.allCollectionsWithoutUnassigned$,
|
||||
]).pipe(
|
||||
map(([organizationId, allCollections]) => {
|
||||
// FIXME: We should not assert that the Unassigned type is a CollectionId.
|
||||
// Instead we should consider representing the Unassigned collection as a different object, given that
|
||||
// it is not actually a collection.
|
||||
const noneCollection = new CollectionAdminView();
|
||||
noneCollection.name = this.i18nService.t("unassigned");
|
||||
noneCollection.id = Unassigned;
|
||||
noneCollection.id = Unassigned as CollectionId;
|
||||
noneCollection.organizationId = organizationId;
|
||||
return allCollections.concat(noneCollection);
|
||||
}),
|
||||
@@ -1420,6 +1433,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
private refresh() {
|
||||
this.refresh$.next();
|
||||
this.vaultItemsComponent?.clearSelection();
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, Input, OnInit } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
|
||||
import { Observable, of } from "rxjs";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export abstract class BasePolicy {
|
||||
abstract name: string;
|
||||
@@ -14,38 +14,56 @@ export abstract class BasePolicy {
|
||||
abstract type: PolicyType;
|
||||
abstract component: any;
|
||||
|
||||
display(organization: Organization) {
|
||||
return true;
|
||||
/**
|
||||
* If true, the description will be reused in the policy edit modal. Set this to false if you
|
||||
* have more complex requirements that you will implement in your template instead.
|
||||
**/
|
||||
showDescription: boolean = true;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return of(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export abstract class BasePolicyComponent implements OnInit {
|
||||
@Input() policyResponse: PolicyResponse;
|
||||
@Input() policy: BasePolicy;
|
||||
@Input() policyResponse: PolicyResponse | undefined;
|
||||
@Input() policy: BasePolicy | undefined;
|
||||
|
||||
enabled = new UntypedFormControl(false);
|
||||
data: UntypedFormGroup = null;
|
||||
data: UntypedFormGroup | undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.enabled.setValue(this.policyResponse.enabled);
|
||||
this.enabled.setValue(this.policyResponse?.enabled);
|
||||
|
||||
if (this.policyResponse.data != null) {
|
||||
if (this.policyResponse?.data != null) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
buildRequest() {
|
||||
const request = new PolicyRequest();
|
||||
request.enabled = this.enabled.value;
|
||||
request.type = this.policy.type;
|
||||
request.data = this.buildRequestData();
|
||||
if (!this.policy) {
|
||||
throw new Error("Policy was not found");
|
||||
}
|
||||
|
||||
const request: PolicyRequest = {
|
||||
type: this.policy.type,
|
||||
enabled: this.enabled.value,
|
||||
data: this.buildRequestData(),
|
||||
};
|
||||
|
||||
return Promise.resolve(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable optional validation before sumitting a respose for policy submission
|
||||
* */
|
||||
confirm(): Promise<boolean> | boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected loadData() {
|
||||
this.data.patchValue(this.policyResponse.data ?? {});
|
||||
this.data?.patchValue(this.policyResponse?.data ?? {});
|
||||
}
|
||||
|
||||
protected buildRequestData() {
|
||||
|
||||
@@ -3,6 +3,7 @@ export { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
export { DisableSendPolicy } from "./disable-send.component";
|
||||
export { MasterPasswordPolicy } from "./master-password.component";
|
||||
export { PasswordGeneratorPolicy } from "./password-generator.component";
|
||||
export { vNextOrganizationDataOwnershipPolicy } from "./vnext-organization-data-ownership.component";
|
||||
export { OrganizationDataOwnershipPolicy } from "./organization-data-ownership.component";
|
||||
export { RequireSsoPolicy } from "./require-sso.component";
|
||||
export { ResetPasswordPolicy } from "./reset-password.component";
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
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";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -9,6 +13,12 @@ export class OrganizationDataOwnershipPolicy extends BasePolicy {
|
||||
description = "personalOwnershipPolicyDesc";
|
||||
type = PolicyType.OrganizationDataOwnership;
|
||||
component = OrganizationDataOwnershipPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService
|
||||
.getFeatureFlag$(FeatureFlag.CreateDefaultLocation)
|
||||
.pipe(map((enabled) => !enabled));
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,38 +1,45 @@
|
||||
<app-header>
|
||||
@let organization = organization$ | async;
|
||||
<button
|
||||
bitBadge
|
||||
class="!tw-align-middle"
|
||||
(click)="changePlan(organization)"
|
||||
*ngIf="isBreadcrumbingEnabled$ | async"
|
||||
slot="title-suffix"
|
||||
type="button"
|
||||
variant="primary"
|
||||
>
|
||||
{{ "upgrade" | i18n }}
|
||||
</button>
|
||||
@if (isBreadcrumbingEnabled$ | async) {
|
||||
<button
|
||||
bitBadge
|
||||
class="!tw-align-middle"
|
||||
(click)="changePlan(organization)"
|
||||
slot="title-suffix"
|
||||
type="button"
|
||||
variant="primary"
|
||||
>
|
||||
{{ "upgrade" | i18n }}
|
||||
</button>
|
||||
}
|
||||
</app-header>
|
||||
|
||||
<bit-container>
|
||||
<ng-container *ngIf="loading">
|
||||
@if (loading) {
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<bit-table *ngIf="!loading">
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let p of policies">
|
||||
<td bitCell *ngIf="p.display(organization)" ngPreserveWhitespaces>
|
||||
<button type="button" bitLink (click)="edit(p)">{{ p.name | i18n }}</button>
|
||||
<span bitBadge variant="success" *ngIf="policiesEnabledMap.get(p.type)">{{
|
||||
"on" | i18n
|
||||
}}</span>
|
||||
<small class="tw-text-muted tw-block">{{ p.description | i18n }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
}
|
||||
@if (!loading) {
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
@for (p of policies; track p.name) {
|
||||
@if (p.display(organization, configService) | async) {
|
||||
<tr bitRow>
|
||||
<td bitCell ngPreserveWhitespaces>
|
||||
<button type="button" bitLink (click)="edit(p)">{{ p.name | i18n }}</button>
|
||||
@if (policiesEnabledMap.get(p.type)) {
|
||||
<span bitBadge variant="success">{{ "on" | i18n }}</span>
|
||||
}
|
||||
<small class="tw-text-muted tw-block">{{ p.description | i18n }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
}
|
||||
</bit-container>
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
import { All } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
import { PolicyListService } from "../../core/policy-list.service";
|
||||
import { BasePolicy, RestrictedItemTypesPolicy } from "../policies";
|
||||
import { BasePolicy } from "../policies";
|
||||
import { CollectionDialogTabType } from "../shared/components/collection-dialog";
|
||||
|
||||
import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component";
|
||||
@@ -53,7 +52,7 @@ export class PoliciesComponent implements OnInit {
|
||||
private policyListService: PolicyListService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
protected configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -71,35 +70,31 @@ export class PoliciesComponent implements OnInit {
|
||||
await this.load();
|
||||
|
||||
// Handle policies component launch from Event message
|
||||
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.policyId != null) {
|
||||
const policyIdFromEvents: string = qParams.policyId;
|
||||
for (const orgPolicy of this.orgPolicies) {
|
||||
if (orgPolicy.id === policyIdFromEvents) {
|
||||
for (let i = 0; i < this.policies.length; i++) {
|
||||
if (this.policies[i].type === orgPolicy.type) {
|
||||
// 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.edit(this.policies[i]);
|
||||
break;
|
||||
this.route.queryParams
|
||||
.pipe(first())
|
||||
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */
|
||||
.subscribe(async (qParams) => {
|
||||
if (qParams.policyId != null) {
|
||||
const policyIdFromEvents: string = qParams.policyId;
|
||||
for (const orgPolicy of this.orgPolicies) {
|
||||
if (orgPolicy.id === policyIdFromEvents) {
|
||||
for (let i = 0; i < this.policies.length; i++) {
|
||||
if (this.policies[i].type === orgPolicy.type) {
|
||||
// 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.edit(this.policies[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (
|
||||
(await this.configService.getFeatureFlag(FeatureFlag.RemoveCardItemTypePolicy)) &&
|
||||
this.policyListService.getPolicies().every((p) => !(p instanceof RestrictedItemTypesPolicy))
|
||||
) {
|
||||
this.policyListService.addPolicies([new RestrictedItemTypesPolicy()]);
|
||||
}
|
||||
const response = await this.policyApiService.getPolicies(this.organizationId);
|
||||
this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.orgPolicies.forEach((op) => {
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div [hidden]="loading">
|
||||
<p bitTypography="body1">{{ policy.description | i18n }}</p>
|
||||
@if (policy.showDescription) {
|
||||
<p bitTypography="body1">{{ policy.description | i18n }}</p>
|
||||
}
|
||||
<ng-template #policyForm></ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -128,13 +128,20 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if ((await this.policyComponent.confirm()) == false) {
|
||||
this.dialogRef.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let request: PolicyRequest;
|
||||
|
||||
try {
|
||||
request = await this.policyComponent.buildRequest();
|
||||
} catch (e) {
|
||||
this.toastService.showToast({ variant: "error", title: null, message: e.message });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.policyApiService.putPolicy(this.data.organizationId, this.data.policy.type, request);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -11,8 +13,8 @@ export class RequireSsoPolicy extends BasePolicy {
|
||||
type = PolicyType.RequireSso;
|
||||
component = RequireSsoPolicyComponent;
|
||||
|
||||
display(organization: Organization) {
|
||||
return organization.useSso;
|
||||
display(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useSso);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -10,6 +10,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -19,8 +20,8 @@ export class ResetPasswordPolicy extends BasePolicy {
|
||||
type = PolicyType.ResetPassword;
|
||||
component = ResetPasswordPolicyComponent;
|
||||
|
||||
display(organization: Organization) {
|
||||
return organization.useResetPassword;
|
||||
display(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useResetPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +53,10 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent implements
|
||||
throw new Error("No user found.");
|
||||
}
|
||||
|
||||
if (!this.policyResponse) {
|
||||
throw new Error("Policies not found");
|
||||
}
|
||||
|
||||
const organization = await firstValueFrom(
|
||||
this.organizationService
|
||||
.organizations$(userId)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
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";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -9,6 +13,10 @@ export class RestrictedItemTypesPolicy extends BasePolicy {
|
||||
description = "restrictedItemTypePolicyDesc";
|
||||
type = PolicyType.RestrictedItemTypes;
|
||||
component = RestrictedItemTypesPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService.getFeatureFlag$(FeatureFlag.RemoveCardItemTypePolicy);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -20,6 +20,9 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnI
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
if (!this.policyResponse) {
|
||||
throw new Error("Policies not found");
|
||||
}
|
||||
if (!this.policyResponse.canToggleState) {
|
||||
this.enabled.disable();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<p>
|
||||
{{ "organizationDataOwnershipContent" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
href="https://bitwarden.com/resources/credential-lifecycle-management/"
|
||||
target="_blank"
|
||||
>
|
||||
{{ "organizationDataOwnershipContentAnchor" | i18n }}.
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />
|
||||
<bit-label>{{ "turnOn" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<ng-template #dialog>
|
||||
<bit-simple-dialog background="alt">
|
||||
<span bitDialogTitle>{{ "organizationDataOwnershipWarningTitle" | i18n }}</span>
|
||||
<ng-container bitDialogContent>
|
||||
<div class="tw-text-left tw-overflow-hidden">
|
||||
{{ "organizationDataOwnershipWarningContentTop" | i18n }}
|
||||
<div class="tw-flex tw-flex-col tw-p-2">
|
||||
<ul class="tw-list-disc tw-pl-5 tw-space-y-2 tw-break-words tw-mb-0">
|
||||
<li>
|
||||
{{ "organizationDataOwnershipWarning1" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
{{ "organizationDataOwnershipWarning2" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
{{ "organizationDataOwnershipWarning3" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ "organizationDataOwnershipWarningContentBottom" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
href="https://bitwarden.com/resources/credential-lifecycle-management/"
|
||||
target="_blank"
|
||||
>
|
||||
{{ "organizationDataOwnershipContentAnchor" | i18n }}.
|
||||
</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<span class="tw-flex tw-gap-2">
|
||||
<button bitButton buttonType="primary" [bitDialogClose]="true" type="submit">
|
||||
{{ "continue" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" [bitDialogClose]="false" type="button">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core";
|
||||
import { lastValueFrom, Observable } from "rxjs";
|
||||
|
||||
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";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
export class vNextOrganizationDataOwnershipPolicy extends BasePolicy {
|
||||
name = "organizationDataOwnership";
|
||||
description = "organizationDataOwnershipDesc";
|
||||
type = PolicyType.OrganizationDataOwnership;
|
||||
component = vNextOrganizationDataOwnershipPolicyComponent;
|
||||
showDescription = false;
|
||||
|
||||
override display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "vnext-policy-organization-data-ownership",
|
||||
templateUrl: "vnext-organization-data-ownership.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class vNextOrganizationDataOwnershipPolicyComponent
|
||||
extends BasePolicyComponent
|
||||
implements OnInit
|
||||
{
|
||||
constructor(private dialogService: DialogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ViewChild("dialog", { static: true }) warningContent!: TemplateRef<unknown>;
|
||||
|
||||
override async confirm(): Promise<boolean> {
|
||||
if (this.policyResponse?.enabled && !this.enabled.value) {
|
||||
const dialogRef = this.dialogService.open(this.warningContent);
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
return Boolean(result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
@@ -87,8 +88,8 @@ enum ButtonType {
|
||||
}
|
||||
|
||||
export interface CollectionDialogParams {
|
||||
collectionId?: string;
|
||||
organizationId: string;
|
||||
collectionId?: CollectionId;
|
||||
organizationId: OrganizationId;
|
||||
initialTab?: CollectionDialogTabType;
|
||||
parentCollectionId?: string;
|
||||
showOrgSelector?: boolean;
|
||||
@@ -136,7 +137,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
externalId: { value: "", disabled: true },
|
||||
parent: undefined as string | undefined,
|
||||
access: [[] as AccessItemValue[]],
|
||||
selectedOrg: "",
|
||||
selectedOrg: "" as OrganizationId,
|
||||
});
|
||||
protected PermissionMode = PermissionMode;
|
||||
protected showDeleteButton = false;
|
||||
|
||||
@@ -35,12 +35,14 @@ import {
|
||||
MasterPasswordPolicy,
|
||||
PasswordGeneratorPolicy,
|
||||
OrganizationDataOwnershipPolicy,
|
||||
vNextOrganizationDataOwnershipPolicy,
|
||||
RequireSsoPolicy,
|
||||
ResetPasswordPolicy,
|
||||
SendOptionsPolicy,
|
||||
SingleOrgPolicy,
|
||||
TwoFactorAuthenticationPolicy,
|
||||
RemoveUnlockWithPinPolicy,
|
||||
RestrictedItemTypesPolicy,
|
||||
} from "./admin-console/organizations/policies";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
@@ -244,8 +246,10 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
new SingleOrgPolicy(),
|
||||
new RequireSsoPolicy(),
|
||||
new OrganizationDataOwnershipPolicy(),
|
||||
new vNextOrganizationDataOwnershipPolicy(),
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
new RestrictedItemTypesPolicy(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, ReplaySubject } from "rxjs";
|
||||
import { firstValueFrom, Observable, ReplaySubject } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import {
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SelfHostedEnvironment,
|
||||
} from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
export type WebRegionConfig = RegionConfig & {
|
||||
key: Region | string; // strings are used for custom environments
|
||||
@@ -27,6 +28,8 @@ export type WebRegionConfig = RegionConfig & {
|
||||
* Web specific environment service. Ensures that the urls are set from the window location.
|
||||
*/
|
||||
export class WebEnvironmentService extends DefaultEnvironmentService {
|
||||
private _environmentSubject: ReplaySubject<Environment>;
|
||||
|
||||
constructor(
|
||||
private win: Window,
|
||||
stateProvider: StateProvider,
|
||||
@@ -60,7 +63,9 @@ export class WebEnvironmentService extends DefaultEnvironmentService {
|
||||
// Override the environment observable with a replay subject
|
||||
const subject = new ReplaySubject<Environment>(1);
|
||||
subject.next(environment);
|
||||
this._environmentSubject = subject;
|
||||
this.environment$ = subject.asObservable();
|
||||
this.globalEnvironment$ = subject.asObservable();
|
||||
}
|
||||
|
||||
// Web setting env means navigating to a new location
|
||||
@@ -100,6 +105,12 @@ export class WebEnvironmentService extends DefaultEnvironmentService {
|
||||
// This return shouldn't matter as we are about to leave the current window
|
||||
return chosenRegionConfig.urls;
|
||||
}
|
||||
|
||||
getEnvironment$(userId: UserId): Observable<Environment> {
|
||||
// Web does not support account switching, and even if it did, you'd be required to be the environment of where the application
|
||||
// is running.
|
||||
return this._environmentSubject.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
export class WebCloudEnvironment extends CloudEnvironment {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Guid } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export class RequestSMAccessRequest {
|
||||
OrganizationId: Guid;
|
||||
OrganizationId: OrganizationId;
|
||||
EmailContent: string;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Guid } from "@bitwarden/common/types/guid";
|
||||
import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
@@ -63,7 +62,7 @@ export class RequestSMAccessComponent implements OnInit {
|
||||
|
||||
const formValue = this.requestAccessForm.value;
|
||||
const request = new RequestSMAccessRequest();
|
||||
request.OrganizationId = formValue.selectedOrganization.id as Guid;
|
||||
request.OrganizationId = formValue.selectedOrganization.id;
|
||||
request.EmailContent = formValue.requestAccessEmailContents;
|
||||
|
||||
await this.smLandingApiService.requestSMAccessFromAdmins(request);
|
||||
|
||||
@@ -166,6 +166,10 @@ export class VaultItemsComponent<C extends CipherViewLike> {
|
||||
);
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.selection.clear();
|
||||
}
|
||||
|
||||
get showExtraColumn() {
|
||||
return this.showCollections || this.showGroups || this.showOwner;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -262,7 +263,7 @@ export const OrganizationTrash: Story = {
|
||||
};
|
||||
|
||||
const unassignedCollection = new CollectionAdminView();
|
||||
unassignedCollection.id = Unassigned;
|
||||
unassignedCollection.id = Unassigned as CollectionId;
|
||||
unassignedCollection.name = "Unassigned";
|
||||
export const OrganizationTopLevelCollection: Story = {
|
||||
args: {
|
||||
@@ -327,7 +328,7 @@ function createCollectionView(i: number): CollectionAdminView {
|
||||
const organization = organizations[i % (organizations.length + 1)];
|
||||
const group = groups[i % (groups.length + 1)];
|
||||
const view = new CollectionAdminView();
|
||||
view.id = `collection-${i}`;
|
||||
view.id = `collection-${i}` as CollectionId;
|
||||
view.name = `Collection ${i}`;
|
||||
view.organizationId = organization?.id;
|
||||
view.manage = true;
|
||||
@@ -357,7 +358,7 @@ function createGroupView(i: number): GroupView {
|
||||
|
||||
function createOrganization(i: number): Organization {
|
||||
const organization = new Organization();
|
||||
organization.id = `organization-${i}`;
|
||||
organization.id = `organization-${i}` as OrganizationId;
|
||||
organization.name = `Organization ${i}`;
|
||||
organization.type = OrganizationUserType.Owner;
|
||||
organization.permissions = new PermissionsApi();
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Injectable, OnDestroy } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationExtras } from "@angular/router";
|
||||
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
isRoutedVaultFilterItemType,
|
||||
RoutedVaultFilterModel,
|
||||
@@ -31,10 +33,12 @@ export class RoutedVaultFilterService implements OnDestroy {
|
||||
const type = isRoutedVaultFilterItemType(unsafeType) ? unsafeType : undefined;
|
||||
|
||||
return {
|
||||
collectionId: queryParams.get("collectionId") ?? undefined,
|
||||
collectionId: (queryParams.get("collectionId") as CollectionId) ?? undefined,
|
||||
folderId: queryParams.get("folderId") ?? undefined,
|
||||
organizationId:
|
||||
params.get("organizationId") ?? queryParams.get("organizationId") ?? undefined,
|
||||
(params.get("organizationId") as OrganizationId) ??
|
||||
(queryParams.get("organizationId") as OrganizationId) ??
|
||||
undefined,
|
||||
organizationIdParamType:
|
||||
params.get("organizationId") != undefined ? ("path" as const) : ("query" as const),
|
||||
type,
|
||||
|
||||
@@ -28,7 +28,7 @@ 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 { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -209,7 +209,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
||||
|
||||
protected getOrganizationFilterMyVault(): TreeNode<OrganizationFilter> {
|
||||
const myVault = new Organization() as OrganizationFilter;
|
||||
myVault.id = "MyVault";
|
||||
myVault.id = "MyVault" as OrganizationId;
|
||||
myVault.icon = "bwi-user";
|
||||
myVault.enabled = true;
|
||||
myVault.hideOptions = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
|
||||
@@ -65,7 +66,7 @@ export class RoutedVaultFilterBridge implements VaultFilter {
|
||||
let type: RoutedVaultFilterItemType | undefined;
|
||||
|
||||
if (value?.node.id === "AllItems" && this.routedFilter.organizationIdParamType === "path") {
|
||||
type = "all";
|
||||
type = All;
|
||||
} else if (
|
||||
value?.node.id === "AllItems" &&
|
||||
this.routedFilter.organizationIdParamType === "query"
|
||||
@@ -98,7 +99,7 @@ export class RoutedVaultFilterBridge implements VaultFilter {
|
||||
return this.legacyFilter.selectedCollectionNode;
|
||||
}
|
||||
set selectedCollectionNode(value: TreeNode<CollectionFilter>) {
|
||||
let collectionId: string | undefined;
|
||||
let collectionId: CollectionId | All | Unassigned | undefined;
|
||||
|
||||
if (value != null && value.node.id === null) {
|
||||
collectionId = Unassigned;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
/**
|
||||
* A constant used to represent viewing "all" of a particular filter.
|
||||
*/
|
||||
export const All = "all";
|
||||
export type All = typeof All;
|
||||
|
||||
// TODO: Remove `All` when moving to vertical navigation.
|
||||
const itemTypes = [
|
||||
@@ -19,9 +26,9 @@ export function isRoutedVaultFilterItemType(value: unknown): value is RoutedVaul
|
||||
}
|
||||
|
||||
export interface RoutedVaultFilterModel {
|
||||
collectionId?: string;
|
||||
collectionId?: CollectionId | All | Unassigned;
|
||||
folderId?: string;
|
||||
organizationId?: string;
|
||||
organizationId?: OrganizationId | Unassigned;
|
||||
type?: RoutedVaultFilterItemType;
|
||||
|
||||
organizationIdParamType?: "path" | "query";
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
{{ trashCleanupWarning }}
|
||||
</bit-callout>
|
||||
<app-vault-items
|
||||
#vaultItems
|
||||
[ciphers]="ciphers"
|
||||
[collections]="collections"
|
||||
[allCollections]="allCollections"
|
||||
|
||||
@@ -108,6 +108,7 @@ import {
|
||||
} from "../components/vault-item-dialog/vault-item-dialog.component";
|
||||
import { VaultItem } from "../components/vault-items/vault-item";
|
||||
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
||||
import { VaultItemsComponent } from "../components/vault-items/vault-items.component";
|
||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||
|
||||
import {
|
||||
@@ -156,6 +157,7 @@ const SearchTextDebounceInterval = 200;
|
||||
})
|
||||
export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestroy {
|
||||
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
|
||||
@ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent<C>;
|
||||
|
||||
trashCleanupWarning: string = null;
|
||||
kdfIterations: number;
|
||||
@@ -1281,6 +1283,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
|
||||
private refresh() {
|
||||
this.refresh$.next();
|
||||
this.vaultItemsComponent?.clearSelection();
|
||||
}
|
||||
|
||||
private async go(queryParams: any = null) {
|
||||
|
||||
@@ -5429,6 +5429,37 @@
|
||||
"organizationDataOwnership": {
|
||||
"message": "Enforce organization data ownership"
|
||||
},
|
||||
"organizationDataOwnershipDesc": {
|
||||
"message": "Require all items to be owned by an organization, removing the option to store items at the account level.",
|
||||
"description": "This is the policy description shown in the policy list."
|
||||
},
|
||||
"organizationDataOwnershipContent": {
|
||||
"message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'"
|
||||
},
|
||||
"organizationDataOwnershipContentAnchor":{
|
||||
"message": "credential lifecycle",
|
||||
"description": "This will be used as a hyperlink"
|
||||
},
|
||||
"organizationDataOwnershipWarningTitle":{
|
||||
"message": "Are you sure you want to proceed?"
|
||||
},
|
||||
"organizationDataOwnershipWarning1":{
|
||||
"message": "will remain accessible to members"
|
||||
},
|
||||
"organizationDataOwnershipWarning2":{
|
||||
"message": "will not be automatically selected when creating new items"
|
||||
},
|
||||
"organizationDataOwnershipWarning3":{
|
||||
"message": "cannot be managed from the Admin Console until the user is offboarded"
|
||||
},
|
||||
"organizationDataOwnershipWarningContentTop":{
|
||||
"message": "By turning this policy off, the default collection: "
|
||||
},
|
||||
"organizationDataOwnershipWarningContentBottom":{
|
||||
"message": "Learn more about the ",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'"
|
||||
},
|
||||
"personalOwnership": {
|
||||
"message": "Remove individual vault"
|
||||
},
|
||||
@@ -10965,5 +10996,11 @@
|
||||
},
|
||||
"unlimitedSecretsAndProjects": {
|
||||
"message": "Unlimited secrets and projects"
|
||||
},
|
||||
"providersubscriptionCanceled": {
|
||||
"message": "Subscription canceled"
|
||||
},
|
||||
"providersubCanceledmessage": {
|
||||
"message" : "To resubscribe, contact Bitwarden Customer Support."
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
@@ -13,8 +15,8 @@ export class ActivateAutofillPolicy extends BasePolicy {
|
||||
type = PolicyType.ActivateAutofill;
|
||||
component = ActivateAutofillPolicyComponent;
|
||||
|
||||
display(organization: Organization) {
|
||||
return organization.useActivateAutofillPolicy;
|
||||
display(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useActivateAutofillPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export class ProviderSubscriptionStatusComponent {
|
||||
}
|
||||
case "incomplete_expired":
|
||||
case "canceled": {
|
||||
const canceledText = this.i18nService.t("canceled");
|
||||
const canceledText = this.i18nService.t("providersubscriptionCanceled");
|
||||
return {
|
||||
status: {
|
||||
label: defaultStatusLabel,
|
||||
@@ -171,7 +171,7 @@ export class ProviderSubscriptionStatusComponent {
|
||||
callout: {
|
||||
severity: "danger",
|
||||
header: canceledText,
|
||||
body: this.i18nService.t("subscriptionCanceled"),
|
||||
body: this.i18nService.t("providersubCanceledmessage"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { CollectionAccessSelectionView } from "./collection-access-selection.vie
|
||||
import { CollectionAccessDetailsResponse } from "./collection.response";
|
||||
import { CollectionView } from "./collection.view";
|
||||
|
||||
// TODO: this is used to represent the pseudo "Unassigned" collection as well as
|
||||
// the user's personal vault (as a pseudo organization). This should be separated out into different values.
|
||||
export const Unassigned = "unassigned";
|
||||
export type Unassigned = typeof Unassigned;
|
||||
|
||||
export class CollectionAdminView extends CollectionView {
|
||||
groups: CollectionAccessSelectionView[] = [];
|
||||
|
||||
@@ -54,7 +54,7 @@ describe("Collection", () => {
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const collection = new Collection();
|
||||
collection.id = "id";
|
||||
collection.id = "id" as CollectionId;
|
||||
collection.organizationId = "orgId" as OrganizationId;
|
||||
collection.name = mockEnc("encName");
|
||||
collection.externalId = "extId";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import Domain, { EncryptableKeys } from "@bitwarden/common/platform/models/domain/domain-base";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { CollectionData } from "./collection.data";
|
||||
@@ -13,8 +14,8 @@ export const CollectionTypes = {
|
||||
export type CollectionType = (typeof CollectionTypes)[keyof typeof CollectionTypes];
|
||||
|
||||
export class Collection extends Domain {
|
||||
id: string | undefined;
|
||||
organizationId: string | undefined;
|
||||
id: CollectionId | undefined;
|
||||
organizationId: OrganizationId | undefined;
|
||||
name: EncString | undefined;
|
||||
externalId: string | undefined;
|
||||
readOnly: boolean = false;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Jsonify } from "type-fest";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { View } from "@bitwarden/common/models/view/view";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
|
||||
import { Collection, CollectionType, CollectionTypes } from "./collection";
|
||||
@@ -10,8 +11,8 @@ import { CollectionAccessDetailsResponse } from "./collection.response";
|
||||
export const NestingDelimiter = "/";
|
||||
|
||||
export class CollectionView implements View, ITreeNodeObject {
|
||||
id: string | undefined;
|
||||
organizationId: string | undefined;
|
||||
id: CollectionId | undefined;
|
||||
organizationId: OrganizationId | undefined;
|
||||
name: string = "";
|
||||
externalId: string | undefined;
|
||||
// readOnly applies to the items within a collection
|
||||
|
||||
@@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
|
||||
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
|
||||
@@ -42,7 +42,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService {
|
||||
const orgIds = new Set(orgs.map((org) => org.id));
|
||||
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||
const hasManageCollections = collections.some(
|
||||
(c) => c.manage && orgIds.has(c.organizationId!),
|
||||
(c) => c.manage && orgIds.has(c.organizationId! as OrganizationId),
|
||||
);
|
||||
|
||||
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
||||
|
||||
@@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
|
||||
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
|
||||
@@ -46,7 +46,7 @@ export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService {
|
||||
const orgIds = new Set(orgs.map((org) => org.id));
|
||||
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||
const hasManageCollections = collections.some(
|
||||
(c) => c.manage && orgIds.has(c.organizationId!),
|
||||
(c) => c.manage && orgIds.has(c.organizationId! as OrganizationId),
|
||||
);
|
||||
|
||||
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ProductTierType } from "../../../billing/enums";
|
||||
import { OrganizationId } from "../../../types/guid";
|
||||
import { OrganizationUserStatusType, OrganizationUserType, ProviderType } from "../../enums";
|
||||
import { PermissionsApi } from "../api/permissions.api";
|
||||
import { OrganizationData } from "../data/organization.data";
|
||||
|
||||
export class Organization {
|
||||
id: string;
|
||||
id: OrganizationId;
|
||||
name: string;
|
||||
status: OrganizationUserStatusType;
|
||||
|
||||
@@ -99,7 +100,7 @@ export class Organization {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = obj.id;
|
||||
this.id = obj.id as OrganizationId;
|
||||
this.name = obj.name;
|
||||
this.status = obj.status;
|
||||
this.type = obj.type;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
// @ts-strict-ignore
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import Domain from "../../../platform/models/domain/domain-base";
|
||||
import { PolicyId } from "../../../types/guid";
|
||||
import { OrganizationId, PolicyId } from "../../../types/guid";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../data/policy.data";
|
||||
import { PolicyResponse } from "../response/policy.response";
|
||||
|
||||
export class Policy extends Domain {
|
||||
id: PolicyId;
|
||||
organizationId: string;
|
||||
organizationId: OrganizationId;
|
||||
type: PolicyType;
|
||||
data: any;
|
||||
|
||||
@@ -26,7 +26,7 @@ export class Policy extends Domain {
|
||||
}
|
||||
|
||||
this.id = obj.id;
|
||||
this.organizationId = obj.organizationId;
|
||||
this.organizationId = obj.organizationId as OrganizationId;
|
||||
this.type = obj.type;
|
||||
this.data = obj.data;
|
||||
this.enabled = obj.enabled;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { PolicyType } from "../../enums";
|
||||
|
||||
export class PolicyRequest {
|
||||
export type PolicyRequest = {
|
||||
type: PolicyType;
|
||||
enabled: boolean;
|
||||
data: any;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common";
|
||||
|
||||
import { CollectionId } from "../../types/guid";
|
||||
|
||||
import { CollectionExport } from "./collection.export";
|
||||
|
||||
export class CollectionWithIdExport extends CollectionExport {
|
||||
id: string;
|
||||
id: CollectionId;
|
||||
|
||||
static toView(req: CollectionWithIdExport, view = new CollectionView()) {
|
||||
view.id = req.id;
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common";
|
||||
|
||||
import { EncString } from "../../key-management/crypto/models/enc-string";
|
||||
import { emptyGuid, OrganizationId } from "../../types/guid";
|
||||
|
||||
import { safeGetString } from "./utils";
|
||||
|
||||
export class CollectionExport {
|
||||
static template(): CollectionExport {
|
||||
const req = new CollectionExport();
|
||||
req.organizationId = "00000000-0000-0000-0000-000000000000";
|
||||
req.organizationId = emptyGuid as OrganizationId;
|
||||
req.name = "Collection name";
|
||||
req.externalId = null;
|
||||
return req;
|
||||
@@ -35,7 +36,7 @@ export class CollectionExport {
|
||||
return domain;
|
||||
}
|
||||
|
||||
organizationId: string;
|
||||
organizationId: OrganizationId;
|
||||
name: string;
|
||||
externalId: string;
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ export abstract class ConfigApiServiceAbstraction {
|
||||
/**
|
||||
* Fetches the server configuration for the given user. If no user is provided, the configuration will not contain user-specific context.
|
||||
*/
|
||||
abstract get(userId: UserId | undefined): Promise<ServerConfigResponse>;
|
||||
abstract get(userId: UserId | null): Promise<ServerConfigResponse>;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ export interface Environment {
|
||||
*/
|
||||
export abstract class EnvironmentService {
|
||||
abstract environment$: Observable<Environment>;
|
||||
|
||||
/**
|
||||
* The environment stored in global state, when a user signs in the state stored here will become
|
||||
* their user environment.
|
||||
*/
|
||||
abstract globalEnvironment$: Observable<Environment>;
|
||||
|
||||
abstract cloudWebVaultUrl$: Observable<string>;
|
||||
|
||||
/**
|
||||
@@ -125,12 +132,12 @@ export abstract class EnvironmentService {
|
||||
* @param userId - The user id to set the cloud web vault app URL for. If null or undefined the global environment is set.
|
||||
* @param region - The region of the cloud web vault app.
|
||||
*/
|
||||
abstract setCloudRegion(userId: UserId, region: Region): Promise<void>;
|
||||
abstract setCloudRegion(userId: UserId | null, region: Region): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the environment from state. Useful if you need to get the environment for another user.
|
||||
*/
|
||||
abstract getEnvironment$(userId: UserId): Observable<Environment | undefined>;
|
||||
abstract getEnvironment$(userId: UserId): Observable<Environment>;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link getEnvironment$} instead.
|
||||
|
||||
@@ -10,7 +10,7 @@ export class ConfigApiService implements ConfigApiServiceAbstraction {
|
||||
private tokenService: TokenService,
|
||||
) {}
|
||||
|
||||
async get(userId: UserId | undefined): Promise<ServerConfigResponse> {
|
||||
async get(userId: UserId | null): Promise<ServerConfigResponse> {
|
||||
// Authentication adds extra context to config responses, if the user has an access token, we want to use it
|
||||
// We don't particularly care about ensuring the token is valid and not expired, just that it exists
|
||||
const authed: boolean =
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
FakeGlobalState,
|
||||
FakeSingleUserState,
|
||||
FakeStateProvider,
|
||||
awaitAsync,
|
||||
mockAccountServiceWith,
|
||||
} from "../../../../spec";
|
||||
import { Matrix } from "../../../../spec/matrix";
|
||||
import { subscribeTo } from "../../../../spec/observable-tracker";
|
||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||
@@ -74,7 +74,8 @@ describe("ConfigService", () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
environmentService.environment$ = environmentSubject;
|
||||
Matrix.autoMockMethod(environmentService.getEnvironment$, () => environmentSubject);
|
||||
environmentService.globalEnvironment$ = environmentSubject;
|
||||
sut = new DefaultConfigService(
|
||||
configApiService,
|
||||
environmentService,
|
||||
@@ -98,9 +99,12 @@ describe("ConfigService", () => {
|
||||
: serverConfigFactory(activeApiUrl + userId, tooOld);
|
||||
const globalStored =
|
||||
configStateDescription === "missing"
|
||||
? {}
|
||||
? {
|
||||
[activeApiUrl]: null,
|
||||
}
|
||||
: {
|
||||
[activeApiUrl]: serverConfigFactory(activeApiUrl, tooOld),
|
||||
[activeApiUrl + "0"]: serverConfigFactory(activeApiUrl + userId, tooOld),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -108,11 +112,6 @@ describe("ConfigService", () => {
|
||||
userState.nextState(userStored);
|
||||
});
|
||||
|
||||
// sanity check
|
||||
test("authed and unauthorized state are different", () => {
|
||||
expect(globalStored[activeApiUrl]).not.toEqual(userStored);
|
||||
});
|
||||
|
||||
describe("fail to fetch", () => {
|
||||
beforeEach(() => {
|
||||
configApiService.get.mockRejectedValue(new Error("Unable to fetch"));
|
||||
@@ -178,6 +177,7 @@ describe("ConfigService", () => {
|
||||
beforeEach(() => {
|
||||
globalState.stateSubject.next(globalStored);
|
||||
userState.nextState(userStored);
|
||||
Matrix.autoMockMethod(environmentService.getEnvironment$, () => environmentSubject);
|
||||
});
|
||||
it("does not fetch from server", async () => {
|
||||
await firstValueFrom(sut.serverConfig$);
|
||||
@@ -189,21 +189,13 @@ describe("ConfigService", () => {
|
||||
const actual = await firstValueFrom(sut.serverConfig$);
|
||||
expect(actual).toEqual(activeUserId ? userStored : globalStored[activeApiUrl]);
|
||||
});
|
||||
|
||||
it("does not complete after emit", async () => {
|
||||
const emissions = [];
|
||||
const subscription = sut.serverConfig$.subscribe((v) => emissions.push(v));
|
||||
await awaitAsync();
|
||||
expect(emissions.length).toBe(1);
|
||||
expect(subscription.closed).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("gets global config when there is an locked active user", async () => {
|
||||
await accountService.switchAccount(userId);
|
||||
environmentService.environment$ = of(environmentFactory(activeApiUrl));
|
||||
environmentService.globalEnvironment$ = of(environmentFactory(activeApiUrl));
|
||||
|
||||
globalState.stateSubject.next({
|
||||
[activeApiUrl]: serverConfigFactory(activeApiUrl + "global"),
|
||||
@@ -236,7 +228,8 @@ describe("ConfigService", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
environmentSubject = new Subject<Environment>();
|
||||
environmentService.environment$ = environmentSubject;
|
||||
environmentService.globalEnvironment$ = environmentSubject;
|
||||
Matrix.autoMockMethod(environmentService.getEnvironment$, () => environmentSubject);
|
||||
sut = new DefaultConfigService(
|
||||
configApiService,
|
||||
environmentService,
|
||||
@@ -327,7 +320,8 @@ describe("ConfigService", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
const config = serverConfigFactory("existing-data", tooOld);
|
||||
environmentService.environment$ = environmentSubject;
|
||||
environmentService.globalEnvironment$ = environmentSubject;
|
||||
Matrix.autoMockMethod(environmentService.getEnvironment$, () => environmentSubject);
|
||||
|
||||
globalState.stateSubject.next({ [apiUrl(0)]: config });
|
||||
userState.stateSubject.next({
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
firstValueFrom,
|
||||
map,
|
||||
mergeWith,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
shareReplay,
|
||||
ReplaySubject,
|
||||
share,
|
||||
Subject,
|
||||
switchMap,
|
||||
tap,
|
||||
timer,
|
||||
} from "rxjs";
|
||||
import { SemVer } from "semver";
|
||||
|
||||
@@ -50,11 +51,15 @@ export const GLOBAL_SERVER_CONFIGURATIONS = KeyDefinition.record<ServerConfig, A
|
||||
},
|
||||
);
|
||||
|
||||
const environmentComparer = (previous: Environment, current: Environment) => {
|
||||
return previous.getApiUrl() === current.getApiUrl();
|
||||
};
|
||||
|
||||
// FIXME: currently we are limited to api requests for active users. Update to accept a UserId and APIUrl once ApiService supports it.
|
||||
export class DefaultConfigService implements ConfigService {
|
||||
private failedFetchFallbackSubject = new Subject<ServerConfig>();
|
||||
private failedFetchFallbackSubject = new Subject<ServerConfig | null>();
|
||||
|
||||
serverConfig$: Observable<ServerConfig>;
|
||||
serverConfig$: Observable<ServerConfig | null>;
|
||||
|
||||
serverSettings$: Observable<ServerSettings>;
|
||||
|
||||
@@ -67,32 +72,61 @@ export class DefaultConfigService implements ConfigService {
|
||||
private stateProvider: StateProvider,
|
||||
private authService: AuthService,
|
||||
) {
|
||||
const userId$ = this.stateProvider.activeUserId$;
|
||||
const authStatus$ = userId$.pipe(
|
||||
switchMap((userId) => (userId == null ? of(null) : this.authService.authStatusFor$(userId))),
|
||||
const globalConfig$ = this.environmentService.globalEnvironment$.pipe(
|
||||
distinctUntilChanged(environmentComparer),
|
||||
switchMap((environment) =>
|
||||
this.globalConfigFor$(environment.getApiUrl()).pipe(
|
||||
map((config) => {
|
||||
return [config, null as UserId | null, environment, config] as const;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
this.serverConfig$ = combineLatest([
|
||||
userId$,
|
||||
this.environmentService.environment$,
|
||||
authStatus$,
|
||||
]).pipe(
|
||||
switchMap(([userId, environment, authStatus]) => {
|
||||
if (userId == null || authStatus !== AuthenticationStatus.Unlocked) {
|
||||
return this.globalConfigFor$(environment.getApiUrl()).pipe(
|
||||
map((config) => [config, null, environment] as const),
|
||||
);
|
||||
this.serverConfig$ = this.stateProvider.activeUserId$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap((userId) => {
|
||||
if (userId == null) {
|
||||
// Global
|
||||
return globalConfig$;
|
||||
}
|
||||
|
||||
return this.userConfigFor$(userId).pipe(
|
||||
map((config) => [config, userId, environment] as const),
|
||||
return this.authService.authStatusFor$(userId).pipe(
|
||||
map((authStatus) => authStatus === AuthenticationStatus.Unlocked),
|
||||
distinctUntilChanged(),
|
||||
switchMap((isUnlocked) => {
|
||||
if (!isUnlocked) {
|
||||
return globalConfig$;
|
||||
}
|
||||
|
||||
return combineLatest([
|
||||
this.environmentService
|
||||
.getEnvironment$(userId)
|
||||
.pipe(distinctUntilChanged(environmentComparer)),
|
||||
this.userConfigFor$(userId),
|
||||
]).pipe(
|
||||
switchMap(([environment, config]) => {
|
||||
if (config == null) {
|
||||
// If the user doesn't have any config yet, use the global config for that url as the fallback
|
||||
return this.globalConfigFor$(environment.getApiUrl()).pipe(
|
||||
map(
|
||||
(globalConfig) =>
|
||||
[null as ServerConfig | null, userId, environment, globalConfig] as const,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return of([config, userId, environment, config] as const);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
tap(async (rec) => {
|
||||
const [existingConfig, userId, environment] = rec;
|
||||
const [existingConfig, userId, environment, fallbackConfig] = rec;
|
||||
// Grab new config if older retrieval interval
|
||||
if (!existingConfig || this.olderThanRetrievalInterval(existingConfig.utcDate)) {
|
||||
await this.renewConfig(existingConfig, userId, environment);
|
||||
await this.renewConfig(existingConfig, userId, environment, fallbackConfig);
|
||||
}
|
||||
}),
|
||||
switchMap(([existingConfig]) => {
|
||||
@@ -106,7 +140,7 @@ export class DefaultConfigService implements ConfigService {
|
||||
}),
|
||||
// If fetch fails, we'll emit on this subject to fallback to the existing config
|
||||
mergeWith(this.failedFetchFallbackSubject),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
share({ connector: () => new ReplaySubject(1), resetOnRefCountZero: () => timer(1000) }),
|
||||
);
|
||||
|
||||
this.cloudRegion$ = this.serverConfig$.pipe(
|
||||
@@ -155,19 +189,18 @@ export class DefaultConfigService implements ConfigService {
|
||||
|
||||
// Updates the on-disk configuration with a newly retrieved configuration
|
||||
private async renewConfig(
|
||||
existingConfig: ServerConfig,
|
||||
userId: UserId,
|
||||
existingConfig: ServerConfig | null,
|
||||
userId: UserId | null,
|
||||
environment: Environment,
|
||||
fallbackConfig: ServerConfig | null,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Feature flags often have a big impact on user experience, lets ensure we return some value
|
||||
// somewhat quickly even though it may not be accurate, we won't cancel the HTTP request
|
||||
// though so that hopefully it can have finished and hydrated a more accurate value.
|
||||
const handle = setTimeout(() => {
|
||||
this.logService.info(
|
||||
"Self-host environment did not respond in time, emitting previous config.",
|
||||
);
|
||||
this.failedFetchFallbackSubject.next(existingConfig);
|
||||
this.logService.info("Environment did not respond in time, emitting previous config.");
|
||||
this.failedFetchFallbackSubject.next(fallbackConfig);
|
||||
}, SLOW_EMISSION_GUARD);
|
||||
const response = await this.configApiService.get(userId);
|
||||
clearTimeout(handle);
|
||||
@@ -195,17 +228,17 @@ export class DefaultConfigService implements ConfigService {
|
||||
// mutate error to be handled by catchError
|
||||
this.logService.error(`Unable to fetch ServerConfig from ${environment.getApiUrl()}`, e);
|
||||
// Emit the existing config
|
||||
this.failedFetchFallbackSubject.next(existingConfig);
|
||||
this.failedFetchFallbackSubject.next(fallbackConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private globalConfigFor$(apiUrl: string): Observable<ServerConfig> {
|
||||
private globalConfigFor$(apiUrl: string): Observable<ServerConfig | null> {
|
||||
return this.stateProvider
|
||||
.getGlobal(GLOBAL_SERVER_CONFIGURATIONS)
|
||||
.state$.pipe(map((configs) => configs?.[apiUrl]));
|
||||
.state$.pipe(map((configs) => configs?.[apiUrl] ?? null));
|
||||
}
|
||||
|
||||
private userConfigFor$(userId: UserId): Observable<ServerConfig> {
|
||||
private userConfigFor$(userId: UserId): Observable<ServerConfig | null> {
|
||||
return this.stateProvider.getUser(userId, USER_SERVER_CONFIG).state$;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
);
|
||||
|
||||
environment$: Observable<Environment>;
|
||||
globalEnvironment$: Observable<Environment>;
|
||||
cloudWebVaultUrl$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
@@ -148,6 +149,10 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
distinctUntilChanged((oldUserId: UserId, newUserId: UserId) => oldUserId == newUserId),
|
||||
);
|
||||
|
||||
this.globalEnvironment$ = this.stateProvider
|
||||
.getGlobal(GLOBAL_ENVIRONMENT_KEY)
|
||||
.state$.pipe(map((state) => this.buildEnvironment(state?.region, state?.urls)));
|
||||
|
||||
this.environment$ = account$.pipe(
|
||||
switchMap((userId) => {
|
||||
const t = userId
|
||||
@@ -263,7 +268,7 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
return new SelfHostedEnvironment(urls);
|
||||
}
|
||||
|
||||
async setCloudRegion(userId: UserId, region: CloudRegion) {
|
||||
async setCloudRegion(userId: UserId | null, region: CloudRegion) {
|
||||
if (userId == null) {
|
||||
await this.globalCloudRegionState.update(() => region);
|
||||
} else {
|
||||
@@ -271,7 +276,7 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
}
|
||||
}
|
||||
|
||||
getEnvironment$(userId: UserId): Observable<Environment | undefined> {
|
||||
getEnvironment$(userId: UserId): Observable<Environment> {
|
||||
return this.stateProvider.getUser(userId, USER_ENVIRONMENT_KEY).state$.pipe(
|
||||
map((state) => {
|
||||
return this.buildEnvironment(state?.region, state?.urls);
|
||||
|
||||
@@ -20,3 +20,8 @@ export type OrganizationIntegrationConfigurationId = Opaque<
|
||||
string,
|
||||
"OrganizationIntegrationConfigurationId"
|
||||
>;
|
||||
|
||||
/**
|
||||
* A string representation of an empty guid.
|
||||
*/
|
||||
export const emptyGuid = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
@@ -98,7 +98,7 @@ export default {
|
||||
<div class="tw-font-bold tw-mb-2">
|
||||
Secondary Projected Content (optional)
|
||||
</div>
|
||||
<button bitButton>Perform Action</button>
|
||||
<button type="button" bitButton>Perform Action</button>
|
||||
</div>
|
||||
</auth-anon-layout>
|
||||
`,
|
||||
|
||||
@@ -13,10 +13,10 @@ import { AsyncActionsModule } from "./async-actions.module";
|
||||
import { BitActionDirective } from "./bit-action.directive";
|
||||
|
||||
const template = /*html*/ `
|
||||
<button bitButton buttonType="primary" [bitAction]="action" class="tw-me-2">
|
||||
<button type="button" bitButton buttonType="primary" [bitAction]="action" class="tw-me-2">
|
||||
Perform action {{ statusEmoji }}
|
||||
</button>
|
||||
<button bitIconButton="bwi-trash" buttonType="danger" [bitAction]="action"></button>`;
|
||||
<button type="button" bitIconButton="bwi-trash" buttonType="danger" [bitAction]="action"></button>`;
|
||||
|
||||
@Component({
|
||||
template,
|
||||
|
||||
@@ -47,7 +47,7 @@ export const Primary: Story = {
|
||||
<span class="tw-text-main">link </span><a href="#" bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</a>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<span class="tw-text-main">button </span><button bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</button>
|
||||
<span class="tw-text-main">button </span><button type="button" bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@@ -108,40 +108,40 @@ export const VariantsAndInteractionStates: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<span class="tw-text-main tw-mx-1">Default</span>
|
||||
<button class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button type="button" class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<br/><br/>
|
||||
<span class="tw-text-main tw-mx-1">Hover</span>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<br/><br/>
|
||||
<span class="tw-text-main tw-mx-1">Focus Visible</span>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button type="button" class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<br/><br/>
|
||||
<span class="tw-text-main tw-mx-1">Disabled</span>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button>
|
||||
<button type="button" disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -49,10 +49,10 @@ export const Base: Story = {
|
||||
render: (args) => {
|
||||
return {
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-banner ${formatArgsForCodeSnippet<BannerComponent>(args)}>
|
||||
Content Really Long Text Lorem Ipsum Ipsum Ipsum
|
||||
<button bitLink linkType="secondary">Button</button>
|
||||
<button type="button" bitLink linkType="secondary">Button</button>
|
||||
</bit-banner>
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<button bitButton ${formatArgsForCodeSnippet<ButtonComponent>(args)}>Button</button>
|
||||
<button type="button" bitButton ${formatArgsForCodeSnippet<ButtonComponent>(args)}>Button</button>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
@@ -58,9 +58,9 @@ export const Small: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center">
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'primary'" [size]="size" [block]="block">Primary small</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'secondary'" [size]="size" [block]="block">Secondary small</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'danger'" [size]="size" [block]="block">Danger small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'primary'" [size]="size" [block]="block">Primary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'secondary'" [size]="size" [block]="block">Secondary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'danger'" [size]="size" [block]="block">Danger small</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
@@ -86,15 +86,15 @@ export const Disabled: Story = {
|
||||
export const DisabledWithAttribute: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
@if (disabled) {
|
||||
<button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button>
|
||||
<button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button>
|
||||
<button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button>
|
||||
<button type="button" bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button>
|
||||
<button type="button" bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button>
|
||||
<button type="button" bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button>
|
||||
} @else {
|
||||
<button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button>
|
||||
<button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button>
|
||||
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button>
|
||||
<button type="button" bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button>
|
||||
<button type="button" bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button>
|
||||
<button type="button" bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button>
|
||||
}
|
||||
`,
|
||||
}),
|
||||
@@ -107,12 +107,12 @@ export const DisabledWithAttribute: Story = {
|
||||
export const Block: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<span class="tw-flex">
|
||||
<button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
|
||||
<button type="button" bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button>
|
||||
<a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ms-2">[block]="true" Link</a>
|
||||
|
||||
<button bitButton [buttonType]="buttonType" block class="tw-ms-2">block Button</button>
|
||||
<button type="button" bitButton [buttonType]="buttonType" block class="tw-ms-2">block Button</button>
|
||||
<a bitButton [buttonType]="buttonType" block href="#" class="tw-ms-2">block Link</a>
|
||||
</span>
|
||||
`,
|
||||
@@ -125,16 +125,16 @@ export const Block: Story = {
|
||||
export const WithIcon: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<span class="tw-flex tw-gap-8">
|
||||
<div>
|
||||
<button bitButton [buttonType]="buttonType" [block]="block">
|
||||
<button type="button" bitButton [buttonType]="buttonType" [block]="block">
|
||||
<i class="bwi bwi-plus tw-me-2"></i>
|
||||
Button label
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button bitButton [buttonType]="buttonType" [block]="block">
|
||||
<button type="button" bitButton [buttonType]="buttonType" [block]="block">
|
||||
Button label
|
||||
<i class="bwi bwi-plus tw-ms-2"></i>
|
||||
</button>
|
||||
@@ -149,11 +149,11 @@ export const InteractionStates: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center">
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button>
|
||||
<button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button>
|
||||
</div>
|
||||
<div class="tw-flex tw-gap-4 tw-items-center">
|
||||
<a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Anchor</a>
|
||||
|
||||
@@ -2,14 +2,8 @@ import { Component, computed, HostBinding, input } from "@angular/core";
|
||||
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum CharacterType {
|
||||
Letter,
|
||||
Emoji,
|
||||
Special,
|
||||
Number,
|
||||
}
|
||||
type CharacterType = "letter" | "emoji" | "special" | "number";
|
||||
|
||||
/**
|
||||
* The color password is used primarily in the Generator pages and in the Login type form. It includes
|
||||
* the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as
|
||||
@@ -36,10 +30,10 @@ export class ColorPasswordComponent {
|
||||
});
|
||||
|
||||
characterStyles: Record<CharacterType, string[]> = {
|
||||
[CharacterType.Emoji]: [],
|
||||
[CharacterType.Letter]: ["tw-text-main"],
|
||||
[CharacterType.Special]: ["tw-text-danger"],
|
||||
[CharacterType.Number]: ["tw-text-primary-600"],
|
||||
emoji: [],
|
||||
letter: ["tw-text-main"],
|
||||
special: ["tw-text-danger"],
|
||||
number: ["tw-text-primary-600"],
|
||||
};
|
||||
|
||||
@HostBinding("class")
|
||||
@@ -68,18 +62,18 @@ export class ColorPasswordComponent {
|
||||
|
||||
private getCharacterType(character: string): CharacterType {
|
||||
if (character.match(Utils.regexpEmojiPresentation)) {
|
||||
return CharacterType.Emoji;
|
||||
return "emoji";
|
||||
}
|
||||
|
||||
if (character.match(/\d/)) {
|
||||
return CharacterType.Number;
|
||||
return "number";
|
||||
}
|
||||
|
||||
const specials = ["&", "<", ">", " "];
|
||||
if (specials.includes(character) || character.match(/[^\w ]/)) {
|
||||
return CharacterType.Special;
|
||||
return "special";
|
||||
}
|
||||
|
||||
return CharacterType.Letter;
|
||||
return "letter";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,16 +78,17 @@ type Story = StoryObj<DialogComponent & { title: string }>;
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-dialog [dialogSize]="dialogSize" [title]="title" [subtitle]="subtitle" [loading]="loading" [disablePadding]="disablePadding">
|
||||
<ng-container bitDialogTitle>
|
||||
<span bitBadge variant="success">Foobar</span>
|
||||
</ng-container>
|
||||
<ng-container bitDialogContent>Dialog body text goes here.</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button type="button" bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="loading"
|
||||
class="tw-ms-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
@@ -142,7 +143,7 @@ export const Loading: Story = {
|
||||
export const ScrollingContent: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-dialog title="Scrolling Example" [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding">
|
||||
<span bitDialogContent>
|
||||
Dialog body text goes here.<br />
|
||||
@@ -152,8 +153,8 @@ export const ScrollingContent: Story = {
|
||||
end of sequence!
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button type="button" bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
`,
|
||||
@@ -166,7 +167,7 @@ export const ScrollingContent: Story = {
|
||||
export const TabContent: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-dialog title="Tab Content Example" [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogContent>
|
||||
<bit-tab-group>
|
||||
@@ -176,8 +177,8 @@ export const TabContent: Story = {
|
||||
</bit-tab-group>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button type="button" bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
`,
|
||||
@@ -211,7 +212,7 @@ export const WithCards: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Foo
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
@@ -231,7 +232,7 @@ export const WithCards: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Bar
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
@@ -248,9 +249,10 @@ export const WithCards: Story = {
|
||||
</bit-section>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button type="button" bitButton buttonType="primary" [disabled]="loading">Save</button>
|
||||
<button type="button" bitButton buttonType="secondary" [disabled]="loading">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
[disabled]="loading"
|
||||
class="tw-ms-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
|
||||
@@ -27,13 +27,13 @@ type Story = StoryObj<SimpleDialogComponent & { useDefaultIcon: boolean }>;
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-simple-dialog>
|
||||
<span bitDialogTitle>Alert Dialog</span>
|
||||
<span bitDialogContent>Message Content</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Yes</button>
|
||||
<button bitButton buttonType="secondary">No</button>
|
||||
<button type="button" bitButton buttonType="primary">Yes</button>
|
||||
<button type="button" bitButton buttonType="secondary">No</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
@@ -43,14 +43,14 @@ export const Default: Story = {
|
||||
export const CustomIcon: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-simple-dialog>
|
||||
<i bitDialogIcon class="bwi bwi-star tw-text-3xl tw-text-success" aria-hidden="true"></i>
|
||||
<span bitDialogTitle>Premium Subscription Available</span>
|
||||
<span bitDialogContent> Message Content</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Yes</button>
|
||||
<button bitButton buttonType="secondary">No</button>
|
||||
<button type="button" bitButton buttonType="primary">Yes</button>
|
||||
<button type="button" bitButton buttonType="secondary">No</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
@@ -60,13 +60,13 @@ export const CustomIcon: Story = {
|
||||
export const HideIcon: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-simple-dialog hideIcon>
|
||||
<span bitDialogTitle>Premium Subscription Available</span>
|
||||
<span bitDialogContent> Message Content</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Yes</button>
|
||||
<button bitButton buttonType="secondary">No</button>
|
||||
<button type="button" bitButton buttonType="primary">Yes</button>
|
||||
<button type="button" bitButton buttonType="secondary">No</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
@@ -76,7 +76,7 @@ export const HideIcon: Story = {
|
||||
export const ScrollingContent: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-simple-dialog>
|
||||
<span bitDialogTitle>Alert Dialog</span>
|
||||
<span bitDialogContent>
|
||||
@@ -87,8 +87,8 @@ export const ScrollingContent: Story = {
|
||||
end of sequence!
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Yes</button>
|
||||
<button bitButton buttonType="secondary">No</button>
|
||||
<button type="button" bitButton buttonType="primary">Yes</button>
|
||||
<button type="button" bitButton buttonType="secondary">No</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
@@ -101,13 +101,13 @@ export const ScrollingContent: Story = {
|
||||
export const TextOverflow: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-simple-dialog>
|
||||
<span bitDialogTitle>Alert Dialogdialogdialogdialogdialogdialogdialogdialogdialogdialogdialogdialogdialog</span>
|
||||
<span bitDialogContent>Message Contentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontent</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Yes</button>
|
||||
<button bitButton buttonType="secondary">No</button>
|
||||
<button type="button" bitButton buttonType="primary">Yes</button>
|
||||
<button type="button" bitButton buttonType="secondary">No</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
|
||||
@@ -346,11 +346,11 @@ export const ButtonInputGroup: Story = {
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-label>
|
||||
<button bitPrefix bitIconButton="bwi-star" [appA11yTitle]="'Favorite Label'"></button>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" [appA11yTitle]="'Favorite Label'"></button>
|
||||
<input bitInput placeholder="Placeholder" />
|
||||
<button bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-ellipsis-v" [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" [appA11yTitle]="'Menu Label'"></button>
|
||||
</bit-form-field>
|
||||
`,
|
||||
}),
|
||||
@@ -363,11 +363,11 @@ export const DisabledButtonInputGroup: Story = {
|
||||
template: /*html*/ `
|
||||
<bit-form-field>
|
||||
<bit-label>Label</bit-label>
|
||||
<button bitPrefix bitIconButton="bwi-star" disabled [appA11yTitle]="'Favorite Label'"></button>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" disabled [appA11yTitle]="'Favorite Label'"></button>
|
||||
<input bitInput placeholder="Placeholder" disabled />
|
||||
<button bitSuffix bitIconButton="bwi-eye" disabled [appA11yTitle]="'Hide Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-clone" disabled [appA11yTitle]="'Clone Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" disabled [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" disabled [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
|
||||
</bit-form-field>
|
||||
`,
|
||||
@@ -382,9 +382,9 @@ export const PartiallyDisabledButtonInputGroup: Story = {
|
||||
<bit-form-field>
|
||||
<bit-label>Label</bit-label>
|
||||
<input bitInput placeholder="Placeholder" disabled />
|
||||
<button bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
</bit-form-field>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
<button type="button" ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
@@ -76,7 +76,7 @@ export const NavContrast: Story = {
|
||||
template: /*html*/ `
|
||||
<div class="tw-bg-background-alt3 tw-p-6 tw-w-full tw-inline-block">
|
||||
<!-- <div> used only to provide dark background color -->
|
||||
<button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
<button type="button" ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
@@ -91,7 +91,7 @@ export const Contrast: Story = {
|
||||
template: /*html*/ `
|
||||
<div class="tw-bg-primary-600 tw-p-6 tw-w-full tw-inline-block">
|
||||
<!-- <div> used only to provide dark background color -->
|
||||
<button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
<button type="button" ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -66,7 +66,7 @@ export const Default: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -120,12 +120,12 @@ export const ContentTypes: Story = {
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content href="#">
|
||||
<button type="button" bit-item-content href="#">
|
||||
And I am a button.
|
||||
</button>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content disabled title="I'm a disabled button">
|
||||
<button type="button" bit-item-content disabled title="I'm a disabled button">
|
||||
And I am a disabled button.
|
||||
</button>
|
||||
</bit-item>
|
||||
@@ -187,7 +187,7 @@ export const TextOverflowWrap: Story = {
|
||||
const multipleActionListTemplate = /*html*/ `
|
||||
<bit-item-group aria-label="Multiple Action List">
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -206,7 +206,7 @@ const multipleActionListTemplate = /*html*/ `
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -225,7 +225,7 @@ const multipleActionListTemplate = /*html*/ `
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -244,7 +244,7 @@ const multipleActionListTemplate = /*html*/ `
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -263,7 +263,7 @@ const multipleActionListTemplate = /*html*/ `
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -282,7 +282,7 @@ const multipleActionListTemplate = /*html*/ `
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
@@ -400,7 +400,7 @@ export const VirtualScrolling: Story = {
|
||||
<cdk-virtual-scroll-viewport [itemSize]="54" class="tw-h-[500px]">
|
||||
<bit-item-group aria-label="Virtual Scrolling">
|
||||
<bit-item *cdkVirtualFor="let item of data">
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-2xl tw-text-muted" aria-hidden="true"></i>
|
||||
{{ item }}
|
||||
</button>
|
||||
@@ -429,7 +429,7 @@ export const WithoutBorderRadius: Story = {
|
||||
template: /*html*/ `
|
||||
<bit-layout>
|
||||
<bit-item>
|
||||
<button bit-item-content>
|
||||
<button type="button" bit-item-content>
|
||||
<i slot="start" class="bwi bwi-globe tw-text-3xl tw-text-muted" aria-hidden="true"></i>
|
||||
Foo
|
||||
<span slot="secondary">Bar</span>
|
||||
|
||||
@@ -73,22 +73,22 @@ export const Buttons: Story = {
|
||||
template: /*html*/ `
|
||||
<div class="tw-p-2" [ngClass]="{ 'tw-bg-transparent': linkType != 'contrast', 'tw-bg-primary-600': linkType === 'contrast' }">
|
||||
<div class="tw-block tw-p-2">
|
||||
<button bitLink [linkType]="linkType">Button</button>
|
||||
<button type="button" bitLink [linkType]="linkType">Button</button>
|
||||
</div>
|
||||
<div class="tw-block tw-p-2">
|
||||
<button bitLink [linkType]="linkType">
|
||||
<button type="button" bitLink [linkType]="linkType">
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
Add Icon Button
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-block tw-p-2">
|
||||
<button bitLink [linkType]="linkType">
|
||||
<button type="button" bitLink [linkType]="linkType">
|
||||
<i class="bwi bwi-fw bwi-sm bwi-angle-right" aria-hidden="true"></i>
|
||||
Chevron Icon Button
|
||||
</button>
|
||||
</div>
|
||||
<div class="tw-block tw-p-2">
|
||||
<button bitLink [linkType]="linkType" class="tw-text-sm">Small Button</button>
|
||||
<button type="button" bitLink [linkType]="linkType" class="tw-text-sm">Small Button</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@@ -134,7 +134,7 @@ export const Inline: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<span class="tw-text-main">
|
||||
On the internet paragraphs often contain <a bitLink href="#">inline links</a>, but few know that <button bitLink>buttons</button> can be used for similar purposes.
|
||||
On the internet paragraphs often contain <a bitLink href="#">inline links</a>, but few know that <button type="button" bitLink>buttons</button> can be used for similar purposes.
|
||||
</span>
|
||||
`,
|
||||
}),
|
||||
@@ -147,10 +147,10 @@ export const Disabled: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<button bitLink disabled linkType="primary" class="tw-me-2">Primary</button>
|
||||
<button bitLink disabled linkType="secondary" class="tw-me-2">Secondary</button>
|
||||
<button type="button" bitLink disabled linkType="primary" class="tw-me-2">Primary</button>
|
||||
<button type="button" bitLink disabled linkType="secondary" class="tw-me-2">Secondary</button>
|
||||
<div class="tw-bg-primary-600 tw-p-2 tw-inline-block">
|
||||
<button bitLink disabled linkType="contrast">Contrast</button>
|
||||
<button type="button" bitLink disabled linkType="contrast">Contrast</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ClosedMenu: Story = {
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<div class="tw-h-40">
|
||||
<button bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button>
|
||||
<button type="button" bitButton buttonType="secondary" [bitMenuTriggerFor]="myMenu">Open menu</button>
|
||||
</div>
|
||||
|
||||
<bit-menu #myMenu>
|
||||
|
||||
@@ -86,9 +86,10 @@ export const WithoutRoute: Story = {
|
||||
export const WithChildButtons: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-nav-item text="Hello World Very Cool World" [route]="['']" icon="bwi-collection-shared">
|
||||
<button
|
||||
type="button"
|
||||
slot="end"
|
||||
class="tw-ms-auto"
|
||||
[bitIconButton]="'bwi-pencil-square'"
|
||||
@@ -97,6 +98,7 @@ export const WithChildButtons: Story = {
|
||||
aria-label="option 2"
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
slot="end"
|
||||
class="tw-ms-auto"
|
||||
[bitIconButton]="'bwi-check'"
|
||||
|
||||
@@ -68,7 +68,7 @@ const popoverContent = /*html*/ `
|
||||
<li>Esse labore veniam tempora</li>
|
||||
<li>Adipisicing elit ipsum <a href="#" bitLink>iustolaborum</a></li>
|
||||
</ul>
|
||||
<button bitButton class="tw-mt-4" (click)="triggerRef.closePopover()">Close</button>
|
||||
<button type="button" bitButton class="tw-mt-4" (click)="triggerRef.closePopover()">Close</button>
|
||||
</bit-popover>
|
||||
`;
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const HeaderVariants: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Title with icon button suffix
|
||||
</h2>
|
||||
<button bitIconButton="bwi-refresh" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-refresh" size="small"></button>
|
||||
</bit-section-header>
|
||||
`,
|
||||
}),
|
||||
@@ -88,7 +88,7 @@ export const HeaderEndSlotVariants: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Title with end slot icon button
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
`,
|
||||
}),
|
||||
@@ -103,7 +103,7 @@ export const HeaderWithPadding: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Card as immediate sibling
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-card>
|
||||
<h3 bitTypography="h3">bit-section-header has padding</h3>
|
||||
@@ -114,7 +114,7 @@ export const HeaderWithPadding: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Card nested in immediate sibling
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<div>
|
||||
<bit-card>
|
||||
@@ -127,7 +127,7 @@ export const HeaderWithPadding: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Item as immediate sibling
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-item>
|
||||
<bit-item-content bitTypography="body1">bit-section-header has padding</bit-item-content>
|
||||
@@ -138,7 +138,7 @@ export const HeaderWithPadding: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Item nested in immediate sibling
|
||||
</h2>
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-item-group>
|
||||
<bit-item>
|
||||
|
||||
@@ -153,7 +153,7 @@ export const PreserveContentTabs: Story = {
|
||||
export const KeyboardNavigation: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">
|
||||
<bit-tab label="Form Tab">
|
||||
<p>
|
||||
@@ -174,7 +174,7 @@ export const KeyboardNavigation: Story = {
|
||||
<p>This tab has no focusable content, but the panel should still be focusable</p>
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
<button bitButton buttonType="primary" class="tw-mt-5">External Button</button>
|
||||
<button type="button" bitButton buttonType="primary" class="tw-mt-5">External Button</button>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||
@@ -20,7 +21,7 @@ import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.
|
||||
import { ImportResult } from "../models/import-result";
|
||||
|
||||
export abstract class BaseImporter {
|
||||
organizationId: string = null;
|
||||
organizationId: OrganizationId = null;
|
||||
|
||||
// FIXME: This should be replaced by injecting the log service.
|
||||
protected logService: LogService = new ConsoleLogService(false);
|
||||
@@ -279,7 +280,7 @@ export abstract class BaseImporter {
|
||||
result.collections = result.folders.map((f) => {
|
||||
const collection = new CollectionView();
|
||||
collection.name = f.name;
|
||||
collection.id = f.id ?? undefined; // folder id may be null, which is not suitable for collections.
|
||||
collection.id = (f.id as CollectionId) ?? undefined; // folder id may be null, which is not suitable for collections.
|
||||
return collection;
|
||||
});
|
||||
result.folderRelationships = [];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
testData as TestData,
|
||||
@@ -103,7 +104,7 @@ describe("Keeper CSV Importer", () => {
|
||||
});
|
||||
|
||||
it("should create collections, with subcollections and relationships", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
@@ -126,7 +127,7 @@ describe("Keeper CSV Importer", () => {
|
||||
});
|
||||
|
||||
it("should create collections tree, with child collections and relationships", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(testDataMultiCollection);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { testData as TestData } from "../spec-data/keeper-json/testdata.json";
|
||||
|
||||
@@ -93,7 +94,7 @@ describe("Keeper Json Importer", () => {
|
||||
});
|
||||
|
||||
it("should create collections if part of an organization", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(testDataJson);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
credentialsData,
|
||||
@@ -79,7 +80,7 @@ describe("Netwrix Password Secure CSV Importer", () => {
|
||||
});
|
||||
|
||||
it("should parse an item and create a collection when importing into an organization", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(credentialsData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
@@ -93,7 +94,7 @@ describe("Netwrix Password Secure CSV Importer", () => {
|
||||
});
|
||||
|
||||
it("should parse multiple collections", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(credentialsDataWithFolders);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { SecureNoteType, CipherType, FieldType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
|
||||
@@ -224,7 +225,7 @@ describe("NordPass CSV Importer", () => {
|
||||
});
|
||||
|
||||
it("should parse an item and create a collection if organizationId is set", async () => {
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(secureNoteData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||
|
||||
@@ -691,7 +692,7 @@ describe("1Password 1Pux Importer", () => {
|
||||
|
||||
it("should create collections if part of an organization", async () => {
|
||||
const importer = new OnePassword1PuxImporter();
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(SanitizedExportJson);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { ImportResult } from "../../models/import-result";
|
||||
@@ -146,7 +147,7 @@ describe("PasswordXPCsvImporter", () => {
|
||||
});
|
||||
|
||||
it("should convert folders to collections when importing into an organization", async () => {
|
||||
importer.organizationId = "someOrg";
|
||||
importer.organizationId = "someOrg" as OrganizationId;
|
||||
const result: ImportResult = await importer.parse(withFolders);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(5);
|
||||
@@ -172,7 +173,7 @@ describe("PasswordXPCsvImporter", () => {
|
||||
});
|
||||
|
||||
it("should convert multi-level folders to collections when importing into an organization", async () => {
|
||||
importer.organizationId = "someOrg";
|
||||
importer.organizationId = "someOrg" as OrganizationId;
|
||||
const result: ImportResult = await importer.parse(withMultipleFolders);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(5);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, SecureNoteType } from "@bitwarden/common/vault/enums";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { CipherType } from "@bitwarden/sdk-internal";
|
||||
@@ -485,7 +486,7 @@ describe("Password Depot 17 Xml Importer", () => {
|
||||
|
||||
it("should parse groups nodes into collections when importing into an organization", async () => {
|
||||
const importer = new PasswordDepot17XmlImporter();
|
||||
importer.organizationId = "someOrgId";
|
||||
importer.organizationId = "someOrgId" as OrganizationId;
|
||||
const collection = new CollectionView();
|
||||
collection.name = "tempDB";
|
||||
const actual = [collection];
|
||||
|
||||
@@ -2,6 +2,7 @@ import { MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { testData } from "../spec-data/protonpass-json/protonpass.json";
|
||||
@@ -90,7 +91,7 @@ describe("Protonpass Json Importer", () => {
|
||||
|
||||
it("should create collections if part of an organization", async () => {
|
||||
const testDataJson = JSON.stringify(testData);
|
||||
importer.organizationId = Utils.newGuid();
|
||||
importer.organizationId = Utils.newGuid() as OrganizationId;
|
||||
const result = await importer.parse(testDataJson);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||
|
||||
@@ -236,7 +237,7 @@ describe("PSONO JSON Importer", () => {
|
||||
|
||||
it("should create collections if part of an organization", async () => {
|
||||
const importer = new PsonoJsonImporter();
|
||||
importer.organizationId = "someOrg";
|
||||
importer.organizationId = "someOrg" as OrganizationId;
|
||||
const result = await importer.parse(FoldersTestDataJson);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
@@ -73,7 +74,7 @@ describe("Zoho Vault CSV Importer", () => {
|
||||
|
||||
it("should create collection and assign ciphers when importing into an organization", async () => {
|
||||
const importer = new ZohoVaultCsvImporter();
|
||||
importer.organizationId = "someOrgId";
|
||||
importer.organizationId = "someOrgId" as OrganizationId;
|
||||
const result = await importer.parse(samplezohovaultcsvdata);
|
||||
expect(result != null).toBe(true);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -67,7 +68,7 @@ describe("ImportService", () => {
|
||||
describe("getImporterInstance", () => {
|
||||
describe("Get bitPasswordProtected importer", () => {
|
||||
let importer: Importer;
|
||||
const organizationId = Utils.newGuid();
|
||||
const organizationId = Utils.newGuid() as OrganizationId;
|
||||
const password = Utils.newGuid();
|
||||
const promptForPassword_callback = async () => {
|
||||
return password;
|
||||
@@ -98,7 +99,7 @@ describe("ImportService", () => {
|
||||
});
|
||||
|
||||
describe("setImportTarget", () => {
|
||||
const organizationId = Utils.newGuid();
|
||||
const organizationId = Utils.newGuid() as OrganizationId;
|
||||
|
||||
let importResult: ImportResult;
|
||||
|
||||
@@ -145,19 +146,19 @@ describe("ImportService", () => {
|
||||
});
|
||||
|
||||
const mockImportTargetCollection = new CollectionView();
|
||||
mockImportTargetCollection.id = "myImportTarget";
|
||||
mockImportTargetCollection.id = "myImportTarget" as CollectionId;
|
||||
mockImportTargetCollection.name = "myImportTarget";
|
||||
mockImportTargetCollection.organizationId = organizationId;
|
||||
|
||||
const mockCollection1 = new CollectionView();
|
||||
mockCollection1.id = "collection1";
|
||||
mockCollection1.id = "collection1" as CollectionId;
|
||||
mockCollection1.name = "collection1";
|
||||
mockCollection1.organizationId = organizationId;
|
||||
|
||||
const mockCollection2 = new CollectionView();
|
||||
mockCollection1.id = "collection2";
|
||||
mockCollection1.name = "collection2";
|
||||
mockCollection1.organizationId = organizationId;
|
||||
mockCollection2.id = "collection2" as CollectionId;
|
||||
mockCollection2.name = "collection2";
|
||||
mockCollection2.organizationId = organizationId;
|
||||
|
||||
it("passing importTarget adds it to collections", async () => {
|
||||
await importService["setImportTarget"](
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response"
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums";
|
||||
@@ -130,7 +131,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
async import(
|
||||
importer: Importer,
|
||||
fileContents: string,
|
||||
organizationId: string = null,
|
||||
organizationId: OrganizationId = null,
|
||||
selectedImportTarget: FolderView | CollectionView = null,
|
||||
canAccessImportExport: boolean,
|
||||
): Promise<ImportResult> {
|
||||
@@ -204,7 +205,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
getImporter(
|
||||
format: ImportType | "bitwardenpasswordprotected",
|
||||
promptForPassword_callback: () => Promise<string>,
|
||||
organizationId: string = null,
|
||||
organizationId: OrganizationId = null,
|
||||
): Importer {
|
||||
if (promptForPassword_callback == null) {
|
||||
return null;
|
||||
@@ -393,7 +394,10 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return await this.importApiService.postImportCiphers(request);
|
||||
}
|
||||
|
||||
private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) {
|
||||
private async handleOrganizationalImport(
|
||||
importResult: ImportResult,
|
||||
organizationId: OrganizationId,
|
||||
) {
|
||||
const request = new ImportOrganizationCiphersRequest();
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { SelectComponent } from "@bitwarden/components";
|
||||
|
||||
@@ -33,9 +34,9 @@ const createMockCollection = (
|
||||
canEdit = true,
|
||||
): CollectionView => {
|
||||
return {
|
||||
id,
|
||||
id: id as CollectionId,
|
||||
name,
|
||||
organizationId,
|
||||
organizationId: organizationId as OrganizationId,
|
||||
externalId: "",
|
||||
readOnly,
|
||||
hidePasswords: false,
|
||||
|
||||
Reference in New Issue
Block a user