This commit is contained in:
@@ -123,11 +123,7 @@ export const createUserWithProfile = mutation({
|
||||
return { error: "Invite expired" };
|
||||
}
|
||||
|
||||
if (
|
||||
invite.maxUses !== undefined &&
|
||||
invite.maxUses !== null &&
|
||||
invite.uses >= invite.maxUses
|
||||
) {
|
||||
if (invite.maxUses !== undefined && invite.uses >= invite.maxUses) {
|
||||
return { error: "Invite max uses reached" };
|
||||
}
|
||||
|
||||
@@ -240,8 +236,12 @@ export const getPublicKeys = query({
|
||||
},
|
||||
});
|
||||
|
||||
// Update user profile (aboutMe, avatar, customStatus)
|
||||
export const updateProfile = mutation({
|
||||
// Internal writer. Public entry point: `authActions.updateProfile`. The
|
||||
// action layer verifies the caller controls `userId` via Ed25519
|
||||
// signature before this runs; calling this from another mutation
|
||||
// without that check would reintroduce the "anyone can change anyone's
|
||||
// profile" vulnerability.
|
||||
export const updateProfileInternal = internalMutation({
|
||||
args: {
|
||||
userId: v.id("userProfiles"),
|
||||
displayName: v.optional(v.string()),
|
||||
@@ -278,8 +278,8 @@ export const getMyJoinSoundUrl = query({
|
||||
},
|
||||
});
|
||||
|
||||
// Update user status
|
||||
export const updateStatus = mutation({
|
||||
// Internal writer. Public entry point: `authActions.updateStatus`.
|
||||
export const updateStatusInternal = internalMutation({
|
||||
args: {
|
||||
userId: v.id("userProfiles"),
|
||||
status: v.string(),
|
||||
@@ -343,8 +343,51 @@ export const getUserForRecovery = internalQuery({
|
||||
},
|
||||
});
|
||||
|
||||
// Set nickname (displayName) for a user
|
||||
export const setNickname = mutation({
|
||||
// Internal: resolve a userId to the fields needed by the voice-token action
|
||||
// (server-side signature verification + LiveKit identity).
|
||||
export const getUserForVoiceToken = internalQuery({
|
||||
args: { userId: v.id("userProfiles") },
|
||||
returns: v.union(
|
||||
v.object({
|
||||
userId: v.id("userProfiles"),
|
||||
username: v.string(),
|
||||
publicSigningKey: v.string(),
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const user = await ctx.db.get(args.userId);
|
||||
if (!user) return null;
|
||||
return {
|
||||
userId: user._id,
|
||||
username: user.username,
|
||||
publicSigningKey: user.publicSigningKey,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Internal: fetch a channel for the voice-token action so it can confirm the
|
||||
// target channel exists and is actually a voice/dm room.
|
||||
export const getChannelForVoiceToken = internalQuery({
|
||||
args: { channelId: v.id("channels") },
|
||||
returns: v.union(
|
||||
v.object({
|
||||
channelId: v.id("channels"),
|
||||
type: v.string(),
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const channel = await ctx.db.get(args.channelId);
|
||||
if (!channel) return null;
|
||||
return { channelId: channel._id, type: channel.type };
|
||||
},
|
||||
});
|
||||
|
||||
// Internal writer. Public entry point: `authActions.setNickname`. The
|
||||
// action layer verifies the caller controls `actorUserId` via
|
||||
// signature; the existing self-or-manage_nicknames gate stays here.
|
||||
export const setNicknameInternal = internalMutation({
|
||||
args: {
|
||||
actorUserId: v.id("userProfiles"),
|
||||
targetUserId: v.id("userProfiles"),
|
||||
@@ -371,8 +414,10 @@ export const setNickname = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
// Delete a user and all their associated data (admin only)
|
||||
export const deleteUser = mutation({
|
||||
// Internal writer. Public entry point: `authActions.deleteUser`. Both
|
||||
// the isAdmin check and the destructive delete live here; the action
|
||||
// layer verifies the caller controls `requestingUserId`.
|
||||
export const deleteUserInternal = internalMutation({
|
||||
args: {
|
||||
requestingUserId: v.id("userProfiles"),
|
||||
targetUserId: v.id("userProfiles"),
|
||||
|
||||
Reference in New Issue
Block a user