Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,5 @@ jobs:
working-directory: ./examples/js-libp2p-client-and-node-server/
- run: ../test-runner/test-runner
working-directory: ./examples/two-js-peers
- run: ./runTest.sh
working-directory: ./examples/peer-id-auth
1 change: 1 addition & 0 deletions examples/peer-id-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go-node
15 changes: 15 additions & 0 deletions examples/peer-id-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# HTTP Peer ID Auth

This folder contains a simple HTTP server and client that in both Go and JS that
will run the HTTP Peer ID Auth protocol to mutually authenticate the client and
server's peer ID.

Use this as an example for how to authenticate your server to libp2p clients (or
any client that wants to authenticate a peer id). You can also use this to
authenticate your client's peer id to a libp2p+HTTP server.

## Tests

There is a simple tests that makes sure authentication works using a Go
{client,server} and a NodeJS {client,server}. In lieu, of a larger interop test
environment, this serves for now (pun intended).
36 changes: 36 additions & 0 deletions examples/peer-id-auth/go-peer/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module github.com/libp2p/js-libp2p-http-fetch/examples/peer-id-auth

go 1.22.1

require github.com/libp2p/go-libp2p v0.35.2

require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.13.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.5.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

// TODO replace with next released version
replace github.com/libp2p/go-libp2p => github.com/libp2p/go-libp2p v0.36.3-0.20241008164053-b198a5146239
261 changes: 261 additions & 0 deletions examples/peer-id-auth/go-peer/go.sum

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions examples/peer-id-auth/go-peer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"crypto/rand"
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
libp2phttp "github.com/libp2p/go-libp2p/p2p/http"
httpauth "github.com/libp2p/go-libp2p/p2p/http/auth"
)

func main() {
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
log.Fatalf("failed to generate key: %v", err)
}

args := os.Args[1:]
if len(args) == 1 && args[0] == "client" {
log.Printf("client connecting to server on localhost:8001")
err := runClient(privKey)
if err != nil {
log.Fatalf("client failed: %v", err)
}
return
}

err = runServer(privKey)
if err != nil {
log.Fatalf("server failed: %v", err)
}
}

func runServer(privKey crypto.PrivKey) error {
id, err := peer.IDFromPrivateKey(privKey)
if err != nil {
return err
}
fmt.Println("Server ID:", id)

wellKnown := &libp2phttp.WellKnownHandler{}
http.Handle(libp2phttp.WellKnownProtocols, wellKnown)
auth := &httpauth.ServerPeerIDAuth{PrivKey: privKey, TokenTTL: time.Hour, NoTLS: true, ValidHostnameFn: func(hostname string) bool { return true }}
http.Handle("/auth", auth)
wellKnown.AddProtocolMeta(httpauth.ProtocolID, libp2phttp.ProtocolMeta{Path: "/auth"})
auth.Next = func(clientID peer.ID, w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/log-my-id" {
fmt.Println("Client ID:", clientID)
}
w.WriteHeader(200)
}
http.Handle("/log-my-id", auth)
wellKnown.AddProtocolMeta("/log-my-id/1", libp2phttp.ProtocolMeta{Path: "/log-my-id"})

log.Printf("server listening on :8001")
return http.ListenAndServe("127.0.0.1:8001", nil)
}

func runClient(privKey crypto.PrivKey) error {
auth := httpauth.ClientPeerIDAuth{PrivKey: privKey}
req, err := http.NewRequest("GET", "http://localhost:8001/auth", nil)
if err != nil {
return err
}
serverID, _, err := auth.AuthenticatedDo(http.DefaultClient, req)
if err != nil {
return err
}
fmt.Println("Server ID:", serverID)

myID, err := peer.IDFromPrivateKey(privKey)
if err != nil {
return err
}
fmt.Println("Client ID:", myID.String())

req, err = http.NewRequest("GET", "http://localhost:8001/log-my-id", nil)
if err != nil {
return err
}
_, _, err = auth.AuthenticatedDo(http.DefaultClient, req)
if err != nil {
return err
}

return nil
}
86 changes: 86 additions & 0 deletions examples/peer-id-auth/node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { unmarshalPrivateKey } from '@libp2p/crypto/keys'
import { WellKnownHandler, WELL_KNOWN_PROTOCOLS } from '@libp2p/http-fetch/well-known-handler.js'
import { ClientAuth, HTTPPeerIDAuthProto, ServerAuth } from '@libp2p/http-fetch/auth.js'
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { createEd25519PeerId } from '@libp2p/peer-id-factory'

const myID = await createEd25519PeerId()
const privKey = await unmarshalPrivateKey(myID.privateKey)
const wellKnownHandler = new WellKnownHandler()

const args = process.argv.slice(2)
if (args.length === 1 && args[0] === 'client') {
// Client mode
const client = new ClientAuth(privKey)
const observedPeerID = await client.authenticateServer(fetch, "localhost:8001", "http://localhost:8001/auth")
console.log("Server ID:", observedPeerID.toString())

const authenticatedReq = new Request("http://localhost:8001/log-my-id", {
headers: {
Authorization: client.bearerAuthHeader("localhost:8001")
}
})
await fetch(authenticatedReq)
console.log("Client ID:", myID.toString())
process.exit(0)
}
// Server mode

