From 42ae7a3ea5e22714bfc476be800d40046759c94f Mon Sep 17 00:00:00 2001 From: Brian Graves Date: Wed, 15 Oct 2025 13:04:42 -0700 Subject: [PATCH 1/2] Refactor to better separate v1 vs v2 oauth flows (#696) Refactors google_drive oauth changes to better separate the two flows --- lib/actions/google/drive/google_drive.d.ts | 11 ++++ lib/actions/google/drive/google_drive.js | 41 ++++++++------ src/actions/google/drive/google_drive.ts | 64 +++++++++++++++------- 3 files changed, 77 insertions(+), 39 deletions(-) diff --git a/lib/actions/google/drive/google_drive.d.ts b/lib/actions/google/drive/google_drive.d.ts index 268013f7..5235faea 100644 --- a/lib/actions/google/drive/google_drive.d.ts +++ b/lib/actions/google/drive/google_drive.d.ts @@ -3,6 +3,10 @@ import { Credentials, OAuth2Client } from "google-auth-library"; import { drive_v3 } from "googleapis"; import * as Hub from "../../../hub"; import Drive = drive_v3.Drive; +interface OauthState { + tokenurl?: string; + stateurl?: string; +} export declare class GoogleDriveAction extends Hub.OAuthActionV2 { name: string; label: string; @@ -37,5 +41,12 @@ export declare class GoogleDriveAction extends Hub.OAuthActionV2 { protected driveClientFromRequest(redirect: string, tokens: Credentials): Promise; protected getUserEmail(redirect: string, tokens: Credentials): Promise; protected validateUserInDomainAllowlist(domainAllowlist: string | undefined, redirect: string, tokens: Credentials, requestWebhookId: string | undefined): Promise; + protected oauthFetchAndStoreInfo(urlParams: { + [key: string]: string; + }, redirectUri: string, statePayload: OauthState): Promise; + protected oauthCreateLookerRedirectUrl(urlParams: { + [key: string]: string; + }, redirectUri: string, actionCrypto: Hub.ActionCrypto, statePayload: OauthState): Promise; private loginForm; } +export {}; diff --git a/lib/actions/google/drive/google_drive.js b/lib/actions/google/drive/google_drive.js index 3c470d25..9234eba4 100644 --- a/lib/actions/google/drive/google_drive.js +++ b/lib/actions/google/drive/google_drive.js @@ -225,29 +225,15 @@ class GoogleDriveAction extends Hub.OAuthActionV2 { throw err; }); const statePayload = JSON.parse(plaintext); - if (statePayload.hasOwnProperty("tokenurl")) { + if (statePayload.tokenurl) { // redirect user back to Looker with context winston.info("Redirected with V2 flow"); - const newState = { - code: urlParams.code, - redirecturi: redirectUri, - }; - const jsonString = JSON.stringify(newState); - const ciphertextBlob = await actionCrypto.encrypt(jsonString).catch((err) => { - winston.error("Encryption not correctly configured"); - throw err; - }); - return `${statePayload.tokenurl}?state=${ciphertextBlob}`; + return this.oauthCreateLookerRedirectUrl(urlParams, redirectUri, actionCrypto, statePayload); } else { // Pass back context to Looker - winston.info("Posting1 to " + statePayload.stateurl); - const tokens = await this.getAccessTokenCredentialsFromCode(redirectUri, urlParams.code); - winston.info("Posting2 to " + statePayload.stateurl); - await https.post({ - url: statePayload.stateurl, - body: JSON.stringify({ tokens, redirect: redirectUri }), - }).catch((_err) => { winston.error(_err.toString()); }); + winston.info("Redirected with V1 flow"); + await this.oauthFetchAndStoreInfo(urlParams, redirectUri, statePayload); return ""; } } @@ -420,6 +406,25 @@ class GoogleDriveAction extends Hub.OAuthActionV2 { } } } + async oauthFetchAndStoreInfo(urlParams, redirectUri, statePayload) { + const tokens = await this.getAccessTokenCredentialsFromCode(redirectUri, urlParams.code); + await https.post({ + url: statePayload.stateurl, + body: JSON.stringify({ tokens, redirect: redirectUri }), + }).catch((_err) => { winston.error(_err.toString()); }); + } + async oauthCreateLookerRedirectUrl(urlParams, redirectUri, actionCrypto, statePayload) { + const newState = { + code: urlParams.code, + redirecturi: redirectUri, + }; + const jsonString = JSON.stringify(newState); + const ciphertextBlob = await actionCrypto.encrypt(jsonString).catch((err) => { + winston.error("Encryption not correctly configured"); + throw err; + }); + return `${statePayload.tokenurl}?state=${ciphertextBlob}`; + } async loginForm(request) { const form = new Hub.ActionForm(); form.fields = []; diff --git a/src/actions/google/drive/google_drive.ts b/src/actions/google/drive/google_drive.ts index f94f814f..995c09d2 100644 --- a/src/actions/google/drive/google_drive.ts +++ b/src/actions/google/drive/google_drive.ts @@ -16,6 +16,11 @@ import { DomainValidator } from "./domain_validator" const LOG_PREFIX = "[GOOGLE_DRIVE]" const FOLDERID_REGEX = /\/folders\/(?[^\/?]+)/ +interface OauthState { + tokenurl?: string + stateurl?: string +} + export class GoogleDriveAction extends Hub.OAuthActionV2 { name = "google_drive" label = "Google Drive" @@ -263,31 +268,16 @@ export class GoogleDriveAction extends Hub.OAuthActionV2 { winston.error("Encryption not correctly configured" + err) throw err }) + const statePayload: OauthState = JSON.parse(plaintext) - const statePayload = JSON.parse(plaintext) - if (statePayload.hasOwnProperty("tokenurl")) { + if (statePayload.tokenurl) { // redirect user back to Looker with context winston.info("Redirected with V2 flow") - const newState = { - code: urlParams.code, - redirecturi: redirectUri, - } - const jsonString = JSON.stringify(newState) - const ciphertextBlob = await actionCrypto.encrypt(jsonString).catch((err: string) => { - winston.error("Encryption not correctly configured") - throw err - }) - - return `${statePayload.tokenurl}?state=${ciphertextBlob}` + return this.oauthCreateLookerRedirectUrl(urlParams, redirectUri, actionCrypto, statePayload) } else { // Pass back context to Looker - winston.info("Posting1 to " + statePayload.stateurl) - const tokens = await this.getAccessTokenCredentialsFromCode(redirectUri, urlParams.code) - winston.info("Posting2 to " + statePayload.stateurl) - await https.post({ - url: statePayload.stateurl, - body: JSON.stringify({tokens, redirect: redirectUri}), - }).catch((_err) => { winston.error(_err.toString()) }) + winston.info("Redirected with V1 flow") + await this.oauthFetchAndStoreInfo(urlParams, redirectUri, statePayload) return "" } } @@ -480,13 +470,45 @@ export class GoogleDriveAction extends Hub.OAuthActionV2 { } + protected async oauthFetchAndStoreInfo( + urlParams: { [key: string]: string }, + redirectUri: string, + statePayload: OauthState, + ) { + const tokens = await this.getAccessTokenCredentialsFromCode(redirectUri, urlParams.code) + await https.post({ + url: statePayload.stateurl!, + body: JSON.stringify({tokens, redirect: redirectUri}), + }).catch((_err) => { winston.error(_err.toString()) }) + } + + protected async oauthCreateLookerRedirectUrl( + urlParams: { [key: string]: string }, + redirectUri: string, + actionCrypto: Hub.ActionCrypto, + statePayload: OauthState, + ) { + const newState = { + code: urlParams.code, + redirecturi: redirectUri, + } + const jsonString = JSON.stringify(newState) + const ciphertextBlob = await actionCrypto.encrypt(jsonString).catch((err: string) => { + winston.error("Encryption not correctly configured") + throw err + }) + + return `${statePayload.tokenurl!}?state=${ciphertextBlob}` + } + private async loginForm(request: Hub.ActionRequest) { const form = new Hub.ActionForm() form.fields = [] const hasTokenUrl = request.params.hasOwnProperty("state_redir_url") winston.info(`Using ${hasTokenUrl ? "V2" : "V1"} flow`) - const state = hasTokenUrl ? {tokenurl: request.params.state_redir_url} : {stateurl: request.params.state_url} + const state: OauthState = + hasTokenUrl ? {tokenurl: request.params.state_redir_url} : {stateurl: request.params.state_url} const jsonString = JSON.stringify(state) const actionCrypto = new Hub.ActionCrypto() From 64092a8e0c9d38ba215fc86ba3fb4b262dc02d5e Mon Sep 17 00:00:00 2001 From: Brian Graves Date: Wed, 15 Oct 2025 16:00:00 -0700 Subject: [PATCH 2/2] Version bump to 1.5.26 (#697) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9904fb2..88069552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "looker-action-hub", - "version": "1.5.20", + "version": "1.5.26", "description": "", "main": "lib/index", "scripts": {