Files
common/README.md

603 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sonr Common Package
[![Go Version](https://img.shields.io/badge/go-1.25-blue.svg)](https://golang.org)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
The `common` package provides high-level, simplified interfaces for IPFS and WebAuthn functionality, designed for easy integration into external libraries and applications within the Sonr network.
## Features
- **IPFS Integration**: Simple, high-level API for storing and retrieving data from IPFS
- **WebAuthn Support**: Comprehensive WebAuthn credential management and verification
- **Helper Functions**: Convenient wrapper functions that abstract complexity
- **Well-Tested**: Comprehensive test coverage with unit and integration tests
- **Production-Ready**: Battle-tested implementations with proper error handling
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [IPFS Module](#ipfs-module)
- [WebAuthn Module](#webauthn-module)
- [Helper Functions](#helper-functions)
- [Testing](#testing)
- [Examples](#examples)
- [Contributing](#contributing)
## Installation
```bash
go get github.com/sonr-io/common
```
## Quick Start
### IPFS Example
```go
package main
import (
"fmt"
"github.com/sonr-io/common"
)
func main() {
// Check if IPFS daemon is running
if !common.IsIPFSDaemonRunning() {
panic("IPFS daemon is not running")
}
// Store data
data := []byte("Hello, IPFS!")
cid, err := common.StoreData(data)
if err != nil {
panic(err)
}
fmt.Printf("Stored data with CID: %s\n", cid)
// Retrieve data
retrieved, err := common.RetrieveData(cid)
if err != nil {
panic(err)
}
fmt.Printf("Retrieved: %s\n", string(retrieved))
}
```
### WebAuthn Example
```go
package main
import (
"fmt"
"github.com/sonr-io/common"
)
func main() {
// Generate a challenge for WebAuthn ceremony
challenge, err := common.NewChallenge()
if err != nil {
panic(err)
}
fmt.Printf("Challenge: %s\n", challenge)
// Verify origin
origin := "https://example.com"
allowedOrigins := []string{"https://example.com", "https://app.example.com"}
if err := common.VerifyOrigin(origin, allowedOrigins); err != nil {
panic(err)
}
fmt.Println("Origin verified successfully!")
}
```
## IPFS Module
The IPFS module (`ipfs/`) provides a high-level interface for interacting with IPFS nodes.
### Core Functions
#### Client Management
```go
// Create a new IPFS client
client, err := common.NewIPFSClient()
// Or use the panic version for critical scenarios
client := common.MustGetIPFSClient()
// Check if daemon is running
if common.IsIPFSDaemonRunning() {
// Daemon is available
}
```
#### Data Storage
```go
// Store raw bytes
cid, err := common.StoreData([]byte("data"))
// Store a file with metadata
cid, err := common.StoreFile("document.txt", fileData)
// Store multiple files as a folder
files := map[string][]byte{
"file1.txt": []byte("content 1"),
"file2.txt": []byte("content 2"),
}
cid, err := common.StoreFolder(files)
```
#### Data Retrieval
```go
// Retrieve data by CID
data, err := common.RetrieveData("QmXxx...")
// Using client directly for advanced operations
client, _ := common.NewIPFSClient()
exists, err := client.Exists("QmXxx...")
entries, err := client.Ls("QmXxx...")
```
### Advanced IPFS Usage
```go
import "github.com/sonr-io/common/ipfs"
client, _ := ipfs.GetClient()
// Pin content
err := client.Pin("QmXxx...", "my-important-data")
// Unpin content
err := client.Unpin("QmXxx...")
// Check if pinned
pinned, err := client.IsPinned("ipns-name")
// Get node status
status, err := client.NodeStatus()
fmt.Printf("Peer ID: %s\n", status.PeerID)
fmt.Printf("Connected Peers: %d\n", status.ConnectedPeers)
```
## WebAuthn Module
The WebAuthn module (`webauthn/`) provides comprehensive support for WebAuthn authentication.
### Challenge Generation
```go
// Generate a cryptographic challenge (32 bytes, base64url encoded)
challenge, err := common.NewChallenge()
// Get the standard challenge length
length := common.ChallengeLength() // Returns 32
```
### Origin Verification
```go
// Verify origin against allowed list
allowedOrigins := []string{
"https://example.com",
"https://app.example.com:8080",
}
err := common.VerifyOrigin("https://example.com", allowedOrigins)
```
### Base64 URL Encoding
```go
// Encode data to URL-safe base64 (no padding)
encoded := common.EncodeBase64URL([]byte("data"))
// Decode URL-safe base64
decoded, err := common.DecodeBase64URL(encoded)
```
### Credential Management
#### Credential Creation (Registration)
```go
// Unmarshal credential creation response from client
credData := []byte(`{"id":"...","type":"public-key",...}`)
credResponse, err := common.UnmarshalCredentialCreation(credData)
// Parse and validate credential creation
parsedCred, err := common.ParseCredentialCreation(credData)
// Marshal credential for storage
jsonData, err := common.MarshalCredentialCreation(credResponse)
```
#### Credential Assertion (Authentication)
```go
// Unmarshal assertion response from client
assertionData := []byte(`{"id":"...","type":"public-key",...}`)
assertionResponse, err := common.UnmarshalCredentialAssertion(assertionData)
// Parse and validate assertion
parsedAssertion, err := common.ParseCredentialAssertion(assertionData)
// Marshal assertion for storage
jsonData, err := common.MarshalCredentialAssertion(assertionResponse)
```
### Advanced WebAuthn Usage
```go
import "github.com/sonr-io/common/webauthn"
// Create registration options
options := &webauthn.PublicKeyCredentialCreationOptions{
RelyingParty: webauthn.RelyingPartyEntity{
Name: "Example Corp",
ID: "example.com",
},
User: webauthn.UserEntity{
ID: []byte("user-id"),
Name: "user@example.com",
DisplayName: "User Name",
},
Challenge: challenge,
Parameters: []webauthn.CredentialParameter{
{Type: webauthn.PublicKeyCredentialType, Algorithm: -7}, // ES256
},
}
// Verify credential creation
clientDataHash, err := parsedCred.Verify(
storedChallenge,
verifyUser,
verifyUserPresence,
relyingPartyID,
rpOrigins,
rpTopOrigins,
rpTopOriginsVerify,
metadataProvider,
credParams,
)
// Verify credential assertion
err = parsedAssertion.Verify(
storedChallenge,
relyingPartyID,
rpOrigins,
rpTopOrigins,
rpTopOriginsVerify,
appID,
verifyUser,
verifyUserPresence,
credentialPublicKey,
)
```
## Helper Functions
### IPFS Helpers
| Function | Description |
|----------|-------------|
| `NewIPFSClient()` | Create IPFS client with detailed error messages |
| `MustGetIPFSClient()` | Panic version for critical initialization |
| `StoreData(data)` | Store raw bytes and return CID |
| `RetrieveData(cid)` | Retrieve content by CID |
| `IsIPFSDaemonRunning()` | Check if IPFS daemon is accessible |
| `StoreFile(name, data)` | Store file with metadata |
| `StoreFolder(files)` | Store multiple files as a folder |
### WebAuthn Helpers
| Function | Description |
|----------|-------------|
| `NewChallenge()` | Generate 32-byte cryptographic challenge |
| `ChallengeLength()` | Get standard challenge length (32) |
| `VerifyOrigin(origin, allowed)` | Verify origin against allowed list |
| `EncodeBase64URL(data)` | Encode to URL-safe base64 |
| `DecodeBase64URL(encoded)` | Decode URL-safe base64 |
| `UnmarshalCredentialCreation(data)` | Parse credential creation response |
| `MarshalCredentialCreation(ccr)` | Serialize credential creation |
| `ParseCredentialCreation(data)` | Parse and validate creation response |
| `UnmarshalCredentialAssertion(data)` | Parse assertion response |
| `MarshalCredentialAssertion(car)` | Serialize assertion response |
| `ParseCredentialAssertion(data)` | Parse and validate assertion |
## Testing
The package includes a comprehensive Makefile for testing and development tasks.
### Available Make Targets
```bash
# Run all tests
make test
# Run tests for specific modules
make test-ipfs
make test-webauthn
# Generate coverage reports
make coverage # Show coverage summary
make coverage-ipfs # IPFS module coverage
make coverage-webauthn # WebAuthn module coverage
make coverage-all # HTML coverage report
# Code quality
make fmt # Format code
make vet # Run go vet
make lint # Run golangci-lint
# Performance
make bench # Run benchmarks
make test-race # Run tests with race detector
# Maintenance
make clean # Clean test cache and coverage files
make deps # Install dependencies
# Quick check
make check # Run fmt, vet, and test
```
### Running Tests Manually
```bash
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run specific tests
go test -v -run TestNewChallenge
# Run benchmarks
go test -bench=. -benchmem ./...
```
### Test Coverage
Current test coverage:
- **Common package**: 49.4%
- **WebAuthn module**: Comprehensive (50+ tests)
- **IPFS module**: Core functionality tested
## Examples
### Complete IPFS Example
```go
package main
import (
"fmt"
"log"
"github.com/sonr-io/common"
"github.com/sonr-io/common/ipfs"
)
func main() {
// Check daemon availability
if !common.IsIPFSDaemonRunning() {
log.Fatal("IPFS daemon is not running. Start it with: ipfs daemon")
}
// Get client for advanced operations
client, err := common.NewIPFSClient()
if err != nil {
log.Fatal(err)
}
// Store a file
fileData := []byte("This is my important document")
cid, err := common.StoreFile("document.txt", fileData)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Stored file with CID: %s\n", cid)
// Pin the content
if err := client.Pin(cid, "important-document"); err != nil {
log.Fatal(err)
}
fmt.Println("Content pinned successfully")
// Check if content exists
exists, err := client.Exists(cid)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Content exists: %v\n", exists)
// Get node status
status, err := client.NodeStatus()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Node Status:\n")
fmt.Printf(" Peer ID: %s\n", status.PeerID)
fmt.Printf(" Connected Peers: %d\n", status.ConnectedPeers)
fmt.Printf(" Version: %s\n", status.Version)
// Store multiple files as a folder
folder := map[string][]byte{
"readme.md": []byte("# Project\nThis is a test project"),
"main.go": []byte("package main\n\nfunc main() {}"),
"config.yml": []byte("port: 8080\nhost: localhost"),
}
folderCID, err := common.StoreFolder(folder)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Stored folder with CID: %s\n", folderCID)
// List folder contents
entries, err := client.Ls(folderCID)
if err != nil {
log.Fatal(err)
}
fmt.Println("Folder contents:")
for _, entry := range entries {
fmt.Printf(" - %s\n", entry)
}
}
```
### Complete WebAuthn Example
```go
package main
import (
"fmt"
"log"
"github.com/sonr-io/common"
"github.com/sonr-io/common/webauthn"
)
func main() {
// Generate challenge for registration
challenge, err := common.NewChallenge()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Registration Challenge: %s\n", challenge)
// Verify request origin
origin := "https://example.com"
allowedOrigins := []string{
"https://example.com",
"https://app.example.com",
}
if err := common.VerifyOrigin(origin, allowedOrigins); err != nil {
log.Fatalf("Origin verification failed: %v", err)
}
fmt.Println("Origin verified!")
// Simulate receiving credential creation response from client
// In a real scenario, this would come from the browser
credentialJSON := []byte(`{
"id": "base64-credential-id",
"type": "public-key",
"rawId": "base64-raw-id",
"response": {
"clientDataJSON": "base64-client-data",
"attestationObject": "base64-attestation"
}
}`)
// Parse credential creation response
credResponse, err := common.UnmarshalCredentialCreation(credentialJSON)
if err != nil {
log.Printf("Parse error (expected in demo): %v", err)
}
// For authentication, generate a new challenge
authChallenge, err := common.NewChallenge()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Authentication Challenge: %s\n", authChallenge)
// Encode/decode example
secretData := []byte("secret-session-data")
encoded := common.EncodeBase64URL(secretData)
fmt.Printf("Encoded: %s\n", encoded)
decoded, err := common.DecodeBase64URL(encoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Decoded: %s\n", string(decoded))
// Challenge length validation
expectedLength := common.ChallengeLength()
fmt.Printf("Expected challenge length: %d bytes\n", expectedLength)
}
```
## Module Structure
```
common/
 common.go # Helper functions and simplified API
 common_test.go # Comprehensive test suite
 Makefile # Testing and development tasks
 README.md # This file
 go.mod # Go module definition
 ipfs/ # IPFS module
  client.go # IPFS client implementation
  file.go # File operations
  folder.go # Folder operations
 webauthn/ # WebAuthn module
 client.go # WebAuthn client operations
 credential.go # Credential types
 assertion.go # Authentication assertions
 attestation.go # Registration attestation
 challenge.go # Challenge generation
 base64.go # Base64 URL encoding
 decoder.go # JSON decoding utilities
 ... # Additional WebAuthn components
```
## Requirements
- Go 1.25 or higher
- IPFS daemon running (for IPFS operations)
- Dependencies managed via `go.mod`
## Contributing
We welcome contributions! Please follow these guidelines:
1. **Fork** the repository
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
3. **Test** your changes (`make test`)
4. **Format** your code (`make fmt`)
5. **Commit** your changes (`git commit -m 'Add amazing feature'`)
6. **Push** to the branch (`git push origin feature/amazing-feature`)
7. **Open** a Pull Request
### Development Workflow
```bash
# Install dependencies
make deps
# Run tests during development
make test
# Check code quality
make check
# Generate coverage report
make coverage-all
# Run benchmarks
make bench
```
## License
This project is part of the Sonr network. See the LICENSE file for details.
## Support
For issues, questions, or contributions, please visit:
- GitHub: https://github.com/sonr-io/common
- Documentation: https://docs.sonr.io
## Acknowledgments
- Built on [Kubo](https://github.com/ipfs/kubo) for IPFS functionality
- WebAuthn implementation based on [W3C WebAuthn specification](https://www.w3.org/TR/webauthn/)