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

Implement Cloudflare Zero Trust Access Control for Notehost with JWT Verification #45

Open
jonatw opened this issue Sep 2, 2024 · 0 comments

Comments

@jonatw
Copy link

jonatw commented Sep 2, 2024

I was looking for a way to add access control using Cloudflare Zero Trust to restrict who can see my note via its Cloudflare Access Gateway.

I found a solution, but unfortunately, I am not very proficient in writing TypeScript or JavaScript. So I asked AI for help and developed my own implementation. The purpose of this issue is to see if someone can make a PR or to leave it here in case anyone else needs this solution.

However, this solution is not perfect for a workspace with many pages. If you don't include all the pages in the slugToPage section, there is a chance that your original Notion domain prefix might be revealed in the URL path. You could potentially use random strings or a UUID-like string to obscure the original Notion site domain, as it is still available to everyone by design.

Step 1

Follow the Readme instructions, using npx to generate your own code from the template.

Step 2

Continue to follow the instructions in the Readme to edit wrangler.toml and site-config.ts.

Step 3

Add the jose package to package.json to decode Cloudflare Access JWT tokens.

  "dependencies": {
    "notehost": "^1.0.33",
    "jose": "^5.8.0"
  },

After adding this package to package.json, run npm install to install it.

Step 4

Modify src/index.ts and adapt the code. Here is an example. Replace YOUR_TEAM_DOMAIN_PREFIX with your own Cloudflare team domain prefix.

import { initializeReverseProxy, reverseProxy } from 'notehost'
import { jwtVerify } from 'jose';
import { SITE_CONFIG } from './site-config'

initializeReverseProxy(SITE_CONFIG)

async function getPublicKey(kid: string): Promise<Uint8Array | null> {
  const response = await fetch('http://YOUR_TEAM_DOMAIN_PREFIX.cloudflareaccess.com/cdn-cgi/access/certs');
  const { keys } = await response.json();

  // Find the key that matches the "kid" (Key ID) in the JWT header
  const key = keys.find((k: any) => k.kid === kid);

  if (!key) return null;

  return new Uint8Array(Buffer.from(key.x5c[0], 'base64'));
}

async function verifyJWT(jwt: string): Promise<boolean> {
  try {
    const { header } = jwtVerify(jwt, async (header) => {
      const publicKey = await getPublicKey(header.kid);
      if (!publicKey) throw new Error('Public key not found');
      return publicKey;
    });

    // If no error is thrown, the JWT is valid
    return true;
  } catch (error) {
    console.error('JWT verification failed:', error);
    return false;
  }
}

export default {
  async fetch(request: Request): Promise<Response> {
    const jwt = request.headers.get('Cf-Access-Jwt-Assertion');

    if (!jwt) {
      return new Response('Unauthorized', { status: 401 });
    }

    const isValid = await verifyJWT(jwt);
    if (!isValid) {
      return new Response('Unauthorized', { status: 401 });
    }

    return await reverseProxy(request)
  },
}
Screenshot 2024-09-01 at 8 26 12 PM

Step 5

Deploy it to Cloudflare Workers, setup an self hosted application in zero trust using on the custom domain you deployed and verify if it works.

Screenshot 2024-09-01 at 8 31 28 PM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant