Files
pedwfrontend/pages/api/auth/[...nextauth].js
T
2026-05-14 08:55:49 +00:00

310 lines
9.2 KiB
JavaScript

/**
* @swagger
* /api/auth/[...nextauth]:
* get:
* summary: Phase 2
* tags: [Login]
* description: NextAuth
* responses:
* 200:
* description: Success
*/
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import NextAuth from "next-auth";
import EmailProvider from "next-auth/providers/email";
import { consoleLogger, redactSensitive } from "../../../actions/core/logger";
import { getPortalLogin } from "../../../actions/services/accountService";
const prisma = new PrismaClient();
const WELSH_LANGUAGE_CODE = 846040000;
const authDiagnostic = (event, metadata = {}) => {
try {
const safeMeta = JSON.parse(JSON.stringify(metadata));
console.info(`[auth][${event}]`, redactSensitive(safeMeta));
} catch (_error) {
console.info(`[auth][${event}]`);
}
};
const appendParamsAndPathToNewUrl = (fromUrl, toUrl) => {
try {
const fromUrlObj = new URL(fromUrl);
const params = fromUrlObj.searchParams;
const toUrlObj = new URL(toUrl);
toUrlObj.pathname = fromUrlObj.pathname;
params.forEach((value, key) => {
if (!toUrlObj.searchParams.has(key)) {
toUrlObj.searchParams.append(key, value);
}
});
return toUrlObj.toString();
} catch (_error) {
return toUrl;
}
};
const resolveCrmLocale = async (email) => {
if (!email) return null;
try {
const portalUserObj = await getPortalLogin(email);
const preferredLanguage =
portalUserObj?.value?.[0]?.pinswg_preferredlanguage;
if (preferredLanguage === WELSH_LANGUAGE_CODE) {
return "cy";
}
if (preferredLanguage != null) {
return "en";
}
return null;
} catch (error) {
consoleLogger(error);
return null;
}
};
const resolveEffectiveLocale = async (req, email) => {
const crmLocale = await resolveCrmLocale(email);
if (crmLocale) {
authDiagnostic("effective-locale.crm", {
locale: crmLocale,
hasEmail: !!email
});
return crmLocale;
}
const requestLocale = resolveRequestLocale(req);
authDiagnostic("effective-locale.request", {
locale: requestLocale,
hasEmail: !!email
});
return requestLocale;
};
const buildLocalizedVerificationUrl = ({ url, email, effectiveLocale }) => {
const { host, protocol, searchParams } = new URL(url);
const baseDomain = `${protocol}//${host}`;
const newURL =
effectiveLocale === "cy"
? baseDomain +
"/api/auth/callback/email?callbackUrl=" +
encodeURIComponent(baseDomain + "/cy") +
"&token=" +
searchParams.get("token") +
"&email=" +
encodeURIComponent(email) +
"&locale=" +
effectiveLocale
: url;
return appendParamsAndPathToNewUrl(url, newURL);
};
const getLocaleFromCallbackUrl = (callbackUrl) => {
if (!callbackUrl) return null;
try {
const parsed = new URL(callbackUrl);
const locale = parsed.searchParams.get("locale");
if (locale === "cy" || parsed.pathname.startsWith("/cy")) {
return "cy";
}
if (locale === "en") {
return "en";
}
return null;
} catch {
return null;
}
};
const resolveRequestLocale = (req) => {
const directLocale =
req?.query?.locale || req?.body?.locale || req?.cookies?.pedw_locale;
if (directLocale === "cy") {
authDiagnostic("request-locale.direct", { locale: "cy" });
return "cy";
}
if (directLocale === "en") {
authDiagnostic("request-locale.direct", { locale: "en" });
return "en";
}
const callbackLocale =
getLocaleFromCallbackUrl(req?.body?.callbackUrl) ||
getLocaleFromCallbackUrl(req?.query?.callbackUrl) ||
getLocaleFromCallbackUrl(req?.cookies?.["next-auth.callback-url"]) ||
getLocaleFromCallbackUrl(
req?.cookies?.["__Secure-next-auth.callback-url"]
);
const resolved = callbackLocale === "cy" ? "cy" : "en";
authDiagnostic("request-locale.callback-fallback", {
locale: resolved,
hasBodyCallback: !!req?.body?.callbackUrl,
hasQueryCallback: !!req?.query?.callbackUrl,
hasLegacyCookieCallback: !!req?.cookies?.["next-auth.callback-url"],
hasSecureCookieCallback:
!!req?.cookies?.["__Secure-next-auth.callback-url"]
});
return resolved;
};
const authOptions = (req, res) => {
const requestLocale = resolveRequestLocale(req);
return {
providers: [
{
id: "emailAPI",
name: "emailAPI",
type: "email",
async sendVerificationRequest({ identifier: email, url }) {
const effectiveLocale = await resolveEffectiveLocale(
req,
email
);
const formURL = buildLocalizedVerificationUrl({
url,
email,
effectiveLocale
});
return res
.writeHead(200, { "Content-Type": "application/json" })
.json({ url: formURL });
}
},
EmailProvider({
maxAge: 2 * 60 * 60,
async sendVerificationRequest({ identifier: email, url }) {
const templateId = "b1b5704b-9bb8-4deb-a75c-d887ca902661";
const templateIdcy = "0614ce53-cd5f-421f-a1a5-8c8486a9113a";
const effectiveLocale = await resolveEffectiveLocale(
req,
email
);
const formURL = buildLocalizedVerificationUrl({
url,
email,
effectiveLocale
});
const personalisation = {
emailAddress: email,
signInLink: formURL,
linkExpiry: 2 * 60 * 60
};
const reference = "PEDW-SIGNIN";
var NotifyClient =
require("notifications-node-client").NotifyClient;
const notifyClient = new NotifyClient(
process.env.NOTIFY_API_KEY
);
try {
await notifyClient.sendEmail(
effectiveLocale === "cy"
? templateIdcy
: templateId,
email,
{
personalisation,
reference
}
);
} catch (error) {
consoleLogger(error);
}
}
})
],
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: "database",
jwt: true,
pages: {},
theme: "dark",
debug: true,
maxAge: 30 * 60,
updateAge: 5 * 60
},
cookies: {
callbackUrl: {
name: `__Secure-next-auth.callback-url`,
options: {
sameSite: "lax",
path: "/",
secure: true
}
}
},
pages: {
signIn: (requestLocale === "cy" ? "/cy" : "") + "/auth/signin",
error: (requestLocale === "cy" ? "/cy" : "") + "/auth/error",
verifyRequest:
(requestLocale === "cy" ? "/cy" : "") + "/auth/verify-request",
newUser: (requestLocale === "cy" ? "/cy" : "") + "/account/register"
},
callbacks: {
session: async (session, user) => {
return Promise.resolve(session);
},
redirect({ url, baseUrl }) {
if (url.startsWith("/")) return `${baseUrl}${url}`;
try {
if (new URL(url).origin === baseUrl) return url;
} catch (_error) {
authDiagnostic("redirect.invalid-url", {
baseUrl,
hasUrl: !!url
});
return baseUrl;
}
const newUrl =
requestLocale === "cy"
? process.env.CY_API_ROOT
: process.env.NEXTAUTH_URL;
const updatedUrl = appendParamsAndPathToNewUrl(url, newUrl);
authDiagnostic("redirect.external-rewrite", {
locale: requestLocale,
targetOrigin: newUrl
});
return updatedUrl;
}
}
};
};
const NextAuthPEDW = (req, res) => {
return NextAuth(req, res, authOptions(req, res));
};
export default NextAuthPEDW;