All checks were successful
Build and Release / build-and-release (push) Successful in 20m36s
110 lines
3.3 KiB
TypeScript
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;
|
|
},
|
|
});
|