// Package auth provides password hashing, JWT, and token helpers used to // authenticate and authorize API requests. package auth import ( "crypto/rand" "encoding/hex" "errors" "net/http" "strings" "time" "github.com/alexedwards/argon2id" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) // HashPassword hashes a plaintext password using argon2id. func HashPassword(password string) (string, error) { return argon2id.CreateHash(password, argon2id.DefaultParams) } // CheckPasswordHash reports whether password matches the given argon2id hash. func CheckPasswordHash(password string, hash string) (bool, error) { return argon2id.ComparePasswordAndHash(password, hash) } // MakeJWT creates a signed HS256 JWT for the given user that expires after // expiresIn. func MakeJWT(userID uuid.UUID, tokenSecret []byte, expiresIn time.Duration) (string, error) { token := jwt.New(jwt.GetSigningMethod(jwt.SigningMethodHS256.Name)) token.Claims = &jwt.RegisteredClaims{ Issuer: "chirpy-access", IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiresIn)), Subject: userID.String(), } return token.SignedString(tokenSecret) } // ValidateJWT verifies the token signature and returns the subject's user ID. func ValidateJWT(tokenString string, tokenSecret []byte) (uuid.UUID, error) { token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) { return tokenSecret, nil }) if err != nil { return uuid.Nil, err } userId, err := token.Claims.GetSubject() if err != nil { return uuid.Nil, err } validUserId, err := uuid.Parse(userId) if err != nil { return uuid.Nil, err } return validUserId, nil } // GetBearerToken extracts the "Bearer" token from the Authorization header. func GetBearerToken(headers http.Header) (string, error) { authorizationHeader := headers.Get("Authorization") if strings.TrimSpace(authorizationHeader) == "" { return "", errors.New("Missing Authorization header") } token := strings.TrimPrefix(authorizationHeader, "Bearer ") return token, nil } // GetAPIKey extracts the API key from the Authorization header using the // "ApiKey" scheme. func GetAPIKey(headers http.Header) (string, error) { authorizationHeader := headers.Get("Authorization") if strings.TrimSpace(authorizationHeader) == "" { return "", errors.New("Missing Authorization header") } apiKey := strings.TrimPrefix(authorizationHeader, "ApiKey ") return apiKey, nil } // MakeRefreshToken generates a cryptographically random, hex-encoded refresh // token. func MakeRefreshToken() string { bytes := make([]byte, 32) rand.Read(bytes) return hex.EncodeToString(bytes) }