Skip to content

feat: add dashboard auth, usage resets, and LAN phone access - #1

Merged
ctt062 merged 6 commits into
mainfrom
feat/dashboard-improvements
Jul 28, 2026
Merged

feat: add dashboard auth, usage resets, and LAN phone access#1
ctt062 merged 6 commits into
mainfrom
feat/dashboard-improvements

Conversation

@ctt062

@ctt062 ctt062 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What Changed

  • Gate the dashboard with Google sign-in on localhost/public origin and PIN auth on LAN, including Bearer tokens for the Vercel UI, remote-address PIN rate limits, and LaunchAgent login auto-start scripts
  • Surface per-provider usage reset windows and a This month range in Agent Deck, with collector and API updates for Cursor, Codex, Claude, and GitHub
  • Add Vercel static hosting plus Vite LAN proxy host handling, and align README auth origin and setup contracts

Risk Assessment

✅ Low: Prior review defects are addressed in source and no new material correctness or security failures were found in the dual-auth, Bearer, and LAN paths.

Testing

Ran the focused unit suite (all green), then exercised a live LAN-bound serve: Google login on loopback, PIN unlock on LAN/mobile, authenticated dashboard with This month selected and per-provider usage resets, plus API month-range payload and LaunchAgent script checks without installing system agents.

  • Evidence: Google sign-in gate on localhost (local file: /var/folders/cc/l65x8jd574bffvp15g7671j40000gn/T/no-mistakes-evidence/01KYKBJFEAH9DV9GEE5TCJ0F5M/login-google-loopback.png)
  • Evidence: PIN unlock on LAN IP (local file: /var/folders/cc/l65x8jd574bffvp15g7671j40000gn/T/no-mistakes-evidence/01KYKBJFEAH9DV9GEE5TCJ0F5M/login-pin-lan.png)
  • Evidence: PIN unlock mobile viewport (local file: /var/folders/cc/l65x8jd574bffvp15g7671j40000gn/T/no-mistakes-evidence/01KYKBJFEAH9DV9GEE5TCJ0F5M/login-pin-mobile.png)
  • Evidence: Dashboard with usage resets (7d default) (local file: /var/folders/cc/l65x8jd574bffvp15g7671j40000gn/T/no-mistakes-evidence/01KYKBJFEAH9DV9GEE5TCJ0F5M/dashboard-month-usage-resets.png)
  • Evidence: Dashboard This month + usage resets + Mac/GitHub (local file: /var/folders/cc/l65x8jd574bffvp15g7671j40000gn/T/no-mistakes-evidence/01KYKBJFEAH9DV9GEE5TCJ0F5M/dashboard-this-month.png)
