package auth import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "strings" "time" ) // Claims represents JWT payload type Claims struct { UserID string `json:"user_id"` CommunityID string `json:"community_id"` Username string `json:"username"` IssuedAt int64 `json:"iat"` ExpiresAt int64 `json:"exp"` } var jwtSecret = []byte("CHANGE-THIS-IN-PRODUCTION-USE-ENV-VAR") // TODO: Load from env // GenerateJWT creates a signed JWT token func GenerateJWT(userID, communityID, username string, duration time.Duration) (string, error) { now := time.Now() claims := Claims{ UserID: userID, CommunityID: communityID, Username: username, IssuedAt: now.Unix(), ExpiresAt: now.Add(duration).Unix(), } // Create header header := map[string]string{ "alg": "HS256", "typ": "JWT", } headerJSON, _ := json.Marshal(header) claimsJSON, _ := json.Marshal(claims) // Base64URL encode headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON) claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON) // Create signature message := headerB64 + "." + claimsB64 signature := createSignature(message) return message + "." + signature, nil } // VerifyJWT validates and parses a JWT token func VerifyJWT(token string) (*Claims, error) { parts := strings.Split(token, ".") if len(parts) != 3 { return nil, fmt.Errorf("invalid token format") } // Verify signature message := parts[0] + "." + parts[1] expectedSig := createSignature(message) if parts[2] != expectedSig { return nil, fmt.Errorf("invalid signature") } // Decode claims claimsJSON, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, fmt.Errorf("failed to decode claims: %w", err) } var claims Claims if err := json.Unmarshal(claimsJSON, &claims); err != nil { return nil, fmt.Errorf("failed to parse claims: %w", err) } // Check expiration if time.Now().Unix() > claims.ExpiresAt { return nil, fmt.Errorf("token expired") } return &claims, nil } // createSignature generates HMAC-SHA256 signature func createSignature(message string) string { h := hmac.New(sha256.New, jwtSecret) h.Write([]byte(message)) return base64.RawURLEncoding.EncodeToString(h.Sum(nil)) } // GenerateSessionToken creates a cryptographically secure session token func GenerateSessionToken() (string, error) { token := make([]byte, 32) if _, err := rand.Read(token); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(token), nil } // HashToken creates SHA256 hash for database storage func HashToken(token string) string { hash := sha256.Sum256([]byte(token)) return base64.RawURLEncoding.EncodeToString(hash[:]) }