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

Merge branch 'main' into autofill/pm-8027-inline-menu-appears-within-input-fields-that-do-not-relate-to-user-login

This commit is contained in:
Cesar Gonzalez 2024-06-05 15:17:15 -05:00 committed by GitHub
commit 5b29d12b04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 303 additions and 224 deletions

View File

@ -77,7 +77,9 @@ type OverlayBackgroundExtensionMessageHandlers = {
updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
unlockCompleted: ({ message }: BackgroundMessageParam) => void;
addedCipher: () => void;
addEditCipherSubmitted: () => void;
editedCipher: () => void;
deletedCipher: () => void;
};

View File

@ -1000,29 +1000,23 @@ describe("OverlayBackground", () => {
});
});
describe("addEditCipherSubmitted message handler", () => {
it("updates the overlay ciphers", () => {
const message = {
command: "addEditCipherSubmitted",
};
jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
describe("extension messages that trigger an update of the inline menu ciphers", () => {
const extensionMessages = [
"addedCipher",
"addEditCipherSubmitted",
"editedCipher",
"deletedCipher",
];
sendMockExtensionMessage(message);
expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
});
beforeEach(() => {
jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation();
});
describe("deletedCipher message handler", () => {
it("updates the overlay ciphers", () => {
const message = {
command: "deletedCipher",
};
jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
sendMockExtensionMessage(message);
expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
extensionMessages.forEach((message) => {
it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => {
sendMockExtensionMessage({ command: message });
expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled();
});
});
});
});

View File

@ -72,7 +72,9 @@ class OverlayBackground implements OverlayBackgroundInterface {
updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender),
collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender),
unlockCompleted: ({ message }) => this.unlockCompleted(message),
addedCipher: () => this.updateOverlayCiphers(),
addEditCipherSubmitted: () => this.updateOverlayCiphers(),
editedCipher: () => this.updateOverlayCiphers(),
deletedCipher: () => this.updateOverlayCiphers(),
};
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {

View File

@ -1,11 +0,0 @@
<div class="tw-flex tw-justify-between tw-items-end tw-gap-1 tw-px-1 tw-pb-1">
<div class="tw-flex tw-items-center tw-gap-1">
<h2 bitTypography="h6" noMargin class="tw-mb-0 tw-text-headers">
{{ title }}
</h2>
<ng-content select="[slot=title-suffix]"></ng-content>
</div>
<div class="tw-text-muted has-[button]:-tw-mb-1">
<ng-content select="[slot=end]"></ng-content>
</div>
</div>

View File

@ -1,13 +0,0 @@
import { Component, Input } from "@angular/core";
import { TypographyModule } from "@bitwarden/components";
@Component({
standalone: true,
selector: "popup-section-header",
templateUrl: "./popup-section-header.component.html",
imports: [TypographyModule],
})
export class PopupSectionHeaderComponent {
@Input() title: string;
}

View File

@ -1,104 +0,0 @@
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import {
CardComponent,
IconButtonModule,
SectionComponent,
TypographyModule,
} from "@bitwarden/components";
import { PopupSectionHeaderComponent } from "./popup-section-header.component";
export default {
title: "Browser/Popup Section Header",
component: PopupSectionHeaderComponent,
args: {
title: "Title",
},
decorators: [
moduleMetadata({
imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule],
}),
],
} as Meta<PopupSectionHeaderComponent>;
type Story = StoryObj<PopupSectionHeaderComponent>;
export const OnlyTitle: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title"></popup-section-header>
`,
}),
args: {
title: "Only Title",
},
};
export const TrailingText: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<span bitTypography="body2" slot="end">13</span>
</popup-section-header>
`,
}),
args: {
title: "Trailing Text",
},
};
export const TailingIcon: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
`,
}),
args: {
title: "Trailing Icon",
},
};
export const TitleSuffix: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<button bitIconButton="bwi-refresh" size="small" slot="title-suffix"></button>
</popup-section-header>
`,
}),
args: {
title: "Title Suffix",
},
};
export const WithSections: Story = {
render: () => ({
template: `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<popup-section-header title="Section 1">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
<bit-card>
<h3 bitTypography="h3">Card 1 Content</h3>
</bit-card>
</bit-section>
<bit-section>
<popup-section-header title="Section 2">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
<bit-card>
<h3 bitTypography="h3">Card 2 Content</h3>
</bit-card>
</bit-section>
</div>
`,
}),
};

