// 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")