Evidence: Auth mode + PIN login + month dashboard API transcript
{
  "authConfigLoopback": {
    "mode": "google",
    "configured": true,
    "clientId": "123456789-test.apps.googleusercontent.com",
    "allowedEmailsConfigured": true,
    "publicOrigin": "https://agent-dashboard-ctt.vercel.app",
    "pinConfigured": true
  },
  "authConfigLan": {
    "mode": "pin",
    "configured": true,
    "clientId": null,
    "allowedEmailsConfigured": true,
    "publicOrigin": "https://agent-dashboard-ctt.vercel.app",
    "pinConfigured": true
  },
  "pinLogin": {
    "user": {
      "email": "pin@local",
      "name": "PIN access",
      "picture": null,
      "method": "pin"
    },
    "tokenIssued": true
  },
  "dashboardMonth": {
    "generatedAt": "2026-07-28T03:42:42.720Z",
    "range": "month",
    "agents": [
      {
        "id": "cursor",
        "name": "Cursor",
        "score": 25990,
        "usageReset": {
          "ok": true,
          "windows": [
            {
              "label": "Billing cycle",
              "at": "2026-08-27T18:05:50.000Z",
              "usedPercent": 22.1,
              "note": "Cycle started Jul 28, 02:05"
            }
          ]
        }
      },
      {
        "id": "claude",
        "name": "Claude Code",
        "score": 9600,
        "usageReset": {
          "ok": true,
          "windows": [
            {
              "label": "5h session",
              "at": null,
              "note": "Rolling window · run `/usage` in Claude Code for exact time"
            },
            {
              "label": "Weekly",
              "at": null,
              "note": "Rolling 7-day cap · not calendar Monday"
            }
          ]
        }
      },
      {
        "id": "codex",
        "name": "Codex",
        "score": 1320,
        "usageReset": {
          "ok": true,
          "windows": [
            {
              "label": "Monthly",
              "at": "2026-08-27T03:42:42.000Z",
              "usedPercent": 0,
              "note": "Resets in ~720h · Plan: free"
            }
          ]
        }
      }
    ],
    "system": {
      "cpu": {
        "model": "Apple M4 Pro",
        "cores": 12,
        "utilization": 11.5
      },
      "memoryUtilization": 98.8
    },
    "githubContributionDays": 367
  }
}
Evidence: Month-range dashboard summary with usage resets
{
  "generatedAt": "2026-07-28T03:42:42.720Z",
  "range": "month",
  "agents": [
    {
      "id": "cursor",
      "name": "Cursor",
      "score": 25990,
      "usageReset": {
        "ok": true,
        "windows": [
          {
            "label": "Billing cycle",
            "at": "2026-08-27T18:05:50.000Z",
            "usedPercent": 22.1,
            "note": "Cycle started Jul 28, 02:05"
          }
        ]
      }
    },
    {
      "id": "claude",
      "name": "Claude Code",
      "score": 9600,
      "usageReset": {
        "ok": true,
        "windows": [
          {
            "label": "5h session",
            "at": null,
            "note": "Rolling window · run `/usage` in Claude Code for exact time"
          },
          {
            "label": "Weekly",
            "at": null,
            "note": "Rolling 7-day cap · not calendar Monday"
          }
        ]
      }
    },
    {
      "id": "codex",
      "name": "Codex",
      "score": 1320,
      "usageReset": {
        "ok": true,
        "windows": [
          {
            "label": "Monthly",
            "at": "2026-08-27T03:42:42.000Z",
            "usedPercent": 0,
            "note": "Resets in ~720h · Plan: free"
          }
        ]
      }
    }
  ],
  "system": {
    "cpu": {
      "model": "Apple M4 Pro",
      "cores": 12,
      "utilization": 11.5
    },
    "memoryUtilization": 98.8
  },
  "githubContributionDays": 367
}
Evidence: LaunchAgent auto-start intent (RunAtLoad/KeepAlive/LAN auth)
=== package scripts ===
{
  "setup": "npm run build && npm run launchagent:install",
  "launchagent": "bash scripts/install-launchagent.sh",
  "serveLan": "npm run build && HOST=0.0.0.0 npm start",
  "devLan": "concurrently -k -n api,web -c gray.bold,white.bold \"HOST=0.0.0.0 npm run dev:api\" \"VITE_HOST=0.0.0.0 npm run dev:web\""
}

=== install-launchagent key lines ===
26:# Prefer a built UI+API serve; build once if dist is missing.
51:HOST_VALUE="${HOST:-0.0.0.0}"
67:  HOST_VALUE="${HOST:-$HOST_VALUE}"
71:if [[ "$HOST_VALUE" == "0.0.0.0" || "$HOST_VALUE" == "::" ]]; then
72:  missing=()
73:  [[ -z "${GOOGLE_CLIENT_ID:-}" ]] && missing+=("GOOGLE_CLIENT_ID")
74:  [[ -z "${ALLOWED_EMAILS:-}" ]] && missing+=("ALLOWED_EMAILS")
75:  [[ -z "${DASHBOARD_PIN:-}" ]] && missing+=("DASHBOARD_PIN")
76:  if [[ "${#missing[@]}" -gt 0 ]]; then
77:    echo "LAN bind (HOST=${HOST_VALUE}) requires ${missing[*]} in ${ENV_FILE}." >&2
85:ENV_XML+="    <key>HOST</key>\n    <string>${HOST_VALUE}</string>\n"
87:if [[ -n "${GOOGLE_CLIENT_ID:-}" ]]; then
88:  ENV_XML+="    <key>GOOGLE_CLIENT_ID</key>\n    <string>${GOOGLE_CLIENT_ID}</string>\n"
90:if [[ -n "${ALLOWED_EMAILS:-}" ]]; then
91:  ENV_XML+="    <key>ALLOWED_EMAILS</key>\n    <string>${ALLOWED_EMAILS}</string>\n"
93:if [[ -n "${DASHBOARD_PIN:-}" ]]; then
94:  ENV_XML+="    <key>DASHBOARD_PIN</key>\n    <string>${DASHBOARD_PIN}</string>\n"
117:  <key>RunAtLoad</key>
119:  <key>KeepAlive</key>
144:if [[ "$HOST_VALUE" == "0.0.0.0" || "$HOST_VALUE" == "::" ]]; then
147:if [[ -z "${GOOGLE_CLIENT_ID:-}" ]]; then
148:  echo "Warning: GOOGLE_CLIENT_ID is not set. Add it to ${ENV_FILE} then re-run npm run setup."
Evidence: Server boot confirming Google + PIN auth modes
agent-dashboard API + UI on http://127.0.0.1:13847
Public UI origin (Google): https://agent-dashboard-ctt.vercel.app
Auth: Google sign-in on localhost and PUBLIC_ORIGIN.
Auth: PIN sign-in enabled for LAN / non-Google hosts.
Phone / LAN (same Wi-Fi, PIN sign-in):
  http://192.168.13.43:13847
  http://10.14.0.2:13847
