Skip to content

Development -> main#2299

Merged
andrew-bierman merged 2 commits into
mainfrom
development
Apr 26, 2026
Merged

Development -> main#2299
andrew-bierman merged 2 commits into
mainfrom
development

Conversation

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@andrew-bierman andrew-bierman commented Apr 26, 2026

Description

Closes #

Type of change

  • 🐛 Bug fix
  • ✨ New feature
  • ♻️ Refactor / code improvement
  • 📝 Documentation update
  • 🔧 CI / configuration change
  • ⬆️ Dependency update
  • 🗄️ Database migration

Area(s) affected

  • Mobile app (apps/expo)
  • API / Backend (packages/api)
  • Landing page (apps/landing)
  • Guides site (apps/guides)
  • CI / CD (.github/)

Testing

  • Added / updated unit tests
  • Manually tested on iOS
  • Manually tested on Android
  • Manually tested on Web
  • API endpoints verified (e.g. curl or Postman)

Screenshots / recordings

Pre-merge checklist

  • bun format && bun lint passes with no errors
  • bun check-types passes with no errors
  • No new secrets or credentials are committed
  • Database migration included (if schema changed)
  • Feature flag added (if this is a new feature)
  • PR title follows conventional commits (feat:, fix:, chore:, etc.)

Summary by CodeRabbit

Release Notes

  • New Features
    • Cloudflare Access authentication now supported as an alternative authentication method. When enabled, users protected by Cloudflare Access are automatically authenticated without requiring manual token submission.
    • System intelligently falls back to token-based authentication when Cloudflare Access is not configured.
    • Added development mode indicator to clarify when Cloudflare Access protection is unavailable.

