Back to Documentation
Go Documentation

Go Documentation

Go 1.18+ integration guide

Official GuideProduction Ready

Go SDK Guide

Installation

bash
go mod init your-project
go get github.com/go-resty/resty/v2

Basic Usage

go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "time"

    "github.com/go-resty/resty/v2"
)

type KeyClaimClient struct {
    apiKey  string
    secret  string
    baseURL string
    client  *resty.Client
}

func NewKeyClaimClient(apiKey, secret, baseURL string) *KeyClaimClient {
    if baseURL == "" {
        baseURL = "https://keyclaim.org/api"
    }
    if secret == "" {
        secret = apiKey
    }
    
    return &KeyClaimClient{
        apiKey:  apiKey,
        secret:  secret,
        baseURL: baseURL,
        client:  resty.New().SetTimeout(30 * time.Second),
    }
}

type ChallengeResponse struct {
    Challenge string `json:"challenge"`
    ExpiresIn int    `json:"expires_in"`
}

type ValidationResponse struct {
    Valid     bool   `json:"valid"`
    Error     string `json:"error,omitempty"`
    Signature string `json:"signature,omitempty"`
}

type CreateChallengeRequest struct {
    Key string `json:"key"`
    TTL int    `json:"ttl"`
}

type ValidateRequest struct {
    Key       string `json:"key"`
    Challenge string `json:"challenge"`
    Response  string `json:"response"`
}

// Step 1: Create a challenge
func (c *KeyClaimClient) CreateChallenge(ttl int) (*ChallengeResponse, error) {
    req := CreateChallengeRequest{
        Key: c.apiKey,
        TTL: ttl,
    }

    var resp ChallengeResponse
    _, err := c.client.R().
        SetBody(req).
        SetResult(&resp).
        Post(c.baseURL + "/challenge/create")

    if err != nil {
        return nil, fmt.Errorf("failed to create challenge: %w", err)
    }

    return &resp, nil
}

// Step 2: Generate response from challenge
// Option A: Simple echo (for testing)
func (c *KeyClaimClient) GenerateResponseSimple(challenge string) string {
    return challenge
}

// Option B: HMAC-SHA256 (recommended)
func (c *KeyClaimClient) GenerateResponseHMAC(challenge string) string {
    mac := hmac.New(sha256.New, []byte(c.secret))
    mac.Write([]byte(challenge))
    return hex.EncodeToString(mac.Sum(nil))
}

// Option C: SHA256 Hash
func (c *KeyClaimClient) GenerateResponseHash(challenge string) string {
    hash := sha256.Sum256([]byte(challenge + c.secret))
    return hex.EncodeToString(hash[:])
}

// Step 3: Validate the response
func (c *KeyClaimClient) Validate(challenge, response string) (*ValidationResponse, error) {
    req := ValidateRequest{
        Key:       c.apiKey,
        Challenge: challenge,
        Response:  response,
    }

    var resp ValidationResponse
    httpResp, err := c.client.R().
        SetBody(req).
        SetResult(&resp).
        Post(c.baseURL + "/challenge/validate")

    if err != nil {
        return &ValidationResponse{Valid: false, Error: err.Error()}, err
    }

    if !httpResp.IsSuccess() {
        resp.Valid = false
        if resp.Error == "" {
            resp.Error = fmt.Sprintf("HTTP %d", httpResp.StatusCode())
        }
    }

    return &resp, nil
}

// Complete example
func (c *KeyClaimClient) ValidateChallenge(challenge, method string) (*ValidationResponse, error) {
    var response string
    
    switch method {
    case "echo":
        response = c.GenerateResponseSimple(challenge)
    case "hmac":
        response = c.GenerateResponseHMAC(challenge)
    case "hash":
        response = c.GenerateResponseHash(challenge)
    default:
        return nil, fmt.Errorf("unknown method: %s", method)
    }
    
    return c.Validate(challenge, response)
}

Usage Example

go
package main

import (
    "fmt"
    "log"
)

func main() {
    client := NewKeyClaimClient(
        "kc_your_api_key",
        "your-secret-key",
        "https://keyclaim.org/api",
    )

    // Create challenge
    challengeResp, err := client.CreateChallenge(30)
    if err != nil {
        log.Fatalf("Failed to create challenge: %v", err)
    }
    
    challenge := challengeResp.Challenge
    fmt.Printf("Challenge: %s\n", challenge)

    // Generate response
    response := client.GenerateResponseHMAC(challenge)
    fmt.Printf("Response: %s\n", response)

    // Validate
    result, err := client.Validate(challenge, response)
    if err != nil {
        log.Fatalf("Validation error: %v", err)
    }
    
    fmt.Printf("Valid: %v\n", result.Valid)
    
    if result.Valid {
        fmt.Println("✓ Challenge validated successfully!")
        if result.Signature != "" {
            fmt.Printf("Signature: %s\n", result.Signature)
        }
    } else {
        fmt.Printf("✗ Validation failed: %s\n", result.Error)
    }
}

Response Generation Methods

Method 1: Echo (Testing)

go
func (c *KeyClaimClient) GenerateResponseEcho(challenge string) string {
    return challenge
}

Method 2: HMAC-SHA256 (Recommended)

go
func (c *KeyClaimClient) GenerateResponseHMAC(challenge string) string {
    mac := hmac.New(sha256.New, []byte(c.secret))
    mac.Write([]byte(challenge))
    return hex.EncodeToString(mac.Sum(nil))
}

Method 3: SHA256 Hash

go
func (c *KeyClaimClient) GenerateResponseHash(challenge string) string {
    hash := sha256.Sum256([]byte(challenge + c.secret))
    return hex.EncodeToString(hash[:])
}

Complete SDK Package

go
// keyclaim.go
package keyclaim

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "time"

    "github.com/go-resty/resty/v2"
)

// ... (all the code from above)

// Helper function for hex encoding
func bytesToHex(data []byte) string {
    return hex.EncodeToString(data)
}