From 5326ba76dedb059e154e9628d94be8e7d21360d3 Mon Sep 17 00:00:00 2001 From: Chaitanya Potti Date: Wed, 28 Aug 2024 21:25:20 +0800 Subject: [PATCH] - refactor customauth params - remove hybrid aggregate login method --- examples/vue-app/package-lock.json | 3 +- src/handlers/AbstractLoginHandler.ts | 37 +++++------ src/handlers/DiscordHandler.ts | 26 +++----- src/handlers/FacebookHandler.ts | 28 +++----- src/handlers/GoogleHandler.ts | 26 +++----- src/handlers/HandlerFactory.ts | 32 ++++----- src/handlers/JwtHandler.ts | 47 +++++--------- src/handlers/MockLoginHandler.ts | 41 +++++------- src/handlers/PasskeysHandler.ts | 28 +++----- src/handlers/PasswordlessHandler.ts | 44 +++++-------- src/handlers/RedditHandler.ts | 64 ------------------ src/handlers/TwitchHandler.ts | 28 +++----- src/handlers/interfaces.ts | 28 ++++---- src/login.ts | 97 ---------------------------- src/utils/enums.ts | 1 - 15 files changed, 131 insertions(+), 399 deletions(-) delete mode 100644 src/handlers/RedditHandler.ts diff --git a/examples/vue-app/package-lock.json b/examples/vue-app/package-lock.json index f01f3760..68430262 100644 --- a/examples/vue-app/package-lock.json +++ b/examples/vue-app/package-lock.json @@ -43,7 +43,7 @@ }, "../..": { "name": "@toruslabs/customauth", - "version": "20.0.3", + "version": "20.1.1", "license": "MIT", "dependencies": { "@chaitanyapotti/register-service-worker": "^1.7.4", @@ -71,6 +71,7 @@ "lint-staged": "^15.2.8", "prettier": "^3.3.3", "rimraf": "^6.0.1", + "typed-emitter": "^2.1.0", "typescript": "^5.5.4" }, "engines": { diff --git a/src/handlers/AbstractLoginHandler.ts b/src/handlers/AbstractLoginHandler.ts index ec8322b7..aa2252ee 100644 --- a/src/handlers/AbstractLoginHandler.ts +++ b/src/handlers/AbstractLoginHandler.ts @@ -1,46 +1,41 @@ import base64url from "base64url"; -import { LOGIN_TYPE, UX_MODE, UX_MODE_TYPE } from "../utils/enums"; +import { UX_MODE } from "../utils/enums"; import { broadcastChannelOptions, getTimeout, randomId } from "../utils/helpers"; import log from "../utils/loglevel"; import PopupHandler from "../utils/PopupHandler"; -import { Auth0ClientOptions, ILoginHandler, LoginWindowResponse, PopupResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, ILoginHandler, LoginWindowResponse, PopupResponse, TorusVerifierResponse } from "./interfaces"; abstract class AbstractLoginHandler implements ILoginHandler { public nonce: string = randomId(); public finalURL: URL; + public params: CreateHandlerParams; + // Not using object constructor because of this issue // https://github.com/microsoft/TypeScript/issues/5326 - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) {} + constructor(params: CreateHandlerParams) { + this.params = params; + } get state(): string { return encodeURIComponent( base64url.encode( JSON.stringify({ - ...(this.customState || {}), + ...(this.params.customState || {}), instanceId: this.nonce, - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, - redirectToOpener: this.redirectToOpener || false, + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, + redirectToOpener: this.params.redirectToOpener || false, }) ) ); } async handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise { - const verifierWindow = new PopupHandler({ url: this.finalURL, features: params.popupFeatures, timeout: getTimeout(this.typeOfLogin) }); - if (this.uxMode === UX_MODE.REDIRECT) { + const verifierWindow = new PopupHandler({ url: this.finalURL, features: params.popupFeatures, timeout: getTimeout(this.params.typeOfLogin) }); + if (this.params.uxMode === UX_MODE.REDIRECT) { verifierWindow.redirect(params.locationReplaceOnRedirect); } else { const { BroadcastChannel } = await import("@toruslabs/broadcast-channel"); @@ -59,9 +54,9 @@ abstract class AbstractLoginHandler implements ILoginHandler { reject(new Error(`Error: ${error}. Info: ${JSON.stringify(ev.data || {})}`)); return; } - if (ev.data && instanceParams.verifier === this.verifier) { + if (ev.data && instanceParams.verifier === this.params.verifier) { log.info(ev.data); - if (!this.redirectToOpener && bc) await bc.postMessage({ success: true }); + if (!this.params.redirectToOpener && bc) await bc.postMessage({ success: true }); resolve({ accessToken, idToken: idToken || "", @@ -76,7 +71,7 @@ abstract class AbstractLoginHandler implements ILoginHandler { } }; - if (!this.redirectToOpener) { + if (!this.params.redirectToOpener) { bc = new BroadcastChannel<{ error: string; data: PopupResponse; diff --git a/src/handlers/DiscordHandler.ts b/src/handlers/DiscordHandler.ts index a4f0cd2f..5b74a55f 100644 --- a/src/handlers/DiscordHandler.ts +++ b/src/handlers/DiscordHandler.ts @@ -1,38 +1,28 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class DiscordHandler extends AbstractLoginHandler { private readonly RESPONSE_TYPE: string = "token"; private readonly SCOPE: string = "identify email"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { const finalUrl = new URL("https://discord.com/api/oauth2/authorize"); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); const finalJwtParams = deepmerge( { state: this.state, response_type: this.RESPONSE_TYPE, - client_id: this.clientId, - redirect_uri: this.redirect_uri, + client_id: this.params.clientId, + redirect_uri: this.params.redirect_uri, scope: this.SCOPE, }, clonedParams @@ -64,8 +54,8 @@ export default class DiscordHandler extends AbstractLoginHandler { name: `${name}#${discriminator}`, email, verifierId: id, - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } } diff --git a/src/handlers/FacebookHandler.ts b/src/handlers/FacebookHandler.ts index c6ad5e8c..51eecd6e 100644 --- a/src/handlers/FacebookHandler.ts +++ b/src/handlers/FacebookHandler.ts @@ -1,38 +1,28 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class FacebookHandler extends AbstractLoginHandler { private readonly RESPONSE_TYPE: string = "token"; private readonly SCOPE: string = "public_profile email"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { - const finalUrl = new URL("https://www.facebook.com/v15.0/dialog/oauth"); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); + const finalUrl = new URL("https://www.facebook.com/v20.0/dialog/oauth"); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); const finalJwtParams = deepmerge( { state: this.state, response_type: this.RESPONSE_TYPE, - client_id: this.clientId, - redirect_uri: this.redirect_uri, + client_id: this.params.clientId, + redirect_uri: this.params.redirect_uri, scope: this.SCOPE, }, clonedParams @@ -59,9 +49,9 @@ export default class FacebookHandler extends AbstractLoginHandler { email, name, profileImage: picture.data.url || "", - verifier: this.verifier, + verifier: this.params.verifier, verifierId: id, - typeOfLogin: this.typeOfLogin, + typeOfLogin: this.params.typeOfLogin, }; } } diff --git a/src/handlers/GoogleHandler.ts b/src/handlers/GoogleHandler.ts index dcfce01c..3cc692ee 100644 --- a/src/handlers/GoogleHandler.ts +++ b/src/handlers/GoogleHandler.ts @@ -1,9 +1,8 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class GoogleHandler extends AbstractLoginHandler { private readonly RESPONSE_TYPE: string = "token id_token"; @@ -12,30 +11,21 @@ export default class GoogleHandler extends AbstractLoginHandler { private readonly PROMPT: string = "select_account"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { const finalUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); const finalJwtParams = deepmerge( { state: this.state, response_type: this.RESPONSE_TYPE, - client_id: this.clientId, + client_id: this.params.clientId, prompt: this.PROMPT, - redirect_uri: this.redirect_uri, + redirect_uri: this.params.redirect_uri, scope: this.SCOPE, nonce: this.nonce, }, @@ -60,9 +50,9 @@ export default class GoogleHandler extends AbstractLoginHandler { email, name, profileImage, - verifier: this.verifier, + verifier: this.params.verifier, verifierId: email.toLowerCase(), - typeOfLogin: this.typeOfLogin, + typeOfLogin: this.params.typeOfLogin, }; } } diff --git a/src/handlers/HandlerFactory.ts b/src/handlers/HandlerFactory.ts index c47b4074..5dde3aa2 100644 --- a/src/handlers/HandlerFactory.ts +++ b/src/handlers/HandlerFactory.ts @@ -7,37 +7,26 @@ import JwtHandler from "./JwtHandler"; import MockLoginHandler from "./MockLoginHandler"; import PasskeysHandler from "./PasskeysHandler"; import PasswordlessHandler from "./PasswordlessHandler"; -import RedditHandler from "./RedditHandler"; import TwitchHandler from "./TwitchHandler"; -const createHandler = ({ - clientId, - redirect_uri, - typeOfLogin, - verifier, - jwtParams, - redirectToOpener, - uxMode, - customState, -}: CreateHandlerParams): ILoginHandler => { +const createHandler = (params: CreateHandlerParams): ILoginHandler => { + const { verifier, typeOfLogin, clientId, jwtParams } = params; if (!verifier || !typeOfLogin || !clientId) { throw new Error("Invalid params. Missing verifier, typeOfLogin or clientId"); } const { domain, login_hint, id_token, access_token } = jwtParams || {}; switch (typeOfLogin) { case LOGIN.GOOGLE: - return new GoogleHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new GoogleHandler(params); case LOGIN.FACEBOOK: - return new FacebookHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new FacebookHandler(params); case LOGIN.TWITCH: - return new TwitchHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); - case LOGIN.REDDIT: - return new RedditHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new TwitchHandler(params); case LOGIN.DISCORD: - return new DiscordHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new DiscordHandler(params); case LOGIN.PASSWORDLESS: if (!domain || !login_hint) throw new Error("Invalid params. Missing domain or login_hint for passwordless login"); - return new PasswordlessHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new PasswordlessHandler(params); case LOGIN.APPLE: case LOGIN.GITHUB: case LOGIN.LINKEDIN: @@ -46,13 +35,14 @@ const createHandler = ({ case LOGIN.LINE: case LOGIN.EMAIL_PASSWORD: case LOGIN.JWT: + case LOGIN.REDDIT: if (id_token || access_token) { - return new MockLoginHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new MockLoginHandler(params); } if (!domain) throw new Error("Invalid params for jwt login. Missing domain"); - return new JwtHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new JwtHandler(params); case LOGIN.PASSKEYS: - return new PasskeysHandler(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + return new PasskeysHandler(params); default: throw new Error("Unsupported login type"); } diff --git a/src/handlers/JwtHandler.ts b/src/handlers/JwtHandler.ts index f5d70505..954d0ae9 100644 --- a/src/handlers/JwtHandler.ts +++ b/src/handlers/JwtHandler.ts @@ -2,17 +2,9 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; import log from "loglevel"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import { decodeToken, getVerifierId, loginToConnectionMap, padUrlString, validateAndConstructUrl } from "../utils/helpers"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { - AUTH0_JWT_LOGIN_TYPE, - Auth0ClientOptions, - Auth0UserInfo, - LoginWindowResponse, - TorusGenericObject, - TorusVerifierResponse, -} from "./interfaces"; +import { AUTH0_JWT_LOGIN_TYPE, Auth0UserInfo, CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class JwtHandler extends AbstractLoginHandler { private readonly SCOPE: string = "openid profile email"; @@ -21,35 +13,26 @@ export default class JwtHandler extends AbstractLoginHandler { private readonly PROMPT: string = "login"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { - const { domain } = this.jwtParams; + const { domain } = this.params.jwtParams; const finalUrl = validateAndConstructUrl(domain); finalUrl.pathname += finalUrl.pathname.endsWith("/") ? "authorize" : "/authorize"; - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams)); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams)); delete clonedParams.domain; const finalJwtParams = deepmerge( { state: this.state, response_type: this.RESPONSE_TYPE, - client_id: this.clientId, + client_id: this.params.clientId, prompt: this.PROMPT, - redirect_uri: this.redirect_uri, + redirect_uri: this.params.redirect_uri, scope: this.SCOPE, - connection: loginToConnectionMap[this.typeOfLogin as AUTH0_JWT_LOGIN_TYPE], + connection: loginToConnectionMap[this.params.typeOfLogin as AUTH0_JWT_LOGIN_TYPE], nonce: this.nonce, }, clonedParams @@ -63,7 +46,7 @@ export default class JwtHandler extends AbstractLoginHandler { async getUserInfo(params: LoginWindowResponse): Promise { const { idToken, accessToken } = params; - const { domain, verifierIdField, isVerifierIdCaseSensitive, user_info_route = "userinfo" } = this.jwtParams; + const { domain, verifierIdField, isVerifierIdCaseSensitive, user_info_route = "userinfo" } = this.params.jwtParams; if (accessToken) { try { const domainUrl = new URL(domain); @@ -77,9 +60,9 @@ export default class JwtHandler extends AbstractLoginHandler { email, name, profileImage: picture, - verifierId: getVerifierId(userInfo, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(userInfo, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } catch (error) { // ignore @@ -93,9 +76,9 @@ export default class JwtHandler extends AbstractLoginHandler { profileImage: picture, name, email, - verifierId: getVerifierId(decodedToken, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(decodedToken, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } throw new Error("Access/id token not available"); diff --git a/src/handlers/MockLoginHandler.ts b/src/handlers/MockLoginHandler.ts index 8b839ea3..02d427c4 100644 --- a/src/handlers/MockLoginHandler.ts +++ b/src/handlers/MockLoginHandler.ts @@ -2,45 +2,36 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; import log from "loglevel"; -import { LOGIN_TYPE, UX_MODE, UX_MODE_TYPE } from "../utils/enums"; +import { UX_MODE } from "../utils/enums"; import { constructURL, decodeToken, getVerifierId, padUrlString } from "../utils/helpers"; import PopupHandler from "../utils/PopupHandler"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, Auth0UserInfo, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { Auth0UserInfo, CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class MockLoginHandler extends AbstractLoginHandler { - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams)); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams)); delete clonedParams.domain; const finalJwtParams = deepmerge( { state: this.state, - client_id: this.clientId, + client_id: this.params.clientId, nonce: this.nonce, }, clonedParams ); - this.finalURL = new URL(constructURL({ baseURL: this.redirect_uri, query: null, hash: finalJwtParams })); + this.finalURL = new URL(constructURL({ baseURL: this.params.redirect_uri, query: null, hash: finalJwtParams })); } async getUserInfo(params: LoginWindowResponse): Promise { const { idToken, accessToken } = params; - const { domain, verifierIdField, isVerifierIdCaseSensitive, user_info_route = "userinfo" } = this.jwtParams; + const { domain, verifierIdField, isVerifierIdCaseSensitive, user_info_route = "userinfo" } = this.params.jwtParams; if (accessToken) { try { const domainUrl = new URL(domain); @@ -54,9 +45,9 @@ export default class MockLoginHandler extends AbstractLoginHandler { email, name, profileImage: picture, - verifierId: getVerifierId(userInfo, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(userInfo, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } catch (error) { // ignore @@ -70,18 +61,18 @@ export default class MockLoginHandler extends AbstractLoginHandler { profileImage: picture, name, email, - verifierId: getVerifierId(decodedToken, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(decodedToken, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } throw new Error("Access/id token not available"); } handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise { - const { id_token: idToken, access_token: accessToken } = this.jwtParams; + const { id_token: idToken, access_token: accessToken } = this.params.jwtParams; const verifierWindow = new PopupHandler({ url: this.finalURL, features: params.popupFeatures }); - if (this.uxMode === UX_MODE.REDIRECT) { + if (this.params.uxMode === UX_MODE.REDIRECT) { verifierWindow.redirect(params.locationReplaceOnRedirect); } else { return Promise.resolve({ diff --git a/src/handlers/PasskeysHandler.ts b/src/handlers/PasskeysHandler.ts index 27e552cf..9d5b37fd 100644 --- a/src/handlers/PasskeysHandler.ts +++ b/src/handlers/PasskeysHandler.ts @@ -1,36 +1,26 @@ import base64url from "base64url"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import { fetchDataFromBroadcastServer } from "../utils/sessionHelper"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, PasskeySessionData, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, LoginWindowResponse, PasskeySessionData, TorusVerifierResponse } from "./interfaces"; export default class PasskeysHandler extends AbstractLoginHandler { - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { - const { passkeysHostUrl } = this.customState || {}; + const { passkeysHostUrl } = this.params.customState || {}; if (!passkeysHostUrl) throw new Error("Invalid passkeys url."); const finalUrl = new URL(passkeysHostUrl); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); const finalJwtParams = deepmerge( { state: this.state, - client_id: this.clientId, - redirect_uri: this.redirect_uri, + client_id: this.params.clientId, + redirect_uri: this.params.redirect_uri, }, clonedParams ); @@ -71,9 +61,9 @@ export default class PasskeysHandler extends AbstractLoginHandler { email: "", name: "Passkeys Login", profileImage: "", - verifier: this.verifier, + verifier: this.params.verifier, verifierId, - typeOfLogin: this.typeOfLogin, + typeOfLogin: this.params.typeOfLogin, extraVerifierParams: { signature, clientDataJSON, diff --git a/src/handlers/PasswordlessHandler.ts b/src/handlers/PasswordlessHandler.ts index 959ab36a..bf3b888f 100644 --- a/src/handlers/PasswordlessHandler.ts +++ b/src/handlers/PasswordlessHandler.ts @@ -1,11 +1,10 @@ import { get, post } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import { broadcastChannelOptions, decodeToken, getVerifierId, padUrlString, validateAndConstructUrl } from "../utils/helpers"; import log from "../utils/loglevel"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, Auth0UserInfo, LoginWindowResponse, PopupResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { Auth0UserInfo, CreateHandlerParams, LoginWindowResponse, PopupResponse, TorusVerifierResponse } from "./interfaces"; export default class JwtHandler extends AbstractLoginHandler { private readonly SCOPE: string = "openid profile email"; @@ -14,22 +13,13 @@ export default class JwtHandler extends AbstractLoginHandler { private readonly PROMPT: string = "login"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { - const { domain } = this.jwtParams; + const { domain } = this.params.jwtParams; const domainUrl = validateAndConstructUrl(domain); domainUrl.pathname = "/passwordless/start"; @@ -38,7 +28,7 @@ export default class JwtHandler extends AbstractLoginHandler { async getUserInfo(params: LoginWindowResponse): Promise { const { idToken, accessToken } = params; - const { domain, verifierIdField, isVerifierIdCaseSensitive } = this.jwtParams; + const { domain, verifierIdField, isVerifierIdCaseSensitive } = this.params.jwtParams; try { const domainUrl = new URL(domain); const userInfo = await get(`${padUrlString(domainUrl)}userinfo`, { @@ -51,9 +41,9 @@ export default class JwtHandler extends AbstractLoginHandler { email, name, profileImage: picture, - verifierId: getVerifierId(userInfo, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(userInfo, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } catch (error) { log.error(error); @@ -63,9 +53,9 @@ export default class JwtHandler extends AbstractLoginHandler { profileImage: picture, name, email, - verifierId: getVerifierId(decodedToken, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifierId: getVerifierId(decodedToken, this.params.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive), + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } } @@ -73,7 +63,7 @@ export default class JwtHandler extends AbstractLoginHandler { async handleLoginWindow(): Promise { const { BroadcastChannel } = await import("@toruslabs/broadcast-channel"); return new Promise((resolve, reject) => { - if (this.redirectToOpener) { + if (this.params.redirectToOpener) { reject(new Error("Cannot use redirect to opener for passwordless")); return; } @@ -89,7 +79,7 @@ export default class JwtHandler extends AbstractLoginHandler { reject(new Error(error)); return; } - if (ev.data && instanceParams.verifier === this.verifier) { + if (ev.data && instanceParams.verifier === this.params.verifier) { log.info(ev.data); resolve({ accessToken, idToken: idToken || "", ...rest, state: instanceParams }); } @@ -107,10 +97,10 @@ export default class JwtHandler extends AbstractLoginHandler { bc.close(); }); try { - const { connection = "email", login_hint } = this.jwtParams; + const { connection = "email", login_hint } = this.params.jwtParams; const finalJwtParams = deepmerge( { - client_id: this.clientId, + client_id: this.params.clientId, connection, email: connection === "email" ? login_hint : undefined, phone_number: connection === "sms" ? login_hint : undefined, @@ -119,13 +109,13 @@ export default class JwtHandler extends AbstractLoginHandler { scope: this.SCOPE, state: this.state, response_type: this.RESPONSE_TYPE, - redirect_uri: this.redirect_uri, + redirect_uri: this.params.redirect_uri, nonce: this.nonce, prompt: this.PROMPT, }, }, { - authParams: this.jwtParams, + authParams: this.params.jwtParams, } ); // using stringify and parse to remove undefined params diff --git a/src/handlers/RedditHandler.ts b/src/handlers/RedditHandler.ts deleted file mode 100644 index 67aeacfe..00000000 --- a/src/handlers/RedditHandler.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { get } from "@toruslabs/http-helpers"; -import deepmerge from "deepmerge"; - -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; -import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; - -export default class RedditHandler extends AbstractLoginHandler { - private readonly RESPONSE_TYPE: string = "token"; - - private readonly SCOPE: string = "identity"; - - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); - this.setFinalUrl(); - } - - setFinalUrl(): void { - const finalUrl = new URL(`https://www.reddit.com/api/v1/authorize${window.innerWidth < 600 ? ".compact" : ""}`); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); - const finalJwtParams = deepmerge( - { - state: this.state, - response_type: this.RESPONSE_TYPE, - client_id: this.clientId, - redirect_uri: this.redirect_uri, - scope: this.SCOPE, - }, - 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 { accessToken } = params; - const userInfo = await get<{ icon_img: string; name: string }>("https://oauth.reddit.com/api/v1/me", { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - const { icon_img: profileImage = "", name = "" } = userInfo; - return { - email: "", - name, - profileImage: profileImage.split("?").length > 0 ? profileImage.split("?")[0] : profileImage, - verifier: this.verifier, - verifierId: name.toLowerCase(), - typeOfLogin: this.typeOfLogin, - }; - } -} diff --git a/src/handlers/TwitchHandler.ts b/src/handlers/TwitchHandler.ts index 6b0ac564..4ebdf1fd 100644 --- a/src/handlers/TwitchHandler.ts +++ b/src/handlers/TwitchHandler.ts @@ -1,38 +1,28 @@ import { get } from "@toruslabs/http-helpers"; import deepmerge from "deepmerge"; -import { LOGIN_TYPE, UX_MODE_TYPE } from "../utils/enums"; import AbstractLoginHandler from "./AbstractLoginHandler"; -import { Auth0ClientOptions, LoginWindowResponse, TorusGenericObject, TorusVerifierResponse } from "./interfaces"; +import { CreateHandlerParams, LoginWindowResponse, TorusVerifierResponse } from "./interfaces"; export default class TwitchHandler extends AbstractLoginHandler { private readonly RESPONSE_TYPE: string = "token"; private readonly SCOPE: string = "user:read:email"; - constructor( - readonly clientId: string, - readonly verifier: string, - readonly redirect_uri: string, - readonly typeOfLogin: LOGIN_TYPE, - readonly uxMode: UX_MODE_TYPE, - readonly redirectToOpener?: boolean, - readonly jwtParams?: Auth0ClientOptions, - readonly customState?: TorusGenericObject - ) { - super(clientId, verifier, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState); + constructor(params: CreateHandlerParams) { + super(params); this.setFinalUrl(); } setFinalUrl(): void { const finalUrl = new URL("https://id.twitch.tv/oauth2/authorize"); - const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {})); + const clonedParams = JSON.parse(JSON.stringify(this.params.jwtParams || {})); const finalJwtParams = deepmerge( { state: this.state, response_type: this.RESPONSE_TYPE, - client_id: this.clientId, - redirect_uri: this.redirect_uri, + client_id: this.params.clientId, + redirect_uri: this.params.redirect_uri, scope: this.SCOPE, force_verify: "true", }, @@ -52,7 +42,7 @@ export default class TwitchHandler extends AbstractLoginHandler { { headers: { Authorization: `Bearer ${accessToken}`, - "Client-ID": this.clientId, + "Client-ID": this.params.clientId, }, } ); @@ -62,8 +52,8 @@ export default class TwitchHandler extends AbstractLoginHandler { name, email, verifierId, - verifier: this.verifier, - typeOfLogin: this.typeOfLogin, + verifier: this.params.verifier, + typeOfLogin: this.params.typeOfLogin, }; } } diff --git a/src/handlers/interfaces.ts b/src/handlers/interfaces.ts index b12e31c8..c1d23573 100644 --- a/src/handlers/interfaces.ts +++ b/src/handlers/interfaces.ts @@ -64,14 +64,6 @@ export interface LoginWindowResponse { state: TorusGenericObject; } -export interface ILoginHandler { - clientId: string; - nonce: string; - finalURL: URL; - getUserInfo(params: LoginWindowResponse, storageServerUrl?: string): Promise; - handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise; -} - export interface TorusAggregateVerifierResponse { userInfo: (TorusVerifierResponse & LoginWindowResponse)[]; } @@ -82,7 +74,6 @@ export interface TorusSingleVerifierResponse { export type TorusLoginResponse = TorusSingleVerifierResponse & TorusKey; export type TorusAggregateLoginResponse = TorusAggregateVerifierResponse & TorusKey; -export type TorusHybridAggregateLoginResponse = { singleLogin: TorusLoginResponse; aggregateLogins: TorusKey[] }; export interface CustomAuthArgs { /** @@ -417,20 +408,15 @@ export interface AggregateLoginParams { subVerifierDetailsArray: SubVerifierDetails[]; } -export interface HybridAggregateLoginParams { - singleLogin: SubVerifierDetails; - aggregateLoginParams: AggregateLoginParams; -} - -export type LoginDetails = { method: TORUS_METHOD_TYPE; args: SingleLoginParams | AggregateLoginParams | HybridAggregateLoginParams }; +export type LoginDetails = { method: TORUS_METHOD_TYPE; args: SingleLoginParams | AggregateLoginParams }; export interface RedirectResult { method: TORUS_METHOD_TYPE; - result?: TorusLoginResponse | TorusAggregateLoginResponse | TorusHybridAggregateLoginResponse | unknown; + result?: TorusLoginResponse | TorusAggregateLoginResponse | unknown; error?: string; state: Record; hashParameters?: Record; - args: SingleLoginParams | AggregateLoginParams | HybridAggregateLoginParams; + args: SingleLoginParams | AggregateLoginParams; } export type AUTH0_JWT_LOGIN_TYPE = "apple" | "github" | "linkedin" | "twitter" | "weibo" | "line" | "email_password" | "passwordless"; @@ -457,3 +443,11 @@ export type PasskeySessionData = { transports: AuthenticatorTransport[]; username: string; }; + +export interface ILoginHandler { + params: CreateHandlerParams; + nonce: string; + finalURL: URL; + getUserInfo(params: LoginWindowResponse, storageServerUrl?: string): Promise; + handleLoginWindow(params: { locationReplaceOnRedirect?: boolean; popupFeatures?: string }): Promise; +} diff --git a/src/login.ts b/src/login.ts index 5dc0fa3a..f5d6e3c5 100644 --- a/src/login.ts +++ b/src/login.ts @@ -7,7 +7,6 @@ import { AggregateVerifierParams, CustomAuthArgs, ExtraParams, - HybridAggregateLoginParams, ILoginHandler, InitParams, LoginWindowResponse, @@ -16,7 +15,6 @@ import { SingleLoginParams, SubVerifierDetails, TorusAggregateLoginResponse, - TorusHybridAggregateLoginResponse, TorusLoginResponse, TorusSubVerifierInfo, TorusVerifierResponse, @@ -262,96 +260,6 @@ class CustomAuth { }; } - async triggerHybridAggregateLogin(args: HybridAggregateLoginParams): Promise { - const { singleLogin, aggregateLoginParams } = args; - // This method shall break if any of the promises fail. This behaviour is intended - if (!this.isInitialized) { - throw new Error("Not initialized yet"); - } - if ( - !aggregateLoginParams.aggregateVerifierType || - !aggregateLoginParams.verifierIdentifier || - !Array.isArray(aggregateLoginParams.subVerifierDetailsArray) - ) { - throw new Error("Invalid params. Missing aggregateVerifierType, verifierIdentifier or subVerifierDetailsArray"); - } - if ( - aggregateLoginParams.aggregateVerifierType === AGGREGATE_VERIFIER.SINGLE_VERIFIER_ID && - aggregateLoginParams.subVerifierDetailsArray.length !== 1 - ) { - throw new Error("Single id verifier can only have one sub verifier"); - } - const { typeOfLogin, clientId, verifier, jwtParams, hash, queryParameters, customState } = singleLogin; - const loginHandler: ILoginHandler = createHandler({ - typeOfLogin, - clientId, - verifier, - redirect_uri: this.config.redirect_uri, - redirectToOpener: this.config.redirectToOpener, - jwtParams, - uxMode: this.config.uxMode, - customState, - }); - let loginParams: LoginWindowResponse; - if (hash && queryParameters) { - const { error, hashParameters, instanceParameters } = handleRedirectParameters(hash, queryParameters); - if (error) throw new Error(error); - const { access_token: accessToken, id_token: idToken, ...rest } = hashParameters; - // State has to be last here otherwise it will be overwritten - loginParams = { accessToken, idToken, ...rest, state: instanceParameters }; - } else { - this.storageHelper.clearOrphanedLoginDetails(); - if (this.config.uxMode === UX_MODE.REDIRECT) { - await this.storageHelper.storeLoginDetails({ method: TORUS_METHOD.TRIGGER_AGGREGATE_HYBRID_LOGIN, args }, loginHandler.nonce); - } - loginParams = await loginHandler.handleLoginWindow({ - locationReplaceOnRedirect: this.config.locationReplaceOnRedirect, - popupFeatures: this.config.popupFeatures, - }); - if (this.config.uxMode === UX_MODE.REDIRECT) return null; - } - - const userInfo = await loginHandler.getUserInfo(loginParams); - const torusKey1Promise = this.getTorusKey( - verifier, - userInfo.verifierId, - { verifier_id: userInfo.verifierId }, - loginParams.idToken || loginParams.accessToken, - userInfo.extraVerifierParams - ); - - const { verifierIdentifier, subVerifierDetailsArray } = aggregateLoginParams; - const aggregateVerifierParams: AggregateVerifierParams = { verify_params: [], sub_verifier_ids: [], verifier_id: "" }; - const aggregateIdTokenSeeds = []; - let aggregateVerifierId = ""; - for (let index = 0; index < subVerifierDetailsArray.length; index += 1) { - const sub = subVerifierDetailsArray[index]; - const { idToken, accessToken } = loginParams; - aggregateVerifierParams.verify_params.push({ verifier_id: userInfo.verifierId, idtoken: idToken || accessToken }); - aggregateVerifierParams.sub_verifier_ids.push(sub.verifier); - aggregateIdTokenSeeds.push(idToken || accessToken); - aggregateVerifierId = userInfo.verifierId; // using last because idk - } - aggregateIdTokenSeeds.sort(); - const aggregateIdToken = keccak256(Buffer.from(aggregateIdTokenSeeds.join(String.fromCharCode(29)), "utf8")).slice(2); - aggregateVerifierParams.verifier_id = aggregateVerifierId; - const torusKey2Promise = this.getTorusKey( - verifierIdentifier, - aggregateVerifierId, - aggregateVerifierParams, - aggregateIdToken, - userInfo.extraVerifierParams - ); - const [torusKey1, torusKey2] = await Promise.all([torusKey1Promise, torusKey2Promise]); - return { - singleLogin: { - userInfo: { ...userInfo, ...loginParams }, - ...torusKey1, - }, - aggregateLogins: [torusKey2], - }; - } - async getTorusKey( verifier: string, verifierId: string, @@ -457,11 +365,6 @@ class CustomAuth { x.queryParameters = queryParams; }); result = await this.triggerAggregateLogin(methodArgs); - } else if (method === TORUS_METHOD.TRIGGER_AGGREGATE_HYBRID_LOGIN) { - const methodArgs = args as HybridAggregateLoginParams; - methodArgs.singleLogin.hash = hash; - methodArgs.singleLogin.queryParameters = queryParams; - result = await this.triggerHybridAggregateLogin(methodArgs); } } catch (err: unknown) { const serializedError = await serializeError(err); diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 16768e6c..8f779a89 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -36,7 +36,6 @@ export const REDIRECT_PARAMS_STORAGE_METHOD = { export const TORUS_METHOD = { TRIGGER_LOGIN: "triggerLogin", TRIGGER_AGGREGATE_LOGIN: "triggerAggregateLogin", - TRIGGER_AGGREGATE_HYBRID_LOGIN: "triggerHybridAggregateLogin", } as const; export type LOGIN_TYPE = (typeof LOGIN)[keyof typeof LOGIN];