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

[PM-9035] desktop build logic to provide credentials to os on sync (#10181)

* feat: scaffold desktop_objc

* feat: rename fido2 to autofill

* feat: scaffold electron autofill

* feat: auto call hello world on init

* feat: scaffold call to basic objc function

* feat: simple log that checks if autofill is enabled

* feat: adding some availability guards

* feat: scaffold services and allow calls from inspector

* feat: create custom type for returning strings across rust/objc boundary

* chore: clean up comments

* feat: enable ARC

* feat: add util function `c_string_to_nsstring`

* chore: refactor and rename to `run_command`

* feat: add try-catch around command execution

* feat: properly implement command calling

Add static typing. Add proper error handling.

* feat: add autoreleasepool to avoid memory leaks

* chore: change objc names to camelCase

* fix: error returning

* feat: extract some helper functions into utils class

* feat: scaffold status command

* feat: implement status command

* feat: implement password credential mapping

* wip: implement sync command

This crashes because we are not properly handling the fact that `saveCredentialIdentities` uses callbacks, resulting in a race condition where we try to access a variable (result) that has already gotten dealloc'd.

* feat: first version of callback

* feat: make run_command async

* feat: functioning callback returns

* chore: refactor to make objc code easier to read and use

* feat: refactor everything to use new callback return method

* feat: re-implement status command with callback

* fix: warning about CommandContext not being FFI-safe

* feat: implement sync command using callbacks

* feat: implement manual password credential sync

* feat: add auto syncing

* docs: add todo

* feat: add support for passkeys

* chore: move desktop autofill service to init service

* feat: auto-add all .m files to builder

* fix: native build on unix and windows

* fix: unused compiler warnings

* fix: napi type exports

* feat: add corresponding dist command

* feat: comment signing profile until we fix signing

* fix: build breaking on non-macOS platforms

* chore: cargo lock update

* chore: revert accidental version change

* feat: put sync behind feature flag

* chore: put files in autofill folder

* fix: obj-c code not recompiling on changes

* feat: add `namespace` to commands

* fix: linting complaining about flag

* feat: add autofill as owner of their objc code

* chore: make autofill owner of run_command in core crate

* fix: re-add napi annotation

* fix: remove dev bypass
This commit is contained in:
Andreas Coroiu
2024-12-06 16:31:30 +01:00
committed by GitHub
parent f95cc7b82c
commit f16bfa4cd2
41 changed files with 1099 additions and 112 deletions

View File

@@ -0,0 +1,9 @@
import { ipcRenderer } from "electron";
import { Command } from "../platform/main/autofill/command";
import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main";
export default {
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
ipcRenderer.invoke("autofill.runCommand", params),
};

View File

@@ -0,0 +1,121 @@
import { Injectable, OnDestroy } from "@angular/core";
import { EMPTY, Subject, distinctUntilChanged, mergeMap, switchMap, takeUntil } from "rxjs";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { NativeAutofillStatusCommand } from "../../platform/main/autofill/status.command";
import {
NativeAutofillFido2Credential,
NativeAutofillPasswordCredential,
NativeAutofillSyncCommand,
} from "../../platform/main/autofill/sync.command";
@Injectable()
export class DesktopAutofillService implements OnDestroy {
private destroy$ = new Subject<void>();
constructor(
private logService: LogService,
private cipherService: CipherService,
private configService: ConfigService,
) {}
async init() {
this.configService
.getFeatureFlag$(FeatureFlag.MacOsNativeCredentialSync)
.pipe(
distinctUntilChanged(),
switchMap((enabled) => {
if (!enabled) {
return EMPTY;
}
return this.cipherService.cipherViews$;
}),
// TODO: This will unset all the autofill credentials on the OS
// when the account locks. We should instead explicilty clear the credentials
// when the user logs out. Maybe by subscribing to the encrypted ciphers observable instead.
mergeMap((cipherViewMap) => this.sync(Object.values(cipherViewMap ?? []))),
takeUntil(this.destroy$),
)
.subscribe();
}
/** Give metadata about all available credentials in the users vault */
async sync(cipherViews: CipherView[]) {
const status = await this.status();
if (status.type === "error") {
return this.logService.error("Error getting autofill status", status.error);
}
if (!status.value.state.enabled) {
// Autofill is disabled
return;
}
let fido2Credentials: NativeAutofillFido2Credential[];
let passwordCredentials: NativeAutofillPasswordCredential[];
if (status.value.support.password) {
passwordCredentials = cipherViews
.filter(
(cipher) =>
cipher.type === CipherType.Login &&
cipher.login.uris?.length > 0 &&
cipher.login.uris.some((uri) => uri.match !== UriMatchStrategy.Never) &&
cipher.login.uris.some((uri) => !Utils.isNullOrWhitespace(uri.uri)) &&
!Utils.isNullOrWhitespace(cipher.login.username),
)
.map((cipher) => ({
type: "password",
cipherId: cipher.id,
uri: cipher.login.uris.find((uri) => uri.match !== UriMatchStrategy.Never).uri,
username: cipher.login.username,
}));
}
if (status.value.support.fido2) {
fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({
type: "fido2",
...credential,
}));
}
const syncResult = await ipc.autofill.runCommand<NativeAutofillSyncCommand>({
namespace: "autofill",
command: "sync",
params: {
credentials: [...fido2Credentials, ...passwordCredentials],
},
});
if (syncResult.type === "error") {
return this.logService.error("Error syncing autofill credentials", syncResult.error);
}
this.logService.debug(`Synced ${syncResult.value.added} autofill credentials`);
}
/** Get autofill status from OS */
private status() {
// TODO: Investigate why this type needs to be explicitly set
return ipc.autofill.runCommand<NativeAutofillStatusCommand>({
namespace: "autofill",
command: "status",
params: {},
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}