feat: ライセンス検証をVPSエンドポイントへ移行

- secrets.dart: posimaiBaseUrl を追加 (https://api.soar-enrich.com)
- license_service.dart: 検証URLをproxyから /api/ponshu/license/validate へ変更
  + LicenseStatus.trialExpired 参照バグを修正
  + _cachedTrialInfo 残骸変数参照を削除
- proxy/server.js: /license/validate と /admin/license/revoke を削除
  (ライセンス管理はPostgreSQL + VPS server.js が担当)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ponshu Developer 2026-04-11 00:16:52 +09:00
parent d47bb201ac
commit 5311241fe5
3 changed files with 9 additions and 76 deletions

View File

@ -72,5 +72,11 @@ class Secrets {
return local.SecretsLocal.geminiApiKey; return local.SecretsLocal.geminiApiKey;
} }
/// Posimai URL使
static const String posimaiBaseUrl = String.fromEnvironment(
'POSIMAI_BASE_URL',
defaultValue: 'https://api.soar-enrich.com',
);
// static const String driveClientId = String.fromEnvironment('DRIVE_CLIENT_ID', defaultValue: ''); // static const String driveClientId = String.fromEnvironment('DRIVE_CLIENT_ID', defaultValue: '');
} }

View File

@ -97,7 +97,6 @@ class LicenseService {
await prefs.remove(_prefLicenseKey); await prefs.remove(_prefLicenseKey);
await prefs.remove(_prefCachedStatus); await prefs.remove(_prefCachedStatus);
await prefs.remove(_prefCachedAt); await prefs.remove(_prefCachedAt);
_cachedTrialInfo = null;
debugPrint('[License] Reset complete.'); debugPrint('[License] Reset complete.');
} }
@ -112,7 +111,7 @@ class LicenseService {
try { try {
final deviceId = await DeviceService.getDeviceId(); final deviceId = await DeviceService.getDeviceId();
final response = await http.post( final response = await http.post(
Uri.parse('${Secrets.aiProxyBaseUrl}/license/validate'), Uri.parse('${Secrets.posimaiBaseUrl}/api/ponshu/license/validate'),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: jsonEncode({'license_key': key, 'device_id': deviceId}), body: jsonEncode({'license_key': key, 'device_id': deviceId}),
).timeout(const Duration(seconds: 15)); ).timeout(const Duration(seconds: 15));
@ -121,7 +120,7 @@ class LicenseService {
if (data['valid'] == true) return LicenseStatus.pro; if (data['valid'] == true) return LicenseStatus.pro;
if ((data['error'] as String? ?? '').contains('無効化')) return LicenseStatus.revoked; if ((data['error'] as String? ?? '').contains('無効化')) return LicenseStatus.revoked;
return LicenseStatus.trialExpired; return LicenseStatus.free;
} catch (e) { } catch (e) {
debugPrint('[License] Validation network error: $e'); debugPrint('[License] Validation network error: $e');

View File

@ -78,7 +78,7 @@ function authMiddleware(req, res, next) {
app.use(bodyParser.json({ limit: '10mb' })); app.use(bodyParser.json({ limit: '10mb' }));
app.use((req, res, next) => { app.use((req, res, next) => {
const publicPaths = ['/health', '/license/validate']; const publicPaths = ['/health'];
if (publicPaths.includes(req.path)) return next(); if (publicPaths.includes(req.path)) return next();
authMiddleware(req, res, next); authMiddleware(req, res, next);
}); });
@ -182,78 +182,6 @@ app.post('/analyze', async (req, res) => {
} }
}); });
// 新規: ライセンス検証 (認証不要 — アプリが直接呼ぶ)
app.post('/license/validate', async (req, res) => {
const { license_key, device_id } = req.body;
if (!license_key || !device_id) {
return res.status(400).json({ valid: false, error: 'Missing parameters' });
}
try {
const license = await redisClient.hGetAll(`license:${license_key}`);
if (!license || Object.keys(license).length === 0) {
return res.json({ valid: false, error: 'ライセンスキーが見つかりません' });
}
if (license.status === 'revoked') {
return res.json({ valid: false, error: 'このライセンスは無効化されています。サポートにお問い合わせください。' });
}
// 初回アクティベート
if (!license.deviceId || license.deviceId === '') {
await redisClient.hSet(`license:${license_key}`, 'deviceId', device_id);
await redisClient.hSet(`license:${license_key}`, 'activatedAt', new Date().toISOString());
console.log(`[License] Activated: ${license_key} → Device: ${device_id.substring(0, 8)}...`);
return res.json({ valid: true, plan: license.plan, activated: true });
}
// 既存デバイスの照合
if (license.deviceId !== device_id) {
console.log(`[License] Device mismatch: ${license_key}`);
return res.json({
valid: false,
error: '別のデバイスで登録済みです。端末変更の場合はサポートまでご連絡ください。',
supportEmail: APP_SUPPORT_EMAIL,
});
}
return res.json({ valid: true, plan: license.plan });
} catch (err) {
console.error('[License] Validate error:', err);
res.status(500).json({ valid: false, error: 'サーバーエラーが発生しました' });
}
});
// 管理用 — ライセンス失効 (認証必須)
app.post('/admin/license/revoke', async (req, res) => {
const { license_key } = req.body;
if (!license_key) {
return res.status(400).json({ success: false, error: 'license_key required' });
}
try {
const exists = await redisClient.hExists(`license:${license_key}`, 'status');
if (!exists) {
return res.json({ success: false, error: 'License not found' });
}
await redisClient.hSet(`license:${license_key}`, 'status', 'revoked');
await redisClient.hSet(`license:${license_key}`, 'revokedAt', new Date().toISOString());
console.log(`[Admin] License revoked: ${license_key}`);
return res.json({ success: true, message: `License ${license_key} has been revoked` });
} catch (err) {
console.error('[Admin] Revoke error:', err);
res.status(500).json({ success: false, error: 'Server error' });
}
});
// ヘルスチェック // ヘルスチェック
app.get('/health', (req, res) => { app.get('/health', (req, res) => {
res.send('OK'); res.send('OK');