mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 13:10:17 +00:00
partial unit test implementation
This commit is contained in:
@@ -9,6 +9,7 @@ export interface SemanticLogger {
|
||||
*/
|
||||
debug(message: string): void;
|
||||
|
||||
// FIXME: replace Jsonify<T> parameter with structural logging schema type
|
||||
/** Logs the content at debug priority.
|
||||
* Debug messages are used for diagnostics, and are typically disabled
|
||||
* in production builds.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { AlgorithmsByType as ABT } from "./data";
|
||||
import { Algorithm as AlgorithmData, AlgorithmsByType as AlgorithmsByTypeData, Type as TypeData } from "./data";
|
||||
import { CredentialType, CredentialAlgorithm } from "./type";
|
||||
|
||||
// `CredentialAlgorithm` is defined in terms of `ABT`; supplying
|
||||
// type information in the barrel file breaks a circular dependency.
|
||||
/** Credential generation algorithms grouped by purpose. */
|
||||
export const AlgorithmsByType: Record<CredentialType, ReadonlyArray<CredentialAlgorithm>> = ABT;
|
||||
export const AlgorithmsByType: Record<CredentialType, ReadonlyArray<CredentialAlgorithm>> = AlgorithmsByTypeData;
|
||||
export const Algorithms: ReadonlyArray<CredentialAlgorithm> = Object.freeze(Object.values(AlgorithmData));
|
||||
export const Types: ReadonlyArray<CredentialType> = Object.freeze(Object.values(TypeData));
|
||||
|
||||
export { Profile, Type } from "./data";
|
||||
export { Profile, Type, Algorithm } from "./data";
|
||||
export { toForwarderMetadata } from "./email/forwarder";
|
||||
export { GeneratorMetadata } from "./generator-metadata";
|
||||
export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata";
|
||||
|
||||
@@ -3,18 +3,25 @@ import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FakeAccountService, FakeStateProvider } from "@bitwarden/common/spec";
|
||||
import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
|
||||
import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
|
||||
import { ExtensionMetadata, ExtensionSite, Site, SiteId, SiteMetadata } from "@bitwarden/common/tools/extension"
|
||||
import { ExtensionService } from "@bitwarden/common/tools/extension/extension.service";
|
||||
import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden";
|
||||
import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log";
|
||||
import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
|
||||
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
|
||||
import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec";
|
||||
import { Algorithm, AlgorithmsByType, Profile, Type, Types } from "../metadata";
|
||||
import password from "../metadata/password/random-password";
|
||||
|
||||
import { GeneratorMetadataProvider } from "./generator-metadata-provider";
|
||||
|
||||
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
const SomeAccount = {
|
||||
id: SomeUser,
|
||||
@@ -59,35 +66,257 @@ const SystemProvider = {
|
||||
log: disabledSemanticLoggerProvider,
|
||||
} as UserStateSubjectDependencyProvider;
|
||||
|
||||
const SomeSiteId: SiteId = Site.forwarder;
|
||||
|
||||
const SomeSite: SiteMetadata = Object.freeze({
|
||||
id: SomeSiteId,
|
||||
availableFields: [],
|
||||
});
|
||||
|
||||
const ApplicationProvider = {
|
||||
/** Policy configured by the administrative console */
|
||||
policy: mock<PolicyService>(),
|
||||
|
||||
/** Client extension metadata and profile access */
|
||||
extension: mock<ExtensionService>(),
|
||||
extension: mock<ExtensionService>({
|
||||
site: () => new ExtensionSite(SomeSite, new Map())
|
||||
}),
|
||||
|
||||
/** Event monitoring and diagnostic interfaces */
|
||||
log: disabledSemanticLoggerProvider,
|
||||
}
|
||||
|
||||
describe("GeneratorMetadatProvider", () => {
|
||||
describe("algorithms", () => {
|
||||
} as SystemServiceProvider;
|
||||
|
||||
describe("GeneratorMetadataProvider", () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("available$", () => {
|
||||
describe("metadata", () => {
|
||||
it("returns algorithm metadata", async () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [password]);
|
||||
|
||||
const metadata = provider.metadata(password.id);
|
||||
|
||||
expect(metadata).toEqual(password);
|
||||
});
|
||||
|
||||
it("returns forwarder metadata", async () => {
|
||||
const extensionMetadata : ExtensionMetadata = {
|
||||
site: SomeSite,
|
||||
product: { vendor: Bitwarden },
|
||||
host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
|
||||
requestedFields: []
|
||||
}
|
||||
const application = {
|
||||
...ApplicationProvider,
|
||||
extension: mock<ExtensionService>({
|
||||
site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]]))
|
||||
})
|
||||
};
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
|
||||
|
||||
const metadata = provider.metadata({ forwarder: Bitwarden.id });
|
||||
|
||||
expect(metadata.id).toEqual({ forwarder: Bitwarden.id });
|
||||
});
|
||||
|
||||
it("panics when metadata not found", async () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
expect(() => provider.metadata("not found" as any)).toThrow("metadata not found");
|
||||
});
|
||||
|
||||
it("panics when an extension not found", async () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
expect(() => provider.metadata({ forwarder: "not found" as any })).toThrow("extension not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("types", () => {
|
||||
it("returns the credential types", async () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const result = provider.types();
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(Types));
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithms", () => {
|
||||
it("returns the password category's algorithms", () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const result = provider.algorithms({ category: Type.password });
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.password]));
|
||||
});
|
||||
|
||||
it("returns the username category's algorithms", () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const result = provider.algorithms({ category: Type.username });
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.username]));
|
||||
});
|
||||
|
||||
it("returns the email category's algorithms", () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const result = provider.algorithms({ category: Type.email });
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.email]));
|
||||
});
|
||||
|
||||
it("includes forwarder vendors in the email category's algorithms", () => {
|
||||
const extensionMetadata : ExtensionMetadata = {
|
||||
site: SomeSite,
|
||||
product: { vendor: Bitwarden },
|
||||
host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
|
||||
requestedFields: []
|
||||
}
|
||||
const application = {
|
||||
...ApplicationProvider,
|
||||
extension: mock<ExtensionService>({
|
||||
site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]]))
|
||||
})
|
||||
};
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
|
||||
|
||||
const result = provider.algorithms({ category: Type.email });
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]))
|
||||
});
|
||||
|
||||
it.each([
|
||||
[Algorithm.catchall],
|
||||
[Algorithm.passphrase],
|
||||
[Algorithm.password],
|
||||
[Algorithm.plusAddress],
|
||||
[Algorithm.username],
|
||||
])("returns explicit algorithms (=%p)", (algorithm) => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const result = provider.algorithms({ algorithm });
|
||||
|
||||
expect(result).toEqual([algorithm]);
|
||||
});
|
||||
|
||||
it("returns explicit forwarders", () => {
|
||||
const extensionMetadata : ExtensionMetadata = {
|
||||
site: SomeSite,
|
||||
product: { vendor: Bitwarden },
|
||||
host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
|
||||
requestedFields: []
|
||||
}
|
||||
const application = {
|
||||
...ApplicationProvider,
|
||||
extension: mock<ExtensionService>({
|
||||
site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]]))
|
||||
})
|
||||
};
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
|
||||
|
||||
const result = provider.algorithms({ algorithm: { forwarder: Bitwarden.id } });
|
||||
|
||||
expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]))
|
||||
});
|
||||
|
||||
it("returns an empty array when the algorithm is invalid", () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
// `any` cast required because this test subverts the type system
|
||||
const result = provider.algorithms({ algorithm: "an invalid algorithm" as any });
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns an empty array when the forwarder is invalid", () => {
|
||||
const extensionMetadata : ExtensionMetadata = {
|
||||
site: SomeSite,
|
||||
product: { vendor: Bitwarden },
|
||||
host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
|
||||
requestedFields: []
|
||||
}
|
||||
const application = {
|
||||
...ApplicationProvider,
|
||||
extension: mock<ExtensionService>({
|
||||
site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]]))
|
||||
})
|
||||
};
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
|
||||
|
||||
// `any` cast required because this test subverts the type system
|
||||
const result = provider.algorithms({ algorithm: { forwarder: "an invalid forwarder" as any } });
|
||||
|
||||
expect(result).toEqual([])
|
||||
});
|
||||
|
||||
it("panics when neither an algorithm nor a category is specified", () => {
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
// `any` cast required because this test subverts the type system
|
||||
expect(() => provider.algorithms({} as any)).toThrow('algorithm or category required');
|
||||
});
|
||||
});
|
||||
|
||||
describe("preference$", () => {
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithm$", () => {
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
|
||||
it("", async () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("preferences", () => {
|
||||
it("returns a user state subject", () => {
|
||||
const metadata = new GeneratorMetadataProvider(SystemProvider, null, null);
|
||||
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
|
||||
|
||||
const subject = metadata.preferences({ account$: SomeAccount$ });
|
||||
const subject = provider.preferences({ account$: SomeAccount$ });
|
||||
|
||||
expect(subject).toBeInstanceOf(UserStateSubject);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import {
|
||||
Observable,
|
||||
combineLatestWith,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
withLatestFrom,
|
||||
} from "rxjs";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -28,6 +26,8 @@ import {
|
||||
isForwarderExtensionId,
|
||||
toForwarderMetadata,
|
||||
Type,
|
||||
Algorithms,
|
||||
Types,
|
||||
} from "../metadata";
|
||||
import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy";
|
||||
import { CredentialPreference } from "../types";
|
||||
@@ -57,18 +57,57 @@ export class GeneratorMetadataProvider {
|
||||
}
|
||||
this.site = site;
|
||||
|
||||
this.generators = new Map(algorithms.map((a) => [a.id, a] as const));
|
||||
this._metadata = new Map(algorithms.map((a) => [a.id, a] as const));
|
||||
}
|
||||
|
||||
private readonly site: ExtensionSite;
|
||||
private readonly log: SemanticLogger;
|
||||
|
||||
private generators: Map<CredentialAlgorithm, GeneratorMetadata<unknown & object>>;
|
||||
private _metadata: Map<CredentialAlgorithm, GeneratorMetadata<unknown & object>>;
|
||||
|
||||
// looks up a set of algorithms; does not enforce policy
|
||||
/** Retrieve an algorithm's generator metadata
|
||||
* @param algorithm identifies the algorithm
|
||||
* @returns the algorithm's generator metadata
|
||||
* @throws when the algorithm doesn't identify a known metadata entry
|
||||
*/
|
||||
metadata(algorithm: CredentialAlgorithm) {
|
||||
let result = null;
|
||||
if (isForwarderExtensionId(algorithm)) {
|
||||
const extension = this.site.extensions.get(algorithm.forwarder);
|
||||
if (!extension) {
|
||||
this.log.panic(algorithm, "extension not found");
|
||||
}
|
||||
|
||||
result = toForwarderMetadata(extension);
|
||||
} else {
|
||||
result = this._metadata.get(algorithm);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this.log.panic({ algorithm }, "metadata not found");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** retrieve credential types */
|
||||
types() : ReadonlyArray<CredentialType> {
|
||||
return Types;
|
||||
}
|
||||
|
||||
/** Retrieve the credential algorithm ids that match the request.
|
||||
* @param requested when this has a `category` property, the method
|
||||
* returns all algorithms in the category. When this has an `algorithm`
|
||||
* property, the method returns 0 or 1 matching algorithms.
|
||||
* @returns the matching algorithms. This method always returns an array;
|
||||
* the array is empty when no algorithms match the input criteria.
|
||||
* @throws when neither `requested.algorithm` nor `requested.category` contains
|
||||
* a value.
|
||||
* @remarks this method enforces technical requirements only.
|
||||
* If you want these algorithms with policy controls applied, use `algorithms$`.
|
||||
*/
|
||||
algorithms(requested: AlgorithmRequest): CredentialAlgorithm[];
|
||||
algorithms(requested: TypeRequest): CredentialAlgorithm[];
|
||||
algorithms(requested: MetadataRequest): CredentialAlgorithm[];
|
||||
algorithms(requested: MetadataRequest): CredentialAlgorithm[] {
|
||||
let algorithms: CredentialAlgorithm[];
|
||||
if (requested.category) {
|
||||
@@ -78,8 +117,10 @@ export class GeneratorMetadataProvider {
|
||||
}
|
||||
|
||||
algorithms = AlgorithmsByType[requested.category].concat(forwarders);
|
||||
} else if (requested.algorithm && isForwarderExtensionId(requested.algorithm)) {
|
||||
algorithms = this.site.extensions.has(requested.algorithm.forwarder) ? [requested.algorithm] : [];
|
||||
} else if (requested.algorithm) {
|
||||
algorithms = [requested.algorithm];
|
||||
algorithms = Algorithms.includes(requested.algorithm) ? [requested.algorithm] : [];
|
||||
} else {
|
||||
this.log.panic(requested, "algorithm or category required");
|
||||
}
|
||||
@@ -99,7 +140,8 @@ export class GeneratorMetadataProvider {
|
||||
const available$ = account$.pipe(
|
||||
switchMap((account) => {
|
||||
const policies$ = this.application.policy.getAll$(PolicyType.PasswordGenerator, account.id).pipe(
|
||||
map((p) => new Set(availableAlgorithms_vNext(p))),
|
||||
map((p) => availableAlgorithms_vNext(p).filter(a => this._metadata.has(a))),
|
||||
map((p) => new Set()),
|
||||
// complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely
|
||||
takeUntil(anyComplete(account$)),
|
||||
);
|
||||
@@ -111,118 +153,74 @@ export class GeneratorMetadataProvider {
|
||||
return available$;
|
||||
}
|
||||
|
||||
// looks up a set of algorithms; enforces policy - emits empty list when there's no algorithm available
|
||||
available$(
|
||||
/** Retrieve credential algorithms filtered by the user's active policy.
|
||||
* @param requested when this has a `category` property, the method
|
||||
* returns all algorithms in the category. When this has an `algorithm`
|
||||
* property, the method returns 0 or 1 matching algorithms.
|
||||
* @param dependencies.account the account requesting algorithm access;
|
||||
* this parameter controls which policy, if any, is applied.
|
||||
* @returns an observable that emits matching algorithms. When no algorithms
|
||||
* match the request, an empty array is emitted.
|
||||
* @throws when neither `requested.algorithm` nor `requested.category` contains
|
||||
* a value.
|
||||
* @remarks this method applies policy controls. In particular, it excludes
|
||||
* algorithms prohibited by a policy control. If you want lists of algorithms
|
||||
* supported by the client, use `algorithms`.
|
||||
*/
|
||||
algorithms$(
|
||||
requested: AlgorithmRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>[]>;
|
||||
available$(
|
||||
): Observable<CredentialAlgorithm[]>;
|
||||
algorithms$(
|
||||
requested: TypeRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>[]>;
|
||||
available$(
|
||||
): Observable<CredentialAlgorithm[]>;
|
||||
algorithms$(
|
||||
requested: MetadataRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>[]> {
|
||||
let available$: Observable<CredentialAlgorithm[]>;
|
||||
): Observable<CredentialAlgorithm[]> {
|
||||
if (requested.category) {
|
||||
const { category } = requested;
|
||||
|
||||
available$ = this.isAvailable$(dependencies).pipe(
|
||||
return this.isAvailable$(dependencies).pipe(
|
||||
map((isAvailable) => AlgorithmsByType[category].filter(isAvailable)),
|
||||
);
|
||||
} else if (requested.algorithm) {
|
||||
const { algorithm } = requested;
|
||||
available$ = this.isAvailable$(dependencies).pipe(
|
||||
return this.isAvailable$(dependencies).pipe(
|
||||
map((isAvailable) => (isAvailable(algorithm) ? [algorithm] : [])),
|
||||
);
|
||||
} else {
|
||||
this.log.panic(requested, "algorithm or category required");
|
||||
}
|
||||
|
||||
const result$ = available$.pipe(
|
||||
map((available) => available.map((algorithm) => this.getMetadata(algorithm))),
|
||||
);
|
||||
|
||||
return result$;
|
||||
}
|
||||
|
||||
// looks up a specific algorithm; enforces policy - observable completes without emission when there's no algorithm available.
|
||||
algorithm$(
|
||||
requested: AlgorithmRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>>;
|
||||
algorithm$(
|
||||
requested: TypeRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>>;
|
||||
algorithm$(
|
||||
requested: MetadataRequest,
|
||||
dependencies: BoundDependency<"account", Account>,
|
||||
): Observable<GeneratorMetadata<object>> {
|
||||
preference$(credentialType: CredentialType, dependencies: BoundDependency<"account", Account>) {
|
||||
const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
||||
|
||||
let algorithm$: Observable<CredentialAlgorithm | undefined>;
|
||||
if (requested.category) {
|
||||
this.log.debug(requested, "retrieving algorithm metadata by category");
|
||||
|
||||
const { category } = requested;
|
||||
algorithm$ = this.preferences({ account$ }).pipe(
|
||||
withLatestFrom(this.isAvailable$({ account$ })),
|
||||
map(([preferences, isAvailable]) => {
|
||||
let algorithm: CredentialAlgorithm | undefined = preferences[category].algorithm;
|
||||
if (isAvailable(algorithm)) {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
const algorithms = AlgorithmsByType[category];
|
||||
algorithm = algorithms.find(isAvailable)!;
|
||||
this.log.debug(
|
||||
{ algorithm, category },
|
||||
"preference not available; defaulting the generator algorithm",
|
||||
);
|
||||
|
||||
const algorithm$ = this.preferences({ account$ }).pipe(
|
||||
combineLatestWith(this.isAvailable$({ account$ })),
|
||||
map(([preferences, isAvailable]) => {
|
||||
const algorithm: CredentialAlgorithm = preferences[credentialType].algorithm;
|
||||
if (isAvailable(algorithm)) {
|
||||
return algorithm;
|
||||
}),
|
||||
);
|
||||
} else if (requested.algorithm) {
|
||||
this.log.debug(requested, "retrieving algorithm metadata by algorithm");
|
||||
}
|
||||
|
||||
const { algorithm } = requested;
|
||||
algorithm$ = this.isAvailable$({ account$ }).pipe(
|
||||
map((isAvailable) => (isAvailable(algorithm) ? algorithm : undefined)),
|
||||
first(),
|
||||
);
|
||||
} else {
|
||||
this.log.panic(requested, "algorithm or category required");
|
||||
}
|
||||
const algorithms = AlgorithmsByType[credentialType];
|
||||
// `?? null` because logging types must be `Jsonify<T>`
|
||||
const defaultAlgorithm = algorithms.find(isAvailable) ?? null;
|
||||
this.log.debug(
|
||||
{ algorithm, defaultAlgorithm, credentialType },
|
||||
"preference not available; defaulting the generator algorithm",
|
||||
);
|
||||
|
||||
const result$ = algorithm$.pipe(
|
||||
filter((value) => !!value),
|
||||
map((algorithm) => this.getMetadata(algorithm!)),
|
||||
// `?? undefined` so that interface is ADR-14 compliant
|
||||
return defaultAlgorithm ?? undefined;
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
return result$;
|
||||
}
|
||||
|
||||
private getMetadata(algorithm: CredentialAlgorithm) {
|
||||
let result = null;
|
||||
if (isForwarderExtensionId(algorithm)) {
|
||||
const extension = this.site.extensions.get(algorithm.forwarder);
|
||||
if (!extension) {
|
||||
this.log.panic(algorithm, "extension not found");
|
||||
}
|
||||
|
||||
result = toForwarderMetadata(extension);
|
||||
} else {
|
||||
result = this.generators.get(algorithm);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this.log.panic({ algorithm }, "failed to load metadata");
|
||||
}
|
||||
|
||||
return result;
|
||||
return algorithm$;
|
||||
}
|
||||
|
||||
/** Get a subject bound to credential generator preferences.
|
||||
|
||||
Reference in New Issue
Block a user