From 5b9df5000e8a8b851ee1bac37cf446e1930c93e1 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Fri, 2 Jan 2026 18:20:50 +0100 Subject: [PATCH 1/5] remove client-side-redirect Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- backend/auth/handler.go | 18 ++++++++---------- backend/auth/types.go | 14 +++++--------- backend/client/client.go | 23 ++++++++++------------- web/src/services/auth.ts | 4 ---- 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/backend/auth/handler.go b/backend/auth/handler.go index c8b68f8a7..c35a0a56d 100644 --- a/backend/auth/handler.go +++ b/backend/auth/handler.go @@ -77,12 +77,11 @@ func (ah *AuthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) { } } else { authReq = AuthorizeRequest{ - RedirectURL: params.RedirectURL, - ClientSideRedirectURL: params.ClientSideRedirectURL, - SessionID: params.SessionID, - ClusterID: params.ClusterID, - ClientType: ClientType(params.ClientType), - ConsumerID: params.ConsumerID, + RedirectURL: params.RedirectURL, + SessionID: params.SessionID, + ClusterID: params.ClusterID, + ClientType: ClientType(params.ClientType), + ConsumerID: params.ConsumerID, } } @@ -249,10 +248,9 @@ func (ah *AuthHandler) HandleCallback(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, session.MakeCookie(r, cookieName, encoded, secure, 1*time.Hour)) clientParams := &client.ClientParameters{ - ClusterID: authCode.ClusterID, - ClientSideRedirectURL: authCode.ClientSideRedirectURL, - RedirectURL: authCode.RedirectURL, - SessionID: authCode.SessionID, + ClusterID: authCode.ClusterID, + RedirectURL: authCode.RedirectURL, + SessionID: authCode.SessionID, } url := clientParams.WithParams(authCode.RedirectURL) diff --git a/backend/auth/types.go b/backend/auth/types.go index f37bf42e9..7bb93b566 100644 --- a/backend/auth/types.go +++ b/backend/auth/types.go @@ -45,16 +45,12 @@ type TokenResponse struct { ClusterID string `json:"cluster_id,omitempty"` } -// TODO: We should remove client_side_redirect_url. -// https://github.com/kube-bind/kube-bind/issues/362 - type AuthorizeRequest struct { - RedirectURL string `json:"redirect_url" form:"redirect_url"` - ClientSideRedirectURL string `json:"client_side_redirect_url" form:"client_side_redirect_url"` - SessionID string `json:"session_id" form:"session_id"` - ClusterID string `json:"cluster_id" form:"cluster_id"` - ClientType ClientType `json:"client_type" form:"client_type"` - ConsumerID string `json:"consumer_id" form:"consumer_id"` + RedirectURL string `json:"redirect_url" form:"redirect_url"` + SessionID string `json:"session_id" form:"session_id"` + ClusterID string `json:"cluster_id" form:"cluster_id"` + ClientType ClientType `json:"client_type" form:"client_type"` + ConsumerID string `json:"consumer_id" form:"consumer_id"` } type CallbackRequest struct { diff --git a/backend/client/client.go b/backend/client/client.go index 42505cc0e..ab22fae3d 100644 --- a/backend/client/client.go +++ b/backend/client/client.go @@ -24,11 +24,9 @@ import ( type ClientParameters struct { ClusterID string RedirectURL string - // This is clients side redirect, in example used for CLI via UI. - ClientSideRedirectURL string - SessionID string - ClientType string - ConsumerID string + SessionID string + ClientType string + ConsumerID string IsClusterScoped bool } @@ -36,12 +34,11 @@ type ClientParameters struct { // GetQueryParams extracts the client parameters from the given HTTP request. func GetQueryParams(r *http.Request) *ClientParameters { p := &ClientParameters{ - ClusterID: r.URL.Query().Get("cluster_id"), - RedirectURL: r.URL.Query().Get("redirect_url"), - ClientSideRedirectURL: r.URL.Query().Get("client_side_redirect_url"), - SessionID: r.URL.Query().Get("session_id"), - ClientType: r.URL.Query().Get("client_type"), - ConsumerID: r.URL.Query().Get("consumer_id"), + ClusterID: r.URL.Query().Get("cluster_id"), + RedirectURL: r.URL.Query().Get("redirect_url"), + SessionID: r.URL.Query().Get("session_id"), + ClientType: r.URL.Query().Get("client_type"), + ConsumerID: r.URL.Query().Get("consumer_id"), } p.IsClusterScoped = p.ClusterID != "" return p @@ -61,8 +58,8 @@ func (r *ClientParameters) WithParams(urlStr string) string { if r.ClusterID != "" { query.Set("cluster_id", r.ClusterID) } - if r.ClientSideRedirectURL != "" { - query.Set("redirect_url", r.ClientSideRedirectURL) + if r.RedirectURL != "" { + query.Set("redirect_url", r.RedirectURL) } if r.SessionID != "" { query.Set("session_id", r.SessionID) diff --git a/web/src/services/auth.ts b/web/src/services/auth.ts index 1d04e8581..2be638483 100644 --- a/web/src/services/auth.ts +++ b/web/src/services/auth.ts @@ -103,10 +103,6 @@ class AuthService { client_type: 'ui' // Use UI type to get cookies }) - if (clientSideRedirectUrl) { - params.set('client_side_redirect_url', clientSideRedirectUrl) - } - if (consumerId) { params.set('consumer_id', consumerId) } From 043662d4bf754785b6a24f8ece27cdbb739e7175 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Fri, 9 Jan 2026 14:34:24 +0100 Subject: [PATCH 2/5] store redirect and session, construct callback URL in web app Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- .../{index.64982fe8.js => index.7407f299.js} | 75 +++++++--- ...{index.68bcd12d.css => index.b2bbc5a6.css} | 132 +++++++++--------- web/dist/index.html | 4 +- web/src/App.vue | 9 +- web/src/services/auth.ts | 86 ++++++++---- web/src/views/Resources.vue | 24 +++- 6 files changed, 213 insertions(+), 117 deletions(-) rename web/dist/assets/{index.64982fe8.js => index.7407f299.js} (99%) rename web/dist/assets/{index.68bcd12d.css => index.b2bbc5a6.css} (90%) diff --git a/web/dist/assets/index.64982fe8.js b/web/dist/assets/index.7407f299.js similarity index 99% rename from web/dist/assets/index.64982fe8.js rename to web/dist/assets/index.7407f299.js index d68e8be4a..b2f56e651 100644 --- a/web/dist/assets/index.64982fe8.js +++ b/web/dist/assets/index.7407f299.js @@ -98,7 +98,7 @@ var __yieldStar = (value) => { }; var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it); var require_index_001 = __commonJS({ - "assets/index.64982fe8.js"(exports) { + "assets/index.7407f299.js"(exports) { (function polyfill() { const relList = document.createElement("link").relList; if (relList && relList.supports && relList.supports("modulepreload")) { @@ -11078,7 +11078,7 @@ var require_index_001 = __commonJS({ } }); } - initiateAuth(sessionId, clusterId, clientSideRedirectUrl, consumerId) { + initiateAuth(sessionId, clusterId, consumerId) { return __async$4(this, null, function* () { const authUrl = `/api/authorize`; const redirect_url = window.location.origin + window.location.pathname; @@ -11104,9 +11104,6 @@ var require_index_001 = __commonJS({ client_type: "ui" // Use UI type to get cookies }); - if (clientSideRedirectUrl) { - params.set("client_side_redirect_url", clientSideRedirectUrl); - } if (consumerId) { params.set("consumer_id", consumerId); } @@ -11135,13 +11132,39 @@ var require_index_001 = __commonJS({ } isCliFlow() { const urlParams = new URLSearchParams(window.location.search); - return urlParams.has("redirect_url"); + if (urlParams.has("redirect_url")) { + return true; + } + const preservedParams = sessionStorage.getItem("kube-bind-preserved-params"); + if (preservedParams) { + try { + const params = JSON.parse(preservedParams); + return !!params.redirect_url; + } catch (e) { + return false; + } + } + return false; } redirectToCliCallback(bindingResponseData) { const urlParams = new URLSearchParams(window.location.search); - const redirectUrl = urlParams.get("redirect_url"); - const sessionId = urlParams.get("session_id"); - const consumerId = urlParams.get("consumer_id"); + let redirectUrl = urlParams.get("redirect_url"); + let sessionId = urlParams.get("session_id"); + let consumerId = urlParams.get("consumer_id"); + if (!redirectUrl) { + const preservedParams = sessionStorage.getItem("kube-bind-preserved-params"); + if (preservedParams) { + try { + const params = JSON.parse(preservedParams); + redirectUrl = params.redirect_url || null; + sessionId = sessionId || params.session_id || null; + consumerId = consumerId || params.consumer_id || null; + console.log("Retrieved CLI params from sessionStorage:", { redirectUrl, sessionId, consumerId }); + } catch (e) { + console.error("Failed to parse preserved params:", e); + } + } + } if (redirectUrl) { const callbackUrl = new URL(redirectUrl); if (sessionId) { @@ -11152,7 +11175,10 @@ var require_index_001 = __commonJS({ } const base64Response = btoa(JSON.stringify(bindingResponseData)); callbackUrl.searchParams.append("binding_response", base64Response); + console.log("Redirecting to CLI callback:", callbackUrl.toString()); window.location.href = callbackUrl.toString(); + } else { + console.error("No redirect URL found for CLI callback"); } } restorePreservedParams() { @@ -11252,9 +11278,8 @@ var require_index_001 = __commonJS({ hasAttemptedAuth.value = true; const cluster = route.query.cluster_id || ""; const sessionId = route.query.session_id || generateSessionId(); - const clientSideRedirectUrl = route.query.redirect_url || ""; const consumerId = route.query.consumer_id || ""; - yield authService.initiateAuth(sessionId, cluster, clientSideRedirectUrl, consumerId); + yield authService.initiateAuth(sessionId, cluster, consumerId); } catch (error) { console.error("Authentication failed:", error); authStatus.value.error = "Authentication failed"; @@ -11294,7 +11319,7 @@ var require_index_001 = __commonJS({ return openBlock(), createElementBlock("div", _hoisted_1$4, [ createBaseVNode("header", _hoisted_2$4, [ createBaseVNode("div", _hoisted_3$4, [ - _cache[2] || (_cache[2] = createStaticVNode('

