From 3e489d0771adda8c71d266735701d79b9955ddfe Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:36:16 -0400 Subject: [PATCH] feat: remove Every App dependency (#8) * remove Every App SDK and add auth modes for Cloudflare Access and local_noauth * align local dev auth defaults and normalize Access team domain * Apply suggestions from code review * restore local drizzle D1 URL helper * save * fix auth error mapping and document self-hosting setup * Tweak readme * improve auth config error UI and remove manifest link * fix team domain config validation and docs anchor --- .env.example | 43 ++--- README.md | 88 +++++++++- SELF_HOSTING_DOCKER.md | 11 +- compose.yaml | 5 +- every-app.jsonc | 5 - knip.jsonc | 2 +- opencode.jsonc | 4 - package.json | 26 ++- src/client/components/AuthConfigErrorCard.tsx | 52 ++++++ .../components/DefaultCatchBoundary.tsx | 21 ++- src/client/components/Sidebar.tsx | 16 +- src/client/lib/error-messages.ts | 7 + src/env.d.ts | 5 +- src/middleware/ensureUser.ts | 161 +++++++++++++++--- src/routes/__root.tsx | 55 +++--- src/routes/index.tsx | 40 ++++- src/routes/p/$projectId/route.tsx | 7 +- src/serverFunctions/middleware.ts | 2 - src/shared/error-codes.ts | 1 + src/types/vite-env.d.ts | 2 - vite.config.ts | 2 +- worker-configuration.d.ts | 10 +- wrangler.jsonc | 12 +- 23 files changed, 422 insertions(+), 155 deletions(-) delete mode 100644 every-app.jsonc create mode 100644 src/client/components/AuthConfigErrorCard.tsx diff --git a/.env.example b/.env.example index bff168a..26073fa 100644 --- a/.env.example +++ b/.env.example @@ -1,31 +1,22 @@ -# Every App / Gateway settings -VITE_APP_ID=open-seo - -# ------------------------------------------------------------- -# # --- Local development -- -# # Run everyapp app setup-local to automatically configure you .env.local if you've set up your gateway in cloudflare -# # This should be: https://every-app-gateway. -# VITE_GATEWAY_URL= -# GATEWAY_URL= - -# # See README.md for instructions for how to get this. +# ----------------------------------------------------------------------------- +# Core settings +# ----------------------------------------------------------------------------- +# Required for all modes. See README.md for setup instructions. # DATAFORSEO_API_KEY= -# # Choose local app port -# # PORT=3001 - -# ------------------------------------------------------------- -# --- Docker self-hosting only (local mode) --- -# See SELF_HOSTING_DOCKER.md -# Docker compose runs with BYPASS_GATEWAY_LOCAL_ONLY=true. -# Optional host/container port (defaults to 3001): +# Optional app port # PORT=3001 -# Optional in .env because compose already sets it. -# BYPASS_GATEWAY_LOCAL_ONLY=true -# Optional override (defaults to open-seo): -# VITE_APP_ID=open-seo -# # See README.md for instructions for how to get this. -# DATAFORSEO_API_KEY= +# ----------------------------------------------------------------------------- +# Auth mode +# ----------------------------------------------------------------------------- +# - cloudflare_access: validate Cloudflare Access JWTs (recommended for deploys) +# - local_noauth: local trusted mode with injected admin user (admin@localhost) +# - hosted: reserved for future hosted auth flow (not implemented yet) +# +# Defaults to cloudflare_access when unset. +# AUTH_MODE=cloudflare_access -# Legacy gateway auth keys are not needed in Docker self-host mode. +# Required when AUTH_MODE=cloudflare_access +# TEAM_DOMAIN=https://your-team.cloudflareaccess.com +# POLICY_AUD=your-cloudflare-access-aud-tag diff --git a/README.md b/README.md index 2399487..ee08f44 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ OpenSEO is an SEO tool for _the people_. If tools like Semrush or Ahrefs are too  +[](https://deploy.workers.cloudflare.com/?url=https://github.com/every-app/open-seo) + ## Table of Contents - [Why Use This](#why-use-this) @@ -12,7 +14,9 @@ OpenSEO is an SEO tool for _the people_. If tools like Semrush or Ahrefs are too - [Community](#community) - [Pricing / Costs (Free + API costs)](#pricing--costs) - [DataForSEO API Key Setup](#dataforseo-api-key-setup) -- [Docker Self Hosting](#docker-self-hosting) +- [Self-hosting](#self-hosting) + - [Cloudflare Deployment + Access Setup](#cloudflare-deployment--access-setup) + - [Docker Self Hosting](#docker-self-hosting) - [Local Development](#local-development) - [Contributing](#contributing) - [SEO API Cost Reference](#seo-api-cost-reference) @@ -80,6 +84,73 @@ printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64 - Docker self-hosting: `.env` - Local development: `.env.local` +## Self-hosting + +OpenSEO supports two self-hosting paths: + +- Cloudflare for hosting on the internet (Recommended). +- Docker for your homelab or local use. + +Use this quick guide: + +- Choose Cloudflare when: + - You've never used Docker before. + - You want a more SaaS like experience. + - You want to use it from multiple devices or with teammates. + - You want support for more powerful features in the future like sharing public links to reports or site audits rendering your websites javascript. +- Choose Docker when: + - You already have Docker installed and want to get setup most quickly. + - You have a homelab setup. + - You only want to use OpenSEO locally on one device. + +## Cloudflare Deployment + Access Setup + +You can use the Deploy button at the top of this README or run with Wrangler directly. + +### 1) Deploy the Worker + +Clicking this button will open a page to deploy OpenSEO in your Cloudflare account. + +[](https://deploy.workers.cloudflare.com/?url=https://github.com/every-app/open-seo) + +### 2) Set required environment variables + +OpenSEO needs these vars in production: + +- `DATAFORSEO_API_KEY`: base64-encoded `login:password` from DataForSEO. +- `AUTH_MODE=cloudflare_access` +- `TEAM_DOMAIN`: your Access team domain (for example `https://your-team.cloudflareaccess.com`). +- `POLICY_AUD`: Access Application Audience tag for your OpenSEO app. + +You can set plain vars and secrets with Wrangler: + +```sh +pnpm exec wrangler secret put DATAFORSEO_API_KEY +pnpm exec wrangler secret put POLICY_AUD +pnpm exec wrangler secret put TEAM_DOMAIN +pnpm exec wrangler secret put AUTH_MODE +``` + +You can also set these in Cloudflare Dashboard under Worker Settings. + +### 3) Configure Cloudflare Access + +In Cloudflare Zero Trust: + +1. Create an Access application for your OpenSEO route/domain. +2. Add policy rules for the identities/groups allowed to access OpenSEO. +3. Ensure requests to your Worker include `cf-access-jwt-assertion`. +4. Copy values into OpenSEO config: + - Access team domain -> `TEAM_DOMAIN` + - Access app AUD tag -> `POLICY_AUD` + +### 4) Validate setup + +- Visit your OpenSEO URL. +- You should be prompted by Cloudflare Access if not signed in. +- After sign-in, the app should load normally. +- If config is missing, OpenSEO shows an in-app setup error that links back to this section. + ## Docker Self Hosting Quickstart: @@ -113,11 +184,11 @@ cp .env.example .env.local pnpm install # Initialize local DB schema (required on a fresh machine) pnpm run db:migrate:local -# This runs in BYPASS_GATEWAY mode for local use and quick testing. +# This runs in local_noauth mode for local use and quick testing. pnpm dev:agents ``` -`pnpm dev` runs on `http://localhost:3001` by default (or `PORT` from `.env.local`). +`pnpm dev` runs on `http://localhost:3001` by default (or `PORT` from `.env.local`) in `AUTH_MODE=local_noauth`. `pnpm dev:agents` runs through [portless](https://github.com/vercel-labs/portless) at `http://open-seo.localhost:1355` by default. @@ -140,6 +211,17 @@ pnpm dev:agents - `pnpm dev:agents` mirrors output to `.logs/dev-server.log` (gitignored). - The log file is overwritten on each run. +### Auth modes + +- `AUTH_MODE=cloudflare_access` (default): validates Cloudflare Access JWTs (`cf-access-jwt-assertion`) using `TEAM_DOMAIN` + `POLICY_AUD`. +- `AUTH_MODE=local_noauth`: local trusted mode, no auth check, injects `admin@localhost`. +- `AUTH_MODE=hosted`: reserved for upcoming multi-tenant auth flow (not yet implemented). + +Local scripts (`pnpm dev` and `pnpm dev:agents`) set `AUTH_MODE=local_noauth` automatically. +Use `AUTH_MODE=cloudflare_access pnpm dev` when you specifically want to test Access validation locally. + +For Cloudflare deployments, ensure Cloudflare Access is enabled on your Worker route/domain and provide `TEAM_DOMAIN` + `POLICY_AUD` in environment variables. + ### Database Commands Generate migration: diff --git a/SELF_HOSTING_DOCKER.md b/SELF_HOSTING_DOCKER.md index bc0581e..13be8c1 100644 --- a/SELF_HOSTING_DOCKER.md +++ b/SELF_HOSTING_DOCKER.md @@ -1,8 +1,8 @@ # Docker Self-Hosting -This guide runs OpenSEO as a local service without Every App Gateway. +This guide runs OpenSEO as a local service. -In this mode, OpenSEO runs with `BYPASS_GATEWAY_LOCAL_ONLY=true`, so authentication and Gateway-managed user accounts are disabled. +In this mode, OpenSEO runs with `AUTH_MODE=local_noauth`, so request authentication is disabled and a local admin user (`admin@localhost`) is injected automatically. ## Prerequisites @@ -13,8 +13,8 @@ In this mode, OpenSEO runs with `BYPASS_GATEWAY_LOCAL_ONLY=true`, so authenticat This stack is local-first and uses dev runtimes to emulate Cloudflare Worker bindings. - Do not expose these ports directly to the public internet. -- There is no built-in Gateway auth in this mode. -- If you expose it beyond localhost, put it behind the same authentication layer you use for your other self-hosted services (or use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes)). +- There is no built-in auth check in this mode. +- If you expose it beyond localhost, put it behind the same authentication layer you use for your other self-hosted services (or use the [Cloudflare deployment path](./README.md#cloudflare-deployment--access-setup)). ## 1) Configure env values @@ -33,8 +33,7 @@ Required: Optional: - `PORT` (defaults to `3001`) -- `VITE_APP_ID` (defaults to `open-seo`) -- `BYPASS_GATEWAY_LOCAL_ONLY=true` (Docker compose already sets this) +- `AUTH_MODE=local_noauth` (Docker compose already sets this) ## 2) Start OpenSEO diff --git a/compose.yaml b/compose.yaml index 1b5231f..ee5031a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,10 +6,7 @@ services: working_dir: /app environment: - PORT=${PORT:-3001} - - BYPASS_GATEWAY_LOCAL_ONLY=true - - VITE_APP_ID=${VITE_APP_ID:-open-seo} - - VITE_GATEWAY_URL=${VITE_GATEWAY_URL:-http://localhost:${PORT:-3001}} - - GATEWAY_URL=${GATEWAY_URL:-http://localhost:${PORT:-3001}} + - AUTH_MODE=local_noauth - DATAFORSEO_API_KEY=${DATAFORSEO_API_KEY} - VITE_SHOW_DEVTOOLS=false command: diff --git a/every-app.jsonc b/every-app.jsonc deleted file mode 100644 index d10ab98..0000000 --- a/every-app.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "appId": "open-seo", - "displayName": "OpenSEO", - "description": "Own your SEO. Research keywords and competitors on your terms.", -} diff --git a/knip.jsonc b/knip.jsonc index a63d875..5916d00 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,5 +1,5 @@ { - "ignoreBinaries": ["everyapp"], + "ignoreBinaries": [], "entry": [ // Detect Tanstack Start Routes "src/router.tsx", diff --git a/opencode.jsonc b/opencode.jsonc index 3f15cc0..f79accb 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -5,9 +5,5 @@ "type": "local", "command": ["npx", "-y", "@upstash/context7-mcp"], }, - "every-app": { - "type": "local", - "command": ["npx", "-y", "@every-app/mcp"], - }, }, } diff --git a/package.json b/package.json index 27dbb15..c24badc 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vite dev", - "dev:agents": "mkdir -p .logs && BYPASS_GATEWAY_LOCAL_ONLY=true portless run vite dev 2>&1 | tee .logs/dev-server.log", + "dev": "AUTH_MODE=local_noauth vite dev", + "dev:agents": "mkdir -p .logs && AUTH_MODE=local_noauth portless run vite dev 2>&1 | tee .logs/dev-server.log", "build": "vite build && tsc --noEmit", "lint": "oxlint .", "lint:fix": "oxlint . --fix", @@ -16,13 +16,27 @@ "format:check": "prettier --check .", "format:write": "prettier . --write", "db:generate": "drizzle-kit generate", - "db:migrate:local": "drizzle-kit migrate", - "db:migrate:prod": "npx everyapp app remote-d1-shell -- drizzle-kit migrate --config=drizzle-prod.config.ts", - "db:studio:local": "drizzle-kit studio", - "db:studio:prod": "npx everyapp app remote-d1-shell -- drizzle-kit studio --config=drizzle-prod.config.ts", + "db:migrate:local": "wrangler d1 migrations apply DB --local", + "db:migrate:prod": "wrangler d1 migrations apply DB --remote", "knip": "knip", "ci": "prettier --check . && knip && tsc --noEmit && oxlint ." }, + "cloudflare": { + "bindings": { + "AUTH_MODE": { + "description": "Set to `cloudflare_access` for secured deployments behind Cloudflare Access, or `local_noauth` for trusted local self-host mode only." + }, + "TEAM_DOMAIN": { + "description": "Cloudflare Access team domain (for example `https://your-team.cloudflareaccess.com`) used to validate `cf-access-jwt-assertion` JWTs." + }, + "POLICY_AUD": { + "description": "Cloudflare Access Application Audience (AUD) tag for this Worker route/domain." + }, + "DATAFORSEO_API_KEY": { + "description": "Base64-encoded `login:password` for DataForSEO API access." + } + } + }, "dependencies": { "@every-app/sdk": "^0.1.13", "@tanstack/query-core": "^5.90.9", diff --git a/src/client/components/AuthConfigErrorCard.tsx b/src/client/components/AuthConfigErrorCard.tsx new file mode 100644 index 0000000..f08989d --- /dev/null +++ b/src/client/components/AuthConfigErrorCard.tsx @@ -0,0 +1,52 @@ +import { ShieldAlert } from "lucide-react"; + +const README_CLOUDFLARE_ACCESS_URL = + "https://github.com/every-app/open-seo#cloudflare-deployment--access-setup"; + +type AuthConfigErrorCardProps = { + message: string; + onRetry?: () => void; +}; + +export function AuthConfigErrorCard({ + message, + onRetry, +}: AuthConfigErrorCardProps) { + return ( +
+ This deployment is missing required Access settings for
+ AUTH_MODE=cloudflare_access. Configure{" "}
+ TEAM_DOMAIN and
+ POLICY_AUD, then retry.
+