diff --git a/lib/secrets.dart b/lib/secrets.dart index eca3095..2e46166 100644 --- a/lib/secrets.dart +++ b/lib/secrets.dart @@ -33,6 +33,23 @@ class Secrets { /// AI Proxy サーバーの解析エンドポイントURL static const String aiProxyAnalyzeUrl = '$aiProxyBaseUrl/analyze'; + /// AI Proxy 認証トークン + /// Proxy経由モード(useProxy=true)時に使用するBearer Token + /// ビルド時の上書き: --dart-define=PROXY_AUTH_TOKEN=... + static const String _proxyAuthTokenEnv = String.fromEnvironment( + 'PROXY_AUTH_TOKEN', + defaultValue: '', + ); + + /// 実際に使用するProxy認証トークン + /// 優先順位: 環境変数 > ローカル設定ファイル + static String get proxyAuthToken { + if (_proxyAuthTokenEnv.isNotEmpty) { + return _proxyAuthTokenEnv; + } + return local.SecretsLocal.proxyAuthToken; + } + /// Gemini API Key /// ⚠️ セキュリティのため、defaultValueは空です /// ローカル開発時: lib/secrets.local.dart を作成してキーを設定 diff --git a/lib/secrets.local.dart.example b/lib/secrets.local.dart.example index 1e6a863..09d86ed 100644 --- a/lib/secrets.local.dart.example +++ b/lib/secrets.local.dart.example @@ -15,4 +15,7 @@ class SecretsLocal { /// ローカル開発時のAI Proxy URL(オプション) static const String aiProxyBaseUrl = 'http://100.76.7.3:8080'; + + /// AI Proxy認証トークン(Proxy経由モード時に使用) + static const String proxyAuthToken = 'YOUR_PROXY_AUTH_TOKEN_HERE'; } diff --git a/lib/services/gemini_service.dart b/lib/services/gemini_service.dart index 627e13b..80f8a54 100644 --- a/lib/services/gemini_service.dart +++ b/lib/services/gemini_service.dart @@ -174,10 +174,15 @@ $extractedText debugPrint('Calling Proxy: $_proxyUrl'); - // 5. 送信 + // 5. 送信(Bearer Token認証付き) + final headers = { + "Content-Type": "application/json", + if (Secrets.proxyAuthToken.isNotEmpty) + "Authorization": "Bearer ${Secrets.proxyAuthToken}", + }; final response = await http.post( Uri.parse(_proxyUrl), - headers: {"Content-Type": "application/json"}, + headers: headers, body: requestBody, ).timeout(const Duration(seconds: 60)); // 拡張: 60秒 (画像サイズ最適化済み) diff --git a/tools/proxy/server.js b/tools/proxy/server.js index b37b916..4bef792 100644 --- a/tools/proxy/server.js +++ b/tools/proxy/server.js @@ -1,19 +1,46 @@ const express = require('express'); const bodyParser = require('body-parser'); -const cors = require('cors'); const { GoogleGenerativeAI } = require('@google/generative-ai'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 8080; const API_KEY = process.env.GEMINI_API_KEY; +const AUTH_TOKEN = process.env.PROXY_AUTH_TOKEN; // Rate Limiting (Simple In-Memory) const DAILY_LIMIT = parseInt(process.env.DAILY_LIMIT || '50', 10); const usageStore = {}; // { deviceId: { date: 'YYYY-MM-DD', count: 0 } } -app.use(cors()); -app.use(bodyParser.json({ limit: '10mb' })); // Allow large image payloads +// Authentication Middleware (skip for /health) +function authMiddleware(req, res, next) { + if (!AUTH_TOKEN) { + // If no token configured, skip auth (backward compatibility) + console.warn('[Auth] WARNING: PROXY_AUTH_TOKEN is not set. Authentication disabled.'); + return next(); + } + + const authHeader = req.headers['authorization']; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + console.log(`[Auth] Rejected: Missing or invalid Authorization header`); + return res.status(401).json({ success: false, error: 'Authentication required' }); + } + + const token = authHeader.substring(7); // Remove 'Bearer ' prefix + if (token !== AUTH_TOKEN) { + console.log(`[Auth] Rejected: Invalid token`); + return res.status(403).json({ success: false, error: 'Invalid authentication token' }); + } + + next(); +} + +// Global middleware: Auth first (skip /health), then body parser +app.use((req, res, next) => { + if (req.path === '/health') return next(); + authMiddleware(req, res, next); +}); +app.use(bodyParser.json({ limit: '10mb' })); // Gemini Client const genAI = new GoogleGenerativeAI(API_KEY); @@ -48,7 +75,7 @@ function checkRateLimit(deviceId) { }; } -// API Endpoint +// API Endpoint (authentication enforced by global middleware) app.post('/analyze', async (req, res) => { const { device_id, images, prompt } = req.body; @@ -126,4 +153,6 @@ app.get('/health', (req, res) => { app.listen(PORT, () => { console.log(`Proxy Server running on port ${PORT}`); if (!API_KEY) console.error('WARNING: GEMINI_API_KEY is not set!'); + if (!AUTH_TOKEN) console.error('WARNING: PROXY_AUTH_TOKEN is not set! Authentication is disabled.'); + else console.log('Authentication: Bearer Token enabled'); });