Skip to content

Passkey recovery

A passkey can be an identity’s recovery credential. The user then gets their identity back on a new device with a fingerprint or a PIN, with no recovery code to keep. @dxos/halo writes and checks the credential; the WebAuthn ceremony that creates and uses the passkey runs in the app, because only the app knows its site and its UI.

This guide uses the browser’s WebAuthn API. Recovery goes through EDGE, so the client needs a network connection.

navigator.credentials.create makes the passkey. Put a random lookup key in its user handle: the browser returns the handle when the passkey signs, and HALO finds the credential by it. Then pass the passkey’s public key, the lookup key and the algorithm to Identity.createRecoveryCredential as externalKey. The identity’s group gains the passkey as a member.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Option
Option
from 'effect/Option';
import {
import Identity
Identity
} from '@dxos/halo';
import {
class PublicKey

The purpose of this class is to assure consistent use of keys throughout the project. Keys should be maintained as buffers in objects and proto definitions, and converted to hex strings as late as possible (eg, to log/display).

PublicKey
} from '@dxos/keys';
const
const registerPasskey: Effect.Effect<undefined, IdentityError, Identity.Service>
registerPasskey
=
import Effect
Effect
.
const gen: <Effect.Effect<Option.Option<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>, never, Identity.Service> | Effect.Effect<Credential | null, never, never> | Effect.Effect<{
recoveryCode?: string;
}, IdentityError, Identity.Service>, undefined>(f: () => Generator<Effect.Effect<Option.Option<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>, never, Identity.Service> | Effect.Effect<...> | Effect.Effect<...>, undefined, never>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const identity: {
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
identity
=
import Option
Option
.
const getOrThrow: <{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>(self: Option.Option<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>) => {
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}

Extracts the value from a Some, or throws a default Error for None.

When to use

Use when you need quick fail-fast unwrapping of an Option and a generic error is acceptable.

Details

  • Some → returns the inner value
  • None → throws new Error("getOrThrow called on a None")

Example (Throwing a default error)

import { Option, Result } from "effect"
Option.getOrThrow(Option.some(1)) // => 1
const failure = Result.try({
try: () => Option.getOrThrow(Option.none()),
catch: (error) => (error as Error).message
})
Result.getFailure(failure).pipe(Option.getOrElse(() => "no error")) // => "getOrThrow called on a None"

@see ― getOrThrowWith for a custom error

@see ― getOrElse for a non-throwing alternative

@stability ― stable

@category ― converting

@since ― 2.0.0

getOrThrow
(yield*
import Identity
Identity
.
const getSnapshot: Effect.Effect<Option.Option<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>, never, Identity.Service>

Synchronous snapshot of the local identity (requires

Service

).

getSnapshot
);
// The lookup key names the passkey. It goes in the user handle, so the
// assertion returns it.
const
const lookupKey: PublicKey
lookupKey
=
class PublicKey

The purpose of this class is to assure consistent use of keys throughout the project. Keys should be maintained as buffers in objects and proto definitions, and converted to hex strings as late as possible (eg, to log/display).

PublicKey
.
PublicKey.random(): PublicKey

Creates a new key.

random
();
const
const credential: Credential | null
credential
= yield*
import Effect
Effect
.
const promise: <Credential | null>(evaluate: (signal: AbortSignal) => PromiseLike<Credential | null>) => Effect.Effect<Credential | null, never, never>

Creates an Effect that represents an asynchronous computation guaranteed to succeed.

When to use

Use to convert a Promise into an Effect when the async operation is guaranteed to succeed and will not reject.

Details

An optional AbortSignal can be provided to allow for interruption of the wrapped Promise API.

Gotchas

The Promise must not reject. If it rejects, the rejection is treated as a defect, not as a typed failure. Use tryPromise when rejection is expected.

Interruption aborts the provided AbortSignal, but the underlying asynchronous operation only stops if it observes that signal.

Example (Wrapping a non-rejecting Promise)

import { Effect } from "effect"
const succeedAsync = (message: string) =>
Effect.promise<string>(() => Promise.resolve(message))
// ┌─── Effect<string, never, never>
// ▼
const program = succeedAsync("Async operation completed successfully!")
await Effect.runPromise(program) // => "Async operation completed successfully!"

@see ― tryPromise for a version that can handle failures.

@stability ― stable

@category ― constructors

@since ― 2.0.0

promise
(() =>
var navigator: Navigator

The Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.

MDN Reference

navigator
.
Navigator.credentials: CredentialsContainer

The credentials read-only property of the Navigator interface returns the CredentialsContainer object associated with the current document, which exposes methods to request credentials. The CredentialsContainer interface also notifies the user agent when an interesting event occurs, such as a successful sign-in or sign-out. This interface can be used for feature detection. Available only in secure contexts.

MDN Reference

credentials
.
CredentialsContainer.create(options?: CredentialCreationOptions): Promise<Credential | null>

The create() method of the CredentialsContainer interface creates a new credential, which can then be stored and later retrieved using the navigator.credentials.get() method. The retrieved credential can then be used by a website to authenticate a user.

MDN Reference

create
({
CredentialCreationOptions.publicKey?: PublicKeyCredentialCreationOptions | undefined
publicKey
: {
PublicKeyCredentialCreationOptions.challenge: BufferSource
challenge
: new
var Uint8Array: Uint8ArrayConstructor
new (length: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(32),
PublicKeyCredentialCreationOptions.rp: PublicKeyCredentialRpEntity
rp
: {
PublicKeyCredentialRpEntity.id?: string | undefined
id
:
const rpId: string

The site the passkey belongs to: the app's own host.

rpId
,
PublicKeyCredentialEntity.name: string
name
: 'Contact book' },
PublicKeyCredentialCreationOptions.user: PublicKeyCredentialUserEntity
user
: {
PublicKeyCredentialUserEntity.id: BufferSource
id
: new
var Uint8Array: Uint8ArrayConstructor
new (elements: Iterable<number>) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const lookupKey: PublicKey
lookupKey
.
PublicKey.asUint8Array(): Uint8Array
asUint8Array
()),
PublicKeyCredentialEntity.name: string
name
:
const identity: {
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
identity
.
did: IdentityDid
did
,
PublicKeyCredentialUserEntity.displayName: string
displayName
:
const identity: {
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}
identity
.
displayName?: string | undefined
displayName
?? '',
},
PublicKeyCredentialCreationOptions.pubKeyCredParams: PublicKeyCredentialParameters[]
pubKeyCredParams
: [
{
PublicKeyCredentialParameters.type: "public-key"
type
: 'public-key',
PublicKeyCredentialParameters.alg: number
alg
: -8 }, // Ed25519
{
PublicKeyCredentialParameters.type: "public-key"
type
: 'public-key',
PublicKeyCredentialParameters.alg: number
alg
: -7 }, // ES256
],
PublicKeyCredentialCreationOptions.authenticatorSelection?: AuthenticatorSelectionCriteria | undefined
authenticatorSelection
: {
AuthenticatorSelectionCriteria.residentKey?: ResidentKeyRequirement | undefined
residentKey
: 'required' },
},
}),
);
if (
!(
const credential: Credential | null
credential
instanceof
var PublicKeyCredential: {
new (): PublicKeyCredential;
prototype: PublicKeyCredential;
getClientCapabilities(): Promise<PublicKeyCredentialClientCapabilities>;
isConditionalMediationAvailable(): Promise<boolean>;
isUserVerifyingPlatformAuthenticatorAvailable(): Promise<boolean>;
parseCreationOptionsFromJSON(options: PublicKeyCredentialCreationOptionsJSON): PublicKeyCredentialCreationOptions;
parseRequestOptionsFromJSON(options: PublicKeyCredentialRequestOptionsJSON): PublicKeyCredentialRequestOptions;
signalAllAcceptedCredentials(options: AllAcceptedCredentialsOptions): Promise<void>;
signalCurrentUserDetails(options: CurrentUserDetailsOptions): Promise<void>;
signalUnknownCredential(options: UnknownCredentialOptions): Promise<void>;
}

The PublicKeyCredential interface provides information about a public key / private key pair, which is a credential for logging in to a service using an un-phishable and data-breach resistant asymmetric key pair instead of a password. It inherits from Credential, and is part of the Web Authentication API extension to the Credential Management API. Available only in secure contexts.

MDN Reference

PublicKeyCredential
) ||
!(
const credential: PublicKeyCredential
credential
.
PublicKeyCredential.response: AuthenticatorResponse

The response read-only property of the PublicKeyCredential interface is an AuthenticatorResponse object which is sent from the authenticator to the user agent for the creation/fetching of credentials. The information contained in this response will be used by the relying party's server to verify the demand is legitimate.

MDN Reference

response
instanceof
var AuthenticatorAttestationResponse: {
new (): AuthenticatorAttestationResponse;
prototype: AuthenticatorAttestationResponse;
}

The AuthenticatorAttestationResponse interface of the Web Authentication API is the result of a WebAuthn credential registration. It contains information about the credential that the server needs to perform WebAuthn assertions, such as its credential ID and public key. Available only in secure contexts.

MDN Reference

AuthenticatorAttestationResponse
)
) {
return yield*
import Effect
Effect
.
const die: (defect: unknown) => Effect.Effect<never>

Creates an effect that terminates a fiber with a specified error.

When to use

Use when you need an Effect to report an unrecoverable defect instead of a typed error.

Details

The die function is used to signal a defect, which represents a critical and unexpected error in the code. When invoked, it produces an effect that does not handle the error and instead terminates the fiber.

The error channel of the resulting effect is of type never, indicating that it cannot recover from this failure.

Example (Failing on division by zero)

import { Effect, Exit } from "effect"
const defect = new Error("Cannot divide by zero")
const divide = (a: number, b: number) =>
b === 0
? Effect.die(defect)
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = divide(1, 0)
Effect.runSyncExit(program) // => Exit.die(defect)

@stability ― stable

@category ― constructors

@since ― 2.0.0

die
('No passkey was created.');
}
const
const response: AuthenticatorAttestationResponse
response
=
const credential: PublicKeyCredential
credential
.
PublicKeyCredential.response: AuthenticatorAttestationResponse

The response read-only property of the PublicKeyCredential interface is an AuthenticatorResponse object which is sent from the authenticator to the user agent for the creation/fetching of credentials. The information contained in this response will be used by the relying party's server to verify the demand is legitimate.

MDN Reference

response
;
const
const publicKey: ArrayBuffer | null
publicKey
=
const response: AuthenticatorAttestationResponse
response
.
AuthenticatorAttestationResponse.getPublicKey(): ArrayBuffer | null

The getPublicKey() method of the AuthenticatorAttestationResponse interface returns an ArrayBuffer containing the DER SubjectPublicKeyInfo of the new credential (see Subject Public Key Info), or null if this is not available.

MDN Reference

getPublicKey
();
if (!
const publicKey: ArrayBuffer | null
publicKey
) {
return yield*
import Effect
Effect
.
const die: (defect: unknown) => Effect.Effect<never>

Creates an effect that terminates a fiber with a specified error.

When to use

Use when you need an Effect to report an unrecoverable defect instead of a typed error.

Details

The die function is used to signal a defect, which represents a critical and unexpected error in the code. When invoked, it produces an effect that does not handle the error and instead terminates the fiber.

The error channel of the resulting effect is of type never, indicating that it cannot recover from this failure.

Example (Failing on division by zero)

import { Effect, Exit } from "effect"
const defect = new Error("Cannot divide by zero")
const divide = (a: number, b: number) =>
b === 0
? Effect.die(defect)
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = divide(1, 0)
Effect.runSyncExit(program) // => Exit.die(defect)

@stability ― stable

@category ― constructors

@since ― 2.0.0

die
('The authenticator returned no public key.');
}
yield*
import Identity
Identity
.
const createRecoveryCredential: (options?: Identity.RecoveryCredentialOptions) => Effect.Effect<{
recoveryCode?: string;
}, IdentityError, Identity.Service>

Write a recovery credential (requires

Service

).

createRecoveryCredential
({
externalKey?: Identity.ExternalRecoveryKey | undefined
externalKey
: {
recoveryKey: string

Hex-encoded public key that will sign the recovery challenge.

recoveryKey
:
class PublicKey

The purpose of this class is to assure consistent use of keys throughout the project. Keys should be maintained as buffers in objects and proto definitions, and converted to hex strings as late as possible (eg, to log/display).

PublicKey
.
PublicKey.from(source: PublicKeyLike): PublicKey

Creates new instance of PublicKey automatically determining the input format.

@param ― source A Buffer, or Uint8Array, or hex encoded string, or something with an asUint8Array method on it

@returns ― PublicKey

from
(new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const publicKey: ArrayBuffer
publicKey
)).
PublicKey.toHex(): string
toHex
(),
lookupKey: string

Hex-encoded public handle used to look up (and revoke) this credential.

lookupKey
:
const lookupKey: PublicKey
lookupKey
.
PublicKey.toHex(): string
toHex
(),
algorithm: string

Signature algorithm of recoveryKey (e.g. ES256, ED25519).

algorithm
:
const response: AuthenticatorAttestationResponse
response
.
AuthenticatorAttestationResponse.getPublicKeyAlgorithm(): COSEAlgorithmIdentifier

The getPublicKeyAlgorithm() method of the AuthenticatorAttestationResponse interface returns a number that is equal to a COSE Algorithm Identifier, representing the cryptographic algorithm used for the new credential.

MDN Reference

getPublicKeyAlgorithm
() === -7 ? 'ES256' : 'ED25519',
label?: string | undefined

User-visible name; without it credentials are told apart only by issuance date.

label
: 'Passkey',
kind?: "passkey" | "recovery-code" | "oauth" | "unknown" | undefined
kind
: 'passkey',
},
});
});

The passkey must be a resident key (residentKey: 'required'), so a new device can find it without being told which one to use.

On the new device, before any identity exists:

  1. Identity.requestRecoveryChallenge gets a challenge from EDGE for this device.
  2. navigator.credentials.get asks the user to sign it with the passkey.
  3. Identity.recover sends the signature, the challenge and the lookup key from the user handle. EDGE checks them against the identity’s recovery credentials and admits the device.
import * as
import Effect
Effect
from 'effect/Effect';
import {
import Identity
Identity
} from '@dxos/halo';
import {
class PublicKey

The purpose of this class is to assure consistent use of keys throughout the project. Keys should be maintained as buffers in objects and proto definitions, and converted to hex strings as late as possible (eg, to log/display).

PublicKey
} from '@dxos/keys';
const
const fromBase64: (value: string) => Uint8Array<ArrayBuffer>
fromBase64
= (
value: string
value
: string) =>
var Uint8Array: Uint8ArrayConstructor

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
.
Uint8ArrayConstructor.from<string>(elements: Iterable<string>, mapfn?: ((v: string, k: number) => number) | undefined, thisArg?: any): Uint8Array<ArrayBuffer> (+3 overloads)

Creates an array from an array-like or iterable object.

@param ― elements An iterable object to convert to an array.

@param ― mapfn A mapping function to call on every element of the array.

@param ― thisArg Value of 'this' used to invoke the mapfn.

from
(
function atob(data: string): string (+1 overload)

Decodes a string of Base64-encoded data into bytes, and encodes those bytes into a string using Latin-1 (ISO-8859-1).

The data may be any JavaScript-value that can be coerced into a string.

This function is only provided for compatibility with legacy web platform APIs and should never be used in new code, because they use strings to represent binary data and predate the introduction of typed arrays in JavaScript. For code running using Node.js APIs, converting between base64-encoded strings and binary data should be performed using Buffer.from(str, 'base64') and buf.toString('base64').

@since ― v15.13.0, v14.17.0

@legacy ― Use Buffer.from(data, 'base64') instead.

@param ― data The Base64-encoded input string.

atob
(
value: string
value
), (
char: string
char
) =>
char: string
char
.
String.charCodeAt(index: number): number

Returns the Unicode value of the character at the specified location.

@param ― index The zero-based index of the desired character. If there is no character at the specified index, NaN is returned.

charCodeAt
(0));
const
const recoverWithPasskey: Effect.Effect<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}, IdentityError, Identity.Service>
recoverWithPasskey
=
import Effect
Effect
.
const gen: <Effect.Effect<{
readonly deviceKey: string;
readonly controlFeedKey: string;
readonly challenge: string;
}, IdentityError, Identity.Service> | Effect.Effect<Credential | null, never, never> | Effect.Effect<{
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}, IdentityError, Identity.Service>, {
readonly did: IdentityDid;
readonly identityKey?: string | undefined;
readonly displayName?: string | undefined;
readonly data?: {
readonly [x: string]: any;
} | undefined;
}>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const challenge: {
readonly deviceKey: string;
readonly controlFeedKey: string;
readonly challenge: string;
}
challenge
= yield*
import Identity
Identity
.
const requestRecoveryChallenge: Effect.Effect<{
readonly deviceKey: string;
readonly controlFeedKey: string;
readonly challenge: string;
}, IdentityError, Identity.Service>

