mirror of
https://github.com/sonr-io/common.git
synced 2026-01-12 04:09:13 +00:00
refactor(common): simplify WebAuthn metadata handling
This commit is contained in:
110
Makefile
Normal file
110
Makefile
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Makefile for sonr-io/common
|
||||||
|
# Provides convenient testing and validation commands for ipfs and webauthn modules
|
||||||
|
|
||||||
|
.PHONY: help test test-ipfs test-webauthn test-all coverage coverage-ipfs coverage-webauthn coverage-all lint vet fmt clean
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Available targets:"
|
||||||
|
@echo " make test - Run all tests"
|
||||||
|
@echo " make test-ipfs - Run tests for ipfs module"
|
||||||
|
@echo " make test-webauthn - Run tests for webauthn module"
|
||||||
|
@echo " make test-all - Run all tests with verbose output"
|
||||||
|
@echo " make coverage - Generate coverage report for all modules"
|
||||||
|
@echo " make coverage-ipfs - Generate coverage report for ipfs module"
|
||||||
|
@echo " make coverage-webauthn - Generate coverage report for webauthn module"
|
||||||
|
@echo " make coverage-all - Generate combined coverage report (HTML)"
|
||||||
|
@echo " make lint - Run golangci-lint"
|
||||||
|
@echo " make vet - Run go vet"
|
||||||
|
@echo " make fmt - Format all Go files"
|
||||||
|
@echo " make clean - Clean test cache and coverage files"
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
test:
|
||||||
|
@echo "Running tests for all modules..."
|
||||||
|
@go test ./ipfs/... ./webauthn/...
|
||||||
|
|
||||||
|
# Run tests for ipfs module
|
||||||
|
test-ipfs:
|
||||||
|
@echo "Running tests for ipfs module..."
|
||||||
|
@go test ./ipfs/... -v
|
||||||
|
|
||||||
|
# Run tests for webauthn module
|
||||||
|
test-webauthn:
|
||||||
|
@echo "Running tests for webauthn module..."
|
||||||
|
@go test ./webauthn/... -v
|
||||||
|
|
||||||
|
# Run all tests with verbose output
|
||||||
|
test-all:
|
||||||
|
@echo "Running all tests with verbose output..."
|
||||||
|
@go test -v ./ipfs/... ./webauthn/...
|
||||||
|
|
||||||
|
# Generate coverage report for all modules
|
||||||
|
coverage:
|
||||||
|
@echo "Generating coverage report for all modules..."
|
||||||
|
@go test ./ipfs/... ./webauthn/... -coverprofile=coverage.out
|
||||||
|
@go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
# Generate coverage report for ipfs module
|
||||||
|
coverage-ipfs:
|
||||||
|
@echo "Generating coverage report for ipfs module..."
|
||||||
|
@go test ./ipfs/... -coverprofile=coverage-ipfs.out
|
||||||
|
@go tool cover -func=coverage-ipfs.out
|
||||||
|
|
||||||
|
# Generate coverage report for webauthn module
|
||||||
|
coverage-webauthn:
|
||||||
|
@echo "Generating coverage report for webauthn module..."
|
||||||
|
@go test ./webauthn/... -coverprofile=coverage-webauthn.out
|
||||||
|
@go tool cover -func=coverage-webauthn.out
|
||||||
|
|
||||||
|
# Generate HTML coverage report for all modules
|
||||||
|
coverage-all:
|
||||||
|
@echo "Generating HTML coverage report for all modules..."
|
||||||
|
@go test ./ipfs/... ./webauthn/... -coverprofile=coverage.out
|
||||||
|
@go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@echo "Coverage report generated: coverage.html"
|
||||||
|
|
||||||
|
# Run golangci-lint (requires golangci-lint to be installed)
|
||||||
|
lint:
|
||||||
|
@echo "Running golangci-lint..."
|
||||||
|
@which golangci-lint > /dev/null || (echo "golangci-lint not found. Install from https://golangci-lint.run/usage/install/" && exit 1)
|
||||||
|
@golangci-lint run ./ipfs/... ./webauthn/...
|
||||||
|
|
||||||
|
# Run go vet
|
||||||
|
vet:
|
||||||
|
@echo "Running go vet..."
|
||||||
|
@go vet ./ipfs/...
|
||||||
|
@go vet ./webauthn/...
|
||||||
|
|
||||||
|
# Format all Go files
|
||||||
|
fmt:
|
||||||
|
@echo "Formatting Go files..."
|
||||||
|
@go fmt ./ipfs/...
|
||||||
|
@go fmt ./webauthn/...
|
||||||
|
|
||||||
|
# Clean test cache and coverage files
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning test cache and coverage files..."
|
||||||
|
@go clean -testcache
|
||||||
|
@rm -f coverage.out coverage-ipfs.out coverage-webauthn.out coverage.html
|
||||||
|
@echo "Clean complete"
|
||||||
|
|
||||||
|
# Run tests with race detector
|
||||||
|
test-race:
|
||||||
|
@echo "Running tests with race detector..."
|
||||||
|
@go test -race ./ipfs/... ./webauthn/...
|
||||||
|
|
||||||
|
# Run benchmarks
|
||||||
|
bench:
|
||||||
|
@echo "Running benchmarks..."
|
||||||
|
@go test -bench=. -benchmem ./ipfs/... ./webauthn/...
|
||||||
|
|
||||||
|
# Install test dependencies
|
||||||
|
deps:
|
||||||
|
@echo "Installing dependencies..."
|
||||||
|
@go mod download
|
||||||
|
@go mod tidy
|
||||||
|
|
||||||
|
# Quick check: fmt, vet, and test
|
||||||
|
check: fmt vet test
|
||||||
|
@echo "All checks passed!"
|
||||||
602
README.md
602
README.md
@@ -0,0 +1,602 @@
|
|||||||
|
# Sonr Common Package
|
||||||
|
|
||||||
|
[](https://golang.org)
|
||||||
|
[](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/
|
||||||
|
| ||||||