From ebc1eabdad6d97fcdf30606e4e4675b254e4bb5d Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Thu, 8 May 2025 17:33:17 -0700 Subject: [PATCH 1/3] HAndle url sanitization --- src/oauth-manager/oauth-utils.ts | 44 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/oauth-manager/oauth-utils.ts b/src/oauth-manager/oauth-utils.ts index b1200a5..3a46054 100644 --- a/src/oauth-manager/oauth-utils.ts +++ b/src/oauth-manager/oauth-utils.ts @@ -451,7 +451,7 @@ export function renderApprovalDialog(request: Request, options: ApprovalDialogOp
-
@@ -498,6 +498,41 @@ export interface ParsedApprovalResult { } +/** + * Validates and sanitizes a URL to ensure it's a valid ThoughtSpot instance URL + * @param url - The URL to validate and sanitize + * @returns The sanitized URL + * @throws Error if the URL is invalid + */ +function validateAndSanitizeUrl(url: string): string { + try { + // Remove any whitespace + const trimmedUrl = url.trim(); + + // Add https:// if no protocol is specified + const urlWithProtocol = trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://') + ? trimmedUrl + : `https://${trimmedUrl}`; + + const parsedUrl = new URL(urlWithProtocol); + + // Ensure it's using HTTPS + if (parsedUrl.protocol !== 'https:') { + throw new Error('URL must use HTTPS'); + } + + // Remove trailing slashes and normalize the URL + const sanitizedUrl = parsedUrl.origin; + + return sanitizedUrl; + } catch (e) { + if (e instanceof Error) { + throw new Error(`Invalid URL: ${e.message}`); + } + throw new Error('Invalid URL format'); + } +} + /** * Parses the form submission from the approval dialog, extracts the state, * and generates Set-Cookie headers to mark the client as approved. @@ -517,7 +552,7 @@ export async function parseRedirectApproval(request: Request): Promise Date: Fri, 9 May 2025 11:27:25 -0700 Subject: [PATCH 2/3] Remove https only restriction --- src/oauth-manager/oauth-utils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/oauth-manager/oauth-utils.ts b/src/oauth-manager/oauth-utils.ts index 3a46054..b4af248 100644 --- a/src/oauth-manager/oauth-utils.ts +++ b/src/oauth-manager/oauth-utils.ts @@ -516,11 +516,6 @@ function validateAndSanitizeUrl(url: string): string { const parsedUrl = new URL(urlWithProtocol); - // Ensure it's using HTTPS - if (parsedUrl.protocol !== 'https:') { - throw new Error('URL must use HTTPS'); - } - // Remove trailing slashes and normalize the URL const sanitizedUrl = parsedUrl.origin; From 3c057f9ec4a8bff6edddc6aa3d0298bc4800a60c Mon Sep 17 00:00:00 2001 From: Shikhar Bhargava Date: Sun, 11 May 2025 16:10:55 -0700 Subject: [PATCH 3/3] Add code for calling /v1/v2/auth/token/authorize once implemented --- src/handlers.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/handlers.ts b/src/handlers.ts index 1e7fc06..63a6d6e 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -53,6 +53,18 @@ app.post("/authorize", async (c) => { // redirect URL as query params = new URL("/callback", c.req.url).href to // send the user back to callback endpoint. // The callback endpoint will get the encrypted token and decrypt it to get the user's access token. + + // const targetURLAuthorize = new URL("callosum/v1/v2/auth/token/authorize", instanceUrl); + // targetURLAuthorize.searchParams.append('validity_time_in_sec', "86400"); + // const targetURLCallbackPath = new URL("/callback", c.req.url); + // targetURLCallbackPath.searchParams.append('instanceUrl', instanceUrl); + // targetURLAuthorize.searchParams.append('redirect_url', btoa(targetURLCallbackPath.toString())); + // const encodedState = btoa(JSON.stringify(state.oauthReqInfo)); + // targetURLAuthorize.searchParams.append('state', encodedState); + // targetURLAuthorize.searchParams.append('token_encryption_key', "1234567812345678"); + // targetURLAuthorize.searchParams.append('encryption_algorithm', 'AES'); + // redirectUrl.searchParams.append('targetURLPath', targetURLAuthorize.href); + const targetURLPath = new URL("/callback", c.req.url); targetURLPath.searchParams.append('instanceUrl', instanceUrl); const encodedState = btoa(JSON.stringify(state.oauthReqInfo)); @@ -64,6 +76,13 @@ app.post("/authorize", async (c) => { }) app.get("/callback", async (c) => { + + // TODO(shikhar.bhargava): remove this once we have a proper callback URL + // With the proper callback URL, we will get the encrypted token in the query params + // along with it we will get the instanceUrl and the state (oauthReqInfo). + // and we will decrypt the token to get the user's access token and complete the authorization. + // const encodedOauthReqInfo = c.req.query('state'); + const instanceUrl = c.req.query('instanceUrl'); const encodedOauthReqInfo = c.req.query('oauthReqInfo'); if (!instanceUrl) {