From 07fbb799be87c1c5229ae3183fa8bc67ae15c756 Mon Sep 17 00:00:00 2001 From: iketw <121973632+iketw@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:50:24 -0800 Subject: [PATCH] [RN] Adds the ability to login with any arbitrary login payload (#2074) --- .changeset/perfect-cups-attack.md | 13 ++++ .../embedded-wallet/embedded-connector.ts | 37 ++++++++++- .../embedded-wallet/embedded/auth.ts | 65 ++++++++++++++++++- .../embedded/helpers/auth/middleware.ts | 5 +- .../embedded/helpers/constants.ts | 1 + .../connectors/embedded-wallet/types.ts | 14 +++- .../unity-js-bridge/src/thirdweb-bridge.ts | 9 ++- 7 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 .changeset/perfect-cups-attack.md diff --git a/.changeset/perfect-cups-attack.md b/.changeset/perfect-cups-attack.md new file mode 100644 index 00000000000..4e6346a0850 --- /dev/null +++ b/.changeset/perfect-cups-attack.md @@ -0,0 +1,13 @@ +--- +"@thirdweb-dev/react-native": patch +--- + +Adds the ability to login with any arbitrary login payload + +```typescript +await embeddedWallet.authenticate({ + strategy: "auth_endpoint", + payload: "SOME_STRING", + encryptionKey: "", +}); +``` diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded-connector.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded-connector.ts index 394dc4c8113..b01b3a0497a 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded-connector.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded-connector.ts @@ -1,4 +1,5 @@ import { + AuthEndpointOptions, AuthOptions, AuthParams, AuthResult, @@ -11,6 +12,7 @@ import type { Chain } from "@thirdweb-dev/chains"; import { providers, Signer } from "ethers"; import { utils } from "ethers"; import { + authEndpoint, customJwt, sendVerificationEmail, socialLogin, @@ -101,7 +103,13 @@ export class EmbeddedWalletConnector extends Connector { + try { + const { verifiedToken, email } = await authEndpoint( + authOptions, + this.options.clientId, + ); + this.email = email; + return { + user: { + status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED, + recoveryShareManagement: + verifiedToken.authDetails.recoveryShareManagement, + }, + isNewUser: verifiedToken.isNewUser, + needsRecoveryCode: + verifiedToken.authDetails.recoveryShareManagement === + RecoveryShareManagement.USER_MANAGED, + }; + } catch (error) { + console.error(`Error while verifying auth_endpoint auth: ${error}`); + this.disconnect(); + throw error; + } + } + async disconnect(): Promise { clearConnectedEmail(); clearConnectedAuthStrategy(); diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts index 11b229ccb0e..38aded57990 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts @@ -27,11 +27,17 @@ import { Auth } from "aws-amplify"; import { DOMAIN_URL_2023, EWS_VERSION_HEADER, + ROUTE_AUTH_ENDPOINT_CALLBACK, ROUTE_AUTH_JWT_CALLBACK, ROUTE_HEADLESS_OAUTH_LOGIN, THIRDWEB_SESSION_NONCE_HEADER, } from "./helpers/constants"; -import { AuthOptions, OauthOption, VerifiedTokenResponse } from "../types"; +import { + AuthEndpointOptions, + AuthOptions, + OauthOption, + VerifiedTokenResponse, +} from "../types"; import { InAppBrowser } from "react-native-inappbrowser-reborn"; import { createErrorMessage } from "./helpers/errors"; import { @@ -307,7 +313,62 @@ export async function customJwt(authOptions: AuthOptions, clientId: string) { return { verifiedToken, email: verifiedToken.authDetails.email }; } catch (e) { throw new Error( - createErrorMessage("Malformed response from post authentication", e), + createErrorMessage("Malformed response from post jwt authentication", e), + ); + } +} + +export async function authEndpoint( + authOptions: AuthEndpointOptions, + clientId: string, +) { + const { payload, encryptionKey } = authOptions; + + const resp = await fetch(ROUTE_AUTH_ENDPOINT_CALLBACK, { + method: "POST", + headers: { + "Content-Type": "application/json", + [EWS_VERSION_HEADER]: reactNativePackageVersion, + [BUNDLE_ID_HEADER]: appBundleId, + [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + }, + body: JSON.stringify({ + payload: payload, + developerClientId: clientId, + }), + }); + if (!resp.ok) { + const error = await resp.json(); + throw new Error( + `Custom auth endpoint authentication error: ${error.message}`, + ); + } + + try { + const { verifiedToken, verifiedTokenJwtString } = await resp.json(); + + const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { + jwtToken: verifiedToken.jwtToken, + authProvider: verifiedToken.authProvider, + authDetails: { + ...verifiedToken.authDetails, + email: verifiedToken.authDetails.email, + }, + developerClientId: verifiedToken.developerClientId, + cookieString: verifiedTokenJwtString, + shouldStoreCookieString: true, + isNewUser: verifiedToken.isNewUser, + }; + + await postPaperAuthUserManaged(toStoreToken, clientId, encryptionKey); + + return { verifiedToken, email: verifiedToken.authDetails.email }; + } catch (e) { + throw new Error( + createErrorMessage( + "Malformed response from post auth_endpoint authentication", + e, + ), ); } } diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/auth/middleware.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/auth/middleware.ts index 2d2d1f11103..eceee8c2ce0 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/auth/middleware.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/auth/middleware.ts @@ -127,7 +127,10 @@ async function getRecoveryCode( storedToken.authDetails.recoveryShareManagement === RecoveryShareManagement.CLOUD_MANAGED ) { - if (storedToken.authProvider === AuthProvider.CUSTOM_JWT) { + if ( + storedToken.authProvider === AuthProvider.CUSTOM_JWT || + storedToken.authProvider === AuthProvider.CUSTOM_AUTH_ENDPOINT + ) { if (!recoveryCode) { throw new Error( `GetRecoveryCode error: ${ErrorMessages.missingRecoveryCode}`, diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/constants.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/constants.ts index 19320d4d28d..a0840730b13 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/constants.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/constants.ts @@ -34,6 +34,7 @@ export const ROUTE_STORE_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedd export const ROUTE_GET_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-shares`; export const ROUTE_VERIFY_THIRDWEB_CLIENT_ID = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/verify-thirdweb-client-id`; export const ROUTE_AUTH_JWT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-jwt`; +export const ROUTE_AUTH_ENDPOINT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-auth-endpoint`; export const ROUTE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/send-user-managed-email-otp`; export const ROUTE_VALIDATE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-thirdweb-email-otp`; diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/types.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/types.ts index cee70817772..27f0ca012e1 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/types.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/types.ts @@ -44,6 +44,11 @@ export interface AuthOptions { password: string; } +export interface AuthEndpointOptions { + payload: string; + encryptionKey: string; +} + export type SendEmailOtpReturnType = { isNewUser: boolean; isNewDevice: boolean; @@ -73,11 +78,18 @@ type JwtAuthParams = { encryptionKey: string; }; +type AuthEndpointParams = { + strategy: "auth_endpoint"; + payload: string; + encryptionKey: string; +}; + // this is the input to 'authenticate' export type AuthParams = | EmailVerificationAuthParams | SocialAuthParams - | JwtAuthParams; + | JwtAuthParams + | AuthEndpointParams; // TODO typed based off AuthParams["strategy"] export type AuthResult = { diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index a001b118c84..ba3dc87edbc 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -349,8 +349,7 @@ class ThirdwebBridge implements TWBridge { chainId: chainIdNumber, authResult, }); - } - else { + } else { throw new Error( "Invalid auth provider: " + authOptionsParsed.authProvider, ); @@ -680,11 +679,11 @@ class ThirdwebBridge implements TWBridge { strategy: "encryptedJson", password, }); - } catch(e) { + } catch (e) { console.warn(e); return localWallet; } - + return localWallet; } @@ -787,7 +786,7 @@ class ThirdwebBridge implements TWBridge { return JSON.stringify({ result: res }, bigNumberReplacer); } - public async getEmail(){ + public async getEmail() { const embeddedWallet = this.walletMap.get( walletIds.embeddedWallet, ) as EmbeddedWallet;