Skip to content

Commit

Permalink
add pw protection from Charca/cloudflare-pages-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
gpmayorga committed Apr 28, 2024
1 parent 0739f41 commit e549718
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
32 changes: 32 additions & 0 deletions tinlake-ui/functions/_middleware.ts
Original file line number Diff line number Diff line change
@@ -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<Response>;
env: { CFP_PASSWORD?: string };
}): Promise<Response> {
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 {

Check failure on line 24 in tinlake-ui/functions/_middleware.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-goerli

Unnecessary 'else' after 'return'

Check failure on line 24 in tinlake-ui/functions/_middleware.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-mainnet

Unnecessary 'else' after 'return'
// No cookie or incorrect hash in cookie. Redirect to login.
return new Response(getTemplate({ redirectPath: pathname, withError: error === '1' }), {
headers: {
'content-type': 'text/html'
}
});
}
}
37 changes: 37 additions & 0 deletions tinlake-ui/functions/cfp_login.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
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 {

Check failure on line 27 in tinlake-ui/functions/cfp_login.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-goerli

Unnecessary 'else' after 'return'

Check failure on line 27 in tinlake-ui/functions/cfp_login.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-mainnet

Unnecessary 'else' after 'return'
// Invalid password. Redirect to login page with error.
return new Response('', {
status: 302,
headers: {
'Cache-Control': 'no-cache',
Location: `${redirectPath}?error=1`
}
});
}
}
16 changes: 16 additions & 0 deletions tinlake-ui/functions/constants.ts
Original file line number Diff line number Diff line change
@@ -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'];
61 changes: 61 additions & 0 deletions tinlake-ui/functions/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export function getTemplate({
redirectPath,
withError
}: {
redirectPath: string;
withError: boolean;
}): string {
return `
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Password Protected Site</title>
<meta name="description" content="This site is password protected.">
<link rel="shortcut icon" href="https://picocss.com/favicon.ico">
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<style>
body > main {
display: flex;
flex-direction: column;
justify-content: center;
min-height: calc(100vh - 7rem);
padding: 1rem 0;
max-width: 600px;
}
.error {
background: white;
border-radius: 10px;
color: var(--del-color);
padding: 0.5em 1em;
}
h2 { color: var(--color-h2); }
</style>
</head>
<body>
<main>
<article>
<hgroup>
<h1>Password</h1>
<h2>Please enter your password for this site.</h2>
</hgroup>
${withError ? `<p class="error">Incorrect password, please try again.</p>` : ''}
<form method="post" action="/cfp_login">
<input type="hidden" name="redirect" value="${redirectPath}" />
<input type="password" name="password" placeholder="Password" aria-label="Password" autocomplete="current-password" required autofocus>
<button type="submit" class="contrast">Login</button>
</form>
</article>
</main>
</body>
</html>
`;
}
13 changes: 13 additions & 0 deletions tinlake-ui/functions/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CFP_COOKIE_KEY } from './constants';

export async function sha256(str: string): Promise<string> {
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))

Check failure on line 6 in tinlake-ui/functions/utils.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-goerli

Unexpected string concatenation

Check failure on line 6 in tinlake-ui/functions/utils.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy-to-mainnet

Unexpected string concatenation
.join('');
}

export async function getCookieKeyValue(password?: string): Promise<string> {
const hash = await sha256(password);
return `${CFP_COOKIE_KEY}=${hash}`;
}

0 comments on commit e549718

Please sign in to comment.