59 lines
2.1 KiB
Go
59 lines
2.1 KiB
Go
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[:])
|
|
}
|