From b09adff173f698fd3b3f23b63d049f5e8198e4d3 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 24 Mar 2026 21:18:30 -0500 Subject: [PATCH 1/5] feat(server): add optional access token auth for pinning API Co-Authored-By: Claude Sonnet 4.6 --- src/commands/server.ts | 4 ++++ src/config.ts | 1 + src/core/synapse/index.ts | 1 + src/filecoin-pinning-server.ts | 5 +++++ 4 files changed, 11 insertions(+) diff --git a/src/commands/server.ts b/src/commands/server.ts index 3a07eb88..11d37d2e 100644 --- a/src/commands/server.ts +++ b/src/commands/server.ts @@ -9,6 +9,7 @@ export const serverCommand = new Command('server') .option('--car-storage ', 'path for CAR file storage', './cars') .option('--database ', 'path to SQLite database', './pins.db') .option('--private-key ', 'private key for Synapse (or use PRIVATE_KEY env var)') + .option('--access-token ', 'bearer token required on all API requests (or use ACCESS_TOKEN env var)') addNetworkOptions(serverCommand) .addOption( @@ -20,6 +21,9 @@ addNetworkOptions(serverCommand) if (options.privateKey) { process.env.PRIVATE_KEY = options.privateKey } + if (options.accessToken) { + process.env.ACCESS_TOKEN = options.accessToken + } // RPC URL takes precedence over network flag if (options.rpcUrl) { process.env.RPC_URL = options.rpcUrl diff --git a/src/config.ts b/src/config.ts index a5677e1c..55c4b570 100644 --- a/src/config.ts +++ b/src/config.ts @@ -50,6 +50,7 @@ export function createConfig(): Config { // Synapse SDK configuration privateKey: process.env.PRIVATE_KEY, // Required: Ethereum-compatible private key + accessToken: process.env.ACCESS_TOKEN, rpcUrl, // Determined from RPC_URL, NETWORK, or default to calibration // Storage paths databasePath: process.env.DATABASE_PATH ?? join(dataDir, 'pins.db'), diff --git a/src/core/synapse/index.ts b/src/core/synapse/index.ts index a7f2d217..fca1a265 100644 --- a/src/core/synapse/index.ts +++ b/src/core/synapse/index.ts @@ -30,6 +30,7 @@ export interface Config { port: number host: string privateKey: string | undefined + accessToken: string | undefined rpcUrl: string databasePath: string carStoragePath: string diff --git a/src/filecoin-pinning-server.ts b/src/filecoin-pinning-server.ts index 6d144997..b6a8eefc 100644 --- a/src/filecoin-pinning-server.ts +++ b/src/filecoin-pinning-server.ts @@ -126,6 +126,11 @@ export async function createFilecoinPinningServer( return } + if (config.accessToken && token !== config.accessToken) { + await reply.code(401).send({ error: 'Invalid access token' }) + return + } + // Add user to request context request.user = DEFAULT_USER_INFO }) From 3fbea2dc303a9607437d5981dab3ac28c8d403f6 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 26 Mar 2026 01:01:43 -0500 Subject: [PATCH 2/5] fix: 403 for wrong token, 401 for missing token --- src/filecoin-pinning-server.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/filecoin-pinning-server.ts b/src/filecoin-pinning-server.ts index b6a8eefc..14bbe717 100644 --- a/src/filecoin-pinning-server.ts +++ b/src/filecoin-pinning-server.ts @@ -114,6 +114,12 @@ export async function createFilecoinPinningServer( return } + // If no access token is configured, allow all requests + if (!config.accessToken) { + request.user = DEFAULT_USER_INFO + return + } + const authHeader = request.headers.authorization if (authHeader?.startsWith('Bearer ') !== true) { await reply.code(401).send({ error: 'Missing or invalid authorization header' }) @@ -126,8 +132,8 @@ export async function createFilecoinPinningServer( return } - if (config.accessToken && token !== config.accessToken) { - await reply.code(401).send({ error: 'Invalid access token' }) + if (token !== config.accessToken) { + await reply.code(403).send({ error: 'Invalid access token' }) return } From 293ab5b5cf23bcee85a659bb47582478edfbc243 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 26 Mar 2026 01:05:55 -0500 Subject: [PATCH 3/5] docs(server): clarify access token option help text Co-Authored-By: Claude Sonnet 4.6 --- src/commands/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/server.ts b/src/commands/server.ts index 11d37d2e..338a6523 100644 --- a/src/commands/server.ts +++ b/src/commands/server.ts @@ -8,8 +8,8 @@ export const serverCommand = new Command('server') .option('--host ', 'server host', '127.0.0.1') .option('--car-storage ', 'path for CAR file storage', './cars') .option('--database ', 'path to SQLite database', './pins.db') - .option('--private-key ', 'private key for Synapse (or use PRIVATE_KEY env var)') - .option('--access-token ', 'bearer token required on all API requests (or use ACCESS_TOKEN env var)') + .option('--private-key ', 'private key for Synapse (env: PRIVATE_KEY)') + .option('--access-token ', 'bearer token required on all API requests except GET / (env: ACCESS_TOKEN)') addNetworkOptions(serverCommand) .addOption( From 148ea936ede19814561afebf56f0cb4533958e9e Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 26 Mar 2026 01:10:34 -0500 Subject: [PATCH 4/5] fix: trim token once --- src/filecoin-pinning-server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filecoin-pinning-server.ts b/src/filecoin-pinning-server.ts index 14bbe717..bcc8955b 100644 --- a/src/filecoin-pinning-server.ts +++ b/src/filecoin-pinning-server.ts @@ -126,8 +126,8 @@ export async function createFilecoinPinningServer( return } - const token = authHeader.slice(7) // Remove 'Bearer ' prefix - if (token.trim().length === 0) { + const token = authHeader.slice(7).trim() // Remove 'Bearer ' prefix + if (token.length === 0) { await reply.code(401).send({ error: 'Invalid access token' }) return } From 9ef64998b93a85bf87b88dcdfb52f530a277c000 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Tue, 31 Mar 2026 19:39:23 -0500 Subject: [PATCH 5/5] group access token with pin server settings, not synapse config --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 55c4b570..948aed3e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,10 +47,10 @@ export function createConfig(): Config { // Application-specific configuration port: parseInt(process.env.PORT ?? '3456', 10), host: process.env.HOST ?? 'localhost', + accessToken: process.env.ACCESS_TOKEN, // Synapse SDK configuration privateKey: process.env.PRIVATE_KEY, // Required: Ethereum-compatible private key - accessToken: process.env.ACCESS_TOKEN, rpcUrl, // Determined from RPC_URL, NETWORK, or default to calibration // Storage paths databasePath: process.env.DATABASE_PATH ?? join(dataDir, 'pins.db'),