mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api.git
synced 2026-09-21 16:52:00 -07:00
feat(basic auth): Allowing basic authentication for API access
This commit is contained in:
+16
-4
@@ -1,13 +1,25 @@
|
||||
# START INFRASTRUCTURE SETUP
|
||||
|
||||
# Full Node Connection
|
||||
RPC_BASEURL=http://172.17.0.1:8332
|
||||
RPC_USERNAME=bitcoin
|
||||
RPC_PASSWORD=password
|
||||
|
||||
# x402 payments required to access this API?
|
||||
X402_ENABLED=false
|
||||
|
||||
# Fulcrum Indexer
|
||||
FULCRUM_API=http://192.168.2.127:3001
|
||||
|
||||
# SLP Indexer
|
||||
SLP_INDEXER_API=http://192.168.2.127:5010
|
||||
SLP_INDEXER_API=http://192.168.2.127:5010
|
||||
|
||||
# END INFRASTRUCTURE SETUP
|
||||
|
||||
|
||||
# START ACCESS CONTROL
|
||||
|
||||
# x402 payments required to access this API?
|
||||
X402_ENABLED=false
|
||||
|
||||
# Basic Authentication required to access this API?
|
||||
USE_BASIC_AUTH=false
|
||||
|
||||
# END ACCESS CONTROL
|
||||
+34
-8
@@ -15,7 +15,8 @@ import { dirname, join } from 'path'
|
||||
import config from '../src/config/index.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import wlogger from '../src/adapters/wlogger.js'
|
||||
import { buildX402Routes, getX402Settings } from '../src/config/x402.js'
|
||||
import { buildX402Routes, getX402Settings, getBasicAuthSettings } from '../src/config/x402.js'
|
||||
import { basicAuthMiddleware } from '../src/middleware/basic-auth.js'
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config()
|
||||
@@ -60,6 +61,7 @@ class Server {
|
||||
const app = express()
|
||||
|
||||
const x402Settings = getX402Settings()
|
||||
const basicAuthSettings = getBasicAuthSettings()
|
||||
|
||||
// MIDDLEWARE START
|
||||
app.use(express.json())
|
||||
@@ -72,22 +74,46 @@ class Server {
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
|
||||
}))
|
||||
|
||||
// Wrap all endpoints in x402 middleware. This handles payments for the API calls.
|
||||
if (x402Settings.enabled) {
|
||||
// Apply basic auth middleware if enabled
|
||||
// This must run before x402 middleware to set req.locals.basicAuthValid
|
||||
if (basicAuthSettings.enabled) {
|
||||
wlogger.info('Basic auth middleware enabled')
|
||||
app.use(basicAuthMiddleware)
|
||||
}
|
||||
|
||||
// Apply x402 middleware based on configuration
|
||||
// Logic:
|
||||
// - If X402_ENABLED=false OR USE_BASIC_AUTH=false: Don't apply x402 (no rate limits)
|
||||
// - If X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally (bypass if basic auth valid)
|
||||
|
||||
// Only apply x402 if both are enabled
|
||||
if (x402Settings.enabled && basicAuthSettings.enabled) {
|
||||
// X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally
|
||||
const routes = buildX402Routes(this.config.apiPrefix)
|
||||
const facilitatorOptions = x402Settings.facilitatorUrl
|
||||
? { url: x402Settings.facilitatorUrl }
|
||||
: undefined
|
||||
|
||||
wlogger.info(`x402 middleware enabled; enforcing ${x402Settings.priceSat} satoshis per request`)
|
||||
app.use(
|
||||
x402PaymentMiddleware(
|
||||
wlogger.info(`x402 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceSat} satoshis per request (unless basic auth provided)`)
|
||||
|
||||
// Create conditional x402 middleware that bypasses if basic auth is valid
|
||||
const conditionalX402Middleware = (req, res, next) => {
|
||||
// If basic auth is valid, bypass x402
|
||||
if (req.locals?.basicAuthValid === true) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// Otherwise, apply x402 middleware
|
||||
return x402PaymentMiddleware(
|
||||
x402Settings.serverAddress,
|
||||
routes,
|
||||
facilitatorOptions
|
||||
)
|
||||
)
|
||||
)(req, res, next)
|
||||
}
|
||||
|
||||
app.use(conditionalX402Middleware)
|
||||
} else {
|
||||
// X402_ENABLED=false OR USE_BASIC_AUTH=false: No x402 middleware
|
||||
wlogger.info('x402 middleware disabled via configuration')
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
@@ -38,6 +38,11 @@ const x402Defaults = {
|
||||
priceSat
|
||||
}
|
||||
|
||||
const basicAuthDefaults = {
|
||||
enabled: normalizeBoolean(process.env.USE_BASIC_AUTH, false),
|
||||
token: process.env.BASIC_AUTH_TOKEN || ''
|
||||
}
|
||||
|
||||
export default {
|
||||
// Server port
|
||||
port: process.env.PORT || 5942,
|
||||
@@ -80,6 +85,8 @@ export default {
|
||||
|
||||
x402: x402Defaults,
|
||||
|
||||
basicAuth: basicAuthDefaults,
|
||||
|
||||
// Version
|
||||
version
|
||||
}
|
||||
|
||||
@@ -41,3 +41,10 @@ export function getX402Settings () {
|
||||
priceSat: config.x402?.priceSat
|
||||
}
|
||||
}
|
||||
|
||||
export function getBasicAuthSettings () {
|
||||
return {
|
||||
enabled: Boolean(config.basicAuth?.enabled),
|
||||
token: config.basicAuth?.token || ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Basic Authentication Middleware
|
||||
|
||||
This middleware validates Bearer tokens from the Authorization header.
|
||||
When a valid token is provided, it sets req.locals.basicAuthValid = true
|
||||
to allow bypassing x402 middleware.
|
||||
*/
|
||||
|
||||
import config from '../config/index.js'
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
/**
|
||||
* Middleware function that validates Bearer token authentication
|
||||
* @param {Object} req - Express request object
|
||||
* @param {Object} res - Express response object
|
||||
* @param {Function} next - Express next middleware function
|
||||
*/
|
||||
export function basicAuthMiddleware (req, res, next) {
|
||||
// Initialize req.locals if it doesn't exist
|
||||
if (!req.locals) {
|
||||
req.locals = {}
|
||||
}
|
||||
|
||||
// Default to false
|
||||
req.locals.basicAuthValid = false
|
||||
|
||||
// Get the configured token
|
||||
const configuredToken = config.basicAuth?.token
|
||||
|
||||
// If no token is configured, skip validation
|
||||
if (!configuredToken) {
|
||||
wlogger.warn('Basic auth enabled but no BASIC_AUTH_TOKEN configured')
|
||||
return next()
|
||||
}
|
||||
|
||||
// Get the Authorization header
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
// If no Authorization header, continue (x402 will handle unauthorized requests)
|
||||
if (!authHeader) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// Check if it's a Bearer token
|
||||
const parts = authHeader.split(' ')
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
return next()
|
||||
}
|
||||
|
||||
const providedToken = parts[1]
|
||||
|
||||
// Compare tokens
|
||||
if (providedToken === configuredToken) {
|
||||
req.locals.basicAuthValid = true
|
||||
wlogger.verbose(`Basic auth validated for request to ${req.path}`)
|
||||
}
|
||||
|
||||
// Always continue to next middleware
|
||||
// If auth failed, x402 middleware will handle the request
|
||||
next()
|
||||
}
|
||||
Reference in New Issue
Block a user