icytv-liq/control/api.js

132 lines
3.4 KiB
JavaScript

const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
require('express-async-errors')
const config = require(path.join(__dirname, 'config.js'))
const app = express()
const port = config.api.port
const host = config.api.host
const mount = config.api.mount
app.enable('trust proxy', 1)
app.disable('x-powered-by')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
const apiRouter = express.Router()
const internalRouter = express.Router()
// Make sure internal router can only be accessed from local network
internalRouter.use(function (req, res, next) {
if (req.ip !== '127.0.0.1' && req.ip !== '::1') {
return res.status(401).end('401 Unauthorized. Accessed internal API endpoint!')
}
next()
})
// Call this here to prevent key from being required on internal endpoints
apiRouter.use('/internal', internalRouter)
// Key-based authorization for API endpoints
apiRouter.use(function (req, res, next) {
let key = req.query.key
// Prefer headers for authentication
if (req.headers['authorization'] && typeof req.headers['authorization'] === 'string') {
let auth = req.headers['authorization']
if (auth.indexOf('Bearer') !== -1) return next(new Error('Invalid authorization header!'))
auth = auth.split(' ')[1]
if (!auth) return next(new Error('Invalid authorization header!'))
key = auth
}
if (!key || config.api.keys.indexOf(key) === -1) return res.status(403).end('403 Forbidden')
req.key = key
next()
})
module.exports = function (liq, sched) {
// Schedule actions
apiRouter.get('/events', (req, res) => {
res.jsonp({
calendar: config.calendar.calendar,
events: sched.events
})
})
apiRouter.post('/events/reload', async (req, res) => {
await sched.calendarFetch()
await sched.update()
res.status(204).end()
})
// Liquidsoap status
apiRouter.get('/', (req, res) => {
res.jsonp({
time: new Date(),
authorization: req.key,
liquidsoap: {
status: liq.running,
meta: liq.metadata
}
})
})
apiRouter.get('/meta', (req, res) => {
res.jsonp(liq.metadata)
})
// Liquidsoap actions
apiRouter.post('/queue', (req, res) => {
let items = req.body.items
if (typeof items === 'string') items = [items]
else if (!items || !items.length) return res.status(400).end('400 Bad Request')
liq.queue(items)
res.status(202).end('OK')
})
apiRouter.post('/skip', (req, res) => {
liq.skip()
res.status(202).end('OK')
})
// Internal router (Only accessible from localhost, no matter the host setting)
// Used by liquidsoap
internalRouter.post('/meta', (req, res) => {
let m = req.body
if (Object.keys(m).length === 0) {
return res.send('(none)')
}
liq.metadata = m
res.send(JSON.stringify(m, null, 1))
})
app.use(mount, apiRouter)
// Handle 404
app.use((req, res, next) => {
res.status(404).end('404 Not Found')
})
// Handle errors
app.use((error, req, res, next) => {
console.error(error.stack)
if (app.get('env') === 'production') {
return res.status(500).end('Internal Server Error: ' + error.message)
}
res.status(500).end(error.stack)
})
// And finally, listen on the configured port and host
app.listen(port, host, () => console.log(`API server listening on ${host}:${port}${mount}`))
}