Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ It supports authenticated publishing, subscribing, rate-limited usage tracking,
- [x] Forward messages to webhook endpoints (POST method) with flexible JSON template formatting
- [x] Health monitoring and system metrics
- [x] Debug mode with detailed timing and proxy information
- [x] Development mode with `--disable-auth` flag for testing

---

Expand Down Expand Up @@ -46,6 +47,14 @@ Download from [releases](https://github.com/getoptimum/mump2p-cli/releases/lates
./mump2p whoami # Check your session
```

**Development/Testing Mode:**
```sh
# Skip authentication for testing (requires --client-id and --service-url)
./mump2p --disable-auth --client-id="my-test-client" publish --topic=test --message="Hello" --service-url="http://34.146.222.111:8080"
./mump2p --disable-auth --client-id="my-test-client" subscribe --topic=test --service-url="http://34.146.222.111:8080"
./mump2p --disable-auth --client-id="my-test-client" list-topics --service-url="http://34.146.222.111:8080"
```

### 3. Basic Usage

```sh
Expand Down Expand Up @@ -243,14 +252,32 @@ Error: required flag(s) "topic" not set
- Include all required arguments
- Check flag spelling and syntax

### **6. Debug Mode & Performance Analysis**
### **6. Development Mode (`--disable-auth`)**

For development and testing, you can bypass authentication:

```sh
# Requires --client-id and --service-url flags
./mump2p --disable-auth --client-id="test-client" \
publish --topic=test --message="Hello" \
--service-url="http://34.146.222.111:8080"
```

> **Note:** This mode is for testing only. No rate limits enforced. See [guide](./docs/guide.md) for full details.

### **7. Debug Mode & Performance Analysis**

The `--debug` flag provides detailed timing and proxy information for troubleshooting:

```sh
# Enable debug mode for operations
./mump2p --debug publish --topic=test-topic --message='Hello World'
./mump2p --debug subscribe --topic=test-topic

# Combine with --disable-auth for testing
./mump2p --disable-auth --client-id="test" --debug \
publish --topic=test --message="Hello" \
--service-url="http://34.146.222.111:8080"
```

For comprehensive debug mode usage, performance analysis, and blast testing examples, see the [Complete User Guide](./docs/guide.md#debug-mode).
Expand Down
20 changes: 20 additions & 0 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ var whoamiCmd = &cobra.Command{
Short: "Show current authentication status",
Long: `Display information about the current authentication token.`,
RunE: func(cmd *cobra.Command, args []string) error {
if IsAuthDisabled() {
// Display authentication status when auth is disabled
clientIDToUse := GetClientID()
if clientIDToUse == "" {
clientIDToUse = "(not set - use --client-id flag)"
}
fmt.Println("Authentication Status:")
fmt.Println("----------------------")
fmt.Printf("Client ID: %s\n", clientIDToUse)
fmt.Println("Auth Mode: Disabled (using --disable-auth)")
fmt.Println("Rate Limits: N/A (no limits enforced)")
fmt.Println("Token: N/A (auth disabled)")
return nil
}

// load token
storage := auth.NewStorageWithPath(GetAuthPath())
token, err := storage.LoadToken()
Expand Down Expand Up @@ -105,6 +120,11 @@ var refreshCmd = &cobra.Command{
Short: "Refresh the authentication token",
Long: `Manually refresh the authentication token before it expires.`,
RunE: func(cmd *cobra.Command, args []string) error {
if IsAuthDisabled() {
fmt.Println("Token refresh skipped (auth disabled)")
return nil
}

// create auth client and storage
authClient := auth.NewClient()
storage := auth.NewStorageWithPath(GetAuthPath())
Expand Down
66 changes: 66 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,72 @@ var listTopicsCmd = &cobra.Command{
Long: `List all topics that the authenticated client is currently subscribed to.
This command shows your active topics and their count.`,
RunE: func(cmd *cobra.Command, args []string) error {
if IsAuthDisabled() {
// When auth is disabled, require client-id flag
clientIDToUse := GetClientID()
if clientIDToUse == "" {
return fmt.Errorf("--client-id is required when using --disable-auth")
}

// Determine service URL
serviceURL := config.LoadConfig().ServiceUrl
if listServiceURL != "" {
serviceURL = listServiceURL
fmt.Printf("Using custom service URL: %s\n", serviceURL)
}

// Create HTTP GET request to /api/v1/topics with client_id query parameter
endpoint := fmt.Sprintf("%s/api/v1/topics?client_id=%s", serviceURL, clientIDToUse)
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return fmt.Errorf("failed to create HTTP request: %v", err)
}

// Set headers (no auth needed for disabled auth)
req.Header.Set("Content-Type", "application/json")

// Execute the request
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("HTTP GET request failed: %v", err)
}
defer resp.Body.Close()

// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %v", err)
}

// Check for HTTP errors
if resp.StatusCode != 200 {
return fmt.Errorf("HTTP GET request error (status %d): %s", resp.StatusCode, string(body))
}

// Parse the JSON response
var listResponse ListResponse
if err := json.Unmarshal(body, &listResponse); err != nil {
return fmt.Errorf("failed to parse response JSON: %v", err)
}

// Display results in a formatted table
fmt.Printf("\n📋 Subscribed Topics for Client: %s (Auth Disabled)\n", listResponse.ClientID)
fmt.Printf("═══════════════════════════════════════════════════════════════\n")

if listResponse.Count == 0 {
fmt.Printf(" No active topics found.\n")
fmt.Printf(" Use './mump2p subscribe --topic=<topic-name>' to subscribe to a topic.\n")
} else {
fmt.Printf(" Total Topics: %d\n\n", listResponse.Count)
for i, topic := range listResponse.Topics {
fmt.Printf(" %d. %s\n", i+1, topic)
}
}

fmt.Printf("═══════════════════════════════════════════════════════════════\n")
return nil
}

// Authenticate
authClient := auth.NewClient()
storage := auth.NewStorageWithPath(GetAuthPath())
Expand Down
77 changes: 50 additions & 27 deletions cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,35 @@ var publishCmd = &cobra.Command{
return errors.New("only one of --message or --file should be used at a time")
}

authClient := auth.NewClient()
storage := auth.NewStorageWithPath(GetAuthPath())
token, err := authClient.GetValidToken(storage)
if err != nil {
return fmt.Errorf("authentication required: %v", err)
}
// parse token to check if the account is active
parser := auth.NewTokenParser()
claims, err := parser.ParseToken(token.Token)
if err != nil {
return fmt.Errorf("error parsing token: %v", err)
}
// check if the account is active
if !claims.IsActive {
return fmt.Errorf("your account is inactive, please contact support")
var claims *auth.TokenClaims
var token *auth.StoredToken
var clientIDToUse string

if !IsAuthDisabled() {
authClient := auth.NewClient()
storage := auth.NewStorageWithPath(GetAuthPath())
var err error
token, err = authClient.GetValidToken(storage)
if err != nil {
return fmt.Errorf("authentication required: %v", err)
}
// parse token to check if the account is active
parser := auth.NewTokenParser()
claims, err = parser.ParseToken(token.Token)
if err != nil {
return fmt.Errorf("error parsing token: %v", err)
}
// check if the account is active
if !claims.IsActive {
return fmt.Errorf("your account is inactive, please contact support")
}
clientIDToUse = claims.ClientID
} else {
// When auth is disabled, require client-id flag
clientIDToUse = GetClientID()
if clientIDToUse == "" {
return fmt.Errorf("--client-id is required when using --disable-auth")
}
}
var (
data []byte
Expand All @@ -104,14 +118,17 @@ var publishCmd = &cobra.Command{
// message size
messageSize := int64(len(data))

limiter, err := ratelimit.NewRateLimiterWithDir(claims, GetAuthDir())
if err != nil {
return fmt.Errorf("rate limiter setup failed: %v", err)
}
// Skip rate limiting if auth is disabled
if !IsAuthDisabled() {
limiter, err := ratelimit.NewRateLimiterWithDir(claims, GetAuthDir())
if err != nil {
return fmt.Errorf("rate limiter setup failed: %v", err)
}

// check all rate limits: size, quota, per-hr, per-sec
if err := limiter.CheckPublishAllowed(messageSize); err != nil {
return err
// check all rate limits: size, quota, per-hr, per-sec
if err := limiter.CheckPublishAllowed(messageSize); err != nil {
return err
}
Comment thread
hpsing marked this conversation as resolved.
}

// use custom service URL if provided, otherwise use the default
Expand Down Expand Up @@ -152,7 +169,7 @@ var publishCmd = &cobra.Command{
}
defer client.Close()

err = client.Publish(ctx, claims.ClientID, pubTopic, publishData)
err = client.Publish(ctx, clientIDToUse, pubTopic, publishData)
if err != nil {
return fmt.Errorf("gRPC publish failed: %v", err)
}
Expand All @@ -178,7 +195,7 @@ var publishCmd = &cobra.Command{
}

reqData := PublishRequest{
ClientID: claims.ClientID,
ClientID: clientIDToUse,
Topic: pubTopic,
Message: string(publishData), // use modified data with debug prefix if enabled
Timestamp: time.Now().UnixMilli(),
Expand All @@ -193,7 +210,10 @@ var publishCmd = &cobra.Command{
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token.Token)
// Only set Authorization header if auth is enabled
if !IsAuthDisabled() && token != nil {
req.Header.Set("Authorization", "Bearer "+token.Token)
}
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
Expand All @@ -215,8 +235,11 @@ var publishCmd = &cobra.Command{
fmt.Println(string(body))
}

if limiter, err := ratelimit.NewRateLimiterWithDir(claims, GetAuthDir()); err == nil {
_ = limiter.RecordPublish(messageSize) // update quota (fail silently)
// Only record publish if auth is enabled
if !IsAuthDisabled() {
if limiter, err := ratelimit.NewRateLimiterWithDir(claims, GetAuthDir()); err == nil {
_ = limiter.RecordPublish(messageSize) // update quota (fail silently)
}
}
return nil
},
Expand Down
24 changes: 20 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package cmd

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

var (
authPath string // Global flag for custom authentication file path
debug bool // Global flag for debug mode
authPath string // Global flag for custom authentication file path
debug bool // Global flag for debug mode
disableAuth bool // Global flag to disable authentication checks
clientID string // Global flag for client ID (used when auth is disabled)
)

var rootCmd = &cobra.Command{
Expand All @@ -22,7 +23,6 @@ without relying on the HTTP server. It directly invokes Go services.`,

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
}
Expand All @@ -34,6 +34,12 @@ func init() {
// Add global debug flag
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug mode with detailed timing and proxy information")

// Add global disable auth flag
rootCmd.PersistentFlags().BoolVar(&disableAuth, "disable-auth", false, "Disable authentication checks (for testing/development)")

// Add global client ID flag
rootCmd.PersistentFlags().StringVar(&clientID, "client-id", "", "Client ID to use (required when --disable-auth is enabled)")

// disable completion option
rootCmd.CompletionOptions.DisableDefaultCmd = true
}
Expand All @@ -56,3 +62,13 @@ func GetAuthDir() string {
func IsDebugMode() bool {
return debug
}

// IsAuthDisabled returns true if authentication is disabled
func IsAuthDisabled() bool {
return disableAuth
}

// GetClientID returns the client ID when auth is disabled
func GetClientID() string {
return clientID
}
Loading
Loading