diff --git a/callback.go b/callback.go index 6c0189c..53209ae 100644 --- a/callback.go +++ b/callback.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/subtle" "errors" "fmt" "html" @@ -97,7 +98,7 @@ func startCallbackServer(ctx context.Context, port int, expectedState string, } state := q.Get("state") - if state != expectedState { + if subtle.ConstantTimeCompare([]byte(state), []byte(expectedState)) == 0 { sanitized := "Authorization failed. Possible security issue detected." writeCallbackPage(w, false, sanitized) sendResult(callbackResult{ diff --git a/tui/flow_renderer.go b/tui/flow_renderer.go index 6e3cdab..33b31f2 100644 --- a/tui/flow_renderer.go +++ b/tui/flow_renderer.go @@ -8,12 +8,18 @@ import ( "charm.land/lipgloss/v2" ) +// ANSI escape sequences for terminal control. +const ( + ansiClearScreen = "\033[H\033[2J" + ansiClearToEnd = "\033[J" + ansiHideCursor = "\033[999;999H" +) + // FlowRenderer manages rendering of the unified flow view without running a full Bubble Tea program type FlowRenderer struct { model *UnifiedFlowModel spinnerFrame int spinnerChars []rune - lastContent string // Track last rendered content to avoid unnecessary redraws contentDirty bool // Flag to indicate if content needs redraw inProgressStepIdx int // Index of the in-progress step for spinner updates hasInProgressStep bool // Whether there's a step in progress @@ -91,7 +97,7 @@ func (r *FlowRenderer) NextSpinner() string { // RenderHeader renders and prints the header func (r *FlowRenderer) RenderHeader() { // Clear screen and move to top - fmt.Print("\033[H\033[2J") + fmt.Print(ansiClearScreen) var b strings.Builder @@ -179,18 +185,14 @@ func (r *FlowRenderer) UpdateDisplay() { currentContent := b.String() - // Move cursor to after header - headerLines := r.headerLines - // Move to position after header - fmt.Printf("\033[%d;0H", headerLines) + fmt.Printf("\033[%d;0H", r.headerLines) // Clear from cursor to end of screen - fmt.Print("\033[J") + fmt.Print(ansiClearToEnd) // Print new content fmt.Print(currentContent) - r.lastContent = currentContent r.contentDirty = false } @@ -201,8 +203,7 @@ func (r *FlowRenderer) updateSpinnerOnly() { } // Calculate the line number for the in-progress step - headerLines := r.headerLines - stepLine := headerLines + r.inProgressStepIdx + stepLine := r.headerLines + r.inProgressStepIdx // Render the spinner character spinnerStyle := lipgloss.NewStyle().Foreground(colorPrimary) @@ -213,7 +214,7 @@ func (r *FlowRenderer) updateSpinnerOnly() { // Print just the spinner character fmt.Print(spinnerChar) // Move cursor back to end (to avoid cursor showing) - fmt.Print("\033[999;999H") + fmt.Print(ansiHideCursor) } // renderDeviceCodeInfo renders the device code information box