diff --git a/src/commands/server.ts b/src/commands/server.ts index 3a07eb88..338a6523 100644 --- a/src/commands/server.ts +++ b/src/commands/server.ts @@ -8,7 +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('--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( @@ -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..948aed3e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,6 +47,7 @@ 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 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..bcc8955b 100644 --- a/src/filecoin-pinning-server.ts +++ b/src/filecoin-pinning-server.ts @@ -114,18 +114,29 @@ 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' }) 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 } + if (token !== config.accessToken) { + await reply.code(403).send({ error: 'Invalid access token' }) + return + } + // Add user to request context request.user = DEFAULT_USER_INFO })