Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pw protection from Charca/cloudflare-pages-auth #48

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-mainnet

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-goerli

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 { getCookieKeyValue, sha256 } 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)
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('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)

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-mainnet

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-goerli

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-mainnet

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-goerli

Unexpected string concatenation
.join('');
}

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