nailed it 2.0
This commit is contained in:
276
cmd/get-kosmi-token/main.go
Normal file
276
cmd/get-kosmi-token/main.go
Normal file
@@ -0,0 +1,276 @@
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user