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:
parent
d47bb201ac
commit
5311241fe5
|
|
@ -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: '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue