gocap

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 15 Imported by: 0

README

gocap

English version: README.en.md

Go 版本的 CAP 服务端实现,参考 tiagozip/cap 的核心思路,提供挑战生成与验证能力,并支持可插拔存储。

特性

  • 挑战生成:基于 FNV-1a + xorshift 派生盐值与目标前缀
  • 挑战验证:基于 SHA-256 前缀匹配验证 PoW 解答
  • 令牌签发:内置 JWT HS256
  • 存储可插拔:通过统一 Storage 接口支持内存或自定义后端
  • 扩展示例:提供基于 Redis 的存储实现文档示例,便于自行接入

安装

go get github.com/fnoopv/gocap

快速开始

1. 初始化实例
package main

import (
    "context"
 "fmt"
 "log"

 "github.com/fnoopv/gocap"
)

func main() {
 secret := []byte("meow-meow-meow-meow-dont-use-in-prod-meow-meow-meow-meow")
    ctx := context.Background()

    capInstance, err := gocap.NewCap(gocap.CapOptions{
        Secret:              secret,
        Storage:             gocap.NewMemoryStorage(),
        ChallengeCount:      50,
        ChallengeSize:       32,
        ChallengeDifficulty: 4,
    })
    if err != nil {
        log.Fatal(err)
    }

 challenge, err := capInstance.CreateChallenge(ctx)
 if err != nil {
  log.Fatal(err)
 }

 fmt.Println("token:", challenge.Token)
 fmt.Println("expires:", challenge.Expires)
}
2. 兑换挑战并验证令牌
package main

import (
    "context"
 "log"

 "github.com/fnoopv/gocap"
)

func main() {
 secret := []byte("meow-meow-meow-meow-dont-use-in-prod-meow-meow-meow-meow")
    ctx := context.Background()

    capInstance, err := gocap.NewCap(gocap.CapOptions{
        Secret:  secret,
        Storage: gocap.NewMemoryStorage(),
    })
    if err != nil {
        log.Fatal(err)
    }

 // token 和 solutions 通常来自客户端,solutions 需为对应 challenge 的 PoW 解
 proof := gocap.ValidateBody{
  Token:     "<challenge-token>",
  Solutions: []int64{1, 2, 3},
 }

 result, err := capInstance.RedeemChallenge(ctx, proof)
 if err != nil {
  log.Fatal(err)
 }
 if !result.Success {
  log.Fatalf("validation failed: %s", result.Reason)
 }

 if vr := capInstance.ValidateToken(ctx, result.Token); !vr.Success {
        log.Fatal("validation token is invalid")
    }

 log.Printf("validation token: %s", result.Token)
}

存储抽象接口

Storage 接口包含以下 5 个能力方法:

  1. 设置质询令牌:SetChallengeToken
  2. 获取质询令牌过期时间:GetChallengeTokenExpiry
  3. 设置验证令牌:SetValidationToken
  4. 获取验证令牌过期时间:GetValidationTokenExpiry
  5. 清理过期数据:CleanupExpired

更多存储示例见:

运行测试

go test ./...

注意事项

  • secret 建议使用高强度随机字节,长度至少 16 字节。
  • 可使用 OpenSSL 生成高强度 secret(示例生成 32 字节并转为十六进制):openssl rand -hex 32
  • 生产环境建议启用持久化存储并配置重放保护策略。
  • 如需接入 Redis,可参考 examples/storage 目录中的示例文档自行实现 Storage

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cap added in v0.2.0

type Cap struct {
	// contains filtered or unexported fields
}

Cap is an instance-based API for creating and redeeming challenges.

func NewCap added in v0.2.0

func NewCap(opts CapOptions) (*Cap, error)

NewCap creates a Cap instance with storage and runtime options.

func (*Cap) CreateChallenge added in v0.2.0

func (c *Cap) CreateChallenge(ctx context.Context) (ChallengeResult, error)

CreateChallenge creates challenge parameters and a signed challenge token.

func (*Cap) RedeemChallenge added in v0.2.0

func (c *Cap) RedeemChallenge(ctx context.Context, proof ValidateBody) (ValidateResult, error)

RedeemChallenge verifies PoW solutions and returns a validation result. On success, result.Success is true and result.Token / result.Expires are set. On validation failure, result.Success is false and result.Reason describes the error. Internal errors (storage, config) are returned as error.

func (*Cap) ValidateToken added in v0.2.0

func (c *Cap) ValidateToken(ctx context.Context, token string) ValidateResult

ValidateToken reports whether a validation token currently exists and is not expired.

type CapOptions added in v0.2.0

type CapOptions struct {
	// Secret is used to sign and verify challenge tokens.
	Secret []byte
	// Storage persists challenge and validation tokens.
	Storage Storage
	// ChallengeCount sets the number of PoW items.
	ChallengeCount int
	// ChallengeSize sets the salt length in hex characters.
	ChallengeSize int
	// ChallengeDifficulty sets the target prefix length in hex characters.
	ChallengeDifficulty int
	// ChallengeTTL controls challenge token lifetime.
	ChallengeTTL time.Duration
	// Scope binds the challenge to a logical site or action.
	Scope string
	// Extra is embedded in the challenge token payload.
	Extra map[string]any
	// TokenTTL controls validation token lifetime.
	TokenTTL time.Duration
	// ConsumeNonce performs replay protection for challenge signatures.
	ConsumeNonce func(ctx context.Context, signatureHex string, ttl time.Duration) (bool, error)
	// SignToken overrides the default validation token format.
	SignToken func(data RedeemTokenData) (string, error)
}

CapOptions configures a Cap instance.

type ChallengeResult

type ChallengeResult struct {
	// Challenge contains the challenge parameters.
	Challenge ChallengeSpec `json:"challenge"`
	// Token is the signed challenge token.
	Token string `json:"token"`
	// Expires is the challenge expiration time in Unix milliseconds.
	Expires int64 `json:"expires"`
	// Instrumentation contains the optional widget script.
	Instrumentation string `json:"instrumentation,omitempty"`
}

ChallengeResult is the public challenge payload returned by GenerateChallenge.

type ChallengeSpec

type ChallengeSpec struct {
	// C is the number of proofs the client must solve.
	C int `json:"c"`
	// S is the salt length in hex characters.
	S int `json:"s"`
	// D is the target prefix difficulty in hex characters.
	D int `json:"d"`
}

ChallengeSpec describes the challenge parameters sent to the client.

type GenerateOptions

type GenerateOptions struct {
	// ChallengeCount sets the number of PoW items.
	ChallengeCount int
	// ChallengeSize sets the salt length in hex characters.
	ChallengeSize int
	// ChallengeDifficulty sets the target prefix length in hex characters.
	ChallengeDifficulty int
	// Expires sets the challenge lifetime.
	Expires time.Duration
	// Scope binds the challenge to a logical site or action.
	Scope string
	// Extra is embedded in the challenge token payload.
	Extra map[string]any
	// Storage receives challenge token persistence callbacks.
	Storage Storage
}

GenerateOptions configures challenge generation.

type MemoryStorage

type MemoryStorage struct {
	// contains filtered or unexported fields
}

MemoryStorage is an in-memory Storage implementation.

func NewMemoryStorage

func NewMemoryStorage() *MemoryStorage

NewMemoryStorage creates a new in-memory Storage implementation.

func (*MemoryStorage) CleanupExpired

func (s *MemoryStorage) CleanupExpired(_ context.Context) error

CleanupExpired removes expired entries from the in-memory store.

func (*MemoryStorage) GetChallengeTokenExpiry

func (s *MemoryStorage) GetChallengeTokenExpiry(_ context.Context, token string) (time.Time, bool, error)

GetChallengeTokenExpiry returns a stored challenge token expiry.

func (*MemoryStorage) GetValidationTokenExpiry

func (s *MemoryStorage) GetValidationTokenExpiry(_ context.Context, token string) (time.Time, bool, error)

GetValidationTokenExpiry returns a stored validation token expiry.

func (*MemoryStorage) SetChallengeToken

func (s *MemoryStorage) SetChallengeToken(_ context.Context, token string, expiresAt time.Time) error

SetChallengeToken stores a challenge token in memory.

func (*MemoryStorage) SetValidationToken

func (s *MemoryStorage) SetValidationToken(_ context.Context, token string, expiresAt time.Time) error

SetValidationToken stores a validation token in memory.

type RedeemTokenData

type RedeemTokenData struct {
	// Scope is the original challenge scope.
	Scope *string `json:"scope"`
	// Expires is the redeem token expiration time in Unix milliseconds.
	Expires int64 `json:"expires"`
	// Iat is the original issue-at time in Unix milliseconds.
	Iat int64 `json:"iat,omitempty"`
}

RedeemTokenData contains the fields needed to mint a validation token.

type Storage

type Storage interface {
	// SetChallengeToken stores a challenge token and its expiration time.
	SetChallengeToken(ctx context.Context, token string, expiresAt time.Time) error
	// GetChallengeTokenExpiry returns the expiration time for a challenge token.
	GetChallengeTokenExpiry(ctx context.Context, token string) (time.Time, bool, error)
	// SetValidationToken stores a validation token and its expiration time.
	SetValidationToken(ctx context.Context, token string, expiresAt time.Time) error
	// GetValidationTokenExpiry returns the expiration time for a validation token.
	GetValidationTokenExpiry(ctx context.Context, token string) (time.Time, bool, error)
	// CleanupExpired removes expired challenge and validation tokens.
	CleanupExpired(ctx context.Context) error
}

type ValidateBody

type ValidateBody struct {
	// Token is the challenge token issued by GenerateChallenge.
	Token string `json:"token"`
	// Solutions contains the PoW solutions in challenge order.
	Solutions []int64 `json:"solutions"`
	// Instr contains optional instrumentation results.
	Instr any `json:"instr,omitempty"`
	// Blocked reports that instrumentation blocked the page.
	Blocked bool `json:"instr_blocked,omitempty"`
	// Timeout reports that instrumentation timed out.
	Timeout bool `json:"instr_timeout,omitempty"`
}

ValidateBody is the request body used to validate a redeemed challenge.

type ValidateOptions

type ValidateOptions struct {
	// Scope must match the original challenge scope when set.
	Scope string
	// TokenTTL controls the lifetime of the redeem token.
	TokenTTL time.Duration
	// Storage receives validation token persistence callbacks.
	Storage Storage
	// ConsumeNonce performs replay protection for the challenge token signature.
	ConsumeNonce func(ctx context.Context, signatureHex string, ttl time.Duration) (bool, error)
	// SignToken allows callers to override the default validation token format.
	SignToken func(data RedeemTokenData) (string, error)
}

ValidateOptions configures challenge validation and redemption behavior.

type ValidateResult

type ValidateResult struct {
	// Success reports whether validation succeeded.
	Success bool `json:"success"`
	// Reason explains a validation failure.
	Reason string `json:"reason,omitempty"`
	// Error carries a lower-level error message when available.
	Error string `json:"error,omitempty"`
	// InstrErr reports instrumentation-specific failures.
	InstrErr bool `json:"instr_error,omitempty"`
	// Token is the issued redeem token on success.
	Token string `json:"token,omitempty"`
	// TokenKey is the lookup key for the default redeem token format.
	TokenKey *string `json:"tokenKey,omitempty"`
	// Expires is the redeem token expiration time in Unix milliseconds.
	Expires int64 `json:"expires,omitempty"`
	// Scope is the validated scope, if any.
	Scope *string `json:"scope"`
	// Iat is the original challenge issue-at time in Unix milliseconds.
	Iat *int64 `json:"iat,omitempty"`
}

ValidateResult is the outcome of ValidateChallenge.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL