Major changes: - Restructure plugin architecture: moved federation logic into a dedicated `federation` plugin with Better Auth integration, defining schemas for server registry, key rotation, and blacklist management - Extract encryption layer: new `oven` plugin handles end-to-end encryption (E2EE) with OLM client/server implementations - Reorganize social features: consolidated social endpoints (posts, follows, blocks, mutes) and removed legacy plugin patterns in favor of unified plugin structure - Decentralized key management: refactored `keytools` and `keygen` to support federation key rotation with challenge tokens and health checks Infrastructure updates: - Upgrade dependencies: bump Better Auth to 1.6.9, React to 19.2.5, Next.js to 16.2.3, Tailwind to 4.2.4 - Add cryptographic libraries: @scure/bip39, @signalapp/libsignal-client, @matrix-org/matrix-sdk-crypto-wasm for enhanced federation security - Add utilities: base58-js, uuid for federation identifier handling - Update database schema with new federation tables (serverRegistry, rotateChallengeTokens, blacklistedServers) Minor updates: test suite alignment, storage client cleanup, PostFederationSchema refinements Co-authored-by: Cursor <cursoragent@cursor.com>
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
import { z } from "zod";
|
|
|
|
/**
|
|
* Matrix-flavoured unpadded base64 for a 32-byte Curve25519/Ed25519 public key.
|
|
* 32 raw bytes encode to exactly 43 base64 characters (no padding).
|
|
*
|
|
* Anything longer (e.g. a 64-byte NaCl secret key → 86 chars) or shorter is
|
|
* rejected, which is our cheap defence against a client trying to upload
|
|
* private key material in a field that should only hold a public key.
|
|
*/
|
|
const OLM_PUBLIC_KEY = z
|
|
.string()
|
|
.regex(/^[A-Za-z0-9+/]{43}$/, "Must be unpadded base64 of a 32-byte public key");
|
|
|
|
const SignaturesSchema = z.record(
|
|
z.string(),
|
|
z.record(z.string(), z.string()),
|
|
);
|
|
|
|
const DeviceKeysSchema = z.object({
|
|
user_id: z.string(),
|
|
device_id: z.string(),
|
|
algorithms: z.array(z.string()),
|
|
keys: z.record(z.string(), OLM_PUBLIC_KEY),
|
|
signatures: SignaturesSchema,
|
|
});
|
|
|
|
export const SignedKeySchema = z.object({
|
|
key: OLM_PUBLIC_KEY,
|
|
signatures: SignaturesSchema,
|
|
});
|
|
|
|
export const SignedFallbackKeySchema = z.object({
|
|
key: OLM_PUBLIC_KEY,
|
|
fallback: z.literal(true),
|
|
signatures: SignaturesSchema,
|
|
});
|
|
|
|
export type SignedKey = z.infer<typeof SignedKeySchema>;
|
|
export type SignedFallbackKey = z.infer<typeof SignedFallbackKeySchema>;
|
|
|
|
export const KeysUploadBodySchema = z
|
|
.object({
|
|
device_keys: DeviceKeysSchema.optional(),
|
|
one_time_keys: z
|
|
.record(z.string(), z.union([z.string(), SignedKeySchema]))
|
|
.optional(),
|
|
fallback_keys: z
|
|
.record(z.string(), z.union([z.string(), SignedFallbackKeySchema]))
|
|
.optional(),
|
|
})
|
|
.refine(
|
|
(b) =>
|
|
b.device_keys !== undefined ||
|
|
b.one_time_keys !== undefined ||
|
|
b.fallback_keys !== undefined,
|
|
{ message: "At least one of device_keys, one_time_keys, or fallback_keys must be present" },
|
|
);
|
|
|
|
export type KeysUploadBody = z.infer<typeof KeysUploadBodySchema>;
|
|
|
|
/**
|
|
* Body for `POST /oven/identity/register`.
|
|
*
|
|
* Carries the user's stable per-account identity material derived client-side
|
|
* from their BIP-39 mnemonic. Both fields are public; the corresponding secret
|
|
* key never leaves the client's encrypted Dexie store.
|
|
*
|
|
* - `signingPublicKey`: base58 of the Ed25519 verification key.
|
|
* - `fingerprint`: base58 of the same public key (kept distinct so we can later
|
|
* migrate to a separate human-readable fingerprint format without breaking
|
|
* the wire schema).
|
|
*/
|
|
export const IdentityRegisterBodySchema = z.object({
|
|
signingPublicKey: z.string().min(1),
|
|
fingerprint: z.string().min(1),
|
|
});
|
|
|
|
export type IdentityRegisterBody = z.infer<typeof IdentityRegisterBodySchema>;
|