Skip to content

Commit

Permalink
AUT-3754: Make blocking code async
Browse files Browse the repository at this point in the history
- nonce generation
- session ID generation

Both "inspired" by changes in ipv-core-front
  • Loading branch information
ethanmills committed Feb 20, 2025
1 parent d959925 commit aabfad9
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 27 deletions.
1 change: 1 addition & 0 deletions @types/express/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ declare namespace Express {
t: TFunction;
csrfToken?: () => string;
log: pino.Logger;
generatedSessionId?: string;
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"qrcode": "^1.5.0",
"redis": "^4.6.13",
"uglify-js": "^3.14.5",
"uid-safe": "^2.1.5",
"uuid": "^11.0.2",
"xss": "^1.0.10",
"xstate": "^4.26.1"
Expand All @@ -124,6 +125,7 @@
"@types/sinon": "^17.0.3",
"@types/sinon-chai": "^3.2.8",
"@types/supertest": "^6.0.2",
"@types/uid-safe": "^2.1.5",
"@typescript-eslint/eslint-plugin": "^8.13.0",
"@typescript-eslint/parser": "^8.13.0",
"chai": "^4.3.6",
Expand Down
75 changes: 54 additions & 21 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,19 @@ import { ENVIRONMENT_NAME } from "./app.constants";
import { enterMfaRouter } from "./components/enter-mfa/enter-mfa-routes";
import { authCodeRouter } from "./components/auth-code/auth-code-routes";
import { resendMfaCodeRouter } from "./components/resend-mfa-code/resend-mfa-code-routes";
import { resendMfaCodeAccountCreationRouter } from "./components/account-creation/resend-mfa-code/resend-mfa-code-routes";
import {
resendMfaCodeAccountCreationRouter,
} from "./components/account-creation/resend-mfa-code/resend-mfa-code-routes";
import { resendEmailCodeRouter } from "./components/resend-email-code/resend-email-code-routes";
import { authorizeRouter } from "./components/authorize/authorize-routes";
import { signedOutRouter } from "./components/signed-out/signed-out-routes";
import {
getSessionIdMiddleware,
initialiseSessionMiddleware,
} from "./middleware/session-middleware";
import { getSessionIdMiddleware, initialiseSessionMiddleware } from "./middleware/session-middleware";
import { updatedTermsConditionsRouter } from "./components/updated-terms-conditions/updated-terms-conditions-routes";
import { signInOrCreateRouter } from "./components/sign-in-or-create/sign-in-or-create-routes";
import { accountNotFoundRouter } from "./components/account-not-found/account-not-found-routes";
import { resetPasswordCheckEmailRouter } from "./components/reset-password-check-email/reset-password-check-email-routes";
import {
resetPasswordCheckEmailRouter,
} from "./components/reset-password-check-email/reset-password-check-email-routes";
import { setLocalVarsMiddleware } from "./middleware/set-local-vars-middleware";
import { resetPasswordRouter } from "./components/reset-password/reset-password-routes";
import { resetPassword2FARouter } from "./components/reset-password-2fa-sms/reset-password-2fa-sms-routes";
Expand All @@ -65,11 +66,7 @@ import { checkYourEmailRouter } from "./components/check-your-email/check-your-e
import { securityCodeErrorRouter } from "./components/security-code-error/security-code-error-routes";
import { upliftJourneyRouter } from "./components/uplift-journey/uplift-journey-routes";
import { contactUsRouter } from "./components/contact-us/contact-us-routes";
import {
disconnectRedisClient,
getSessionCookieOptions,
getSessionStore,
} from "./config/session";
import { disconnectRedisClient, getSessionCookieOptions, getSessionStore } from "./config/session";
import session from "express-session";
import { proveIdentityRouter } from "./components/prove-identity/prove-identity-routes";
import { healthcheckRouter } from "./components/healthcheck/healthcheck-routes";
Expand All @@ -80,16 +77,30 @@ import { docCheckingAppRouter } from "./components/doc-checking-app/doc-checking
import { docCheckingAppCallbackRouter } from "./components/doc-checking-app-callback/doc-checking-app-callback-routes";
import { selectMFAOptionsRouter } from "./components/select-mfa-options/select-mfa-options-routes";
import { setupAuthenticatorAppRouter } from "./components/setup-authenticator-app/setup-authenticator-app-routes";
import { enterAuthenticatorAppCodeRouter } from "./components/enter-authenticator-app-code/enter-authenticator-app-code-routes";
import {
enterAuthenticatorAppCodeRouter,
} from "./components/enter-authenticator-app-code/enter-authenticator-app-code-routes";
import { cookiesRouter } from "./components/common/cookies/cookies-routes";
import { errorPageRouter } from "./components/common/errors/error-routes";
import { checkYourEmailSecurityCodesRouter } from "./components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-routes";
import { changeSecurityCodesConfirmationRouter } from "./components/account-recovery/change-security-codes-confirmation/change-security-codes-confirmation-routes";
import {
checkYourEmailSecurityCodesRouter,
} from "./components/account-recovery/check-your-email-security-codes/check-your-email-security-codes-routes";
import {
changeSecurityCodesConfirmationRouter,
} from "./components/account-recovery/change-security-codes-confirmation/change-security-codes-confirmation-routes";
import { outboundContactUsLinksMiddleware } from "./middleware/outbound-contact-us-links-middleware";
import { accountInterventionRouter } from "./components/account-intervention/password-reset-required/password-reset-required-router";
import { permanentlyBlockedRouter } from "./components/account-intervention/permanently-blocked/permanently-blocked-router";
import { temporarilyBlockedRouter } from "./components/account-intervention/temporarily-blocked/temporarily-blocked-router";
import { resetPassword2FAAuthAppRouter } from "./components/reset-password-2fa-auth-app/reset-password-2fa-auth-app-routes";
import {
accountInterventionRouter,
} from "./components/account-intervention/password-reset-required/password-reset-required-router";
import {
permanentlyBlockedRouter,
} from "./components/account-intervention/permanently-blocked/permanently-blocked-router";
import {
temporarilyBlockedRouter,
} from "./components/account-intervention/temporarily-blocked/temporarily-blocked-router";
import {
resetPassword2FAAuthAppRouter,
} from "./components/reset-password-2fa-auth-app/reset-password-2fa-auth-app-routes";
import { setGTM } from "./middleware/analytics-middleware";
import { setCurrentUrlMiddleware } from "./middleware/current-url-middleware";
import { getRedisConfig } from "./utils/redis";
Expand All @@ -101,7 +112,9 @@ import { Server } from "node:http";
import { getAnalyticsPropertiesMiddleware } from "./middleware/get-analytics-properties-middleware";
import { ipvCallbackRouter } from "./components/ipv-callback/ipv-callback-routes";
import { mfaResetWithIpvRouter } from "./components/mfa-reset-with-ipv/mfa-reset-with-ipv-routes";
import { asyncHandler } from "./utils/async";
import { environmentBannerMiddleware } from "./middleware/environment-banner-middleware";
import UID from "uid-safe";

const APP_VIEWS = [
path.join(__dirname, "components"),
Expand Down Expand Up @@ -186,7 +199,7 @@ async function createApp(): Promise<express.Application> {

app.use("/public", express.static(path.join(__dirname, "public")));
app.set("view engine", configureNunjucks(app, APP_VIEWS));
app.use(setLocalVarsMiddleware);
app.use(asyncHandler(setLocalVarsMiddleware));
app.use(setGTM);

await i18next
Expand All @@ -201,9 +214,24 @@ async function createApp(): Promise<express.Application> {
app.use(i18nextMiddleware.handle(i18next));
app.use(helmet(helmetConfiguration));

app.use(cookieParser());

const SESSION_COOKIE_NAME = "aps";
// Generate a new session ID asynchronously if no session cookie
// `express-session` does not support async session ID generation
// https://github.com/expressjs/session/issues/107
app.use(
asyncHandler(async (req, res, next) => {
if (!req.cookies?.[SESSION_COOKIE_NAME]) {
req.generatedSessionId = await UID(24);
}
next();
})
);

app.use(
session({
name: "aps",
name: SESSION_COOKIE_NAME,
store: getSessionStore(await getRedisConfig()),
saveUninitialized: false,
secret: getSessionSecret(),
Expand All @@ -214,10 +242,15 @@ async function createApp(): Promise<express.Application> {
getSessionExpiry(),
getSessionSecret()
),
// Use the newly generated session ID, or fall back to the default behaviour
genid: (req) => {
const sessionId = req.generatedSessionId || UID.sync(24);
delete req.generatedSessionId;
return sessionId;
},
})
);

app.use(cookieParser());
app.use(csurf({ cookie: getCSRFCookieOptions(isProduction) }));

app.use(channelMiddleware);
Expand Down
6 changes: 3 additions & 3 deletions src/middleware/set-local-vars-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
} from "../config";
import { generateNonce } from "../utils/strings";

export function setLocalVarsMiddleware(
export async function setLocalVarsMiddleware(
req: Request,
res: Response,
next: NextFunction
): void {
res.locals.scriptNonce = generateNonce();
): Promise<void> {
res.locals.scriptNonce = await generateNonce();
res.locals.accountManagementUrl = getAccountManagementUrl();
res.locals.analyticsCookieDomain = getAnalyticsCookieDomain();
res.locals.languageToggleEnabled = getLanguageToggleEnabled();
Expand Down
7 changes: 5 additions & 2 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { randomBytes } from "crypto";
import { promisify } from "util";
import xss from "xss";

export function containsNumber(value: string): boolean {
Expand All @@ -13,8 +14,10 @@ export function redactPhoneNumber(value: string): string | undefined {
return value ? value.trim().slice(value.length - 4) : undefined;
}

export function generateNonce(): string {
return randomBytes(16).toString("hex");
const asyncRandomBytes = promisify(randomBytes);

export async function generateNonce(): Promise<string> {
return (await asyncRandomBytes(16)).toString("hex");
}

export function sanitize(value: string): string {
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,11 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==

"@types/uid-safe@^2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@types/uid-safe/-/uid-safe-2.1.5.tgz#32b27c6f1a9022a29762cf7c4350891dd7b154e2"
integrity sha512-RwEfbxqXKEay2b5p8QQVllfnMbVPUZChiKKZ2M6+OSRRmvr4HTCCUZTWhr/QlmrMnNE0ViNBBbP1+5plF9OGRw==

"@types/uuid@^9.0.1":
version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
Expand Down Expand Up @@ -6571,7 +6576,7 @@ uglify-js@^3.14.5:
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f"
integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==

[email protected], uid-safe@~2.1.5:
[email protected], uid-safe@^2.1.5, uid-safe@~2.1.5:
version "2.1.5"
resolved "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz"
integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
Expand Down

0 comments on commit aabfad9

Please sign in to comment.