OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / lib / auth / providers / noise-key.js

114 lines · javascript ref:
/**
 * NoiseKeyProvider
 *
 * Authenticates P2P connections by their Noise protocol public key.
 * The Noise key is verified at the transport layer (Hyperswarm),
 * so by the time we see it, it's already authenticated — we just
 * need to look up the User it maps to.
 *
 * This provider is NOT used for HTTP requests (no extractCredentials).
 * Instead, it's called directly by the P2P admin handler.
 *
 * All reads/writes go through IdentityRegistry methods (Autobase-backed).
 */

export class NoiseKeyProvider {
  constructor (registry) {
    this.registry = registry
    this.name = 'noise-key'
  }

  /**
   * Authenticate by Noise public key (hex string)
   * Called directly by P2P handlers, not via HTTP middleware.
   *
   * @param {string} publicKeyHex - Noise public key as hex
   * @returns {Promise<Object|null>} Auth result or null
   */
  async authenticateByKey (publicKeyHex) {
    const identity = await this.registry.getIdentityByNoiseKey(publicKeyHex)
    if (!identity) return null

    // Check if identity is revoked
    if (identity.revoked) return null

    const user = await this.registry.getUser(identity.userId)
    if (!user) return null

    return {
      userId: user.userId,
      user,
      identityId: identity.identityId,
      method: 'noise-key',
      verified: true
    }
  }

  /**
   * Register a Noise key for a user
   *
   * @param {string} publicKeyHex - Noise public key as hex
   * @param {string} userId - User ID to link to
   * @returns {Promise<Object>} Created identity
   */
  async register (publicKeyHex, userId) {
    // Check for collision — key already linked to a different user
    const existing = await this.registry.getIdentityByNoiseKey(publicKeyHex)
    if (existing) {
      if (existing.userId && existing.userId !== userId) {
        throw new Error('Noise key is already linked to a different user')
      }
      // Already linked to this user — return existing
      if (existing.userId === userId) {
        return existing
      }
    }

    const identityId = `noise:${publicKeyHex}`

    // Create identity via registry (goes through Autobase)
    const identity = await this.registry.createIdentity({
      identityId,
      method: 'noise-key',
      userId,
      credentials: { publicKey: publicKeyHex }
    })

    // Link to user (adds to user.identities array)
    await this.registry.linkIdentity(identityId, userId)

    console.log(`[NoiseKey] Registered ${publicKeyHex.slice(0, 16)}... for user ${userId}`)
    return identity
  }

  /**
   * Unregister a Noise key
   *
   * @param {string} publicKeyHex - Noise public key as hex
   * @returns {Promise<boolean>} True if removed
   */
  async unregister (publicKeyHex) {
    const identity = await this.registry.getIdentityByNoiseKey(publicKeyHex)
    if (!identity) return false

    // Unlink from user and revoke
    try {
      await this.registry.unlinkIdentity(identity.identityId)
    } catch {
      // May already be unlinked
    }

    try {
      await this.registry.revokeIdentity(identity.identityId, {
        reason: 'Noise key unregistered',
        revokedBy: 'system'
      })
    } catch {
      // May already be revoked
    }

    console.log(`[NoiseKey] Unregistered ${publicKeyHex.slice(0, 16)}...`)
    return true
  }
}