View File

@ -46,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../platform/popup/layout/popup-page.component";
import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component";
import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.component";
import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component";
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
@ -119,7 +118,6 @@ import "../platform/popup/locales";
PopupFooterComponent,
PopupHeaderComponent,
UserVerificationDialogComponent,
PopupSectionHeaderComponent,
CurrentAccountComponent,
],
declarations: [

View File

@ -8,17 +8,19 @@
></app-vault-list-items-container>
<ng-container *ngIf="showEmptyAutofillTip$ | async">
<bit-section>
<popup-section-header [title]="'autofillSuggestions' | i18n">
<bit-section-header>
<h2 bitTypography="h6">
{{ "autofillSuggestions" | i18n }}
</h2>
<button
*ngIf="showRefresh"
bitIconButton="bwi-refresh"
size="small"
slot="title-suffix"
type="button"
[appA11yTitle]="'refresh' | i18n"
(click)="refreshCurrentTab()"
></button>
</popup-section-header>
</bit-section-header>
<span class="tw-text-muted tw-px-1" bitTypography="body2">{{
"autofillSuggestionsTip" | i18n
}}</span>

View File

@ -3,10 +3,14 @@ import { Component } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components";
import {
IconButtonModule,
SectionComponent,
SectionHeaderComponent,
TypographyModule,
} from "@bitwarden/components";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
import { PopupCipherView } from "../../../views/popup-cipher.view";
import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component";
@ -19,7 +23,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
TypographyModule,
VaultListItemsContainerComponent,
JslibModule,
PopupSectionHeaderComponent,
SectionHeaderComponent,
IconButtonModule,
],
selector: "app-autofill-vault-list-items",

View File

@ -1,16 +1,18 @@
<bit-section *ngIf="ciphers?.length > 0">
<popup-section-header [title]="title">
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
<bit-section-header>
<h2 bitTypography="h6">
{{ title }}
</h2>
<button
*ngIf="showRefresh"
bitIconButton="bwi-refresh"
type="button"
size="small"
slot="title-suffix"
(click)="onRefresh.emit()"
[appA11yTitle]="'refresh' | i18n"
></button>
</popup-section-header>
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
</bit-section-header>
<bit-item-group>
<bit-item *ngFor="let cipher of ciphers">
<a

View File

@ -10,10 +10,10 @@ import {
IconButtonModule,
ItemModule,
SectionComponent,
SectionHeaderComponent,
TypographyModule,
} from "@bitwarden/components";
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { PopupCipherView } from "../../../views/popup-cipher.view";
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component";
@ -28,7 +28,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
SectionComponent,
TypographyModule,
JslibModule,
PopupSectionHeaderComponent,
SectionHeaderComponent,
RouterLink,
ItemCopyActionsComponent,
ItemMoreOptionsComponent,

View File

