Add godoc comments to the exported auth package helpers. Expand the README with feature and tech stack overviews, setup instructions, an API reference, and make targets for development workflows.
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
// 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)
|
|
}
|