in src/app/api/utils/auth.tsx [115:244]
async jwt({ token, account, profile, trigger }) {
if (trigger === "update") {
// Refresh the user data from FxA, in case e.g. new subscriptions got added:
const subscriberFromDb = await getSubscriberByFxaUid(
token.subscriber?.fxa_uid ?? "",
);
if (subscriberFromDb) {
const sanitizedSubscriber = sanitizeSubscriberRow(subscriberFromDb);
profile = sanitizedSubscriber.fxa_profile_json as FxaProfile;
token.subscriber =
sanitizedSubscriber as unknown as SerializedSubscriber;
}
}
if (profile) {
token.fxa = {
locale: profile.locale,
twoFactorAuthentication: profile.twoFactorAuthentication,
metricsEnabled: profile.metricsEnabled,
avatar: profile.avatar,
avatarDefault: profile.avatarDefault,
subscriptions: profile.subscriptions ?? [],
};
}
if (!account) {
return token;
}
if (typeof profile?.uid === "string") {
// We're signing in with FxA; store user in database if not present yet.
// Note: we could create an [Adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
// to store the user in the database, but by doing it in the callback,
// we can also store FxA account data. We also don't have to worry
// about model mismatches (i.e. Next-Auth expecting one User to have
// multiple Accounts at multiple providers).
const existingUser = await getSubscriberByFxaUid(profile.uid);
if (existingUser) {
const sanitizedSubscriber = sanitizeSubscriberRow(existingUser);
token.subscriber =
sanitizedSubscriber as unknown as SerializedSubscriber;
if (account.access_token && account.refresh_token) {
const updatedUser = await updateFxAData(
existingUser,
account.access_token,
account.refresh_token,
account.expires_at ?? 0,
profile,
);
if (updatedUser) {
const sanitizedUpdatedUser = sanitizeSubscriberRow(updatedUser);
// Next-Auth implicitly converts `updatedUser` to a SerializedSubscriber,
// hence the type assertion:
token.subscriber =
sanitizedUpdatedUser as unknown as SerializedSubscriber;
}
}
} else if (!existingUser && profile.email) {
const verifiedSubscriber = await addSubscriber(
profile.email,
profile.locale,
account.access_token,
account.refresh_token,
account.expires_at,
profile,
);
if (verifiedSubscriber) {
const sanitizedSubscriber =
sanitizeSubscriberRow(verifiedSubscriber);
// The date fields of `verifiedSubscriber` get converted to an ISO 8601
// date string when serialised in the token, hence the type assertion:
token.subscriber =
sanitizedSubscriber as unknown as SerializedSubscriber;
}
const allBreaches = await getBreaches();
const unsafeBreachesForEmail = await getBreachesForEmail(
getSha1(profile.email),
allBreaches,
true,
);
// Send report email
const l10n = getL10n(
verifiedSubscriber?.signup_language ??
(await getAcceptLangHeaderInServerComponents()),
);
const subject = unsafeBreachesForEmail?.length
? l10n.getString("email-subject-found-breaches")
: l10n.getString("email-subject-no-breaches");
record("account", "create", {
string: {
monitorUserId: account.userId ?? "",
},
});
await initEmail(process.env.SMTP_URL);
await sendEmail(
profile.email,
subject,
await renderEmail(
<SignupReportEmail
l10n={l10n}
breachedEmailAddress={profile.email}
breaches={unsafeBreachesForEmail}
/>,
),
);
} else {
logger.warn("no_existing_user_or_email", {
token,
account,
profile,
trigger,
});
}
} else {
logger.warn("profile_email_not_string", {
token,
account,
profile,
trigger,
});
}
return token;
},