UNPKG

@flowfuse/flowfuse

Version:

An open source low-code development platform

205 lines (201 loc) • 8.22 kB
const mqttMatch = require('mqtt-match') const { parseNrMqttId } = require('../db/utils') module.exports = async function (app) { app.post('/auth', { schema: { body: { type: 'object', required: ['username', 'password'], properties: { username: { type: 'string' }, password: { type: 'string' }, clientId: { type: 'string' } } }, response: { 200: { type: 'object', required: ['result'], properties: { result: { type: 'string' }, is_superuser: { type: 'boolean' }, client_attrs: { type: 'object', additionalProperties: true } }, additionalProperties: true } } } }, async (request, reply) => { const username = request.body.username const password = request.body.password const clientId = request.body.clientId if ((username.startsWith('device:') && password.startsWith('ffbd_')) || (username.startsWith('project:') && password.startsWith('ffbp_')) || (username.startsWith('frontend:') && password.startsWith('ffbf_')) || (username.startsWith('expert-agent:') && password.startsWith('ffbea_')) || (username.startsWith('expert-client:') && password.startsWith('ffbec_')) || (username === 'forge_platform')) { const isValid = await app.db.controllers.BrokerClient.authenticateCredentials( username, password ) if (isValid) { reply.send({ result: 'allow', is_superuser: false, client_attrs: { team: '' } }) } else { reply.send({ result: 'deny' }) } } else if (username.startsWith('agent:')) { const parts = username.split('@') const teamId = parts[1] const agent = await app.db.models.TeamBrokerAgent.byTeam(teamId) if (agent && agent.auth === password) { reply.send({ result: 'allow', is_superuser: false, client_attrs: { team: `ff/v1/${teamId}/c/` } }) } else { reply.send({ result: 'deny' }) } } else { if (app.license.active()) { let teamId = null let authorized = false // mq:hosted/mq:remote are nr-mqtt-nodes node authorization requests if (username.startsWith('mq:hosted:') || username.startsWith('mq:remote:')) { const auth = await app.db.controllers.TeamBrokerClient.authenticateNrMqttNodeUser( username, clientId, password ) teamId = auth.teamId authorized = auth.clientIdValid } else { const parts = username.split('@') teamId = parts[1] if (username === clientId) { authorized = await app.db.controllers.TeamBrokerClient.authenticateCredentials(username, password) } } // this test is to ensure that only a fixed number of clients can connect if (authorized) { // we might pass ACL values here // const user = await app.db.models.TeamBrokerClient.byUsername(parts[0], parts[1]) return reply.send({ result: 'allow', is_superuser: false, client_attrs: { team: `ff/v1/${teamId}/c/` } }) } } reply.send({ result: 'deny' }) } }) app.post('/acls', { schema: { body: { type: 'object', properties: { username: { type: 'string' }, topic: { type: 'string' }, action: { type: 'string' } } }, response: { 200: { type: 'object', required: ['result'], properties: { result: { type: 'string' } }, additionalProperties: true } } } }, async (request, reply) => { const username = request.body.username const topic = request.body.topic const action = request.body.action if ((username.startsWith('device:') || username.startsWith('project:') || username.startsWith('frontend:') || username.startsWith('expert-agent:') || username.startsWith('expert-client:') || username === 'forge_platform') && !username.includes('@')) { const acc = action === 'subscribe' ? 1 : 2 const allowed = await app.comms.aclManager.verify(username, topic, acc) if (allowed) { if (action === 'publish') { const m = /^ff\/v1\/([^/]+)\/c\/(.+)$/.exec(topic) if (m) { app.teamBroker.addUsedTopic(m[2], m[1]) } } reply.send({ result: 'allow' }) } else { reply.send({ result: 'deny' }) } // return } else if (username.startsWith('agent:')) { if (action === 'subscribe') { reply.send({ result: 'allow' }) } else { reply.send({ result: 'deny' }) } } else { if (app.license.active()) { let teamClientUsername let teamId // mq:hosted/mq:remote are nr-mqtt-nodes node acls checks if (username.startsWith('mq:hosted:') || username.startsWith('mq:remote:')) { const parsedNrMqttId = parseNrMqttId(username) teamClientUsername = parsedNrMqttId.username // e.g. instance:xxxx or device:xxxx teamId = parsedNrMqttId.teamId } else { const parts = username.split('@') teamClientUsername = parts[0] teamId = parts[1] } const user = await app.db.models.TeamBrokerClient.byUsername(teamClientUsername, teamId, false, false) if (user) { const acls = JSON.parse(user.acls) for (const acl of acls) { if (request.body.action === 'subscribe') { if (mqttMatch(acl.pattern, request.body.topic)) { if (acl.action === 'both' || acl.action === 'subscribe') { reply.send({ result: 'allow' }) return } } } else { if (mqttMatch(acl.pattern, request.body.topic)) { if (acl.action === 'both' || acl.action === 'publish') { app.teamBroker.addUsedTopic(request.body.topic, teamId) reply.send({ result: 'allow' }) return } } } } } } reply.send({ result: 'deny' }) } }) }