Skip to content
Open
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
71 changes: 55 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,77 @@ jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
name: Deploy Auth Inbox Worker
steps:
# 步骤 1: 检出代码
- name: Checkout Repository
uses: actions/checkout@v4

# 步骤 2: 设置 Node.js 环境
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20' # 根据您的项目需求选择 Node.js 版本
node-version: '20'

# 步骤 3: 安装依赖项
- name: Install Dependencies
run: npm install
- name: Enable Corepack
run: corepack enable

- name: Install Web Dependencies
run: npm --prefix web install
- name: Install Dependencies
run: corepack pnpm install --frozen-lockfile

- name: Build Web App
run: npm run build:web
run: corepack pnpm run build:web

# 步骤 4: 部署 Worker
- name: Deploy Backend for ${{ github.ref_name }}
- name: Prepare Wrangler Config
run: |
cp wrangler.toml.example.clear wrangler.toml

if [ -n "${{ vars.CF_WORKER_NAME }}" ]; then
sed -i "s/^name = .*/name = \"${{ vars.CF_WORKER_NAME }}\"/" wrangler.toml
fi

if [ -n "${{ vars.CF_D1_DATABASE_NAME }}" ]; then
sed -i "s/^database_name = .*/database_name = \"${{ vars.CF_D1_DATABASE_NAME }}\"/" wrangler.toml
fi

if [ -n "${{ vars.CF_D1_DATABASE_ID }}" ]; then
sed -i "s/^database_id = .*/database_id = \"${{ vars.CF_D1_DATABASE_ID }}\"/" wrangler.toml
fi

- name: Validate Wrangler Config
run: |
if grep -q '\*\*\*\*' wrangler.toml; then
echo "wrangler.toml still has placeholder values. Set repository vars CF_D1_DATABASE_ID (and optionally CF_WORKER_NAME/CF_D1_DATABASE_NAME)." >&2
exit 1
fi

# Write secrets.TOML to wrangler.toml file
echo '${{ secrets.TOML }}' > wrangler.toml
- name: Run ASSETS Smoke QA (local worker)
run: corepack pnpm run qa:assets-smoke
env:
QA_WRANGLER_CONFIG: wrangler.toml
QA_ADMIN_ID: admin
QA_ADMIN_PASSWORD: qa-password

- name: Deploy Backend for ${{ github.ref_name }}
run: corepack pnpm exec wrangler deploy --config wrangler.toml
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

- name: Sync Sensitive Worker Secrets (Optional)
run: |
put_secret () {
local key="$1"
local value="$2"
if [ -n "$value" ]; then
printf '%s' "$value" | corepack pnpm exec wrangler secret put "$key" --config wrangler.toml
fi
}

# Deploy the worker using Wrangler
npx wrangler deploy
put_secret "FrontEndAdminPassword" "${{ secrets.FRONTEND_ADMIN_PASSWORD }}"
put_secret "SESSION_SIGNING_KEY" "${{ secrets.SESSION_SIGNING_KEY }}"
put_secret "AI_API_KEY" "${{ secrets.AI_API_KEY }}"
put_secret "AI_FALLBACK_API_KEY" "${{ secrets.AI_FALLBACK_API_KEY }}"
put_secret "barkTokens" "${{ secrets.BARK_TOKENS }}"
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
22 changes: 11 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@

```bash
# Install
pnpm install # root (Worker) deps
corepack pnpm install # root (Worker) deps
# Dev
pnpm run dev # wrangler dev — local Worker on :8787
pnpm run dev:remote # wrangler dev --remote — use live D1
pnpm -C web run dev # Vite dev server on :5173 (proxies /api → :8787)
corepack pnpm run dev # wrangler dev — local Worker on :8787
corepack pnpm run dev:web # Vite dev server on :5173 (proxies /api → :8787)
# Build & deploy
pnpm run build:web # vite build → web/dist
pnpm run deploy # build:web + wrangler deploy
corepack pnpm run build:web # vite build → web/dist
corepack pnpm run deploy # build:web + wrangler deploy
# Misc
pnpm run test # vitest with @cloudflare/vitest-pool-workers
pnpm run cf-typegen # regenerate Worker type bindings
corepack pnpm run test # run vitest suite
corepack pnpm run qa:assets-smoke # local smoke test for React assets route
corepack pnpm run cf-typegen # regenerate Worker type bindings
```

Local setup:
```bash
cp wrangler.toml.example wrangler.toml # fill in database_id + secrets
pnpm wrangler d1 execute inbox-d1 --local --file=db/schema.sql
pnpm run build:web
pnpm run dev
corepack pnpm exec wrangler d1 execute inbox-d1 --local --file=db/schema.sql
corepack pnpm run build:web
corepack pnpm run dev
```

## Architecture Constraints
Expand Down
9 changes: 4 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ Package manager: **pnpm**. Never use npm commands.
## Key Commands

