Skip to content

[WIP] introduce generator deep dive documentation #595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking โ€œSign up for GitHubโ€, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions custom-words.txt
Original file line number Diff line number Diff line change
@@ -19,6 +19,8 @@ dockerized
dotfile
F-Droid
Gitter
heisenbug
heisenbugs
HKDF
hotfix
hotfixed
Empty file.
Empty file.
54 changes: 54 additions & 0 deletions docs/architecture/deep-dives/credential-generators/index.md
Original file line number Diff line number Diff line change
@@ -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)
220 changes: 220 additions & 0 deletions docs/architecture/deep-dives/credential-generators/ts-service.md
Original file line number Diff line number Diff line change
@@ -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<T>` - An observable that emits requests to be fulfilled by the service.
The `generate$` method watches an `Do<GeneratorRequest>` that signals
when it should generate a new value.
* `Live<T>` - An observable that emits when its value changes. These interfaces
are mostly used to track internal state. The `Observable<T>`
returned by `preference$` acts like a `Live<T>`.
* `Once<T>` - An observable that emits a single value and completes when that
value is no longer available. Many methods complete when their
`Once<Account>` dependency completes.


In code, these are all modelled using `Observable<T>` 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<GeneratorRequest>` semantics
const on$ = new Subject<GeneratorRequest>();

// account$ requires `Once<Account>` 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<GeneratorRequest>` semantics
const on$ = new Subject<GeneratorRequest>();

// account$ requires `Once<Account>` 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.

:::