Files
DiscordClone/convex/authActions.ts
Bryan1029384756 593eaba82e 1.1.3
2026-04-18 15:41:51 -05:00

114 lines
3.7 KiB
TypeScript

"use node";
import { action } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
import { requireAuth } from "./authGuard";
/**
* Signed profile update. The canonical message binds userId + timestamp;
* the 5-minute replay window limits damage if a signature leaks. See
* `authGuard.requireAuth` for the full verification flow.
*/
export const updateProfile = action({
args: {
userId: v.id("userProfiles"),
displayName: v.optional(v.string()),
aboutMe: v.optional(v.string()),
avatarStorageId: v.optional(v.id("_storage")),
customStatus: v.optional(v.string()),
joinSoundStorageId: v.optional(v.id("_storage")),
removeJoinSound: v.optional(v.boolean()),
accentColor: v.optional(v.string()),
bannerStorageId: v.optional(v.id("_storage")),
removeBanner: v.optional(v.boolean()),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `updateProfile:${args.userId}:${args.authTimestamp}`;
await requireAuth(ctx, args.userId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.auth.updateProfileInternal, {
userId: args.userId,
displayName: args.displayName,
aboutMe: args.aboutMe,
avatarStorageId: args.avatarStorageId,
customStatus: args.customStatus,
joinSoundStorageId: args.joinSoundStorageId,
removeJoinSound: args.removeJoinSound,
accentColor: args.accentColor,
bannerStorageId: args.bannerStorageId,
removeBanner: args.removeBanner,
});
return null;
},
});
export const updateStatus = action({
args: {
userId: v.id("userProfiles"),
status: v.string(),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `updateStatus:${args.userId}:${args.status}:${args.authTimestamp}`;
await requireAuth(ctx, args.userId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.auth.updateStatusInternal, {
userId: args.userId,
status: args.status,
});
return null;
},
});
export const setNickname = action({
args: {
actorUserId: v.id("userProfiles"),
targetUserId: v.id("userProfiles"),
displayName: v.string(),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.null(),
handler: async (ctx, args): Promise<null> => {
const canonical = `setNickname:${args.actorUserId}:${args.targetUserId}:${args.authTimestamp}`;
await requireAuth(ctx, args.actorUserId, args.authTimestamp, args.authSignature, canonical);
await ctx.runMutation(internal.auth.setNicknameInternal, {
actorUserId: args.actorUserId,
targetUserId: args.targetUserId,
displayName: args.displayName,
});
return null;
},
});
export const deleteUser = action({
args: {
requestingUserId: v.id("userProfiles"),
targetUserId: v.id("userProfiles"),
authTimestamp: v.number(),
authSignature: v.string(),
},
returns: v.object({ success: v.boolean(), error: v.optional(v.string()) }),
handler: async (
ctx,
args,
): Promise<{ success: boolean; error?: string }> => {
const canonical = `deleteUser:${args.requestingUserId}:${args.targetUserId}:${args.authTimestamp}`;
await requireAuth(
ctx,
args.requestingUserId,
args.authTimestamp,
args.authSignature,
canonical,
);
return await ctx.runMutation(internal.auth.deleteUserInternal, {
requestingUserId: args.requestingUserId,
targetUserId: args.targetUserId,
});
},
});