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.

import { createMailboxChallenge } from "@passlock/server";
const user = await requireSignedInUser();
const newEmail = "new-address@example.com";
const result = await createMailboxChallenge({
tenancyId: "myTenancyId",
apiKey: "myApiKey",
email: newEmail,
purpose: "email-change",
userId: String(user.id),
invalidateOthers: true,
});
// message contains rendered HTML and plain-text email content.
// result.challenge.code is also available if you prefer to render your own email.
const { challengeId, secret, message, email } = result.challenge;
// save this in the user's session or a secure HTTP only cookie
await savePendingEmailChange({
challengeId,
secret,
userId: user.id,
email,
});
await sendCodeEmail({ email, message });

Verify the new mailbox and update the account

Section titled “Verify the new mailbox and update the account”
import { verifyMailboxChallenge } from "@passlock/server";
const user = await requireSignedInUser();
// fetch from the user's session or secure cookie
const pending = await loadPendingEmailChange(user.id);
const verified = await verifyMailboxChallenge({
tenancyId: "myTenancyId",
apiKey: "myApiKey",
challengeId: pending.challengeId,
secret: pending.secret,
code: form.code,
});
if (verified.challenge.purpose !== "email-change") {
throw new Error("Unexpected challenge purpose");
}
if (verified.challenge.userId !== String(user.id)) {
throw new Error("Unexpected user");
}
await updateUserEmail(user.id, verified.challenge.email);
await clearPendingEmailChange(user.id);
await sendEmailChangeNotice(user.email);

The same endpoints are available over raw HTTP if you are not using @passlock/server. See the REST API mailbox challenge reference.