Skip to content

Commit

Permalink
feat: handle auth flow natively (#2869)
Browse files Browse the repository at this point in the history
* feat: handle auth flow natively

* feat: close redirect url tab once code is received

* feat: handle case where user stats auth flow and closes the tab

* chore: fix names

* feat: fix redirect url

* fix: remove obsolete cond

* fix: simplify webpack config

---------

Co-authored-by: René Aaron <[email protected]>
  • Loading branch information
pavanjoshi914 and reneaaron authored Dec 6, 2023
1 parent 4f857df commit c393a3e
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 61 deletions.
69 changes: 51 additions & 18 deletions src/extension/background-script/connectors/alby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ export default class Alby implements Connector {
throw new Error("OAuth client credentials missing");
}

const redirectURL = browser.identity.getRedirectURL();
const redirectURL = "https://getalby.com/extension/connect";

const authClient = new auth.OAuth2User({
request_options: this._getRequestOptions(),
client_id: clientId,
Expand Down Expand Up @@ -286,8 +287,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);
}
}
Expand All @@ -298,28 +297,62 @@ export default class Alby implements Connector {
});

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;
const oAuthTab = await browser.tabs.create({ url: authUrl });

return new Promise<auth.OAuth2User>((resolve, reject) => {
const handleTabUpdated = (
tabId: number,
changeInfo: browser.Tabs.OnUpdatedChangeInfoType,
tab: browser.Tabs.Tab
) => {
if (changeInfo.status === "complete" && tabId === oAuthTab.id) {
const authorizationCode = this.extractCodeFromTabUrl(tab.url);

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 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(handleTabRemoved);
}
};

browser.tabs.onUpdated.addListener(handleTabUpdated);
browser.tabs.onRemoved.addListener(handleTabRemoved);
});
} catch (error) {
console.error(error);
throw error;
}
}

async launchWebAuthFlow(authUrl: string) {
const authResult = await browser.identity.launchWebAuthFlow({
interactive: true,
url: authUrl,
});

return authResult;
private extractCodeFromTabUrl(url: string | undefined): string | null {
if (!url) {
return null;
}
const urlSearchParams = new URLSearchParams(url.split("?")[1]);
return urlSearchParams.get("code");
}

private async _request<T>(func: (client: Client) => T) {
Expand Down
52 changes: 9 additions & 43 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,58 +51,24 @@ if (clientId && clientSecret) {
);
} else {
const oauthCredentials = {
development: {
testnet: {
chrome: {
id: "CLAp8AfS3W",
secret: "KwIxF0VbGX2ZHLbbbYgE",
},
firefox: {
id: "zWdxnF04Hd",
secret: "wY5uLJJDjNWrDlB6lAj8",
},
},
mainnet: {
chrome: {
id: "Zf7u3Zlyxl",
secret: "7wtcdVi61emqwzAH9Nm6",
},
firefox: {
id: "uQkyHFBkaC",
secret: "0agh0cKkGWQSXTGRz9oy",
},
},
testnet: {
id: "TliTCTtJ49",
secret: "35RixI2gv0xloVYA0Iq3",
},
production: {
testnet: {
// only chrome is used for E2E tests
chrome: {
id: "mI5TEUKCwD",
secret: "47lxj2WNCJyVpxiy6vgq",
},
},
mainnet: {
chrome: {
id: "R7lZBSqfQt",
secret: "W4yWprd5ib6OSfq27InN",
},
firefox: {
id: "V682entasX",
secret: "GhL3g37I3NAwzavCB3A5",
},
},
mainnet: {
id: "NT8bFB23Sk",
secret: "wWp7BPpYOm1H0GwSVVpY",
},
};

// setup ALBY_OAUTH_CLIENT_ID
const selectedOAuthCredentials =
oauthCredentials[nodeEnv]?.[network]?.[oauthBrowser];
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;
}
Expand Down

0 comments on commit c393a3e

Please sign in to comment.