This commit is contained in:
109
convex/messageActions.ts
Normal file
109
convex/messageActions.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
"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;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user