mirror of
https://github.com/bitwarden/browser
synced 2026-02-17 09:59:41 +00:00
Merge branch 'main' into auth/pm-9115/implement-view-data-persistence-in-2FA-flows
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
|
||||
@Component({
|
||||
selector: "app-sso",
|
||||
templateUrl: "sso-v1.component.html",
|
||||
})
|
||||
export class SsoComponentV1 extends BaseSsoComponent {
|
||||
constructor(
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
syncService: SyncService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
private authService: AuthService,
|
||||
@Inject(WINDOW) private win: Window,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
ssoLoginService,
|
||||
loginStrategyService,
|
||||
router,
|
||||
i18nService,
|
||||
route,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
cryptoFunctionService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
|
||||
environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => {
|
||||
this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html";
|
||||
});
|
||||
this.clientId = "browser";
|
||||
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
|
||||
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
|
||||
if ((await this.authService.getAuthStatus()) !== AuthenticationStatus.Unlocked) {
|
||||
BrowserApi.reloadOpenWindows();
|
||||
}
|
||||
|
||||
this.win.close();
|
||||
};
|
||||
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
this.onSuccessfulLoginTdeNavigate = async () => {
|
||||
this.win.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { CLEAR_NOTIFICATION_LOGIN_DATA_DURATION } from "@bitwarden/common/autofill/constants";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EnvironmentServerConfigData } from "@bitwarden/common/platform/models/data/server-config.data";
|
||||
@@ -25,8 +23,6 @@ import { OverlayNotificationsBackground } from "./overlay-notifications.backgrou
|
||||
|
||||
describe("OverlayNotificationsBackground", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let getFeatureFlagMock$: BehaviorSubject<boolean>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
let notificationBackground: NotificationBackground;
|
||||
let getEnableChangedPasswordPromptSpy: jest.SpyInstance;
|
||||
let getEnableAddedLoginPromptSpy: jest.SpyInstance;
|
||||
@@ -35,10 +31,6 @@ describe("OverlayNotificationsBackground", () => {
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
logService = mock<LogService>();
|
||||
getFeatureFlagMock$ = new BehaviorSubject(true);
|
||||
configService = mock<ConfigService>({
|
||||
getFeatureFlag$: jest.fn().mockReturnValue(getFeatureFlagMock$),
|
||||
});
|
||||
notificationBackground = mock<NotificationBackground>();
|
||||
getEnableChangedPasswordPromptSpy = jest
|
||||
.spyOn(notificationBackground, "getEnableChangedPasswordPrompt")
|
||||
@@ -48,10 +40,8 @@ describe("OverlayNotificationsBackground", () => {
|
||||
.mockResolvedValue(true);
|
||||
overlayNotificationsBackground = new OverlayNotificationsBackground(
|
||||
logService,
|
||||
configService,
|
||||
notificationBackground,
|
||||
);
|
||||
configService.getFeatureFlag.mockResolvedValue(true);
|
||||
await overlayNotificationsBackground.init();
|
||||
});
|
||||
|
||||
@@ -60,27 +50,6 @@ describe("OverlayNotificationsBackground", () => {
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
describe("feature flag behavior", () => {
|
||||
let runtimeRemoveListenerSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
runtimeRemoveListenerSpy = jest.spyOn(chrome.runtime.onMessage, "removeListener");
|
||||
});
|
||||
|
||||
it("removes the extension listeners if the current flag value is set to `false`", () => {
|
||||
getFeatureFlagMock$.next(false);
|
||||
|
||||
expect(runtimeRemoveListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores the feature flag change if the previous flag value is equal to the current flag value", () => {
|
||||
getFeatureFlagMock$.next(false);
|
||||
getFeatureFlagMock$.next(false);
|
||||
|
||||
expect(runtimeRemoveListenerSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setting up the form submission listeners", () => {
|
||||
let fields: MockProxy<AutofillField>[];
|
||||
let details: MockProxy<AutofillPageDetails>;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { startWith, Subject, Subscription, switchMap, timer } from "rxjs";
|
||||
import { pairwise } from "rxjs/operators";
|
||||
import { Subject, switchMap, timer } from "rxjs";
|
||||
|
||||
import { CLEAR_NOTIFICATION_LOGIN_DATA_DURATION } from "@bitwarden/common/autofill/constants";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@@ -26,7 +23,6 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg
|
||||
private websiteOriginsWithFields: WebsiteOriginsWithFields = new Map();
|
||||
private activeFormSubmissionRequests: ActiveFormSubmissionRequests = new Set();
|
||||
private modifyLoginCipherFormData: ModifyLoginCipherFormDataForTab = new Map();
|
||||
private featureFlagState$: Subscription;
|
||||
private clearLoginCipherFormDataSubject: Subject<void> = new Subject();
|
||||
private notificationFallbackTimeout: number | NodeJS.Timeout | null;
|
||||
private readonly formSubmissionRequestMethods: Set<string> = new Set(["POST", "PUT", "PATCH"]);
|
||||
@@ -38,7 +34,6 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private configService: ConfigService,
|
||||
private notificationBackground: NotificationBackground,
|
||||
) {}
|
||||
|
||||
@@ -46,35 +41,13 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg
|
||||
* Initialize the overlay notifications background service.
|
||||
*/
|
||||
async init() {
|
||||
this.featureFlagState$ = this.configService
|
||||
.getFeatureFlag$(FeatureFlag.NotificationBarAddLoginImprovements)
|
||||
.pipe(startWith(undefined), pairwise())
|
||||
.subscribe(([prev, current]) => this.handleInitFeatureFlagChange(prev, current));
|
||||
this.setupExtensionListeners();
|
||||
|
||||
this.clearLoginCipherFormDataSubject
|
||||
.pipe(switchMap(() => timer(CLEAR_NOTIFICATION_LOGIN_DATA_DURATION)))
|
||||
.subscribe(() => this.modifyLoginCipherFormData.clear());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles enabling/disabling the extension listeners that trigger the
|
||||
* overlay notifications based on the feature flag state.
|
||||
*
|
||||
* @param previousValue - The previous value of the feature flag
|
||||
* @param currentValue - The current value of the feature flag
|
||||
*/
|
||||
private handleInitFeatureFlagChange = (previousValue: boolean, currentValue: boolean) => {
|
||||
if (previousValue === currentValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentValue) {
|
||||
this.setupExtensionListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeExtensionListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the response from the content script with the page details. Triggers an initialization
|
||||
* of the add login or change password notification if the conditions are met.
|
||||
@@ -520,15 +493,6 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg
|
||||
chrome.tabs.onUpdated.addListener(this.handleTabUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners for the extension messages and the tab events.
|
||||
*/
|
||||
private removeExtensionListeners() {
|
||||
BrowserApi.removeListener(chrome.runtime.onMessage, this.handleExtensionMessage);
|
||||
chrome.tabs.onRemoved.removeListener(this.handleTabRemoved);
|
||||
chrome.tabs.onUpdated.removeListener(this.handleTabUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles messages that are sent to the extension background.
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@ const NotificationTypes = {
|
||||
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
|
||||
|
||||
type NotificationBarIframeInitData = {
|
||||
applyRedesign?: boolean;
|
||||
ciphers?: NotificationCipherData[];
|
||||
folders?: FolderView[];
|
||||
importType?: string;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<body>
|
||||
<div id="notification-bar-outer-wrapper" class="outer-wrapper">
|
||||
<div class="logo">
|
||||
<div class="logo-wrapper">
|
||||
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link" rel="noreferrer">
|
||||
<img id="logo" alt="Bitwarden" />
|
||||
</a>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import "../shared/styles/variables";
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
@@ -14,16 +14,30 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
font-size: $font-size-base;
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
|
||||
.outer-wrapper {
|
||||
padding: 0 10px;
|
||||
border-bottom: 2px solid transparent;
|
||||
display: grid;
|
||||
grid-template-columns: 24px auto 55px;
|
||||
grid-column-gap: 10px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
min-height: 42px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include themify($themes) {
|
||||
border-color: themed("borderColor");
|
||||
border-bottom-color: themed("primaryColor");
|
||||
}
|
||||
|
||||
@@ -50,20 +64,34 @@ body {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#close-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
@include themify($themes) {
|
||||
border-color: rgba(themed("textColor"), 0.2);
|
||||
background-color: rgba(themed("textColor"), 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#close {
|
||||
@@ -78,15 +106,79 @@ img {
|
||||
}
|
||||
}
|
||||
|
||||
#close-button:hover {
|
||||
@include themify($themes) {
|
||||
border-color: rgba(themed("textColor"), 0.2);
|
||||
background-color: rgba(themed("textColor"), 0.2);
|
||||
.notification-close {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
#content .inner-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
.notification-body {
|
||||
width: 100%;
|
||||
padding: 4px 38px 24px 42px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.notification-actions {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: stretch;
|
||||
justify-content: flex-end;
|
||||
|
||||
#never-save {
|
||||
margin-right: auto;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#select-folder {
|
||||
width: 125px;
|
||||
margin-right: 6px;
|
||||
font-size: 12px;
|
||||
appearance: none;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right 4px;
|
||||
background-size: 16px;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed("mutedTextColor");
|
||||
border-color: themed("mutedTextColor");
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.primary,
|
||||
.secondary {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
margin-right: 6px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.success-message,
|
||||
&.error-message {
|
||||
padding: 4px 36px 6px 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.35rem 15px;
|
||||
padding: 4px 8px;
|
||||
border-radius: $border-radius;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
@@ -133,14 +225,13 @@ button.neutral {
|
||||
text-decoration: underline;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: transparent;
|
||||
color: darken(themed("primaryColor"), 6%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.35rem;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid #000000;
|
||||
border-radius: $border-radius;
|
||||
|
||||
@@ -151,16 +242,9 @@ select {
|
||||
}
|
||||
}
|
||||
|
||||
select,
|
||||
button {
|
||||
font-size: $font-size-base;
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
|
||||
@include themify($themes) {
|
||||
@@ -184,6 +268,13 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.success-event,
|
||||
.error-event {
|
||||
.notification-body {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
#select-folder {
|
||||
display: none;
|
||||
@@ -196,131 +287,8 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.notification-bar-redesign {
|
||||
button {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.outer-wrapper {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border-top: 1px solid transparent;
|
||||
border-left: 1px solid transparent;
|
||||
border-right: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
@include themify($themes) {
|
||||
border-top-color: themed("borderColor");
|
||||
border-left-color: themed("borderColor");
|
||||
border-right-color: themed("borderColor");
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
|
||||
#close-button {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#content .inner-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.notification-body {
|
||||
width: 100%;
|
||||
padding: 4px 38px 24px 42px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.notification-actions {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
align-content: stretch;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
#never-save {
|
||||
margin-right: auto;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#select-folder {
|
||||
width: 125px;
|
||||
margin-right: 6px;
|
||||
font-size: 12px;
|
||||
appearance: none;
|
||||
background-size: 16px;
|
||||
background-position: center right 4px;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed("mutedTextColor");
|
||||
border-color: themed("mutedTextColor");
|
||||
}
|
||||
|
||||
&:not([disabled]) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.primary,
|
||||
.secondary {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
margin-right: 6px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.success-message,
|
||||
&.error-message {
|
||||
padding: 4px 36px 6px 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success-event,
|
||||
.error-event {
|
||||
.notification-body {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme_light {
|
||||
.notification-bar-redesign #content .inner-wrapper {
|
||||
#content .inner-wrapper {
|
||||
#select-folder {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB3aWR0aD0nMTYnIGhlaWdodD0nMTYnIGZpbGw9J25vbmUnPjxwYXRoIHN0cm9rZT0nIzIxMjUyOScgZD0nbTUgNiAzIDMgMy0zJy8+PC9zdmc+");
|
||||
}
|
||||
@@ -328,7 +296,7 @@ button {
|
||||
}
|
||||
|
||||
.theme_dark {
|
||||
.notification-bar-redesign #content .inner-wrapper {
|
||||
#content .inner-wrapper {
|
||||
#select-folder {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNicgaGVpZ2h0PScxNicgZmlsbD0nbm9uZSc+PHBhdGggc3Ryb2tlPScjZmZmZmZmJyBkPSdtNSA2IDMgMyAzLTMnLz48L3N2Zz4=");
|
||||
}
|
||||
|
||||
@@ -507,10 +507,6 @@ function setNotificationBarTheme() {
|
||||
const theme = getTheme(globalThis, notificationBarIframeInitData.theme);
|
||||
|
||||
document.documentElement.classList.add(`theme_${theme}`);
|
||||
|
||||
if (notificationBarIframeInitData.applyRedesign) {
|
||||
document.body.classList.add("notification-bar-redesign");
|
||||
}
|
||||
}
|
||||
|
||||
function postMessageToParent(message: NotificationBarWindowMessage) {
|
||||
|
||||
@@ -17,6 +17,7 @@ export class OverlayNotificationsContentService
|
||||
private notificationBarIframeElement: HTMLIFrameElement | null = null;
|
||||
private currentNotificationBarType: string | null = null;
|
||||
private removeTabFromNotificationQueueTypes = new Set(["add", "change"]);
|
||||
private notificationRefreshFlag: boolean;
|
||||
private notificationBarElementStyles: Partial<CSSStyleDeclaration> = {
|
||||
height: "82px",
|
||||
width: "430px",
|
||||
@@ -54,6 +55,9 @@ export class OverlayNotificationsContentService
|
||||
|
||||
constructor() {
|
||||
void sendExtensionMessage("checkNotificationQueue");
|
||||
void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => {
|
||||
this.notificationRefreshFlag = !!notificationRefreshFlag;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +88,6 @@ export class OverlayNotificationsContentService
|
||||
theme: typeData.theme,
|
||||
removeIndividualVault: typeData.removeIndividualVault,
|
||||
importType: typeData.importType,
|
||||
applyRedesign: true,
|
||||
launchTimestamp: typeData.launchTimestamp,
|
||||
};
|
||||
|
||||
@@ -192,7 +195,13 @@ export class OverlayNotificationsContentService
|
||||
{ transform: "translateX(0)", opacity: "1" },
|
||||
true,
|
||||
);
|
||||
setElementStyles(this.notificationBarElement, { boxShadow: "2px 4px 6px 0px #0000001A" }, true);
|
||||
if (!this.notificationRefreshFlag) {
|
||||
setElementStyles(
|
||||
this.notificationBarElement,
|
||||
{ boxShadow: "2px 4px 6px 0px #0000001A" },
|
||||
true,
|
||||
);
|
||||
}
|
||||
this.notificationBarIframeElement.removeEventListener(
|
||||
EVENTS.LOAD,
|
||||
this.handleNotificationBarIframeOnLoad,
|
||||
@@ -206,7 +215,13 @@ export class OverlayNotificationsContentService
|
||||
if (this.notificationBarIframeElement) {
|
||||
this.notificationBarElement = globalThis.document.createElement("div");
|
||||
this.notificationBarElement.id = "bit-notification-bar";
|
||||
|
||||
setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true);
|
||||
|
||||
if (this.notificationRefreshFlag) {
|
||||
setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true);
|
||||
}
|
||||
|
||||
this.notificationBarElement.appendChild(this.notificationBarIframeElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ describe("AutofillService", () => {
|
||||
const autofillOverlayMenuBootstrapScript = "bootstrap-autofill-overlay-menu.js";
|
||||
const autofillOverlayNotificationsBootstrapScript =
|
||||
"bootstrap-autofill-overlay-notifications.js";
|
||||
const defaultAutofillScripts = ["autofiller.js", "notificationBar.js", "contextMenuHandler.js"];
|
||||
const defaultAutofillScripts = ["autofiller.js", "contextMenuHandler.js"];
|
||||
const defaultExecuteScriptOptions = { runAt: "document_start" };
|
||||
let tabMock: chrome.tabs.Tab;
|
||||
let sender: chrome.runtime.MessageSender;
|
||||
@@ -400,13 +400,9 @@ describe("AutofillService", () => {
|
||||
});
|
||||
|
||||
it("accepts an extension message sender and injects the autofill scripts into the tab of the sender", async () => {
|
||||
configService.getFeatureFlag.mockImplementation(async (_feature) => {
|
||||
if (_feature === FeatureFlag.NotificationBarAddLoginImprovements) {
|
||||
return false as FeatureFlagValueType<any>;
|
||||
}
|
||||
enableChangedPasswordPromptMock$.next(false);
|
||||
enableAddedLoginPromptMock$.next(false);
|
||||
|
||||
return true as FeatureFlagValueType<any>;
|
||||
});
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true);
|
||||
|
||||
[autofillOverlayMenuBootstrapScript, ...defaultAutofillScripts].forEach((scriptName) => {
|
||||
@@ -457,25 +453,12 @@ describe("AutofillService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("will inject the bootstrap-autofill script if the user does not have the autofill overlay enabled", async () => {
|
||||
it("will inject the overlay script if the user does not have the autofill overlay enabled", async () => {
|
||||
jest
|
||||
.spyOn(autofillService, "getInlineMenuVisibility")
|
||||
.mockResolvedValue(AutofillOverlayVisibility.Off);
|
||||
configService.getFeatureFlag.mockImplementation(async (_feature) => {
|
||||
if (_feature === FeatureFlag.NotificationBarAddLoginImprovements) {
|
||||
return false as FeatureFlagValueType<any>;
|
||||
}
|
||||
|
||||
return true as FeatureFlagValueType<any>;
|
||||
});
|
||||
|
||||
await autofillService.injectAutofillScripts(sender.tab, sender.frameId);
|
||||
|
||||
expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, {
|
||||
file: `content/${autofillBootstrapScript}`,
|
||||
frameId: sender.frameId,
|
||||
...defaultExecuteScriptOptions,
|
||||
});
|
||||
expect(BrowserApi.executeScriptInTab).not.toHaveBeenCalledWith(tabMock.id, {
|
||||
file: `content/${autofillOverlayBootstrapScript}`,
|
||||
frameId: sender.frameId,
|
||||
|
||||
@@ -236,13 +236,8 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
const authStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
||||
const accountIsUnlocked = authStatus === AuthenticationStatus.Unlocked;
|
||||
let autoFillOnPageLoadIsEnabled = false;
|
||||
const addLoginImprovementsFlagActive = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.NotificationBarAddLoginImprovements,
|
||||
);
|
||||
|
||||
const injectedScripts = [
|
||||
await this.getBootstrapAutofillContentScript(activeAccount, addLoginImprovementsFlagActive),
|
||||
];
|
||||
const injectedScripts = [await this.getBootstrapAutofillContentScript(activeAccount)];
|
||||
|
||||
if (activeAccount && accountIsUnlocked) {
|
||||
autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad();
|
||||
@@ -259,10 +254,6 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
});
|
||||
}
|
||||
|
||||
if (!addLoginImprovementsFlagActive) {
|
||||
injectedScripts.push("notificationBar.js");
|
||||
}
|
||||
|
||||
injectedScripts.push("contextMenuHandler.js");
|
||||
|
||||
for (const injectedScript of injectedScripts) {
|
||||
@@ -283,11 +274,9 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
* enabled.
|
||||
*
|
||||
* @param activeAccount - The active account
|
||||
* @param addLoginImprovementsFlagActive - Whether the add login improvements feature flag is active
|
||||
*/
|
||||
private async getBootstrapAutofillContentScript(
|
||||
activeAccount: { id: UserId | undefined } & AccountInfo,
|
||||
addLoginImprovementsFlagActive = false,
|
||||
): Promise<string> {
|
||||
let inlineMenuVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off;
|
||||
|
||||
@@ -310,8 +299,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
const enableAddedLoginPrompt = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableAddedLoginPrompt$,
|
||||
);
|
||||
const isNotificationBarEnabled =
|
||||
addLoginImprovementsFlagActive && (enableChangedPasswordPrompt || enableAddedLoginPrompt);
|
||||
const isNotificationBarEnabled = enableChangedPasswordPrompt || enableAddedLoginPrompt;
|
||||
|
||||
if (!inlineMenuVisibility && !isNotificationBarEnabled) {
|
||||
return "bootstrap-autofill.js";
|
||||
|
||||
@@ -1195,7 +1195,6 @@ export default class MainBackground {
|
||||
|
||||
this.overlayNotificationsBackground = new OverlayNotificationsBackground(
|
||||
this.logService,
|
||||
this.configService,
|
||||
this.notificationBackground,
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import { RemovePasswordComponent } from "../auth/popup/remove-password.component
|
||||
import { SetPasswordComponent } from "../auth/popup/set-password.component";
|
||||
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
|
||||
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
|
||||
import { SsoComponentV1 } from "../auth/popup/sso-v1.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
@@ -89,7 +88,6 @@ import "../platform/popup/locales";
|
||||
ColorPasswordPipe,
|
||||
ColorPasswordCountPipe,
|
||||
SetPasswordComponent,
|
||||
SsoComponentV1,
|
||||
TabsV2Component,
|
||||
UpdateTempPasswordComponent,
|
||||
UserVerificationComponent,
|
||||
|
||||
@@ -201,7 +201,6 @@ const mainConfig = {
|
||||
"./src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts",
|
||||
"content/autofiller": "./src/autofill/content/autofiller.ts",
|
||||
"content/auto-submit-login": "./src/autofill/content/auto-submit-login.ts",
|
||||
"content/notificationBar": "./src/autofill/content/notification-bar.ts",
|
||||
"content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
|
||||
"content/content-message-handler": "./src/autofill/content/content-message-handler.ts",
|
||||
"content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts",
|
||||
|
||||
@@ -15,7 +15,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
import { LoginModule } from "../auth/login/login.module";
|
||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||
import { SsoComponentV1 } from "../auth/sso-v1.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
||||
import { SshAgentService } from "../autofill/services/ssh-agent.service";
|
||||
import { PremiumComponent } from "../billing/app/accounts/premium.component";
|
||||
@@ -76,7 +75,6 @@ import { SharedModule } from "./shared/shared.module";
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SsoComponentV1,
|
||||
UpdateTempPasswordComponent,
|
||||
VaultComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<form id="sso-page" (ngSubmit)="submit()">
|
||||
<div class="content">
|
||||
<img class="logo-image" alt="Bitwarden" />
|
||||
<div class="box">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
{{ "loading" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
@Component({
|
||||
selector: "app-sso",
|
||||
templateUrl: "sso-v1.component.html",
|
||||
})
|
||||
export class SsoComponentV1 extends BaseSsoComponent {
|
||||
constructor(
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
syncService: SyncService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
logService: LogService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
ssoLoginService,
|
||||
loginStrategyService,
|
||||
router,
|
||||
i18nService,
|
||||
route,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
cryptoFunctionService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
this.onSuccessfulLogin = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
this.onSuccessfulLoginTde = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
syncService.fullSync(true);
|
||||
};
|
||||
|
||||
this.redirectUri = "bitwarden://sso-callback";
|
||||
this.clientId = "desktop";
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
@import "variables.scss";
|
||||
|
||||
#lock-page,
|
||||
#sso-page,
|
||||
#set-password-page,
|
||||
#remove-password-page {
|
||||
display: flex;
|
||||
@@ -106,18 +105,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#sso-page {
|
||||
.content {
|
||||
width: 325px;
|
||||
|
||||
.box {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#set-password-page,
|
||||
#remove-password-page {
|
||||
.content {
|
||||
|
||||
@@ -1,18 +1,83 @@
|
||||
FROM ghcr.io/bitwarden/server
|
||||
###############################################
|
||||
# Build stage 1 #
|
||||
###############################################
|
||||
ARG NODE_VERSION=20
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION} AS node-build
|
||||
|
||||
ARG NPM_COMMAND=dist:bit:selfhost
|
||||
|
||||
WORKDIR /source
|
||||
COPY . .
|
||||
|
||||
RUN npm ci
|
||||
|
||||
WORKDIR /source/apps/web
|
||||
RUN npm run ${NPM_COMMAND}
|
||||
|
||||
###############################################
|
||||
# Build stage 2 #
|
||||
###############################################
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
|
||||
# Docker buildx supplies the value for this arg
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Determine proper runtime value for .NET
|
||||
# We put the value in a file to be read by later layers.
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||
RID=linux-x64 ; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||
RID=linux-arm64 ; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||
RID=linux-arm ; \
|
||||
fi \
|
||||
&& echo "RID=$RID" > /tmp/rid.txt
|
||||
|
||||
# Copy csproj files as distinct layers
|
||||
WORKDIR /source
|
||||
COPY server/util/Server/*.csproj ./util/Server/
|
||||
COPY server/Directory.Build.props .
|
||||
COPY server/.editorconfig .
|
||||
|
||||
# Restore Server project dependencies and tools
|
||||
WORKDIR /source/util/Server
|
||||
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||
|
||||
# Copy required project files
|
||||
WORKDIR /source
|
||||
COPY server/util/Server/. ./util/Server/
|
||||
COPY server/.git/. ./.git/
|
||||
|
||||
# Build Server app
|
||||
WORKDIR /source/util/Server
|
||||
RUN . /tmp/rid.txt && dotnet publish -c release -o /app/Server --no-restore --no-self-contained -r $RID
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
###############################################
|
||||
# App stage #
|
||||
###############################################
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
LABEL com.bitwarden.product="bitwarden"
|
||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||
ENV ASPNETCORE_URLS=http://+:5000
|
||||
EXPOSE 5000
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy app from the build stage
|
||||
WORKDIR /bitwarden_server
|
||||
COPY --from=build /app/Server ./
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
COPY ./build .
|
||||
COPY entrypoint.sh /
|
||||
COPY --from=node-build /source/apps/web/build .
|
||||
COPY --from=node-build /source/apps/web/entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
|
||||
|
||||
@@ -111,10 +111,10 @@
|
||||
|
||||
<ng-container *ngIf="loaded && usePlaceHolderEvents">
|
||||
<div
|
||||
class="tw-relative tw--top-72 tw-bg-[#ffffff] tw-bg-opacity-90 tw-pb-5 tw-flex tw-items-center tw-justify-center tw-h-[19rem]"
|
||||
class="tw-relative tw--top-72 tw-bg-background tw-bg-opacity-90 tw-pb-5 tw-flex tw-items-center tw-justify-center tw-h-[19rem]"
|
||||
>
|
||||
<div
|
||||
class="tw-bg-[#ffffff] tw-max-w-xl tw-flex-col tw-justify-center tw-text-center tw-p-5 tw-px-10 tw-rounded tw-border-0 tw-border-b tw-border-secondary-300 tw-border-solid tw-mt-5"
|
||||
class="tw-bg-background tw-max-w-xl tw-flex-col tw-justify-center tw-text-center tw-p-5 tw-px-10 tw-rounded tw-border-0 tw-border-b tw-border-secondary-300 tw-border-solid tw-mt-5"
|
||||
>
|
||||
<i class="bwi bwi-2x bwi-business tw-text-primary-600"></i>
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
@Component({
|
||||
selector: "emergency-access-attachments",
|
||||
templateUrl: "../../../../vault/individual-vault/attachments.component.html",
|
||||
})
|
||||
export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponent {
|
||||
viewOnly = true;
|
||||
canAccessAttachments = true;
|
||||
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
encryptService: EncryptService,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
logService: LogService,
|
||||
fileDownloadService: FileDownloadService,
|
||||
dialogService: DialogService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
i18nService,
|
||||
keyService,
|
||||
encryptService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
window,
|
||||
logService,
|
||||
stateService,
|
||||
fileDownloadService,
|
||||
dialogService,
|
||||
billingAccountProfileStateService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
// Do nothing since cipher is already decoded
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -38,28 +38,6 @@
|
||||
<br />
|
||||
<small class="tw-text-xs">{{ currentCipher.subTitle }}</small>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<div *ngIf="currentCipher.hasAttachments">
|
||||
<button
|
||||
[bitMenuTriggerFor]="optionsMenu"
|
||||
type="button"
|
||||
buttonType="main"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
></button>
|
||||
<bit-menu #optionsMenu>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
appStopClick
|
||||
(click)="viewAttachments(currentCipher)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
||||
{{ "attachments" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { EmergencyAccessId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault";
|
||||
|
||||
import { EmergencyAccessService } from "../../../emergency-access";
|
||||
import { EmergencyAccessAttachmentsComponent } from "../attachments/emergency-access-attachments.component";
|
||||
|
||||
import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component";
|
||||
|
||||
@@ -20,56 +18,34 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"
|
||||
})
|
||||
export class EmergencyAccessViewComponent implements OnInit {
|
||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||
attachmentsModalRef: ViewContainerRef;
|
||||
|
||||
id: string;
|
||||
id: EmergencyAccessId | null = null;
|
||||
ciphers: CipherView[] = [];
|
||||
loaded = false;
|
||||
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private dialogService: DialogService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.route.params.subscribe((qParams) => {
|
||||
if (qParams.id == null) {
|
||||
return this.router.navigate(["settings/emergency-access"]);
|
||||
}
|
||||
async ngOnInit() {
|
||||
const qParams = await firstValueFrom(this.route.params);
|
||||
if (qParams.id == null) {
|
||||
await this.router.navigate(["settings/emergency-access"]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = qParams.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
|
||||
this.load();
|
||||
});
|
||||
this.id = qParams.id;
|
||||
this.ciphers = await this.emergencyAccessService.getViewOnlyCiphers(qParams.id);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
async selectCipher(cipher: CipherView) {
|
||||
EmergencyViewDialogComponent.open(this.dialogService, {
|
||||
cipher,
|
||||
emergencyAccessId: this.id!,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.ciphers = await this.emergencyAccessService.getViewOnlyCiphers(this.id);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
// FIXME PM-17747: This will also need to be replaced with the new AttachmentViewDialog
|
||||
async viewAttachments(cipher: CipherView) {
|
||||
await this.modalService.openViewRef(
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
this.attachmentsModalRef,
|
||||
(comp) => {
|
||||
comp.cipher = cipher;
|
||||
comp.emergencyAccessId = this.id;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{ title }}
|
||||
</span>
|
||||
<div bitDialogContent #dialogContent>
|
||||
<app-cipher-view [cipher]="cipher"></app-cipher-view>
|
||||
<app-cipher-view [emergencyAccessId]="emergencyAccessId" [cipher]="cipher"></app-cipher-view>
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="cancel()">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { EmergencyAccessId } from "@bitwarden/common/types/guid";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -20,6 +21,7 @@ import { WebViewPasswordHistoryService } from "../../../../vault/services/web-vi
|
||||
export interface EmergencyViewDialogParams {
|
||||
/** The cipher being viewed. */
|
||||
cipher: CipherView;
|
||||
emergencyAccessId: EmergencyAccessId;
|
||||
}
|
||||
|
||||
/** Stubbed class, premium upgrade is not applicable for emergency viewing */
|
||||
@@ -59,6 +61,10 @@ export class EmergencyViewDialogComponent {
|
||||
return this.params.cipher;
|
||||
}
|
||||
|
||||
get emergencyAccessId(): EmergencyAccessId {
|
||||
return this.params.emergencyAccessId;
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.dialogRef.close();
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit" class="tw-container">
|
||||
<div *ngIf="loggingIn">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
{{ "loading" | i18n }}
|
||||
</div>
|
||||
<div *ngIf="!loggingIn">
|
||||
<p bitTypography="body1">{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="identifier" appAutofocus />
|
||||
</bit-form-field>
|
||||
<hr />
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary" [block]="true">
|
||||
{{ "logIn" | i18n }}
|
||||
</button>
|
||||
<a bitButton buttonType="secondary" routerLink="/login" [block]="true">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,186 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
|
||||
import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response";
|
||||
import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
@Component({
|
||||
selector: "app-sso",
|
||||
templateUrl: "sso-v1.component.html",
|
||||
})
|
||||
export class SsoComponentV1 extends BaseSsoComponent implements OnInit {
|
||||
protected formGroup = new FormGroup({
|
||||
identifier: new FormControl(null, [Validators.required]),
|
||||
});
|
||||
|
||||
get identifierFormControl() {
|
||||
return this.formGroup.controls.identifier;
|
||||
}
|
||||
|
||||
constructor(
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
logService: LogService,
|
||||
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
||||
private validationService: ValidationService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
ssoLoginService,
|
||||
loginStrategyService,
|
||||
router,
|
||||
i18nService,
|
||||
route,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
cryptoFunctionService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
this.redirectUri = window.location.origin + "/sso-connector.html";
|
||||
this.clientId = "web";
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// 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
|
||||
super.ngOnInit();
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.identifier != null) {
|
||||
// SSO Org Identifier in query params takes precedence over claimed domains
|
||||
this.identifierFormControl.setValue(qParams.identifier);
|
||||
this.loggingIn = true;
|
||||
await this.submit();
|
||||
} else {
|
||||
// Note: this flow is written for web but both browser and desktop
|
||||
// redirect here on SSO button click.
|
||||
|
||||
// Check if email matches any claimed domains
|
||||
if (qParams.email) {
|
||||
// show loading spinner
|
||||
this.loggingIn = true;
|
||||
try {
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) {
|
||||
const response: ListResponse<VerifiedOrganizationDomainSsoDetailsResponse> =
|
||||
await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email);
|
||||
|
||||
if (response.data.length > 0) {
|
||||
this.identifierFormControl.setValue(response.data[0].organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const response: OrganizationDomainSsoDetailsResponse =
|
||||
await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email);
|
||||
|
||||
if (response?.ssoAvailable && response?.verifiedDate) {
|
||||
this.identifierFormControl.setValue(response.organizationIdentifier);
|
||||
await this.submit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleGetClaimedDomainByEmailError(error);
|
||||
}
|
||||
|
||||
this.loggingIn = false;
|
||||
}
|
||||
|
||||
// Fallback to state svc if domain is unclaimed
|
||||
const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier();
|
||||
if (storedIdentifier != null) {
|
||||
this.identifierFormControl.setValue(storedIdentifier);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleGetClaimedDomainByEmailError(error: any): void {
|
||||
if (error instanceof ErrorResponse) {
|
||||
const errorResponse: ErrorResponse = error as ErrorResponse;
|
||||
switch (errorResponse.statusCode) {
|
||||
case HttpStatusCode.NotFound:
|
||||
//this is a valid case for a domain not found
|
||||
return;
|
||||
|
||||
default:
|
||||
this.validationService.showError(errorResponse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const autoSubmit = (await firstValueFrom(this.route.queryParams)).identifier != null;
|
||||
|
||||
this.identifier = this.identifierFormControl.value;
|
||||
await this.ssoLoginService.setOrganizationSsoIdentifier(this.identifier);
|
||||
if (this.clientId === "browser") {
|
||||
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
|
||||
}
|
||||
try {
|
||||
await Object.getPrototypeOf(this).submit.call(this);
|
||||
} catch (error) {
|
||||
if (autoSubmit) {
|
||||
await this.router.navigate(["/login"]);
|
||||
} else {
|
||||
this.validationService.showError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import { DangerZoneComponent } from "../auth/settings/account/danger-zone.compon
|
||||
import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component";
|
||||
import { DeleteAccountDialogComponent } from "../auth/settings/account/delete-account-dialog.component";
|
||||
import { ProfileComponent } from "../auth/settings/account/profile.component";
|
||||
import { EmergencyAccessAttachmentsComponent } from "../auth/settings/emergency-access/attachments/emergency-access-attachments.component";
|
||||
import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component";
|
||||
import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
|
||||
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
|
||||
@@ -42,7 +41,6 @@ import { TwoFactorSetupYubiKeyComponent } from "../auth/settings/two-factor/two-
|
||||
import { TwoFactorSetupComponent } from "../auth/settings/two-factor/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "../auth/settings/two-factor/two-factor-verify.component";
|
||||
import { UserVerificationModule } from "../auth/shared/components/user-verification";
|
||||
import { SsoComponentV1 } from "../auth/sso-v1.component";
|
||||
import { UpdatePasswordComponent } from "../auth/update-password.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
||||
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
|
||||
@@ -116,7 +114,6 @@ import { SharedModule } from "./shared.module";
|
||||
DeleteAccountDialogComponent,
|
||||
DomainRulesComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
@@ -146,7 +143,6 @@ import { SharedModule } from "./shared.module";
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponentV1,
|
||||
TwoFactorSetupAuthenticatorComponent,
|
||||
TwoFactorSetupDuoComponent,
|
||||
TwoFactorSetupEmailComponent,
|
||||
@@ -175,7 +171,6 @@ import { SharedModule } from "./shared.module";
|
||||
DomainRulesComponent,
|
||||
DynamicAvatarComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessTakeoverComponent,
|
||||
@@ -206,7 +201,6 @@ import { SharedModule } from "./shared.module";
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponentV1,
|
||||
TwoFactorSetupAuthenticatorComponent,
|
||||
TwoFactorSetupDuoComponent,
|
||||
TwoFactorSetupEmailComponent,
|
||||
|
||||
Reference in New Issue
Block a user