@ -246,7 +246,10 @@ export class LoginViaAuthRequestComponent
const deviceIdentifier = await this.appIdService.getAppId();
const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey);
const accessCode = await this.passwordGenerationService.generatePassword({ length: 25 });
const accessCode = await this.passwordGenerationService.generatePassword({
type: "password",
length: 25,
});
this.fingerprintPhrase = (
await this.cryptoService.getFingerprint(this.email, this.authRequestKeyPair.publicKey)

View File

@ -3,7 +3,6 @@ import { Observable } from "rxjs";
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { MasterKey } from "@bitwarden/common/types/key";
import {
@ -72,12 +71,4 @@ export abstract class LoginStrategyServiceAbstraction {
* Creates a master key from the provided master password and email.
*/
makePreloginKey: (masterPassword: string, email: string) => Promise<MasterKey>;
/**
* Sends a response to an auth request.
*/
passwordlessLogin: (
id: string,
key: string,
requestApproved: boolean,
) => Promise<AuthRequestResponse>;
}

View File

@ -24,8 +24,6 @@ import {
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@ -39,7 +37,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@ -263,47 +260,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig);
}
// TODO: move to auth request service
async passwordlessLogin(
id: string,
key: string,
requestApproved: boolean,
): Promise<AuthRequestResponse> {
const pubKey = Utils.fromB64ToArray(key);
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
let keyToEncrypt;
let encryptedMasterKeyHash = null;
if (masterKey) {
keyToEncrypt = masterKey.encKey;
// Only encrypt the master password hash if masterKey exists as
// we won't have a masterKeyHash without a masterKey
const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
if (masterKeyHash != null) {
encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
Utils.fromUtf8ToArray(masterKeyHash),
pubKey,
);
}
} else {
const userKey = await this.cryptoService.getUserKey();
keyToEncrypt = userKey.key;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey);
const request = new PasswordlessAuthRequest(
encryptedKey.encryptedString,
encryptedMasterKeyHash?.encryptedString,
await this.appIdService.getAppId(),
requestApproved,
);
return await this.apiService.putAuthRequest(id, request);
}
private async clearCache(): Promise<void> {
await this.currentAuthnTypeState.update((_) => null);
await this.loginStrategyCacheState.update((_) => null);

View File

@ -1 +1,2 @@
export * from "./section.component";
export * from "./section-header.component";

View File

@ -0,0 +1,8 @@
<div class="tw-flex tw-justify-between tw-items-end tw-gap-1">
<div class="[&>*]:tw-mb-0 [&>*]:tw-text-headers tw-flex tw-items-center tw-gap-1">
<ng-content></ng-content>
</div>
<div class="tw-text-muted has-[button]:-tw-mb-1">
<ng-content select="[slot=end]"></ng-content>
</div>
</div>

View File

@ -0,0 +1,16 @@
import { Component } from "@angular/core";
import { TypographyModule } from "../typography";
@Component({
standalone: true,
selector: "bit-section-header",
templateUrl: "./section-header.component.html",
imports: [TypographyModule],
host: {
class:
// apply bottom and x padding when a `bit-card` or `bit-item` is the immediate sibling, or nested in the immediate sibling
"tw-block has-[+_*_bit-card]:tw-pb-1 has-[+_bit-card]:tw-pb-1 has-[+_*_bit-item]:tw-pb-1 has-[+_bit-item]:tw-pb-1 has-[+_*_bit-card]:tw-px-1 has-[+_bit-card]:tw-px-1 has-[+_*_bit-item]:tw-px-1 has-[+_bit-item]:tw-px-1",
},
})
export class SectionHeaderComponent {}

View File

@ -0,0 +1,89 @@
import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs";
import * as stories from "./section.stories";
<Meta of={stories} />
```ts
import { SectionComponent, SectionHeaderComponent } from "@bitwarden/components";
```
# Section
Sections are simple containers that apply a responsive bottom margin and utilize the semantic
`section` HTML element.
<Canvas>
<Story of={stories.Default} />
</Canvas>
## Section Header
Sections often contain a heading. Use `bit-section-header` inside of the `bit-section`.
```html
<bit-section>
<bit-section-header>
<h1 bitTypography="h1">I'm a section header</h2>
</bit-section-header>
<div>Section content here!</div>
</bit-section>
```
### Section Header Padding
When placed inside of a section with a `bit-card` or `bit-item` as the immediate next sibling (or
nested in the immediate next sibling), the section header will automatically apply bottom and x-axis
padding to align the header with the border radius of the card/item.
```html
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">I'm a section header</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-card>
<h3 bitTypography="h3">I'm card content</h3>
</bit-card>
</bit-section>
```
<Canvas>
<Story of={stories.HeaderWithPadding} />
</Canvas>
If placed inside of a section without a `bit-card` or `bit-item`, or with a `bit-card`/`bit-item`
that is not a descendant of the immediate next sibling, the padding is not applied.
<Canvas>
<Story of={stories.HeaderWithoutPadding} />
</Canvas>
### Section Header Content Slots
`bit-section-header` contains the following slots to help position the content:
| Slot | Description |
| ------------ | ------------------------------- |
| default | title text of the header |
| `slot="end"` | placed at the end of the header |
#### Default slot
Anything passed to the default slot will display as part of the title. The title should be a
`bitTypography` element, usually an `h2` styled as an `h6`.
Title suffixes (typically an icon or icon button) can be added as well. A gap is automatically
applied between the children of the default slot.
<Canvas>
<Story of={stories.HeaderVariants} />
</Canvas>
#### End slot
The `end` slot will typically be used for text or an icon button.
<Canvas>
<Story of={stories.HeaderEndSlotVariants} />
</Canvas>

View File

@ -1,15 +1,24 @@
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
import { CardComponent } from "../card";
import { IconButtonModule } from "../icon-button";
import { ItemModule } from "../item";
import { TypographyModule } from "../typography";
import { SectionComponent } from "./section.component";
import { SectionComponent, SectionHeaderComponent } from "./";
export default {
title: "Component Library/Section",
component: SectionComponent,
decorators: [
moduleMetadata({
imports: [TypographyModule],
imports: [
TypographyModule,
SectionHeaderComponent,
CardComponent,
IconButtonModule,
ItemModule,
],
}),
componentWrapperDecorator((story) => `<div class="tw-text-main">${story}</div>`),
],
@ -17,19 +26,149 @@ export default {
type Story = StoryObj<SectionComponent>;
/** Sections are simple containers that apply a responsive bottom margin. They often contain a heading. */
export const Default: Story = {
render: (args) => ({
props: args,
template: `
render: () => ({
template: /*html*/ `
<bit-section>
<bit-section-header>
<h2 bitTypography="h2">Foo</h2>
</bit-section-header>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h2">Bar</h2>
</bit-section-header>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section>
`,
}),
};
export const HeaderVariants: Story = {
render: () => ({
template: /*html*/ `
<bit-section-header>
<h2 bitTypography="h6">
Title only
</h2>
</bit-section-header>
<bit-section-header>
<h2 bitTypography="h6">
Title with icon button suffix
</h2>
<button bitIconButton="bwi-refresh" size="small"></button>
</bit-section-header>
`,
}),
};
export const HeaderEndSlotVariants: Story = {
render: () => ({
template: /*html*/ `
<bit-section-header>
<h2 bitTypography="h6">
Title with end slot text
</h2>
<span bitTypography="body2" slot="end">13</span>
</bit-section-header>
<bit-section-header>
<h2 bitTypography="h6">
Title with end slot icon button
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
`,
}),
};
export const HeaderWithPadding: Story = {
render: () => ({
template: /*html*/ `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card as immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-card>
<h3 bitTypography="h3">bit-section-header has padding</h3>
</bit-card>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card nested in immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div>
<bit-card>
<h3 bitTypography="h3">bit-section-header has padding</h3>
</bit-card>
</div>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Item as immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-item>
<bit-item-content bitTypography="body1">bit-section-header has padding</bit-item-content>
</bit-item>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Item nested in immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-item-group>
<bit-item>
<bit-item-content bitTypography="body1">bit-section-header has padding</bit-item-content>
</bit-item>
</bit-item-group>
</bit-section>
</div>
`,
}),
};
export const HeaderWithoutPadding: Story = {
render: () => ({
template: /*html*/ `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
No card or item used
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div>
<h3 bitTypography="h3">just a div, so bit-section-header has no padding</h3>
</div>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card nested in non-immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div class="tw-text-main">
a div here
</div>
<bit-card>
<h3 bitTypography="h3">bit-section-header has no padding</h3>
</bit-card>
</bit-section>
</div>
`,
}),
};