Use room key encryption for private chat
This commit is contained in:
@@ -1,19 +1,26 @@
|
||||
import { Router } from "express";
|
||||
import { z } from "zod";
|
||||
import { requireAuth } from "../auth/middleware.js";
|
||||
import { addMessage, addReaction, listChat } from "./store.js";
|
||||
import { addMessage, addReaction, listChat, upsertKeyWrap } from "./store.js";
|
||||
|
||||
export const chatRouter = Router();
|
||||
|
||||
const MessageBody = z.object({
|
||||
content: z.string().trim().min(1).max(2000),
|
||||
content: z.string().trim().min(1).max(12000),
|
||||
replyToId: z.string().max(128).optional(),
|
||||
replyToPubkey: z.string().max(128).optional(),
|
||||
});
|
||||
|
||||
const ReactionBody = z.object({
|
||||
messageId: z.string().min(1).max(128),
|
||||
content: z.string().trim().min(1).max(32),
|
||||
content: z.string().trim().min(1).max(12000),
|
||||
});
|
||||
|
||||
const KeyWrapBody = z.object({
|
||||
wraps: z.array(z.object({
|
||||
recipientPubkey: z.string().regex(/^[0-9a-f]{64}$/),
|
||||
ciphertext: z.string().trim().min(1).max(12000),
|
||||
})).min(1).max(10),
|
||||
});
|
||||
|
||||
chatRouter.use(requireAuth);
|
||||
@@ -42,3 +49,15 @@ chatRouter.post("/reactions", (req, res) => {
|
||||
});
|
||||
res.status(201).json(reaction);
|
||||
});
|
||||
|
||||
chatRouter.post("/key-wraps", (req, res) => {
|
||||
const body = KeyWrapBody.parse(req.body);
|
||||
const wraps = body.wraps.map((wrap) =>
|
||||
upsertKeyWrap({
|
||||
recipientPubkey: wrap.recipientPubkey,
|
||||
senderPubkey: req.session!.pubkey,
|
||||
ciphertext: wrap.ciphertext,
|
||||
}),
|
||||
);
|
||||
res.status(201).json({ wraps });
|
||||
});
|
||||
|
||||
@@ -15,14 +15,26 @@ export type StoredChatReaction = {
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type StoredChatKeyWrap = {
|
||||
recipientPubkey: string;
|
||||
senderPubkey: string;
|
||||
ciphertext: string;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
const MAX_MESSAGES = 500;
|
||||
const MAX_REACTIONS = 2000;
|
||||
|
||||
const messages: StoredChatMessage[] = [];
|
||||
const reactions: StoredChatReaction[] = [];
|
||||
const keyWraps: StoredChatKeyWrap[] = [];
|
||||
|
||||
export function listChat(): { messages: StoredChatMessage[]; reactions: StoredChatReaction[] } {
|
||||
return { messages: [...messages], reactions: [...reactions] };
|
||||
export function listChat(): {
|
||||
messages: StoredChatMessage[];
|
||||
reactions: StoredChatReaction[];
|
||||
keyWraps: StoredChatKeyWrap[];
|
||||
} {
|
||||
return { messages: [...messages], reactions: [...reactions], keyWraps: [...keyWraps] };
|
||||
}
|
||||
|
||||
export function addMessage(input: {
|
||||
@@ -60,3 +72,26 @@ export function addReaction(input: {
|
||||
if (reactions.length > MAX_REACTIONS) reactions.splice(0, reactions.length - MAX_REACTIONS);
|
||||
return reaction;
|
||||
}
|
||||
|
||||
export function upsertKeyWrap(input: {
|
||||
recipientPubkey: string;
|
||||
senderPubkey: string;
|
||||
ciphertext: string;
|
||||
}): StoredChatKeyWrap {
|
||||
const updatedAt = Math.floor(Date.now() / 1000);
|
||||
const existing = keyWraps.find((wrap) => wrap.recipientPubkey === input.recipientPubkey);
|
||||
if (existing) {
|
||||
existing.senderPubkey = input.senderPubkey;
|
||||
existing.ciphertext = input.ciphertext;
|
||||
existing.updatedAt = updatedAt;
|
||||
return existing;
|
||||
}
|
||||
const wrap: StoredChatKeyWrap = {
|
||||
recipientPubkey: input.recipientPubkey,
|
||||
senderPubkey: input.senderPubkey,
|
||||
ciphertext: input.ciphertext,
|
||||
updatedAt,
|
||||
};
|
||||
keyWraps.push(wrap);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user