44 lines
1.5 KiB
JavaScript
44 lines
1.5 KiB
JavaScript
import { Router } from 'express';
|
|
import crypto from 'crypto';
|
|
import { querySensors as sensors } from '../data/db.js';
|
|
import { redis } from '../data/redis.js';
|
|
import { verify } from '../core/securitycore.js';
|
|
|
|
const router = Router();
|
|
const rateLimiter = 10;
|
|
const rateLimitWindow = 60;
|
|
|
|
router.post('/connect', async (req, res) => {
|
|
const { sensorID, code } = req.body;
|
|
|
|
const ip = (req.headers['x-forwarded-for']?.split(',')[0]?.trim()) || req.socket.remoteAddress || 'unknown';
|
|
const tryKey = `streamconnect:fail:${ip}`;
|
|
const fails = Number(await redis.get(tryKey).catch(() => 0));
|
|
if (fails >= rateLimiter) {
|
|
return res.status(429).json({ error: 'Too many failed attempts' });
|
|
}
|
|
|
|
if (!sensorID || !code) {
|
|
await redis.multi().incr(tryKey).expire(tryKey, rateLimitWindow).exec().catch(() => { });
|
|
return res.status(400).json({ error: 'sensor and code are required' });
|
|
}
|
|
|
|
const { rows } = await sensors('select id, name, code_hash from sensors where id = $1', [sensorID]);
|
|
if (rows.length === 0) {
|
|
return res.status(404).json({ error: 'sensor not found' });
|
|
}
|
|
if (!rows[0] || !verify(code, rows[0].code_hash)) {
|
|
await redis.multi().incr(tryKey).expire(tryKey, rateLimitWindow).exec().catch(() => { });
|
|
return res.status(401).json({ error: 'invalid code' });
|
|
}
|
|
|
|
const token = crypto.randomUUID();
|
|
await redis.set(`sensor:pending:${token}`, rows[0].id, 'EX', 5);
|
|
res.json({
|
|
token,
|
|
expiresIn: 5
|
|
})
|
|
|
|
})
|
|
|
|
export { router as connectsAPI } |