package authsupport import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "math/big" ) // tokenBytes is the amount of entropy in a raw token before encoding — // 32 bytes (256 bits) is comfortably enough that guessing is infeasible. const tokenBytes = 32 // NewToken returns a fresh, URL-safe random token (for password-reset // links and session cookies) and the hex-encoded SHA-256 hash of it that // should be persisted in its place. Only the hash ever touches the // database — the raw token lives solely in the email link or the // browser's cookie, so a DB dump alone can't be replayed. func NewToken() (raw string, hash string, err error) { b := make([]byte, tokenBytes) if _, err := rand.Read(b); err != nil { return "", "", fmt.Errorf("auth: generate token: %w", err) } raw = base64.RawURLEncoding.EncodeToString(b) return raw, HashToken(raw), nil } // codeSpace is how many distinct 6-digit codes NewCode can produce // (000000 through 999999). const codeSpace = 1_000_000 // NewCode returns a fresh random 6-digit numeric code — as a // zero-padded string, so "007123" round-trips correctly — for emailing // as an email-verification code, and the hex-encoded SHA-256 hash of it // that should be persisted in its place. Unlike NewToken's 256 bits of // entropy, a 6-digit code is deliberately guessable-by-brute-force if // nothing else stops repeated attempts; callers must pair it with an // attempt limit and a short expiry (see internal/db.VerifyEmailCode and // verifyCodeTTL) rather than relying on the code space alone. func NewCode() (raw string, hash string, err error) { n, err := rand.Int(rand.Reader, big.NewInt(codeSpace)) if err != nil { return "", "", fmt.Errorf("auth: generate code: %w", err) } raw = fmt.Sprintf("%06d", n.Int64()) return raw, HashToken(raw), nil } // HashToken hashes a raw token or code the same way NewToken/NewCode do, // so a value received back from the client (a reset link's ?token=, a // session cookie, or a typed verification code) can be looked up by its // hash. func HashToken(raw string) string { sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) }