import { AuthProvider } from '../base-provider.js'
import IdentityKey from 'keet-identity-key'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
/**
* KeetIdentityProvider
*
* Authenticates users via Keet identity (BIP39 mnemonic-based):
* - Uses keet-identity-key package for BIP39 derivation
* - Looks for X-Keet-Signature header with signed challenge
* - Public key derived from BIP39 mnemonic
* - Used by hyper-wallet and other Keet-compatible apps
*/
export class KeetIdentityProvider extends AuthProvider {
constructor(registry) {
super(registry)
this.name = 'keet-identity'
}
/**
* Extract Keet signature from request headers
* @param {Object} req - HTTP request
* @returns {Promise<Object|null>} { publicKey, signature, challenge } or null
*/
async extractCredentials(req) {
const keetSignature = req.headers['x-keet-signature']
const keetPublicKey = req.headers['x-keet-publickey']
const keetChallenge = req.headers['x-keet-challenge']
if (!keetSignature || !keetPublicKey || !keetChallenge) {
return null
}
return {
publicKey: keetPublicKey,
signature: keetSignature,
challenge: keetChallenge
}
}
/**
* Authenticate using Keet identity signature
* @param {Object} credentials - { publicKey, signature, challenge, challengeId }
* @returns {Promise<Object>} { identityId, method, verified }
*/
async authenticate(credentials) {
const { publicKey, signature, challenge, challengeId } = credentials
// Verify signature
const isValid = await this.verifySignature(publicKey, signature, challenge)
if (!isValid) {
throw new Error('Invalid Keet signature')
}
// Check if identity exists
let identity = await this.registry.getIdentityByPublicKey(publicKey)
if (!identity) {
// Auto-register new Keet identity with read-only permissions
const { user, identity: newIdentity } = await this.registry.autoRegister({
identityId: publicKey,
method: 'keet-identity',
credentials: { publicKey }
})
console.log(`[KeetIdentity] Auto-registered: ${publicKey.substring(0, 8)}... → user ${user.userId}`)
return {
identityId: newIdentity.identityId,
method: 'keet-identity',
verified: true,
credentials: { publicKey }
}
}
// Check if identity is revoked
const isRevoked = await this.registry.isRevoked(identity.identityId)
if (isRevoked) {
throw new Error('Keet identity has been revoked')
}
return {
identityId: identity.identityId,
method: 'keet-identity',
verified: true,
credentials: { publicKey }
}
}
/**
* Verify Keet signature using ed25519
* @param {string} publicKey - Base64 public key
* @param {string} signature - Base64 signature
* @param {string} challenge - Challenge string
* @returns {Promise<boolean>} True if valid
*/
async verifySignature(publicKey, signature, challenge) {
try {
// Convert from base64
const publicKeyBuf = b4a.from(publicKey, 'base64')
const signatureBuf = b4a.from(signature, 'base64')
const challengeBuf = b4a.from(challenge, 'utf8')
// Validate buffer sizes
if (publicKeyBuf.length !== 32) {
console.error('[KeetIdentity] Invalid public key size:', publicKeyBuf.length)
return false
}
if (signatureBuf.length !== 64) {
console.error('[KeetIdentity] Invalid signature size:', signatureBuf.length)
return false
}
// Verify ed25519 signature using hypercore-crypto
const isValid = crypto.verify(challengeBuf, signatureBuf, publicKeyBuf)
if (!isValid) {
console.warn('[KeetIdentity] Signature verification failed')
}
return isValid
} catch (err) {
console.error('[KeetIdentity] Signature verification error:', err)
return false
}
}
/**
* Generate new Keet identity from BIP39 mnemonic
* @param {Object} options - { mnemonic } (optional)
* @returns {Promise<Object>} { publicKey, mnemonic }
*/
async generateIdentity(options = {}) {
// Generate new mnemonic if not provided
const mnemonic = options.mnemonic || IdentityKey.generateMnemonic()
// Derive identity from mnemonic
const identity = IdentityKey.fromMnemonic(mnemonic)
const publicKey = identity.publicKey
return {
publicKey: b4a.toString(publicKey, 'base64'), // 44 chars
mnemonic: mnemonic // BIP39 12/24 word mnemonic
}
}
}