Request a recovery challenge for this device (requires

Service

).

requestRecoveryChallenge
;
const
const credential: Credential | null
credential
= yield*
import Effect
Effect
.
const promise: <Credential | null>(evaluate: (signal: AbortSignal) => PromiseLike<Credential | null>) => Effect.Effect<Credential | null, never, never>

Creates an Effect that represents an asynchronous computation guaranteed to succeed.

When to use

Use to convert a Promise into an Effect when the async operation is guaranteed to succeed and will not reject.

Details

An optional AbortSignal can be provided to allow for interruption of the wrapped Promise API.

Gotchas

The Promise must not reject. If it rejects, the rejection is treated as a defect, not as a typed failure. Use tryPromise when rejection is expected.

Interruption aborts the provided AbortSignal, but the underlying asynchronous operation only stops if it observes that signal.

Example (Wrapping a non-rejecting Promise)

import { Effect } from "effect"
const succeedAsync = (message: string) =>
Effect.promise<string>(() => Promise.resolve(message))
// ┌─── Effect<string, never, never>
// ▼
const program = succeedAsync("Async operation completed successfully!")
await Effect.runPromise(program) // => "Async operation completed successfully!"

@see ― tryPromise for a version that can handle failures.

@stability ― stable

@category ― constructors

@since ― 2.0.0

