From e54971881c7640843cdc763822b596b297e96bc0 Mon Sep 17 00:00:00 2001 From: Guillermo Perez Date: Sun, 28 Apr 2024 12:55:00 +0200 Subject: [PATCH 1/3] add pw protection from Charca/cloudflare-pages-auth --- tinlake-ui/functions/_middleware.ts | 32 +++++++++++++++ tinlake-ui/functions/cfp_login.ts | 37 +++++++++++++++++ tinlake-ui/functions/constants.ts | 16 ++++++++ tinlake-ui/functions/template.ts | 61 +++++++++++++++++++++++++++++ tinlake-ui/functions/utils.ts | 13 ++++++ 5 files changed, 159 insertions(+) create mode 100644 tinlake-ui/functions/_middleware.ts create mode 100644 tinlake-ui/functions/cfp_login.ts create mode 100644 tinlake-ui/functions/constants.ts create mode 100644 tinlake-ui/functions/template.ts create mode 100644 tinlake-ui/functions/utils.ts diff --git a/tinlake-ui/functions/_middleware.ts b/tinlake-ui/functions/_middleware.ts new file mode 100644 index 00000000..559a2ccd --- /dev/null +++ b/tinlake-ui/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/tinlake-ui/functions/cfp_login.ts b/tinlake-ui/functions/cfp_login.ts new file mode 100644 index 00000000..80724e33 --- /dev/null +++ b/tinlake-ui/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/tinlake-ui/functions/constants.ts b/tinlake-ui/functions/constants.ts new file mode 100644 index 00000000..d29828c0 --- /dev/null +++ b/tinlake-ui/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/tinlake-ui/functions/template.ts b/tinlake-ui/functions/template.ts new file mode 100644 index 00000000..b3f67ac0 --- /dev/null +++ b/tinlake-ui/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/tinlake-ui/functions/utils.ts b/tinlake-ui/functions/utils.ts new file mode 100644 index 00000000..d82bab77 --- /dev/null +++ b/tinlake-ui/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}`; +} From e33b754038a5fa301ca2ed9335f4163e0fd2eaa1 Mon Sep 17 00:00:00 2001 From: Guillermo Perez Date: Sun, 28 Apr 2024 13:26:28 +0200 Subject: [PATCH 2/3] check if pw set --- tinlake-ui/functions/cfp_login.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tinlake-ui/functions/cfp_login.ts b/tinlake-ui/functions/cfp_login.ts index 80724e33..492e7c63 100644 --- a/tinlake-ui/functions/cfp_login.ts +++ b/tinlake-ui/functions/cfp_login.ts @@ -8,6 +8,9 @@ export async function onRequestPost(context: { const { request, env } = context; const body = await request.formData(); const { password, redirect } = Object.fromEntries(body); + if (!env.CFP_PASSWORD) { + throw new Error("CFP_PASSWORD is not set in the environment variables."); + } const hashedPassword = await sha256(password.toString()); const hashedCfpPassword = await sha256(env.CFP_PASSWORD); const redirectPath = redirect.toString() || '/'; From 44908d4948b43a36d043c83d24bda5e9f6c23338 Mon Sep 17 00:00:00 2001 From: Guillermo Perez Date: Sun, 28 Apr 2024 14:52:07 +0200 Subject: [PATCH 3/3] try plain text --- tinlake-ui/functions/cfp_login.ts | 37 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tinlake-ui/functions/cfp_login.ts b/tinlake-ui/functions/cfp_login.ts index 492e7c63..cbc23f41 100644 --- a/tinlake-ui/functions/cfp_login.ts +++ b/tinlake-ui/functions/cfp_login.ts @@ -1,40 +1,37 @@ -import { CFP_COOKIE_MAX_AGE } from './constants'; -import { sha256, getCookieKeyValue } from './utils'; +import { CFP_COOKIE_MAX_AGE } from './constants' +import { getCookieKeyValue, sha256 } 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); +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) if (!env.CFP_PASSWORD) { - throw new Error("CFP_PASSWORD is not set in the environment variables."); + throw new Error('CFP_PASSWORD is not set in the environment variables.') } - const hashedPassword = await sha256(password.toString()); - const hashedCfpPassword = await sha256(env.CFP_PASSWORD); - const redirectPath = redirect.toString() || '/'; + const hashedPassword = await sha256(password.toString()) + const hashedCfpPassword = await sha256('Centrifuge') + 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); + 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 - } - }); + 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` - } - }); + Location: `${redirectPath}?error=1`, + }, + }) } }