ff4d443269
Shared primitives for /api/* auth: mint a url-safe opaque token, hash it for storage, verify a bcrypt password hash. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
41 lines
1.4 KiB
Go
41 lines
1.4 KiB
Go
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
|
|
}
|