diff --git a/examples/vue-app/src/App.vue b/examples/vue-app/src/App.vue index 80c8e27..b4418c7 100644 --- a/examples/vue-app/src/App.vue +++ b/examples/vue-app/src/App.vue @@ -213,11 +213,11 @@ import { sapphireDevnetVerifierOptions, testnetVerifierMap, testnetVerifierOptions, - TORUS_EMAIL_PASSWORDLESS, - TORUS_SMS_PASSWORDLESS, TWITTER, uxModeOptions, WEB3AUTH_CLIENT_ID, + WEB3AUTH_EMAIL_PASSWORDLESS, + WEB3AUTH_SMS_PASSWORDLESS, WEIBO, } from "./config"; import { fetchLatestBlock, signEthMessage, signTypedData_v1 } from "./services/chainHandlers"; @@ -251,9 +251,9 @@ const isDisplay = (name: string): boolean => { case "appHeading": return !!privKey.value; case "loginHintEmail": - return formData.value.loginProvider === TORUS_EMAIL_PASSWORDLESS; + return formData.value.loginProvider === WEB3AUTH_EMAIL_PASSWORDLESS; case "loginHintPhone": - return formData.value.loginProvider === TORUS_SMS_PASSWORDLESS; + return formData.value.loginProvider === WEB3AUTH_SMS_PASSWORDLESS; default: { return true; @@ -288,18 +288,11 @@ const loginToConnectionMap = computed((): Record ({ name: x, value: x })); export const WEB3AUTH_CLIENT_ID = "BJ6l3_kIQiy6YVL7zDlCcEAvGpGukwFgp-C_0WvNI_fAEeIaoVRLDrV5OjtbZr_zJxbyXFsXMT-yhQiUNYvZWpo"; @@ -72,15 +72,15 @@ export const testnetVerifierMap = { clientId: "78i338ev9lkgjst3mfeuih9tsh", verifier: "demo-cognito-example", }, - [TORUS_EMAIL_PASSWORDLESS]: { - name: "Torus Email Passwordless", - typeOfLogin: "jwt", + [WEB3AUTH_EMAIL_PASSWORDLESS]: { + name: "Web3Auth Email Passwordless", + typeOfLogin: "email_passwordless", clientId: "P7PJuBCXIHP41lcyty0NEb7Lgf7Zme8Q", verifier: "torus-auth0-email-passwordless-lrc", }, - [TORUS_SMS_PASSWORDLESS]: { - name: "Torus Sms Passwordless", - typeOfLogin: "jwt", + [WEB3AUTH_SMS_PASSWORDLESS]: { + name: "Web3Auth Sms Passwordless", + typeOfLogin: "sms_passwordless", clientId: "P7PJuBCXIHP41lcyty0NEb7Lgf7Zme8Q", verifier: "torus-sms-passwordless-lrc", }, @@ -130,15 +130,15 @@ export const sapphireDevnetVerifierMap = { clientId: "4jK24VpfepWRSe5EMdd2if0RBD55pAuA", verifier: "web3auth-auth0-sms-passwordless-sapphire-devnet", }, - [TORUS_EMAIL_PASSWORDLESS]: { - name: "Torus Email Passwordless", - typeOfLogin: "jwt", + [WEB3AUTH_EMAIL_PASSWORDLESS]: { + name: "Web3Auth Email Passwordless", + typeOfLogin: "email_passwordless", clientId: "d84f6xvbdV75VTGmHiMWfZLeSPk8M07C", verifier: "web3auth-auth0-email-passwordless-sapphire-devnet", }, - [TORUS_SMS_PASSWORDLESS]: { - name: "Torus Sms Passwordless", - typeOfLogin: "jwt", + [WEB3AUTH_SMS_PASSWORDLESS]: { + name: "Web3Auth Sms Passwordless", + typeOfLogin: "sms_passwordless", clientId: "4jK24VpfepWRSe5EMdd2if0RBD55pAuA", verifier: "web3auth-auth0-sms-passwordless-sapphire-devnet", }, diff --git a/src/handlers/HandlerFactory.ts b/src/handlers/HandlerFactory.ts index 5dde3aa..09627f2 100644 --- a/src/handlers/HandlerFactory.ts +++ b/src/handlers/HandlerFactory.ts @@ -8,6 +8,7 @@ import MockLoginHandler from "./MockLoginHandler"; import PasskeysHandler from "./PasskeysHandler"; import PasswordlessHandler from "./PasswordlessHandler"; import TwitchHandler from "./TwitchHandler"; +import Web3AuthPasswordlessHandler from "./Web3AuthPasswordlessHandler"; const createHandler = (params: CreateHandlerParams): ILoginHandler => { const { verifier, typeOfLogin, clientId, jwtParams } = params; @@ -24,6 +25,10 @@ const createHandler = (params: CreateHandlerParams): ILoginHandler => { return new TwitchHandler(params); case LOGIN.DISCORD: return new DiscordHandler(params); + case LOGIN.EMAIL_PASSWORDLESS: + case LOGIN.SMS_PASSWORDLESS: + if (!login_hint) throw new Error("Invalid params. Missing login_hint for web3auth passwordless login"); + return new Web3AuthPasswordlessHandler(params); case LOGIN.PASSWORDLESS: if (!domain || !login_hint) throw new Error("Invalid params. Missing domain or login_hint for passwordless login"); return new PasswordlessHandler(params); diff --git a/src/handlers/PasswordlessHandler.ts b/src/handlers/PasswordlessHandler.ts index bf3b888..a2b3ba1 100644 --- a/src/handlers/PasswordlessHandler.ts +++ b/src/handlers/PasswordlessHandler.ts @@ -6,7 +6,7 @@ import log from "../utils/loglevel"; import AbstractLoginHandler from "./AbstractLoginHandler"; import { Auth0UserInfo, CreateHandlerParams, LoginWindowResponse, PopupResponse, TorusVerifierResponse } from "./interfaces"; -export default class JwtHandler extends AbstractLoginHandler { +export default class PasswordlessHandler extends AbstractLoginHandler { private readonly SCOPE: string = "openid profile email"; private readonly RESPONSE_TYPE: string = "token id_token"; diff --git a/src/handlers/Web3AuthPasswordlessHandler.ts b/src/handlers/Web3AuthPasswordlessHandler.ts new file mode 100644 index 0000000..6a8b0ca --- /dev/null +++ b/src/handlers/Web3AuthPasswordlessHandler.ts @@ -0,0 +1,60 @@ +import deepmerge from "deepmerge"; + +import { decodeToken, loginToConnectionMap } from "../utils/helpers"; +import AbstractLoginHandler from "./AbstractLoginHandler"; +import { Auth0UserInfo, CreateHandlerParams, EMAIL_FLOW, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; + +export default class Web3AuthPasswordlessHandler extends AbstractLoginHandler { + private readonly SCOPE: string = "openid profile email"; + + private readonly RESPONSE_TYPE: string = "token id_token"; + + private readonly PROMPT: string = "login"; + + constructor(params: CreateHandlerParams) { + super(params); + this.setFinalUrl(); + } + + setFinalUrl(): void { + const finalUrl = new URL("https://passwordless.web3auth.io/v6/authorize"); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); + this.params.customState = { ...(this.params.customState || {}), client: this.params.web3AuthClientId }; + const finalJwtParams = deepmerge( + { + state: this.state, + client_id: this.params.clientId || this.params.web3AuthClientId, + redirect_uri: this.params.redirect_uri, + nonce: this.nonce, + network: this.params.web3AuthNetwork, + connection: loginToConnectionMap[this.params.typeOfLogin], + web3auth_client_id: this.params.web3AuthClientId, + scope: this.SCOPE, + response_type: this.RESPONSE_TYPE, + prompt: this.PROMPT, + flow_type: clonedParams?.flow_type || EMAIL_FLOW.code, + }, + clonedParams + ); + Object.keys(finalJwtParams).forEach((key: string) => { + const localKey = key as keyof typeof finalJwtParams; + if (finalJwtParams[localKey]) finalUrl.searchParams.append(localKey, finalJwtParams[localKey]); + }); + this.finalURL = finalUrl; + } + + async getUserInfo(params: LoginWindowResponse): Promise { + const { idToken } = params; + + const decodedToken = decodeToken(idToken).payload; + const { name, email, picture } = decodedToken; + return { + profileImage: picture, + name, + email, + verifierId: name.toLowerCase(), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, + }; + } +} diff --git a/src/handlers/interfaces.ts b/src/handlers/interfaces.ts index c1d2357..7735e15 100644 --- a/src/handlers/interfaces.ts +++ b/src/handlers/interfaces.ts @@ -327,6 +327,13 @@ export interface BaseLoginOptions { connection?: string; } +export const EMAIL_FLOW = { + link: "link", + code: "code", +} as const; + +export type EMAIL_FLOW_TYPE = (typeof EMAIL_FLOW)[keyof typeof EMAIL_FLOW]; + export interface Auth0ClientOptions extends BaseLoginOptions { /** * Your Auth0 account domain such as `'example.auth0.com'`, @@ -372,6 +379,11 @@ export interface Auth0ClientOptions extends BaseLoginOptions { * @defaultValue userinfo * */ user_info_route?: string; + + /** + * The flow type for email_passwordless login + */ + flow_type?: EMAIL_FLOW_TYPE; } export interface SubVerifierDetails { @@ -393,6 +405,8 @@ export interface CreateHandlerParams { redirectToOpener?: boolean; jwtParams?: Auth0ClientOptions; customState?: TorusGenericObject; + web3AuthClientId: string; + web3AuthNetwork: TORUS_NETWORK_TYPE; } export interface RedirectResultParams { diff --git a/src/login.ts b/src/login.ts index f5d6e3c..8806947 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,3 +1,4 @@ +import { TORUS_NETWORK_TYPE } from "@toruslabs/constants"; import { NodeDetailManager } from "@toruslabs/fetch-node-details"; import { keccak256, Torus, TorusKey } from "@toruslabs/torus.js"; @@ -38,6 +39,8 @@ class CustomAuth { locationReplaceOnRedirect: boolean; popupFeatures: string; useDkg?: boolean; + web3AuthClientId: string; + web3AuthNetwork: TORUS_NETWORK_TYPE; }; torus: Torus; @@ -81,6 +84,8 @@ class CustomAuth { locationReplaceOnRedirect, popupFeatures, useDkg, + web3AuthClientId, + web3AuthNetwork: network, }; const torus = new Torus({ network, @@ -146,6 +151,8 @@ class CustomAuth { jwtParams, uxMode: this.config.uxMode, customState, + web3AuthClientId: this.config.web3AuthClientId, + web3AuthNetwork: this.config.web3AuthNetwork, }); let loginParams: LoginWindowResponse; if (hash && queryParameters) { @@ -209,6 +216,8 @@ class CustomAuth { jwtParams, uxMode: this.config.uxMode, customState, + web3AuthClientId: this.config.web3AuthClientId, + web3AuthNetwork: this.config.web3AuthNetwork, }); // We let the user login to each verifier in a loop. Don't wait for key derivation here.! let loginParams: LoginWindowResponse; diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 8f779a8..6d4e95f 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -12,6 +12,8 @@ export const LOGIN = { LINE: "line", EMAIL_PASSWORD: "email_password", PASSWORDLESS: "passwordless", + EMAIL_PASSWORDLESS: "email_passwordless", + SMS_PASSWORDLESS: "sms_passwordless", JWT: "jwt", PASSKEYS: "passkeys", } as const; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 02e5e50..8d949e9 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -27,7 +27,7 @@ export function eventToPromise(emitter: EmitterType): Promise { } // These are the default connection names used by auth0 -export const loginToConnectionMap = { +export const loginToConnectionMap: Record = { [LOGIN.APPLE]: "apple", [LOGIN.GITHUB]: "github", [LOGIN.LINKEDIN]: "linkedin", @@ -36,6 +36,8 @@ export const loginToConnectionMap = { [LOGIN.LINE]: "line", [LOGIN.EMAIL_PASSWORD]: "Username-Password-Authentication", [LOGIN.PASSWORDLESS]: "email", + [LOGIN.EMAIL_PASSWORDLESS]: "email", + [LOGIN.SMS_PASSWORDLESS]: "sms", }; export const padUrlString = (url: URL): string => (url.href.endsWith("/") ? url.href : `${url.href}/`); @@ -66,6 +68,8 @@ export const getVerifierId = ( switch (typeOfLogin) { case LOGIN.PASSWORDLESS: case LOGIN.EMAIL_PASSWORD: + case LOGIN.EMAIL_PASSWORDLESS: + case LOGIN.SMS_PASSWORDLESS: return caseSensitiveField(name, isVerifierIdCaseSensitive); case LOGIN.WEIBO: case LOGIN.GITHUB: