New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@flowfuse/flowfuse

Package Overview
Dependencies
Maintainers
2
Versions
997
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flowfuse/flowfuse - npm Package Compare versions

Comparing version 1.15.1-cc8404f-202401101717.0 to 1.15.1-cea84ad-202401161048.0

forge/db/migrations/20240112-01-add-commandresponse-table.js

4

forge/auditLog/index.js

@@ -11,3 +11,3 @@ const fp = require('fastify-plugin')

module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
const loggers = {

@@ -23,6 +23,4 @@ User: user.getLoggers(app),

app.decorate('auditLog', loggers)
next()
}, {
name: 'app.auditLog'
})

@@ -107,11 +107,11 @@ /**

// Receive status events from project launchers
// - ff/v1/+/l/+/status
{ topic: /^ff\/v1\/[^/]+\/l\/[^/]+\/status$/ },
// - ff/v1/<team>/l/<instance>/status
{ topic: /^ff\/v1\/[^/]+\/l\/[^/]+\/status$/, shared: true },
// Receive status events, logs and command responses from devices
// - ff/v1/+/d/+/status
{ topic: /^ff\/v1\/[^/]+\/d\/[^/]+\/status$/ },
// - ff/v1/+/d/+/logs
// - ff/v1/<team>/d/<device>/status
{ topic: /^ff\/v1\/[^/]+\/d\/[^/]+\/status$/, shared: true },
// - ff/v1/<team>/d/<device>/logs
{ topic: /^ff\/v1\/[^/]+\/d\/[^/]+\/logs$/ },
// - ff/v1/+/d/+/logs
{ topic: /^ff\/v1\/[^/]+\/d\/[^/]+\/response$/ }
// Receive broadcast response notification
{ topic: /^ff\/v1\/[^/]+\/d\/[^/]+\/response(\/[^/]+)?$/ }
],

@@ -163,4 +163,4 @@ pub: [

{ topic: /^ff\/v1\/([^/]+)\/d\/([^/]+)\/logs$/, verify: 'checkTeamAndObjectIds' },
// - ff/v1/<team>/d/<device>/response
{ topic: /^ff\/v1\/([^/]+)\/d\/([^/]+)\/response$/, verify: 'checkTeamAndObjectIds' }
// - ff/v1/<team>/d/<device>/response[/<instance>]
{ topic: /^ff\/v1\/([^/]+)\/d\/([^/]+)\/response(\/[^/]+)?$/, verify: 'checkTeamAndObjectIds' }
]

@@ -203,3 +203,3 @@ }

const shareGroup = sharedSubParts[1]
if (shareGroup !== usernameParts[2]) {
if (shareGroup !== 'platform' && shareGroup !== usernameParts[2]) {
return false

@@ -206,0 +206,0 @@ }

const EventEmitter = require('events')
const mqtt = require('mqtt')
const { v4: uuidv4 } = require('uuid')

@@ -13,2 +14,3 @@ /**

this.app = app
this.platformId = uuidv4()
}

@@ -73,10 +75,10 @@

this.client.subscribe([
// Launcher status
'ff/v1/+/l/+/status',
// Device status
'ff/v1/+/d/+/status',
// Device logs
// Launcher status - shared subscription
'$share/platform/ff/v1/+/l/+/status',
// Device status - shared subscription
'$share/platform/ff/v1/+/d/+/status',
// Device logs - not shared subscription
'ff/v1/+/d/+/logs',
// Device response
'ff/v1/+/d/+/response'
// Device response - not shared subscription
'ff/v1/+/d/+/response/' + this.platformId
])

@@ -83,0 +85,0 @@ }

@@ -6,2 +6,3 @@ /**

const SemVer = require('semver')
const { v4: uuidv4 } = require('uuid')

@@ -83,2 +84,9 @@

// If the device is owned by an application (in the DB) and the agent is reporting version < 1.11.0
// then we need to send an update command to the device
if (Object.hasOwn(payload, 'snapshot') && device.isApplicationOwned) {
if (!device.agentVersion || SemVer.lt(device.agentVersion, '1.11.0')) {
sendUpdateCommand = true
}
}
if (Object.hasOwn(payload, 'project') && payload.project !== (device.Project?.id || null)) {

@@ -153,3 +161,4 @@ // The Project is incorrect

const inFlightCommand = this.inFlightCommands[message.correlationData]
if (inFlightCommand && inFlightCommand.command === message.command) {
if (inFlightCommand) {
// This command is known to the local instance - process it
inFlightCommand.resolve(message.payload)

@@ -246,3 +255,3 @@ delete this.inFlightCommands[response.correlationData]

const inFlightCommand = DeviceCommsHandler.newResponseMonitor(command, deviceId, teamId, options)
const inFlightCommand = DeviceCommsHandler.newResponseMonitor(command, deviceId, teamId, this.client.platformId, options)

@@ -300,3 +309,3 @@ const promise = new Promise((resolve, reject) => {

commandMessage.correlationData = cmr.correlationData
commandMessage.responseTopic = `ff/v1/${cmr.teamId}/d/${cmr.deviceId}/response`
commandMessage.responseTopic = `ff/v1/${cmr.teamId}/d/${cmr.deviceId}/response/${cmr.platformId}`
commandMessage.payload = payload

@@ -314,3 +323,3 @@ return commandMessage

*/
static newResponseMonitor (command, deviceId, teamId, options = { timeout: DEFAULT_TIMEOUT }) {
static newResponseMonitor (command, deviceId, teamId, platformId, options = { timeout: DEFAULT_TIMEOUT }) {
const now = Date.now()

@@ -329,2 +338,3 @@ const correlationData = uuidv4() // generate a random correlation data (uuid)

responseMonitor.teamId = teamId
responseMonitor.platformId = platformId
responseMonitor.correlationData = correlationData

@@ -331,0 +341,0 @@ return responseMonitor

@@ -17,3 +17,3 @@ const fp = require('fastify-plugin')

*/
module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
// Check the runtime configuration includes the minimum required configuration

@@ -60,5 +60,4 @@ // to use the MQTT broker service

}
next()
}, {
name: 'app.comms'
})

