/**
* FILE: lib/auth.cjs
* STATUS: ACTIVE
* RUNTIME: Bare
* MODULE: CommonJS
*
* PURPOSE:
* API authentication system for wallet-server.cjs
* Generates and verifies Bearer tokens for HTTP API access
*
* USED BY:
* - wallet-server.cjs (authentication middleware)
*
* KEY EXPORTS:
* - generateToken() - Generate secure random token
* - saveToken() - Save token to file
* - loadToken() - Load token from file
* - verifyToken() - Verify Bearer token from request
*
* SECURITY:
* - 32-byte random tokens (256-bit security)
* - Token file mode 0600 (owner read/write only)
* - Bearer token authentication (RFC 6750)
*/
const crypto = require('hypercore-crypto')
const fs = require('bare-fs')
const path = require('bare-path')
const os = require('bare-os')
const STORAGE_DIR = path.join(os.homedir(), '.hyper-wallet')
const TOKEN_FILE = path.join(STORAGE_DIR, '.api-token')
/**
* Generate secure random API token
* Uses hypercore-crypto for cryptographically secure random bytes
*
* @returns {string} - Hex-encoded 32-byte token (64 hex characters)
*/
function generateToken() {
const randomBytes = crypto.randomBytes(32)
return randomBytes.toString('hex')
}
/**
* Save API token to file with secure permissions
*
* @param {string} token - API token to save
*/
function saveToken(token) {
// Ensure storage directory exists
if (!fs.existsSync(STORAGE_DIR)) {
fs.mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 })
}
// Write token with mode 0600 (owner read/write only)
fs.writeFileSync(TOKEN_FILE, token, { mode: 0o600 })
}
/**
* Load API token from file
*
* @returns {string|null} - Token if exists, null otherwise
*/
function loadToken() {
if (!fs.existsSync(TOKEN_FILE)) {
return null
}
try {
return fs.readFileSync(TOKEN_FILE, 'utf8').trim()
} catch (err) {
console.error('Error loading API token:', err.message)
return null
}
}
/**
* Verify Bearer token from HTTP request
*
* @param {object} req - HTTP request object
* @returns {boolean} - True if token is valid, false otherwise
*/
function verifyToken(req) {
// Get Authorization header
const authHeader = req.headers.authorization
// Check if header exists and has Bearer scheme
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false
}
// Extract token (remove "Bearer " prefix)
const token = authHeader.slice(7)
// Load stored token
const storedToken = loadToken()
// Verify token matches
if (!storedToken) {
return false
}
// Use constant-time comparison to prevent timing attacks
// Note: crypto.timingSafeEqual doesn't exist in Bare runtime
// Implement manual constant-time comparison
if (token.length !== storedToken.length) {
return false
}
let diff = 0
for (let i = 0; i < token.length; i++) {
diff |= token.charCodeAt(i) ^ storedToken.charCodeAt(i)
}
return diff === 0
}
/**
* Delete API token (for security - regenerate on demand)
*/
function deleteToken() {
if (fs.existsSync(TOKEN_FILE)) {
fs.unlinkSync(TOKEN_FILE)
}
}
/**
* Check if API token exists
*
* @returns {boolean} - True if token file exists
*/
function tokenExists() {
return fs.existsSync(TOKEN_FILE)
}
module.exports = {
generateToken,
saveToken,
loadToken,
verifyToken,
deleteToken,
tokenExists
}