const httpServerAuth = new ServerAuth(privKey, (_) => true, { logger: console })

const app = new Hono()
app.get(WELL_KNOWN_PROTOCOLS, async (c) => {
return wellKnownHandler.handleRequest(c.req)
})

// Register HTTP ping protocol
app.all('/auth', (c) => {
return httpServerAuth.httpHandler(addHeadersProxy(c.req))
})
wellKnownHandler.registerProtocol(HTTPPeerIDAuthProto, "/auth")

app.all('/log-my-id', async (c) => {
try {
const id = await httpServerAuth.unwrapBearerToken("localhost:8001", c.req.header('Authorization'))
console.log("Client ID:", id.toString())
} catch (e) {
console.error(e)
return c.text(e.message, { status: 400 })
}
c.status(200)
})
wellKnownHandler.registerProtocol("/log-my-id/1", "/log-my-id")

const server = serve({
fetch: app.fetch,
port: 8001,
hostname: '127.0.0.1',
})

console.log("Server ID:", myID.toString())

// wait for SIGINT
await new Promise(resolve => process.on('SIGINT', resolve))

// Stop the http server
server.close()

// Proxy helper to handle the difference in how the standard Request type
// exposes headers and how hono's Request type exposes headers.
// The standard Request type exposes headers as a Headers object, while
// hono's Request type has a function `header(name: string): string | null` to get the header
function addHeadersProxy(req) {
return new Proxy(req, {
get: (target, prop) => {
if (prop === 'headers') {
return {
get: (header) => {
return req.header(header)
}
}
}
return target[prop]
}
})
}
59 changes: 59 additions & 0 deletions examples/peer-id-auth/runTest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail

# A simple script that runs the auth flow using both Go and NodeJS servers and clients.
# The server prints its peer ID to stdout.
# The client prints the server's peer ID to stdout.
# If both stdout match, the test passes. If they differ the test fails.
# Exit code 0 indicates success.

# Function to cleanup temporary directory
cleanup() {
rm -rf "$TMPDIR"
}

stop_server() {
local pid=$1
kill "$pid"
wait "$pid" > /dev/null 2>&1 || true
}

TMPDIR=$(mktemp -d)
trap cleanup EXIT

# Build Go code
(cd go-peer && go build -o ../go-node main.go)

GO_SERVER="./go-node"
GO_CLIENT="./go-node client"

NODE_SERVER="node node.mjs"
NODE_CLIENT="node node.mjs client"

# Define server arrays
SERVERS=("$GO_SERVER" "$NODE_SERVER")
CLIENTS=("$GO_CLIENT" "$NODE_CLIENT")

for server in "${SERVERS[@]}"; do
for client in "${CLIENTS[@]}"; do
echo "Running server='$server' client='$client'"
$server > "$TMPDIR/server.out" &
SERVER_PID=$!
sleep 1
eval $client > "$TMPDIR/client.out"
stop_server "$SERVER_PID"

if ! diff "$TMPDIR/server.out" "$TMPDIR/client.out"; then
echo "Outputs differ"
echo "Server:"
cat "$TMPDIR/server.out"
echo "Client:"
cat "$TMPDIR/client.out"
echo "Diff:"
diff -u "$TMPDIR/server.out" "$TMPDIR/client.out"
exit 1
fi
done
done

exit 0
16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
"./ping.js": {
"types": "./dist/src/ping.d.ts",
"import": "./dist/src/ping.js"
},
"./auth.js": {
"types": "./dist/src/auth/auth.d.ts",
"import": "./dist/src/auth/auth.js"
},
"./well-known-handler.js": {
"types": "./dist/src/well-known-handler.d.ts",
"import": "./dist/src/well-known-handler.js"
}
},
"eslintConfig": {
Expand Down Expand Up @@ -145,16 +153,20 @@
"dependencies": {
"@libp2p/interface": "^1.2.0",
"@libp2p/interface-internal": "^1.1.0",
"@libp2p/crypto": "^4.1.2",
"@libp2p/peer-id": "^4.1.3",
"@multiformats/multiaddr": "^12.3.0",
"@multiformats/multiaddr-to-uri": "^10.1.0",
"@perseveranza-pets/milo": "^0.2.1",
"p-defer": "^4.0.1",
"uint8arraylist": "^2.4.8"
"uint8-varint": "^2.0.4",
"uint8arraylist": "^2.4.8",
"uint8arrays": "^5.1.0"
},
"devDependencies": {
"@libp2p/interface-compliance-tests": "^5.4.1",
"@libp2p/logger": "^4.0.10",
"@libp2p/peer-id": "^4.1.3",
"@libp2p/peer-id-factory": "^4.1.2",
"aegir": "^43.0.1",
"it-pair": "^2.0.6",
"libp2p": "^1.6.0",
Expand Down
3 changes: 3 additions & 0 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { PeerIDAuthScheme, HTTPPeerIDAuthProto } from './common.js'
export { ClientAuth } from './client.js'
export { ServerAuth } from './server.js'
Loading
Loading