feat: Add Bearer Token authentication to AI Proxy and disable CORS

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ponshu Developer 2026-02-16 11:34:00 +09:00
parent f2f6acd070
commit 3502694d89
4 changed files with 60 additions and 6 deletions

View File

@ -33,6 +33,23 @@ class Secrets {
/// AI Proxy URL /// AI Proxy URL
static const String aiProxyAnalyzeUrl = '$aiProxyBaseUrl/analyze'; 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 /// Gemini API Key
/// defaultValueは空です /// defaultValueは空です
/// : lib/secrets.local.dart /// : lib/secrets.local.dart

View File

@ -15,4 +15,7 @@ class SecretsLocal {
/// ローカル開発時のAI Proxy URLオプション /// ローカル開発時のAI Proxy URLオプション
static const String aiProxyBaseUrl = 'http://100.76.7.3:8080'; static const String aiProxyBaseUrl = 'http://100.76.7.3:8080';
/// AI Proxy認証トークンProxy経由モード時に使用
static const String proxyAuthToken = 'YOUR_PROXY_AUTH_TOKEN_HERE';
} }

View File

@ -174,10 +174,15 @@ $extractedText
debugPrint('Calling Proxy: $_proxyUrl'); 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( final response = await http.post(
Uri.parse(_proxyUrl), Uri.parse(_proxyUrl),
headers: {"Content-Type": "application/json"}, headers: headers,
body: requestBody, body: requestBody,
).timeout(const Duration(seconds: 60)); // : 60 () ).timeout(const Duration(seconds: 60)); // : 60 ()

View File

@ -1,19 +1,46 @@
const express = require('express'); const express = require('express');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const cors = require('cors');
const { GoogleGenerativeAI } = require('@google/generative-ai'); const { GoogleGenerativeAI } = require('@google/generative-ai');
require('dotenv').config(); require('dotenv').config();
const app = express(); const app = express();
const PORT = process.env.PORT || 8080; const PORT = process.env.PORT || 8080;
const API_KEY = process.env.GEMINI_API_KEY; const API_KEY = process.env.GEMINI_API_KEY;
const AUTH_TOKEN = process.env.PROXY_AUTH_TOKEN;
// Rate Limiting (Simple In-Memory) // Rate Limiting (Simple In-Memory)
const DAILY_LIMIT = parseInt(process.env.DAILY_LIMIT || '50', 10); const DAILY_LIMIT = parseInt(process.env.DAILY_LIMIT || '50', 10);
const usageStore = {}; // { deviceId: { date: 'YYYY-MM-DD', count: 0 } } const usageStore = {}; // { deviceId: { date: 'YYYY-MM-DD', count: 0 } }
app.use(cors()); // Authentication Middleware (skip for /health)
app.use(bodyParser.json({ limit: '10mb' })); // Allow large image payloads 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 // Gemini Client
const genAI = new GoogleGenerativeAI(API_KEY); 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) => { app.post('/analyze', async (req, res) => {
const { device_id, images, prompt } = req.body; const { device_id, images, prompt } = req.body;
@ -126,4 +153,6 @@ app.get('/health', (req, res) => {
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Proxy Server running on port ${PORT}`); console.log(`Proxy Server running on port ${PORT}`);
if (!API_KEY) console.error('WARNING: GEMINI_API_KEY is not set!'); 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');
}); });