promise
(() =>
var navigator: Navigator

The Window.navigator read-only property returns a reference to the Navigator object, which has methods and properties about the application running the script.

MDN Reference

navigator
.
Navigator.credentials: CredentialsContainer

The credentials read-only property of the Navigator interface returns the CredentialsContainer object associated with the current document, which exposes methods to request credentials. The CredentialsContainer interface also notifies the user agent when an interesting event occurs, such as a successful sign-in or sign-out. This interface can be used for feature detection. Available only in secure contexts.

MDN Reference

credentials
.
CredentialsContainer.get(options?: CredentialRequestOptions): Promise<Credential | null>

The get() method of the CredentialsContainer interface returns a Promise that fulfills with a single credential, which can then be used to authenticate a user to a website.

MDN Reference

get
({
CredentialRequestOptions.publicKey?: PublicKeyCredentialRequestOptions | undefined
publicKey
: {
PublicKeyCredentialRequestOptions.challenge: BufferSource
challenge
:
const fromBase64: (value: string) => Uint8Array<ArrayBuffer>
fromBase64
(
const challenge: {
readonly deviceKey: string;
readonly controlFeedKey: string;
readonly challenge: string;
}
challenge
.
challenge: string

Base64-encoded challenge bytes.

challenge
),
PublicKeyCredentialRequestOptions.rpId?: string | undefined
rpId
,
PublicKeyCredentialRequestOptions.userVerification?: UserVerificationRequirement | undefined
userVerification
: 'required',
},
}),
);
if (
!(
const credential: Credential | null
credential
instanceof
var PublicKeyCredential: {
new (): PublicKeyCredential;
prototype: PublicKeyCredential;
getClientCapabilities(): Promise<PublicKeyCredentialClientCapabilities>;
isConditionalMediationAvailable(): Promise<boolean>;
isUserVerifyingPlatformAuthenticatorAvailable(): Promise<boolean>;
parseCreationOptionsFromJSON(options: PublicKeyCredentialCreationOptionsJSON): PublicKeyCredentialCreationOptions;
parseRequestOptionsFromJSON(options: PublicKeyCredentialRequestOptionsJSON): PublicKeyCredentialRequestOptions;
signalAllAcceptedCredentials(options: AllAcceptedCredentialsOptions): Promise<void>;
signalCurrentUserDetails(options: CurrentUserDetailsOptions): Promise<void>;
signalUnknownCredential(options: UnknownCredentialOptions): Promise<void>;
}

The PublicKeyCredential interface provides information about a public key / private key pair, which is a credential for logging in to a service using an un-phishable and data-breach resistant asymmetric key pair instead of a password. It inherits from Credential, and is part of the Web Authentication API extension to the Credential Management API. Available only in secure contexts.

MDN Reference

PublicKeyCredential
) ||
!(
const credential: PublicKeyCredential
credential
.
PublicKeyCredential.response: AuthenticatorResponse

The response read-only property of the PublicKeyCredential interface is an AuthenticatorResponse object which is sent from the authenticator to the user agent for the creation/fetching of credentials. The information contained in this response will be used by the relying party's server to verify the demand is legitimate.

MDN Reference

response
instanceof
var AuthenticatorAssertionResponse: {
new (): AuthenticatorAssertionResponse;
prototype: AuthenticatorAssertionResponse;
}

The AuthenticatorAssertionResponse interface of the Web Authentication API contains a digital signature from the private key of a particular WebAuthn credential. The relying party's server can verify this signature to authenticate a user, for example when they sign in. Available only in secure contexts.

MDN Reference

AuthenticatorAssertionResponse
)
) {
return yield*
import Effect
Effect
.
const die: (defect: unknown) => Effect.Effect<never>

Creates an effect that terminates a fiber with a specified error.

When to use

Use when you need an Effect to report an unrecoverable defect instead of a typed error.

Details

The die function is used to signal a defect, which represents a critical and unexpected error in the code. When invoked, it produces an effect that does not handle the error and instead terminates the fiber.

The error channel of the resulting effect is of type never, indicating that it cannot recover from this failure.

Example (Failing on division by zero)

import { Effect, Exit } from "effect"
const defect = new Error("Cannot divide by zero")
const divide = (a: number, b: number) =>
b === 0
? Effect.die(defect)
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = divide(1, 0)
Effect.runSyncExit(program) // => Exit.die(defect)

@stability ― stable

@category ― constructors

@since ― 2.0.0

die
('No passkey assertion was returned.');
}
const
const response: AuthenticatorAssertionResponse
response
=
const credential: PublicKeyCredential
credential
.
PublicKeyCredential.response: AuthenticatorAssertionResponse

