From 6824921cdcba56aaaec70ba6da88327f33cf5499 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 16 Nov 2023 12:30:15 +0530 Subject: [PATCH 1/7] feat: handle auth flow natively --- .../background-script/connectors/alby.ts | 77 ++++++++++++++----- src/manifest.json | 1 - 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index 38af1df487..d089b399e3 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -237,7 +237,9 @@ export default class Alby implements Connector { throw new Error("OAuth client credentials missing"); } - const redirectURL = browser.identity.getRedirectURL(); + // handling redirect url + const redirectURL = + "https://9dfeeffab3456fdb4be6e2296ca1a4c6b124ee94.extensions.allizom.org/"; const authClient = new auth.OAuth2User({ request_options: this._getRequestOptions(), client_id: clientId, @@ -259,9 +261,7 @@ export default class Alby implements Connector { authClient.on("tokenRefreshed", (token: Token) => { this._updateOAuthToken(token); }); - // Currently the JS SDK guarantees request of a new refresh token is done synchronously. - // The only way a refresh should fail is if the refresh token has expired, which is handled when the connector is initialized. - // If a token refresh fails after init then the connector will be unusable, but we will still log errors here so that this can be debugged if it does ever happen. + authClient.on("tokenRefreshFailed", (error: Error) => { console.error("Failed to Refresh token", error); }); @@ -273,8 +273,6 @@ export default class Alby implements Connector { } return authClient; } catch (error) { - // if auth token refresh fails, the refresh token has probably expired or is invalid - // the user will be asked to re-login console.error("Failed to request new auth token", error); } } @@ -284,31 +282,68 @@ export default class Alby implements Connector { authorizeUrl: process.env.ALBY_OAUTH_AUTHORIZE_URL, }); + console.log(authUrl); + authUrl += "&webln=false"; // stop getalby.com login modal launching lnurl auth - const authResult = await this.launchWebAuthFlow(authUrl); - const code = new URL(authResult).searchParams.get("code"); - if (!code) { - throw new Error("Authentication failed: missing authResult"); - } - const token = await authClient.requestAccessToken(code); - await this._updateOAuthToken(token.token); - return authClient; + // Use the tabs API to create a new tab with the authorization URL + const createTab = await browser.tabs.create({ url: authUrl }); + + return new Promise((resolve, reject) => { + // Attach an event listener to the created tab to capture the authorization code + const handleUpdated = ( + tabId: number, + changeInfo: browser.Tabs.OnUpdatedChangeInfoType, + tab: browser.Tabs.Tab + ) => { + console.log(changeInfo.status); + if (changeInfo.status === "complete" && tabId === createTab.id) { + // The tab has finished loading, you can check the tab URL for the authorization code + // Extract the code from the URL and proceed with token exchange + + // Note: You might need to handle errors and corner cases depending on the authorization flow. + const authorizationCode = this.extractCodeFromTabUrl(tab.url); + + console.log(changeInfo); + + console.log(authorizationCode); + + if (authorizationCode) { + authClient + .requestAccessToken(authorizationCode) + .then((token) => { + this._updateOAuthToken(token.token); + resolve(authClient); + }) + .catch((error) => { + console.error("Failed to request new auth token", error); + reject(error); + }) + .finally(() => { + // Remove the event listener once the code is received + browser.tabs.onUpdated.removeListener(handleUpdated); + }); + } + } + }; + + // Attach the event listener + browser.tabs.onUpdated.addListener(handleUpdated); + }); } catch (error) { console.error(error); throw error; } } - async launchWebAuthFlow(authUrl: string) { - const authResult = await browser.identity.launchWebAuthFlow({ - interactive: true, - url: authUrl, - }); + private extractCodeFromTabUrl(url: string | undefined): string | null { + if (!url) { + return null; + } - return authResult; + const urlSearchParams = new URLSearchParams(url.split("?")[1]); + return urlSearchParams.get("code") || null; } - private async _request(func: (client: Client) => T) { if (!this._authUser || !this._client) { throw new Error("Alby client was not initialized"); diff --git a/src/manifest.json b/src/manifest.json index 99874b412e..49d8765566 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -14,7 +14,6 @@ "description": "Your Bitcoin Lightning wallet and companion for accessing Bitcoin and Nostr apps, payments across the globe and passwordless logins.", "homepage_url": "https://getAlby.com/", "permissions": [ - "nativeMessaging", "notifications", "storage", "tabs", From 38c289a7790f1d29e5b18f10e34f9cab05d4709b Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 16 Nov 2023 13:30:51 +0530 Subject: [PATCH 2/7] feat: close redirect url tab once code is received --- src/extension/background-script/connectors/alby.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index d089b399e3..09a7c628a9 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -282,8 +282,6 @@ export default class Alby implements Connector { authorizeUrl: process.env.ALBY_OAUTH_AUTHORIZE_URL, }); - console.log(authUrl); - authUrl += "&webln=false"; // stop getalby.com login modal launching lnurl auth // Use the tabs API to create a new tab with the authorization URL @@ -296,18 +294,9 @@ export default class Alby implements Connector { changeInfo: browser.Tabs.OnUpdatedChangeInfoType, tab: browser.Tabs.Tab ) => { - console.log(changeInfo.status); if (changeInfo.status === "complete" && tabId === createTab.id) { - // The tab has finished loading, you can check the tab URL for the authorization code - // Extract the code from the URL and proceed with token exchange - - // Note: You might need to handle errors and corner cases depending on the authorization flow. const authorizationCode = this.extractCodeFromTabUrl(tab.url); - console.log(changeInfo); - - console.log(authorizationCode); - if (authorizationCode) { authClient .requestAccessToken(authorizationCode) @@ -320,6 +309,7 @@ export default class Alby implements Connector { reject(error); }) .finally(() => { + browser.tabs.remove(tabId); // Remove the event listener once the code is received browser.tabs.onUpdated.removeListener(handleUpdated); }); From 5be0f2af957500a75d0db47f560f3e213e004310 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 17 Nov 2023 12:40:41 +0530 Subject: [PATCH 3/7] feat: handle case where user stats auth flow and closes the tab --- .../background-script/connectors/alby.ts | 21 ++++++++++++------- src/manifest.json | 1 + 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index 09a7c628a9..69cf584313 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -237,9 +237,9 @@ export default class Alby implements Connector { throw new Error("OAuth client credentials missing"); } - // handling redirect url const redirectURL = "https://9dfeeffab3456fdb4be6e2296ca1a4c6b124ee94.extensions.allizom.org/"; + const authClient = new auth.OAuth2User({ request_options: this._getRequestOptions(), client_id: clientId, @@ -284,17 +284,15 @@ export default class Alby implements Connector { authUrl += "&webln=false"; // stop getalby.com login modal launching lnurl auth - // Use the tabs API to create a new tab with the authorization URL - const createTab = await browser.tabs.create({ url: authUrl }); + const oAuthTab = await browser.tabs.create({ url: authUrl }); return new Promise((resolve, reject) => { - // Attach an event listener to the created tab to capture the authorization code const handleUpdated = ( tabId: number, changeInfo: browser.Tabs.OnUpdatedChangeInfoType, tab: browser.Tabs.Tab ) => { - if (changeInfo.status === "complete" && tabId === createTab.id) { + if (changeInfo.status === "complete" && tabId === oAuthTab.id) { const authorizationCode = this.extractCodeFromTabUrl(tab.url); if (authorizationCode) { @@ -310,15 +308,22 @@ export default class Alby implements Connector { }) .finally(() => { browser.tabs.remove(tabId); - // Remove the event listener once the code is received browser.tabs.onUpdated.removeListener(handleUpdated); }); } } }; + const handleRemoved = (tabId: number) => { + if (tabId === oAuthTab.id) { + // The user closed the authentication tab without completing the flow + const error = new Error("OAuth authentication canceled by user"); + reject(error); + browser.tabs.onRemoved.removeListener(handleRemoved); + } + }; - // Attach the event listener browser.tabs.onUpdated.addListener(handleUpdated); + browser.tabs.onRemoved.addListener(handleRemoved); }); } catch (error) { console.error(error); @@ -330,10 +335,10 @@ export default class Alby implements Connector { if (!url) { return null; } - const urlSearchParams = new URLSearchParams(url.split("?")[1]); return urlSearchParams.get("code") || null; } + private async _request(func: (client: Client) => T) { if (!this._authUser || !this._client) { throw new Error("Alby client was not initialized"); diff --git a/src/manifest.json b/src/manifest.json index 49d8765566..99874b412e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -14,6 +14,7 @@ "description": "Your Bitcoin Lightning wallet and companion for accessing Bitcoin and Nostr apps, payments across the globe and passwordless logins.", "homepage_url": "https://getAlby.com/", "permissions": [ + "nativeMessaging", "notifications", "storage", "tabs", From eecbbfc9039851696517e893c7f87b9ba1f71c11 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 17 Nov 2023 15:52:56 +0530 Subject: [PATCH 4/7] chore: fix names --- .../background-script/connectors/alby.ts | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index 69cf584313..f81ef7d74c 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -261,7 +261,9 @@ export default class Alby implements Connector { authClient.on("tokenRefreshed", (token: Token) => { this._updateOAuthToken(token); }); - + // Currently the JS SDK guarantees request of a new refresh token is done synchronously. + // The only way a refresh should fail is if the refresh token has expired, which is handled when the connector is initialized. + // If a token refresh fails after init then the connector will be unusable, but we will still log errors here so that this can be debugged if it does ever happen. authClient.on("tokenRefreshFailed", (error: Error) => { console.error("Failed to Refresh token", error); }); @@ -287,7 +289,7 @@ export default class Alby implements Connector { const oAuthTab = await browser.tabs.create({ url: authUrl }); return new Promise((resolve, reject) => { - const handleUpdated = ( + const handleTabUpdated = ( tabId: number, changeInfo: browser.Tabs.OnUpdatedChangeInfoType, tab: browser.Tabs.Tab @@ -295,35 +297,37 @@ export default class Alby implements Connector { if (changeInfo.status === "complete" && tabId === oAuthTab.id) { const authorizationCode = this.extractCodeFromTabUrl(tab.url); - if (authorizationCode) { - authClient - .requestAccessToken(authorizationCode) - .then((token) => { - this._updateOAuthToken(token.token); - resolve(authClient); - }) - .catch((error) => { - console.error("Failed to request new auth token", error); - reject(error); - }) - .finally(() => { - browser.tabs.remove(tabId); - browser.tabs.onUpdated.removeListener(handleUpdated); - }); + if (!authorizationCode) { + throw new Error("no authorization code"); } + + authClient + .requestAccessToken(authorizationCode) + .then((token) => { + this._updateOAuthToken(token.token); + resolve(authClient); + }) + .catch((error) => { + console.error("Failed to request new auth token", error); + reject(error); + }) + .finally(() => { + browser.tabs.remove(tabId); + browser.tabs.onUpdated.removeListener(handleTabUpdated); + }); } }; - const handleRemoved = (tabId: number) => { + const handleTabRemoved = (tabId: number) => { if (tabId === oAuthTab.id) { // The user closed the authentication tab without completing the flow const error = new Error("OAuth authentication canceled by user"); reject(error); - browser.tabs.onRemoved.removeListener(handleRemoved); + browser.tabs.onRemoved.removeListener(handleTabRemoved); } }; - browser.tabs.onUpdated.addListener(handleUpdated); - browser.tabs.onRemoved.addListener(handleRemoved); + browser.tabs.onUpdated.addListener(handleTabUpdated); + browser.tabs.onRemoved.addListener(handleTabRemoved); }); } catch (error) { console.error(error); From 4eb6c69179b9441c6adffc16c9d205e4bf482ab8 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 17 Nov 2023 17:18:21 +0530 Subject: [PATCH 5/7] feat: fix redirect url --- .../background-script/connectors/alby.ts | 3 +- webpack.config.js | 40 +++++-------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index f81ef7d74c..31eecb9825 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -237,8 +237,7 @@ export default class Alby implements Connector { throw new Error("OAuth client credentials missing"); } - const redirectURL = - "https://9dfeeffab3456fdb4be6e2296ca1a4c6b124ee94.extensions.allizom.org/"; + const redirectURL = "https://getalby.com/extension/connect"; const authClient = new auth.OAuth2User({ request_options: this._getRequestOptions(), diff --git a/webpack.config.js b/webpack.config.js index f1e002c544..9f1a732e88 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,50 +53,28 @@ if (clientId && clientSecret) { const oauthCredentials = { development: { testnet: { - chrome: { - id: "CLAp8AfS3W", - secret: "KwIxF0VbGX2ZHLbbbYgE", - }, - firefox: { - id: "zWdxnF04Hd", - secret: "wY5uLJJDjNWrDlB6lAj8", - }, + id: "TliTCTtJ49", + secret: "35RixI2gv0xloVYA0Iq3", }, mainnet: { - chrome: { - id: "Zf7u3Zlyxl", - secret: "7wtcdVi61emqwzAH9Nm6", - }, - firefox: { - id: "uQkyHFBkaC", - secret: "0agh0cKkGWQSXTGRz9oy", - }, + id: "NT8bFB23Sk", + secret: "wWp7BPpYOm1H0GwSVVpY", }, }, production: { testnet: { - // only chrome is used for E2E tests - chrome: { - id: "mI5TEUKCwD", - secret: "47lxj2WNCJyVpxiy6vgq", - }, + id: "TliTCTtJ49", + secret: "35RixI2gv0xloVYA0Iq3", }, mainnet: { - chrome: { - id: "R7lZBSqfQt", - secret: "W4yWprd5ib6OSfq27InN", - }, - firefox: { - id: "V682entasX", - secret: "GhL3g37I3NAwzavCB3A5", - }, + id: "NT8bFB23Sk", + secret: "wWp7BPpYOm1H0GwSVVpY", }, }, }; // setup ALBY_OAUTH_CLIENT_ID - const selectedOAuthCredentials = - oauthCredentials[nodeEnv]?.[network]?.[oauthBrowser]; + const selectedOAuthCredentials = oauthCredentials[nodeEnv]?.[network]; if (!selectedOAuthCredentials) { throw new Error( `No OAuth credentials found for current configuration: NODE_ENV=${nodeEnv} network=${network} oauthBrowser=${oauthBrowser}` From af984cf67bfc3a0ad896b297b44fddf6c3d948e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Wed, 6 Dec 2023 14:22:52 +0100 Subject: [PATCH 6/7] fix: remove obsolete cond --- src/extension/background-script/connectors/alby.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index 31eecb9825..2e5a9aa916 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -339,7 +339,7 @@ export default class Alby implements Connector { return null; } const urlSearchParams = new URLSearchParams(url.split("?")[1]); - return urlSearchParams.get("code") || null; + return urlSearchParams.get("code"); } private async _request(func: (client: Client) => T) { From 97dd14d876095deecbaf2328505ed57241973493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Wed, 6 Dec 2023 16:42:01 +0100 Subject: [PATCH 7/7] fix: simplify webpack config --- webpack.config.js | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 9f1a732e88..0bf05a879d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -51,36 +51,24 @@ if (clientId && clientSecret) { ); } else { const oauthCredentials = { - development: { - testnet: { - id: "TliTCTtJ49", - secret: "35RixI2gv0xloVYA0Iq3", - }, - mainnet: { - id: "NT8bFB23Sk", - secret: "wWp7BPpYOm1H0GwSVVpY", - }, + testnet: { + id: "TliTCTtJ49", + secret: "35RixI2gv0xloVYA0Iq3", }, - production: { - testnet: { - id: "TliTCTtJ49", - secret: "35RixI2gv0xloVYA0Iq3", - }, - mainnet: { - id: "NT8bFB23Sk", - secret: "wWp7BPpYOm1H0GwSVVpY", - }, + mainnet: { + id: "NT8bFB23Sk", + secret: "wWp7BPpYOm1H0GwSVVpY", }, }; // setup ALBY_OAUTH_CLIENT_ID - const selectedOAuthCredentials = oauthCredentials[nodeEnv]?.[network]; + const selectedOAuthCredentials = oauthCredentials[network]; if (!selectedOAuthCredentials) { throw new Error( - `No OAuth credentials found for current configuration: NODE_ENV=${nodeEnv} network=${network} oauthBrowser=${oauthBrowser}` + `No OAuth credentials found for current configuration: network=${network}` ); } - console.info("Using OAuth credentials for", nodeEnv, oauthBrowser, network); + console.info("Using OAuth credentials for", network); process.env.ALBY_OAUTH_CLIENT_ID = selectedOAuthCredentials.id; process.env.ALBY_OAUTH_CLIENT_SECRET = selectedOAuthCredentials.secret; }