瀏覽代碼

feat: add token authentication for /sync endpoint

Add SYNC_TOKEN secret to protect manual sync endpoint from unauthorized access.
Token can be provided via Authorization header or query parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kotoyuuko 3 天之前
父節點
當前提交
8e79055e15
共有 4 個文件被更改,包括 20 次插入2 次删除
  1. 1 0
      .dev.vars.example
  2. 5 2
      CLAUDE.md
  3. 1 0
      src/env.d.ts
  4. 13 0
      src/index.ts

+ 1 - 0
.dev.vars.example

@@ -4,3 +4,4 @@
 OXR_APP_ID=your_open_exchange_rates_app_id
 NOTION_TOKEN=your_notion_integration_token
 NOTION_DATABASE_ID=your_notion_database_id
+SYNC_TOKEN=your_sync_token

+ 5 - 2
CLAUDE.md

@@ -21,8 +21,10 @@ After `npm run dev`:
 # Test scheduled handler
 curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"
 
-# Manual sync
-curl "http://localhost:8787/sync"
+# Manual sync (requires token)
+curl "http://localhost:8787/sync?token=YOUR_SYNC_TOKEN"
+# or with Authorization header
+curl -H "Authorization: Bearer YOUR_SYNC_TOKEN" "http://localhost:8787/sync"
 
 # Currency conversion
 curl "http://localhost:8787/convert?from=USD&to=CNY&amount=100"
@@ -54,6 +56,7 @@ Set via `wrangler secret put <NAME>` or `.dev.vars` for local development:
 | `OXR_APP_ID` | Open Exchange Rates API App ID |
 | `NOTION_TOKEN` | Notion Integration Token |
 | `NOTION_DATABASE_ID` | Notion Database ID |
+| `SYNC_TOKEN` | Token for manual sync endpoint authorization |
 
 ### Notion Database Structure
 

+ 1 - 0
src/env.d.ts

@@ -6,6 +6,7 @@ declare global {
     OXR_APP_ID: string;
     NOTION_TOKEN: string;
     NOTION_DATABASE_ID: string;
+    SYNC_TOKEN: string;
   }
 }
 

+ 13 - 0
src/index.ts

@@ -312,6 +312,19 @@ export default {
 
     // Manual sync endpoint
     if (url.pathname === '/sync') {
+      // Validate token (from Authorization header or query parameter)
+      const authHeader = req.headers.get('Authorization');
+      const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
+      const queryToken = url.searchParams.get('token');
+      const providedToken = bearerToken || queryToken;
+
+      if (!env.SYNC_TOKEN || providedToken !== env.SYNC_TOKEN) {
+        return new Response(
+          JSON.stringify({ error: 'Unauthorized' }),
+          { status: 401, headers: { 'Content-Type': 'application/json' } }
+        );
+      }
+
       const result = await syncExchangeRates(env);
       return new Response(JSON.stringify(result, null, 2), {
         headers: { 'Content-Type': 'application/json' },