feat: Add Bearer Token authentication to AI Proxy and disable CORS
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f2f6acd070
commit
3502694d89
|
|
@ -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 を作成してキーを設定
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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秒 (画像サイズ最適化済み)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue