import { BaseProvider } from '../base-provider.js'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
/**
* BearerTokenProvider — API token authentication
*
* Manages bearer tokens with embedded permissions.
* Tokens are stored as SHA256 hashes for security.
*/
export class BearerTokenProvider extends BaseProvider {
constructor (identityRegistry, opts = {}) {
super('bearer-token')
this.registry = identityRegistry
// Use separate db for tokens (not Autobase view)
this.db = opts.db || (identityRegistry && identityRegistry.db) || null
}
async extractCredentials (req) {
const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) return null
return { token: authHeader.substring(7) }
}
async authenticate (credentials) {
const { token } = credentials
const tokenData = await this.validateToken(token)
if (!tokenData) throw new Error('Invalid token')
return {
identityId: `token:${tokenData.hash.substring(0, 16)}`,
method: 'bearer-token',
credentials: { username: tokenData.username, tokenHash: tokenData.hash },
permissions: tokenData.permissions || ['read'],
resources: tokenData.resources || ['*']
}
}
/**
* Create a new bearer token
*/
async createToken (opts = {}) {
const token = b4a.toString(crypto.randomBytes(32), 'hex')
const hash = this._hashToken(token)
const tokenData = {
hash,
username: opts.username || 'anonymous',
description: opts.description || '',
permissions: opts.permissions || ['read'],
resources: opts.resources || ['*'],
createdAt: Date.now(),
expiresAt: opts.expiresAt || null
}
if (this.db) {
await this.db.put(`token:${hash}`, tokenData)
}
return { token, ...tokenData }
}
/**
* Validate a token
*/
async validateToken (token) {
if (!this.db) return null
const hash = this._hashToken(token)
const entry = await this.db.get(`token:${hash}`)
if (!entry) return null
const data = entry.value
if (data.expiresAt && data.expiresAt < Date.now()) return null
if (data.revoked) return null
return data
}
/**
* Revoke a token
*/
async revokeToken (tokenHash) {
if (!this.db) return
const entry = await this.db.get(`token:${tokenHash}`)
if (entry) {
entry.value.revoked = true
entry.value.revokedAt = Date.now()
await this.db.put(`token:${tokenHash}`, entry.value)
}
}
/**
* List all tokens
*/
async listAllTokens () {
if (!this.db) return []
const tokens = []
for await (const entry of this.db.createReadStream({
gte: 'token:',
lt: 'token:\xff'
})) {
tokens.push(entry.value)
}
return tokens
}
/**
* Get tokens for specific user
*/
async getUserTokens (username) {
const all = await this.listAllTokens()
return all.filter(t => t.username === username)
}
_hashToken (token) {
// Use hypercore-crypto for hashing (works in both Node and Bare)
const data = b4a.from(token)
return b4a.toString(crypto.data(data), 'hex')
}
}
export default BearerTokenProvider