@@ -123,3 +123,3 @@ /**

attach: fp(async function (app, opts, next) {
attach: fp(async function (app, opts) {
config.features = features(app, config)

@@ -139,4 +139,3 @@ config.rate_limits = rateLimits.getLimits(app, config.rate_limits)

}
next()
}, { name: 'app.config' })
}

@@ -45,3 +45,3 @@ /**

module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
const containerDialect = app.config.driver.type

@@ -69,4 +69,2 @@ const containerModule = DRIVER_MODULES[containerDialect]

}
next()
}, { name: 'app.containers' })

@@ -54,3 +54,3 @@ const { Op } = require('sequelize')

ownerType: 'user',
scope: 'password:Reset',
scope: 'password:reset',
ownerId: user.hashid

@@ -57,0 +57,0 @@ }

@@ -0,1 +1,2 @@

const SemVer = require('semver')
const { literal } = require('sequelize')

@@ -71,3 +72,8 @@

delete payload.project // exclude project property to avoid triggering the wrong kind of update on the device
if (payload.snapshot === null) {
if (!device.agentVersion || SemVer.lt(device.agentVersion, '1.11.0')) {
// device is running an agent version < 1.11.0 we need to clear it
payload.snapshot = null
payload.project = null
payload.settings = null
} else if (payload.snapshot === null) {
payload.snapshot = '0' // '0' indicates that the application owned device should start with starter flows

@@ -74,0 +80,0 @@ }

@@ -33,2 +33,7 @@ const jwt = require('jsonwebtoken')

}
if (newPassword === user.username) {
throw new Error('Password must not match username')
} else if (newPassword === user.email) {
throw new Error('Password must not match email')
}
user.password = newPassword

@@ -43,5 +48,10 @@ user.password_expired = false

resetPassword: async function (app, user, newPassword) {
// if (zxcvbn(newPassword.score < 3)) {
// throw new Error('Password Too Weak')
// }
if (zxcvbn(newPassword).score < 2) {
throw new Error('Password Too Weak')
}
if (newPassword === user.username) {
throw new Error('Password must not match username')
} else if (newPassword === user.email) {
throw new Error('Password must not match email')
}
user.password = newPassword

@@ -173,2 +183,5 @@ user.password_expired = false

await requestingUser.save()
await app.db.controllers.AccessToken.deleteAllUserPasswordResetTokens(requestingUser)
return requestingUser

@@ -175,0 +188,0 @@ } catch (err) {

@@ -26,3 +26,3 @@ /**

module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
utils.init(app)

@@ -92,4 +92,2 @@ const dbOptions = {

await controllers.init(app)
next()
}, { name: 'app.db' })

@@ -51,2 +51,18 @@ /**

},
getTeamsForUser: async (userId, includeTeam = false) => {
if (typeof userId === 'string') {
userId = M.User.decodeHashid(userId)
}
const opts = {
where: {
UserId: userId
}
}
if (includeTeam) {
opts.include = {
model: M.Team
}
}
return this.findAll(opts)
},
getTeamMembership: async (userId, teamId, includeTeam) => {

@@ -53,0 +69,0 @@ if (typeof teamId === 'string') {

@@ -259,2 +259,5 @@ /**

},
getTeamMemberships: async function (includeTeam = false) {
return M.TeamMember.getTeamsForUser(this.id, includeTeam)
},
teamCount: async function () {

@@ -261,0 +264,0 @@ return M.TeamMember.count({

@@ -7,2 +7,3 @@ module.exports = function (app) {

properties: {
email: { type: 'string' },
email_verified: { type: 'boolean' },

@@ -20,2 +21,5 @@ defaultTeam: { type: 'string' },

const result = userSummary(user)
if (user.email) {
result.email = user.email
}
if (user.password_expired) {

@@ -48,3 +52,2 @@ result.password_expired = true

name: { type: 'string' },
email: { type: 'string' },
avatar: { type: 'string' },

@@ -63,3 +66,2 @@ admin: { type: 'boolean' },

'name',
'email',
'avatar',

@@ -66,0 +68,0 @@ 'admin',

@@ -6,3 +6,3 @@ const fp = require('fastify-plugin')

*/
module.exports = fp(async function (app, opts, next) {
module.exports = fp(async function (app, opts) {
// Load ee only if enabled in the license

@@ -9,0 +9,0 @@ if (app.license.active()) {

@@ -107,3 +107,3 @@ /**

const connected = this.isConnected(deviceId)
return { url, enabled, connected }
return { url, enabled, connected, affinity: this.#getTunnel(deviceId)?.affinity }
}

@@ -134,2 +134,9 @@

setTunnelAffinity (deviceId, affinity) {
const tunnel = this.#getTunnel(deviceId)
if (tunnel) {
tunnel.affinity = affinity
}
}
isEnabled (deviceId) {

@@ -369,3 +376,4 @@ const tunnel = this.#getTunnel(deviceId)

_handleHTTP: null,
_handleWS: null
_handleWS: null,
affinity: null
}

@@ -372,0 +380,0 @@ return tunnel

const fp = require('fastify-plugin')
module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
if (app.config.billing) {

@@ -28,4 +28,2 @@ app.decorate('billing', await require('./billing').init(app))

app.config.features.register('customCatalogs', true, true)
done()
}, { name: 'app.ee.lib' })

@@ -0,1 +1,3 @@

const { Roles, TeamRoles } = require('../../../lib/roles')
module.exports.init = async function (app) {

@@ -63,2 +65,123 @@ // Set the SSO feature flag

/**
* Update a user's team memberships according to the SAML Assertions
* received when they logged in.
*
* @param {*} samlUser The user profile object provided by the authentication provider
* @param {*} user The FF User object who is logging in
* @param {*} providerOpts The SAML Provider configuration object
*/
async function updateTeamMembership (samlUser, user, providerOpts) {
// Look for the expected assertion in the SAML profile we have received
// This is an array of groups the user belongs to. We expect them to be
// of the form 'ff-SLUG-ROLE' - anything else is ignored
let groupAssertions = samlUser[providerOpts.groupAssertionName]
if (groupAssertions) {
const promises = []
if (!Array.isArray(groupAssertions)) {
groupAssertions = [groupAssertions]
}
const desiredTeamMemberships = {}
groupAssertions.forEach(ga => {
// Parse the group name - format: 'ff-SLUG-ROLE'
// Generate a slug->role object (desiredTeamMemberships)
const match = /^ff-(.+)-([^-]+)$/.exec(ga)
if (match) {
const teamSlug = match[1]
const teamRoleName = match[2]
const teamRole = Roles[teamRoleName]
// Check this role is a valid team role
if (TeamRoles.includes(teamRole)) {
// Check if this team is allowed to be managed for this SSO provider
// - either `groupAllTeams` is true (allowing all teams to be managed this way)
// - or `groupTeams` (array) contains the teamSlug
if (providerOpts.groupAllTeams || (providerOpts.groupTeams || []).includes(teamSlug)) {
// In case we have multiple assertions for a single team,
// ensure we keep the highest level of access
desiredTeamMemberships[teamSlug] = Math.max(desiredTeamMemberships[teamSlug] || 0, teamRole)
}
}
}
})
// Get the existing memberships and generate a slug->membership object (existingMemberships)
const existingMemberships = {}
;((await user.getTeamMemberships(true)) || []).forEach(membership => {
// Filter out any teams that are not to be managed by this configuration.
// A team is managed by this configuration if any of the follow is true:
// - groupAllTeams is true (all teams to be managed)
// - groupTeams includes this team (this is explicitly a team to be managed)
// - groupOtherTeams is false (not allowed to be a member of other teams - so need to remove them)
if (
providerOpts.groupAllTeams ||
(providerOpts.groupTeams || []).includes(membership.Team.slug) ||
!providerOpts.groupOtherTeams
) {
existingMemberships[membership.Team.slug] = membership
}
})
// We now have the list of desiredTeamMemberships and existingMemberships
// that are in scope of being modified
// - Check each existing membership
// - if in desired list, update role to match and delete from desired list
// - if not in desired list,
// - if groupOtherTeams is false or, delete membership
// - else leave alone
for (const [teamSlug, membership] of Object.entries(existingMemberships)) {
if (Object.hasOwn(desiredTeamMemberships, teamSlug)) {
// This team is in the desired list
if (desiredTeamMemberships[teamSlug] !== membership.role) {
// Role has changed - update membership
// console.log(`changing role in team ${teamSlug} from ${membership.role} to ${desiredTeamMemberships[teamSlug]}`)
const updates = new app.auditLog.formatters.UpdatesCollection()
const oldRole = app.auditLog.formatters.roleObject(membership.role)
const role = app.auditLog.formatters.roleObject(desiredTeamMemberships[teamSlug])
updates.push('role', oldRole.role, role.role)
membership.role = desiredTeamMemberships[teamSlug]
promises.push(membership.save().then(() => {
return app.auditLog.Team.team.user.roleChanged(user, null, membership.Team, user, updates)
}))
} else {
// Role has not changed - no update needed
// console.log(`no change needed for team ${teamSlug} role ${membership.role}`)
}
// Remove from the desired list as it has been dealt with
delete desiredTeamMemberships[teamSlug]
} else {
// console.log(`removing from team ${teamSlug}`)
// This team is not in the desired list - delete the membership
promises.push(membership.destroy().then(() => {
return app.auditLog.Team.team.user.removed(user, null, membership.Team, user)
}))
}
}
// - Check remaining desired memberships
// - create membership
for (const [teamSlug, teamRole] of Object.entries(desiredTeamMemberships)) {
// This is a new team membership
promises.push(app.db.models.Team.bySlug(teamSlug).then(team => {
if (team) {
// console.log(`adding to team ${teamSlug} role ${teamRole}`)
return app.db.controllers.Team.addUser(team, user, teamRole).then(() => {
return app.auditLog.Team.team.user.added(user, null, team, user)
})
} else {
// console.log(`team not found ${teamSlug}`)
// Unrecognised team - ignore
return null
}
}))
}
await Promise.all(promises)
} else {
const missingGroupAssertions = new Error(`SAML response missing ${providerOpts.groupAssertionName} assertion`)
missingGroupAssertions.code = 'unknown_sso_user'
throw missingGroupAssertions
}
}
return {

@@ -68,4 +191,5 @@ handleLoginRequest,

getProviderOptions,
getProviderForEmail
getProviderForEmail,
updateTeamMembership
}
}

@@ -115,2 +115,5 @@ const { generateToken } = require('../../../db/utils')

}
if (cmdResponse.affinity) {
tunnelManager.setTunnelAffinity(deviceId, cmdResponse.affinity)
}
} catch (error) {

@@ -117,0 +120,0 @@ // ensure any attempt to enable the editor is cleaned up if an error occurs

@@ -29,3 +29,3 @@ module.exports = async function (app) {

} else if (request.session.ownerId !== request.params.projectId) {
// AccesToken being used - but not owned by this project
// AccessToken being used - but not owned by this project
reply.code(404).send({ code: 'not_found', error: 'Not Found' })

@@ -32,0 +32,0 @@ return // eslint-disable-line no-useless-return

@@ -1,2 +0,2 @@

module.exports = async function (app, opts, done) {
module.exports = async function (app, opts) {
/**

@@ -63,3 +63,2 @@ * Begin MFA setup

})
done()
}

@@ -5,3 +5,3 @@ const { Authenticator } = require('@fastify/passport')

module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
app.addHook('onRequest', async (request, reply) => {

@@ -72,11 +72,23 @@ if (!request.session) {

}
}, async (req, profile, done) => {
if (profile.nameID) {
const user = await app.db.models.User.byUsernameOrEmail(profile.nameID)
}, async (request, samlUser, done) => {
if (samlUser.nameID) {
// console.log(profile)
const user = await app.db.models.User.byUsernameOrEmail(samlUser.nameID)
if (user) {
const state = JSON.parse(request.body.RelayState)
const providerOpts = await app.sso.getProviderOptions(state.provider)
if (providerOpts.groupMapping) {
// This SSO provider is configured to manage team membership.
try {
await app.sso.updateTeamMembership(samlUser, user, providerOpts)
} catch (err) {
done(err)
return
}
}
done(null, user)
} else {
const unknownError = new Error(`Unknown user: ${profile.nameID}`)
const unknownError = new Error(`Unknown user: ${samlUser.nameID}`)
unknownError.code = 'unknown_sso_user'
const userInfo = app.auditLog.formatters.userObject({ email: profile.nameID })
const userInfo = app.auditLog.formatters.userObject({ email: samlUser.nameID })
const resp = { code: 'unknown_sso_user', error: 'unauthorized' }

@@ -140,3 +152,2 @@ await app.auditLog.User.account.login(userInfo, resp, userInfo)

})
done()
}, { name: 'app.ee.routes.sso.auth' })

@@ -6,3 +6,3 @@ const fp = require('fastify-plugin')

module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
registerPermissions({

@@ -94,4 +94,2 @@ 'saml-provider:create': { description: 'Create a SAML Provider', role: Roles.Admin },

await app.register(require('./auth'))
done()
}, { name: 'app.ee.routes.sso' })

@@ -20,3 +20,3 @@ const { captureCheckIn, captureException } = require('@sentry/node')

*/
module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
const tasks = {}

@@ -26,3 +26,3 @@ const delayedStartupTasks = []

// Ensure we stop any scheduled tasks when the app is shutting down
app.addHook('onClose', async (_) => {
app.addHook('onClose', async () => {
Object.values(tasks).forEach(task => {

@@ -153,4 +153,2 @@ if (task.job) {

})
next()
}, { name: 'app.housekeeper' })
const { Op } = require('sequelize')
const { randomInt } = require('../utils')
module.exports = {
name: 'expireTokens',
startup: true,
schedule: '@daily',
// Pick a random hour/minute for this task to run at. If the application is
// horizontal scaled, this will avoid two instances running at the same time
schedule: `${randomInt(0, 59)} ${randomInt(0, 23)} * * *`,
run: async function (app) {

@@ -8,0 +12,0 @@ await app.db.models.Session.destroy({ where: { expiresAt: { [Op.lt]: Date.now() } } })

@@ -6,7 +6,8 @@ const fs = require('fs/promises')

const { randomInt } = require('../utils')
const METRICS_DIR = path.join(__dirname, 'telemetryMetrics')
// Pick a random time to run the ping on
const randomInt = (min, max) => { return min + Math.floor(Math.random() * (max - min)) }
const PING_TIME = `${randomInt(0, 60)} ${randomInt(0, 24)} * * *`
const PING_TIME = `${randomInt(0, 59)} ${randomInt(0, 23)} * * *`

@@ -13,0 +14,0 @@ /**

@@ -18,2 +18,7 @@ const Roles = {

// For convenience, we want to be able to look up role values with both 'Role' and 'role'
Object.keys(RoleNames).forEach(role => {
Roles[RoleNames[role]] = parseInt(role)
})
const TeamRoles = [

@@ -20,0 +25,0 @@ Roles.Dashboard,

@@ -5,3 +5,3 @@ const fp = require('fastify-plugin')

module.exports = fp(async function (app, opts, next) {
module.exports = fp(async function (app, opts) {
// Dev License:

@@ -87,4 +87,2 @@ /*

next()
function status () {

@@ -91,0 +89,0 @@ const PRE_EXPIRE_WARNING_DAYS = 30 // hard coded

@@ -7,3 +7,3 @@ const fp = require('fastify-plugin')

module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
let mailTransport

@@ -179,4 +179,2 @@ let exportableSettings = {}

})
next()
}, { name: 'app.postoffice' })

@@ -5,3 +5,3 @@ const { readFileSync, existsSync } = require('fs')

const fp = require('fastify-plugin')
module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
await app.register(require('@fastify/swagger'), {

@@ -70,2 +70,9 @@ openapi: {

hideUntagged: true,
staticCSP: true,
transformStaticCSP: header => {
header.replace(
/script-src 'self'/,
"script-src 'self' 'unsafe-inline'"
)
},
uiConfig: {

@@ -98,2 +105,3 @@ defaultModelsExpandDepth: -1,

}
// Fully built path

@@ -162,4 +170,2 @@ let faviconPath = path.join(__dirname, '../../frontend/dist/favicon-32x32.png')

})
done()
}, { name: 'app.routes.api-docs' })

@@ -129,2 +129,5 @@ const { Op } = require('sequelize')

preHandler: app.needsPermission('platform:stats'),
config: {
rateLimit: app.config.rate_limits
},
schema: {

@@ -265,30 +268,2 @@ summary: 'Get a platform stats - admin-only',

// Undocumented
app.get('/debug/db-migrations', { preHandler: app.needsPermission('platform:debug') }, async (request, reply) => {
reply.send((await app.db.sequelize.query('select * from "MetaVersions"'))[0])
})
// Undocumented
app.get('/debug/db-schema', { preHandler: app.needsPermission('platform:debug') }, async (request, reply) => {
const result = {}
let tables
if (app.config.db.type === 'postgres') {
const response = await app.db.sequelize.query('select * from information_schema.tables')
const tt = response[0]
tables = []
for (let i = 0; i < tt.length; i++) {
const table = tt[i]
if (table.table_schema === 'public') {
tables.push(table.table_name)
}
}
} else {
const response = await app.db.sequelize.showAllSchemas()
tables = response.map(t => t.name)
}
for (let i = 0; i < tables.length; i++) {
result[tables[i]] = await app.db.sequelize.getQueryInterface().describeTable(tables[i])
}
reply.send(result)
})
/**

@@ -295,0 +270,0 @@ * Get platform audit logs

@@ -1,3 +0,1 @@

const semver = require('semver')
// eslint-disable-next-line no-unused-vars

@@ -409,8 +407,2 @@ const { DeviceTunnelManager } = require('../../ee/lib/deviceEditor/DeviceTunnelManager')

} else {
// check the agent version is compatible with the application
// the agent semver must be greater than or equal to first version that supports applications
if (!device.agentVersion || semver.lt(device.agentVersion, '1.11.0')) {
reply.code(400).send({ code: 'invalid_agent_version', error: 'invalid agent version' })
return
}
// Check if the specified application is in the same team

@@ -417,0 +409,0 @@ assignToApplication = await app.db.models.Application.byId(request.body.application)

@@ -39,2 +39,14 @@ const SemVer = require('semver')

await app.db.controllers.Device.updateState(request.device, request.body)
if (request.device.isApplicationOwned) {
if (!request.device.agentVersion || SemVer.lt(request.device.agentVersion, '1.11.0')) {
reply.code(409).send({
error: 'incorrect-agent-version',
mode: request.device.mode || null,
project: null,
settings: null,
snapshot: null
})
return
}
}
if (Object.hasOwn(request.body, 'project') && request.body.project !== (request.device.Project?.id || null)) {

@@ -107,2 +119,10 @@ reply.code(409).send({

}
if (SemVer.satisfies(SemVer.coerce(device.agentVersion), '>=1.11.2')) {
// 1.11.2 includes fix for ESM loading of GOT, so lets use 'latest' as before
nodeRedVersion = 'latest'
}
if (!device.agentVersion || SemVer.lt(device.agentVersion, '1.11.0')) {
reply.code(400).send({ code: 'invalid_agent_version', error: 'invalid agent version' })
return
}
// determine is device is in application mode? if so, return a default snapshot to permit the user to generate flows

@@ -109,0 +129,0 @@ const DEFAULT_APP_SNAPSHOT = {

@@ -59,2 +59,5 @@ /**

app.post('/', {
config: {
rateLimit: app.config.rate_limits ? { max: 5, timeWindow: 30000 } : false
},
schema: {

@@ -61,0 +64,0 @@ summary: 'Create an invitation',

@@ -52,3 +52,6 @@ const sharedUser = require('./shared/users')

preHandler: app.needsPermission('user:edit'),
config: { allowExpiredPassword: true },
config: {
allowExpiredPassword: true,
rateLimit: app.config.rate_limits ? { max: 5, timeWindow: 30000 } : false
},
schema: {

@@ -141,2 +144,5 @@ summary: 'Change the current users password',

preHandler: app.needsPermission('user:edit'),
config: {
rateLimit: app.config.rate_limits ? { max: 5, timeWindow: 30000 } : false
},
schema: {

@@ -240,2 +246,5 @@ summary: 'Update the current users settings',

app.post('/tokens', {
config: {
rateLimit: app.config.rate_limits ? { max: 5, timeWindow: 30000 } : false
},
schema: {

@@ -242,0 +251,0 @@ summary: 'Create user Personal Access Token',

@@ -48,3 +48,3 @@ /**

*/
async function init (app, opts, done) {
async function init (app, opts) {
await app.register(require('./oauth'), { logLevel: app.config.logging.http })

@@ -177,6 +177,2 @@ await app.register(require('./permissions'))

// app.post('/account/register', (request, reply) => {
//
// })
/**

@@ -199,3 +195,10 @@ * Login a user.

config: {
rateLimit: app.config.rate_limits // rate limit this route regardless of global/per-route mode (if enabled)
rateLimit: app.config.rate_limits
? {
max: 5,
timeWindow: 30000,
keyGenerator: app.config.rate_limits.keyGenerator,
hard: true
}
: false
},

@@ -332,3 +335,10 @@ schema: {

config: {
rateLimit: app.config.rate_limits // rate limit this route regardless of global/per-route mode (if enabled)
rateLimit: app.config.rate_limits
? {
max: 5,
timeWindow: 30000,
keyGenerator: app.config.rate_limits.keyGenerator,
hard: true
}
: false
},

@@ -453,7 +463,7 @@ schema: {

if (/user_username_lower_unique|Users_username_key/.test(err.parent?.toString())) {
responseMessage = 'username not available'
responseCode = 'invalid_username'
responseMessage = 'Username or email not available'
responseCode = 'invalid_request'
} else if (/user_email_lower_unique|Users_email_key/.test(err.parent?.toString())) {
responseMessage = 'email not available'
responseCode = 'invalid_email'
responseMessage = 'Username or email not available'
responseCode = 'invalid_request'
} else if (err.errors) {

@@ -616,3 +626,10 @@ responseMessage = err.errors.map(err => err.message).join(',')

config: {
rateLimit: false, // never rate limit this route
rateLimit: app.config.rate_limits
? {
max: 5,
timeWindow: 30000,
keyGenerator: app.config.rate_limits.keyGenerator,
hard: true
}
: false,
allowUnverifiedEmail: true

@@ -699,3 +716,10 @@ },

config: {
rateLimit: app.config.rate_limits // rate limit this route regardless of global/per-route mode (if enabled)
rateLimit: app.config.rate_limits
? {
max: 5,
timeWindow: 30000,
keyGenerator: app.config.rate_limits.keyGenerator,
hard: true
}
: false
},

@@ -801,4 +825,2 @@ schema: {

})
done()
}

@@ -24,3 +24,3 @@ const fp = require('fastify-plugin')

module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
function hasPermission (teamMembership, scope) {

@@ -104,3 +104,2 @@ if (!teamMembership) {

app.decorate('needsPermission', needsPermission)
done()
}, { name: 'app.routes.auth.permissions' })

@@ -14,3 +14,3 @@ /**

const fp = require('fastify-plugin')
module.exports = fp(async function (app, opts, done) {
module.exports = fp(async function (app, opts) {
app.decorate('getPaginationOptions', (request, defaults) => {

@@ -32,3 +32,2 @@ const result = { ...defaults, ...request.query }

await app.register(require('./logging'), { prefix: '/logging', logLevel: app.config.logging.http })
done()
}, { name: 'app.routes' })

@@ -9,3 +9,3 @@ const fs = require('fs')

module.exports = fp(async function (app, _opts, next) {
module.exports = fp(async function (app, _opts) {
const settings = { ...defaultSettings }

@@ -59,3 +59,2 @@

app.decorate('settings', settingsApi)
next()
}, { name: 'app.settings' })

@@ -1,1 +0,1 @@

!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t=(new Error).stack;t&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[t]="bcaf1993-55f0-41e5-981d-a47e57d80c87",e._sentryDebugIdIdentifier="sentry-dbid-bcaf1993-55f0-41e5-981d-a47e57d80c87")}catch(e){}}();var _global="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};_global.SENTRY_RELEASE={id:"cc8404f0f69946a44010db493510d3e02abcb85e"},function(){"use strict";var e,t,n,r,o,u={},i={};function f(e){var t=i[e];if(void 0!==t)return t.exports;var n=i[e]={id:e,loaded:!1,exports:{}};return u[e](n,n.exports,f),n.loaded=!0,n.exports}f.m=u,e=[],f.O=function(t,n,r,o){if(!n){var u=1/0;for(d=0;d<e.length;d++){n=e[d][0],r=e[d][1],o=e[d][2];for(var i=!0,a=0;a<n.length;a++)(!1&o||u>=o)&&Object.keys(f.O).every((function(e){return f.O[e](n[a])}))?n.splice(a--,1):(i=!1,o<u&&(u=o));if(i){e.splice(d--,1);var c=r();void 0!==c&&(t=c)}}return t}o=o||0;for(var d=e.length;d>0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},f.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);f.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach((function(t){u[t]=function(){return e[t]}}));return u.default=function(){return e},f.d(o,u),o},f.d=function(e,t){for(var n in t)f.o(t,n)&&!f.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},f.f={},f.e=function(e){return Promise.all(Object.keys(f.f).reduce((function(t,n){return f.f[n](e,t),t}),[]))},f.u=function(e){return"async-vendors.js"},f.miniCssF=function(e){},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="@flowfuse/flowfuse:",f.l=function(e,t,n,u){if(r[e])r[e].push(t);else{var i,a;if(void 0!==n)for(var c=document.getElementsByTagName("script"),d=0;d<c.length;d++){var l=c[d];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(a=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,f.nc&&i.setAttribute("nonce",f.nc),i.setAttribute("data-webpack",o+n),i.src=e),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(b);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((function(e){return e(n)})),t)return t(n)},b=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),a&&document.head.appendChild(i)}},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.p="/app/",function(){f.b=document.baseURI||self.location.href;var e={666:0};f.f.j=function(t,n){var r=f.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else if(666!=t){var o=new Promise((function(n,o){r=e[t]=[n,o]}));n.push(r[2]=o);var u=f.p+f.u(t),i=new Error;f.l(u,(function(n){if(f.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),u=n&&n.target&&n.target.src;i.message="Loading chunk "+t+" failed.\n("+o+": "+u+")",i.name="ChunkLoadError",i.type=o,i.request=u,r[1](i)}}),"chunk-"+t,t)}else e[t]=0},f.O.j=function(t){return 0===e[t]};var t=function(t,n){var r,o,u=n[0],i=n[1],a=n[2],c=0;if(u.some((function(t){return 0!==e[t]}))){for(r in i)f.o(i,r)&&(f.m[r]=i[r]);if(a)var d=a(f)}for(t&&t(n);c<u.length;c++)o=u[c],f.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return f.O(d)},n=self.webpackChunk_flowfuse_flowfuse=self.webpackChunk_flowfuse_flowfuse||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))}(),f.nc=void 0}();
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t=(new Error).stack;t&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[t]="bcaf1993-55f0-41e5-981d-a47e57d80c87",e._sentryDebugIdIdentifier="sentry-dbid-bcaf1993-55f0-41e5-981d-a47e57d80c87")}catch(e){}}();var _global="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};_global.SENTRY_RELEASE={id:"cea84ad20d245166791498f3641423986d69875f"},function(){"use strict";var e,t,n,r,o,u={},i={};function f(e){var t=i[e];if(void 0!==t)return t.exports;var n=i[e]={id:e,loaded:!1,exports:{}};return u[e](n,n.exports,f),n.loaded=!0,n.exports}f.m=u,e=[],f.O=function(t,n,r,o){if(!n){var u=1/0;for(d=0;d<e.length;d++){n=e[d][0],r=e[d][1],o=e[d][2];for(var i=!0,a=0;a<n.length;a++)(!1&o||u>=o)&&Object.keys(f.O).every((function(e){return f.O[e](n[a])}))?n.splice(a--,1):(i=!1,o<u&&(u=o));if(i){e.splice(d--,1);var c=r();void 0!==c&&(t=c)}}return t}o=o||0;for(var d=e.length;d>0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},f.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);f.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach((function(t){u[t]=function(){return e[t]}}));return u.default=function(){return e},f.d(o,u),o},f.d=function(e,t){for(var n in t)f.o(t,n)&&!f.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},f.f={},f.e=function(e){return Promise.all(Object.keys(f.f).reduce((function(t,n){return f.f[n](e,t),t}),[]))},f.u=function(e){return"async-vendors.js"},f.miniCssF=function(e){},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="@flowfuse/flowfuse:",f.l=function(e,t,n,u){if(r[e])r[e].push(t);else{var i,a;if(void 0!==n)for(var c=document.getElementsByTagName("script"),d=0;d<c.length;d++){var l=c[d];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(a=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,f.nc&&i.setAttribute("nonce",f.nc),i.setAttribute("data-webpack",o+n),i.src=e),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((function(e){return e(n)})),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),a&&document.head.appendChild(i)}},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.p="/app/",function(){f.b=document.baseURI||self.location.href;var e={666:0};f.f.j=function(t,n){var r=f.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else if(666!=t){var o=new Promise((function(n,o){r=e[t]=[n,o]}));n.push(r[2]=o);var u=f.p+f.u(t),i=new Error;f.l(u,(function(n){if(f.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),u=n&&n.target&&n.target.src;i.message="Loading chunk "+t+" failed.\n("+o+": "+u+")",i.name="ChunkLoadError",i.type=o,i.request=u,r[1](i)}}),"chunk-"+t,t)}else e[t]=0},f.O.j=function(t){return 0===e[t]};var t=function(t,n){var r,o,u=n[0],i=n[1],a=n[2],c=0;if(u.some((function(t){return 0!==e[t]}))){for(r in i)f.o(i,r)&&(f.m[r]=i[r]);if(a)var d=a(f)}for(t&&t(n);c<u.length;c++)o=u[c],f.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return f.O(d)},n=self.webpackChunk_flowfuse_flowfuse=self.webpackChunk_flowfuse_flowfuse||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))}(),f.nc=void 0}();
{
"name": "@flowfuse/flowfuse",
"version": "1.15.1-cc8404f-202401101717.0",
"version": "1.15.1-cea84ad-202401161048.0",
"description": "An open source low-code development platform",

@@ -67,3 +67,3 @@ "homepage": "https://flowfuse.com",

"@fastify/swagger": "^8.10.1",
"@fastify/swagger-ui": "^1.9.0",
"@fastify/swagger-ui": "^2.1.0",
"@fastify/websocket": "^8.1.0",

@@ -84,5 +84,5 @@ "@flowfuse/driver-localfs": "nightly",

"dotenv": "^16.3.1",
"fastify": "^4.24.0",
"fastify": "^4.25.2",
"fastify-metrics": "^10.3.2",
"fastify-plugin": "^4.5.0",
"fastify-plugin": "^4.5.1",
"handlebars": "^4.7.7",

@@ -89,0 +89,0 @@ "hashids": "^2.3.0",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc