b64c002a33
apierr: - Add New() constructor, StatusFor() HTTP-status mapping - Add ErrUnauthorized, ErrForbidden, ErrNotFound, ErrConflict predefined errors - Add chi-compatible Middleware for panic(*Error) → JSON recovery - Add apierr_test.go (8 tests; covers New, StatusFor, WriteJSON, Middleware) idgen: - Implement idgen.go: New()/NewString() (UUID v7 via google/uuid v1.6.0) - Implement GenerateCode/CanonicalizeCode/HashCode (Crockford Base32 moved from codes) - Add idgen_test.go (12 tests; UUID v7 ordering/uniqueness + Crockford format/normalization/check) codes: - Refactor generator.go to delegate GenerateCode/Canonicalize/Hash to idgen - All existing codes generator tests continue to pass unchanged server: - Add CONVENTIONS.md covering package structure, error handling, ID generation, database conventions, handler templates, auth context, testing, and logging rules - Move google/uuid from indirect to direct dependency in go.mod Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
42 lines
1.6 KiB
Go
42 lines
1.6 KiB
Go
// Package codes implements the activation-code lifecycle:
|
|
// batch generation, card-store webhook ingestion, idempotent redemption,
|
|
// subscription extension, and CSV export.
|
|
//
|
|
// Security invariant: plaintext codes are NEVER written to the database or
|
|
// to any log. The database stores only SHA-256(canonical_plaintext).
|
|
// Plaintext appears exactly once: in the batch-generation response / CSV export.
|
|
package codes
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/wangjia/pangolin/server/internal/idgen"
|
|
)
|
|
|
|
// GenerateCode generates a single random activation code in canonical Crockford
|
|
// Base32 form (15 random data chars + 1 check char = 16 chars total).
|
|
// It delegates to idgen.GenerateCode which uses crypto/rand.
|
|
func GenerateCode() (string, error) {
|
|
return idgen.GenerateCode()
|
|
}
|
|
|
|
// Canonicalize converts an activation-code string into canonical form:
|
|
// uppercase, with I/L→1 and O→0 substitutions applied.
|
|
// Hyphens and spaces are stripped before validation.
|
|
// Returns an error if the string contains characters outside the normalised
|
|
// Crockford alphabet, if the length is not exactly 16, or if the check character
|
|
// is incorrect.
|
|
func Canonicalize(code string) (string, error) {
|
|
return idgen.CanonicalizeCode(code)
|
|
}
|
|
|
|
// Hash returns the hex-encoded SHA-256 of the canonical plaintext code.
|
|
// This is the value stored in the database; the plaintext is never stored.
|
|
func Hash(canonical string) string {
|
|
return idgen.HashCode(canonical)
|
|
}
|
|
|
|
// ErrDuplicate is returned by the batch generator when a generated code
|
|
// already exists in the database (hash collision). The caller should retry.
|
|
var ErrDuplicate = errors.New("codes: duplicate code hash")
|