Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 8 additions & 0 deletions 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,13 @@ 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 --service-url for network operations)
./mump2p --disable-auth publish --topic=test --message="Hello" --service-url="http://34.146.222.111:8080"
./mump2p --disable-auth subscribe --topic=test --service-url="http://34.146.222.111:8080"
```

### 3. Basic Usage

```sh
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 mock authentication status when auth is disabled
fmt.Println("Authentication Status:")
fmt.Println("----------------------")
fmt.Println("Client ID: mock-client-id (auth disabled)")
fmt.Println("Is Active: true (auth disabled)")
fmt.Println("Rate Limits:")
fmt.Println(" Max Publish Per Hour: unlimited")
fmt.Println(" Max Publish Per Sec: unlimited")
fmt.Println(" Max Message Size: unlimited")
fmt.Println(" Daily Quota: unlimited")
fmt.Println("Token Expires: 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
61 changes: 61 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,67 @@ 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, we still need to make the API call with mock client ID
// 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 mock client_id
endpoint := fmt.Sprintf("%s/api/v1/topics?client_id=mock-client-id", serviceURL)
Comment thread
hpsing marked this conversation as resolved.
Outdated
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
65 changes: 43 additions & 22 deletions cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,39 @@ 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
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")
}
} else {
// Create mock claims and token for disabled auth
Comment thread
swarna1101 marked this conversation as resolved.
Outdated
claims = &auth.TokenClaims{
IsActive: true,
MaxPublishPerHour: 1000,
MaxPublishPerSec: 100,
MaxMessageSize: 1024 * 1024, // 1MB
DailyQuota: 100 * 1024 * 1024, // 100MB
ClientID: "mock-client-id",
}
Comment thread
swarna1101 marked this conversation as resolved.
Outdated
token = &auth.StoredToken{
Token: "mock-token-for-disabled-auth",
Comment thread
swarna1101 marked this conversation as resolved.
Outdated
}
}
var (
data []byte
Expand All @@ -104,14 +122,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
15 changes: 11 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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
)

var rootCmd = &cobra.Command{
Expand All @@ -22,7 +22,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 +33,9 @@ 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)")

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

// IsAuthDisabled returns true if authentication is disabled
func IsAuthDisabled() bool {
return disableAuth
}
46 changes: 30 additions & 16 deletions cmd/subscribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,36 @@ var subscribeCmd = &cobra.Command{
Use: "subscribe",
Short: "Subscribe to a topic via WebSocket or gRPC stream",
RunE: func(cmd *cobra.Command, args []string) error {
// auth
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
if !IsAuthDisabled() {
// auth
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")
}
} else {
// Create mock claims and token for disabled auth
claims = &auth.TokenClaims{
IsActive: true,
ClientID: "mock-client-id",
}
token = &auth.StoredToken{
Token: "mock-token-for-disabled-auth",
}
}

// setup persistence if path is provided
Expand Down
36 changes: 36 additions & 0 deletions cmd/usages.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@ var usageCmd = &cobra.Command{
Use: "usage",
Short: "Display usage statistics and rate limits",
RunE: func(cmd *cobra.Command, args []string) error {
if IsAuthDisabled() {
// Create mock claims for disabled auth and initialize rate limiter
mockClaims := &auth.TokenClaims{
IsActive: true,
ClientID: "mock-client-id",
MaxPublishPerHour: 1000, // Set reasonable limits for testing
MaxPublishPerSec: 10,
MaxMessageSize: 10 * 1024 * 1024, // 10MB
DailyQuota: 100 * 1024 * 1024, // 100MB
}

// Initialize rate limiter with mock claims
limiter, err := ratelimit.NewRateLimiterWithDir(mockClaims, GetAuthDir())
if err != nil {
return fmt.Errorf("error initializing rate limiter: %v", err)
}

// get usage statistics
stats := limiter.GetUsageStats()

// display usage statistics
fmt.Println("Usage Statistics (Auth Disabled):")
fmt.Printf(" Publish (hour): %d / %d\n", stats.PublishCount, stats.PublishLimitPerHour)
fmt.Printf(" Publish (second): %d / %d\n", stats.SecondPublishCount, stats.PublishLimitPerSec)
fmt.Printf(" Data Used: %.4f MB / %.4f MB\n", float64(stats.BytesPublished)/(1<<20), float64(stats.DailyQuota)/(1<<20))
fmt.Printf(" Next Reset: %s (%s from now)\n", stats.NextReset.Format(time.RFC822), stats.TimeUntilReset)

if !stats.LastPublishTime.IsZero() {
fmt.Printf(" Last Publish: %s\n", stats.LastPublishTime.Format(time.RFC822))
}
if !stats.LastSubscribeTime.IsZero() {
fmt.Printf(" Last Subscribe: %s\n", stats.LastSubscribeTime.Format(time.RFC822))
}
return nil
}

// get valid token (refreshes if needed)
authClient := auth.NewClient()
storage := auth.NewStorageWithPath(GetAuthPath())
Expand Down
21 changes: 21 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
After completing the README's quick start, this guide will teach you:

- **Authentication Management**: Token management, refresh, and troubleshooting
- **Development Mode**: Testing without authentication using `--disable-auth` flag
- **Service Configuration**: Using different proxy servers and custom URLs
- **Protocol Deep Dive**: When to use HTTP/WebSocket vs gRPC
- **Advanced Features**: Message persistence, webhooks, and monitoring
Expand Down Expand Up @@ -100,6 +101,26 @@ export MUMP2P_AUTH_PATH="/opt/mump2p/auth/token.yml"
- Rate limiting usage files will be stored in the same directory
- Ensure the user has write permissions to the specified directory

### Development/Testing Mode

For development and testing scenarios, you can bypass authentication entirely using the `--disable-auth` flag:

```sh
# All commands work without login (requires --service-url for network operations)
./mump2p --disable-auth whoami
./mump2p --disable-auth publish --topic=test --message="Hello" --service-url="http://34.146.222.111:8080"
./mump2p --disable-auth subscribe --topic=test --service-url="http://34.146.222.111:8080"
./mump2p --disable-auth list --service-url="http://34.146.222.111:8080"
Comment thread
swarna1101 marked this conversation as resolved.
Outdated
./mump2p --disable-auth usage
Comment thread
paiva marked this conversation as resolved.
```

**When using `--disable-auth`:**
- Uses mock client ID (`mock-client-id`)
- Unlimited rate limits for testing
- All functionality works without authentication
- **Requires `--service-url` for network operations** (publish, subscribe, list)
- Perfect for development and testing

### Logout

To remove your stored authentication token:
Expand Down
Loading