Kube Bind

', 1)), + _cache[2] || (_cache[2] = createStaticVNode('

Kube Bind

', 1)), authStatus.value.isAuthenticated ? (openBlock(), createElementBlock("div", _hoisted_4$4, [ _cache[1] || (_cache[1] = createBaseVNode("div", { class: "user-info" }, [ createBaseVNode("div", { class: "status-indicator" }), @@ -11364,7 +11389,7 @@ var require_index_001 = __commonJS({ }; } }); - const App_vue_vue_type_style_index_0_scoped_504b04cf_lang = ""; + const App_vue_vue_type_style_index_0_scoped_99283ff2_lang = ""; const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { @@ -11372,7 +11397,7 @@ var require_index_001 = __commonJS({ } return target; }; - const App = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-504b04cf"]]); + const App = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-99283ff2"]]); var __async$2 = (__this, __arguments, generator) => { return new Promise((resolve2, reject) => { var fulfilled = (value) => { @@ -12344,6 +12369,21 @@ ${formattedObject}`; var _a; try { const bindUrl = buildApiUrl("/bind"); + const sessionIdFromRoute = route.query.session_id || ""; + const clusterIdentity = consumerId.value || sessionIdFromRoute; + console.log("Bind request parameters:", { + templateName, + bindingName, + consumerId: consumerId.value, + sessionIdFromRoute, + clusterIdentity, + bindUrl, + allRouteQuery: route.query + }); + if (!clusterIdentity) { + showAlertModal("Missing cluster identity. Please ensure you have authenticated properly.", "Binding Failed", "error"); + return; + } const bindingRequest = { metadata: { name: bindingName @@ -12352,9 +12392,10 @@ ${formattedObject}`; name: templateName }, clusterIdentity: { - identity: consumerId.value || "" + identity: clusterIdentity } }; + console.log("Sending bind request:", JSON.stringify(bindingRequest, null, 2)); const response = yield httpClient.post(bindUrl, bindingRequest); if (response.status === 200) { closeBindingModal(); @@ -12569,8 +12610,8 @@ Details: ${kubeError.details || "No additional details available"}`; }; } }); - const Resources_vue_vue_type_style_index_0_scoped_1c21cc87_lang = ""; - const Resources = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-1c21cc87"]]); + const Resources_vue_vue_type_style_index_0_scoped_5abc4499_lang = ""; + const Resources = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5abc4499"]]); const routes = [ { path: "/", component: Resources }, { path: "/resources", component: Resources } diff --git a/web/dist/assets/index.68bcd12d.css b/web/dist/assets/index.b2bbc5a6.css similarity index 90% rename from web/dist/assets/index.68bcd12d.css rename to web/dist/assets/index.b2bbc5a6.css index b98d69c3e..0639da6b1 100644 --- a/web/dist/assets/index.68bcd12d.css +++ b/web/dist/assets/index.b2bbc5a6.css @@ -1,8 +1,8 @@ -#app[data-v-504b04cf] { +#app[data-v-99283ff2] { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; } -.header[data-v-504b04cf] { +.header[data-v-99283ff2] { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-bottom: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); @@ -10,7 +10,7 @@ top: 0; z-index: 100; } -.header-content[data-v-504b04cf] { +.header-content[data-v-99283ff2] { max-width: 1200px; margin: 0 auto; display: flex; @@ -18,30 +18,30 @@ align-items: center; padding: 1rem 2rem; } -.brand[data-v-504b04cf] { +.brand[data-v-99283ff2] { display: flex; align-items: center; gap: 0.75rem; } -.logo[data-v-504b04cf] { +.logo[data-v-99283ff2] { color: rgba(255, 255, 255, 0.9); display: flex; align-items: center; justify-content: center; } -.header h1[data-v-504b04cf] { +.header h1[data-v-99283ff2] { margin: 0; color: white; font-size: 1.5rem; font-weight: 600; letter-spacing: -0.025em; } -.user-section[data-v-504b04cf] { +.user-section[data-v-99283ff2] { display: flex; align-items: center; gap: 1.5rem; } -.user-info[data-v-504b04cf] { +.user-info[data-v-99283ff2] { display: flex; align-items: center; gap: 0.5rem; @@ -49,15 +49,15 @@ font-size: 0.875rem; font-weight: 500; } -.status-indicator[data-v-504b04cf] { +.status-indicator[data-v-99283ff2] { width: 8px; height: 8px; background: #10b981; border-radius: 50%; box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.3); - animation: pulse-504b04cf 2s infinite; + animation: pulse-99283ff2 2s infinite; } -@keyframes pulse-504b04cf { +@keyframes pulse-99283ff2 { 0%, 100% { opacity: 1; } @@ -65,10 +65,10 @@ opacity: 0.5; } } -.welcome-text[data-v-504b04cf] { +.welcome-text[data-v-99283ff2] { color: rgba(255, 255, 255, 0.8); } -.logout-btn[data-v-504b04cf] { +.logout-btn[data-v-99283ff2] { display: flex; align-items: center; gap: 0.5rem; @@ -83,18 +83,18 @@ transition: all 0.2s ease; backdrop-filter: blur(10px); } -.logout-btn[data-v-504b04cf]:hover { +.logout-btn[data-v-99283ff2]:hover { background: rgba(255, 255, 255, 0.2); color: white; border-color: rgba(255, 255, 255, 0.3); transform: translateY(-1px); } -.main[data-v-504b04cf] { +.main[data-v-99283ff2] { min-height: calc(100vh - 80px); background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 0; } -.auth-placeholder[data-v-504b04cf] { +.auth-placeholder[data-v-99283ff2] { display: flex; flex-direction: column; align-items: center; @@ -103,7 +103,7 @@ padding: 4rem 2rem; min-height: calc(100vh - 200px); } -.auth-placeholder h2[data-v-504b04cf] { +.auth-placeholder h2[data-v-99283ff2] { color: #1f2937; margin-bottom: 1rem; font-size: 2rem; @@ -113,14 +113,14 @@ -webkit-text-fill-color: transparent; background-clip: text; } -.auth-placeholder p[data-v-504b04cf] { +.auth-placeholder p[data-v-99283ff2] { color: #6b7280; margin-bottom: 2rem; font-size: 1.125rem; max-width: 500px; line-height: 1.6; } -.auth-btn[data-v-504b04cf] { +.auth-btn[data-v-99283ff2] { display: inline-flex; align-items: center; gap: 0.5rem; @@ -135,12 +135,12 @@ transition: all 0.2s ease; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); } -.auth-btn[data-v-504b04cf]:hover { +.auth-btn[data-v-99283ff2]:hover { background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4); } -.auth-error[data-v-504b04cf] { +.auth-error[data-v-99283ff2] { display: flex; align-items: flex-start; gap: 1rem; @@ -153,18 +153,18 @@ text-align: left; box-shadow: 0 2px 8px rgba(239, 68, 68, 0.1); } -.error-icon[data-v-504b04cf] { +.error-icon[data-v-99283ff2] { font-size: 1.5rem; line-height: 1; margin-top: 0.125rem; } -.error-content h3[data-v-504b04cf] { +.error-content h3[data-v-99283ff2] { margin: 0 0 0.5rem 0; color: #dc2626; font-size: 1rem; font-weight: 600; } -.error-content p[data-v-504b04cf] { +.error-content p[data-v-99283ff2] { margin: 0; color: #991b1b; font-size: 0.875rem; @@ -912,30 +912,30 @@ to { transform: translateY(0) scale(1); opacity: 1; transform: translateY(-1px); } -.resources[data-v-1c21cc87] { +.resources[data-v-5abc4499] { max-width: 1200px; margin: 0 auto; padding: 2rem; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; } -.header-section[data-v-1c21cc87] { +.header-section[data-v-5abc4499] { margin-bottom: 2rem; } -.resources h2[data-v-1c21cc87] { +.resources h2[data-v-5abc4499] { color: #1e293b; margin-bottom: 1rem; font-size: 1.875rem; font-weight: 600; font-family: inherit; } -.resources h3[data-v-1c21cc87] { +.resources h3[data-v-5abc4499] { color: #334155; font-size: 1.25rem; font-weight: 600; font-family: inherit; margin: 0; } -.cli-indicator[data-v-1c21cc87] { +.cli-indicator[data-v-5abc4499] { background-color: #dbeafe; border: 1px solid #3b82f6; border-radius: 8px; @@ -947,17 +947,17 @@ to { transform: translateY(0) scale(1); opacity: 1; align-items: center; gap: 0.5rem; } -.loading[data-v-1c21cc87] { +.loading[data-v-5abc4499] { text-align: center; padding: 2rem; color: #666; } -.error[data-v-1c21cc87] { +.error[data-v-5abc4499] { text-align: center; padding: 2rem; color: #dc3545; } -.retry-btn[data-v-1c21cc87] { +.retry-btn[data-v-5abc4499] { padding: 0.5rem 1rem; background-color: #007bff; color: white; @@ -966,32 +966,32 @@ to { transform: translateY(0) scale(1); opacity: 1; cursor: pointer; margin-top: 1rem; } -.retry-btn[data-v-1c21cc87]:hover { +.retry-btn[data-v-5abc4499]:hover { background-color: #0056b3; } -.resources-container[data-v-1c21cc87] { +.resources-container[data-v-5abc4499] { display: grid; gap: 3rem; } -.templates-section[data-v-1c21cc87], -.collections-section[data-v-1c21cc87] { +.templates-section[data-v-5abc4499], +.collections-section[data-v-5abc4499] { background: #f8fafc; padding: 1.5rem; border-radius: 12px; border: 1px solid #e2e8f0; } -.no-resources[data-v-1c21cc87] { +.no-resources[data-v-5abc4499] { text-align: center; color: #666; padding: 1rem; } -.section-header[data-v-1c21cc87] { +.section-header[data-v-5abc4499] { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; } -.item-count[data-v-1c21cc87] { +.item-count[data-v-5abc4499] { font-size: 0.9rem; color: #666; background: #e9ecef; @@ -999,7 +999,7 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 12px; font-weight: 500; } -.no-resources[data-v-1c21cc87] { +.no-resources[data-v-5abc4499] { text-align: center; padding: 3rem 2rem; color: #6b7280; @@ -1007,26 +1007,26 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 12px; border: 2px dashed #d1d5db; } -.no-resources-icon[data-v-1c21cc87] { +.no-resources-icon[data-v-5abc4499] { margin-bottom: 1rem; opacity: 0.4; color: #9ca3af; } -.no-resources h4[data-v-1c21cc87] { +.no-resources h4[data-v-5abc4499] { margin: 0 0 0.5rem 0; color: #374151; font-weight: 600; } -.no-resources p[data-v-1c21cc87] { +.no-resources p[data-v-5abc4499] { margin: 0; font-size: 0.9rem; } -.resource-grid[data-v-1c21cc87] { +.resource-grid[data-v-5abc4499] { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1.5rem; } -.template-card[data-v-1c21cc87], .collection-card[data-v-1c21cc87] { +.template-card[data-v-5abc4499], .collection-card[data-v-5abc4499] { background: white; border: 1px solid #e2e8f0; border-radius: 12px; @@ -1035,27 +1035,27 @@ to { transform: translateY(0) scale(1); opacity: 1; overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } -.template-card[data-v-1c21cc87]:hover, .collection-card[data-v-1c21cc87]:hover { +.template-card[data-v-5abc4499]:hover, .collection-card[data-v-5abc4499]:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); border-color: #cbd5e1; } -.card-header[data-v-1c21cc87] { +.card-header[data-v-5abc4499] { padding: 1.5rem 1.5rem 1rem 1.5rem; border-bottom: 1px solid #f1f5f9; } -.card-title[data-v-1c21cc87] { +.card-title[data-v-5abc4499] { margin: 0 0 0.75rem 0; color: #1e293b; font-size: 1.125rem; font-weight: 600; } -.card-badges[data-v-1c21cc87] { +.card-badges[data-v-5abc4499] { display: flex; flex-wrap: wrap; gap: 0.5rem; } -.badge[data-v-1c21cc87] { +.badge[data-v-5abc4499] { font-size: 0.75rem; padding: 0.25rem 0.75rem; border-radius: 12px; @@ -1063,42 +1063,42 @@ to { transform: translateY(0) scale(1); opacity: 1; display: inline-flex; align-items: center; } -.resources-badge[data-v-1c21cc87] { +.resources-badge[data-v-5abc4499] { background: #dbeafe; color: #1d4ed8; } -.permissions-badge[data-v-1c21cc87] { +.permissions-badge[data-v-5abc4499] { background: #fef3c7; color: #d97706; } -.namespaces-badge[data-v-1c21cc87] { +.namespaces-badge[data-v-5abc4499] { background: #d1fae5; color: #047857; } -.card-content[data-v-1c21cc87] { +.card-content[data-v-5abc4499] { padding: 1rem 1.5rem; } -.card-description[data-v-1c21cc87] { +.card-description[data-v-5abc4499] { color: #64748b; margin: 0 0 1rem 0; line-height: 1.5; font-size: 0.9rem; } -.card-preview[data-v-1c21cc87], .collection-templates[data-v-1c21cc87] { +.card-preview[data-v-5abc4499], .collection-templates[data-v-5abc4499] { margin-top: 1rem; } -.card-preview strong[data-v-1c21cc87], .collection-templates strong[data-v-1c21cc87] { +.card-preview strong[data-v-5abc4499], .collection-templates strong[data-v-5abc4499] { color: #374151; font-size: 0.875rem; display: block; margin-bottom: 0.5rem; } -.resource-preview[data-v-1c21cc87], .template-list[data-v-1c21cc87] { +.resource-preview[data-v-5abc4499], .template-list[data-v-5abc4499] { display: flex; flex-wrap: wrap; gap: 0.5rem; } -.resource-tag[data-v-1c21cc87], .template-tag[data-v-1c21cc87] { +.resource-tag[data-v-5abc4499], .template-tag[data-v-5abc4499] { font-size: 0.75rem; background: #f1f5f9; color: #475569; @@ -1106,12 +1106,12 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 6px; border: 1px solid #e2e8f0; } -.more-indicator[data-v-1c21cc87] { +.more-indicator[data-v-5abc4499] { font-size: 0.75rem; color: #6b7280; font-style: italic; } -.card-actions[data-v-1c21cc87] { +.card-actions[data-v-5abc4499] { padding: 1rem 1.5rem; background: #fafbfc; border-top: 1px solid #f1f5f9; @@ -1119,7 +1119,7 @@ to { transform: translateY(0) scale(1); opacity: 1; gap: 0.75rem; justify-content: flex-end; } -.details-btn[data-v-1c21cc87], .bind-btn[data-v-1c21cc87] { +.details-btn[data-v-5abc4499], .bind-btn[data-v-5abc4499] { padding: 0.5rem 1rem; border: none; border-radius: 6px; @@ -1128,21 +1128,21 @@ to { transform: translateY(0) scale(1); opacity: 1; font-size: 0.875rem; transition: all 0.2s ease; } -.details-btn[data-v-1c21cc87] { +.details-btn[data-v-5abc4499] { background: #f8fafc; color: #475569; border: 1px solid #e2e8f0; } -.details-btn[data-v-1c21cc87]:hover { +.details-btn[data-v-5abc4499]:hover { background: #f1f5f9; border-color: #cbd5e1; } -.bind-btn[data-v-1c21cc87] { +.bind-btn[data-v-5abc4499] { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: 1px solid transparent; } -.bind-btn[data-v-1c21cc87]:hover { +.bind-btn[data-v-5abc4499]:hover { background: linear-gradient(135deg, #059669 0%, #047857 100%); transform: translateY(-1px); box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2); diff --git a/web/dist/index.html b/web/dist/index.html index 54f799d9f..313a675a3 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -5,8 +5,8 @@ Kube Bind - - + +
diff --git a/web/src/App.vue b/web/src/App.vue index 52849843c..8672ef9ef 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -28,7 +28,7 @@ - +