```bash
pnpm run dev # Worker backend :8787
pnpm -C web run dev # React frontend :5173 (proxy /api → :8787)
pnpm run dev:remote # backend with live D1
pnpm run deploy # build:web + wrangler deploy
pnpm run test
corepack pnpm run dev # Worker backend :8787
corepack pnpm run dev:web # React frontend :5173 (proxy /api → :8787)
corepack pnpm run deploy # build:web + wrangler deploy
corepack pnpm run test
```

## Architecture Boundaries — Never Violate
Expand Down
80 changes: 59 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[English](https://github.com/TooonyChen/AuthInbox/blob/main/README.md) | [简体中文](https://github.com/TooonyChen/AuthInbox/blob/main/README_CN.md)

**Auth Inbox** is an open-source, self-hosted email verification code platform built on [Cloudflare](https://cloudflare.com/)'s free serverless services. It automatically processes incoming emails, filters out promotional mail before hitting the AI, extracts verification codes or links, and stores them in a database. A modern React dashboard lets administrators review extracted codes, inspect raw emails, and render HTML email previews — all protected by Basic Auth.
**Auth Inbox** is an open-source, self-hosted email verification code platform built on [Cloudflare](https://cloudflare.com/)'s free serverless services. It automatically processes incoming emails, filters out promotional mail before hitting the AI, extracts verification codes or links, and stores them in a database. A modern React dashboard lets administrators review extracted codes, inspect raw emails, and render HTML email previews — protected with Basic Auth, session login, or both.

Don't want ads and spam in your main inbox? Need a bunch of alternative addresses for signups? Try this **secure**, **serverless**, **lightweight** service!

Expand Down Expand Up @@ -36,6 +36,7 @@ flowchart LR
- **Promotional Filter**: Detects and skips bulk/marketing emails via headers (`List-Unsubscribe`, `Precedence: bulk`, etc.) before calling the AI — saves tokens.
- **AI Code Extraction**: Uses Google Gemini (with OpenAI as fallback) to extract verification codes, links, and organization names.
- **Modern Dashboard**: React 18 + shadcn/ui interface with mail list, detail panel, and three tabs — Extracted, Raw Email, Rendered HTML preview.
- **Gmail-like Admin UI**: Inbox categories, search operators, keyboard shortcuts, configurable reading pane (`none/right/bottom`), and density modes (`default/comfortable/compact`).
- **Safe HTML Preview**: Email HTML is sanitized with DOMPurify and rendered in a sandboxed iframe.
- **One-click Copy**: Verification codes and links have copy buttons with toast confirmation.
- **Real-Time Notifications**: Optionally sends Bark push notifications when new codes arrive.
Expand Down Expand Up @@ -83,11 +84,18 @@ flowchart LR
In your forked repository, go to `Settings` → `Secrets and variables` → `Actions` and add:
- `CLOUDFLARE_ACCOUNT_ID`
- `CLOUDFLARE_API_TOKEN`
- `TOML` — use the [comment-free template](https://github.com/TooonyChen/AuthInbox/blob/main/wrangler.toml.example.clear) to avoid parse errors.
- `FRONTEND_ADMIN_PASSWORD`
- `SESSION_SIGNING_KEY` *(required for `AUTH_MODE=session` or `AUTH_MODE=both`)*
- `AI_API_KEY`
- *(Optional)* `AI_FALLBACK_API_KEY`
- *(Optional, Bark)* `BARK_TOKENS`

Then go to `Actions` → `Deploy Auth Inbox to Cloudflare Workers` → `Run workflow`.
Add these repository **Variables**:
- `CF_D1_DATABASE_ID` (required)
- *(Optional)* `CF_WORKER_NAME`
- *(Optional)* `CF_D1_DATABASE_NAME`

After success, **delete the workflow logs** to avoid leaking your config.
Then go to `Actions` → `Deploy Auth Inbox to Cloudflare Workers` → `Run workflow`.

3. Jump to [Set Email Forwarding](#3-set-email-forwarding-).

Expand All @@ -100,14 +108,16 @@ flowchart LR
```bash
git clone https://github.com/TooonyChen/AuthInbox.git
cd AuthInbox
pnpm install
corepack pnpm install
```

2. **Create D1 database**

```bash
pnpm wrangler d1 create inbox-d1
pnpm wrangler d1 execute inbox-d1 --remote --file=./db/schema.sql
corepack pnpm exec wrangler d1 create inbox-d1
corepack pnpm exec wrangler d1 execute inbox-d1 --remote --file=./db/schema.sql
corepack pnpm exec wrangler d1 execute inbox-d1 --remote --file=./db/migrations/001_gmail_ui.sql
corepack pnpm exec wrangler d1 execute inbox-d1 --remote --file=./db/migrations/002_auth_sessions.sql
```

Copy the `database_id` from the output.
Expand All @@ -118,26 +128,42 @@ flowchart LR
cp wrangler.toml.example wrangler.toml
```

Edit `wrangler.toml` — at minimum fill in:
Edit `wrangler.toml` — at minimum fill in non-sensitive values:

```toml
[vars]
FrontEndAdminID = "your-username"
FrontEndAdminPassword = "your-password"
UseBark = "false"
[vars]
FrontEndAdminID = "your-username"
AUTH_MODE = "session" # basic | session | both
UseBark = "false"

# AI provider — choose any compatible service
AI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai"
AI_API_KEY = "your-api-key"
AI_API_FORMAT = "openai"
AI_MODEL = "gemini-2.0-flash"
# AI provider — choose any compatible service
AI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai"
AI_API_FORMAT = "openai"
AI_MODEL = "gemini-2.0-flash"

[[d1_databases]]
binding = "DB"
database_name = "inbox-d1"
database_id = "<your-database-id>"
database_id = "<your-database-id>"
```

Set secrets (never store these in `wrangler.toml`):

```bash
corepack pnpm exec wrangler secret put FrontEndAdminPassword
corepack pnpm exec wrangler secret put SESSION_SIGNING_KEY
corepack pnpm exec wrangler secret put AI_API_KEY

# Optional (fallback provider and Bark)
corepack pnpm exec wrangler secret put AI_FALLBACK_API_KEY
corepack pnpm exec wrangler secret put barkTokens
```

Auth mode options:
- `AUTH_MODE = "session"`: modern in-app `/login` form (recommended)
- `AUTH_MODE = "basic"`: browser-native Basic Auth prompt only
- `AUTH_MODE = "both"`: accepts session cookie and Basic Auth

**`AI_API_FORMAT`** options:

| Value | Endpoint | Compatible providers |
Expand All @@ -158,21 +184,26 @@ flowchart LR
**Optional fallback provider** (triggered if primary fails after 3 retries):
```toml
# AI_FALLBACK_BASE_URL = "https://api.openai.com"
# AI_FALLBACK_API_KEY = "your-fallback-key"
# AI_FALLBACK_API_FORMAT = "openai"
# AI_FALLBACK_MODEL = "gpt-4o-mini"
```

Optional Bark vars: `barkTokens`, `barkUrl`.
Optional Bark vars: `barkUrl` (`barkTokens` should be configured as a secret).

4. **Build and deploy**

```bash
pnpm run deploy
corepack pnpm run deploy
```

Output: `https://auth-inbox.<your-subdomain>.workers.dev`

Optional local ASSETS smoke QA:

```bash
corepack pnpm run qa:assets-smoke
```

---

### 3. Set Email Forwarding ✉️
Expand All @@ -189,6 +220,13 @@ Go to [Cloudflare Dashboard](https://dash.cloudflare.com/) → `Websites` → `<

Visit your Worker URL, log in with the credentials you set, and start receiving verification emails.

### 5. Security Hardening for Cloudflare Free 🔐

1. Enable **Cloudflare Access** on your `workers.dev` (or custom domain) route, and keep app auth enabled (`AUTH_MODE = "session"` or `AUTH_MODE = "both"`).
2. Enable Cloudflare **Managed WAF ruleset** in your zone.
3. Add a **Rate Limiting** rule for `/api/*` (for example, protect against brute-force and scraping bursts).
4. Keep `workers.dev` disabled in production if you only use custom domain routes.

---

## License 📜
Expand Down
32 changes: 32 additions & 0 deletions db/migrations/001_gmail_ui.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- Gmail-like UI state and user settings for AuthInbox
CREATE TABLE IF NOT EXISTS mail_states (
raw_id INTEGER PRIMARY KEY,
is_read INTEGER NOT NULL DEFAULT 0,
is_starred INTEGER NOT NULL DEFAULT 0,
is_archived INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
is_important INTEGER NOT NULL DEFAULT 0,
is_muted INTEGER NOT NULL DEFAULT 0,
category TEXT,
labels_json TEXT,
snoozed_until DATETIME,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (raw_id) REFERENCES raw_mails(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS ui_settings (
id INTEGER PRIMARY KEY CHECK (id = 1),
density TEXT NOT NULL DEFAULT 'default',
reading_pane TEXT NOT NULL DEFAULT 'right',
theme TEXT NOT NULL DEFAULT 'dark',
shortcuts_enabled INTEGER NOT NULL DEFAULT 1,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

INSERT OR IGNORE INTO ui_settings (id, density, reading_pane, theme, shortcuts_enabled)
VALUES (1, 'default', 'right', 'dark', 1);

CREATE INDEX IF NOT EXISTS idx_mail_states_archived ON mail_states (is_archived, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_mail_states_deleted ON mail_states (is_deleted, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_mail_states_read ON mail_states (is_read, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_mail_states_starred ON mail_states (is_starred, updated_at DESC);
22 changes: 22 additions & 0 deletions db/migrations/002_auth_sessions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Session auth and login throttling tables for AuthInbox
CREATE TABLE IF NOT EXISTS auth_sessions (
session_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
csrf_token TEXT NOT NULL,
ip_hash TEXT,
user_agent_hash TEXT,
expires_at DATETIME NOT NULL,
revoked INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS auth_login_attempts (
ip_key TEXT PRIMARY KEY,
attempt_count INTEGER NOT NULL DEFAULT 0,
blocked_until DATETIME,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_auth_sessions_expires ON auth_sessions (expires_at);
CREATE INDEX IF NOT EXISTS idx_auth_sessions_revoked ON auth_sessions (revoked, expires_at);
Loading