1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-14 10:26:19 +01:00

Merge branch 'main' into auth/pm-14369/hide-account-switcher-if-on-login-page-and-not-logged-in

This commit is contained in:
Alec Rippberger 2024-11-04 11:35:47 -06:00 committed by GitHub
commit 9c824628aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 4615 additions and 7368 deletions

View File

@ -73,7 +73,7 @@
"reviewers": ["team:team-admin-console-dev"]
},
{
"matchPackageNames": ["@types/node-ipc", "node-ipc", "qrious", "regedit"],
"matchPackageNames": ["@types/node-ipc", "node-ipc", "qrious"],
"description": "Auth owned dependencies",
"commitMessagePrefix": "[deps] Auth:",
"reviewers": ["team:team-auth-dev"]
@ -258,5 +258,5 @@
"reviewers": ["team:team-vault-dev"]
}
],
"ignoreDeps": ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm", "regedit"]
"ignoreDeps": ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm"]
}

View File

@ -128,10 +128,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "test-storybook:build:production"
"buildTarget": "test-storybook:build:production"
},
"development": {
"browserTarget": "test-storybook:build:development"
"buildTarget": "test-storybook:build:development"
}
},
"defaultConfiguration": "development"

View File

@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
"version": "2024.10.1",
"version": "2024.11.0",
"scripts": {
"build": "cross-env MANIFEST_VERSION=3 webpack",
"build:mv2": "webpack",

View File

@ -32,6 +32,7 @@ import {
} from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -106,6 +107,7 @@ describe("OverlayBackground", () => {
let selectedThemeMock$: BehaviorSubject<ThemeType>;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
let themeStateService: MockProxy<ThemeStateService>;
let totpService: MockProxy<TotpService>;
let overlayBackground: OverlayBackground;
let portKeyForTabSpy: Record<number, string>;
let pageDetailsForTabSpy: PageDetailsForTab;
@ -184,6 +186,7 @@ describe("OverlayBackground", () => {
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
themeStateService = mock<ThemeStateService>();
themeStateService.selectedTheme$ = selectedThemeMock$;
totpService = mock<TotpService>();
overlayBackground = new OverlayBackground(
logService,
cipherService,
@ -198,6 +201,7 @@ describe("OverlayBackground", () => {
fido2ActiveRequestManager,
inlineMenuFieldQualificationService,
themeStateService,
totpService,
generatedPasswordCallbackMock,
addPasswordCallbackMock,
);

View File

@ -33,6 +33,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
@ -217,6 +218,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
private fido2ActiveRequestManager: Fido2ActiveRequestManager,
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
private themeStateService: ThemeStateService,
private totpService: TotpService,
private generatePasswordCallback: () => Promise<string>,
private addPasswordCallback: (password: string) => Promise<void>,
) {
@ -1058,7 +1060,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
}
const cipher = this.inlineMenuCiphers.get(inlineMenuCipherId);
if (usePasskey && cipher.login?.hasFido2Credentials) {
await this.authenticatePasskeyCredential(
sender,
@ -1066,6 +1067,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
);
this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher);
if (cipher.login?.totp) {
this.platformUtilsService.copyToClipboard(
await this.totpService.getCode(cipher.login.totp),
);
}
return;
}

View File

@ -1678,6 +1678,7 @@ export default class MainBackground {
this.fido2ActiveRequestManager,
inlineMenuFieldQualificationService,
this.themeStateService,
this.totpService,
() => this.generatePassword(),
(password) => this.addPasswordToHistory(password),
);

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.10.1",
"version": "2024.11.0",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.10.1",
"version": "2024.11.0",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@ -1,7 +1,7 @@
{
"name": "@bitwarden/cli",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.10.0",
"version": "2024.11.0",
"keywords": [
"bitwarden",
"password",
@ -80,7 +80,7 @@
"papaparse": "5.4.1",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldts": "6.1.56",
"tldts": "6.1.58",
"zxcvbn": "4.4.2"
}
}

View File

@ -546,6 +546,7 @@ dependencies = [
"napi-derive",
"tokio",
"tokio-util",
"windows-registry",
]
[[package]]
@ -2226,7 +2227,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-result 0.1.2",
"windows-targets 0.52.6",
]
@ -2252,6 +2253,17 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-registry"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a"
dependencies = [
"windows-result 0.2.0",
"windows-strings",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.1.2"
@ -2261,6 +2273,24 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978d65aedf914c664c510d9de43c8fd85ca745eaff1ed53edf409b479e441663"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View File

@ -21,5 +21,8 @@ napi-derive = "=2.16.12"
tokio = { version = "1.38.0" }
tokio-util = "0.7.11"
[target.'cfg(windows)'.dependencies]
windows-registry = "=0.3.0"
[build-dependencies]
napi-build = "=2.1.3"

View File

@ -51,6 +51,10 @@ export declare namespace powermonitors {
export function onLock(callback: (err: Error | null, ) => any): Promise<void>
export function isLockMonitorAvailable(): Promise<boolean>
}
export declare namespace windows_registry {
export function createKey(key: string, subkey: string, value: string): Promise<void>
export function deleteKey(key: string, subkey: string): Promise<void>
}
export declare namespace ipc {
export interface IpcMessage {
clientId: number

View File

@ -1,5 +1,8 @@
#[macro_use]
extern crate napi_derive;
mod registry;
#[napi]
pub mod passwords {
/// Fetch the stored password from the keychain.
@ -190,6 +193,21 @@ pub mod powermonitors {
}
#[napi]
pub mod windows_registry {
#[napi]
pub async fn create_key(key: String, subkey: String, value: String) -> napi::Result<()> {
crate::registry::create_key(&key, &subkey, &value)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[napi]
pub async fn delete_key(key: String, subkey: String) -> napi::Result<()> {
crate::registry::delete_key(&key, &subkey)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
}
#[napi]
pub mod ipc {
use desktop_core::ipc::server::{Message, MessageType};

View File

@ -0,0 +1,9 @@
use anyhow::{bail, Result};
pub fn create_key(_key: &str, _subkey: &str, _value: &str) -> Result<()> {
bail!("Not implemented")
}
pub fn delete_key(_key: &str, _subkey: &str) -> Result<()> {
bail!("Not implemented")
}

View File

@ -0,0 +1,4 @@
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(not(target_os = "windows"), path = "dummy.rs")]
mod internal;
pub use internal::*;

View File

@ -0,0 +1,29 @@
use anyhow::{bail, Result};
fn convert_key(key: &str) -> Result<&'static windows_registry::Key> {
Ok(match key.to_uppercase().as_str() {
"HKEY_CURRENT_USER" | "HKCU" => windows_registry::CURRENT_USER,
"HKEY_LOCAL_MACHINE" | "HKLM" => windows_registry::LOCAL_MACHINE,
"HKEY_CLASSES_ROOT" | "HKCR" => windows_registry::CLASSES_ROOT,
_ => bail!("Invalid key"),
})
}
pub fn create_key(key: &str, subkey: &str, value: &str) -> Result<()> {
let key = convert_key(key)?;
let subkey = key.create(subkey)?;
const DEFAULT: &str = "";
subkey.set_string(DEFAULT, value)?;
Ok(())
}
pub fn delete_key(key: &str, subkey: &str) -> Result<()> {
let key = convert_key(key)?;
key.remove_tree(subkey)?;
Ok(())
}

View File

@ -90,13 +90,6 @@
"electronUpdaterCompatibility": ">=0.0.1",
"target": ["portable", "nsis-web", "appx"],
"sign": "./sign.js",
"extraResources": [
{
"from": "../../node_modules/regedit/vbs",
"to": "regedit/vbs",
"filter": ["**/*"]
}
],
"extraFiles": [
{
"from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe",

View File

@ -1,7 +1,7 @@
{
"name": "@bitwarden/desktop",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.10.3",
"version": "2024.11.0",
"keywords": [
"bitwarden",
"password",

View File

@ -1,12 +1,11 @@
import { existsSync, promises as fs } from "fs";
import { homedir, userInfo } from "os";
import * as path from "path";
import * as util from "util";
import { ipcMain } from "electron";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ipc } from "@bitwarden/desktop-napi";
import { ipc, windows_registry } from "@bitwarden/desktop-napi";
import { isDev } from "../utils";
@ -142,12 +141,12 @@ export class NativeMessagingMain {
await this.writeManifest(path.join(destination, "chrome.json"), chromeJson);
const nmhs = this.getWindowsNMHS();
for (const [key, value] of Object.entries(nmhs)) {
for (const [name, [key, subkey]] of Object.entries(nmhs)) {
let manifestPath = path.join(destination, "chrome.json");
if (key === "Firefox") {
if (name === "Firefox") {
manifestPath = path.join(destination, "firefox.json");
}
await this.createWindowsRegistry(value, manifestPath);
await windows_registry.createKey(key, subkey, manifestPath);
}
break;
}
@ -225,8 +224,8 @@ export class NativeMessagingMain {
await this.removeIfExists(path.join(this.userPath, "browsers", "chrome.json"));
const nmhs = this.getWindowsNMHS();
for (const [, value] of Object.entries(nmhs)) {
await this.deleteWindowsRegistry(value);
for (const [, [key, subkey]] of Object.entries(nmhs)) {
await windows_registry.deleteKey(key, subkey);
}
break;
}
@ -274,11 +273,14 @@ export class NativeMessagingMain {
private getWindowsNMHS() {
return {
Firefox: "HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden",
Chrome: "HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden",
Chromium: "HKCU\\SOFTWARE\\Chromium\\NativeMessagingHosts\\com.8bit.bitwarden",
Firefox: ["HKCU", "SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden"],
Chrome: ["HKCU", "SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden"],
Chromium: ["HKCU", "SOFTWARE\\Chromium\\NativeMessagingHosts\\com.8bit.bitwarden"],
// Edge uses the same registry key as Chrome as a fallback, but it's has its own separate key as well.
"Microsoft Edge": "HKCU\\SOFTWARE\\Microsoft\\Edge\\NativeMessagingHosts\\com.8bit.bitwarden",
"Microsoft Edge": [
"HKCU",
"SOFTWARE\\Microsoft\\Edge\\NativeMessagingHosts\\com.8bit.bitwarden",
],
};
}
@ -419,52 +421,6 @@ export class NativeMessagingMain {
return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`);
}
private getRegeditInstance() {
// eslint-disable-next-line
const regedit = require("regedit");
regedit.setExternalVBSLocation(path.join(path.dirname(this.exePath), "resources/regedit/vbs"));
return regedit;
}
private async createWindowsRegistry(location: string, jsonFile: string) {
const regedit = this.getRegeditInstance();
const createKey = util.promisify(regedit.createKey);
const putValue = util.promisify(regedit.putValue);
this.logService.debug(`Adding registry: ${location}`);
await createKey(location);
// Insert path to manifest
const obj: any = {};
obj[location] = {
default: {
value: jsonFile,
type: "REG_DEFAULT",
},
};
return putValue(obj);
}
private async deleteWindowsRegistry(key: string) {
const regedit = this.getRegeditInstance();
const list = util.promisify(regedit.list);
const deleteKey = util.promisify(regedit.deleteKey);
this.logService.debug(`Removing registry: ${key}`);
try {
await list(key);
await deleteKey(key);
} catch {
this.logService.error(`Unable to delete registry key: ${key}`);
}
}
private homedir() {
if (process.platform === "darwin") {
return userInfo().homedir;

View File

@ -1,12 +1,12 @@
{
"name": "@bitwarden/desktop",
"version": "2024.10.3",
"version": "2024.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop",
"version": "2024.10.3",
"version": "2024.11.0",
"license": "GPL-3.0",
"dependencies": {
"@bitwarden/desktop-napi": "file:../desktop_native/napi",

View File

@ -2,7 +2,7 @@
"name": "@bitwarden/desktop",
"productName": "Bitwarden",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.10.3",
"version": "2024.11.0",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2024.10.5",
"version": "2024.11.0",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@ -23,15 +23,14 @@
<bit-tab [label]="'role' | i18n">
<ng-container *ngIf="!editMode">
<p bitTypography="body1">{{ "inviteUserDesc" | i18n }}</p>
<bit-form-field *ngIf="remainingSeats$ | async as remainingSeats">
<bit-form-field>
<bit-label>{{ "email" | i18n }}</bit-label>
<input id="emails" type="text" appAutoFocus bitInput formControlName="emails" />
<bit-hint *ngIf="remainingSeats > 1; else singleSeat">{{
"inviteMultipleEmailDesc" | i18n: remainingSeats
<bit-hint>{{
"inviteMultipleEmailDesc"
| i18n
: (organization.productTierType === ProductTierType.TeamsStarter ? "10" : "20")
}}</bit-hint>
<ng-template #singleSeat>
<bit-hint>{{ "inviteSingleEmailDesc" | i18n: remainingSeats }}</bit-hint>
</ng-template>
</bit-form-field>
</ng-container>
<bit-radio-group formControlName="type">
@ -265,6 +264,16 @@
<button
*ngIf="editMode"
type="button"
bitIconButton="bwi-close"
buttonType="danger"
bitFormButton
[appA11yTitle]="'remove' | i18n"
[bitAction]="remove"
[disabled]="loading"
></button>
<button
*ngIf="editMode && params.managedByOrganization === true"
type="button"
bitIconButton="bwi-trash"
buttonType="danger"
bitFormButton

View File

@ -65,6 +65,7 @@ export interface MemberDialogParams {
isOnSecretsManagerStandalone: boolean;
initialTab?: MemberDialogTab;
numConfirmedMembers: number;
managedByOrganization?: boolean;
}
export enum MemberDialogResult {
@ -89,7 +90,6 @@ export class MemberDialogComponent implements OnDestroy {
PermissionMode = PermissionMode;
showNoMasterPasswordWarning = false;
isOnSecretsManagerStandalone: boolean;
remainingSeats$: Observable<number>;
protected organization$: Observable<Organization>;
protected collectionAccessItems: AccessItemView[] = [];
@ -251,10 +251,6 @@ export class MemberDialogComponent implements OnDestroy {
this.loading = false;
});
this.remainingSeats$ = this.organization$.pipe(
map((organization) => organization.seats - this.params.numConfirmedMembers),
);
}
private setFormValidators(organization: Organization) {
@ -469,7 +465,7 @@ export class MemberDialogComponent implements OnDestroy {
this.close(MemberDialogResult.Saved);
};
delete = async () => {
remove = async () => {
if (!this.editMode) {
return;
}
@ -566,6 +562,39 @@ export class MemberDialogComponent implements OnDestroy {
this.close(MemberDialogResult.Restored);
};
delete = async () => {
if (!this.editMode) {
return;
}
const confirmed = await this.dialogService.openSimpleDialog({
title: {
key: "deleteOrganizationUser",
placeholders: [this.params.name],
},
content: { key: "deleteOrganizationUserWarning" },
type: "warning",
acceptButtonText: { key: "delete" },
cancelButtonText: { key: "cancel" },
});
if (!confirmed) {
return false;
}
await this.organizationUserApiService.deleteOrganizationUser(
this.params.organizationId,
this.params.organizationUserId,
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("organizationUserDeleted", this.params.name),
});
this.close(MemberDialogResult.Deleted);
};
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();

View File

@ -320,6 +320,17 @@
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "remove" | i18n }}
</span>
</button>
<button
*ngIf="u.managedByOrganization === true"
type="button"
bitMenuItem
(click)="deleteUser(u)"
>
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</td>
</tr>

View File

@ -518,6 +518,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
initialTab: initialTab,
numConfirmedMembers: this.dataSource.confirmedUserCount,
managedByOrganization: user?.managedByOrganization,
},
});
@ -725,6 +726,40 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
return true;
}
async deleteUser(user: OrganizationUserView) {
const confirmed = await this.dialogService.openSimpleDialog({
title: {
key: "deleteOrganizationUser",
placeholders: [this.userNamePipe.transform(user)],
},
content: { key: "deleteOrganizationUserWarning" },
type: "warning",
acceptButtonText: { key: "delete" },
cancelButtonText: { key: "cancel" },
});
if (!confirmed) {
return false;
}
this.actionPromise = this.organizationUserApiService.deleteOrganizationUser(
this.organization.id,
user.id,
);
try {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("organizationUserDeleted", this.userNamePipe.transform(user)),
});
this.dataSource.removeUser(user);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) {
return this.dialogService.openSimpleDialog({
title: {

View File

@ -21,7 +21,13 @@
>
{{ "purgeVault" | i18n }}
</button>
<button type="button" bitButton buttonType="danger" [bitAction]="deleteAccount">
<button
*ngIf="showDeleteAccount$ | async"
type="button"
bitButton
buttonType="danger"
[bitAction]="deleteAccount"
>
{{ "deleteAccount" | i18n }}
</button>
</app-danger-zone>

View File

@ -23,6 +23,7 @@ export class AccountComponent implements OnInit {
showChangeEmail$: Observable<boolean>;
showPurgeVault$: Observable<boolean>;
showDeleteAccount$: Observable<boolean>;
constructor(
private modalService: ModalService,
@ -63,6 +64,16 @@ export class AccountComponent implements OnInit {
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
),
);
this.showDeleteAccount$ = combineLatest([
isAccountDeprovisioningEnabled$,
userIsManagedByOrganization$,
]).pipe(
map(
([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
),
);
}
async deauthorizeSessions() {

View File

@ -3218,9 +3218,6 @@
}
}
},
"inviteSingleEmailDesc": {
"message": "You have 1 invite remaining."
},
"userUsingTwoStep": {
"message": "This user is using two-step login to protect their account."
},
@ -9557,5 +9554,31 @@
},
"single-org-revoked-user-warning": {
"message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations."
},
"deleteOrganizationUser": {
"message": "Delete $NAME$",
"placeholders": {
"name": {
"content": "$1",
"example": "John Doe"
},
"description": "Title for the delete organization user dialog"
}
},
"deleteOrganizationUserWarning": {
"message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.",
"description": "Warning for the delete organization user dialog"
},
"organizationUserDeleted": {
"message": "Deleted $NAME$",
"placeholders": {
"name": {
"content": "$1",
"example": "John Doe"
}
}
},
"organizationUserDeletedDesc": {
"message": "The user was removed from the organization and all associated user data has been deleted."
}
}

View File

@ -1,10 +1,9 @@
import "core-js/stable";
require("zone.js/dist/zone");
import "zone.js";
if (process.env.NODE_ENV === "production") {
// Production
} else {
// Development and test
Error["stackTraceLimit"] = Infinity;
require("zone.js/dist/long-stack-trace-zone");
}

View File

@ -275,4 +275,11 @@ export abstract class OrganizationUserApiService {
organizationId: string,
ids: string[],
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Remove an organization user's access to the organization and delete their account data
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
*/
abstract deleteOrganizationUser(organizationId: string, id: string): Promise<void>;
}

View File

@ -359,4 +359,14 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
deleteOrganizationUser(organizationId: string, id: string): Promise<void> {
return this.apiService.send(
"DELETE",
"/organizations/" + organizationId + "/users/" + id + "/delete-account",
null,
true,
false,
);
}
}

View File

@ -220,7 +220,8 @@ describe("DefaultActiveUserState", () => {
it("should not emit a previous users value if that user is no longer active", async () => {
const user1Data: Jsonify<TestState> = {
date: "2020-09-21T13:14:17.648Z",
array: ["value"],
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
array: ["value"] as any,
};
const user2Data: Jsonify<TestState> = {
date: "2020-09-21T13:14:17.648Z",

View File

@ -192,7 +192,8 @@ describe("KeyDefinition", () => {
expect(arrayDefinition).toBeTruthy();
expect(arrayDefinition.deserializer).toBeTruthy();
const deserializedValue = arrayDefinition.deserializer([false, true]);
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
const deserializedValue = arrayDefinition.deserializer([false, true] as any);
expect(deserializedValue).toBeTruthy();
expect(deserializedValue).toHaveLength(2);

View File

@ -151,6 +151,7 @@ describe("Login DTO", () => {
password: "myPassword" as EncryptedString,
passwordRevisionDate: passwordRevisionDate.toISOString(),
totp: "myTotp" as EncryptedString,
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
fido2Credentials: [
{
credentialId: "keyId" as EncryptedString,
@ -167,7 +168,7 @@ describe("Login DTO", () => {
discoverable: "discoverable" as EncryptedString,
creationDate: fido2CreationDate.toISOString(),
},
],
] as any,
});
expect(actual).toEqual({

View File

@ -71,7 +71,7 @@ The content can be a button, anchor, or static container.
<bit-item>
<button bit-item-content type="button">
<bit-avatar slot="start" text="Foo"></bit-avatar>
foo@bitwarden.com
foo&#64;bitwarden.com
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
<ng-container slot="secondary">
<div>Bitwarden.com</div>

View File

@ -94,7 +94,7 @@ export const ContentSlots: Story = {
slot="start"
[text]="'Foo'"
></bit-avatar>
foo@bitwarden.com
foo&#64;bitwarden.com
<ng-container slot="secondary">
<div>Bitwarden.com</div>
<div><em>locked</em></div>
@ -285,37 +285,37 @@ export const SingleActionList: Story = {
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Foobar
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
</bit-item-group>
`,
@ -332,14 +332,14 @@ export const SingleActionWithBadge: Story = {
Foobar
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
<bit-item>
<a bit-item-content href="#">
Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</a>
</bit-item>
</bit-item-group>
`,

11511
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -35,19 +35,20 @@
"libs/*"
],
"devDependencies": {
"@angular-devkit/build-angular": "16.2.14",
"@angular-eslint/eslint-plugin": "16.3.1",
"@angular-eslint/eslint-plugin-template": "16.3.1",
"@angular-eslint/template-parser": "16.3.1",
"@angular/cli": "16.2.14",
"@angular/compiler-cli": "16.2.12",
"@angular/elements": "16.2.12",
"@angular-devkit/build-angular": "17.3.9",
"@angular-eslint/eslint-plugin": "17.5.3",
"@angular-eslint/eslint-plugin-template": "17.5.3",
"@angular-eslint/schematics": "17.5.3",
"@angular-eslint/template-parser": "17.5.3",
"@angular/cli": "17.3.9",
"@angular/compiler-cli": "17.3.12",
"@angular/elements": "17.3.12",
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.25",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.0",
"@ngtools/webpack": "16.2.14",
"@ngtools/webpack": "17.3.9",
"@storybook/addon-a11y": "8.2.9",
"@storybook/addon-actions": "8.2.9",
"@storybook/addon-designs": "8.0.3",
@ -58,7 +59,7 @@
"@storybook/manager-api": "8.2.9",
"@storybook/theming": "8.2.9",
"@types/argon2-browser": "1.18.4",
"@types/chrome": "0.0.272",
"@types/chrome": "0.0.280",
"@types/firefox-webext-browser": "120.0.4",
"@types/inquirer": "8.2.10",
"@types/jest": "29.5.12",
@ -127,7 +128,6 @@
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
"process": "0.11.10",
"regedit": "3.0.3",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
"sass": "1.74.1",
@ -139,7 +139,7 @@
"ts-loader": "9.5.1",
"tsconfig-paths-webpack-plugin": "4.1.0",
"type-fest": "2.19.0",
"typescript": "5.1.6",
"typescript": "5.4.2",
"url": "0.11.4",
"util": "0.12.5",
"wait-on": "8.0.1",
@ -149,22 +149,22 @@
"webpack-node-externals": "3.0.0"
},
"dependencies": {
"@angular/animations": "16.2.12",
"@angular/cdk": "16.2.14",
"@angular/common": "16.2.12",
"@angular/compiler": "16.2.12",
"@angular/core": "16.2.12",
"@angular/forms": "16.2.12",
"@angular/platform-browser": "16.2.12",
"@angular/platform-browser-dynamic": "16.2.12",
"@angular/router": "16.2.12",
"@angular/animations": "17.3.12",
"@angular/cdk": "17.3.10",
"@angular/common": "17.3.12",
"@angular/compiler": "17.3.12",
"@angular/core": "17.3.12",
"@angular/forms": "17.3.12",
"@angular/platform-browser": "17.3.12",
"@angular/platform-browser-dynamic": "17.3.12",
"@angular/router": "17.3.12",
"@bitwarden/sdk-internal": "0.1.7",
"@electron/fuses": "1.8.0",
"@koa/multer": "3.0.2",
"@koa/router": "13.1.0",
"@microsoft/signalr": "8.0.7",
"@microsoft/signalr-protocol-msgpack": "8.0.7",
"@ng-select/ng-select": "11.2.0",
"@ng-select/ng-select": "12.0.7",
"argon2": "0.41.1",
"argon2-browser": "1.18.0",
"big-integer": "1.6.52",
@ -187,8 +187,8 @@
"lowdb": "1.0.0",
"lunr": "2.3.9",
"multer": "1.4.5-lts.1",
"ngx-infinite-scroll": "16.0.0",
"ngx-toastr": "17.0.2",
"ngx-infinite-scroll": "17.0.1",
"ngx-toastr": "18.0.0",
"node-fetch": "2.6.12",
"node-forge": "1.3.1",
"nord": "0.2.1",
@ -202,9 +202,9 @@
"qrious": "4.0.2",
"rxjs": "7.8.1",
"tabbable": "6.2.0",
"tldts": "6.1.56",
"tldts": "6.1.58",
"utf-8-validate": "6.0.4",
"zone.js": "0.13.3",
"zone.js": "0.14.10",
"zxcvbn": "4.4.2"
},
"overrides": {