posimai-root/routes/ponshu.js

123 lines
5.3 KiB
JavaScript
Raw Normal View History

'use strict';
const express = require('express');
/**
* Ponshu Room ライセンス管理ルーター
*
* エンドポイント:
* POST /ponshu/license/validate (認証不要 モバイルアプリから直接呼ぶ)
* POST /ponshu/admin/license/revoke (認証必須)
*/
module.exports = function createPonshuRouter(pool, authMiddleware, checkRateLimit) {
const router = express.Router();
const APP_SUPPORT_EMAIL = process.env.APP_SUPPORT_EMAIL || 'support@posimai.soar-enrich.com';
// POST /ponshu/license/validate — 認証不要
router.post('/ponshu/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' });
}
// IP ごとに 10回/分 の制限(ブルートフォース防止)
if (checkRateLimit && !checkRateLimit('ponshu_validate', req.ip || 'unknown', 10, 60 * 1000)) {
return res.status(429).json({ valid: false, error: 'リクエストが多すぎます。しばらく待ってから再試行してください' });
}
try {
const result = await pool.query(
`SELECT license_key, plan, status, device_id FROM ponshu_licenses WHERE license_key = $1`,
[license_key]
);
if (result.rows.length === 0) {
return res.json({ valid: false, error: 'ライセンスキーが見つかりません' });
}
const license = result.rows[0];
if (license.status === 'revoked') {
return res.json({
valid: false,
error: 'このライセンスは無効化されています。サポートにお問い合わせください。',
supportEmail: APP_SUPPORT_EMAIL,
});
}
// 初回アクティベート — WHERE device_id IS NULL で競合を防ぐ
if (!license.device_id) {
const activateResult = await pool.query(
`UPDATE ponshu_licenses SET device_id = $1, activated_at = NOW()
WHERE license_key = $2 AND device_id IS NULL
RETURNING license_key`,
[device_id, license_key]
);
if (activateResult.rowCount === 0) {
// 別リクエストが先にアクティベートした → 再取得して照合
const retry = await pool.query(
`SELECT device_id FROM ponshu_licenses WHERE license_key = $1`, [license_key]
);
const current = retry.rows[0];
if (!current || current.device_id !== device_id) {
return res.json({
valid: false,
error: '別のデバイスで登録済みです。端末変更の場合はサポートまでご連絡ください。',
supportEmail: APP_SUPPORT_EMAIL,
});
}
}
console.log(`[Ponshu] License activated: ${license_key.substring(0, 12)}... -> Device: ${device_id.substring(0, 8)}...`);
return res.json({ valid: true, plan: license.plan, activated: true });
}
// 既存デバイスの照合
if (license.device_id !== device_id) {
console.log(`[Ponshu] Device mismatch for license: ${license_key.substring(0, 12)}...`);
return res.json({
valid: false,
error: '別のデバイスで登録済みです。端末変更の場合はサポートまでご連絡ください。',
supportEmail: APP_SUPPORT_EMAIL,
});
}
return res.json({ valid: true, plan: license.plan });
} catch (err) {
console.error('[Ponshu] License validate error:', err.message);
return res.status(500).json({ valid: false, error: 'サーバーエラーが発生しました' });
}
});
// POST /ponshu/admin/license/revoke — 認証必須
router.post('/ponshu/admin/license/revoke', authMiddleware, async (req, res) => {
const { license_key } = req.body;
if (!license_key) {
return res.status(400).json({ success: false, error: 'license_key required' });
}
try {
const result = await pool.query(
`UPDATE ponshu_licenses SET status = 'revoked', revoked_at = NOW()
WHERE license_key = $1 AND status != 'revoked'
RETURNING license_key`,
[license_key]
);
if (result.rowCount === 0) {
return res.json({ success: false, error: 'License not found or already revoked' });
}
console.log(`[Ponshu] License revoked: ${license_key.substring(0, 12)}...`);
return res.json({ success: true, message: `License ${license_key} has been revoked` });
} catch (err) {
console.error('[Ponshu] Revoke error:', err.message);
return res.status(500).json({ success: false, error: 'Server error' });
}
});
return router;
};