~/personal-dashboard — Deployment & Security

Architecture Overview

High-Level Architecture

                    Public Traffic

┌─────────────┐ ┌─────────────────────────┐ ┌────────────────────┐ │ Browser │ ──▶ │ Cloudflare Zero Trust │ ──▶ │ Vercel (Next.js) │ │ (Public) │ │ │ │ │ │ │ │ • Access authentication │ │ • proxy.ts │ │ │ │ • Origin secret header │ │ • Origin validation│ └─────────────┘ └─────────────────────────┘ └────────────────────┘

                     Local Traffic

┌─────────────┐ │ Browser │ ──▶ Vercel directly │ (Local) │ (localhost / LAN / Tailscale) │ │ │ │ proxy.ts bypasses authentication └─────────────┘

Security Layers

The application uses two independent layers of protection:

Cloudflare Zero Trust

Authenticates visitors before requests ever reach Vercel.

Prevents unauthorized users from accessing the application.

Origin Secret Verification (proxy.ts)

Cryptographically verifies that requests actually passed through Cloudflare.

Prevents direct access to the Vercel deployment.

Blocks forged Host headers and header spoofing attacks.

Domain & DNS

Purpose

Domain

Target

Public application

home.parthjain.tech

Cloudflare DNS → Vercel

Default Vercel deployment

personal-dashboard-*.vercel.app

Redirected by proxy.ts

Local development

localhost:3000

Direct access

[!IMPORTANT] The DNS record for home.parthjain.tech must be proxied (orange cloud enabled) so Cloudflare Zero Trust policies are enforced.

Cloudflare Zero Trust Configuration

Access Application

Create a Self-hosted Access application.

Setting

Value

Type

Self-hosted

Domain

home.parthjain.tech

Access Policy

Allow only your identity (email, One-Time PIN, etc.)

Transform Rule — Origin Secret Injection

This rule prevents attackers from bypassing Cloudflare and connecting directly to Vercel.

Location

Cloudflare Dashboard └── parthjain.tech └── Rules └── Transform Rules └── Modify Request Header

Rule

Setting

Value

Rule Name

Inject Vercel Origin Secret

Condition

Hostname equals home.parthjain.tech

Action

Set static request header

Header

Header

Value

X-Vercel-Origin-Secret

Same value as X_VERCEL_ORIGIN_SECRET in Vercel

[!IMPORTANT] Cloudflare does not allow custom headers beginning with cf-.

Use X-Vercel-Origin-Secret instead.

proxy.ts accepts both hyphenated and underscored header names for compatibility.

Vercel Configuration

Production Environment Variables

Variable

Purpose

DATABASE_URL

PostgreSQL connection string

INTERVALS_API_KEY

Intervals.icu API key

INTERVALS_ATHLETE_ID

Athlete ID (0 = authenticated user)

X_VERCEL_ORIGIN_SECRET

Shared origin verification secret

CF_ACCESS_CLIENT_ID

(Optional) Cloudflare Access service token

CF_ACCESS_CLIENT_SECRET

(Optional) Cloudflare Access service token secret

UPTIME_KUMA_URL

Prometheus endpoint

UPTIME_KUMA_API_KEY

API key (used as HTTP Basic password)

TELEMETRY_URL

Host telemetry endpoint

Deployment Protection

Vercel's built-in production deployment protection requires:

Vercel Pro

Advanced Deployment Protection (~$150/month add-on)

On the Hobby plan it only protects Preview Deployments, not Production.

Because of this limitation, Cloudflare Zero Trust is used instead, providing enterprise-grade authentication at no additional cost.

proxy.ts

Location

project-root/ └── proxy.ts

[!NOTE] Next.js 16 renamed middleware.ts to proxy.ts.

The exported function is proxy(), not middleware().

Request Flow

Incoming Request │ ▼

┌──────────────────────────────────────────────────────────────┐ │ Is the hostname local? │ │ │ │ localhost │ │ 127.0.0.1 │ │ *.local │ │ LAN IPs │ │ Tailscale │ │ Device hostnames │ │ │ │ YES → Allow request │ └──────────────────────────────────────────────────────────────┘ │ NO ▼

┌──────────────────────────────────────────────────────────────┐ │ Is hostname home.parthjain.tech? │ │ │ │ NO → Redirect (307) │ │ https://home.parthjain.tech/... │ └──────────────────────────────────────────────────────────────┘ │ YES ▼

┌──────────────────────────────────────────────────────────────┐ │ Is an origin secret configured? │ │ │ │ NO → Allow request │ │ (graceful setup/development mode) │ │ │ │ YES → Validate request header │ │ │ │ Match → Allow │ │ Mismatch → 403 Forbidden │ └──────────────────────────────────────────────────────────────┘

Accepted Request Headers

Checked in this order:

X-Vercel-Origin-Secret

X_Vercel_Origin_Secret

X-Origin-Secret

X_Origin_Secret

X-CF-Origin-Secret

Accepted Environment Variables

Checked in this order:

X_VERCEL_ORIGIN_SECRET

VERCEL_ORIGIN_SECRET

ORIGIN_SECRET

CF_ACCESS_ORIGIN_SECRET

Matcher

Runs on every request except static assets.

Excluded paths include:

_next/static _next/image favicon.ico

.css .js .svg .png .jpg .jpeg .gif .webp *.ico

Attack Scenarios

Scenario

Result

Visitor opens home.parthjain.tech

Cloudflare Zero Trust login

Visitor discovers .vercel.app URL

Redirected to canonical domain

Attacker spoofs Host: home.parthjain.tech

Origin secret validation fails → 403

Attacker spoofs host + secret header

Secret is unknown → 403

Bot or crawler accesses deployment

Redirect or 403

Access from localhost / LAN / Tailscale

Allowed immediately

Rotating the Origin Secret

If the shared secret is ever compromised:

Generate a new random secret.

Update both locations:

Vercel

X_VERCEL_ORIGIN_SECRET

Cloudflare Transform Rule

X-Vercel-Origin-Secret

Redeploy the Vercel application so the updated environment variable takes effect.

Security Summary

Layer

Purpose

Cloudflare Zero Trust

User authentication at the edge

Transform Rule

Injects trusted origin header

proxy.ts

Validates origin secret

Canonical Redirect

Prevents direct .vercel.app usage

Local Bypass

Preserves localhost/LAN/Tailscale development workflow