@@ -31,12 +31,12 @@ import (
3131var validClusterNamePattern = regexp .MustCompile (`^[a-zA-Z0-9][a-zA-Z0-9._-]*$` )
3232
3333const (
34- // Cookie names for OAuth flow state
34+ // Cookie names for OAuth flow state.
3535 gcpOAuthStateCookie = "gcp_oauth_state"
3636 gcpOAuthClusterCookie = "gcp_oauth_cluster"
3737 gcpOAuthVerifierCookie = "gcp_oauth_verifier"
3838
39- // OAuth flow timeout
39+ // OAuth flow timeout.
4040 oauthFlowTimeout = 10 * time .Minute
4141)
4242
@@ -54,6 +54,7 @@ func HandleGCPAuthLogin(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.Hand
5454 if err != nil {
5555 logger .Log (logger .LevelError , nil , err , "failed to generate state" )
5656 http .Error (w , "failed to generate state" , http .StatusInternalServerError )
57+
5758 return
5859 }
5960
@@ -62,6 +63,7 @@ func HandleGCPAuthLogin(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.Hand
6263 if err != nil {
6364 logger .Log (logger .LevelError , nil , err , "failed to generate code verifier" )
6465 http .Error (w , "failed to generate code verifier" , http .StatusInternalServerError )
66+
6567 return
6668 }
6769
@@ -85,111 +87,107 @@ func HandleGCPAuthLogin(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.Hand
8587 }
8688}
8789
88- // HandleGCPAuthCallback handles the OAuth callback from Google.
89- func HandleGCPAuthCallback (gcpAuth * gcp.GCPAuthenticator , baseURL string ) http.HandlerFunc {
90- return func (w http.ResponseWriter , r * http.Request ) {
91- ctx := r .Context ()
90+ // gcpCallbackData holds validated data from the OAuth callback.
91+ type gcpCallbackData struct {
92+ cluster string
93+ codeVerifier string
94+ code string
95+ }
9296
93- // Validate state token (CSRF protection)
94- stateCookie , err := r . Cookie ( gcpOAuthStateCookie )
95- if err != nil {
96- logger . Log ( logger . LevelError , nil , err , "state cookie not found" )
97- http . Error ( w , "invalid OAuth state" , http . StatusBadRequest )
98- return
99- }
97+ // validateGCPCallback validates the OAuth callback request and returns extracted data.
98+ func validateGCPCallback ( r * http. Request ) ( * gcpCallbackData , error ) {
99+ // Validate state token (CSRF protection)
100+ stateCookie , err := r . Cookie ( gcpOAuthStateCookie )
101+ if err != nil {
102+ return nil , fmt . Errorf ( "state cookie not found: %w" , err )
103+ }
100104
101- stateParam := r .URL .Query ().Get ("state" )
102- if stateCookie .Value != stateParam {
103- logger .Log (logger .LevelError , nil ,
104- fmt .Errorf ("state mismatch: cookie=%s, param=%s" , stateCookie .Value , stateParam ),
105- "state validation failed" )
106- http .Error (w , "invalid state parameter" , http .StatusBadRequest )
107- return
108- }
105+ stateParam := r .URL .Query ().Get ("state" )
106+ if stateCookie .Value != stateParam {
107+ return nil , fmt .Errorf ("state mismatch: cookie=%s, param=%s" , stateCookie .Value , stateParam )
108+ }
109109
110- // Get cluster from cookie
111- clusterCookie , err := r .Cookie (gcpOAuthClusterCookie )
112- if err != nil {
113- logger .Log (logger .LevelError , nil , err , "cluster cookie not found" )
114- http .Error (w , "cluster not found in session" , http .StatusBadRequest )
115- return
116- }
117- cluster := clusterCookie .Value
110+ // Get cluster from cookie
111+ clusterCookie , err := r .Cookie (gcpOAuthClusterCookie )
112+ if err != nil {
113+ return nil , fmt .Errorf ("cluster cookie not found: %w" , err )
114+ }
118115
119- // Validate cluster name to prevent URL injection
120- if ! validClusterNamePattern .MatchString (cluster ) {
121- logger .Log (logger .LevelError , map [string ]string {
122- "cluster" : cluster ,
123- }, nil , "invalid cluster name format" )
124- http .Error (w , "invalid cluster name" , http .StatusBadRequest )
125- return
126- }
116+ cluster := clusterCookie .Value
117+ if ! validClusterNamePattern .MatchString (cluster ) {
118+ return nil , fmt .Errorf ("invalid cluster name format: %s" , cluster )
119+ }
127120
128- // Get PKCE code verifier
129- verifierCookie , err := r .Cookie (gcpOAuthVerifierCookie )
130- codeVerifier := ""
131- if err == nil {
132- codeVerifier = verifierCookie .Value
133- }
121+ // Check for OAuth errors
122+ if errParam := r .URL .Query ().Get ("error" ); errParam != "" {
123+ errDesc := r .URL .Query ().Get ("error_description" )
124+ return nil , fmt .Errorf ("OAuth error: %s - %s" , errParam , errDesc )
125+ }
134126
135- // Check for OAuth errors
136- if errParam := r .URL .Query ().Get ("error" ); errParam != "" {
137- errDesc := r .URL .Query ().Get ("error_description" )
138- logger .Log (logger .LevelError , map [string ]string {
139- "cluster" : cluster ,
140- "error" : errParam ,
141- "desc" : errDesc ,
142- }, nil , "OAuth error from provider" )
143- http .Error (w , fmt .Sprintf ("OAuth error: %s - %s" , errParam , errDesc ), http .StatusBadRequest )
144- return
145- }
127+ code := r .URL .Query ().Get ("code" )
128+ if code == "" {
129+ return nil , fmt .Errorf ("no code in request" )
130+ }
131+
132+ // Get PKCE code verifier (optional)
133+ codeVerifier := ""
134+ if verifierCookie , err := r .Cookie (gcpOAuthVerifierCookie ); err == nil {
135+ codeVerifier = verifierCookie .Value
136+ }
137+
138+ return & gcpCallbackData {
139+ cluster : cluster ,
140+ codeVerifier : codeVerifier ,
141+ code : code ,
142+ }, nil
143+ }
144+
145+ // HandleGCPAuthCallback handles the OAuth callback from Google.
146+ func HandleGCPAuthCallback (gcpAuth * gcp.GCPAuthenticator , baseURL string ) http.HandlerFunc {
147+ return func (w http.ResponseWriter , r * http.Request ) {
148+ ctx := r .Context ()
149+
150+ data , err := validateGCPCallback (r )
151+ if err != nil {
152+ logger .Log (logger .LevelError , nil , err , "OAuth callback validation failed" )
153+ http .Error (w , err .Error (), http .StatusBadRequest )
146154
147- // Exchange code for token
148- code := r .URL .Query ().Get ("code" )
149- if code == "" {
150- logger .Log (logger .LevelError , map [string ]string {"cluster" : cluster }, nil , "no code in request" )
151- http .Error (w , "no code in request" , http .StatusBadRequest )
152155 return
153156 }
154157
155- token , err := gcpAuth .Exchange (ctx , code , codeVerifier )
158+ token , err := gcpAuth .Exchange (ctx , data . code , data . codeVerifier )
156159 if err != nil {
157- logger .Log (logger .LevelError , map [string ]string {"cluster" : cluster }, err , "failed to exchange code for token " )
160+ logger .Log (logger .LevelError , map [string ]string {"cluster" : data . cluster }, err , "failed to exchange code" )
158161 http .Error (w , "failed to exchange token" , http .StatusInternalServerError )
162+
159163 return
160164 }
161165
162- // Get GKE access token
163166 gkeToken , err := gcpAuth .GetGKEAccessToken (ctx , token )
164167 if err != nil {
165- logger .Log (logger .LevelError , map [string ]string {"cluster" : cluster }, err , "failed to get GKE access token" )
168+ logger .Log (logger .LevelError , map [string ]string {"cluster" : data . cluster }, err , "failed to get GKE token" )
166169 http .Error (w , "failed to get GKE token" , http .StatusInternalServerError )
170+
167171 return
168172 }
169173
170- // Cache the refresh token for later use
174+ // Cache the refresh token (non-fatal if it fails)
171175 if token .RefreshToken != "" {
172- if err := gcpAuth .CacheRefreshToken (ctx , cluster , gkeToken , token .RefreshToken ); err != nil {
173- logger .Log (logger .LevelError , map [string ]string {"cluster" : cluster }, err , "failed to cache refresh token" )
174- // Non-fatal error, continue
176+ if cacheErr := gcpAuth .CacheRefreshToken (ctx , data .cluster , gkeToken , token .RefreshToken ); cacheErr != nil {
177+ logger .Log (logger .LevelError , map [string ]string {"cluster" : data .cluster }, cacheErr , "failed to cache refresh token" )
175178 }
176179 }
177180
178- // Set token in cookie (using existing Headlamp pattern)
179- SetTokenCookie (w , r , cluster , gkeToken , baseURL )
181+ SetTokenCookie (w , r , data .cluster , gkeToken , baseURL )
180182
181- // Clear OAuth state cookies
182183 secure := IsSecureContext (r )
183184 clearOAuthCookie (w , gcpOAuthStateCookie , secure )
184185 clearOAuthCookie (w , gcpOAuthClusterCookie , secure )
185186 clearOAuthCookie (w , gcpOAuthVerifierCookie , secure )
186187
187- logger .Log (logger .LevelInfo , map [string ]string {
188- "cluster" : cluster ,
189- }, nil , "GCP OAuth flow completed successfully" )
188+ logger .Log (logger .LevelInfo , map [string ]string {"cluster" : data .cluster }, nil , "GCP OAuth flow completed" )
190189
191- // Redirect to cluster view
192- redirectURL := fmt .Sprintf ("/#/c/%s" , cluster )
190+ redirectURL := fmt .Sprintf ("/#/c/%s" , data .cluster )
193191 if baseURL != "" {
194192 redirectURL = "/" + baseURL + redirectURL
195193 }
@@ -216,6 +214,7 @@ func HandleGCPTokenRefresh(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.H
216214 "cluster" : cluster ,
217215 }, err , "failed to get cached refresh token" )
218216 http .Error (w , "no refresh token available" , http .StatusUnauthorized )
217+
219218 return
220219 }
221220
@@ -226,6 +225,7 @@ func HandleGCPTokenRefresh(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.H
226225 "cluster" : cluster ,
227226 }, err , "failed to refresh token" )
228227 http .Error (w , "failed to refresh token" , http .StatusInternalServerError )
228+
229229 return
230230 }
231231
@@ -236,16 +236,14 @@ func HandleGCPTokenRefresh(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.H
236236 "cluster" : cluster ,
237237 }, err , "failed to get new GKE access token" )
238238 http .Error (w , "failed to get new GKE token" , http .StatusInternalServerError )
239+
239240 return
240241 }
241242
242- // Cache the new refresh token if we got one
243+ // Cache the new refresh token if we got one (non-fatal if it fails)
243244 if newToken .RefreshToken != "" {
244- if err := gcpAuth .CacheRefreshToken (ctx , cluster , newGKEToken , newToken .RefreshToken ); err != nil {
245- logger .Log (logger .LevelError , map [string ]string {
246- "cluster" : cluster ,
247- }, err , "failed to cache new refresh token" )
248- // Non-fatal, continue
245+ if cacheErr := gcpAuth .CacheRefreshToken (ctx , cluster , newGKEToken , newToken .RefreshToken ); cacheErr != nil {
246+ logger .Log (logger .LevelError , map [string ]string {"cluster" : cluster }, cacheErr , "failed to cache new refresh token" )
249247 }
250248 }
251249
@@ -257,7 +255,7 @@ func HandleGCPTokenRefresh(gcpAuth *gcp.GCPAuthenticator, baseURL string) http.H
257255 }, nil , "token refreshed successfully" )
258256
259257 w .WriteHeader (http .StatusOK )
260- w .Write ([]byte ("token refreshed" ))
258+ _ , _ = w .Write ([]byte ("token refreshed" ))
261259 }
262260}
263261
0 commit comments