@flowfuse/flowfuse
Version:
An open source low-code development platform
205 lines (201 loc) • 8.22 kB
JavaScript
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' })
}
})
}