1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-27 01:53:23 +00:00

Merge branch 'main' into km/userkey-rotation-v2

This commit is contained in:
Bernd Schoolmann
2025-02-07 11:59:40 +01:00
committed by GitHub
84 changed files with 840 additions and 1518 deletions

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { UserId } from "@bitwarden/common/types/guid";
export abstract class SsoLoginServiceAbstraction {
@@ -13,7 +11,7 @@ export abstract class SsoLoginServiceAbstraction {
* @see https://datatracker.ietf.org/doc/html/rfc7636
* @returns The code verifier used for SSO.
*/
getCodeVerifier: () => Promise<string>;
abstract getCodeVerifier: () => Promise<string>;
/**
* Sets the code verifier used for SSO.
*
@@ -23,7 +21,7 @@ export abstract class SsoLoginServiceAbstraction {
* and verify it matches the one sent in the request for the `authorization_code`.
* @see https://datatracker.ietf.org/doc/html/rfc7636
*/
setCodeVerifier: (codeVerifier: string) => Promise<void>;
abstract setCodeVerifier: (codeVerifier: string) => Promise<void>;
/**
* Gets the value of the SSO state.
*
@@ -33,7 +31,7 @@ export abstract class SsoLoginServiceAbstraction {
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
* @returns The SSO state.
*/
getSsoState: () => Promise<string>;
abstract getSsoState: () => Promise<string>;
/**
* Sets the value of the SSO state.
*
@@ -42,7 +40,7 @@ export abstract class SsoLoginServiceAbstraction {
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
*/
setSsoState: (ssoState: string) => Promise<void>;
abstract setSsoState: (ssoState: string) => Promise<void>;
/**
* Gets the value of the user's organization sso identifier.
*
@@ -50,20 +48,20 @@ export abstract class SsoLoginServiceAbstraction {
* Do not use this value outside of the SSO login flow.
* @returns The user's organization identifier.
*/
getOrganizationSsoIdentifier: () => Promise<string>;
abstract getOrganizationSsoIdentifier: () => Promise<string>;
/**
* Sets the value of the user's organization sso identifier.
*
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
* Do not use this value outside of the SSO login flow.
*/
setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
abstract setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
/**
* Gets the user's email.
* Note: This should only be used during the SSO flow to identify the user that is attempting to log in.
* @returns The user's email.
*/
getSsoEmail: () => Promise<string>;
abstract getSsoEmail: () => Promise<string>;
/**
* Sets the user's email.
* Note: This should only be used during the SSO flow to identify the user that is attempting to log in.
@@ -71,20 +69,20 @@ export abstract class SsoLoginServiceAbstraction {
* @returns A promise that resolves when the email has been set.
*
*/
setSsoEmail: (email: string) => Promise<void>;
abstract setSsoEmail: (email: string) => Promise<void>;
/**
* Gets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
* @param userId The user id for retrieving the org identifier state.
*/
getActiveUserOrganizationSsoIdentifier: (userId: UserId) => Promise<string>;
abstract getActiveUserOrganizationSsoIdentifier: (userId: UserId) => Promise<string | null>;
/**
* Sets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
*/
setActiveUserOrganizationSsoIdentifier: (
abstract setActiveUserOrganizationSsoIdentifier: (
organizationIdentifier: string,
userId: UserId | undefined,
) => Promise<void>;

View File

@@ -87,7 +87,7 @@ describe("SSOLoginService ", () => {
const orgIdentifier = "test-active-org-identifier";
await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, undefined);
expect(mockLogService.warning).toHaveBeenCalledWith(
expect(mockLogService.error).toHaveBeenCalledWith(
"Tried to set a user organization sso identifier with an undefined user id.",
);
});

View File

@@ -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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -107,7 +105,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.ssoEmailState.update((_) => email);
}
getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise<string> {
getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise<string | null> {
return firstValueFrom(this.userOrgSsoIdentifierState(userId).state$);
}
@@ -116,7 +114,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
userId: UserId | undefined,
): Promise<void> {
if (userId === undefined) {
this.logService.warning(
this.logService.error(
"Tried to set a user organization sso identifier with an undefined user id.",
);
return;

View File

@@ -26,7 +26,6 @@ export enum FeatureFlag {
/* Tools */
ItemShare = "item-share",
GeneratorToolsModernization = "generator-tools-modernization",
CriticalApps = "pm-14466-risk-insights-critical-application",
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
@@ -50,6 +49,7 @@ export enum FeatureFlag {
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
NewDeviceVerification = "new-device-verification",
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
}
@@ -88,7 +88,6 @@ export const DefaultFeatureFlagValue = {
/* Tools */
[FeatureFlag.ItemShare]: FALSE,
[FeatureFlag.GeneratorToolsModernization]: FALSE,
[FeatureFlag.CriticalApps]: FALSE,
[FeatureFlag.EnableRiskInsightsNotifications]: FALSE,
@@ -112,6 +111,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
[FeatureFlag.ResellerManagedOrgAlert]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.NewDeviceVerification]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;

View File

@@ -29,9 +29,20 @@ export const ORGANIZATION_MANAGEMENT_PREFERENCES_DISK = new StateDefinition(
web: "disk-local",
},
);
export const AC_BANNERS_DISMISSED_DISK = new StateDefinition("acBannersDismissed", "disk", {
web: "disk-local",
});
export const ACCOUNT_DEPROVISIONING_BANNER_DISK = new StateDefinition(
"showAccountDeprovisioningBanner",
"disk",
{
web: "disk-local",
},
);
export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
"showDeleteManagedUserWarning",
"disk",
{
web: "disk-local",
},
);
// Billing
export const BILLING_DISK = new StateDefinition("billing", "disk");

View File

@@ -68,12 +68,13 @@ import { RemoveUnassignedItemsBannerDismissed } from "./migrations/67-remove-una
import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date";
import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3;
export const CURRENT_VERSION = 69;
export const CURRENT_VERSION = 70;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -144,7 +145,8 @@ export function createMigrationBuilder() {
.with(MoveFinalDesktopSettingsMigrator, 65, 66)
.with(RemoveUnassignedItemsBannerDismissed, 66, 67)
.with(MoveLastSyncDate, 67, 68)
.with(MigrateIncorrectFolderKey, 68, CURRENT_VERSION);
.with(MigrateIncorrectFolderKey, 68, 69)
.with(RemoveAcBannersDismissed, 69, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,50 @@
import { runMigrator } from "../migration-helper.spec";
import { IRREVERSIBLE } from "../migrator";
import { RemoveAcBannersDismissed } from "./70-remove-ac-banner-dismissed";
describe("RemoveAcBannersDismissed", () => {
const sut = new RemoveAcBannersDismissed(69, 70);
describe("migrate", () => {
it("deletes ac banner from all users", async () => {
const output = await runMigrator(sut, {
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
user_user1_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
user_user2_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
});
expect(output).toEqual({
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
});
});
});
describe("rollback", () => {
it("is irreversible", async () => {
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
});
});
});

View File

@@ -0,0 +1,23 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { IRREVERSIBLE, Migrator } from "../migrator";
export const SHOW_BANNER_KEY: KeyDefinitionLike = {
key: "acBannersDismissed",
stateDefinition: { name: "showProviderClientVaultPrivacyBanner" },
};
export class RemoveAcBannersDismissed extends Migrator<69, 70> {
async migrate(helper: MigrationHelper): Promise<void> {
await Promise.all(
(await helper.getAccounts()).map(async ({ userId }) => {
if (helper.getFromUser(userId, SHOW_BANNER_KEY) != null) {
await helper.removeFromUser(userId, SHOW_BANNER_KEY);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
throw IRREVERSIBLE;
}
}