andrew-bierman and others added 2 commits April 25, 2026 18:01
Introduces cryptographic request verification on all /api/admin/* routes
using Cloudflare Access JWT assertions validated against the team JWKS
endpoint. When CF_ACCESS_TEAM_DOMAIN and CF_ACCESS_AUD are set, access
is gated exclusively on a valid signed JWT — no password fallthrough.

Also hardens the admin SPA authentication flow with improved concurrency
handling, cancellation safety, and CF Access identity forwarding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
feat: add Cloudflare Zero Trust authentication layer for admin routes
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b76e41e3-7343-4ff4-b898-82fee9a2e3f6

📥 Commits

Reviewing files that changed from the base of the PR and between 6b88739 and 61be211.

📒 Files selected for processing (9)
  • apps/admin/app/login/page.tsx
  • apps/admin/components/auth-guard.tsx
  • apps/admin/lib/api.ts
  • apps/admin/lib/cfAccess.ts
  • packages/api/src/middleware/cfAccess.ts
  • packages/api/src/middleware/index.ts
  • packages/api/src/routes/admin/index.ts
  • packages/api/src/utils/env-validation.ts
  • packages/api/wrangler.jsonc

📝 Walkthrough

Walkthrough

This PR integrates Cloudflare Access authentication into the admin application. The frontend detects CF Access availability and conditionally handles authentication, while the API client and backend now support CF Access JWT verification as an alternative to bearer token authentication.

Changes

Cohort / File(s) Summary
Admin Frontend CF Access Detection
apps/admin/app/login/page.tsx, apps/admin/components/auth-guard.tsx
Login page and auth guard now check CF Access availability on mount; login redirects to dashboard if CF Access is detected, while auth guard bypasses token requirements when CF Access is present.
Admin API Client Headers
apps/admin/lib/api.ts
API requests now dynamically include CF Access JWT via CF-Access-JWT-Assertion header when available, falling back to bearer token authentication.
CF Access Client Utilities
apps/admin/lib/cfAccess.ts
New module provides memoized access to CF Access identity endpoint (/cdn-cgi/access/get-identity), exposing functions to retrieve identity, JWT assertion, and detect CF Access presence.
API CF Access Verification Middleware
packages/api/src/middleware/cfAccess.ts
New middleware module verifies CF Access JWT tokens cryptographically using remote JWKS validation, extracting and returning the authenticated user's email identity.
Middleware Module Exports
packages/api/src/middleware/index.ts
Exports new CFAccessIdentity type and verifyCFAccessRequest function from the CF Access verification module.
Admin Route Authorization Updates
packages/api/src/routes/admin/index.ts
Admin JWT now includes fixed issuer and audience claims; authorization guard replaces email header checks with cryptographic CF Access token verification when configured, maintaining backward compatibility with bearer token auth.
Environment & Configuration
packages/api/src/utils/env-validation.ts, packages/api/wrangler.jsonc
Added optional environment variables CF_ACCESS_TEAM_DOMAIN and CF_ACCESS_AUD for CF Access configuration, with documentation in wrangler config.

Sequence Diagram

sequenceDiagram
    participant Client as Admin Client
    participant CFE as CF Access<br/>Identity Endpoint
    participant APIClient as API Client
    participant Backend as Admin API
    participant CFPK as CF JWKS<br/>Provider

    rect rgba(100, 150, 200, 0.5)
    Note over Client: App Initialization
    Client->>CFE: GET /cdn-cgi/access/get-identity
    CFE-->>Client: { email, name, jwt }
    Client->>Client: isBehindCFAccess() = true
    Client->>Client: Redirect to /dashboard
    end

    rect rgba(150, 100, 200, 0.5)
    Note over Client: Authenticated API Request
    Client->>APIClient: GET /api/admin/data
    APIClient->>CFE: Retrieve JWT assertion
    CFE-->>APIClient: jwt token
    APIClient->>Backend: GET /api/admin/data<br/>(CF-Access-JWT-Assertion: jwt)
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Backend: JWT Verification
    Backend->>CFPK: Fetch JWKS (cached)
    CFPK-->>Backend: Public keys
    Backend->>Backend: jwtVerify(token, keys,<br/>audience, issuer)
    Backend-->>Client: 200 OK + data
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

web

Suggested reviewers

  • Isthisanmol

Poem

🐰 A carrot-cloaked portal awaits,
Where CF Access unlocks the gates,
No tokens needed, just claims so true,
JWT whispers, "The identity's you!"
Hopping forward with crypto in tow! 🔐

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@andrew-bierman andrew-bierman marked this pull request as ready for review April 26, 2026 00:03
@github-actions github-actions Bot added the api label Apr 26, 2026
Copilot AI review requested due to automatic review settings April 26, 2026 00:03
@andrew-bierman andrew-bierman merged commit 68aa6cc into main Apr 26, 2026
12 of 16 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for API Unit Tests Coverage (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 75% 582 / 776
🔵 Statements 75% (🎯 65%) 582 / 776
🔵 Functions 95.83% 46 / 48
🔵 Branches 88.19% 269 / 305
File CoverageNo changed files found.
Generated in workflow #758 for commit 61be211 by the Vitest Coverage Report Action

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for Expo Unit Tests Coverage (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 81.38% 516 / 634
🔵 Statements 81.38% (🎯 75%) 516 / 634
🔵 Functions 92.85% 52 / 56
🔵 Branches 92.55% 199 / 215
File CoverageNo changed files found.
Generated in workflow #758 for commit 61be211 by the Vitest Coverage Report Action

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional Cloudflare Zero Trust / Access authentication for the admin surface by verifying CF Access JWT assertions server-side and teaching the admin SPA to forward the assertion when available.

Changes:

  • Add CF Access env vars and a Worker-side verifyCFAccessRequest middleware that verifies CF-Access-JWT-Assertion via remote JWKS.
  • Tighten admin session JWT verification by setting/validating issuer + audience, and switch admin auth to prefer CF Access when configured.
  • Add admin SPA utilities to detect CF Access, fetch /cdn-cgi/access/get-identity, and attach the assertion to admin API requests; adjust login/auth-guard flows accordingly.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/api/wrangler.jsonc Documents optional CF Access configuration for admin route protection.
packages/api/src/utils/env-validation.ts Introduces CF Access env variables in the validated API env schema.
packages/api/src/routes/admin/index.ts Implements CF Access-based admin guarding and strengthens admin JWT issuer/audience checks.
packages/api/src/middleware/index.ts Re-exports CF Access types/helpers from the middleware barrel.
packages/api/src/middleware/cfAccess.ts Adds CF Access JWT extraction + verification against the team JWKS.
apps/admin/lib/cfAccess.ts Adds client utilities to fetch/memoize CF Access identity + JWT assertion.
apps/admin/lib/api.ts Updates admin API client to send CF Access assertion header when present.
apps/admin/components/auth-guard.tsx Allows access when behind CF Access, otherwise enforces existing session token behavior.
apps/admin/app/login/page.tsx Auto-redirects away from login when behind CF Access; shows local-dev hint otherwise.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +25
// Cloudflare Zero Trust / Access (optional — enables CF Access JWT verification for admin routes)
CF_ACCESS_TEAM_DOMAIN: z.string().optional(), // e.g. "packrat.cloudflareaccess.com"
CF_ACCESS_AUD: z.string().optional(), // CF Access policy Application Audience tag
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CF_ACCESS_TEAM_DOMAIN/CF_ACCESS_AUD are independently optional, which makes it easy to misconfigure production: if only one is set, the API will silently fall back to local Basic/Bearer auth. Consider validating these as an all-or-nothing pair (e.g., schema refinement requiring both or neither), and validate CF_ACCESS_TEAM_DOMAIN as a full URL (including https://) to match verifyCFAccessRequest expectations; also update the example value in the comment accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +86
const env = getEnv();
const { CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD } = env;

const header = request.headers.get('authorization') ?? '';
if (header.startsWith('Bearer ')) {
return verifyAdminJwt(header.slice(7));
}
if (header.startsWith('Basic ')) {
return basicAuthGuard(request).authorized;
if (CF_ACCESS_TEAM_DOMAIN && CF_ACCESS_AUD) {
// CF Access configured: cryptographic JWT verification only, no fallthrough.
const cfIdentity = await verifyCFAccessRequest(request, CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD);
return cfIdentity !== null;
}

// CF Access not configured — local dev fallbacks only.
const header = request.headers.get('authorization') ?? '';
if (header.startsWith('Bearer ')) return verifyAdminJwt(header.slice(7));
if (header.startsWith('Basic ')) return basicAuthGuard(request).authorized;
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adminAuthGuard treats CF Access as enabled only when both env vars are set; if one is missing (or empty), it falls back to Bearer/Basic auth. That fallback is a security footgun in production because a partial/mistyped CF Access config can unintentionally re-enable local auth methods. Prefer failing closed (deny all admin requests or throw on startup) when either CF_ACCESS_TEAM_DOMAIN or CF_ACCESS_AUD is set without the other.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +80
if (CF_ACCESS_TEAM_DOMAIN && CF_ACCESS_AUD) {
// CF Access configured: cryptographic JWT verification only, no fallthrough.
const cfIdentity = await verifyCFAccessRequest(request, CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD);
return cfIdentity !== null;
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New CF Access auth behavior (strict mode when CF_ACCESS_TEAM_DOMAIN/CF_ACCESS_AUD are configured) isn’t covered by tests. Since packages/api/test/admin.test.ts already asserts admin auth behavior, it would be good to add cases for: (1) CF Access configured → Basic/Bearer rejected; (2) valid CF assertion header accepted (can be done by stubbing the JWKS fetch used by createRemoteJWKSet).

Copilot uses AI. Check for mistakes.
// - Local development: .dev.vars file (not committed to git)
//
// Cloudflare Zero Trust / Access (optional — set to enable JWT verification on /api/admin/*):
// CF_ACCESS_TEAM_DOMAIN=<team>.cloudflareaccess.com
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration comment suggests CF_ACCESS_TEAM_DOMAIN=<team>.cloudflareaccess.com, but verifyCFAccessRequest expects teamDomain to be a full URL (e.g. https://<team>.cloudflareaccess.com). Updating this example (and keeping it consistent with apiEnvSchema) will prevent misconfiguration that would otherwise cause all admin requests to be denied.

Suggested change
// CF_ACCESS_TEAM_DOMAIN=<team>.cloudflareaccess.com
// CF_ACCESS_TEAM_DOMAIN=https://<team>.cloudflareaccess.com

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants