@openlfcp/client 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data-unit.d.ts +11 -0
- package/dist/data-unit.js +9 -0
- package/dist/identity/accept.d.ts +115 -0
- package/dist/identity/accept.js +335 -0
- package/dist/identity/context.d.ts +214 -0
- package/dist/identity/context.js +495 -0
- package/dist/identity/devices.d.ts +127 -0
- package/dist/identity/devices.js +221 -0
- package/dist/identity/document.d.ts +112 -0
- package/dist/identity/document.js +54 -0
- package/dist/identity/index.d.ts +17 -0
- package/dist/identity/index.js +17 -0
- package/dist/identity/journal.d.ts +139 -0
- package/dist/identity/journal.js +202 -0
- package/dist/identity/network.d.ts +88 -0
- package/dist/identity/network.js +243 -0
- package/dist/identity/pairing-request.d.ts +147 -0
- package/dist/identity/pairing-request.js +422 -0
- package/dist/identity/pairing.d.ts +225 -0
- package/dist/identity/pairing.js +624 -0
- package/dist/identity/phases.d.ts +75 -0
- package/dist/identity/phases.js +104 -0
- package/dist/identity/proof.d.ts +45 -0
- package/dist/identity/proof.js +71 -0
- package/dist/identity/reconcile.d.ts +73 -0
- package/dist/identity/reconcile.js +273 -0
- package/dist/identity/resource.d.ts +74 -0
- package/dist/identity/resource.js +201 -0
- package/dist/identity/revocation.d.ts +106 -0
- package/dist/identity/revocation.js +403 -0
- package/dist/identity/roster.d.ts +89 -0
- package/dist/identity/roster.js +130 -0
- package/dist/identity/rotation.d.ts +131 -0
- package/dist/identity/rotation.js +363 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/revoke.d.ts +8 -0
- package/dist/revoke.js +12 -8
- package/dist/storage.d.ts +18 -0
- package/dist/storage.js +26 -0
- package/dist/sync-client.d.ts +2 -0
- package/dist/sync-client.js +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type PrincipalId, type ResourceId } from "@openlfcp/core";
|
|
2
|
+
import type { SecretStore } from "@openlfcp/storage";
|
|
3
|
+
import { type IdentityStorage, type Installation } from "./context.js";
|
|
4
|
+
import type { FlowOptions } from "./devices.js";
|
|
5
|
+
import { type IdentityDocumentPort, type RosterEntryValue } from "./document.js";
|
|
6
|
+
import { type IdentityNetwork } from "./network.js";
|
|
7
|
+
import type { LinearChain } from "./phases.js";
|
|
8
|
+
/** The Identity Resource recorded as this installation's identity (W §86), if any. */
|
|
9
|
+
export declare function localIdentityResource(storage: Pick<IdentityStorage, "localMarks">): Promise<ResourceId | undefined>;
|
|
10
|
+
/** Records `I` as this installation's identity. */
|
|
11
|
+
export declare function setLocalIdentityResource(storage: Pick<IdentityStorage, "commit">, identity: ResourceId): Promise<void>;
|
|
12
|
+
/** The document port of an Identity Resource, made once its ID and root are known. */
|
|
13
|
+
export type IdentityDocumentFactory = (identity: ResourceId, root: PrincipalId) => IdentityDocumentPort;
|
|
14
|
+
export interface CreateIdentityResourceOptions {
|
|
15
|
+
/** The installation `P`, a root without devices (W §7.2 N1). */
|
|
16
|
+
readonly installation: Installation;
|
|
17
|
+
readonly storage: IdentityStorage;
|
|
18
|
+
readonly secrets: SecretStore;
|
|
19
|
+
readonly network: IdentityNetwork;
|
|
20
|
+
/** The default server: route, coordinator and host of `I`. */
|
|
21
|
+
readonly url: string;
|
|
22
|
+
/** This installation's label for `devices[p:<P>]`. */
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly platform?: string;
|
|
25
|
+
/** The local Resource list for the roster. */
|
|
26
|
+
readonly roster: readonly {
|
|
27
|
+
readonly resource: ResourceId;
|
|
28
|
+
readonly entry: RosterEntryValue;
|
|
29
|
+
}[];
|
|
30
|
+
readonly document: IdentityDocumentFactory;
|
|
31
|
+
/** RFC 3339 UTC time for `added_at`; omitted when absent. */
|
|
32
|
+
readonly addedAt?: string;
|
|
33
|
+
readonly now: () => number;
|
|
34
|
+
}
|
|
35
|
+
export type CreatedIdentityResource = {
|
|
36
|
+
readonly kind: "created";
|
|
37
|
+
readonly identity: ResourceId;
|
|
38
|
+
readonly chain: LinearChain;
|
|
39
|
+
readonly document: IdentityDocumentPort;
|
|
40
|
+
readonly operation: string;
|
|
41
|
+
}
|
|
42
|
+
/** §5.1 step 1: the default server's READY does not list identity-01. */
|
|
43
|
+
| {
|
|
44
|
+
readonly kind: "unsupported";
|
|
45
|
+
}
|
|
46
|
+
/** The coordinator refused hosting or a unit: the operation is abandoned. */
|
|
47
|
+
| {
|
|
48
|
+
readonly kind: "refused";
|
|
49
|
+
readonly code: string;
|
|
50
|
+
}
|
|
51
|
+
/** The coordinator could not be reached: resume with resumeIdentityCreation. */
|
|
52
|
+
| {
|
|
53
|
+
readonly kind: "unavailable";
|
|
54
|
+
readonly operation: string;
|
|
55
|
+
readonly reason: string;
|
|
56
|
+
};
|
|
57
|
+
/** §5.1: creates and hosts `I`, journaled; resumes an open creation first. */
|
|
58
|
+
export declare function createIdentityResource(options: CreateIdentityResourceOptions): Promise<CreatedIdentityResource>;
|
|
59
|
+
/** Sends what an open creation has not sent yet, from its journaled exact bytes. */
|
|
60
|
+
export declare function resumeIdentityCreation(options: Omit<CreateIdentityResourceOptions, "roster" | "label">, operation: string): Promise<CreatedIdentityResource>;
|
|
61
|
+
export interface IdentityFlowOptions extends FlowOptions {
|
|
62
|
+
/** The Identity Resource; default: the one recorded locally, at its chain's coordinator. */
|
|
63
|
+
readonly identity?: {
|
|
64
|
+
readonly resource: ResourceId;
|
|
65
|
+
readonly url: string;
|
|
66
|
+
};
|
|
67
|
+
readonly document: IdentityDocumentFactory;
|
|
68
|
+
}
|
|
69
|
+
/** The Identity Resource of this installation and its coordinator. */
|
|
70
|
+
export declare function identityTarget(o: Pick<IdentityFlowOptions, "identity" | "storage">): Promise<{
|
|
71
|
+
readonly resource: ResourceId;
|
|
72
|
+
readonly url: string;
|
|
73
|
+
}>;
|
|
74
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { dataEpoch, fromHex, generateResourceId, resourceId, secureRandom, toHex, } from "@openlfcp/core";
|
|
2
|
+
import { dekCommitment, generateResourceDEK } from "@openlfcp/crypto";
|
|
3
|
+
import { signControlRecord, validateControlChain } from "@openlfcp/wire";
|
|
4
|
+
import { createDataUnit } from "../data-unit.js";
|
|
5
|
+
import { loadControlChain } from "../storage.js";
|
|
6
|
+
import { fetchDeks, loadIdentityDocument, runUnitPhase, storeDek, syncChain, } from "./context.js";
|
|
7
|
+
import { IDENTITY_PROFILE, identityPlaintextCodec, principalRefOf, resourceRef, } from "./document.js";
|
|
8
|
+
import { newOperationId, OperationJournal, phaseOf, } from "./journal.js";
|
|
9
|
+
import { ChannelPool, IDENTITY_EXTENSION, IdentityUnavailable, } from "./network.js";
|
|
10
|
+
/**
|
|
11
|
+
* Creating the Identity Resource of an existing installation
|
|
12
|
+
* (IDENTITY-PROFILE-01 §5.1), on the first "Add my other device…".
|
|
13
|
+
*
|
|
14
|
+
* The installation's Principal `P` becomes the root: Genesis with owner `P`
|
|
15
|
+
* (no field 5), profile org.openlfcp.identity.v1, the default server as
|
|
16
|
+
* route and coordinator; RESOURCE_HOST; then the initial change (§2.2) in
|
|
17
|
+
* the root's Data Unit with actor sequence 1 and the fill change (roster
|
|
18
|
+
* from the local Resource list, `devices[p:<P>]`) in sequence 2. The
|
|
19
|
+
* Genesis, both units and the epoch-0 DEK are persisted before anything is
|
|
20
|
+
* sent, and re-sent as exact bytes after a crash: the initial change is
|
|
21
|
+
* never created twice (§2.2). A run that cannot obtain actor sequence 1
|
|
22
|
+
* for the new Resource abandons it and creates another.
|
|
23
|
+
*/
|
|
24
|
+
const IDENTITY_MARK = "identity:resource";
|
|
25
|
+
/** The Identity Resource recorded as this installation's identity (W §86), if any. */
|
|
26
|
+
export async function localIdentityResource(storage) {
|
|
27
|
+
const v = await storage.localMarks.get(IDENTITY_MARK);
|
|
28
|
+
return v === undefined ? undefined : resourceId(fromHex(v));
|
|
29
|
+
}
|
|
30
|
+
/** Records `I` as this installation's identity. */
|
|
31
|
+
export async function setLocalIdentityResource(storage, identity) {
|
|
32
|
+
const r = await storage.commit([
|
|
33
|
+
{ op: "put-local-mark", key: IDENTITY_MARK, value: toHex(identity) },
|
|
34
|
+
]);
|
|
35
|
+
if (!r.ok)
|
|
36
|
+
throw new Error(`the identity was not recorded: ${r.reason}`);
|
|
37
|
+
}
|
|
38
|
+
/** §5.1: creates and hosts `I`, journaled; resumes an open creation first. */
|
|
39
|
+
export async function createIdentityResource(options) {
|
|
40
|
+
const journal = new OperationJournal(options.storage);
|
|
41
|
+
const open = (await journal.list({ kind: "create-identity", status: "open" }))[0];
|
|
42
|
+
if (open !== undefined)
|
|
43
|
+
return resumeIdentityCreation(options, open.id);
|
|
44
|
+
const pool = new ChannelPool(options.network);
|
|
45
|
+
try {
|
|
46
|
+
const channel = await pool.get(options.installation.signer, options.url);
|
|
47
|
+
if (!channel.extensions.includes(IDENTITY_EXTENSION))
|
|
48
|
+
return { kind: "unsupported" };
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (e instanceof IdentityUnavailable)
|
|
52
|
+
return { kind: "unavailable", operation: "", reason: e.message };
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
pool.closeAll();
|
|
57
|
+
}
|
|
58
|
+
for (;;) {
|
|
59
|
+
const id = await prepare(options, journal);
|
|
60
|
+
if (id !== undefined)
|
|
61
|
+
return resumeIdentityCreation(options, id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Builds Genesis, the epoch-0 DEK and both units and journals them before
|
|
66
|
+
* anything is sent. Undefined when actor sequence 1 was not available for
|
|
67
|
+
* the new Resource (it is then abandoned unhosted).
|
|
68
|
+
*/
|
|
69
|
+
async function prepare(options, journal) {
|
|
70
|
+
const P = options.installation.signer;
|
|
71
|
+
const I = generateResourceId();
|
|
72
|
+
const dek = generateResourceDEK();
|
|
73
|
+
const genesis = signControlRecord({ resourceId: I, controlSeq: 0n, prevControlId: null }, {
|
|
74
|
+
type: "GENESIS",
|
|
75
|
+
dataProfile: IDENTITY_PROFILE,
|
|
76
|
+
owner: P.descriptor,
|
|
77
|
+
dekCommitment: dekCommitment(I, dataEpoch(0n), dek),
|
|
78
|
+
endpoints: [{ url: options.url, priority: 0n }],
|
|
79
|
+
coordinatorUrl: options.url,
|
|
80
|
+
}, P);
|
|
81
|
+
const chain = validateControlChain([genesis.bytes]);
|
|
82
|
+
if (chain.kind !== "linear")
|
|
83
|
+
throw new Error("the new Genesis does not validate");
|
|
84
|
+
// The DEK first (LFCP-034), then the chain row that references it.
|
|
85
|
+
await storeDek(options.storage, options.secrets, chain, dataEpoch(0n), dek);
|
|
86
|
+
const doc = options.document(I, P.descriptor.principalId);
|
|
87
|
+
const unit = async (seq, intents, previous) => {
|
|
88
|
+
const created = await createDataUnit({
|
|
89
|
+
view: chain,
|
|
90
|
+
controlHead: chain.state.head,
|
|
91
|
+
actor: P,
|
|
92
|
+
dek,
|
|
93
|
+
sequences: options.storage.actorSequences,
|
|
94
|
+
previousUnitId: previous,
|
|
95
|
+
profile: identityPlaintextCodec,
|
|
96
|
+
value: doc.write({ signer: P.descriptor.principalId, actorSeq: seq, writerClass: "root" }, intents),
|
|
97
|
+
});
|
|
98
|
+
return created.seq === seq ? created : undefined;
|
|
99
|
+
};
|
|
100
|
+
const initial = await unit(1n, [{ intent: "identity.initialize" }], null);
|
|
101
|
+
if (initial === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
const fill = await unit(2n, [
|
|
104
|
+
...options.roster.map((r) => ({
|
|
105
|
+
intent: "roster.put",
|
|
106
|
+
key: resourceRef(r.resource),
|
|
107
|
+
entry: r.entry,
|
|
108
|
+
})),
|
|
109
|
+
{
|
|
110
|
+
intent: "device.put",
|
|
111
|
+
key: principalRefOf(P.descriptor.principalId),
|
|
112
|
+
entry: {
|
|
113
|
+
label: options.label,
|
|
114
|
+
kind: 0,
|
|
115
|
+
...(options.platform === undefined ? {} : { platform: options.platform }),
|
|
116
|
+
...(options.addedAt === undefined ? {} : { added_at: options.addedAt }),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
], initial.unitId);
|
|
120
|
+
if (fill === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
const id = newOperationId("create-identity", secureRandom(8));
|
|
123
|
+
const R = toHex(I);
|
|
124
|
+
await journal.begin({
|
|
125
|
+
id,
|
|
126
|
+
kind: "create-identity",
|
|
127
|
+
createdAt: options.now(),
|
|
128
|
+
intent: { identity: R, url: options.url, label: options.label },
|
|
129
|
+
phases: [
|
|
130
|
+
{ id: "host", kind: "host", resource: R, state: "pending", bytes: toHex(genesis.bytes) },
|
|
131
|
+
{ id: "unit:1", kind: "unit", resource: R, state: "pending", bytes: toHex(initial.bytes) },
|
|
132
|
+
{ id: "unit:2", kind: "unit", resource: R, state: "pending", bytes: toHex(fill.bytes) },
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
/** Sends what an open creation has not sent yet, from its journaled exact bytes. */
|
|
138
|
+
export async function resumeIdentityCreation(options, operation) {
|
|
139
|
+
const journal = new OperationJournal(options.storage);
|
|
140
|
+
let op = await journal.load(operation);
|
|
141
|
+
const I = resourceId(fromHex(op.intent.identity));
|
|
142
|
+
const pool = new ChannelPool(options.network);
|
|
143
|
+
try {
|
|
144
|
+
const channel = await pool.get(options.installation.signer, op.intent.url);
|
|
145
|
+
const host = phaseOf(op, "host");
|
|
146
|
+
if (host.state !== "committed-own") {
|
|
147
|
+
const answer = await channel.host(fromHex(host.bytes));
|
|
148
|
+
// W §39: the Genesis a server already hosts is answered with RESOURCE_HOSTED again.
|
|
149
|
+
if (answer.kind === "nack") {
|
|
150
|
+
await journal.putPhase(operation, { ...host, state: "refused", code: answer.code });
|
|
151
|
+
await journal.setStatus(operation, "abandoned");
|
|
152
|
+
return { kind: "refused", code: answer.code };
|
|
153
|
+
}
|
|
154
|
+
op = await journal.putPhase(operation, { ...host, state: "committed-own" });
|
|
155
|
+
}
|
|
156
|
+
for (const id of ["unit:1", "unit:2"]) {
|
|
157
|
+
const r = await runUnitPhase(channel, journal, operation, {
|
|
158
|
+
id,
|
|
159
|
+
resource: I,
|
|
160
|
+
build: () => Promise.reject(new Error("the unit is journaled")),
|
|
161
|
+
});
|
|
162
|
+
if (r.state === "refused") {
|
|
163
|
+
await journal.setStatus(operation, "abandoned");
|
|
164
|
+
return { kind: "refused", code: r.code ?? "refused" };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const chain = await syncChain(options.storage, channel, I);
|
|
168
|
+
await setLocalIdentityResource(options.storage, I);
|
|
169
|
+
await journal.setStatus(operation, "done");
|
|
170
|
+
const document = options.document(I, chain.state.owner.principalId);
|
|
171
|
+
// The document as the coordinator holds it (the local port of a resumed run is fresh).
|
|
172
|
+
await loadIdentityDocument({
|
|
173
|
+
channel,
|
|
174
|
+
chain,
|
|
175
|
+
deks: await fetchDeks(options.storage, options.secrets, channel, chain, options.installation),
|
|
176
|
+
doc: document,
|
|
177
|
+
});
|
|
178
|
+
return { kind: "created", identity: I, chain, document, operation };
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
if (e instanceof IdentityUnavailable)
|
|
182
|
+
return { kind: "unavailable", operation, reason: e.message };
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
pool.closeAll();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** The Identity Resource of this installation and its coordinator. */
|
|
190
|
+
export async function identityTarget(o) {
|
|
191
|
+
if (o.identity !== undefined)
|
|
192
|
+
return o.identity;
|
|
193
|
+
const resource = await localIdentityResource(o.storage);
|
|
194
|
+
if (resource === undefined)
|
|
195
|
+
throw new Error("this installation has no Identity Resource");
|
|
196
|
+
const chain = await loadControlChain(o.storage, resource);
|
|
197
|
+
if (chain?.kind !== "linear")
|
|
198
|
+
throw new Error("the Identity Resource's chain is not stored");
|
|
199
|
+
return { resource, url: chain.state.route.coordinatorUrl };
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type PrincipalId, type ResourceId } from "@openlfcp/core";
|
|
2
|
+
import { type DocumentWrite } from "./context.js";
|
|
3
|
+
import { type FlowOptions } from "./devices.js";
|
|
4
|
+
import { type IdentityChannel } from "./network.js";
|
|
5
|
+
import { type IdentityFlowOptions } from "./resource.js";
|
|
6
|
+
import { type RotationOutcome } from "./rotation.js";
|
|
7
|
+
/**
|
|
8
|
+
* Revoking a device (IDENTITY-PROFILE-01 §8, decision I4) and the
|
|
9
|
+
* automatic rotation policy (§8.4).
|
|
10
|
+
*/
|
|
11
|
+
/** What revoking `device` does to one Resource, computed on the post-state (§8.1). */
|
|
12
|
+
export interface ResourcePreview {
|
|
13
|
+
readonly resource: ResourceId;
|
|
14
|
+
readonly status: "ok" | "self-dependent" | "already-revoked" | "not-authorized" | "unreachable" | "unsupported" | "conflict";
|
|
15
|
+
/** Devices active before and suspended after, by the cascade (W §17.5). */
|
|
16
|
+
readonly suspended: readonly PrincipalId[];
|
|
17
|
+
/** Whether the revocation removes a reader (W §17.5). */
|
|
18
|
+
readonly exposing: boolean;
|
|
19
|
+
/** Whether this installation rotates afterwards (§8.1); else the Resource awaits rotation. */
|
|
20
|
+
readonly rotator: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface RevocationPreview {
|
|
23
|
+
readonly device: PrincipalId;
|
|
24
|
+
/** The revoker's own chain passes through the device: re-certify first (§8.1). */
|
|
25
|
+
readonly selfDependent: boolean;
|
|
26
|
+
readonly resources: readonly ResourcePreview[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* §8.1: the post-state of revoking `device` in `I` and every reachable
|
|
30
|
+
* roster Resource: suspended devices, exposure, whether this installation
|
|
31
|
+
* stays active and who rotates.
|
|
32
|
+
*/
|
|
33
|
+
export declare function previewRevocation(o: IdentityFlowOptions & {
|
|
34
|
+
readonly device: PrincipalId;
|
|
35
|
+
readonly reason?: bigint;
|
|
36
|
+
}): Promise<RevocationPreview>;
|
|
37
|
+
/** One Resource's part of a revocation. */
|
|
38
|
+
export interface RevocationResult {
|
|
39
|
+
readonly resource: ResourceId;
|
|
40
|
+
readonly revoke: "revoked" | "complete" | "superseded" | "not-authorized"
|
|
41
|
+
/** The revoker's own chain passes through the device here: re-certify it here first (§8.1). */
|
|
42
|
+
| "self-dependent" | "refused" | "unreachable" | "unsupported" | "conflict";
|
|
43
|
+
readonly rotation?: RotationOutcome;
|
|
44
|
+
}
|
|
45
|
+
export type RevokeDeviceResult = {
|
|
46
|
+
readonly kind: "revoked";
|
|
47
|
+
readonly operation: string;
|
|
48
|
+
readonly results: readonly RevocationResult[];
|
|
49
|
+
/**
|
|
50
|
+
* §8.2 display: the device's `revoked` flag in the Identity Resource:
|
|
51
|
+
* `none` when the device has no entry, `complete` when another writer
|
|
52
|
+
* set the flag. Refused or unreachable leaves the operation open;
|
|
53
|
+
* revokeDevice with that `operation` writes the flag again.
|
|
54
|
+
*/
|
|
55
|
+
readonly document: DocumentWrite;
|
|
56
|
+
}
|
|
57
|
+
/** The revoker's chain passes through the device: obtain a fresh chain first (§8.1). */
|
|
58
|
+
| {
|
|
59
|
+
readonly kind: "self-dependent";
|
|
60
|
+
}
|
|
61
|
+
/** Only the root or a managing device revokes devices (§8). */
|
|
62
|
+
| {
|
|
63
|
+
readonly kind: "not-authorized";
|
|
64
|
+
}
|
|
65
|
+
/** The pair was consumed by a pairing approval (§9.2 Revoke: superseded). */
|
|
66
|
+
| {
|
|
67
|
+
readonly kind: "superseded";
|
|
68
|
+
readonly operation: string;
|
|
69
|
+
}
|
|
70
|
+
/** Revocation needs the Identity Resource's coordinator: an offline "Remove device…" is refused (§8.5). */
|
|
71
|
+
| {
|
|
72
|
+
readonly kind: "unavailable";
|
|
73
|
+
readonly operation?: string;
|
|
74
|
+
readonly reason: string;
|
|
75
|
+
} | {
|
|
76
|
+
readonly kind: "conflict";
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* §8.2–§8.5: `DEVICE_REVOKE {R, D, r}` in `I`, then in every roster
|
|
80
|
+
* Resource where this installation may act, each followed by the rotation
|
|
81
|
+
* when the revocation is exposing there and this installation is the
|
|
82
|
+
* rotator (else `awaiting-rotation`). A self-retiring device (`D` is this
|
|
83
|
+
* installation, reason 0) commits the revocations only. Every phase is
|
|
84
|
+
* journaled and classified by §9.2: a revocation by another hand with
|
|
85
|
+
* another reason is `complete` and the rotation test still runs.
|
|
86
|
+
*/
|
|
87
|
+
export declare function revokeDevice(o: IdentityFlowOptions & {
|
|
88
|
+
readonly device: PrincipalId;
|
|
89
|
+
/** 0 retired, 1 lost, 2 compromised. */
|
|
90
|
+
readonly reason: 0n | 1n | 2n;
|
|
91
|
+
/** Resume this operation. */
|
|
92
|
+
readonly operation?: string;
|
|
93
|
+
}): Promise<RevokeDeviceResult>;
|
|
94
|
+
/**
|
|
95
|
+
* §8.4 rotation policy: when the Resource holds an exposing revocation
|
|
96
|
+
* committed after its current epoch opened and this installation is the
|
|
97
|
+
* rotator, rotate once (covering every such revocation) and seal the new
|
|
98
|
+
* DEK to every reader; otherwise `awaiting-rotation`, or `not-needed` when
|
|
99
|
+
* nothing exposing happened since the last rotation.
|
|
100
|
+
*/
|
|
101
|
+
export declare function rotateIfExposed(o: FlowOptions & {
|
|
102
|
+
readonly channel: IdentityChannel;
|
|
103
|
+
readonly operation: string;
|
|
104
|
+
readonly resource: ResourceId;
|
|
105
|
+
}): Promise<RotationOutcome>;
|
|
106
|
+
//# sourceMappingURL=revocation.d.ts.map
|