package auth import ( "bufio" "errors" "os" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) // Claims is the JWT payload. type Claims struct { Username string `json:"username"` jwt.RegisteredClaims } // LoadUsers reads username:bcrypt_hash pairs from path. // Lines starting with '#' and blank lines are ignored. func LoadUsers(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() users := make(map[string]string) scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { continue } users[parts[0]] = parts[1] } return users, scanner.Err() } // VerifyPassword checks username+password against the bcrypt hash stored for that user. // The peppered input is "username:password". func VerifyPassword(users map[string]string, username, password string) bool { hash, ok := users[username] if !ok { return false } return bcrypt.CompareHashAndPassword([]byte(hash), []byte(username+":"+password)) == nil } // GenerateJWT creates a signed HS256 token for username, valid for 1 hour. func GenerateJWT(username, secret string) (string, error) { claims := Claims{ Username: username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret)) } // ValidateJWT parses and verifies token, returning its claims. func ValidateJWT(tokenStr, secret string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return []byte(secret), nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, errors.New("invalid token") } return claims, nil }