The response read-only property of the PublicKeyCredential interface is an AuthenticatorResponse object which is sent from the authenticator to the user agent for the creation/fetching of credentials. The information contained in this response will be used by the relying party's server to verify the demand is legitimate.

MDN Reference

response
;
// The user handle carries the lookup key the passkey was registered with.
const
const userHandle: ArrayBuffer | null
userHandle
=
const response: AuthenticatorAssertionResponse
response
.
AuthenticatorAssertionResponse.userHandle: ArrayBuffer | null

The userHandle read-only property of the AuthenticatorAssertionResponse interface is an ArrayBuffer object providing an opaque identifier for the given user. Such an identifier can be used by the relying party's server to link the user account with its corresponding credentials and other data.

MDN Reference

userHandle
;
if (!
const userHandle: ArrayBuffer | null
userHandle
) {
return yield*
import Effect
Effect
.
const die: (defect: unknown) => Effect.Effect<never>

Creates an effect that terminates a fiber with a specified error.

When to use

Use when you need an Effect to report an unrecoverable defect instead of a typed error.

Details

The die function is used to signal a defect, which represents a critical and unexpected error in the code. When invoked, it produces an effect that does not handle the error and instead terminates the fiber.

The error channel of the resulting effect is of type never, indicating that it cannot recover from this failure.

Example (Failing on division by zero)

import { Effect, Exit } from "effect"
const defect = new Error("Cannot divide by zero")
const divide = (a: number, b: number) =>
b === 0
? Effect.die(defect)
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = divide(1, 0)
Effect.runSyncExit(program) // => Exit.die(defect)

@stability ― stable

@category ― constructors

@since ― 2.0.0

die
('The passkey has no user handle.');
}
return yield*
import Identity
Identity
.
const recover: (args: Identity.RecoverArgs) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>

