71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
"use node";
|
|
|
|
import { action } from "./_generated/server";
|
|
import { internal } from "./_generated/api";
|
|
import { v } from "convex/values";
|
|
import crypto from "crypto";
|
|
|
|
export const resetPasswordAction = action({
|
|
args: {
|
|
username: v.string(),
|
|
newSalt: v.string(),
|
|
newEncryptedMK: v.string(),
|
|
newHAK: v.string(),
|
|
signature: v.string(),
|
|
timestamp: v.number(),
|
|
},
|
|
returns: v.union(
|
|
v.object({ success: v.boolean() }),
|
|
v.object({ error: v.string() })
|
|
),
|
|
handler: async (ctx, args) => {
|
|
// Validate timestamp is within 5 minutes
|
|
const now = Date.now();
|
|
if (Math.abs(now - args.timestamp) > 5 * 60 * 1000) {
|
|
return { error: "Request expired. Please try again." };
|
|
}
|
|
|
|
// Get user data
|
|
const userData = await ctx.runQuery(internal.auth.getUserForRecovery, {
|
|
username: args.username,
|
|
});
|
|
|
|
if (!userData) {
|
|
return { error: "User not found" };
|
|
}
|
|
|
|
// Verify Ed25519 signature
|
|
const message = `password-reset:${args.username}:${args.timestamp}`;
|
|
try {
|
|
const publicKeyObj = crypto.createPublicKey({
|
|
key: userData.publicSigningKey,
|
|
format: "pem",
|
|
type: "spki",
|
|
});
|
|
|
|
const isValid = crypto.verify(
|
|
null,
|
|
Buffer.from(message),
|
|
publicKeyObj,
|
|
Buffer.from(args.signature, "hex")
|
|
);
|
|
|
|
if (!isValid) {
|
|
return { error: "Invalid recovery key" };
|
|
}
|
|
} catch (e) {
|
|
return { error: "Signature verification failed" };
|
|
}
|
|
|
|
// Update credentials
|
|
await ctx.runMutation(internal.auth.updateCredentials, {
|
|
userId: userData.userId,
|
|
clientSalt: args.newSalt,
|
|
encryptedMasterKey: args.newEncryptedMK,
|
|
hashedAuthKey: args.newHAK,
|
|
});
|
|
|
|
return { success: true };
|
|
},
|
|
});
|