Require current node possession proof

This commit is contained in:
冰朔 2026-08-10 08:00:53 +08:00
commit 64062fadac
10 changed files with 417 additions and 10 deletions

View file

@ -19,6 +19,11 @@ import {
type NodeRegistrationSnapshotReceipt,
} from './node-registration-snapshot.js';
import type { NodeRegistrationClaimSource } from './node-registration-client.js';
import {
createNodePossessionChallenge,
verifyNodePossessionResponse,
type NodePossessionProofSource,
} from './node-possession-proof.js';
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
@ -81,10 +86,13 @@ export class DomainAccessOrchestrator {
private readonly nodeRegistrationSnapshots: NodeRegistrationSnapshotLoader,
private readonly identities: DomainIdentitySource,
private readonly nodeRegistrations: NodeRegistrationClaimSource,
private readonly nodePossessions: NodePossessionProofSource,
private readonly handoffs: DomainRuntimeHandoffSource,
private readonly clock: () => number = Date.now,
) {}
async domainAccess(domainId: string, nodeType: DomainNodeType, now = Date.now()): Promise<Readonly<DomainAccessProjection>> {
async domainAccess(domainId: string, nodeType: DomainNodeType, at?: number): Promise<Readonly<DomainAccessProjection>> {
let now = at ?? this.clock();
if (!IDENTIFIER_PATTERN.test(domainId)) throw new Error('domain_access_domain_invalid');
if (nodeType !== 'local-terminal' && nodeType !== 'cloud-resident') throw new Error('domain_access_node_type_invalid');
const [snapshot, nodeRegistrationSnapshot, identity] = await Promise.all([
@ -93,6 +101,7 @@ export class DomainAccessOrchestrator {
this.identities.read(domainId, nodeType).then(safeIdentity, () => safeIdentity(null)),
]);
let nodeRegistrationVerified = false;
let nodePossessionVerified = false;
if (identity.accountVerified && nodeRegistrationSnapshot.receipt.status === 'CURRENT') {
try {
const loaded = assertLoadedNodeRegistrationSnapshot(nodeRegistrationSnapshot);
@ -106,13 +115,20 @@ export class DomainAccessOrchestrator {
nodeType,
}));
if (claim) {
verifyNodeRegistrationClaim(claim, {
const registration = verifyNodeRegistrationClaim(claim, {
accountId: identity.accountId,
domainId,
nodeId: identity.nodeId,
nodeType,
}, endpoint, now);
nodeRegistrationVerified = true;
const challenge = createNodePossessionChallenge(registration, now);
const response = await this.nodePossessions.prove(challenge);
if (response) {
if (at === undefined) now = this.clock();
verifyNodePossessionResponse(response, challenge, registration, now);
nodePossessionVerified = true;
}
}
}
} catch {
@ -123,11 +139,12 @@ export class DomainAccessOrchestrator {
accountVerified: identity.accountVerified,
domainId,
nodeId: identity.nodeId,
nodePossessionVerified,
nodeRegistrationVerified,
nodeType,
};
if (identity.accountVerified && nodeRegistrationVerified) {
if (identity.accountVerified && nodeRegistrationVerified && nodePossessionVerified) {
let candidate: DomainRuntimeHandoffCandidate | null = null;
try {
candidate = await this.handoffs.read(domainId, identity.nodeId, nodeType);