Skip to content

Commit

Permalink
feat(thirdweb): adds redirect oauth (#3822)
Browse files Browse the repository at this point in the history
### TL;DR

Added support for OAuth redirects in the In-App wallet authentication flow.

### What changed?

1. Introduced `auth.mode` with options `

---

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces the ability to open OAuth windows as a redirect, beneficial for embedded applications like Telegram web apps.

### Detailed summary
- Added `mode: "redirect"` option for OAuth windows
- Implemented functions for OAuth redirection
- Updated authentication options for in-app wallets

> The following files were skipped due to too many changes: `packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts`, `packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
gregfromstl committed Jul 25, 2024
1 parent bff7a0a commit 3848327
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 17 deletions.
16 changes: 16 additions & 0 deletions .changeset/tall-donuts-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"thirdweb": minor
---

Adds the ability to open OAuth windows as a redirect. This is useful for embedded applications such as telegram web apps.

Be sure to include your domain in the allowlisted domains for your client ID.

```ts
import { inAppWallet } from "thirdweb/wallets";
const wallet = inAppWallet({
auth: {
mode: "redirect"
}
});
```
1 change: 1 addition & 0 deletions apps/playground-web/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const WALLETS = [
"passkey",
"phone",
],
mode: "redirect",
},
}),
createWallet("io.metamask"),
Expand Down
20 changes: 17 additions & 3 deletions packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useQuery } from "@tanstack/react-query";
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
import { getUrlToken } from "../../../../wallets/in-app/web/lib/get-url-token.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import {
type ConnectionManager,
getLastConnectedChain,
getStoredActiveWalletId,
getStoredConnectedWalletIds,
} from "../../../../wallets/manager/index.js";
import { setLastAuthProvider } from "../../utils/storage.js";
import { timeoutPromise } from "../../utils/timeoutPromise.js";
import type { AutoConnectProps } from "../connection/types.js";
import { useConnectCore } from "./useConnect.js";
Expand All @@ -32,12 +34,23 @@ export function useAutoConnectCore(
const autoConnect = async (): Promise<boolean> => {
let autoConnected = false;
isAutoConnecting.setValue(true);
const [lastConnectedWalletIds, lastActiveWalletId] = await Promise.all([
let [lastConnectedWalletIds, lastActiveWalletId] = await Promise.all([
getStoredConnectedWalletIds(storage),
getStoredActiveWalletId(storage),
]);

// if no wallets were last connected
const { authResult, walletId, authProvider } = getUrlToken();
if (authResult && walletId) {
lastActiveWalletId = walletId;
lastConnectedWalletIds = lastConnectedWalletIds?.includes(walletId)
? lastConnectedWalletIds
: [walletId, ...(lastConnectedWalletIds || [])];
}

Check warning on line 48 in packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts#L44-L48

Added lines #L44 - L48 were not covered by tests
if (authProvider) {
await setLastAuthProvider(authProvider, storage);
}

Check warning on line 51 in packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts#L50-L51

Added lines #L50 - L51 were not covered by tests

// if no wallets were last connected or we didn't receive an auth token
if (!lastConnectedWalletIds) {
return autoConnected;
}
Expand All @@ -48,6 +61,7 @@ export function useAutoConnectCore(
return wallet.autoConnect({
client: props.client,
chain: lastConnectedChain ?? undefined,
authResult,

Check warning on line 64 in packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts#L64

Added line #L64 was not covered by tests
});
}

Expand All @@ -61,7 +75,7 @@ export function useAutoConnectCore(
setConnectionStatus("connecting"); // only set connecting status if we are connecting the last active EOA
await timeoutPromise(handleWalletConnection(activeWallet), {
ms: timeout,
message: `AutoConnect timeout : ${timeout}ms limit exceeded.`,
message: `AutoConnect timeout: ${timeout}ms limit exceeded.`,

Check warning on line 78 in packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts#L78

Added line #L78 was not covered by tests
});

// connected wallet could be activeWallet or smart wallet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ThirdwebClient } from "../../../../client/client.js";
import { webLocalStorage } from "../../../../utils/storage/webStorage.js";
import { getEcosystemWalletAuthOptions } from "../../../../wallets/ecosystem/get-ecosystem-wallet-auth-options.js";
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
import { loginWithOauthRedirect } from "../../../../wallets/in-app/web/lib/auth/oauth.js";
import type { Account, Wallet } from "../../../../wallets/interfaces/wallet.js";
import {
type AuthOption,
Expand Down Expand Up @@ -37,7 +38,7 @@ import { InputSelectionUI } from "../in-app/InputSelectionUI.js";
import { validateEmail } from "../in-app/validateEmail.js";
import { LoadingScreen } from "./LoadingScreen.js";
import type { InAppWalletLocale } from "./locale/types.js";
import { openOauthSignInWindow } from "./openOauthSignInWindow.js";
import { openOauthSignInWindow } from "./oauthSignIn.js";

export type ConnectWalletSelectUIState =
| undefined
Expand Down Expand Up @@ -164,6 +165,19 @@ export const ConnectWalletSocialOptions = (

// Need to trigger login on button click to avoid popup from being blocked
const handleSocialLogin = async (strategy: SocialAuthOption) => {
const walletConfig = wallet.getConfig();
if (
walletConfig &&
"auth" in walletConfig &&
walletConfig?.auth?.mode === "redirect"
) {
return loginWithOauthRedirect({
authOption: strategy,
client: props.client,
ecosystem: ecosystemInfo,
});
}

Check warning on line 180 in packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx#L168-L180

Added lines #L168 - L180 were not covered by tests
try {
const socialLoginWindow = openOauthSignInWindow({
authOption: strategy,
Expand Down Expand Up @@ -205,10 +219,10 @@ export const ConnectWalletSocialOptions = (

props.select(); // show Connect UI

// Note: do not call done() here, it will be called InAppWalletSocialLogin component
// we simply trigger the connect and save promise here - its resolution is handled in InAppWalletSocialLogin
// Note: do not call done() here, it will be called SocialLogin component
// we simply trigger the connect and save promise here - its resolution is handled in SocialLogin

Check warning on line 223 in packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx#L222-L223

Added lines #L222 - L223 were not covered by tests
} catch (e) {
console.error(`Error sign in with ${strategy}`, e);
console.error(`Error signing in with ${strategy}`, e);

Check warning on line 225 in packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx#L225

Added line #L225 was not covered by tests
}
};

Expand Down
21 changes: 20 additions & 1 deletion packages/thirdweb/src/react/web/wallets/shared/SocialLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ThirdwebClient } from "../../../../client/client.js";
import { webLocalStorage } from "../../../../utils/storage/webStorage.js";
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
import type { InAppWalletSocialAuth } from "../../../../wallets/in-app/core/wallet/types.js";
import { loginWithOauthRedirect } from "../../../../wallets/in-app/web/lib/auth/oauth.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
import { setLastAuthProvider } from "../../../core/utils/storage.js";
Expand All @@ -15,7 +16,7 @@ import { Button } from "../../ui/components/buttons.js";
import { Text } from "../../ui/components/text.js";
import type { ConnectWalletSelectUIState } from "./ConnectWalletSocialOptions.js";
import type { InAppWalletLocale } from "./locale/types.js";
import { openOauthSignInWindow } from "./openOauthSignInWindow.js";
import { openOauthSignInWindow } from "./oauthSignIn.js";

/**
* @internal
Expand All @@ -42,6 +43,24 @@ export function SocialLogin(props: {
);

const handleSocialLogin = async () => {
const walletConfig = wallet.getConfig();
if (
walletConfig &&
"auth" in walletConfig &&
walletConfig?.auth?.mode === "redirect"
) {
return loginWithOauthRedirect({
authOption: props.socialAuth,
client: props.client,
ecosystem: isEcosystemWallet(wallet)
? {
id: wallet.id,
partnerId: wallet.getConfig()?.partnerId,
}
: undefined,
});
}

Check warning on line 63 in packages/thirdweb/src/react/web/wallets/shared/SocialLogin.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/wallets/shared/SocialLogin.tsx#L46-L63

Added lines #L46 - L63 were not covered by tests
try {
const socialWindow = openOauthSignInWindow({
authOption: props.socialAuth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ function getOauthLoginPath(
}

/**
*
* @internal
*/
export function openOauthSignInWindow({
Expand Down Expand Up @@ -137,7 +136,7 @@ const spinnerWindowHtml = `
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
}
</style>
`;
3 changes: 3 additions & 0 deletions packages/thirdweb/src/wallets/ecosystem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import type { Ecosystem } from "../in-app/web/types.js";

export type EcosystemWalletCreationOptions = {
partnerId?: string;
auth?: {
mode?: "popup" | "redirect";
};
};

export type EcosystemWalletConnectionOptions = InAppWalletConnectionOptions & {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { SocialAuthOption } from "../../../../wallets/types.js";
import type { Account } from "../../../interfaces/wallet.js";
import type {
AuthArgsType,
AuthLoginReturnType,
AuthStoredTokenWithCookieReturnType,
GetUser,
LogoutReturnType,
PreAuthArgsType,
Expand All @@ -12,6 +14,10 @@ export interface InAppConnector {
getUser(): Promise<GetUser>;
getAccount(): Promise<Account>;
preAuthenticate(args: PreAuthArgsType): Promise<SendEmailOtpReturnType>;
authenticateWithRedirect?(strategy: SocialAuthOption): void;
loginWithAuthToken?(
authResult: AuthStoredTokenWithCookieReturnType,
): Promise<AuthLoginReturnType>;
authenticate(args: AuthArgsType): Promise<AuthLoginReturnType>;
logout(): Promise<LogoutReturnType>;
}
22 changes: 22 additions & 0 deletions packages/thirdweb/src/wallets/in-app/core/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import {
type SocialAuthOption,
socialAuthOptions,
} from "../../../../wallets/types.js";
import type { Account, Wallet } from "../../../interfaces/wallet.js";
import type { EcosystemWalletId, WalletId } from "../../../wallet-types.js";
import type {
Expand Down Expand Up @@ -35,6 +39,20 @@ export async function connectInAppWallet(
| CreateWalletArgs<EcosystemWalletId>[1],
connector: InAppConnector,
): Promise<[Account, Chain]> {
if (
createOptions?.auth?.mode === "redirect" &&
connector.authenticateWithRedirect
) {
const strategy = options.strategy;
if (!socialAuthOptions.includes(strategy as SocialAuthOption)) {
throw new Error("This authentication method does not support redirects");
}
connector.authenticateWithRedirect(strategy as SocialAuthOption);
}
// If we don't have authenticateWithRedirect then it's likely react native, so the default is to redirect and we can carry on
// IF WE EVER ADD MORE CONNECTOR TYPES, this could cause redirect to be ignored despite being specified
// TODO: In V6, make everything redirect auth

Check warning on line 55 in packages/thirdweb/src/wallets/in-app/core/wallet/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/core/wallet/index.ts#L42-L55

Added lines #L42 - L55 were not covered by tests
const authResult = await connector.authenticate(options);
const authAccount = authResult.user.account;

Expand Down Expand Up @@ -66,6 +84,10 @@ export async function autoConnectInAppWallet(
| CreateWalletArgs<EcosystemWalletId>[1],
connector: InAppConnector,
): Promise<[Account, Chain]> {
if (options.authResult && connector.loginWithAuthToken) {
await connector.loginWithAuthToken(options.authResult);
}

Check warning on line 90 in packages/thirdweb/src/wallets/in-app/core/wallet/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/core/wallet/index.ts#L87-L90

Added lines #L87 - L90 were not covered by tests
const user = await getAuthenticatedUser(connector);
if (!user) {
throw new Error("Failed to authenticate user.");
Expand Down
4 changes: 4 additions & 0 deletions packages/thirdweb/src/wallets/in-app/core/wallet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ThirdwebClient } from "../../../../client/client.js";
import type { SmartWalletOptions } from "../../../smart/types.js";
import type { AuthOption, SocialAuthOption } from "../../../types.js";
import type {
AuthStoredTokenWithCookieReturnType,
MultiStepAuthArgsType,
SingleStepAuthArgsType,
} from "../authentication/type.js";
Expand All @@ -13,10 +14,12 @@ export type InAppWalletConnectionOptions = (
) & {
client: ThirdwebClient;
chain?: Chain;
redirect?: boolean;
};

export type InAppWalletAutoConnectOptions = {
client: ThirdwebClient;
authResult?: AuthStoredTokenWithCookieReturnType;
chain?: Chain;
};

Expand All @@ -27,6 +30,7 @@ export type InAppWalletCreationOptions =
| {
auth?: {
options: InAppWalletAuth[];
mode?: "popup" | "redirect";
};
metadata?: {
image?: {
Expand Down
10 changes: 10 additions & 0 deletions packages/thirdweb/src/wallets/in-app/web/in-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ import { createInAppWallet } from "../core/wallet/in-app-core.js";
* hidePrivateKeyExport: true
* });
* ```
*
* Open the Oauth window in the same tab
* ```ts
* import { inAppWallet } from "thirdweb/wallets";
* const wallet = inAppWallet({
* auth: {
* mode: "redirect"
* }
* });
* ```
* @wallet
*/
export function inAppWallet(
Expand Down
17 changes: 14 additions & 3 deletions packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ThirdwebClient } from "../../../../../client/client.js";
import type { OneOf } from "../../../../../utils/type-utils.js";
import type { SocialAuthOption } from "../../../../../wallets/types.js";
import {
type AuthArgsType,
type AuthLoginReturnType,
type GetAuthenticatedUserParams,
type PreAuthArgsType,
UserWalletStatus,
Expand Down Expand Up @@ -143,8 +144,18 @@ export async function preAuthenticate(args: PreAuthArgsType) {
* @wallet
*/
export async function authenticate(
args: AuthArgsType,
): Promise<AuthLoginReturnType> {
args: OneOf<
| AuthArgsType
| {
strategy: SocialAuthOption;
client: ThirdwebClient;
ecosystem?: Ecosystem;
redirect: boolean;
}
>,
) {

Check warning on line 156 in packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts#L147-L156

Added lines #L147 - L156 were not covered by tests
const connector = await getInAppWalletConnector(args.client, args.ecosystem);
if (args.redirect && connector.authenticateWithRedirect)
return connector.authenticateWithRedirect(args.strategy);

Check warning on line 159 in packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/lib/auth/index.ts#L158-L159

Added lines #L158 - L159 were not covered by tests
return connector.authenticate(args);
}
12 changes: 12 additions & 0 deletions packages/thirdweb/src/wallets/in-app/web/lib/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ export const getSocialAuthLoginPath = (
return baseUrl;
};

export const loginWithOauthRedirect = (options: {
authOption: SocialAuthOption;
client: ThirdwebClient;
ecosystem?: Ecosystem;
}): void => {
const redirectUrl = new URL(window.location.href);
redirectUrl.searchParams.set("walletId", options.ecosystem?.id || "inApp");
redirectUrl.searchParams.set("authProvider", options.authOption);
const loginUrl = `${getSocialAuthLoginPath(options.authOption, options.client, options.ecosystem)}&redirectUrl=${encodeURIComponent(redirectUrl.toString())}`;
window.location.href = loginUrl;
};

Check warning on line 53 in packages/thirdweb/src/wallets/in-app/web/lib/auth/oauth.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/lib/auth/oauth.ts#L44-L53

Added lines #L44 - L53 were not covered by tests

export const loginWithOauth = async (options: {
authOption: SocialAuthOption;
client: ThirdwebClient;
Expand Down
36 changes: 36 additions & 0 deletions packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { AuthOption } from "../../../../wallets/types.js";
import type { WalletId } from "../../../wallet-types.js";
import type { AuthStoredTokenWithCookieReturnType } from "../../core/authentication/type.js";

/**
* Checks for an auth token and associated metadata in the current URL
*/
export function getUrlToken(): {
walletId?: WalletId;
authResult?: AuthStoredTokenWithCookieReturnType;
authProvider?: AuthOption;
} {
if (!window) {
throw new Error("Attempted to fetch a URL token on the server");
}

Check warning on line 15 in packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts#L14-L15

Added lines #L14 - L15 were not covered by tests

const queryString = window.location.search;
const params = new URLSearchParams(queryString);
const authResultString = params.get("authResult");
const walletId = params.get("walletId") as WalletId | undefined;
const authProvider = params.get("authProvider") as AuthOption | undefined;

if (authResultString && walletId) {
const authResult = JSON.parse(authResultString);
params.delete("authResult");
params.delete("walletId");
params.delete("authProvider");
window.history.pushState(
{},
"",
`${window.location.pathname}?${params.toString()}`,
);
return { walletId, authResult, authProvider };
}

Check warning on line 34 in packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/lib/get-url-token.ts#L24-L34

Added lines #L24 - L34 were not covered by tests
return {};
}
Loading

0 comments on commit 3848327

Please sign in to comment.