Skip to content

Account management

Account management flows often need mailbox verification too. A common example is changing the email address on an existing account: create a challenge for the new mailbox, verify the code, then update the account.

backend/email-change.ts
import { Passlock, isChallengeRateLimitedError } from "@passlock/server";
const passlock = new Passlock({ ... });
const { userId } = await getLoggedInUser();
const email = "..."; // new email address
const result = await passlock.createMailboxChallenge({
email,
userId,
purpose: "email-change",
// any pending "email-change" challenges for this userId will be nuked
invalidateOthers: true,
});
if (result.success) {
const { challengeId, secret, message } = result.value.challenge;
// save this in the user's session or a secure HTTP only cookie
await savePendingChallenge({
purpose: "email-change",
challengeId,
secret,
});
await emailCodeToUser({ email, message });
} else if (isChallengeRateLimitedError(result.error)) {
return showRateLimit(result.error.retryAfterSeconds);
} else {
throw new Error(result.error.message);
}
backend/email-change.ts
import { Passlock } from "@passlock/server";
const passlock = new Passlock({... });
const { userId, email: currentEmail } = await getLoggedInUser();
// fetch from the user's session or secure cookie
const pendingChallenge = await loadPendingChallenge({ purpose: "email-change" });
const result = await passlock.verifyMailboxChallenge({
challengeId: pendingChallenge.challengeId,
secret: pendingChallenge.secret,
code: form.code, // the user submitted this
});
if (result.success) {
const email = result.challenge.email;
// email currentEmail notifying user of the change
await sendEmailUpdatedMessage(currentEmail);
// update the account email
await updateUserEmail(userId, email);
} else {
return throw new Error(result.error.message);
}