Compare commits
4 Commits
a7e1a9c210
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ccdf167daf | |||
| d2a16bcb02 | |||
| 2611304084 | |||
| 8744a771a2 |
1826
package-lock.json
generated
Normal file
1826
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"check": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
|
||||
@@ -128,7 +128,7 @@ export function decodeBuiltin(raw: string): BuiltinEnvelope {
|
||||
throw new CodecError("Missing or invalid 'type' field in envelope");
|
||||
}
|
||||
|
||||
return envelope as BuiltinEnvelope;
|
||||
return envelope as unknown as BuiltinEnvelope;
|
||||
} catch (cause) {
|
||||
if (cause instanceof CodecError) {
|
||||
throw cause;
|
||||
|
||||
49
src/crypto.ts
Normal file
49
src/crypto.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { randomBytes, generateKeyPair as _generateKeyPair, sign, verify } from "node:crypto";
|
||||
|
||||
export interface KeyPair {
|
||||
readonly privateKey: string;
|
||||
readonly publicKey: string;
|
||||
readonly algorithm: "Ed25519";
|
||||
}
|
||||
|
||||
export async function generateKeyPair(): Promise<KeyPair> {
|
||||
const { publicKey, privateKey } = await new Promise<{ publicKey: string; privateKey: string }>((resolve, reject) => {
|
||||
_generateKeyPair(
|
||||
"ed25519",
|
||||
{
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
||||
},
|
||||
(err, pubKey, privKey) => (err ? reject(err) : resolve({ publicKey: pubKey, privateKey: privKey }))
|
||||
);
|
||||
});
|
||||
return { privateKey, publicKey, algorithm: "Ed25519" };
|
||||
}
|
||||
|
||||
export async function signMessage(privateKeyPem: string, message: Buffer | string): Promise<string> {
|
||||
return sign(null, typeof message === "string" ? Buffer.from(message) : message, privateKeyPem).toString("base64");
|
||||
}
|
||||
|
||||
export async function verifySignature(publicKeyPem: string, message: Buffer | string, signature: string): Promise<boolean> {
|
||||
try {
|
||||
return verify(null, typeof message === "string" ? Buffer.from(message) : message, publicKeyPem, Buffer.from(signature, "base64"));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function generatePairingCode(): string {
|
||||
const bytes = randomBytes(8);
|
||||
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
let code = "";
|
||||
for (let i = 0; i < 12; i++) code += chars[bytes[i % bytes.length] % chars.length];
|
||||
return `${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8, 12)}`;
|
||||
}
|
||||
|
||||
export function generateSecret(): string {
|
||||
return randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
export function generateNonce(): string {
|
||||
return randomBytes(18).toString("base64url").slice(0, 24);
|
||||
}
|
||||
@@ -2,3 +2,4 @@ export * from "./types.js";
|
||||
export * from "./codec.js";
|
||||
export * from "./errors.js";
|
||||
export * from "./auth.js";
|
||||
export * from "./crypto.js";
|
||||
|
||||
32
src/types.ts
32
src/types.ts
@@ -22,7 +22,7 @@ export type BuiltinMessageType = (typeof builtinMessageTypes)[number];
|
||||
|
||||
export interface BuiltinEnvelope<
|
||||
TType extends BuiltinMessageType = BuiltinMessageType,
|
||||
TPayload extends Record<string, unknown> = Record<string, unknown>
|
||||
TPayload = Record<string, unknown>
|
||||
> {
|
||||
type: TType;
|
||||
requestId?: string;
|
||||
@@ -30,7 +30,7 @@ export interface BuiltinEnvelope<
|
||||
payload?: TPayload;
|
||||
}
|
||||
|
||||
export interface HelloPayload {
|
||||
export interface HelloPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
hasSecret: boolean;
|
||||
hasKeyPair: boolean;
|
||||
@@ -44,14 +44,14 @@ export type HelloAckNextAction =
|
||||
| "rejected"
|
||||
| "waiting_pair_confirm";
|
||||
|
||||
export interface HelloAckPayload {
|
||||
export interface HelloAckPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
nextAction: HelloAckNextAction;
|
||||
}
|
||||
|
||||
export type AdminNotificationStatus = "sent" | "failed";
|
||||
|
||||
export interface PairRequestPayload {
|
||||
export interface PairRequestPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
expiresAt: number;
|
||||
ttlSeconds: number;
|
||||
@@ -59,12 +59,12 @@ export interface PairRequestPayload {
|
||||
codeDelivery: "out_of_band";
|
||||
}
|
||||
|
||||
export interface PairConfirmPayload {
|
||||
export interface PairConfirmPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
pairingCode: string;
|
||||
}
|
||||
|
||||
export interface PairSuccessPayload {
|
||||
export interface PairSuccessPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
secret: string;
|
||||
pairedAt: number;
|
||||
@@ -77,12 +77,12 @@ export type PairFailedReason =
|
||||
| "admin_notification_failed"
|
||||
| "internal_error";
|
||||
|
||||
export interface PairFailedPayload {
|
||||
export interface PairFailedPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
reason: PairFailedReason;
|
||||
}
|
||||
|
||||
export interface AuthRequestPayload {
|
||||
export interface AuthRequestPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
nonce: string;
|
||||
proofTimestamp: number;
|
||||
@@ -90,7 +90,7 @@ export interface AuthRequestPayload {
|
||||
publicKey?: string;
|
||||
}
|
||||
|
||||
export interface AuthSuccessPayload {
|
||||
export interface AuthSuccessPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
authenticatedAt: number;
|
||||
status: "online";
|
||||
@@ -107,33 +107,33 @@ export type AuthFailedReason =
|
||||
| "rate_limited"
|
||||
| "re_pair_required";
|
||||
|
||||
export interface AuthFailedPayload {
|
||||
export interface AuthFailedPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
reason: AuthFailedReason;
|
||||
}
|
||||
|
||||
export interface RePairRequiredPayload {
|
||||
export interface RePairRequiredPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
reason: "nonce_collision" | "rate_limited" | "trust_revoked";
|
||||
}
|
||||
|
||||
export interface HeartbeatPayload {
|
||||
export interface HeartbeatPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
status: "alive";
|
||||
}
|
||||
|
||||
export interface HeartbeatAckPayload {
|
||||
export interface HeartbeatAckPayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
status: "online" | "unstable" | "offline";
|
||||
}
|
||||
|
||||
export interface StatusUpdatePayload {
|
||||
export interface StatusUpdatePayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
status: "online" | "unstable" | "offline";
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface DisconnectNoticePayload {
|
||||
export interface DisconnectNoticePayload extends Record<string, unknown> {
|
||||
identifier: string;
|
||||
reason: string;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export type ProtocolErrorCode =
|
||||
| "CLIENT_OFFLINE"
|
||||
| "INTERNAL_ERROR";
|
||||
|
||||
export interface ErrorPayload {
|
||||
export interface ErrorPayload extends Record<string, unknown> {
|
||||
code: ProtocolErrorCode;
|
||||
message?: string;
|
||||
details?: Record<string, unknown>;
|
||||
|
||||
Reference in New Issue
Block a user