diff --git a/custom-words.txt b/custom-words.txt index ab723341a..196255afd 100644 --- a/custom-words.txt +++ b/custom-words.txt @@ -19,6 +19,8 @@ dockerized dotfile F-Droid Gitter +heisenbug +heisenbugs HKDF hotfix hotfixed diff --git a/docs/architecture/deep-dives/credential-generators/add-a-profile.md b/docs/architecture/deep-dives/credential-generators/add-a-profile.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/architecture/deep-dives/credential-generators/angular-components.md b/docs/architecture/deep-dives/credential-generators/angular-components.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/architecture/deep-dives/credential-generators/index.md b/docs/architecture/deep-dives/credential-generators/index.md new file mode 100644 index 000000000..101dffaf1 --- /dev/null +++ b/docs/architecture/deep-dives/credential-generators/index.md @@ -0,0 +1,54 @@ +# Credential Generation + +Bitwarden's credential generation framework provides services used to generate passwords, usernames, +and email addresses across all of our clients. At present, there are several frameworks in use. + +- The Android and iOS clients directly invoke SDK functions. +- The CLI client invoke promise-based `PasswordGenerationService` and `UsernameGenerationService` + interfaces. +- All other clients are transitioning to the reactive `CredentialGeneratorService` interface. + +This deep dive is focused on the reactive interfaces, which are the compatibility target Tools is +focusing on across all implementations. + +## Concepts + +### Algorithms and Types + +Credential algorithm - each algorithm provides a different way to generate a credential. Credential +types - these allow a caller to say ‘use my preferred credential algorithm’. + +### Metadata + +The core logic resides in several files: + + metadata/data.ts - these are enum-alike data structures used to derive and conveniently access type information + metadata/type.ts - root data structures used to identify extensions and interact with extension data + +Domain metadata then builds on top of the algorithms and types: + + Algorithm metadata - describes algorithms and capabilities + Profile metadata - describes a use-case for the generator + At present all generators share the "account" profile. + This was introduced to enable a secondary profile for master password generation. + Profiles also enable settings to be provided by the generator ("core") or to be delegated to a secondary system. The first use-case enables the extension system to provide settings (future PR). It could also be used to create collection-bound or autofill-provided profiles. + Generator metadata - combines algorithm metadata, profile metadata, and engine creation logic for use by the credential generator service. + +The metadata provider combines static generator metadata provided at startup time with a dynamic +list of forwarder metadata computed at metadata-lookup-time. This lets observables with complex type +signatures, such as the generator code, operate in a type-safe way with type erasure while +encapsulating policy, platform availability, and user preference settings. + +### Profiles + +Credential profiles - these allow system integrations (e.g. device login) to customize how policy +and settings are managed at an integration site. The generator profiles may include their own +storage options, default generator values, generator UI rules (constraints), and policy targets. + +### Preferences + +... + +## Further Reading + +- [generator internals](https://github.com/bitwarden/clients/blob/main/libs/tools/generator/CONVENTIONS.md) diff --git a/docs/architecture/deep-dives/credential-generators/ts-service.md b/docs/architecture/deep-dives/credential-generators/ts-service.md new file mode 100644 index 000000000..9eb843953 --- /dev/null +++ b/docs/architecture/deep-dives/credential-generators/ts-service.md @@ -0,0 +1,220 @@ +# `CredentialGeneratorService` + +The `CredentialGeneratorService` is the entry point to the generator ecosystem. It +serves generator instances, persistent settings, user preferences, and algorithm +metadata to its callers. Its interfaces are fully reactive, and its settings objects +operate like rxjs subjects. + +:::tip[Run hot] + +The service's observables are optimized to run hot, and are inefficient +when used with `firstValueFrom`. For the best performance, prefer +subscription to one-time activation. + +::: + +## Reactivity + +The generator's reactivity relies on three reactive dependency patterns. When your +observables follow these patterns, generator emissions are highly predictable. + +* `Do` - An observable that emits requests to be fulfilled by the service. + The `generate$` method watches an `Do` that signals + when it should generate a new value. +* `Live` - An observable that emits when its value changes. These interfaces + are mostly used to track internal state. The `Observable` + returned by `preference$` acts like a `Live`. +* `Once` - An observable that emits a single value and completes when that + value is no longer available. Many methods complete when their + `Once` dependency completes. + + +In code, these are all modelled using `Observable` for interoperability. Their +their behaviors are enforced programmatically. + +All generator observables have nondeterministic behavior. The primary form of +nondeterminism is asynchronous observables. Methods like `algorithms$` and +`generate$` depend on user state, and thus are necessarily asynchronous. +Consult the service documentation and tests for further details. + +### Applied Reactivity + +Assume: +```ts +import * from "@bitwarden/generator-core"; // other imports omitted + +// on$ requires `Do` semantics +const on$ = new Subject(); + +// account$ requires `Once` semantics +const account$ = inject(AccountService).activeAccount$.pipe(pin()); + +const service = inject(CredentialGeneratorService); +``` + +#### Generating an email address + +The following example generates an email address using the user's preferred +email generator algorithm. + +```ts +service.generate$({ on$, account$ }).subscribe((generated) => { + console.log(generated); +}); + +on$.next({ type: Type.email }); +``` + +#### Generating a password + +The following example generates a password explicitly. If the password type is +rejected by a policy, then `generate$` falls back to a passphrase. + +```ts +service.generate$({ on$, account$ }).subscribe((generated) => { + console.log(generated); +}); + +on$.next({ algorithm: Algorithm.password }) +``` + +:::info + +If your use case requires a precise algorithm, you need to create a custom profile +that disables the default policy. See [Add a Profile](./add-a-profile.md) for more +information. + +::: + +#### Loading and saving built-in settings + +Settings are always algorithm-specific. They're loaded from the service +using a subject that derives its type from its metadata argument. + +`BuiltIn` contains static metadata for algorithms provided by the generator +system. + +```ts +// settings are emitted once $account emits and the `UserKey` becomes available. +const settings$ = service.settings$(BuiltIn.passphrase, { $account }); +settings$.subscribe({ + next: (settings) => console.log(settings), + error: (error) => console.log(error), + complete: () => console.log("settings$ completed"), +}); + +// write to the settings using `next(...)`; the value emits on *all* instances +// loaded using `BuiltIn.passphrase`. +settings$.next({ + numWords: 6, + wordSeparator: "-"; + capitalize: true; + includeNumber: true; +}) + +// settings$ forwards errors to instance subscribers only +service.settings$(BuiltIn.passphrase, { $account }).subscribe({ + error: () => console.log("this doesn't receive the error"), +}); +settings$.error(new Error("I've made a huge mistake")); + +// settings$ forwards complete to listeners subscribed to settings$ instance only +service.settings$(BuiltIn.passphrase, { $account }).subscribe({ + compelte: () => console.log("this doesn't receive the complete"), +}); +settings$.complete(); +``` + +#### Loading and saving forwarder extension settings + +The forwarder metadata is constructed dynamically and must be queried. + +```ts +import { Vendor } from "@bitwarden/common/tools/extension/vendors" + +const forwarder = service.forwarder(Vendor.bitwarden); +const settings$ = service.settings(forwarder, { $account }); + +// from here it acts as above. +``` + + +## Algorithm metadata + +Algorithm metadata includes shared i18n keys and UX behavior hints so that service +consumers can provide a consistent experience with the angular components. They also +indicate an algorithm's type and algorithm. + +### Applied Metadata + +```ts +import * from "@bitwarden/generator-core"; // other imports omitted + +// on$ requires `Do` semantics +const on$ = new Subject(); + +// account$ requires `Once` semantics +const account$ = inject(AccountService).activeAccount$.pipe(pin()); + +const service = inject(CredentialGeneratorService); +``` + +#### System metadata + +```ts +const passwordAlgorithm = service.algorithm(Algorithm.password); +const emailAlgorithms = service.algorithms(Type.email); +const mixedAlgorithms = service.algorithms([Type.email, Type.username]); + +// the email category contains forwarder algorithms +const forwarderAlgorithms = service.algorithms(Type.email).map(isForwarderExtensionId); +``` + +:::warn + +Metadata provided by the system endpoints does not enforce algorithm +availability policy. + +::: + + +#### Live algorithm metadata + +Live algorithm metadata requires an account and filters its output according +to the account's policy controls. + +```ts +const emailAlgs$ = service.algorithms$(Type.email, { $account }); +const passwordAlgs$ = service.algorithms$(Type.password, { $account }); +const usernameAlgs$ = service.algorithms$(Type.username, { $account }); +``` + + + + + +## Forwarder Extensions + +Vendors can integrate their email forwarding services at the `forwarder` extension +site. The site allows them to define an account identifier lookup and a generate +forwarding address RPC. Forwarder settings storage and availability are delegated +to the extension system. All forwarders share a single format for their settings +and determine which are surfaced to the user via the extension's `requestedFields` +logic. + +Forwarder extensions presently invoke 3rd party APIs using `tools/integration/rpc` +functions. These interfaces let the generator surface contextual information, +such as the user's current website or a custom API host, to the extension +on a need-to-know basis. At the time of writing, the website is specified as a +generator request parameter. + +:::note + +The integration code, including RPC and context control, will not be discussed +at the present time. These systems were introduced hastily, and require refinement. + +It is our intention to review the interfaces as we port the generator system to +the SDK. + +::: +