Only use on a trusted network. This exposes local agent + Mac metrics.
security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 5 issues found → auto-fixed (3) ✅
  • 🚨 README.md:45 - Google Identity Services rejects raw IP JavaScript origins (only localhost IPs are exempt; hosts cannot be raw IPs). README and Login still tell users to add http://192.168.x.x:3847 as an authorized origin, while /api/dashboard requires Google sign-in and session cookies are host-scoped. Reachable failure: phone opens the LAN URL → login UI → GIS origin rejected → no session → API stays 401. Localhost sign-in cannot help the phone. Fix the product path (e.g. mDNS/HTTPS hostname Google accepts, localhost-only auth with a different phone gate, or document that phone viewing cannot use Google GIS on LAN IPs) and remove the invalid origin instructions.
  • ⚠️ server/index.ts:87 - refresh=1 forces collector re-read via collectRaw(force), but usage resets still go through getUsageResets() which returns usageResetsCache for up to 180s. Clicking Refresh can refresh agent/system/github data while Cursor/Codex/Claude reset windows and used% stay stale. Pass force through and bypass/clear the usage-reset cache on manual refresh.
  • ⚠️ server/collectors/usageResets.ts:166 - refreshCodexAccessToken may receive a rotated refresh_token, then ignores failures when writing ~/.codex/auth.json. If OpenAI invalidates the old refresh token and the atomic write fails, Agent Deck keeps a one-shot access token while Codex CLI is left with a dead refresh token. Fail closed: do not treat refresh as success unless persist succeeds, or avoid writing/rotating the user's Codex auth file and only use a read-only access token.
  • ⚠️ scripts/install-launchagent.sh:47 - LaunchAgent env parsing exports raw .env values, so GOOGLE_CLIENT_ID=&#34;....apps.googleusercontent.com&#34; keeps the quotes. dotenv strips quotes for npm start, but the plist does not, so the recommended npm run setup path can start with a bad client ID and break Google verify while manual npm start works. Strip matching single/double quotes when exporting.
  • ⚠️ scripts/install-launchagent.sh:37 - npm run setup defaults LaunchAgent HOST to 0.0.0.0, while ALLOWED_EMAILS remains optional (empty = any verified Google account). Recommended README setup shows an allowlist, but .env.example and auth code do not require it. On any origin where GIS works, any Google account can open Mac agent/system/GitHub metrics. Confirm whether allowlist should be required whenever LAN bind is enabled.

