mirror of
https://github.com/bitwarden/browser
synced 2026-01-30 16:23:53 +00:00
Merge branch 'main' into beeep/dev-container
This commit is contained in:
@@ -2,11 +2,14 @@
|
||||
|
||||
A library for native autofill providers to interact with a host Bitwarden desktop app.
|
||||
|
||||
In this desktop context, "native autofill providers" are operating system frameworks or APIs (like the macOS [autofill framework](https://developer.apple.com/documentation/Security/password-autofill)) that allow Bitwarden to provide provide user credentials for things like autofill, passkey operations, etc.
|
||||
|
||||
# Explainer: Mac OS Native Passkey Provider
|
||||
|
||||
This document describes the changes introduced in https://github.com/bitwarden/clients/pull/13963, where we introduce the MacOS Native Passkey Provider. It gives the high level explanation of the architecture and some of the quirks and additional good to know context.
|
||||
|
||||
## The high level
|
||||
|
||||
MacOS has native APIs (similar to iOS) to allow Credential Managers to provide credentials to the MacOS autofill system (in the PR referenced above, we only provide passkeys).
|
||||
|
||||
We’ve written a Swift-based native autofill-extension. It’s bundled in the app-bundle in PlugIns, similar to the safari-extension.
|
||||
@@ -16,7 +19,7 @@ This swift extension currently communicates with our Electron app through IPC ba
|
||||
Footnotes:
|
||||
|
||||
* We're not using the IPC framework as the implementation pre-dates the IPC framework.
|
||||
* Alternatives like XPC or CFMessagePort may have better support for when the app is sandboxed.
|
||||
* Alternatives like XPC or CFMessagePort may have better support for when the app is sandboxed.
|
||||
|
||||
Electron receives the messages and passes it to Angular (through the electron-renderer event system).
|
||||
|
||||
@@ -26,7 +29,7 @@ Our existing fido2 services in the renderer respond to events, displaying UI as
|
||||
|
||||
We utilize the same FIDO2 implementation and interface that is already present for our browser authentication. It was designed by @coroiu with multiple ‘ui environments' in mind.
|
||||
|
||||
Therefore, a lot of the plumbing is implemented in /autofill/services/desktop-fido2-user-interface.service.ts, which implements the interface that our fido2 authenticator/client expects to drive UI related behaviors.
|
||||
Therefore, a lot of the plumbing is implemented in /autofill/services/desktop-fido2-user-interface.service.ts, which implements the interface that our fido2 authenticator/client expects to drive UI related behaviors.
|
||||
|
||||
We’ve also implemented a couple FIDO2 UI components to handle registration/sign in flows, but also improved the “modal mode” of the desktop app.
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
></button>
|
||||
}
|
||||
@for (childFolder of folder().children; track childFolder.node.id) {
|
||||
<app-folder-filter [folder]="childFolder" [activeFilter]="activeFilter()" />
|
||||
<app-folder-filter
|
||||
[folder]="childFolder"
|
||||
[activeFilter]="activeFilter()"
|
||||
(onEditFolder)="editFolder($event)"
|
||||
/>
|
||||
}
|
||||
</bit-nav-group>
|
||||
} @else {
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
<bit-container>
|
||||
<p>{{ "reportsDesc" | i18n }}</p>
|
||||
|
||||
<app-report-list [reports]="reports"></app-report-list>
|
||||
<app-report-list [reports]="reports()"></app-report-list>
|
||||
</bit-container>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, OnInit, signal } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -16,7 +16,7 @@ import { ReportEntry, ReportVariant } from "../shared";
|
||||
standalone: false,
|
||||
})
|
||||
export class ReportsHomeComponent implements OnInit {
|
||||
reports: ReportEntry[];
|
||||
readonly reports = signal<ReportEntry[]>([]);
|
||||
|
||||
constructor(
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
@@ -32,7 +32,7 @@ export class ReportsHomeComponent implements OnInit {
|
||||
? ReportVariant.Enabled
|
||||
: ReportVariant.RequiresPremium;
|
||||
|
||||
this.reports = [
|
||||
this.reports.set([
|
||||
{
|
||||
...reports[ReportType.ExposedPasswords],
|
||||
variant: reportRequiresPremium,
|
||||
@@ -57,6 +57,6 @@ export class ReportsHomeComponent implements OnInit {
|
||||
...reports[ReportType.DataBreach],
|
||||
variant: ReportVariant.Enabled,
|
||||
},
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div
|
||||
class="tw-inline-grid tw-place-items-stretch tw-place-content-center tw-grid-cols-1 @xl:tw-grid-cols-2 @4xl:tw-grid-cols-3 tw-gap-4 [&_a]:tw-max-w-none @5xl:[&_a]:tw-max-w-72"
|
||||
>
|
||||
@for (report of reports; track report) {
|
||||
@for (report of reports(); track report) {
|
||||
<div>
|
||||
<app-report-card
|
||||
[title]="report.title | i18n"
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, input } from "@angular/core";
|
||||
|
||||
import { ReportEntry } from "../models/report-entry";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: "app-report-list",
|
||||
templateUrl: "report-list.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class ReportListComponent {
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() reports: ReportEntry[];
|
||||
readonly reports = input<ReportEntry[]>([]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
Injector,
|
||||
OnInit,
|
||||
Signal,
|
||||
computed,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
input,
|
||||
signal,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -59,6 +58,9 @@ export class PasswordChangeMetricComponent implements OnInit {
|
||||
private readonly _tasks: Signal<SecurityTask[]> = signal<SecurityTask[]>([]);
|
||||
private readonly _atRiskCipherIds: Signal<CipherId[]> = signal<CipherId[]>([]);
|
||||
private readonly _hasCriticalApplications: Signal<boolean> = signal<boolean>(false);
|
||||
private readonly _reportGeneratedAt: Signal<Date | undefined> = signal<Date | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Computed properties
|
||||
readonly tasksCount = computed(() => this._tasks().length);
|
||||
@@ -80,8 +82,24 @@ export class PasswordChangeMetricComponent implements OnInit {
|
||||
}
|
||||
|
||||
const inProgressTasks = tasks.filter((task) => task.status === SecurityTaskStatus.Pending);
|
||||
const assignedIdSet = new Set(inProgressTasks.map((task) => task.cipherId));
|
||||
const unassignedIds = atRiskIds.filter((id) => !assignedIdSet.has(id));
|
||||
const inProgressTaskIds = new Set(inProgressTasks.map((task) => task.cipherId));
|
||||
|
||||
const reportGeneratedAt = this._reportGeneratedAt();
|
||||
const completedTasksAfterReportGeneration = reportGeneratedAt
|
||||
? tasks.filter(
|
||||
(task) =>
|
||||
task.status === SecurityTaskStatus.Completed &&
|
||||
new Date(task.revisionDate) >= reportGeneratedAt,
|
||||
)
|
||||
: [];
|
||||
const completedTaskIds = new Set(
|
||||
completedTasksAfterReportGeneration.map((task) => task.cipherId),
|
||||
);
|
||||
|
||||
// find cipher ids from last report that do not have a corresponding in progress task (awaiting password reset) OR completed task
|
||||
const unassignedIds = atRiskIds.filter(
|
||||
(id) => !inProgressTaskIds.has(id) && !completedTaskIds.has(id),
|
||||
);
|
||||
|
||||
return unassignedIds.length;
|
||||
});
|
||||
@@ -109,36 +127,26 @@ export class PasswordChangeMetricComponent implements OnInit {
|
||||
constructor(
|
||||
private allActivitiesService: AllActivitiesService,
|
||||
private i18nService: I18nService,
|
||||
private injector: Injector,
|
||||
private riskInsightsDataService: RiskInsightsDataService,
|
||||
protected securityTasksService: AccessIntelligenceSecurityTasksService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
// Setup the _tasks signal by manually passing in the injector
|
||||
this._tasks = toSignal(this.securityTasksService.tasks$, {
|
||||
initialValue: [],
|
||||
injector: this.injector,
|
||||
});
|
||||
// Setup the _atRiskCipherIds signal by manually passing in the injector
|
||||
this._tasks = toSignal(this.securityTasksService.tasks$, { initialValue: [] });
|
||||
this._atRiskCipherIds = toSignal(
|
||||
this.riskInsightsDataService.criticalApplicationAtRiskCipherIds$,
|
||||
{
|
||||
initialValue: [],
|
||||
injector: this.injector,
|
||||
},
|
||||
{ initialValue: [] },
|
||||
);
|
||||
|
||||
this._hasCriticalApplications = toSignal(
|
||||
this.riskInsightsDataService.criticalReportResults$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((report) => {
|
||||
return report != null && (report.reportData?.length ?? 0) > 0;
|
||||
}),
|
||||
),
|
||||
{
|
||||
initialValue: false,
|
||||
injector: this.injector,
|
||||
},
|
||||
{ initialValue: false },
|
||||
);
|
||||
this._reportGeneratedAt = toSignal(
|
||||
this.riskInsightsDataService.enrichedReportData$.pipe(map((report) => report?.creationDate)),
|
||||
{ initialValue: undefined },
|
||||
);
|
||||
|
||||
effect(() => {
|
||||
|
||||
Reference in New Issue
Block a user