mirror of
https://github.com/temetro/temetro.git
synced 2026-08-11 02:57:55 +00:00
99aa534e88
The relay is now multi-clinic. Each clinic authenticates to the /hub namespace by signing a challenge with its own Ed25519 clinic signing key (a per-clinic identity, not a shared RELAY_TOKEN), and the relay routes every device response back to only the clinic that originated the request (keyed by requestId) — so clinics never see each other's traffic. Backend: - clinic_signing_keys.network_enabled + GET/PUT /api/signing/network (owner/admin) to join/leave the network. - relay-client keeps one authenticated hub connection per network-enabled org (connectOrg/disconnectOrg, hubs map keyed by orgId); emitToWallet/ sendToWallet take orgId; offline flush is org-scoped. - Wallet import/push return 409 until a clinic joins. - RELAY_TOKEN is now optional/legacy (open relay needs no shared secret). Frontend: - "Join Temetro Network" toggle in Settings → Signing, localized in all five languages (en, fr, de, so, ar). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
// Client for the clinic signing key (Settings → Signing) and the "import from a
|
|
// patient app" wallet-share flow. Both call the backend over the shared fetch
|
|
// wrapper (session cookie sent automatically).
|
|
|
|
import { apiFetch } from "@/lib/api-client";
|
|
|
|
export type SigningKey = {
|
|
algorithm: string;
|
|
publicKey: string;
|
|
fingerprint: string;
|
|
createdAt: string; // ISO
|
|
rotatedAt: string | null;
|
|
};
|
|
|
|
export type WalletShareMode = "permanent" | "temporary";
|
|
|
|
export type SharedRecord = {
|
|
id: string;
|
|
walletNumber: string;
|
|
status: "pending" | "approved" | "denied" | "expired";
|
|
shareMode: WalletShareMode;
|
|
shareExpiresAt: string | null;
|
|
// The draft is only returned by the request-share poll, not the list.
|
|
};
|
|
|
|
// The clinic's Ed25519 signing key. The backend creates one lazily on first
|
|
// read, so this always resolves to a real key + fingerprint.
|
|
export async function getSigningKey(): Promise<SigningKey> {
|
|
return apiFetch<SigningKey>("/api/signing/key");
|
|
}
|
|
|
|
// Rotate the signing key (owner/admin only). Returns the new key.
|
|
export async function rotateSigningKey(): Promise<SigningKey> {
|
|
return apiFetch<SigningKey>("/api/signing/key/rotate", { method: "POST" });
|
|
}
|
|
|
|
// Recent records shared from patient wallets — feeds the panel's list.
|
|
export async function listSignedRecords(): Promise<SharedRecord[]> {
|
|
return apiFetch<SharedRecord[]>("/api/signing/records");
|
|
}
|
|
|
|
// Whether this clinic has joined the Temetro Network relay (patient-wallet
|
|
// sharing rides it). Readable by any clinician.
|
|
export async function getNetworkEnabled(): Promise<boolean> {
|
|
const { enabled } = await apiFetch<{ enabled: boolean }>(
|
|
"/api/signing/network",
|
|
);
|
|
return enabled;
|
|
}
|
|
|
|
// Join or leave the Temetro Network (owner/admin only). Returns the new state.
|
|
export async function setNetworkEnabled(enabled: boolean): Promise<boolean> {
|
|
const res = await apiFetch<{ enabled: boolean }>("/api/signing/network", {
|
|
method: "PUT",
|
|
body: JSON.stringify({ enabled }),
|
|
});
|
|
return res.enabled;
|
|
}
|