2025-11-01 21:00:16 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-05 05:49:26 -04:00
|
|
|
"bytes"
|
2025-11-01 21:00:16 -04:00
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2026-04-05 05:49:26 -04:00
|
|
|
"net/http"
|
2025-11-01 21:00:16 -04:00
|
|
|
"os"
|
|
|
|
|
"strings"
|
2026-04-05 05:49:26 -04:00
|
|
|
"time"
|
2025-11-01 21:00:16 -04:00
|
|
|
|
|
|
|
|
bkosmi "github.com/42wim/matterbridge/bridge/kosmi"
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if len(os.Args) < 3 {
|
|
|
|
|
fmt.Println("Usage: compare-auth <email> <password>")
|
|
|
|
|
fmt.Println("")
|
|
|
|
|
fmt.Println("This script will:")
|
|
|
|
|
fmt.Println("1. Get an anonymous token")
|
|
|
|
|
fmt.Println("2. Get an authenticated token via browser")
|
|
|
|
|
fmt.Println("3. Decode and compare both JWT tokens")
|
|
|
|
|
fmt.Println("4. Show what's different")
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
email := os.Args[1]
|
|
|
|
|
password := os.Args[2]
|
|
|
|
|
|
|
|
|
|
// Set up logging
|
|
|
|
|
log := logrus.New()
|
|
|
|
|
log.SetLevel(logrus.InfoLevel)
|
|
|
|
|
entry := logrus.NewEntry(log)
|
|
|
|
|
|
|
|
|
|
fmt.Println(strings.Repeat("=", 80))
|
|
|
|
|
fmt.Println("COMPARING ANONYMOUS vs AUTHENTICATED TOKENS")
|
|
|
|
|
fmt.Println(strings.Repeat("=", 80))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
2026-04-05 05:49:26 -04:00
|
|
|
// Get anonymous token via HTTP (same mutation the client uses internally)
|
2025-11-01 21:00:16 -04:00
|
|
|
fmt.Println("📝 Step 1: Getting anonymous token...")
|
2026-04-05 05:49:26 -04:00
|
|
|
anonToken, err := getAnonymousToken()
|
2025-11-01 21:00:16 -04:00
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf("❌ Failed to get anonymous token: %v\n", err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("✅ Anonymous token obtained (length: %d)\n", len(anonToken))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
2026-04-05 05:49:26 -04:00
|
|
|
// Get authenticated token via browser automation
|
2025-11-01 21:00:16 -04:00
|
|
|
fmt.Println("📝 Step 2: Getting authenticated token via browser...")
|
2026-04-05 05:49:26 -04:00
|
|
|
authToken, err := bkosmi.LoginWithChromedp(email, password, entry)
|
2025-11-01 21:00:16 -04:00
|
|
|
if err != nil {
|
|
|
|
|
fmt.Printf("❌ Failed to get authenticated token: %v\n", err)
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("✅ Authenticated token obtained (length: %d)\n", len(authToken))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Decode both tokens
|
|
|
|
|
fmt.Println("📝 Step 3: Decoding JWT tokens...")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
anonClaims := decodeJWT(anonToken)
|
|
|
|
|
authClaims := decodeJWT(authToken)
|
|
|
|
|
|
|
|
|
|
fmt.Println("ANONYMOUS TOKEN:")
|
|
|
|
|
fmt.Println(strings.Repeat("=", 80))
|
|
|
|
|
printClaims(anonClaims)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
fmt.Println("AUTHENTICATED TOKEN:")
|
|
|
|
|
fmt.Println(strings.Repeat("=", 80))
|
|
|
|
|
printClaims(authClaims)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Compare
|
|
|
|
|
fmt.Println("DIFFERENCES:")
|
|
|
|
|
fmt.Println(strings.Repeat("=", 80))
|
|
|
|
|
compareClaims(anonClaims, authClaims)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
fmt.Println("📝 Step 4: Testing connection with both tokens...")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// We can't easily test the actual connection here, but we've shown the token differences
|
|
|
|
|
fmt.Println("✅ Analysis complete!")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println("RECOMMENDATION:")
|
|
|
|
|
fmt.Println("If the authenticated token has a different 'sub' (user ID), that user")
|
|
|
|
|
fmt.Println("might not have the same permissions or profile as the anonymous user.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func decodeJWT(token string) map[string]interface{} {
|
|
|
|
|
parts := strings.Split(token, ".")
|
|
|
|
|
if len(parts) != 3 {
|
|
|
|
|
return map[string]interface{}{"error": "invalid JWT format"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode payload
|
|
|
|
|
payload := parts[1]
|
|
|
|
|
// Add padding if needed
|
|
|
|
|
switch len(payload) % 4 {
|
|
|
|
|
case 2:
|
|
|
|
|
payload += "=="
|
|
|
|
|
case 3:
|
|
|
|
|
payload += "="
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace URL-safe characters
|
|
|
|
|
payload = strings.ReplaceAll(payload, "-", "+")
|
|
|
|
|
payload = strings.ReplaceAll(payload, "_", "/")
|
|
|
|
|
|
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(payload)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return map[string]interface{}{"error": fmt.Sprintf("decode error: %v", err)}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var claims map[string]interface{}
|
|
|
|
|
if err := json.Unmarshal(decoded, &claims); err != nil {
|
|
|
|
|
return map[string]interface{}{"error": fmt.Sprintf("parse error: %v", err)}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return claims
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func printClaims(claims map[string]interface{}) {
|
|
|
|
|
keys := []string{"sub", "aud", "iss", "typ", "iat", "exp", "nbf", "jti"}
|
|
|
|
|
for _, key := range keys {
|
|
|
|
|
if val, ok := claims[key]; ok {
|
|
|
|
|
fmt.Printf(" %-10s: %v\n", key, val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 05:49:26 -04:00
|
|
|
func getAnonymousToken() (string, error) {
|
|
|
|
|
mutation := map[string]interface{}{
|
|
|
|
|
"query": `mutation { anonLogin { token } }`,
|
|
|
|
|
}
|
|
|
|
|
jsonBody, err := json.Marshal(mutation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
|
req, err := http.NewRequest("POST", "https://engine.kosmi.io/", bytes.NewReader(jsonBody))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
req.Header.Set("Referer", "https://app.kosmi.io/")
|
|
|
|
|
req.ContentLength = int64(len(jsonBody))
|
|
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
|
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result map[string]interface{}
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if data, ok := result["data"].(map[string]interface{}); ok {
|
|
|
|
|
if anonLogin, ok := data["anonLogin"].(map[string]interface{}); ok {
|
|
|
|
|
if token, ok := anonLogin["token"].(string); ok {
|
|
|
|
|
return token, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "", fmt.Errorf("no token in response")
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 21:00:16 -04:00
|
|
|
func compareClaims(anon, auth map[string]interface{}) {
|
|
|
|
|
allKeys := make(map[string]bool)
|
|
|
|
|
for k := range anon {
|
|
|
|
|
allKeys[k] = true
|
|
|
|
|
}
|
|
|
|
|
for k := range auth {
|
|
|
|
|
allKeys[k] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
different := false
|
|
|
|
|
for key := range allKeys {
|
|
|
|
|
anonVal := anon[key]
|
|
|
|
|
authVal := auth[key]
|
|
|
|
|
|
|
|
|
|
if fmt.Sprintf("%v", anonVal) != fmt.Sprintf("%v", authVal) {
|
|
|
|
|
different = true
|
|
|
|
|
fmt.Printf(" %-10s: ANON=%v AUTH=%v\n", key, anonVal, authVal)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !different {
|
|
|
|
|
fmt.Println(" (No differences found - tokens are essentially the same)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|