From 60971ac89090bcc276465bd86955560ac8ef9790 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 27 Jan 2025 03:12:05 -0500 Subject: [PATCH 1/4] PGOV-440: Basic auth for frontend application and config to make sure images work. --- src/frontend/lib/drupal.ts | 1 + src/frontend/middleware.ts | 33 ++++++++++++++++++++++++++++ src/frontend/next.config.js | 29 ++++++++++++++++++++++++ src/frontend/package.json | 8 ++++++- src/frontend/pages/api/basic-auth.ts | 9 ++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/frontend/middleware.ts create mode 100644 src/frontend/pages/api/basic-auth.ts diff --git a/src/frontend/lib/drupal.ts b/src/frontend/lib/drupal.ts index d37aca89..7fee231b 100644 --- a/src/frontend/lib/drupal.ts +++ b/src/frontend/lib/drupal.ts @@ -6,6 +6,7 @@ export const drupal = new DrupalClient( auth: { clientId: process.env.DRUPAL_CLIENT_ID, clientSecret: process.env.DRUPAL_CLIENT_SECRET, + 'Authorization': `Basic ${btoa(process.env.BASIC_AUTH_USER + ':' + process.env.BASIC_AUTH_PASSWORD)}` }, headers: { "Content-Type": "application/json", diff --git a/src/frontend/middleware.ts b/src/frontend/middleware.ts new file mode 100644 index 00000000..c28e0b2f --- /dev/null +++ b/src/frontend/middleware.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from "next/server"; + +export const config = { + matcher: ["/", "/index"], +}; + +export function middleware(req: NextRequest) { + // Getting the Pup IP from the request + const { ip } = req; + // console.log("Middleware IP:", ip); + const basicAuth = req.headers.get("authorization"); + const url = req.nextUrl; + + // Bypass the basic auth on a certain env variable and Pub IP + if ( + process.env.LOCAL_URL === "http://localhost:3000" + ) { + if (basicAuth) { + const authValue = basicAuth.split(" ")[1]; + const [user, pwd] = atob(authValue).split(":"); + + const validUser = process.env.BASIC_AUTH_USER; + const validPassWord = process.env.BASIC_AUTH_PASSWORD; + + if (user === validUser && pwd === validPassWord) { + return NextResponse.next(); + } + } + url.pathname = "/api/basicauth"; + + return NextResponse.rewrite(url); + } +} diff --git a/src/frontend/next.config.js b/src/frontend/next.config.js index 7c0edbfc..b862f691 100644 --- a/src/frontend/next.config.js +++ b/src/frontend/next.config.js @@ -1,5 +1,19 @@ const path = require("path"); + +const cspHeader = ` + default-src 'self'; + script-src 'self' 'unsafe-eval' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: https://pgov-cms.app.cloud.gov; + font-src 'self'; + object-src 'none'; + base-uri 'self'; + form-action 'self'; + frame-ancestors 'self' https://pgov-cms.app.cloud.gov; + upgrade-insecure-requests +`; + /** @type {import('next').NextConfig} */ const nextConfig = { images: { @@ -11,6 +25,21 @@ const nextConfig = { path.join(__dirname, "node_modules", "@uswds", "uswds", "packages"), ], }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'Content-Security-Policy', + value: cspHeader + .replace(/\s{2,}/g, " ") + .trim(), + }, + ], + }, + ] + } }; module.exports = nextConfig; diff --git a/src/frontend/package.json b/src/frontend/package.json index 381645e5..1d07a1e0 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -30,8 +30,10 @@ "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-next": "^14.2.2", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "husky": "^9.1.7", "lint-staged": "^15.2.10", "postcss": "^8.4.19", @@ -41,5 +43,9 @@ }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" + }, + "engines": { + "node": "22.11.0", + "npm": "10.9.2" } } diff --git a/src/frontend/pages/api/basic-auth.ts b/src/frontend/pages/api/basic-auth.ts new file mode 100644 index 00000000..47bd05a9 --- /dev/null +++ b/src/frontend/pages/api/basic-auth.ts @@ -0,0 +1,9 @@ +export async function GET(request: Request) { + console.log("GET /api/basicauth/route.ts"); + return new Response("Authentication Required!", { + status: 401, + headers: { + "WWW-Authenticate": "Basic realm='private_pages'", + }, + }); +} \ No newline at end of file From acd6cd3637b8ba430d124fae310c892a888f7c4c Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 27 Jan 2025 03:27:52 -0500 Subject: [PATCH 2/4] pgov-440: update path for auth endpoint. --- src/frontend/middleware.ts | 39 ++++++++++------------------ src/frontend/pages/api/basic-auth.ts | 16 +++++------- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/frontend/middleware.ts b/src/frontend/middleware.ts index c28e0b2f..e02c620e 100644 --- a/src/frontend/middleware.ts +++ b/src/frontend/middleware.ts @@ -1,33 +1,22 @@ -import { NextRequest, NextResponse } from "next/server"; +import { NextRequest, NextResponse } from 'next/server' export const config = { - matcher: ["/", "/index"], -}; - -export function middleware(req: NextRequest) { - // Getting the Pup IP from the request - const { ip } = req; - // console.log("Middleware IP:", ip); - const basicAuth = req.headers.get("authorization"); - const url = req.nextUrl; + matcher: ['/', '/index'], +} - // Bypass the basic auth on a certain env variable and Pub IP - if ( - process.env.LOCAL_URL === "http://localhost:3000" - ) { - if (basicAuth) { - const authValue = basicAuth.split(" ")[1]; - const [user, pwd] = atob(authValue).split(":"); +export default function middleware(req: NextRequest) { + const basicAuth = req.headers.get('authorization') + const url = req.nextUrl - const validUser = process.env.BASIC_AUTH_USER; - const validPassWord = process.env.BASIC_AUTH_PASSWORD; + if (basicAuth) { + const authValue = basicAuth.split(' ')[1] + const [user, pwd] = atob(authValue).split(':') - if (user === validUser && pwd === validPassWord) { - return NextResponse.next(); - } + if (user === 'admin' && pwd === 'civicactions') { + return NextResponse.next() } - url.pathname = "/api/basicauth"; - - return NextResponse.rewrite(url); } + url.pathname = '/api/basic-auth' + + return NextResponse.rewrite(url) } diff --git a/src/frontend/pages/api/basic-auth.ts b/src/frontend/pages/api/basic-auth.ts index 47bd05a9..154ba5fb 100644 --- a/src/frontend/pages/api/basic-auth.ts +++ b/src/frontend/pages/api/basic-auth.ts @@ -1,9 +1,7 @@ -export async function GET(request: Request) { - console.log("GET /api/basicauth/route.ts"); - return new Response("Authentication Required!", { - status: 401, - headers: { - "WWW-Authenticate": "Basic realm='private_pages'", - }, - }); -} \ No newline at end of file +import type { NextApiRequest, NextApiResponse } from 'next' + +export default function handler(_: NextApiRequest, res: NextApiResponse) { + res.setHeader('WWW-authenticate', 'Basic realm="Secure Area"') + res.statusCode = 401 + res.end(`Auth Required.`) +} From 1176919f3a475440f00f3570187880ea8c5bf8f0 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Sun, 2 Feb 2025 22:55:14 -0500 Subject: [PATCH 3/4] PGOV-440: Use basic auth creds from .env. --- src/frontend/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/middleware.ts b/src/frontend/middleware.ts index e02c620e..d3d445af 100644 --- a/src/frontend/middleware.ts +++ b/src/frontend/middleware.ts @@ -12,7 +12,7 @@ export default function middleware(req: NextRequest) { const authValue = basicAuth.split(' ')[1] const [user, pwd] = atob(authValue).split(':') - if (user === 'admin' && pwd === 'civicactions') { + if (user === process.env.BASIC_AUTH_USER && pwd === process.env.BASIC_AUTH_PASSWORD) { return NextResponse.next() } } From ff867a4fb77759b8dade9a84a59e04fb9218e85d Mon Sep 17 00:00:00 2001 From: Adrienne Date: Sun, 2 Feb 2025 22:58:25 -0500 Subject: [PATCH 4/4] PGOV-440: Basic auth creds to requesst header for api calls. --- src/frontend/lib/drupal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/lib/drupal.ts b/src/frontend/lib/drupal.ts index 7fee231b..269fd91a 100644 --- a/src/frontend/lib/drupal.ts +++ b/src/frontend/lib/drupal.ts @@ -5,11 +5,11 @@ export const drupal = new DrupalClient( { auth: { clientId: process.env.DRUPAL_CLIENT_ID, - clientSecret: process.env.DRUPAL_CLIENT_SECRET, - 'Authorization': `Basic ${btoa(process.env.BASIC_AUTH_USER + ':' + process.env.BASIC_AUTH_PASSWORD)}` + clientSecret: process.env.DRUPAL_CLIENT_SECRET }, headers: { "Content-Type": "application/json", + 'Authorization': `Basic ${btoa(process.env.BASIC_AUTH_USER + ':' + process.env.BASIC_AUTH_PASSWORD)}` }, }, );