diff --git a/realtime/src/routes/connect.js b/realtime/src/routes/connect.js index e6332b0..f76751a 100644 --- a/realtime/src/routes/connect.js +++ b/realtime/src/routes/connect.js @@ -1,8 +1,12 @@ const router = require('express').Router(); const db = require('../store/db'); +const { appendAsConnection, createConnectionToken } = require('../store/redis'); const crypto = require('crypto'); -router.post('/connect/new', async (req, res) => { +/** + * Aggiunge un nuovo sensore autorizzato a partire da un nome univoco e un codice che verrĂ  salvato in forma hashata. + */ +router.post('/new', async (req, res) => { const { name, code } = req.body; if (!name || !code) { @@ -32,4 +36,38 @@ router.post('/connect/new', async (req, res) => { } }); +router.post('/', async (req, res) => { + const { name, code } = req.body; + + if (!name || !code) { + return res.status(400).json({ error: 'name and code required' }); + } + + try { + const result = await db.query('sensors', + 'SELECT code_hash FROM sensors WHERE name = $1', + [name] + ); + + if (result.rows.length === 0) { + return res.status(401).json({ error: 'invalid name or code' }); + } + + const [salt, storedHash] = result.rows[0].code_hash.split(':'); + const hash = crypto.scryptSync(code, salt, 64).toString('hex'); + + if (hash !== storedHash) { + return res.status(401).json({ error: 'invalid name or code' }); + } + + await appendAsConnection(name, 'pending', new Date().toISOString()); + const token = await createConnectionToken(name); + + res.status(200).json({ s: 'ok', t: token }); + } catch (err) { + console.error('Error verifying connection', err); + res.status(500).json({ error: 'internal server error' }); + } +}); + module.exports = router; diff --git a/realtime/src/routes/sensors.js b/realtime/src/routes/sensors.js index 0b88c49..80fe5fb 100644 --- a/realtime/src/routes/sensors.js +++ b/realtime/src/routes/sensors.js @@ -1,7 +1,7 @@ const router = require('express').Router(); const db = require('../store/db'); -router.get('/sensors', async (req, res) => { +router.get('/', async (req, res) => { try { const result = await db.query('SELECT id, name FROM sensors', [], 'sensors'); res.json(result.rows); @@ -11,4 +11,20 @@ router.get('/sensors', async (req, res) => { } }); +router.get('/:id', async (req, res) => { + const { id } = req.params; + try { + const result = await db.query('SELECT id, name FROM sensors WHERE id = $1', [id], 'sensors'); + if (result.rows.length === 0) { + return res.status(404).json({ error: 'sensor not found' }); + } + res.json(result.rows[0]); + } catch (err) { + console.error('Error fetching sensor', err); + res.status(500).json({ error: 'internal server error' }); + } +}); + + + module.exports = router; \ No newline at end of file diff --git a/realtime/src/routes/sessions.js b/realtime/src/routes/sessions.js new file mode 100644 index 0000000..8126b88 --- /dev/null +++ b/realtime/src/routes/sessions.js @@ -0,0 +1,15 @@ +const router = require('express').Router(); +const db = require('../store/db'); +const { query } = require('../store/redis'); + +router.get('/pendingtokens', (req, res) => { + try { + const pendingTokens = queryAll('snsr_pending_token'); + res.json(pendingTokens); + } catch (err) { + console.error('Error fetching pending tokens', err); + res.status(500).json({ error: 'internal server error' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/realtime/src/store/redis.js b/realtime/src/store/redis.js index d8a8142..8d7a5c0 100644 --- a/realtime/src/store/redis.js +++ b/realtime/src/store/redis.js @@ -1,6 +1,10 @@ const redis = require('ioredis'); -const redisClient = new redis({ +const connectionsToken = "snsr_pending_token"; +const connectedSensorsKey = "sensors"; + + +const client = new redis({ host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT), password: process.env.REDIS_PASSWORD, @@ -12,22 +16,22 @@ const redisClient = new redis({ } }); -redisClient.on('error', (error) => { +client.on('error', (error) => { console.error('Redis error', error); }); -redisClient.on('connect', () => { +client.on('connect', () => { console.log('Connected to Redis'); }); -redisClient.on('reconnecting', () => { +client.on('reconnecting', () => { console.log('Reconnecting to Redis'); }); async function configure() { try { - await redisClient.connect(); - await redisClient.ping(); + await client.connect(); + await client.ping(); console.log('Redis connection established'); } catch (err) { console.error('Failed to connect to Redis', err); @@ -35,21 +39,87 @@ async function configure() { } function connected() { - return redisClient.status === 'ready'; + return client.status === 'ready'; } async function checkRedis() { try { - if (redisClient.status !== 'ready') { - await redisClient.connect().catch(() => {}); + if (client.status !== 'ready') { + await client.connect().catch(() => {}); } - await redisClient.ping(); + await client.ping(); return true; } catch (err) { return false; } } +/** + * + * @param {*} sensor + * @param {*} status + * @param {*} timestamp + */ +async function appendAsConnection(sensor, status, timestamp) { + try { + await client.hset(`sensor:${sensor}`, 'status', status, 'timestamp', timestamp); + } catch (err) { + console.error('Redis append error', err); + } +} + +/** + * Recupera i valori di un campo specifico per una chiave data. + * @param {String} name - la chiave da cui leggere, es. "sensor:123" + * @param {String} from - il nome del campo da leggere, es. "status" o "timestamp" + * @returns {Object} un oggetto contenente i valori del campo richiesto + */ +async function query(name, from) { + const key = `${from}:${name}`; + return await client.hgetall(key); +} + +/** + * Crea un token temporaneo per una nuova connessione WebSocket, scade dopo 5 secondi. + * @returns {string} il token + */ +async function createConnectionToken(sensor) { + const crypto = require('crypto'); + const token = crypto.randomBytes(32).toString('hex'); + const key = `${connectionsToken}:${token}`; + await client.set(key, sensor, 'EX', 5); + return token; +} + +/** + * Verifica e consuma un token di connessione. + * @returns {string|null} il nome del sensore se valido, null altrimenti + */ +async function consumeConnectionToken(token) { + const key = `${connectionsToken}:${token}`; + const sensor = await client.get(key); + if (sensor) { + await client.del(key); + } + return sensor; +} + +/** + * Restituisce tutte le chiavi che corrispondono a un prefisso. + * @param {String} from - il prefisso da cercare, es. "sensor", "ws_token" + * @returns {string[]} lista di chiavi trovate + */ +async function queryAll(from) { + const keys = []; + let cursor = '0'; + do { + const [next, found] = await client.scan(cursor, 'MATCH', `${from}:*`, 'COUNT', 100); + cursor = next; + keys.push(...found); + } while (cursor !== '0'); + return keys; +} + configure(); -module.exports = { redisClient, connected, checkRedis }; \ No newline at end of file +module.exports = { checkRedis, appendAsConnection, createConnectionToken, consumeConnectionToken, query, queryAll }; \ No newline at end of file