Files
DiscordClone/convex/messageActions.ts
Bryan1029384756 6813bb40dc
All checks were successful
Build and Release / build-and-release (push) Successful in 20m36s
1.1.00
2026-04-16 20:14:27 -05:00

110 lines
3.3 KiB
TypeScript

"use node";
import { action } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import { requireAuth } from "./authGuard";
/**
* Signed-send: the client signs `send:${channelId}:${senderId}:${timestamp}`
* with their Ed25519 key so the server can prove the caller controls
* `senderId`. Prevents the "anyone posts as anyone" bypass that existed
* when `messages.send` was a plain mutation trusting the client-supplied
* `senderId`.
*
* The per-message `signature` over the ciphertext is a separate,
* recipient-verified integrity check and is unchanged.
*/
export const send = action({
args: {
channelId: v.id("channels"),
senderId: v.id("userProfiles"),
ciphertext: v.string(),
nonce: v.string(),
signature: v.string(),
keyVersion: v.number(),
replyTo: v.optional(v.id("messages")),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.object({ id: v.id("messages") }),
handler: async (ctx, args): Promise<{ id: any }> => {
const canonical = `send:${args.channelId}:${args.senderId}:${args.authTimestamp}`;
await requireAuth(ctx, args.senderId, args.authTimestamp, args.authSignature, canonical);
return await ctx.runMutation(internal.messages.sendInternal, {
channelId: args.channelId,
senderId: args.senderId,
ciphertext: args.ciphertext,
nonce: args.nonce,
signature: args.signature,
keyVersion: args.keyVersion,
replyTo: args.replyTo,
});
},
});
export const edit = action({
args: {
id: v.id("messages"),
userId: v.id("userProfiles"),
ciphertext: v.string(),
nonce: v.string(),
signature: v.string(),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `edit:${args.id}:${args.userId}:${args.authTimestamp}`;
await requireAuth(ctx, args.userId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.messages.editInternal, {
id: args.id,
userId: args.userId,
ciphertext: args.ciphertext,
nonce: args.nonce,
signature: args.signature,
});
return null;
},
});
export const pin = action({
args: {
id: v.id("messages"),
userId: v.id("userProfiles"),
pinned: v.boolean(),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `pin:${args.id}:${args.userId}:${args.pinned}:${args.authTimestamp}`;
await requireAuth(ctx, args.userId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.messages.pinInternal, {
id: args.id,
userId: args.userId,
pinned: args.pinned,
});
return null;
},
});
export const remove = action({
args: {
id: v.id("messages"),
userId: v.id("userProfiles"),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `remove:${args.id}:${args.userId}:${args.authTimestamp}`;
await requireAuth(ctx, args.userId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.messages.removeInternal, {
id: args.id,
userId: args.userId,
});
return null;
},
});