package main import ( "context" "flag" "fmt" "log" "os" "time" "github.com/chromedp/chromedp" ) // Standalone script to extract JWT token from Kosmi using browser automation // Usage: go run get-token.go --email "email@email.com" --password "password" var ( emailFlag = flag.String("email", "", "Email for Kosmi login") passwordFlag = flag.String("password", "", "Password for Kosmi login") headlessFlag = flag.Bool("headless", true, "Run browser in headless mode") verboseFlag = flag.Bool("verbose", false, "Enable verbose logging") ) func main() { flag.Parse() if *emailFlag == "" || *passwordFlag == "" { log.Fatal("Error: --email and --password are required") } // Set up logging if !*verboseFlag { log.SetOutput(os.Stderr) } log.Println("Starting browser automation to extract JWT token...") log.Printf("Email: %s", *emailFlag) log.Printf("Headless mode: %v", *headlessFlag) token, err := extractToken(*emailFlag, *passwordFlag, *headlessFlag) if err != nil { log.Fatalf("Failed to extract token: %v", err) } // Output token to stdout (so it can be captured) fmt.Println(token) if *verboseFlag { log.Printf("✓ Successfully extracted token (length: %d)", len(token)) } } func extractToken(email, password string, headless bool) (string, error) { // Create context with timeout ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() // Set up chromedp options opts := []chromedp.ExecAllocatorOption{ chromedp.NoFirstRun, chromedp.NoDefaultBrowserCheck, chromedp.DisableGPU, chromedp.NoSandbox, } if headless { opts = append(opts, chromedp.Headless) } // Create allocator context allocCtx, cancel := chromedp.NewExecAllocator(ctx, opts...) defer cancel() // Create browser context - suppress cookie errors unless verbose logFunc := func(string, ...interface{}) {} if *verboseFlag { logFunc = log.Printf } ctx, cancel = chromedp.NewContext(allocCtx, chromedp.WithLogf(logFunc)) defer cancel() var token string // Run the automation tasks err := chromedp.Run(ctx, // Step 1: Navigate to Kosmi chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Navigating to https://app.kosmi.io") return chromedp.Navigate("https://app.kosmi.io").Do(ctx) }), // Step 2: Wait for page to load chromedp.WaitReady("body"), chromedp.Sleep(3*time.Second), // Step 3: Find and click Login button chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Looking for Login button...") // Log all buttons we can see if *verboseFlag { var buttonTexts []string chromedp.Evaluate(` Array.from(document.querySelectorAll('button')).map(el => el.textContent.trim()) `, &buttonTexts).Do(ctx) log.Printf(" Found buttons: %v", buttonTexts) } var found bool if err := chromedp.Evaluate(` (() => { const buttons = Array.from(document.querySelectorAll('button')); const btn = buttons.find(el => { const text = el.textContent.trim(); return text === 'Login' || text === 'Log in'; }); if (btn) { btn.click(); return true; } return false; })() `, &found).Do(ctx); err != nil { return err } if !found { return fmt.Errorf("Login button not found") } log.Println("✓ Clicked Login button") return nil }), // Step 4: Wait for login modal chromedp.Sleep(3*time.Second), // Step 5: Click "Login with Email" button chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Looking for 'Login with Email' button...") if *verboseFlag { var buttonTexts []string chromedp.Evaluate(` Array.from(document.querySelectorAll('button')).map(el => el.textContent.trim()) `, &buttonTexts).Do(ctx) log.Printf(" Found buttons: %v", buttonTexts) } var found bool if err := chromedp.Evaluate(` (() => { const btn = Array.from(document.querySelectorAll('button')).find(el => el.textContent.includes('Email') ); if (btn) { btn.click(); return true; } return false; })() `, &found).Do(ctx); err != nil { return err } if !found { return fmt.Errorf("'Login with Email' button not found") } log.Println("✓ Clicked 'Login with Email' button") return nil }), // Step 6: Wait for email form chromedp.Sleep(3*time.Second), chromedp.WaitVisible(`input[type="password"]`, chromedp.ByQuery), // Step 7: Fill in email chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Filling email field...") return nil }), chromedp.Click(`input[placeholder*="Email"], input[placeholder*="Username"]`, chromedp.ByQuery), chromedp.Sleep(200*time.Millisecond), chromedp.SendKeys(`input[placeholder*="Email"], input[placeholder*="Username"]`, email, chromedp.ByQuery), chromedp.ActionFunc(func(ctx context.Context) error { log.Println("✓ Email entered") return nil }), chromedp.Sleep(500*time.Millisecond), // Step 8: Fill in password chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Filling password field...") return nil }), chromedp.Click(`input[type="password"]`, chromedp.ByQuery), chromedp.Sleep(200*time.Millisecond), chromedp.SendKeys(`input[type="password"]`, password+"\n", chromedp.ByQuery), chromedp.ActionFunc(func(ctx context.Context) error { log.Println("✓ Password entered and form submitted") return nil }), chromedp.Sleep(500*time.Millisecond), // Step 10: Wait for login to complete - check for modal to close chromedp.ActionFunc(func(ctx context.Context) error { log.Println("→ Waiting for login to complete...") // Wait for the login modal to disappear (indicates successful login) maxAttempts := 30 // 15 seconds total for i := 0; i < maxAttempts; i++ { time.Sleep(500 * time.Millisecond) // Check if login modal is gone (successful login) var modalGone bool chromedp.Evaluate(` (() => { // Check if the email/password form is still visible const emailInput = document.querySelector('input[placeholder*="Email"], input[placeholder*="Username"]'); const passwordInput = document.querySelector('input[type="password"]'); return !emailInput && !passwordInput; })() `, &modalGone).Do(ctx) if modalGone { log.Println("✓ Login modal closed - login successful") // Modal is gone, wait a bit more for token to be set time.Sleep(2 * time.Second) chromedp.Evaluate(`localStorage.getItem('token')`, &token).Do(ctx) if token != "" { log.Println("✓ Authenticated token received") return nil } } // Check for error messages var errorText string chromedp.Evaluate(` (() => { const errorEl = document.querySelector('[role="alert"], .error, .error-message'); return errorEl ? errorEl.textContent.trim() : ''; })() `, &errorText).Do(ctx) if errorText != "" && errorText != "null" { return fmt.Errorf("login failed: %s", errorText) } } // Timeout - get whatever token is there log.Println("⚠ Login timeout - extracting current token") chromedp.Evaluate(`localStorage.getItem('token')`, &token).Do(ctx) if token == "" { return fmt.Errorf("login timeout: no token found") } log.Println("✓ Token extracted (may be anonymous)") return nil }), ) if err != nil { return "", fmt.Errorf("browser automation failed: %w", err) } if token == "" { return "", fmt.Errorf("failed to extract token from localStorage") } return token, nil }