Re-admit this device via a recovery credential (requires

Service

).

recover
({
passkey: {
readonly challenge: Identity.RecoveryChallenge;
readonly lookupKey: string;
readonly signature: Uint8Array;
readonly clientDataJson?: Uint8Array;
readonly authenticatorData?: Uint8Array;
}
passkey
: {
challenge: {
readonly deviceKey: string;
readonly controlFeedKey: string;
readonly challenge: string;
}
challenge
,
lookupKey: string

Hex-encoded lookup key of the credential being presented.

lookupKey
:
class PublicKey

The purpose of this class is to assure consistent use of keys throughout the project. Keys should be maintained as buffers in objects and proto definitions, and converted to hex strings as late as possible (eg, to log/display).

PublicKey
.
PublicKey.from(source: PublicKeyLike): PublicKey

Creates new instance of PublicKey automatically determining the input format.

@param ― source A Buffer, or Uint8Array, or hex encoded string, or something with an asUint8Array method on it

@returns ― PublicKey

from
(new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const userHandle: ArrayBuffer
userHandle
)).
PublicKey.toHex(): string
toHex
(),
signature: Uint8Array<ArrayBufferLike>
signature
: new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const response: AuthenticatorAssertionResponse
response
.
AuthenticatorAssertionResponse.signature: ArrayBuffer

The signature read-only property of the AuthenticatorAssertionResponse interface is an ArrayBuffer object which is the signature of the authenticator for both AuthenticatorAssertionResponse.authenticatorData and a SHA-256 hash of the client data (AuthenticatorAssertionResponse.clientDataJSON).

MDN Reference

signature
),
clientDataJson?: Uint8Array<ArrayBufferLike> | undefined
clientDataJson
: new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const response: AuthenticatorAssertionResponse
response
.
AuthenticatorResponse.clientDataJSON: ArrayBuffer

The clientDataJSON property of the AuthenticatorResponse interface stores a JSON string in an ArrayBuffer, representing the client data that was passed to navigator.credentials.create() or navigator.credentials.get(). This property is only accessed on one of the child objects of AuthenticatorResponse, specifically AuthenticatorAttestationResponse or AuthenticatorAssertionResponse.

MDN Reference

clientDataJSON
),
authenticatorData?: Uint8Array<ArrayBufferLike> | undefined
authenticatorData
: new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(
const response: AuthenticatorAssertionResponse
response
.
AuthenticatorAssertionResponse.authenticatorData: ArrayBuffer

The authenticatorData property of the AuthenticatorAssertionResponse interface returns an ArrayBuffer containing information from the authenticator such as the Relying Party ID Hash (rpIdHash), a signature counter, test of user presence, user verification flags, and any extensions processed by the authenticator.

MDN Reference

authenticatorData
),
},
});
});

