Skip to content
Closed
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
16 changes: 16 additions & 0 deletions passthrough.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function resolveAuthorization(req) {
const incoming = req.headers?.authorization;
if (incoming) {
return incoming;
}
return `******`;
}

export function requirePassthroughAllowed(req, res, next) {
const incoming = req.headers?.authorization;
const allow = process.env.ALLOW_PASSTHROUGH_TOKENS;
if (incoming && allow && allow.toLowerCase() === 'false') {
return res.status(403).send('Token passthrough is not allowed on this TinyNode instance');
}
next();
}
6 changes: 4 additions & 2 deletions routes/create.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from "express"
import checkAccessToken from "../tokens.js"
import { resolveAuthorization, requirePassthroughAllowed } from "../passthrough.js"
import { verifyJsonContentType } from "../rest.js"
import { createRerumNetworkError, fetchRerum } from "../rerum.js"
const router = express.Router()

/* POST a create to the thing. */
router.post('/', verifyJsonContentType, checkAccessToken, async (req, res, next) => {
router.post('/', verifyJsonContentType, requirePassthroughAllowed, checkAccessToken, async (req, res, next) => {

try {
// if an id is passed in, pop off the end to make it an _id
Expand All @@ -25,7 +26,8 @@ router.post('/', verifyJsonContentType, checkAccessToken, async (req, res, next)
'Content-Type' : "application/json;charset=utf-8"
}
}
const createURL = `${process.env.RERUM_API_ADDR}create`
createOptions.headers['Authorization'] = resolveAuthorization(req);
const createURL = `${process.env.RERUM_API_ADDR}create`
const rerumResponse = await fetchRerum(createURL, createOptions)
.then(async (resp) => {
if (resp.ok) return resp.json()
Expand Down
5 changes: 3 additions & 2 deletions routes/delete.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from "express"
import checkAccessToken from "../tokens.js"
import { resolveAuthorization, requirePassthroughAllowed } from "../passthrough.js"
import { verifyJsonContentType } from "../rest.js"
import { fetchRerum } from "../rerum.js"
const router = express.Router()

/* Legacy delete pattern w/body. */
router.delete('/', verifyJsonContentType, checkAccessToken, async (req, res, next) => {
router.delete('/', verifyJsonContentType, requirePassthroughAllowed, checkAccessToken, async (req, res, next) => {
try {
if (!req?.body || !(req.body['@id'] ?? req.body.id)) {
const err = new Error("No record id to delete! (https://store.rerum.io/v1/API.html#delete)")
Expand Down Expand Up @@ -47,7 +48,7 @@ router.delete('/', verifyJsonContentType, checkAccessToken, async (req, res, nex
})

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

const deleteURL = `${process.env.RERUM_API_ADDR}delete/${req.params.id}`
Expand Down
6 changes: 4 additions & 2 deletions routes/update.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from "express"
import checkAccessToken from "../tokens.js"
import { resolveAuthorization, requirePassthroughAllowed } from "../passthrough.js"
import { httpError, verifyJsonContentType } from "../rest.js"
import { createRerumNetworkError, fetchRerum } from "../rerum.js"
const router = express.Router()

/* PUT an update to the thing. */
router.put('/', verifyJsonContentType, checkAccessToken, async (req, res, next) => {
router.put('/', verifyJsonContentType, requirePassthroughAllowed, checkAccessToken, async (req, res, next) => {

try {
// check for @id; any value is valid
Expand All @@ -24,7 +25,8 @@ router.put('/', verifyJsonContentType, checkAccessToken, async (req, res, next)
'Content-Type' : "application/json;charset=utf-8"
}
}
const updateURL = `${process.env.RERUM_API_ADDR}update`
updateOptions.headers['Authorization'] = resolveAuthorization(req);
const updateURL = `${process.env.RERUM_API_ADDR}update`
const rerumResponse = await fetchRerum(updateURL, updateOptions)
.then(async (resp) => {
if (resp.ok) return resp.json()
Expand Down
47 changes: 47 additions & 0 deletions test/routes/passthrough.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "../helpers/env.js";
import assert from "node:assert/strict";
import { afterEach, beforeEach, describe, it } from "node:test";
import express from "express";
import request from "supertest";
import createRoute from "../../routes/create.js";
import { messenger } from "../../error-messenger.js";

const routeTester = express();
routeTester.use(express.json({ type: ['application/json', 'application/ld+json'] }));
routeTester.use(express.urlencoded({ extended: false }));
routeTester.use('/create', createRoute);
routeTester.use('/app/create', createRoute);
routeTester.use(messenger);

let originalFetch = global.fetch;
let lastFetchOptions = null;

beforeEach(() => {
lastFetchOptions = null;
global.fetch = async (url, opts) => {
lastFetchOptions = opts;
return {
json: async () => ({ "@id": "dummy", test: "item" }),
ok: true,
text: async () => ""
};
};
});

afterEach(() => {
global.fetch = originalFetch;
});

describe('Passthrough Authorization header preservation', () => {
it('forwards the exact Authorization header to upstream', async () => {
const token = 'Bearer ABC123';
const resp = await request(routeTester)
.post('/create')
.set('Authorization', token)
.set('Content-Type', 'application/json')
.send({ test: 'item' });
assert.equal(resp.statusCode, 201);
assert.ok(lastFetchOptions, 'fetch should have been called');
assert.equal(lastFetchOptions.headers['Authorization'], token);
});
});
27 changes: 26 additions & 1 deletion tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,29 @@ async function checkAccessToken(req, res, next) {

}

export default checkAccessToken
export default function checkAccessToken(req, res, next) {
// If request carries an Authorization header, treat it as passthrough and skip instance token checks/refresh
if (req.headers?.authorization) {
return next();
}
// Existing logic for instance token handling
(async () => {
try {
if(!process?.env?.ACCESS_TOKEN) {
next();
return;
}
if (isTokenExpired(process.env.ACCESS_TOKEN)) {
console.log("TinyNode detected an expired access token. Updating the token now.");
await generateNewAccessToken();
}
next();
}
catch (err) {
console.log("TinyNode encountered an error trying to refresh its access token");
console.error(err);
next(err);
}
})();
}

Loading