Phase 1 reliability/stabilisation: auth logging, logout client, loader+xml hardening
This commit is contained in:
@@ -104,3 +104,18 @@ export const conLog = (err) => {
|
||||
console.log(errStr);
|
||||
return errStr;
|
||||
};
|
||||
|
||||
export const logInfo = (event, data = {}) => {
|
||||
try {
|
||||
console.info(
|
||||
JSON.stringify({
|
||||
level: "info",
|
||||
event,
|
||||
data: redactSensitive(data),
|
||||
ts: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
} catch (_error) {
|
||||
console.info(`[info] ${event}`);
|
||||
}
|
||||
};
|
||||
|
||||
+3
-10
@@ -2,12 +2,12 @@ import _ from "lodash";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { destroyCookie, setCookie } from "nookies";
|
||||
import { setCookie } from "nookies";
|
||||
import { connect } from "react-redux";
|
||||
import Banner from "../components/banner";
|
||||
import { setLogout } from "../store/accountDetails/action";
|
||||
|
||||
import { signOut } from "next-auth/react";
|
||||
import { logoutClient } from "../lib/auth/logoutClient";
|
||||
|
||||
const Header = (props) => {
|
||||
let { t, lang } = useTranslation();
|
||||
@@ -85,14 +85,7 @@ const Header = (props) => {
|
||||
|
||||
const handleLogout = () => {
|
||||
console.info("////////////\n" + "logout" + "\n////////////");
|
||||
(window.localStorage.clear(),
|
||||
destroyCookie(null, "next-auth.csrf-token", { path: "/" }),
|
||||
destroyCookie(null, "next-auth.callback-url", { path: "/" }),
|
||||
destroyCookie(null, "pedw_locale", { path: "/" }),
|
||||
destroyCookie(null, "pinsUser", { path: "/" }),
|
||||
signOut({
|
||||
callbackUrl: locale == "cy" ? "/cy/allgofnodi" : "/logout"
|
||||
}));
|
||||
logoutClient(locale);
|
||||
};
|
||||
|
||||
//const switchLocale = locale === "en" ? "cy" : "en";
|
||||
|
||||
@@ -1,41 +1,22 @@
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import router from "next/router";
|
||||
import Link from "next/link";
|
||||
import { destroyCookie } from "nookies";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { logoutClient } from "../../lib/auth/logoutClient";
|
||||
|
||||
const ServiceBanner = (props) => {
|
||||
props = props.props;
|
||||
let { t, lang } = useTranslation();
|
||||
|
||||
const signInButton = () => {
|
||||
router.push(
|
||||
`${
|
||||
router.locale == "cy" ? "/awd/mewngofnodi" : "/auth/signin"
|
||||
}?callbackUrl=${encodeURIComponent(
|
||||
window.location.href
|
||||
)}&error=EmailSignin`
|
||||
);
|
||||
};
|
||||
|
||||
const handleLogout = (locale) => {
|
||||
console.info("////////////\n" + "logout" + "\n////////////");
|
||||
window.localStorage.clear(),
|
||||
destroyCookie(null, "next-auth.csrf-token"),
|
||||
{ path: "/" },
|
||||
destroyCookie(null, "next-auth.callback-url", { path: "/" }),
|
||||
destroyCookie(null, "pedw_locale", { path: "/" }),
|
||||
destroyCookie(null, "pinsUser", { path: "/" }),
|
||||
signOut({
|
||||
callbackUrl: locale == "cy" ? "/cy/allgofnodi" : "/logout",
|
||||
});
|
||||
logoutClient(locale);
|
||||
};
|
||||
|
||||
const portalLogoLink = [
|
||||
"/myportal/[appealtypes]",
|
||||
"/newappeal",
|
||||
"/newappeal/[appealtypes]",
|
||||
"/myportal/representation",
|
||||
"/myportal/representation"
|
||||
];
|
||||
return (
|
||||
<div className="servicebanner myportal govuk-!-margin-bottom-4">
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { signOut } from "next-auth/react";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { useRouter } from "next/router";
|
||||
import { destroyCookie } from "nookies";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useIdleTimer } from "react-idle-timer";
|
||||
import TimeoutModal from "./timeoutmodal";
|
||||
import { logoutClient } from "../../lib/auth/logoutClient";
|
||||
|
||||
const TimeOut = (props) => {
|
||||
let { t } = useTranslation();
|
||||
@@ -21,14 +19,7 @@ const TimeOut = (props) => {
|
||||
const onIdle = () => {
|
||||
setState("Idle");
|
||||
console.info("////////////\n" + "logout" + "\n////////////");
|
||||
window.localStorage.clear(),
|
||||
destroyCookie(null, "next-auth.csrf-token", { path: "/" }),
|
||||
destroyCookie(null, "next-auth.callback-url", { path: "/" }),
|
||||
destroyCookie(null, "pedw_locale", { path: "/" }),
|
||||
destroyCookie(null, "pinsUser", { path: "/" }),
|
||||
signOut({
|
||||
callbackUrl: locale == "cy" ? "/cy/allgofnodi" : "/logout",
|
||||
});
|
||||
logoutClient(locale);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
@@ -48,7 +39,7 @@ const TimeOut = (props) => {
|
||||
onPrompt,
|
||||
timeout,
|
||||
promptBeforeIdle,
|
||||
throttle: 500,
|
||||
throttle: 500
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -89,14 +80,14 @@ const TimeOut = (props) => {
|
||||
<div
|
||||
className={open ? "modal fade show" : "modal fade"}
|
||||
style={{
|
||||
display: open ? "flex" : "none",
|
||||
display: open ? "flex" : "none"
|
||||
}}
|
||||
>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<h3>
|
||||
{t("common:login-modal-message", {
|
||||
remaining: remaining,
|
||||
remaining: remaining
|
||||
})}
|
||||
</h3>
|
||||
<button
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { destroyCookie } from "nookies";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
const AUTH_COOKIE_NAMES = [
|
||||
"next-auth.csrf-token",
|
||||
"next-auth.callback-url",
|
||||
"__Secure-next-auth.callback-url",
|
||||
"pedw_locale",
|
||||
"pinsUser"
|
||||
];
|
||||
|
||||
export const clearClientSessionArtifacts = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
AUTH_COOKIE_NAMES.forEach((cookieName) => {
|
||||
destroyCookie(null, cookieName, { path: "/" });
|
||||
});
|
||||
};
|
||||
|
||||
export const logoutClient = async (locale) => {
|
||||
clearClientSessionArtifacts();
|
||||
await signOut({
|
||||
callbackUrl: locale === "cy" ? "/cy/allgofnodi" : "/logout"
|
||||
});
|
||||
};
|
||||
@@ -2,6 +2,8 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const xmlCache = new Map();
|
||||
|
||||
/**
|
||||
* Reads /data/formsxml/{appealtypes}.xml and normalises it.
|
||||
* Returns { xmlStr } or throws on missing file.
|
||||
@@ -9,6 +11,10 @@ import path from "path";
|
||||
export function readFormXml(appealtypes) {
|
||||
if (!appealtypes) throw new Error("readFormXml: appealtypes is required");
|
||||
|
||||
if (xmlCache.has(appealtypes)) {
|
||||
return xmlCache.get(appealtypes);
|
||||
}
|
||||
|
||||
const configDirectory = path.resolve(process.cwd(), "data/formsxml");
|
||||
const filePath = path.join(configDirectory, `${appealtypes}.xml`);
|
||||
|
||||
@@ -20,5 +26,8 @@ export function readFormXml(appealtypes) {
|
||||
xmlStr = xmlStr.replace(/> <"/g, "><");
|
||||
xmlStr = xmlStr.toString();
|
||||
|
||||
return { xmlStr, filePath };
|
||||
const result = { xmlStr, filePath };
|
||||
xmlCache.set(appealtypes, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../actions/services/referenceDataService";
|
||||
import { getProgressFromBlob } from "../../actions/services/documentService";
|
||||
import { getPersonalAccount } from "../../actions/services/accountService";
|
||||
import { getIP } from "../../actions/core/logger";
|
||||
import { getIP, logInfo, consoleLogger } from "../../actions/core/logger";
|
||||
import { readFormXml } from "../forms/readFormXml";
|
||||
import { requireQueryParams } from "../routing/requireQueryParams";
|
||||
|
||||
@@ -29,6 +29,10 @@ export async function loadNewAppealPage(ctx) {
|
||||
|
||||
const session = await getSession(ctx);
|
||||
if (!session) {
|
||||
logInfo("auth.guard.redirect", {
|
||||
route: "/newappeal/[appealtypes]",
|
||||
reason: "missing_session"
|
||||
});
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/auth/signin",
|
||||
@@ -52,7 +56,18 @@ export async function loadNewAppealPage(ctx) {
|
||||
const blobProgress = await getProgressFromBlob(loggedInUserIdent, query.id);
|
||||
const accountDetails = await getPersonalAccount(loggedInUser);
|
||||
|
||||
const { xmlStr } = readFormXml(query.appealtypes);
|
||||
let xmlStr = "";
|
||||
try {
|
||||
({ xmlStr } = readFormXml(query.appealtypes));
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/error?reason=formxml",
|
||||
permanent: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
|
||||
@@ -16,6 +16,46 @@ Validation:
|
||||
Follow-ups:
|
||||
```
|
||||
|
||||
### CL-2026-05-13-PH1: reliability & stabilisation pass (auth/session + new-appeal loader hardening)
|
||||
|
||||
date: 2026-05-13
|
||||
author: Cline
|
||||
scope: `actions/core/logger.js`, `lib/auth/logoutClient.js`, `pages/api/auth/[...nextauth].js`, `pages/myportal/index.js`, `components/{header.js,myportal/servicebanner.js,timeout/index.js}`, `lib/forms/readFormXml.js`, `lib/newappeal/loadNewAppealPage.js`
|
||||
type: change
|
||||
rationale: Phase 1 stabilisation work to improve session/auth observability, reduce logout-flow drift, add defensive SSR handling, and apply low-risk XML read performance caching while preserving live behaviour.
|
||||
impact: Refactor/stabilisation only; no intended business-rule or route-contract changes; improved auth diagnostics and safer failure handling in new-appeal SSR loader path.
|
||||
status: completed
|
||||
|
||||
Summary:
|
||||
|
||||
- Added structured `logInfo(...)` helper in `actions/core/logger.js` (with existing redaction path reuse).
|
||||
- Added shared logout client helper `lib/auth/logoutClient.js` and wired logout calls in:
|
||||
- `components/header.js`
|
||||
- `components/myportal/servicebanner.js`
|
||||
- `components/timeout/index.js`
|
||||
- Fixed service-banner regression from refactor artifact (`handleLogout` undefined) and removed orphaned sign-in/logout call path.
|
||||
- Updated `pages/api/auth/[...nextauth].js`:
|
||||
- added auth callback/session/verification logging events
|
||||
- added readable verification URL console lines for emailAPI flow
|
||||
- made debug/cookie secure config environment-aware (`NODE_ENV`)
|
||||
- Added auth guard redirect logging in `pages/myportal/index.js` for missing-session redirects.
|
||||
- Hardened `lib/newappeal/loadNewAppealPage.js` with:
|
||||
- missing-session logging before redirect
|
||||
- XML read try/catch + safe redirect fallback (`/error?reason=formxml`)
|
||||
- Added process-level XML cache in `lib/forms/readFormXml.js` to avoid repeated reads for same appeal type.
|
||||
|
||||
Validation:
|
||||
|
||||
- Manual code-path verification performed for modified files.
|
||||
- Syntax issues introduced during patching were corrected in `pages/api/auth/[...nextauth].js` and `components/myportal/servicebanner.js`.
|
||||
- Targeted `next lint --file ...` execution could not be completed in current environment due to existing local Node/Next CLI compatibility issue (`SyntaxError: Unexpected token ?` in Next bundled commander), unrelated to these functional edits.
|
||||
|
||||
Follow-ups:
|
||||
|
||||
- Branching guardrail alignment: split this stabilisation work into explicit refactor slices on short-lived branches created from `refactor` (one concern per slice), then merge back to `refactor`.
|
||||
- Re-run required validation gates (`npm run lint` in compatible environment + protected journey regression + EN/CY parity checks) before promotion.
|
||||
- Add/record PR minimum details (scope, files, risk notes, validation evidence, rollback plan) per branch rules.
|
||||
|
||||
---
|
||||
|
||||
### CL-22541: breadcrumb/back-link route-state helper extraction (`va/adv/ads/key`)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { connect } from "react-redux";
|
||||
import pLimit from "p-limit";
|
||||
|
||||
import { getIP } from "../../actions/core/logger";
|
||||
import { logInfo } from "../../actions/core/logger";
|
||||
import {
|
||||
getPersonalAccount,
|
||||
getPortalLogin
|
||||
@@ -184,6 +185,10 @@ export const getServerSideProps = wrapper.getServerSideProps(
|
||||
const thisSession = await getSession(ctx);
|
||||
|
||||
if (!thisSession) {
|
||||
logInfo("auth.guard.redirect", {
|
||||
route: "/myportal",
|
||||
reason: "missing_session"
|
||||
});
|
||||
return {
|
||||
redirect: { destination: "/auth/signin", permanent: false }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user