Authentication Required

@@ -45,7 +45,7 @@ {{ authStatus.error ? 'Try Again' : 'Authenticate' }}
- +
@@ -85,10 +85,9 @@ const authenticate = async () => { hasAttemptedAuth.value = true const cluster = route.query.cluster_id as string || '' const sessionId = route.query.session_id as string || generateSessionId() - const clientSideRedirectUrl = route.query.redirect_url as string || '' const consumerId = route.query.consumer_id as string || '' - await authService.initiateAuth(sessionId, cluster, clientSideRedirectUrl, consumerId) + await authService.initiateAuth(sessionId, cluster, consumerId) } catch (error) { console.error('Authentication failed:', error) authStatus.value.error = 'Authentication failed' @@ -330,4 +329,4 @@ onMounted(() => { font-size: 0.875rem; line-height: 1.5; } - \ No newline at end of file + diff --git a/web/src/services/auth.ts b/web/src/services/auth.ts index 2be638483..a21132b3a 100644 --- a/web/src/services/auth.ts +++ b/web/src/services/auth.ts @@ -25,30 +25,26 @@ class AuthService { const urlParams = new URLSearchParams(window.location.search) const clusterId = urlParams.get('cluster_id') || '' const consumerId = urlParams.get('consumer_id') || '' - // Make a simple API call to check if we're authenticated const authCheckUrl = clusterId ? `/ping?cluster_id=${clusterId}` : '/ping' - const response = await httpClient.get(authCheckUrl) - const isAuth = response.status === 200 console.log('Auth check:', { clusterId, status: response.status, isAuth }) - return { isAuthenticated: isAuth } } catch (error) { // Handle structured errors if (error instanceof StructuredError) { const kubeError = error.kubeBindError - + // Return structured error message for auth/authorization failures if (kubeError.code === ErrorCodes.AUTHENTICATION_FAILED || kubeError.code === ErrorCodes.AUTHORIZATION_FAILED) { - return { - isAuthenticated: false, - error: kubeError.message + return { + isAuthenticated: false, + error: kubeError.message } } } - + // Handle cases where we get 401/403 but don't have the expected structured error if ((error as any)?.response?.status === 401 || (error as any)?.response?.status === 403) { console.log('Auth check failed with authentication error but no structured response') @@ -57,11 +53,11 @@ class AuthService { error: 'Authentication required' } } - + console.error('Auth check error:', error) - return { - isAuthenticated: false, - error: 'Authentication check failed' + return { + isAuthenticated: false, + error: 'Authentication check failed' } } } @@ -69,7 +65,6 @@ class AuthService { async initiateAuth( sessionId: string, clusterId: string, - clientSideRedirectUrl?: string, consumerId?: string, ): Promise { const authUrl = `/api/authorize` @@ -115,22 +110,22 @@ class AuthService { // Call backend logout endpoint to clear HttpOnly cookies const urlParams = new URLSearchParams(window.location.search) const clusterId = urlParams.get('cluster_id') || '' - + // Build logout URL with cluster_id parameter if present - const logoutUrl = clusterId + const logoutUrl = clusterId ? `/api/logout?cluster_id=${encodeURIComponent(clusterId)}` : '/api/logout' - + // Make POST request to logout endpoint const response = await httpClient.post(logoutUrl) - + console.log('Logout response:', { status: response.status, clusterId }) - + // Redirect to clear any cached state regardless of response window.location.href = window.location.origin + window.location.pathname } catch (error) { console.error('Logout error:', error) - + // Still try to reload even if there was an error window.location.reload() } @@ -142,23 +137,59 @@ class AuthService { // based on the current cluster_id in the URL const urlParams = new URLSearchParams(window.location.search) const clusterId = urlParams.get('cluster_id') || '' - + // Return the expected cookie name format for the HTTP interceptor to use return clusterId ? `kube-bind-${clusterId}` : 'kube-bind' } isCliFlow(): boolean { const urlParams = new URLSearchParams(window.location.search) - return urlParams.has('redirect_url') + // Check URL params first + if (urlParams.has('redirect_url')) { + return true + } + + // Check sessionStorage in case params were lost during OAuth flow + const preservedParams = sessionStorage.getItem('kube-bind-preserved-params') + if (preservedParams) { + try { + const params = JSON.parse(preservedParams) + return !!params.redirect_url + } catch (e) { + return false + } + } + + return false } redirectToCliCallback(bindingResponseData: any): void { + // Try to get parameters from URL first, then fall back to sessionStorage const urlParams = new URLSearchParams(window.location.search) - const redirectUrl = urlParams.get('redirect_url') - const sessionId = urlParams.get('session_id') - const consumerId = urlParams.get('consumer_id') + let redirectUrl = urlParams.get('redirect_url') + let sessionId = urlParams.get('session_id') + let consumerId = urlParams.get('consumer_id') + + // If not in URL, check sessionStorage (in case they were lost during OAuth flow) + if (!redirectUrl) { + const preservedParams = sessionStorage.getItem('kube-bind-preserved-params') + if (preservedParams) { + try { + const params = JSON.parse(preservedParams) + redirectUrl = params.redirect_url || null + sessionId = sessionId || params.session_id || null + consumerId = consumerId || params.consumer_id || null + console.log('Retrieved CLI params from sessionStorage:', { redirectUrl, sessionId, consumerId }) + } catch (e) { + console.error('Failed to parse preserved params:', e) + } + } + } + if (redirectUrl) { + // Construct the callback URL entirely on the client side const callbackUrl = new URL(redirectUrl) + if (sessionId) { callbackUrl.searchParams.append('session_id', sessionId) } @@ -170,7 +201,10 @@ class AuthService { const base64Response = btoa(JSON.stringify(bindingResponseData)) callbackUrl.searchParams.append('binding_response', base64Response) + console.log('Redirecting to CLI callback:', callbackUrl.toString()) window.location.href = callbackUrl.toString() + } else { + console.error('No redirect URL found for CLI callback') } } @@ -214,4 +248,4 @@ class AuthService { } } -export const authService = new AuthService() \ No newline at end of file +export const authService = new AuthService() diff --git a/web/src/views/Resources.vue b/web/src/views/Resources.vue index 73f0a7294..c36ba03d1 100644 --- a/web/src/views/Resources.vue +++ b/web/src/views/Resources.vue @@ -321,6 +321,26 @@ const handleBind = async (templateName: string, bindingName: string) => { const bindUrl = buildApiUrl('/bind') // Create the binding request + // Use consumerId if available (CLI flow), otherwise use sessionId as cluster identity + // Read from Vue Router's route.query instead of window.location + const sessionIdFromRoute = route.query.session_id as string || '' + const clusterIdentity = consumerId.value || sessionIdFromRoute + + console.log('Bind request parameters:', { + templateName, + bindingName, + consumerId: consumerId.value, + sessionIdFromRoute, + clusterIdentity, + bindUrl, + allRouteQuery: route.query + }) + + if (!clusterIdentity) { + showAlertModal('Missing cluster identity. Please ensure you have authenticated properly.', 'Binding Failed', 'error') + return + } + const bindingRequest: BindableResourcesRequest = { metadata: { name: bindingName @@ -329,10 +349,12 @@ const handleBind = async (templateName: string, bindingName: string) => { name: templateName }, clusterIdentity: { - identity: consumerId.value || '' + identity: clusterIdentity } } + console.log('Sending bind request:', JSON.stringify(bindingRequest, null, 2)) + const response = await httpClient.post(bindUrl, bindingRequest) if (response.status === 200) { From dfcecb3307e23b176bbb531750e6b4bbf20d1177 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Wed, 21 Jan 2026 12:14:37 +0100 Subject: [PATCH 3/5] update review code Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- ...{index.b2bbc5a6.css => index.2f8736bf.css} | 84 +++++++++--------- .../{index.7407f299.js => index.6494c4af.js} | 32 +++---- web/dist/index.html | 4 +- web/src/services/auth.ts | 27 +++--- web/src/views/Resources.vue | 88 ++++++++----------- 5 files changed, 107 insertions(+), 128 deletions(-) rename web/dist/assets/{index.b2bbc5a6.css => index.2f8736bf.css} (93%) rename web/dist/assets/{index.7407f299.js => index.6494c4af.js} (99%) diff --git a/web/dist/assets/index.b2bbc5a6.css b/web/dist/assets/index.2f8736bf.css similarity index 93% rename from web/dist/assets/index.b2bbc5a6.css rename to web/dist/assets/index.2f8736bf.css index 0639da6b1..1d965fc82 100644 --- a/web/dist/assets/index.b2bbc5a6.css +++ b/web/dist/assets/index.2f8736bf.css @@ -912,30 +912,30 @@ to { transform: translateY(0) scale(1); opacity: 1; transform: translateY(-1px); } -.resources[data-v-5abc4499] { +.resources[data-v-32c19ba7] { max-width: 1200px; margin: 0 auto; padding: 2rem; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; } -.header-section[data-v-5abc4499] { +.header-section[data-v-32c19ba7] { margin-bottom: 2rem; } -.resources h2[data-v-5abc4499] { +.resources h2[data-v-32c19ba7] { color: #1e293b; margin-bottom: 1rem; font-size: 1.875rem; font-weight: 600; font-family: inherit; } -.resources h3[data-v-5abc4499] { +.resources h3[data-v-32c19ba7] { color: #334155; font-size: 1.25rem; font-weight: 600; font-family: inherit; margin: 0; } -.cli-indicator[data-v-5abc4499] { +.cli-indicator[data-v-32c19ba7] { background-color: #dbeafe; border: 1px solid #3b82f6; border-radius: 8px; @@ -947,17 +947,17 @@ to { transform: translateY(0) scale(1); opacity: 1; align-items: center; gap: 0.5rem; } -.loading[data-v-5abc4499] { +.loading[data-v-32c19ba7] { text-align: center; padding: 2rem; color: #666; } -.error[data-v-5abc4499] { +.error[data-v-32c19ba7] { text-align: center; padding: 2rem; color: #dc3545; } -.retry-btn[data-v-5abc4499] { +.retry-btn[data-v-32c19ba7] { padding: 0.5rem 1rem; background-color: #007bff; color: white; @@ -966,32 +966,32 @@ to { transform: translateY(0) scale(1); opacity: 1; cursor: pointer; margin-top: 1rem; } -.retry-btn[data-v-5abc4499]:hover { +.retry-btn[data-v-32c19ba7]:hover { background-color: #0056b3; } -.resources-container[data-v-5abc4499] { +.resources-container[data-v-32c19ba7] { display: grid; gap: 3rem; } -.templates-section[data-v-5abc4499], -.collections-section[data-v-5abc4499] { +.templates-section[data-v-32c19ba7], +.collections-section[data-v-32c19ba7] { background: #f8fafc; padding: 1.5rem; border-radius: 12px; border: 1px solid #e2e8f0; } -.no-resources[data-v-5abc4499] { +.no-resources[data-v-32c19ba7] { text-align: center; color: #666; padding: 1rem; } -.section-header[data-v-5abc4499] { +.section-header[data-v-32c19ba7] { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; } -.item-count[data-v-5abc4499] { +.item-count[data-v-32c19ba7] { font-size: 0.9rem; color: #666; background: #e9ecef; @@ -999,7 +999,7 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 12px; font-weight: 500; } -.no-resources[data-v-5abc4499] { +.no-resources[data-v-32c19ba7] { text-align: center; padding: 3rem 2rem; color: #6b7280; @@ -1007,26 +1007,26 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 12px; border: 2px dashed #d1d5db; } -.no-resources-icon[data-v-5abc4499] { +.no-resources-icon[data-v-32c19ba7] { margin-bottom: 1rem; opacity: 0.4; color: #9ca3af; } -.no-resources h4[data-v-5abc4499] { +.no-resources h4[data-v-32c19ba7] { margin: 0 0 0.5rem 0; color: #374151; font-weight: 600; } -.no-resources p[data-v-5abc4499] { +.no-resources p[data-v-32c19ba7] { margin: 0; font-size: 0.9rem; } -.resource-grid[data-v-5abc4499] { +.resource-grid[data-v-32c19ba7] { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1.5rem; } -.template-card[data-v-5abc4499], .collection-card[data-v-5abc4499] { +.template-card[data-v-32c19ba7], .collection-card[data-v-32c19ba7] { background: white; border: 1px solid #e2e8f0; border-radius: 12px; @@ -1035,27 +1035,27 @@ to { transform: translateY(0) scale(1); opacity: 1; overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } -.template-card[data-v-5abc4499]:hover, .collection-card[data-v-5abc4499]:hover { +.template-card[data-v-32c19ba7]:hover, .collection-card[data-v-32c19ba7]:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); border-color: #cbd5e1; } -.card-header[data-v-5abc4499] { +.card-header[data-v-32c19ba7] { padding: 1.5rem 1.5rem 1rem 1.5rem; border-bottom: 1px solid #f1f5f9; } -.card-title[data-v-5abc4499] { +.card-title[data-v-32c19ba7] { margin: 0 0 0.75rem 0; color: #1e293b; font-size: 1.125rem; font-weight: 600; } -.card-badges[data-v-5abc4499] { +.card-badges[data-v-32c19ba7] { display: flex; flex-wrap: wrap; gap: 0.5rem; } -.badge[data-v-5abc4499] { +.badge[data-v-32c19ba7] { font-size: 0.75rem; padding: 0.25rem 0.75rem; border-radius: 12px; @@ -1063,42 +1063,42 @@ to { transform: translateY(0) scale(1); opacity: 1; display: inline-flex; align-items: center; } -.resources-badge[data-v-5abc4499] { +.resources-badge[data-v-32c19ba7] { background: #dbeafe; color: #1d4ed8; } -.permissions-badge[data-v-5abc4499] { +.permissions-badge[data-v-32c19ba7] { background: #fef3c7; color: #d97706; } -.namespaces-badge[data-v-5abc4499] { +.namespaces-badge[data-v-32c19ba7] { background: #d1fae5; color: #047857; } -.card-content[data-v-5abc4499] { +.card-content[data-v-32c19ba7] { padding: 1rem 1.5rem; } -.card-description[data-v-5abc4499] { +.card-description[data-v-32c19ba7] { color: #64748b; margin: 0 0 1rem 0; line-height: 1.5; font-size: 0.9rem; } -.card-preview[data-v-5abc4499], .collection-templates[data-v-5abc4499] { +.card-preview[data-v-32c19ba7], .collection-templates[data-v-32c19ba7] { margin-top: 1rem; } -.card-preview strong[data-v-5abc4499], .collection-templates strong[data-v-5abc4499] { +.card-preview strong[data-v-32c19ba7], .collection-templates strong[data-v-32c19ba7] { color: #374151; font-size: 0.875rem; display: block; margin-bottom: 0.5rem; } -.resource-preview[data-v-5abc4499], .template-list[data-v-5abc4499] { +.resource-preview[data-v-32c19ba7], .template-list[data-v-32c19ba7] { display: flex; flex-wrap: wrap; gap: 0.5rem; } -.resource-tag[data-v-5abc4499], .template-tag[data-v-5abc4499] { +.resource-tag[data-v-32c19ba7], .template-tag[data-v-32c19ba7] { font-size: 0.75rem; background: #f1f5f9; color: #475569; @@ -1106,12 +1106,12 @@ to { transform: translateY(0) scale(1); opacity: 1; border-radius: 6px; border: 1px solid #e2e8f0; } -.more-indicator[data-v-5abc4499] { +.more-indicator[data-v-32c19ba7] { font-size: 0.75rem; color: #6b7280; font-style: italic; } -.card-actions[data-v-5abc4499] { +.card-actions[data-v-32c19ba7] { padding: 1rem 1.5rem; background: #fafbfc; border-top: 1px solid #f1f5f9; @@ -1119,7 +1119,7 @@ to { transform: translateY(0) scale(1); opacity: 1; gap: 0.75rem; justify-content: flex-end; } -.details-btn[data-v-5abc4499], .bind-btn[data-v-5abc4499] { +.details-btn[data-v-32c19ba7], .bind-btn[data-v-32c19ba7] { padding: 0.5rem 1rem; border: none; border-radius: 6px; @@ -1128,21 +1128,21 @@ to { transform: translateY(0) scale(1); opacity: 1; font-size: 0.875rem; transition: all 0.2s ease; } -.details-btn[data-v-5abc4499] { +.details-btn[data-v-32c19ba7] { background: #f8fafc; color: #475569; border: 1px solid #e2e8f0; } -.details-btn[data-v-5abc4499]:hover { +.details-btn[data-v-32c19ba7]:hover { background: #f1f5f9; border-color: #cbd5e1; } -.bind-btn[data-v-5abc4499] { +.bind-btn[data-v-32c19ba7] { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: 1px solid transparent; } -.bind-btn[data-v-5abc4499]:hover { +.bind-btn[data-v-32c19ba7]:hover { background: linear-gradient(135deg, #059669 0%, #047857 100%); transform: translateY(-1px); box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2); diff --git a/web/dist/assets/index.7407f299.js b/web/dist/assets/index.6494c4af.js similarity index 99% rename from web/dist/assets/index.7407f299.js rename to web/dist/assets/index.6494c4af.js index b2f56e651..2163c2393 100644 --- a/web/dist/assets/index.7407f299.js +++ b/web/dist/assets/index.6494c4af.js @@ -98,7 +98,7 @@ var __yieldStar = (value) => { }; var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it); var require_index_001 = __commonJS({ - "assets/index.7407f299.js"(exports) { + "assets/index.6494c4af.js"(exports) { (function polyfill() { const relList = document.createElement("link").relList; if (relList && relList.supports && relList.supports("modulepreload")) { @@ -11034,6 +11034,7 @@ var require_index_001 = __commonJS({ step((generator = generator.apply(__this, __arguments)).next()); }); }; + const PRESERVED_PARAMS_STORAGE_KEY = "kube-bind-preserved-params"; class AuthService { isAuthenticated() { return __async$4(this, null, function* () { @@ -11047,7 +11048,6 @@ var require_index_001 = __commonJS({ try { const urlParams = new URLSearchParams(window.location.search); const clusterId = urlParams.get("cluster_id") || ""; - const consumerId = urlParams.get("consumer_id") || ""; const authCheckUrl = clusterId ? `/ping?cluster_id=${clusterId}` : "/ping"; const response = yield httpClient.get(authCheckUrl); const isAuth = response.status === 200; @@ -11096,7 +11096,7 @@ var require_index_001 = __commonJS({ if (currentParams.has("cluster_id")) { paramsToPreserve.cluster_id = currentParams.get("cluster_id"); } - sessionStorage.setItem("kube-bind-preserved-params", JSON.stringify(paramsToPreserve)); + sessionStorage.setItem(PRESERVED_PARAMS_STORAGE_KEY, JSON.stringify(paramsToPreserve)); const params = new URLSearchParams({ session_id: sessionId, redirect_url, @@ -11135,7 +11135,7 @@ var require_index_001 = __commonJS({ if (urlParams.has("redirect_url")) { return true; } - const preservedParams = sessionStorage.getItem("kube-bind-preserved-params"); + const preservedParams = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY); if (preservedParams) { try { const params = JSON.parse(preservedParams); @@ -11152,7 +11152,7 @@ var require_index_001 = __commonJS({ let sessionId = urlParams.get("session_id"); let consumerId = urlParams.get("consumer_id"); if (!redirectUrl) { - const preservedParams = sessionStorage.getItem("kube-bind-preserved-params"); + const preservedParams = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY); if (preservedParams) { try { const params = JSON.parse(preservedParams); @@ -11182,7 +11182,7 @@ var require_index_001 = __commonJS({ } } restorePreservedParams() { - const preservedParamsJson = sessionStorage.getItem("kube-bind-preserved-params"); + const preservedParamsJson = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY); if (!preservedParamsJson) { return; } @@ -11197,15 +11197,15 @@ var require_index_001 = __commonJS({ } } if (needsUpdate) { - sessionStorage.removeItem("kube-bind-preserved-params"); + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY); const newUrl = `${window.location.pathname}?${currentParams.toString()}`; window.location.replace(newUrl); } else { - sessionStorage.removeItem("kube-bind-preserved-params"); + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY); } } catch (error) { console.error("Failed to restore preserved params:", error); - sessionStorage.removeItem("kube-bind-preserved-params"); + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY); } } } @@ -12371,15 +12371,6 @@ ${formattedObject}`; const bindUrl = buildApiUrl("/bind"); const sessionIdFromRoute = route.query.session_id || ""; const clusterIdentity = consumerId.value || sessionIdFromRoute; - console.log("Bind request parameters:", { - templateName, - bindingName, - consumerId: consumerId.value, - sessionIdFromRoute, - clusterIdentity, - bindUrl, - allRouteQuery: route.query - }); if (!clusterIdentity) { showAlertModal("Missing cluster identity. Please ensure you have authenticated properly.", "Binding Failed", "error"); return; @@ -12395,7 +12386,6 @@ ${formattedObject}`; identity: clusterIdentity } }; - console.log("Sending bind request:", JSON.stringify(bindingRequest, null, 2)); const response = yield httpClient.post(bindUrl, bindingRequest); if (response.status === 200) { closeBindingModal(); @@ -12610,8 +12600,8 @@ Details: ${kubeError.details || "No additional details available"}`; }; } }); - const Resources_vue_vue_type_style_index_0_scoped_5abc4499_lang = ""; - const Resources = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5abc4499"]]); + const Resources_vue_vue_type_style_index_0_scoped_32c19ba7_lang = ""; + const Resources = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-32c19ba7"]]); const routes = [ { path: "/", component: Resources }, { path: "/resources", component: Resources } diff --git a/web/dist/index.html b/web/dist/index.html index 313a675a3..823056065 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -5,8 +5,8 @@ Kube Bind - - + +
diff --git a/web/src/services/auth.ts b/web/src/services/auth.ts index a21132b3a..238645ab9 100644 --- a/web/src/services/auth.ts +++ b/web/src/services/auth.ts @@ -13,6 +13,8 @@ export interface AuthCheckResult { error?: string } +const PRESERVED_PARAMS_STORAGE_KEY = 'kube-bind-preserved-params' + class AuthService { async isAuthenticated(): Promise { const result = await this.checkAuthentication() @@ -24,7 +26,6 @@ class AuthService { // Since cookies are HTTP-only, we need to check authentication by making an API call const urlParams = new URLSearchParams(window.location.search) const clusterId = urlParams.get('cluster_id') || '' - const consumerId = urlParams.get('consumer_id') || '' // Make a simple API call to check if we're authenticated const authCheckUrl = clusterId ? `/ping?cluster_id=${clusterId}` : '/ping' const response = await httpClient.get(authCheckUrl) @@ -89,7 +90,7 @@ class AuthService { paramsToPreserve.cluster_id = currentParams.get('cluster_id')! } - sessionStorage.setItem('kube-bind-preserved-params', JSON.stringify(paramsToPreserve)) + sessionStorage.setItem(PRESERVED_PARAMS_STORAGE_KEY, JSON.stringify(paramsToPreserve)) const params = new URLSearchParams({ session_id: sessionId, @@ -148,9 +149,9 @@ class AuthService { if (urlParams.has('redirect_url')) { return true } - + // Check sessionStorage in case params were lost during OAuth flow - const preservedParams = sessionStorage.getItem('kube-bind-preserved-params') + const preservedParams = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY) if (preservedParams) { try { const params = JSON.parse(preservedParams) @@ -159,7 +160,7 @@ class AuthService { return false } } - + return false } @@ -169,10 +170,10 @@ class AuthService { let redirectUrl = urlParams.get('redirect_url') let sessionId = urlParams.get('session_id') let consumerId = urlParams.get('consumer_id') - + // If not in URL, check sessionStorage (in case they were lost during OAuth flow) if (!redirectUrl) { - const preservedParams = sessionStorage.getItem('kube-bind-preserved-params') + const preservedParams = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY) if (preservedParams) { try { const params = JSON.parse(preservedParams) @@ -185,11 +186,11 @@ class AuthService { } } } - + if (redirectUrl) { // Construct the callback URL entirely on the client side const callbackUrl = new URL(redirectUrl) - + if (sessionId) { callbackUrl.searchParams.append('session_id', sessionId) } @@ -210,7 +211,7 @@ class AuthService { restorePreservedParams(): void { // Check if we have preserved params from before OAuth redirect - const preservedParamsJson = sessionStorage.getItem('kube-bind-preserved-params') + const preservedParamsJson = sessionStorage.getItem(PRESERVED_PARAMS_STORAGE_KEY) if (!preservedParamsJson) { return @@ -232,18 +233,18 @@ class AuthService { if (needsUpdate) { // Clear the stored params - sessionStorage.removeItem('kube-bind-preserved-params') + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY) // Update URL with preserved params and reload to ensure Vue Router picks up the changes const newUrl = `${window.location.pathname}?${currentParams.toString()}` window.location.replace(newUrl) } else { // Params are already in URL, just clean up storage - sessionStorage.removeItem('kube-bind-preserved-params') + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY) } } catch (error) { console.error('Failed to restore preserved params:', error) - sessionStorage.removeItem('kube-bind-preserved-params') + sessionStorage.removeItem(PRESERVED_PARAMS_STORAGE_KEY) } } } diff --git a/web/src/views/Resources.vue b/web/src/views/Resources.vue index c36ba03d1..1a9e9f486 100644 --- a/web/src/views/Resources.vue +++ b/web/src/views/Resources.vue @@ -6,24 +6,24 @@ CLI Mode: Select a template to bind for CLI - +
Loading resources...
- +

Error Loading Resources

{{ error }}

- +

Templates

{{ templates.length }} available
- +
@@ -33,7 +33,7 @@

No templates available

There are no API service export templates available in this cluster.

- +
@@ -50,18 +50,18 @@
- +

{{ template.spec.description }}

- +
Key Resources:
- {{ resource.resource }} @@ -72,7 +72,7 @@
- +
- +

Collections

{{ collections.length }} available
- +

{{ collection.metadata.name }}

- +

{{ collection.spec.description }}

- +
Templates in this collection:
- {{ templateName }} @@ -122,7 +122,7 @@
- + { const params = new URLSearchParams() if (cluster.value) params.set('cluster_id', cluster.value) if (consumerId.value) params.set('consumer_id', consumerId.value) - + return params.toString() ? `${endpoint}?${params.toString()}` : endpoint } const loadResources = async () => { loading.value = true error.value = null - + try { const templatesUrl = buildApiUrl('/templates') const collectionsUrl = buildApiUrl('/collections') - + const [templatesResponse, collectionsResponse] = await Promise.all([ httpClient.get(templatesUrl), httpClient.get(collectionsUrl) ]) - + templates.value = templatesResponse.data.items || [] collections.value = collectionsResponse.data.items || [] } catch (err: any) { console.error('Failed to load resources:', err) - + // Handle structured errors if (err instanceof StructuredError) { const kubeError = err.kubeBindError - + // Don't show error for auth failures - let the HTTP interceptor handle them if (kubeError.code === 'AUTHENTICATION_FAILED') { return } - + // Show specific error messages based on error code switch (kubeError.code) { case 'AUTHORIZATION_FAILED': @@ -319,28 +319,18 @@ const showTemplateDetails = (template: Template) => { const handleBind = async (templateName: string, bindingName: string) => { try { const bindUrl = buildApiUrl('/bind') - + // Create the binding request // Use consumerId if available (CLI flow), otherwise use sessionId as cluster identity // Read from Vue Router's route.query instead of window.location const sessionIdFromRoute = route.query.session_id as string || '' const clusterIdentity = consumerId.value || sessionIdFromRoute - - console.log('Bind request parameters:', { - templateName, - bindingName, - consumerId: consumerId.value, - sessionIdFromRoute, - clusterIdentity, - bindUrl, - allRouteQuery: route.query - }) - + if (!clusterIdentity) { showAlertModal('Missing cluster identity. Please ensure you have authenticated properly.', 'Binding Failed', 'error') return } - + const bindingRequest: BindableResourcesRequest = { metadata: { name: bindingName @@ -352,15 +342,13 @@ const handleBind = async (templateName: string, bindingName: string) => { identity: clusterIdentity } } - - console.log('Sending bind request:', JSON.stringify(bindingRequest, null, 2)) - + const response = await httpClient.post(bindUrl, bindingRequest) - + if (response.status === 200) { // Close the binding modal first closeBindingModal() - + // Check if this is a CLI flow if (authService.isCliFlow()) { console.log(response.data) @@ -376,16 +364,16 @@ const handleBind = async (templateName: string, bindingName: string) => { } } catch (err: any) { console.error('Failed to bind template:', err) - + // Handle structured errors if (err instanceof StructuredError) { const kubeError = err.kubeBindError - + // Don't show alert for auth failures - let the HTTP interceptor handle them if (kubeError.code === 'AUTHENTICATION_FAILED') { return } - + // Show specific error messages based on error code let errorMessage: string switch (kubeError.code) { @@ -401,7 +389,7 @@ const handleBind = async (templateName: string, bindingName: string) => { default: errorMessage = `Failed to bind template: ${kubeError.message}\n\nDetails: ${kubeError.details || 'No additional details available'}` } - + showAlertModal(errorMessage, 'Binding Failed', 'error', true) } else { // Fallback for non-structured errors @@ -714,4 +702,4 @@ onMounted(() => { box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2); } - \ No newline at end of file + From e89cff6c73da0b466a3f7c15615e1eb3ab107d97 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Wed, 21 Jan 2026 13:17:14 +0100 Subject: [PATCH 4/5] remove not needed logs Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- web/dist/assets/{index.6494c4af.js => index.a8fc1aeb.js} | 4 +--- web/dist/index.html | 2 +- web/src/services/auth.ts | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) rename web/dist/assets/{index.6494c4af.js => index.a8fc1aeb.js} (99%) diff --git a/web/dist/assets/index.6494c4af.js b/web/dist/assets/index.a8fc1aeb.js similarity index 99% rename from web/dist/assets/index.6494c4af.js rename to web/dist/assets/index.a8fc1aeb.js index 2163c2393..eea8ce063 100644 --- a/web/dist/assets/index.6494c4af.js +++ b/web/dist/assets/index.a8fc1aeb.js @@ -98,7 +98,7 @@ var __yieldStar = (value) => { }; var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it); var require_index_001 = __commonJS({ - "assets/index.6494c4af.js"(exports) { + "assets/index.a8fc1aeb.js"(exports) { (function polyfill() { const relList = document.createElement("link").relList; if (relList && relList.supports && relList.supports("modulepreload")) { @@ -11159,7 +11159,6 @@ var require_index_001 = __commonJS({ redirectUrl = params.redirect_url || null; sessionId = sessionId || params.session_id || null; consumerId = consumerId || params.consumer_id || null; - console.log("Retrieved CLI params from sessionStorage:", { redirectUrl, sessionId, consumerId }); } catch (e) { console.error("Failed to parse preserved params:", e); } @@ -11175,7 +11174,6 @@ var require_index_001 = __commonJS({ } const base64Response = btoa(JSON.stringify(bindingResponseData)); callbackUrl.searchParams.append("binding_response", base64Response); - console.log("Redirecting to CLI callback:", callbackUrl.toString()); window.location.href = callbackUrl.toString(); } else { console.error("No redirect URL found for CLI callback"); diff --git a/web/dist/index.html b/web/dist/index.html index 823056065..287c954f6 100644 --- a/web/dist/index.html +++ b/web/dist/index.html @@ -5,7 +5,7 @@ Kube Bind - + diff --git a/web/src/services/auth.ts b/web/src/services/auth.ts index 238645ab9..c55c297c4 100644 --- a/web/src/services/auth.ts +++ b/web/src/services/auth.ts @@ -180,7 +180,6 @@ class AuthService { redirectUrl = params.redirect_url || null sessionId = sessionId || params.session_id || null consumerId = consumerId || params.consumer_id || null - console.log('Retrieved CLI params from sessionStorage:', { redirectUrl, sessionId, consumerId }) } catch (e) { console.error('Failed to parse preserved params:', e) } @@ -202,7 +201,6 @@ class AuthService { const base64Response = btoa(JSON.stringify(bindingResponseData)) callbackUrl.searchParams.append('binding_response', base64Response) - console.log('Redirecting to CLI callback:', callbackUrl.toString()) window.location.href = callbackUrl.toString() } else { console.error('No redirect URL found for CLI callback') From 9c33b0f6cc2e385c0fd6f7d462a62ff973fb5deb Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Wed, 21 Jan 2026 13:22:48 +0100 Subject: [PATCH 5/5] remove redirect from client params Signed-off-by: Karol Szwaj On-behalf-of: @SAP karol.szwaj@sap.com --- backend/client/client.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/client/client.go b/backend/client/client.go index ab22fae3d..fb14658b7 100644 --- a/backend/client/client.go +++ b/backend/client/client.go @@ -58,9 +58,6 @@ func (r *ClientParameters) WithParams(urlStr string) string { if r.ClusterID != "" { query.Set("cluster_id", r.ClusterID) } - if r.RedirectURL != "" { - query.Set("redirect_url", r.RedirectURL) - } if r.SessionID != "" { query.Set("session_id", r.SessionID) }