package auth import ( "crypto/rand" "crypto/sha256" "encoding/base64" "golang.org/x/crypto/bcrypt" ) // sessionTokenBytes is the raw entropy per session token. 32 bytes of // crypto/rand gives ~256 bits; after base64 url-safe encoding the cookie // value is 43 chars with no padding. const sessionTokenBytes = 32 // MintSessionToken returns a freshly-generated, url-safe opaque token. // The token is what the client carries; the DB only ever sees its sha256. func MintSessionToken() (string, error) { b := make([]byte, sessionTokenBytes) if _, err := rand.Read(b); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(b), nil } // HashSessionToken is the single source of truth for mapping a raw token to // the `sessions.token_hash` column. sha256 is fine here — we're not guarding // against offline brute force (the token has 256 bits of entropy); we only // want "leaked DB row can't be replayed without also having the raw token." func HashSessionToken(token string) []byte { sum := sha256.Sum256([]byte(token)) return sum[:] } // VerifyPassword is the canonical bcrypt comparison. Returns false on a // malformed hash so callers don't need to distinguish "hash invalid" from // "password wrong" — both are auth failures from the client's perspective. func VerifyPassword(hash, plaintext string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(plaintext)) == nil }