@rootherald/node
Node 18+, built-in fetch, no dependencies. Holds the rh_sk_ key and does every Root Herald call.
terminalbash
npm install @rootherald/nodeThe calls
new RootHeraldClient({ secretKey, baseUrl? })—baseUrldefaults tohttps://rootherald.io; only https or loopback is accepted.issueChallenge({ ask?, policy?, keyPurpose?, deviceHint? })→{ challengeId, challenge, nonce, expiresAt }.verify(evidence, { challengeId, policy?, requestedDisclosureClass? })→ verdict plusassuranceClaimsMet,enrollmentRequired,key?.relayEnroll(enrollRequestBlob, { challengeId? })→{ deviceId, challenge };relayActivate(activationResponse)→{ deviceId }.verifyMobileEvidence(body)— the companion-app bridge; wrapsverify.verifyKeySignature(jwk, message, signature)→boolean. ECDSA P-256/SHA-256 with Nodecrypto; rawr||sor DER; never throws.- Errors:
InvalidSecretKeyError,ChallengeError,InvalidEvidenceError,UnknownPolicyError,PolicyDowngradeError,AdmissionRefusedError,QuotaExceededError. A failing device is a verdict, not an error.
Example
app/api/signup/route.tsts
import { RootHeraldClient, QuotaExceededError } from "@rootherald/node";
const rh = new RootHeraldClient({ secretKey: process.env.RH_SECRET_KEY! });
export async function GET() {
const { challengeId, challenge } = await rh.issueChallenge({
ask: ["identity"], // signup needs "same chip as before", nothing more
policy: "rootherald:builtin:strict-hardware",
});
return Response.json({ challengeId, challenge }); // relay `challenge` verbatim
}
export async function POST(req: Request) {
const { challengeId, evidence, email } = await req.json();
let result;
try {
result = await rh.verify(evidence, { challengeId });
} catch (err) {
if (err instanceof QuotaExceededError) return Response.json({ error: "rate_limited" }, { status: 429 });
throw err;
}
if (result.enrollmentRequired) return Response.json({ error: "enrollment_required" }, { status: 409 });
if (result.device.verdict !== "pass") return Response.json({ error: "device_rejected" }, { status: 403 });
if (await users.deviceRegistered(result.device.ueid)) return Response.json({ error: "device_already_registered" }, { status: 409 });
const user = await users.create({ email, deviceId: result.device.ueid });
return Response.json({ userId: user.id });
}