Skip to content

Commit

Permalink
GAP-2521 | merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
ConorFayleAND committed Apr 22, 2024
2 parents 899e4fa + af9c234 commit 55d307a
Show file tree
Hide file tree
Showing 12 changed files with 1,426 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/admin/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,5 @@ export const ELASTIC_GRANT_PAGE_FIELDS = [
export const HEADERS = {
CORRELATION_ID: 'x-correlation-id',
} as const;

export const IS_PRODUCTION = process.env.NODE_ENV === 'production';
6 changes: 2 additions & 4 deletions packages/admin/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { GetServerSidePropsContext, NextApiRequest } from 'next';
import pino from 'pino';
import { HEADERS } from './constants';
import { HEADERS, IS_PRODUCTION } from './constants';

const log = pino({
browser: {
Expand All @@ -20,8 +20,6 @@ const log = pino({
},
});

const isProd = process.env.NODE_ENV === 'production';

const CONSOLE_COLOURS = {
BLACK: '\x1b[30m',
RED: '\x1b[31m',
Expand Down Expand Up @@ -67,7 +65,7 @@ const getLoggerWithLevel =
(level: LogLevel) => (logMessage: string, info?: object | Error) => {
const date = new Date();
const time = formatTime(date);
if (!isProd) {
if (!IS_PRODUCTION) {
console.log(
`[${time}] ` +
withLogColour(`${level.toUpperCase()}: ${logMessage}`, level)
Expand Down
5 changes: 4 additions & 1 deletion packages/applicant/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ SUB_PATH=/apply/applicant
USER_SERVICE_URL=http://localhost:8082
USER_TOKEN_NAME=user-service-token
V2_LOGIN_URL=http://localhost:8082/v2/login?redirectUrl=http://localhost:3000/apply/applicant/isAdmin
V2_LOGOUT_URL=http://localhost:8082/v2/logout
V2_LOGOUT_URL=http://localhost:8082/v2/logout
CSRF_SECRET_ARN=arnGoesHere
# only needed for local dev
AWS_REGION=eu-west-2
4 changes: 4 additions & 0 deletions packages/applicant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ yarn dev

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Running in Production Mode

To get a CSRF token the app requires AWS credentials - the `IS_PRODUCTION` flag in `constants.ts` determines whether an endpoint that allows the middleware to fetch credentials from the filesystem is enabled. To run the app in production mode locally you'll need to edit the value of this flag to `false` to allow the app to fetch credentials from the local filesystem.

## Learn More

To learn more about Next.js, take a look at the following resources:
Expand Down
2 changes: 2 additions & 0 deletions packages/applicant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
},
"dependencies": {
"@aws-crypto/client-node": "3.2.0",
"@aws-sdk/client-secrets-manager": "^3.554.0",
"@aws-sdk/credential-providers": "^3.556.0",
"@contentful/rich-text-from-markdown": "15.16.15",
"@contentful/rich-text-react-renderer": "^15.19.6",
"axios": "^0.27.2",
Expand Down
4 changes: 1 addition & 3 deletions packages/applicant/src/middleware.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ export function buildMiddlewareResponse(req: NextRequest, redirectUri: string) {
maxAge: 900,
});
}
const logResponse = getConditionalLogger(req, 'res');
logResponse(req, res);
return res;
}

Expand Down Expand Up @@ -313,7 +311,7 @@ export const middleware = async (req: NextRequest) => {
res = await authenticateRequest(req, res);
await csrfMiddleware(req, res);
} catch (err) {
logger.error(logger.utils.addErrorInfo(err, req));
logger.error('Middleware failure', logger.utils.addErrorInfo(err, req));
// redirect to homepage on any middleware error
res = buildMiddlewareResponse(req, HOST);
}
Expand Down
19 changes: 19 additions & 0 deletions packages/applicant/src/pages/api/aws-local.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { APIGlobalHandler } from '../../utils/apiErrorHandler';
import { fromIni } from '@aws-sdk/credential-providers';
import { IS_PRODUCTION } from '../../utils/constants';

const credentials = fromIni({ profile: 'default' });

// this handler is required to pull shared AWS credentials stored locally
// for use in middleware, as the middleware cannot access the filesystem
// directly
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (IS_PRODUCTION) return res.status(403).json('local dev use only');
res.status(200).json(await credentials());
}

const apiHandler = (req: NextApiRequest, res: NextApiResponse) =>
APIGlobalHandler(req, res, handler);

export default apiHandler;
2 changes: 2 additions & 0 deletions packages/applicant/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,5 @@ export const MQ_ORG_TYPES = {
export const HEADERS = {
CORRELATION_ID: 'x-correlation-id',
} as const;

export const IS_PRODUCTION = process.env.NODE_ENV === 'production';
2 changes: 1 addition & 1 deletion packages/applicant/src/utils/csrfMiddleware/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from 'next/server';
import {
createSecret,
getTokenFromRequest,
createToken,
verifyToken,
utoa,
atou,
createSecret,
} from './utils';

const METHODS_TO_IGNORE = ['GET', 'HEAD', 'OPTIONS'];
Expand Down
65 changes: 65 additions & 0 deletions packages/applicant/src/utils/csrfMiddleware/secret.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
GetSecretValueCommand,
SecretsManagerClient,
SecretsManagerClientConfig,
} from '@aws-sdk/client-secrets-manager';
import { atou } from './utils';
import { logger } from '../logger';
import { IS_PRODUCTION } from '../constants';

const HOST = process.env.HOST;
const CSRF_SECRET_ARN = process.env.CSRF_SECRET_ARN;
const AWS_REGION = process.env.AWS_REGION;

const fetchCredentials = async () => {
const response = await fetch(`${HOST}/api/aws-local`);
const json = await response.json();
return json;
};

let _client: SecretsManagerClient;

const getClient = async () => {
if (!_client) {
const clientConfig: SecretsManagerClientConfig = {};
if (!IS_PRODUCTION) {
clientConfig.credentials = await fetchCredentials();
clientConfig.region = AWS_REGION;
}
_client = new SecretsManagerClient(clientConfig);
}
return _client;
};

const fetchSecret = async () => {
const getSecretCommand = new GetSecretValueCommand({
SecretId: CSRF_SECRET_ARN,
});
try {
logger.info('Fetching CSRF secret');
const client = await getClient();
const response = await client.send(getSecretCommand);
return atou(JSON.parse(response.SecretString).csrfSecret);
} catch (e) {
// the aws sdk throws a string, not an error...
if (typeof e === 'string') throw new Error(e);
throw e;
}
};

class CsrfSecret {
private _lastFetch: number;
// 1 hour
private _ttl: number = 3600 * 1000;
private _value: Uint8Array;

async get() {
if (!this._value || Date.now() - this._lastFetch >= this._ttl) {
this._value = await fetchSecret();
this._lastFetch = Date.now();
}
return this._value;
}
}

export const csrfSecret = new CsrfSecret();
6 changes: 2 additions & 4 deletions packages/applicant/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { GetServerSidePropsContext, NextApiRequest } from 'next';
import pino from 'pino';
import { HEADERS } from './constants';
import { HEADERS, IS_PRODUCTION } from './constants';

const log = pino({
browser: {
Expand All @@ -20,8 +20,6 @@ const log = pino({
},
});

const isProd = process.env.NODE_ENV === 'production';

const CONSOLE_COLOURS = {
BLACK: '\x1b[30m',
RED: '\x1b[31m',
Expand Down Expand Up @@ -67,7 +65,7 @@ const getLoggerWithLevel =
(level: LogLevel) => (logMessage: string, info?: object | Error) => {
const date = new Date();
const time = formatTime(date);
if (!isProd) {
if (!IS_PRODUCTION) {
console.log(
`[${time}] ` +
withLogColour(`${level.toUpperCase()}: ${logMessage}`, level)
Expand Down
Loading

0 comments on commit 55d307a

Please sign in to comment.