@flowfuse/flowfuse
Version:
An open source low-code development platform
185 lines (168 loc) • 7.4 kB
JavaScript
module.exports = async function (app) {
app.addHook('preHandler', app.verifySession)
app.addHook('preHandler', async (request, response) => {
// The request has a valid token, but need to check the token is allowed
// to access the library
// For initial shared library implementation, this will be the teamId
const id = request.params.libraryId
const team = await app.db.models.Team.byId(id)
if (team) {
request.team = team
// Check this feature is enabled for this team type.
if (team.TeamType.getFeatureProperty('shared-library', true)) {
// If this is a session token, verify the project or device is in the team
if (request.session.ownerType === 'project') {
const project = await app.db.models.Project.byId(request.session.ownerId)
if (project.Team.hashid === id) {
// Project exists and the auth token is for this team
return
}
} else if (request.session.ownerType === 'device') {
const deviceId = +request.session.ownerId
const device = await app.db.models.Device.byId(deviceId)
if (device?.Team.hashid === id) {
// Device exists and the auth token is for this team
return
}
} else if (!request.session.ownerType) {
// This is a logged-in user. Get their teamMembership so the needsPermission
// checks in the routes will evaluate properly
request.teamMembership = await request.session.User.getTeamMembership(request.team.id)
if (request.teamMembership) {
return
}
}
}
}
response.status(404).send({ code: 'not_found', error: 'Not Found' })
})
app.post('/storage/library/:libraryId/*', {
preHandler: async (request, reply) => {
return app.needsPermission('library:entry:create')(request, reply)
}
}, async (request, response) => {
const type = request.body.type
let body = request.body.body
const name = request.params['*']
const meta = request.body.meta
if (!type) {
response.code(400).send({ code: 'invalid_request', error: 'Missing type parameter' })
return
}
if (typeof body === 'object') {
body = JSON.stringify(body)
}
const direct = await app.db.models.StorageSharedLibrary.byName(request.team.id, type, name)
if (direct) {
// Updating an existing entry
direct.body = body
direct.meta = JSON.stringify(meta)
await direct.save()
} else {
// Adding a new entry. We need to check each part of the path to ensure
// none are existing 'files' - otherwise we could end up with a directory
// with a file and subdirectory with the same name, making it untraversable
const parts = name.split('/')
for (let i = 0; i < parts.length - 1; i++) {
const subpath = parts.slice(0, i + 1).join('/')
const count = await app.db.models.StorageSharedLibrary.count({
where: {
name: subpath,
TeamId: request.team.id
}
})
if (count > 0) {
response.status(400).send({ code: 'invalid_name', error: 'Invalid entry name' })
return
}
}
// Finally, need to check the new entries full path isn't actually
// an existing path.
const existing = await app.db.models.StorageSharedLibrary.byPath(request.team.id, null, name + '/')
if (existing.length > 0) {
response.status(400).send({ code: 'invalid_name', error: 'Invalid entry name' })
return
}
await app.db.models.StorageSharedLibrary.create({
name,
type,
meta: JSON.stringify(meta),
body,
TeamId: request.team.id
})
}
response.status(201).send()
})
app.get('/storage/library/:libraryId/*', {
preHandler: async (request, reply) => {
return app.needsPermission('library:entry:list')(request, reply)
}
}, async (request, response) => {
const type = request.query.type
let name = request.params['*']
let reply = []
// Try to get the exact name
const direct = await app.db.models.StorageSharedLibrary.byName(request.team.id, type, name)
if (direct) {
if (direct.type === 'flows') {
reply = JSON.parse(direct.body)
} else {
reply = direct.body
}
} else {
// No entry with that exact name. Check to see if its a partial
// path name
if (name.length > 0 && name[name.length - 1] !== '/') {
name += '/'
}
const subPaths = new Set()
const entries = await app.db.models.StorageSharedLibrary.byPath(request.team.id, type, name)
entries.forEach(entry => {
const shortName = entry.name.substring(name.length)
const pathParts = shortName.split('/')
if (pathParts.length === 1) {
reply.push({ fn: shortName, ...JSON.parse(entry.meta), type: entry.type, updatedAt: entry.updatedAt })
} else {
subPaths.add(pathParts[0])
}
})
reply.push(...subPaths)
}
// add meta info to headers
response.header('x-meta-type', direct ? direct.type : 'folder')
response.send(reply)
})
app.delete('/storage/library/:libraryId/*', {
preHandler: async (request, reply) => {
if (request.teamMembership) {
return app.needsPermission('library:entry:delete')(request, reply)
}
}
}, async (request, response) => {
const type = request.query.type
let name = request.params['*']
let deleteCount = 0
// Try to get the exact name
const direct = await app.db.models.StorageSharedLibrary.byName(request.team.id, type, name)
if (direct) {
await direct.destroy()
deleteCount++
} else {
// No entry with that exact name. Check to see if its a partial
// path name
if (name.length > 0 && name[name.length - 1] !== '/') {
name += '/'
}
const entries = await app.db.models.StorageSharedLibrary.byPath(request.team.id, type, name)
for (const entry of entries) {
await entry.destroy()
deleteCount++
}
}
if (deleteCount === 0) {
response.status(404).send({ code: 'not_found', error: 'Not Found' })
} else {
response.send({ status: 'okay', deleteCount })
}
})
}