Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 8 additions & 18 deletions bin/tinyNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import http from "http"
* Get port from environment and store in Express.
*/

const port = normalizePort(process.env.PORT || '3002')
const envPort = normalizePort(process.env.PORT)
if (process.env.PORT && !envPort) console.warn(`Invalid PORT "${process.env.PORT}", using 3002`)
const port = envPort || 3002
app.set('port', port)

/**
Expand All @@ -34,19 +36,13 @@ server.on('error', onError)
server.on('listening', onListening)

/**
* Normalize a port into a number, string, or false.
* Normalize a port into a valid TCP port number or false.
*/

function normalizePort(val) {
const port = parseInt(val, 10)
const port = Number(val)

if (isNaN(port)) {
// named pipe
return val
}

if (port >= 0) {
// port number
if (Number.isInteger(port) && port >= 1 && port <= 65535) {
return port
}

Expand All @@ -62,9 +58,7 @@ function onError(error) {
throw error
}

const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port
const bind = 'Port ' + port

// handle specific listen errors with friendly messages
switch (error.code) {
Expand All @@ -86,9 +80,5 @@ function onError(error) {
*/

function onListening() {
const addr = server.address()
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port
console.log('Listening on ' + bind)
console.log('Listening on port ' + server.address().port)
}
63 changes: 11 additions & 52 deletions routes/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@ import { fetchRerum } from "../rerum.js"
import { isPassthroughRequest, requirePassthroughAllowed, resolveAuthorization } from "../passthrough.js"
const router = express.Router()

/* Legacy delete pattern w/body. */
router.delete('/', verifyJsonContentType, requirePassthroughAllowed, checkAccessToken, async (req, res, next) => {
async function deleteRecord(req, res) {
try {
if (!req?.body || !(req.body['@id'] ?? req.body.id)) {
const uri = req.params.id ?? req.body?.['@id'] ?? req.body?.id
const id = typeof uri === "string" ? uri.split("/").pop() : undefined
if (!id || id === "." || id === "..") {
const err = new Error("No record id to delete! (https://store.rerum.io/v1/API.html#delete)")
err.status = 400
throw err
}

const deleteBody = JSON.stringify(req.body)
const deleteURL = `${process.env.RERUM_API_ADDR}delete/${encodeURIComponent(id)}`
const deleteOptions = {
body: deleteBody,
method: 'DELETE',
headers: {
'user-agent': 'Tiny-Things/1.0',
'Origin': process.env.ORIGIN,
'Authorization': resolveAuthorization(req),
'Content-Type' : "application/json; charset=utf-8"
'Authorization': resolveAuthorization(req)
}
}
const deleteURL = `${process.env.RERUM_API_ADDR}delete`
await fetchRerum(deleteURL, deleteOptions)
.then(async (resp) => {
if (resp.ok) return
Expand Down Expand Up @@ -54,51 +51,13 @@ router.delete('/', verifyJsonContentType, requirePassthroughAllowed, checkAccess
console.error(err)
res.status(err.status ?? 500).type('text/plain').send(err.message ?? 'An error occurred')
}
})
}

/* Legacy delete pattern w/body. */
router.delete('/', verifyJsonContentType, requirePassthroughAllowed, checkAccessToken, deleteRecord)

/* DELETE an object by ID via the RERUM API. */
router.delete('/:id', requirePassthroughAllowed, checkAccessToken, async (req, res, next) => {
try {

const deleteURL = `${process.env.RERUM_API_ADDR}delete/${req.params.id}`
const deleteOptions = {
method: "DELETE",
headers: {
'user-agent': 'Tiny-Things/1.0',
'Origin': process.env.ORIGIN,
'Authorization': resolveAuthorization(req),
}
}
await fetchRerum(deleteURL, deleteOptions)
.then(async (resp) => {
if (resp.ok) return
if (isPassthroughRequest(req) && (resp.status === 401 || resp.status === 403)) {
let rerumAuthMessage
try {
rerumAuthMessage = `${resp.status}: ${deleteURL} - ${await resp.text()}`
} catch (e) {
rerumAuthMessage = `${resp.status}: ${deleteURL} - A RERUM error occurred`
}
throw httpError(rerumAuthMessage, resp.status)
}
// The response from RERUM indicates a failure, likely with a specific code and textual body
let rerumErrorMessage
try {
rerumErrorMessage = `${resp.status ?? 500}: ${deleteURL} - ${await resp.text()}`
} catch (e) {
rerumErrorMessage = `500: ${deleteURL} - A RERUM error occurred`
}
const err = new Error(rerumErrorMessage)
err.status = 502
throw err
})
res.status(204).end()
}
catch (err) {
console.error(err)
res.status(err.status ?? 500).type('text/plain').send(err.message ?? 'An error occurred')
}
})
router.delete('/:id', requirePassthroughAllowed, checkAccessToken, deleteRecord)

router.all('/', (req, res, next) => {
res.status(405).send("Method Not Allowed")
Expand Down
31 changes: 28 additions & 3 deletions test/routes/delete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ describe("Check that the request/response behavior of the TinyNode delete route

assert.equal(response.statusCode, 204)

// Verify upstream contract for body-based delete
assert.match(lastFetchUrl, /\/delete$/, "URL should end with /delete")
// Verify body-based delete is forwarded upstream as a path-based delete
assert.match(lastFetchUrl, /\/delete\/_not_$/, "URL should end with /delete/_not_")
assert.equal(lastFetchOptions.method, "DELETE", "Method should be DELETE")
assert.match(lastFetchOptions.headers["Authorization"], /^Bearer /, "Authorization header missing or invalid")
assert.equal(lastFetchOptions.headers["Content-Type"], "application/json; charset=utf-8", "Content-Type header mismatch")
assert.equal(lastFetchOptions.body, undefined, "Body should not be forwarded upstream")
assert.equal(lastFetchOptions.headers["Content-Type"], undefined, "Content-Type should not be sent")

response = await request(routeTester)
.delete("/delete/00000")
Expand All @@ -58,6 +59,30 @@ describe("Check that the request/response behavior of the TinyNode delete route
assert.match(lastFetchUrl, /\/delete\/00000$/, "URL should end with /delete/00000")
assert.equal(lastFetchOptions.method, "DELETE", "Method should be DELETE")
})

it("Extracts the record id from the body 'id' key and from an encoded URI in the path.", async () => {
let response = await request(routeTester)
.delete("/delete")
.send({ id: rerumUri })
.set("Content-Type", "application/json")

assert.equal(response.statusCode, 204)
assert.match(lastFetchUrl, /\/delete\/_not_$/, "URL should end with /delete/_not_")

response = await request(routeTester)
.delete(`/delete/${encodeURIComponent(rerumUri)}`)

assert.equal(response.statusCode, 204)
assert.match(lastFetchUrl, /\/delete\/_not_$/, "URL should end with /delete/_not_")

response = await request(routeTester)
.delete("/delete")
.send({ "@id": `${rerumUri}?x=1#f` })
.set("Content-Type", "application/json")

assert.equal(response.statusCode, 204)
assert.match(lastFetchUrl, /\/delete\/_not_%3Fx%3D1%23f$/, "id should be URI encoded upstream")
})
})

describe("Check that incorrect TinyNode delete route usage results in expected RESTful responses from RERUM. __rest __core", () => {
Expand Down
Loading