🔧 Fix: Add PIN LAN auth, Vercel UI, review fixes
3 issues (1 error, 2 warnings) still open:

  • 🚨 server/auth.ts:160 - Vercel UI → Mac API auth relies on cross-site cookie sessions (SameSite=None; Secure when Origin is PUBLIC_ORIGIN, plus credentials:&#39;include&#39; in apiFetch). Reachable failure: open https://agent-dashboard-ctt.vercel.app with VITE_API_BASE/runtime API pointing at an HTTPS Mac tunnel → Google GIS succeeds on Vercel → POST /api/auth/google sets a cookie for the API host → Safari/ITP (and other third-party cookie blocks) drop or refuse that cookie → subsequent /api/dashboard and /api/auth/me stay 401, so the accepted static-UI path cannot stay signed in. Same-origin Mac UI and LAN PIN paths are unaffected. Earliest durable boundary: return a bearer/session token in the login JSON and send it via Authorization from first-party storage on the Vercel origin (or same-site proxy the API under PUBLIC_ORIGIN), instead of depending on third-party cookies.
  • ⚠️ README.md:47 - Documented Google JS origins are only http://127.0.0.1:3847 and PUBLIC_ORIGIN, and Login setup copy matches that. CORS already allowlists http://127.0.0.1:5174 / http://localhost:5174, and the Download/npm run dev path opens port 5174. Reachable failure: follow clone → npm run dev → Google GIS rejects the 5174 origin → no credential → API stays 401. Add http://127.0.0.1:5174 and http://localhost:5174 (and localhost:3847 if you keep that host) to README and Login setup instructions.
  • ⚠️ server/auth.ts:313 - POST /api/auth/pin compares the PIN with no per-IP/per-session throttle or lockout. With LaunchAgent/serve:lan binding 0.0.0.0, any same-Wi-Fi client can brute-force a short DASHBOARD_PIN and get a full dashboard session. Add a small in-memory rate limit / backoff on failed PIN attempts before issuing the session cookie.

🔧 Fix: Add Bearer auth and PIN rate limits
2 warnings still open:

  • ⚠️ server/auth.ts:132 - PIN rate limiting keys off clientIp(), which trusts the first X-Forwarded-For hop, and attachSession enables trust proxy. On the intended LAN bind (HOST=0.0.0.0 / serve:lan) there is no trusted reverse proxy, so any same-Wi-Fi client can rotate X-Forwarded-For on POST /api/auth/pin and never hit the 5-attempt lockout. Reachable failure: brute-force DASHBOARD_PIN despite the new limiter. Earliest fix: key PIN attempts on req.socket.remoteAddress only (ignore XFF/req.ip for this check).
  • ⚠️ vite.config.ts:14 - npm run dev:lan proxies /api with changeOrigin: true and does not set X-Forwarded-Host. authModeForRequest prefers x-forwarded-host then Host, so phone GETs to /api/auth/config often see Host=127.0.0.1:3847 and no Origin, returning mode=google. Reachable failure: open the documented Vite LAN URL on a phone → Google GIS UI → raw IP origin rejected (or later POSTs get wrong_auth_mode when Origin is the LAN URL). serve:lan is unaffected. Fix: set changeOrigin: false, or enable xfwd/X-Forwarded-Host so the original LAN Host drives PIN mode.

🔧 Fix: Harden PIN rate limit and Vite LAN proxy
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • npm test (range, lan-intent, auth-mode, bearer-token, pin-rate-limit, types)
  • npm run build then HOST=0.0.0.0 PORT=13847 serve with Google/PIN env
  • curl http://127.0.0.1:13847/api/auth/config (google mode)
  • curl -H &#39;Host: 192.168.13.43:13847&#39; /api/auth/config (pin mode)
  • curl POST /api/auth/pin then GET /api/dashboard?range=month with Bearer token
  • Chrome CDP screenshots: Google login, LAN PIN login, mobile PIN, authenticated dashboard with This month + usage resets
  • bash -n scripts/install-launchagent.sh and inspect RunAtLoad/KeepAlive/HOST=0.0.0.0 + auth env requirements (no LaunchAgent install)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-dashboard Ready Ready Preview, Comment Jul 28, 2026 3:47am

@ctt062 ctt062 changed the title feat: improve Agent Deck ranges, charts, and production serve feat: improve Agent Deck with usage resets and month range Jul 28, 2026
@ctt062 ctt062 changed the title feat: improve Agent Deck with usage resets and month range feat: add LAN phone webapp, usage resets, and daily Agent Deck upgrades Jul 28, 2026
Bring the feature branch onto cleaned main after reverting mistaken commits, keeping per-provider usage resets, This month range, Local Mac LAN/phone mode, and related collector/UI hardening.
@ctt062 ctt062 changed the title feat: add LAN phone webapp, usage resets, and daily Agent Deck upgrades feat: add dashboard auth, usage resets, and LAN phone access Jul 28, 2026
@ctt062
ctt062 merged commit fa3661c into main Jul 28, 2026
3 checks passed
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

Successfully merging this pull request may close these issues.

1 participant