From 32ac55d452ed3d4bcd70a417a758df84a02a3817 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Tue, 27 May 2025 22:07:10 -0700 Subject: [PATCH 1/8] add tokenExchangeCallback --- src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.ts b/src/index.ts index e33da29..4e4e1d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,4 +23,9 @@ export default new OAuthProvider({ authorizeEndpoint: "/authorize", tokenEndpoint: "/token", clientRegistrationEndpoint: "/register", + accessTokenTTL: 30, + tokenExchangeCallback: async (options) => { + if (options.grantType === "refresh_token") { + console.log("tokenExchangeCallback- refreshtoken options", options); + }, }); From 2c754daa21065481c498dfaacb56be5f4b6d2b25 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Tue, 27 May 2025 22:11:37 -0700 Subject: [PATCH 2/8] correct error --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 4e4e1d7..e466cb8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ export default new OAuthProvider({ accessTokenTTL: 30, tokenExchangeCallback: async (options) => { if (options.grantType === "refresh_token") { - console.log("tokenExchangeCallback- refreshtoken options", options); + console.log("tokenExchangeCallback- refreshtoken options", options); + } }, }); From 7e398c44602870ca3f5bb1c7f89d34501f7de7d5 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Tue, 27 May 2025 22:16:19 -0700 Subject: [PATCH 3/8] return on other types of grant --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index e466cb8..65a1805 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,5 +28,6 @@ export default new OAuthProvider({ if (options.grantType === "refresh_token") { console.log("tokenExchangeCallback- refreshtoken options", options); } + return; }, }); From 1b4bf961253e6452a4a6e9d174ffae9ef2193095 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Tue, 27 May 2025 22:19:33 -0700 Subject: [PATCH 4/8] increase accesstoken ttl to 65 --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 65a1805..1653740 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ export default new OAuthProvider({ authorizeEndpoint: "/authorize", tokenEndpoint: "/token", clientRegistrationEndpoint: "/register", - accessTokenTTL: 30, + accessTokenTTL: 65, tokenExchangeCallback: async (options) => { if (options.grantType === "refresh_token") { console.log("tokenExchangeCallback- refreshtoken options", options); From 25c9323867b5275c5a394c3f55ded1e6ae8c9be6 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Tue, 27 May 2025 22:45:27 -0700 Subject: [PATCH 5/8] test --- src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1653740..938159e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,8 +25,14 @@ export default new OAuthProvider({ clientRegistrationEndpoint: "/register", accessTokenTTL: 65, tokenExchangeCallback: async (options) => { - if (options.grantType === "refresh_token") { - console.log("tokenExchangeCallback- refreshtoken options", options); + if (options.grantType === "authorization_code") { + // Your custom logic here + console.log("User logged in with code grant:", { + options + }); + + // Don't return anything → fallback to default behavior + return; } return; }, From 28b3ef3575058d135ca6536ab2d411c9b46d7eab Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Wed, 28 May 2025 12:45:54 -0700 Subject: [PATCH 6/8] ttl time to 120 and token validity time to 150 --- src/index.ts | 49 ++++++++++++++++++++++++++------ src/oauth-manager/token-utils.ts | 2 +- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 938159e..4c7b888 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,17 +23,50 @@ export default new OAuthProvider({ authorizeEndpoint: "/authorize", tokenEndpoint: "/token", clientRegistrationEndpoint: "/register", - accessTokenTTL: 65, + accessTokenTTL: 120, tokenExchangeCallback: async (options) => { - if (options.grantType === "authorization_code") { - // Your custom logic here - console.log("User logged in with code grant:", { - options + if (options.grantType === "refresh_token") { + const { accessToken, instanceUrl } = options.props as Props; + if (!accessToken || !instanceUrl) { + throw new Error("Missing accessToken or instanceUrl in props"); + } + + // Call the ThoughtSpot token fetch API + const url = `${instanceUrl.replace(/\/$/, "")}/callosum/v1/v2/auth/token/fetch?validity_time_in_sec=150`; + const response = await fetch(url, { + method: "GET", + headers: { + "Authorization": `Bearer ${accessToken}`, + "Accept": "application/json", + "User-Agent": "ThoughtSpot-ts-client", + }, }); - - // Don't return anything → fallback to default behavior - return; + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to refresh token: ${response.status} ${errorText}`); + } + + const data = await response.json() as { data: { token: string } }; + const newToken = data.data.token; + if (!newToken) { + throw new Error("No token found in response"); + } + + // Return new props with the refreshed token + return { + accessTokenProps: { + ...options.props, + accessToken: newToken, + }, + newProps: { + ...options.props, + accessToken: newToken, + }, + accessTokenTTL: 86300 + }; } + // fallback to default behavior for other grant types return; }, }); diff --git a/src/oauth-manager/token-utils.ts b/src/oauth-manager/token-utils.ts index 35b4efe..c9b32b2 100644 --- a/src/oauth-manager/token-utils.ts +++ b/src/oauth-manager/token-utils.ts @@ -78,7 +78,7 @@ export function renderTokenCallback(instanceUrl: string, oauthReqInfo: string) { // Immediately invoke the async function (async function() { try { - const tokenUrl = new URL('callosum/v1/v2/auth/token/fetch?validity_time_in_sec=2592000', '${instanceUrl}'); + const tokenUrl = new URL('callosum/v1/v2/auth/token/fetch?validity_time_in_sec=150', '${instanceUrl}'); console.log('Fetching token from:', tokenUrl.toString()); document.getElementById('status').textContent = 'Retrieving authentication token...'; From ef324316be6192eb7c80e21e9533ec23e98afaf4 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Wed, 28 May 2025 12:58:03 -0700 Subject: [PATCH 7/8] added console logs --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 4c7b888..e8455dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,13 +26,14 @@ export default new OAuthProvider({ accessTokenTTL: 120, tokenExchangeCallback: async (options) => { if (options.grantType === "refresh_token") { + console.log("tokenExchangeCallback- refresh_token options", options); const { accessToken, instanceUrl } = options.props as Props; if (!accessToken || !instanceUrl) { throw new Error("Missing accessToken or instanceUrl in props"); } // Call the ThoughtSpot token fetch API - const url = `${instanceUrl.replace(/\/$/, "")}/callosum/v1/v2/auth/token/fetch?validity_time_in_sec=150`; + const url = `${instanceUrl.replace(/\/$/, "")}/callosum/v1/v2/auth/token/fetch?validity_time_in_sec=180`; const response = await fetch(url, { method: "GET", headers: { From 0255937ce86c813c2de1df251c4e8c8910e80045 Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Thu, 29 May 2025 14:42:58 -0700 Subject: [PATCH 8/8] Update time for access toke ttl to 20days and TS token to 30 days . --- src/index.ts | 84 ++++++++++++++------------- src/oauth-manager/token-utils.ts | 2 +- src/thoughtspot/thoughtspot-client.ts | 8 +++ 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/index.ts b/src/index.ts index e8455dd..5525ea6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,49 +23,51 @@ export default new OAuthProvider({ authorizeEndpoint: "/authorize", tokenEndpoint: "/token", clientRegistrationEndpoint: "/register", - accessTokenTTL: 120, + accessTokenTTL: 1728000, // 20 days, tokenExchangeCallback: async (options) => { if (options.grantType === "refresh_token") { - console.log("tokenExchangeCallback- refresh_token options", options); - const { accessToken, instanceUrl } = options.props as Props; - if (!accessToken || !instanceUrl) { - throw new Error("Missing accessToken or instanceUrl in props"); - } - - // Call the ThoughtSpot token fetch API - const url = `${instanceUrl.replace(/\/$/, "")}/callosum/v1/v2/auth/token/fetch?validity_time_in_sec=180`; - const response = await fetch(url, { - method: "GET", - headers: { - "Authorization": `Bearer ${accessToken}`, - "Accept": "application/json", - "User-Agent": "ThoughtSpot-ts-client", - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Failed to refresh token: ${response.status} ${errorText}`); - } - - const data = await response.json() as { data: { token: string } }; - const newToken = data.data.token; - if (!newToken) { - throw new Error("No token found in response"); - } - - // Return new props with the refreshed token - return { - accessTokenProps: { - ...options.props, - accessToken: newToken, - }, - newProps: { - ...options.props, - accessToken: newToken, - }, - accessTokenTTL: 86300 - }; + if (options.grantType === "refresh_token") { + const { accessToken, instanceUrl } = options.props; + // fetch a new TS token + + const url = `${instanceUrl}/callosum/v1/v2/auth/token/fetch?validity_time_in_sec=2592000`; // 30 days + + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, // old token (may still be valid) + Accept: "application/json", + "User-Agent": "ThoughtSpot-ts-client", + }, + }); + + if (!response.ok) { + + console.error("Failed to fetch new TS token:", await response.text()); + + // Don't issue new Cloudflare token — force user to reauth + throw new Error(JSON.stringify({ + error: "invalid_grant", + error_description: "TS access token expired. Please reauthenticate." + })); + } + + const data = await response.json(); + const newToken = data.data?.token; + + + return { + accessTokenProps: { + ...options.props, + accessToken: newToken, + }, + newProps: { + ...options.props, + accessToken: newToken, + }, + accessTokenTTL:1728000 // 20 days + }; + }; } // fallback to default behavior for other grant types return; diff --git a/src/oauth-manager/token-utils.ts b/src/oauth-manager/token-utils.ts index c9b32b2..35b4efe 100644 --- a/src/oauth-manager/token-utils.ts +++ b/src/oauth-manager/token-utils.ts @@ -78,7 +78,7 @@ export function renderTokenCallback(instanceUrl: string, oauthReqInfo: string) { // Immediately invoke the async function (async function() { try { - const tokenUrl = new URL('callosum/v1/v2/auth/token/fetch?validity_time_in_sec=150', '${instanceUrl}'); + const tokenUrl = new URL('callosum/v1/v2/auth/token/fetch?validity_time_in_sec=2592000', '${instanceUrl}'); console.log('Fetching token from:', tokenUrl.toString()); document.getElementById('status').textContent = 'Retrieving authentication token...'; diff --git a/src/thoughtspot/thoughtspot-client.ts b/src/thoughtspot/thoughtspot-client.ts index 8c5908a..dca5045 100644 --- a/src/thoughtspot/thoughtspot-client.ts +++ b/src/thoughtspot/thoughtspot-client.ts @@ -2,6 +2,9 @@ import { createBearerAuthenticationConfig, ThoughtSpotRestApi } from "@thoughtsp import YAML from "yaml"; export const getThoughtSpotClient = (instanceUrl: string, bearerToken: string) => { + if (!instanceUrl || !bearerToken) { + throw Error("instanceUrl and bearerToken are required to create a ThoughtSpot client"); + } const client = new ThoughtSpotRestApi(createBearerAuthenticationConfig( instanceUrl, () => Promise.resolve(bearerToken), @@ -82,6 +85,11 @@ async function addGetSessionInfo(client: any, instanceUrl: string, token: string "Authorization": `Bearer ${token}`, } }); + + if (response.status !== 200) { + console.log("response", response); + throw new Error(`Failed to get session info.`); + } const data: any = await response.json(); const info = data.info;