recover returns the identity. The device then syncs the identity’s spaces, as any new device does.

Identity.credentials lists the recovery credentials, each with its kind and lookup key. revokeRecoveryCredential removes one. HALO refuses to revoke the last active recovery credential, so register the replacement first.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Stream
Stream
from 'effect/Stream';
import {
import Identity
Identity
} from '@dxos/halo';
const
const revokePasskeys: Effect.Effect<void, IdentityError, Identity.Service>
revokePasskeys
=
import Effect
Effect
.
const gen: <Effect.Effect<void, IdentityError, Identity.Service>, void>(f: () => Generator<Effect.Effect<void, IdentityError, Identity.Service>, void, never>) => Effect.Effect<void, IdentityError, Identity.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const [
const credentials: readonly {
readonly type: string;
readonly id?: string | undefined;
readonly issuanceDate?: Date | undefined;
readonly recovery?: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined;
}[]
credentials
] = yield*
import Identity
Identity
.
const credentials: Stream.Stream<readonly {
readonly type: string;
readonly id?: string | undefined;
readonly issuanceDate?: Date | undefined;
readonly recovery?: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined;
}[], never, Identity.Service>

HALO credentials as a current-value stream (requires

Service

).

credentials
.
Pipeable.pipe<Stream.Stream<readonly {
readonly type: string;
readonly id?: string | undefined;
readonly issuanceDate?: Date | undefined;
readonly recovery?: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined;
}[], never, Identity.Service>, Stream.Stream<readonly {
readonly type: string;
readonly id?: string | undefined;
readonly issuanceDate?: Date | undefined;
readonly recovery?: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined;
}[], never, Identity.Service>, Effect.Effect<...>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Stream.Stream<...>, bc: (_: Stream.Stream<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const take: (n: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R> (+1 overload)

Takes the first n elements from this stream.

Details

Finite fractional values of n are rounded down. NaN and non-positive values return Stream.empty without evaluating the source stream.

Example (Taking values from the left)

import { Effect, Stream } from "effect"
const program = Effect.gen(function*() {
const values = yield* Stream.make(1, 2, 3, 4, 5).pipe(
Stream.take(3),
Stream.runCollect
)
values // => [ 1, 2, 3 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― filtering

@since ― 2.0.0

take
(1),
import Stream
Stream
.
const runCollect: <A, E, R>(self: Stream.Stream<A, E, R>) => Effect.Effect<Array<A>, E, R>

Runs the stream and collects all elements into an array.

Example (Collecting stream values)

import { Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3, 4, 5)
const program = Effect.gen(function*() {
const collected = yield* Stream.runCollect(stream)
collected // => [ 1, 2, 3, 4, 5 ]
})
await Effect.runPromise(program)

@stability ― stable

@category ― destructors

@since ― 2.0.0

runCollect
,
);
for (const {
const recovery: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined
recovery
} of
const credentials: readonly {
readonly type: string;
readonly id?: string | undefined;
readonly issuanceDate?: Date | undefined;
readonly recovery?: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined;
}[]
credentials
) {
if (
const recovery: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
} | undefined
recovery
?.
kind: "passkey" | "recovery-code" | "oauth" | "unknown" | undefined
kind
=== 'passkey' &&
!
const recovery: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
}
recovery
.
revoked: boolean

Whether an IdentityRecoveryRevoked assertion cancels this credential.

revoked
&&
const recovery: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
}
recovery
.
lookupKey?: string | undefined

Hex-encoded lookup key — the public handle used to revoke this credential.

lookupKey
) {
yield*
import Identity
Identity
.
const revokeRecoveryCredential: (lookupKey: string) => Effect.Effect<void, IdentityError, Identity.Service>

Revoke a recovery credential by lookup key (requires

Service

).

revokeRecoveryCredential
(
const recovery: {
readonly kind: "passkey" | "recovery-code" | "oauth" | "unknown";
readonly revoked: boolean;
readonly lookupKey?: string | undefined;
readonly label?: string | undefined;
}
recovery
.
lookupKey?: string

Hex-encoded lookup key — the public handle used to revoke this credential.

lookupKey
);
}
}
});