From 9a20f0df264c0674c70738ea6facc5f88be06aba Mon Sep 17 00:00:00 2001 From: Lance Ewing Date: Fri, 8 Mar 2024 15:16:04 +0000 Subject: [PATCH] Adding functions folder, to test if this solution works. --- html/webapp/functions/_middleware.ts | 32 +++++++++++++++ html/webapp/functions/cfp_login.ts | 37 +++++++++++++++++ html/webapp/functions/constants.ts | 16 ++++++++ html/webapp/functions/template.ts | 61 ++++++++++++++++++++++++++++ html/webapp/functions/utils.ts | 13 ++++++ 5 files changed, 159 insertions(+) create mode 100644 html/webapp/functions/_middleware.ts create mode 100644 html/webapp/functions/cfp_login.ts create mode 100644 html/webapp/functions/constants.ts create mode 100644 html/webapp/functions/template.ts create mode 100644 html/webapp/functions/utils.ts diff --git a/html/webapp/functions/_middleware.ts b/html/webapp/functions/_middleware.ts new file mode 100644 index 0000000..559a2cc --- /dev/null +++ b/html/webapp/functions/_middleware.ts @@ -0,0 +1,32 @@ +import { CFP_ALLOWED_PATHS } from './constants'; +import { getCookieKeyValue } from './utils'; +import { getTemplate } from './template'; + +export async function onRequest(context: { + request: Request; + next: () => Promise; + env: { CFP_PASSWORD?: string }; +}): Promise { + const { request, next, env } = context; + const { pathname, searchParams } = new URL(request.url); + const { error } = Object.fromEntries(searchParams); + const cookie = request.headers.get('cookie') || ''; + const cookieKeyValue = await getCookieKeyValue(env.CFP_PASSWORD); + + if ( + cookie.includes(cookieKeyValue) || + CFP_ALLOWED_PATHS.includes(pathname) || + !env.CFP_PASSWORD + ) { + // Correct hash in cookie, allowed path, or no password set. + // Continue to next middleware. + return await next(); + } else { + // No cookie or incorrect hash in cookie. Redirect to login. + return new Response(getTemplate({ redirectPath: pathname, withError: error === '1' }), { + headers: { + 'content-type': 'text/html' + } + }); + } +} diff --git a/html/webapp/functions/cfp_login.ts b/html/webapp/functions/cfp_login.ts new file mode 100644 index 0000000..80724e3 --- /dev/null +++ b/html/webapp/functions/cfp_login.ts @@ -0,0 +1,37 @@ +import { CFP_COOKIE_MAX_AGE } from './constants'; +import { sha256, getCookieKeyValue } from './utils'; + +export async function onRequestPost(context: { + request: Request; + env: { CFP_PASSWORD?: string }; +}): Promise { + const { request, env } = context; + const body = await request.formData(); + const { password, redirect } = Object.fromEntries(body); + const hashedPassword = await sha256(password.toString()); + const hashedCfpPassword = await sha256(env.CFP_PASSWORD); + const redirectPath = redirect.toString() || '/'; + + if (hashedPassword === hashedCfpPassword) { + // Valid password. Redirect to home page and set cookie with auth hash. + const cookieKeyValue = await getCookieKeyValue(env.CFP_PASSWORD); + + return new Response('', { + status: 302, + headers: { + 'Set-Cookie': `${cookieKeyValue}; Max-Age=${CFP_COOKIE_MAX_AGE}; Path=/; HttpOnly; Secure`, + 'Cache-Control': 'no-cache', + Location: redirectPath + } + }); + } else { + // Invalid password. Redirect to login page with error. + return new Response('', { + status: 302, + headers: { + 'Cache-Control': 'no-cache', + Location: `${redirectPath}?error=1` + } + }); + } +} diff --git a/html/webapp/functions/constants.ts b/html/webapp/functions/constants.ts new file mode 100644 index 0000000..d29828c --- /dev/null +++ b/html/webapp/functions/constants.ts @@ -0,0 +1,16 @@ +/** + * Key for the auth cookie. + */ +export const CFP_COOKIE_KEY = 'CFP-Auth-Key'; + +/** + * Max age of the auth cookie in seconds. + * Default: 1 week. + */ +export const CFP_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; + +/** + * Paths that don't require authentication. + * The /cfp_login path must be included. + */ +export const CFP_ALLOWED_PATHS = ['/cfp_login']; diff --git a/html/webapp/functions/template.ts b/html/webapp/functions/template.ts new file mode 100644 index 0000000..b3f67ac --- /dev/null +++ b/html/webapp/functions/template.ts @@ -0,0 +1,61 @@ +export function getTemplate({ + redirectPath, + withError +}: { + redirectPath: string; + withError: boolean; +}): string { + return ` + + + + + + + Password Protected Site + + + + + + + + + +
+
+
+

Password

+

Please enter your password for this site.

+
+ ${withError ? `

Incorrect password, please try again.

` : ''} +
+ + + +
+
+
+ + + + `; +} diff --git a/html/webapp/functions/utils.ts b/html/webapp/functions/utils.ts new file mode 100644 index 0000000..d82bab7 --- /dev/null +++ b/html/webapp/functions/utils.ts @@ -0,0 +1,13 @@ +import { CFP_COOKIE_KEY } from './constants'; + +export async function sha256(str: string): Promise { + const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str)); + return Array.prototype.map + .call(new Uint8Array(buf), (x) => ('00' + x.toString(16)).slice(-2)) + .join(''); +} + +export async function getCookieKeyValue(password?: string): Promise { + const hash = await sha256(password); + return `${CFP_COOKIE_KEY}=${hash}`; +}