diff --git a/package-lock.json b/package-lock.json index 6abf0a10..47ed4862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "esbuild-register": "^3.5.0", "eslint": "^8.49.0", "husky": "^8.0.3", + "jsonwebtoken": "^9.0.2", "lint-staged": "^14.0.1", "mocha": "^10.2.0", "node-fetch": "^3.3.2", @@ -3481,11 +3482,10 @@ } }, "node_modules/@tkey-mpc/service-provider-base": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@tkey-mpc/service-provider-base/-/service-provider-base-9.0.2.tgz", - "integrity": "sha512-Gk3xF1NFV/BqmwMNKDB//DNoN9nff4kGeNxXiVnlxhQKZuX9phzd/sxJljKc6tutOK5YdOcvot7ZuwJg2/vZjA==", + "version": "9.0.1", + "license": "MIT", "dependencies": { - "@tkey-mpc/common-types": "^9.0.2", + "@tkey-mpc/common-types": "^9.0.1", "bn.js": "^5.2.1", "elliptic": "^6.5.4" }, @@ -3502,10 +3502,10 @@ "resolved": "https://registry.npmjs.org/@tkey-mpc/service-provider-torus/-/service-provider-torus-9.0.3.tgz", "integrity": "sha512-9/OjTqjruR5AWMwJPRuR+ZxsZXzt9jQfzfNtde13VHSjtogcLwT12U1QTHJ7tsIZYckBy3ZVDbNpIcTOzz70tQ==", "dependencies": { - "@tkey-mpc/common-types": "^9.0.2", - "@tkey-mpc/service-provider-base": "^9.0.2", + "@tkey-mpc/common-types": "^9.0.1", + "@tkey-mpc/service-provider-base": "^9.0.1", "@toruslabs/customauth": "^16.0.6", - "@toruslabs/torus.js": "^11.0.6", + "@toruslabs/torus.js": "^11.0.5", "bn.js": "^5.2.1", "elliptic": "^6.5.4" }, @@ -4370,7 +4370,8 @@ }, "node_modules/@toruslabs/torus.js": { "version": "11.0.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@toruslabs/torus.js/-/torus.js-11.0.6.tgz", + "integrity": "sha512-l+Ba/DX1L2cLngiL08r8zZmRQ/A3MJ4pl20MaTzQuwxS2OfOXSReKld4YOWHwo5NEd36PHZdZ43qbh0NrzrwVQ==", "dependencies": { "@toruslabs/constants": "^13.0.1", "@toruslabs/eccrypto": "^4.0.0", @@ -6353,6 +6354,12 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-from": { "version": "1.1.2", "dev": true, @@ -7300,6 +7307,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.515", "dev": true, @@ -10657,6 +10673,82 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "3.1.2", "license": "MIT" @@ -10909,11 +11001,35 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, "node_modules/lodash.invokemap": { "version": "4.6.0", "dev": true, "license": "MIT" }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "dev": true, @@ -10933,6 +11049,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, "node_modules/lodash.pullall": { "version": "4.2.0", "dev": true, diff --git a/package.json b/package.json index 5b81c413..0e826cb9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "homepage": "https://github.com/Web3Auth/mpc-core-kit/tree/master#readme", "license": "ISC", "scripts": { - "test": "echo no tests available", + "test": "node --test -r esbuild-register tests/*.spec.ts ", "dev": "torus-scripts start", "build": "torus-scripts build", "release": "torus-scripts release", @@ -74,6 +74,7 @@ "esbuild-register": "^3.5.0", "eslint": "^8.49.0", "husky": "^8.0.3", + "jsonwebtoken": "^9.0.2", "lint-staged": "^14.0.1", "mocha": "^10.2.0", "node-fetch": "^3.3.2", diff --git a/src/helper/browserStorage.ts b/src/helper/browserStorage.ts index 9e130f38..3e05100f 100644 --- a/src/helper/browserStorage.ts +++ b/src/helper/browserStorage.ts @@ -4,6 +4,28 @@ import { FIELD_ELEMENT_HEX_LEN } from "../constants"; import { ICoreKit, IStorage, TkeyLocalStoreData } from "../interfaces"; import { storageAvailable } from "../utils"; +export type SupportedStorageType = "local" | "session" | "mock"; + +export class MockStorage implements IStorage { + private _store: Record = {}; + + getItem(key: string): string | null { + return this._store[key] || null; + } + + setItem(key: string, value: string): void { + this._store[key] = value; + } + + removeItem(key: string): void { + delete this._store[key]; + } + + clear(): void { + this._store = {}; + } +} + export class BrowserStorage { // eslint-disable-next-line no-use-before-define private static instance: BrowserStorage; @@ -12,7 +34,7 @@ export class BrowserStorage { private _storeKey: string; - private constructor(storeKey: string, storage: IStorage) { + constructor(storeKey: string, storage: IStorage) { this.storage = storage; this._storeKey = storeKey; try { @@ -24,16 +46,18 @@ export class BrowserStorage { } } - static getInstance(key: string, storageKey: "session" | "local" = "local"): BrowserStorage { + static getInstance(key: string, storageKey: SupportedStorageType = "local"): BrowserStorage { if (!this.instance) { - let storage: Storage | undefined; + let storage: IStorage | undefined; if (storageKey === "local" && storageAvailable("localStorage")) { storage = localStorage; } if (storageKey === "session" && storageAvailable("sessionStorage")) { storage = sessionStorage; } - + if (storageKey === "mock") { + storage = new MockStorage(); + } if (!storage) { throw new Error("No valid storage available"); } @@ -76,7 +100,7 @@ export class BrowserStorage { } } -export async function storeWebBrowserFactor(factorKey: BN, mpcCoreKit: ICoreKit, storageKey: "local" | "session" = "local"): Promise { +export async function storeWebBrowserFactor(factorKey: BN, mpcCoreKit: ICoreKit, storageKey: SupportedStorageType = "local"): Promise { const metadata = mpcCoreKit.tKey.getMetadata(); const currentStorage = BrowserStorage.getInstance("mpc_corekit_store", storageKey); @@ -89,7 +113,7 @@ export async function storeWebBrowserFactor(factorKey: BN, mpcCoreKit: ICoreKit, ); } -export async function getWebBrowserFactor(mpcCoreKit: ICoreKit, storageKey: "local" | "session" = "local"): Promise { +export async function getWebBrowserFactor(mpcCoreKit: ICoreKit, storageKey: SupportedStorageType = "local"): Promise { const metadata = mpcCoreKit.tKey.getMetadata(); const currentStorage = BrowserStorage.getInstance("mpc_corekit_store", storageKey); diff --git a/src/helper/securityQuestion.ts b/src/helper/securityQuestion.ts index 1e0b77a3..7843e013 100644 --- a/src/helper/securityQuestion.ts +++ b/src/helper/securityQuestion.ts @@ -3,7 +3,8 @@ import { keccak256 } from "@toruslabs/torus.js"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, VALID_SHARE_INDICES } from "../constants"; -import type { Web3AuthMPCCoreKit } from "../mpcCoreKit"; +import { ICoreKit } from "../interfaces"; +// import type { Web3AuthMPCCoreKit } from "../mpcCoreKit"; import { Point } from "../point"; export class TssSecurityQuestionStore { @@ -34,7 +35,7 @@ export class TssSecurityQuestionStore { } export interface setSecurityQuestionParams { - mpcCoreKit: Web3AuthMPCCoreKit; + mpcCoreKit: ICoreKit; question: string; answer: string; shareType?: TssShareType; @@ -43,12 +44,16 @@ export interface setSecurityQuestionParams { } export interface changeSecurityQuestionParams { - mpcCoreKit: Web3AuthMPCCoreKit; + mpcCoreKit: ICoreKit; newQuestion: string; newAnswer: string; answer: string; } +// Idea using hash of answer + pubKey as security question factor key factor key +// opposed to use the hash to encrypt random factor key as both are equally secure +// but this way we can use the hash to recover the factor key and reduce the api call and storage + export class TssSecurityQuestion { storeDomainName = "tssSecurityQuestion"; @@ -160,8 +165,9 @@ export class TssSecurityQuestion { if (!tkey.manualSync) await tkey._syncShareMetadata(); } - // Should we check with answer before deleting? - async deleteSecurityQuestion(mpcCoreKit: Web3AuthMPCCoreKit, deleteFactorKey = true) { + // provide option to delete security question incase user forgot the password + // option to not delete the factor key if user only want to remove the question but not the factor key + async deleteSecurityQuestion(mpcCoreKit: ICoreKit, deleteFactorKey = true) { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } @@ -184,7 +190,7 @@ export class TssSecurityQuestion { if (!tkey.manualSync) await tkey._syncShareMetadata(); } - async recoverFactor(mpcCoreKit: Web3AuthMPCCoreKit, answer: string): Promise { + async recoverFactor(mpcCoreKit: ICoreKit, answer: string): Promise { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } @@ -217,7 +223,7 @@ export class TssSecurityQuestion { return hash; } - getQuestion(mpcCoreKit: Web3AuthMPCCoreKit): string { + getQuestion(mpcCoreKit: ICoreKit): string { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } diff --git a/src/interfaces.ts b/src/interfaces.ts index 25c8f9d1..94fc8042 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -9,6 +9,7 @@ import type { UX_MODE_TYPE, WebAuthnExtraParams, } from "@toruslabs/customauth"; +import { BNString } from "@toruslabs/torus.js"; import { CustomChainConfig, SafeEventEmitterProvider } from "@web3auth/base"; import BN from "bn.js"; @@ -214,7 +215,7 @@ export interface ICoreKit { * associated metadata. * @param factorPub - The public key of the factor to delete. */ - deleteFactor(factorPub: TkeyPoint): Promise; + deleteFactor(factorPub: TkeyPoint, factorKey?: BNString): Promise; /** * Logs out the user, terminating the session. @@ -277,7 +278,7 @@ export interface Web3AuthOptions { * * @defaultValue `'local'` */ - storageKey?: "session" | "local"; + storageKey?: "session" | "local" | "mock"; /** * @defaultValue 86400 diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index c1b10331..00259483 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/member-ordering */ import { BNString, encrypt, getPubKeyPoint, Point as TkeyPoint, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey-mpc/common-types"; -import ThresholdKey, { CoreError } from "@tkey-mpc/core"; +import ThresholdKey, { CoreError, lagrangeInterpolation } from "@tkey-mpc/core"; import { TorusServiceProvider } from "@tkey-mpc/service-provider-torus"; import { ShareSerializationModule } from "@tkey-mpc/share-serialization"; import { TorusStorageLayer } from "@tkey-mpc/storage-layer-torus"; @@ -16,6 +16,7 @@ import { EthereumSigningProvider } from "@web3auth-mpc/ethereum-provider"; import BN from "bn.js"; import bowser from "bowser"; +// import { name, version } from "../package.json"; import { CURVE, DEFAULT_CHAIN_CONFIG, @@ -85,6 +86,11 @@ export class Web3AuthMPCCoreKit implements ICoreKit { private ready = false; constructor(options: Web3AuthOptions) { + // log.info("======================================================"); + // log.info(`WEB3AUTH SDK : ${name}:${version}`); + + // log.info("======================================================"); + if (!options.chainConfig) options.chainConfig = DEFAULT_CHAIN_CONFIG; if (options.chainConfig.chainNamespace !== CHAIN_NAMESPACES.EIP155) { throw new Error("You must specify a eip155 chain config."); @@ -183,7 +189,43 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return this.options.uxMode === UX_MODE.REDIRECT; } + // an option for user to recover/port out their account incase the oauth provider or storage/service provider is down + // not recommended developer to use this function in normal use case as it will reduce the secure element of the TssKey MPC + public async _UNSAFE_recoverTssKey(factorKey: string[]) { + this.checkReady(); + const factorKeyBN = new BN(factorKey[0], "hex"); + + const shareStore0 = await this.getFactorKeyMetadata(factorKeyBN); + await this.tKey.initialize({ withShare: shareStore0 }); + + this.tkey.privKey = new BN(factorKey[1], "hex"); + + const tssShares: BN[] = []; + const tssIndexes: number[] = []; + const tssIndexesBN: BN[] = []; + for (let i = 0; i < factorKey.length; i++) { + const factorKeyBNInput = new BN(factorKey[i], "hex"); + const { tssIndex, tssShare } = await this.tKey.getTSSShare(factorKeyBNInput); + if (tssIndexes.includes(tssIndex)) { + await this.init(); + throw new Error("Duplicate TSS Index"); + } + tssIndexes.push(tssIndex); + tssIndexesBN.push(new BN(tssIndex)); + tssShares.push(tssShare); + } + + const finalKey = lagrangeInterpolation(tssShares, tssIndexesBN); + await this.init(); + return finalKey.toString("hex"); + } + + // Initialize handle the routine of initializing the tkey, session and provider if required + // should reset instance state if called + // Should provide option to skip handleRedirectFlow and rehydrateSession public async init(params: InitParams = { handleRedirectResult: true }): Promise { + this.resetState(); + const nodeDetails = await this.nodeDetailManager.getNodeDetails({ verifier: "test-verifier", verifierId: "test@example.com" }); if (!nodeDetails) { @@ -240,7 +282,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { // if not redirect flow or session rehydration, ask for factor key to login } - public async loginWithOauth(params: OauthLoginParams): Promise { + // Login in with Oauth flow where it will popup / redirect to Oauth Login + // if importTssKey is provided during first time login, the key will be use as the tss key + public async loginWithOauth(params: OauthLoginParams, importTssKey?: string): Promise { this.checkReady(); const tkeyServiceProvider = this.tKey.serviceProvider as TorusServiceProvider; @@ -275,7 +319,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { }); } - await this.setupTkey(); + await this.setupTkey(importTssKey); } catch (err: unknown) { log.error("login error", err); if (err instanceof CoreError) { @@ -285,7 +329,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } - public async loginWithJWT(idTokenLoginParams: IdTokenLoginParams): Promise { + // Option for developer to login with JWT token where developer handle the oauth login flow themselve + // if importTssKey is provided during first time login, the key will be use as the tss key + public async loginWithJWT(idTokenLoginParams: IdTokenLoginParams, importTssKey?: string): Promise { this.checkReady(); const { verifier, verifierId, idToken } = idTokenLoginParams; @@ -325,7 +371,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData), }); - await this.setupTkey(); + await this.setupTkey(importTssKey); } catch (err: unknown) { log.error("login error", err); if (err instanceof CoreError) { @@ -335,6 +381,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } + // handle redirect result after return from the oauth redirect login + // should be public where developer can call this function after redirect on their terms. private async handleRedirectResult(): Promise { this.checkReady(); @@ -378,6 +426,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } + // Incase of the instance/ device do not have access to the device factor key, + // this function will allow developer to import the factor key (either device/backup) to the instance public async inputFactorKey(factorKey: BN): Promise { this.checkReady(); try { @@ -420,6 +470,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return this.tKey.getTSSPub(); } + // MPC core kit is defaulted with Oauth Factor and Hash Factor (which derived from the postboxkey) which equavalent of 1 out of 1 factor + // enableMFA will remove the Hash Factor and replace it with Device Factor (which random generated) and stored in device storage and backup factor (by default) + // This make the instance to be 2 out of 3 factors + // setting recoveryFactor to false will resultant 2 out of 2 factors as some developer want to control the flow public async enableMFA(enableMFAParams: EnableMFAParams, recoveryFactor = true): Promise { this.checkReady(); @@ -465,6 +519,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return factorPubsList.map((factorPub) => Point.fromTkeyPoint(factorPub).toBufferSEC1(true).toString("hex")); }; + // Create Factor will create a factor key that point to the tss share and a metadataKey device share. + // Very time the tss share is changed, the factor key will automatically point to the latest tss share. + // The metadataKey's share is required to reconsturct the metatdataKey which is required to update any changes of the metadata to storage layer public async createFactor(createFactorParams: CreateFactorParams): Promise { this.checkReady(); @@ -535,11 +592,12 @@ export class Web3AuthMPCCoreKit implements ICoreKit { if (!this.tKey.manualSync) await this.tKey._syncShareMetadata(); } + // logout will invalidate the session and reset the instance state public async logout(): Promise { - if (!this.sessionManager.sessionId) { - throw new Error("User is not logged in."); + if (this.sessionManager.sessionId) { + // throw new Error("User is not logged in."); + await this.sessionManager.invalidateSession(); } - await this.sessionManager.invalidateSession(); this.currentStorage.set("sessionId", ""); this.resetState(); await this.init(); @@ -570,6 +628,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return keyDetails; } + // In case of manualSync mode, any operation that changed the metadata ( like createFactor, deleteFactor and etc ) will not be sync to storage layer. + // This function will sync the metadata to storage layer public async commitChanges(): Promise { this.checkReady(); if (!this.state.factorKey) throw new Error("factorKey not present"); @@ -594,15 +654,17 @@ export class Web3AuthMPCCoreKit implements ICoreKit { this.tKey.manualSync = manualSync; } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // allow user to import their key as the tss key for mpc + // not recommended developer to use this function in normal use case as it will reduce the secure element of the TssKey MPC private async importTssKey(tssKey: string, factorPub: TkeyPoint, newTSSIndex: TssShareType = TssShareType.DEVICE): Promise { if (!this.state.signatures) throw new Error("signatures not present"); const tssKeyBN = new BN(tssKey, "hex"); - this.tKey.importTssKey({ tag: this.tKey.tssTag, importKey: tssKeyBN, factorPub, newTSSIndex }, { authSignatures: this.state.signatures }); + await this.tKey.importTssKey({ tag: this.tKey.tssTag, importKey: tssKeyBN, factorPub, newTSSIndex }, { authSignatures: this.state.signatures }); } + // an option for user to port out + // not recommended developer to use this function in normal use case as it will reduce the secure element of the TssKey MPC public async _UNSAFE_exportTssKey(): Promise { if (!this.state.factorKey) throw new Error("factorKey not present"); if (!this.state.signatures) throw new Error("signatures not present"); @@ -622,12 +684,11 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return tssNonce; } - private async setupTkey(): Promise { + private async setupTkey(importTssKey?: string): Promise { if (!this.state.oAuthKey) { throw new Error("user not logged in"); } const existingUser = await this.isMetadataPresent(this.state.oAuthKey); - if (!existingUser) { // Generate or use hash factor and initialize tkey with it. let factorKey: BN; @@ -639,10 +700,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } else { factorKey = getHashedPrivateKey(this.state.oAuthKey, this.options.web3AuthClientId); } - const deviceTSSShare = new BN(generatePrivate()); const deviceTSSIndex = TssShareType.DEVICE; const factorPub = getPubKeyPoint(factorKey); - await this.tKey.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + if (!importTssKey) { + const deviceTSSShare = new BN(generatePrivate()); + await this.tKey.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + } else { + await this.tKey.initialize(); + await this.importTssKey(importTssKey, factorPub, deviceTSSIndex); + } // Finalize initialization. await this.tKey.reconstructKey(); @@ -656,6 +722,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.HashedShare); } } else { + if (importTssKey) throw new Error("Cannot import tss key for existing user"); await this.tKey.initialize({ neverInitializeNewKey: true }); const hashedFactorKey = getHashedPrivateKey(this.state.oAuthKey, this.options.web3AuthClientId); if ((await this.checkIfFactorKeyValid(hashedFactorKey)) && !this.options.disableHashedFactorKey) { @@ -854,6 +921,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { if (!this.tkey?.manualSync) await this.tkey?.syncLocalMetadataTransitions(); } + // backup only the share store as other data will be available in the metadata once tkey is reconstructed private async backupMetadataShare(factorKey: BN) { const metadataShare = await this.getMetadataShare(); @@ -883,6 +951,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { await this.tKey?.addShareDescription(factorPub, JSON.stringify(params), updateMetadata); } + // we should make the provider to be more generalised private async setupProvider(): Promise { const signingProvider = new EthereumSigningProvider({ config: { chainConfig: this.options.chainConfig } }); let { tssShareIndex, tssPubKey } = this.state; diff --git a/src/point.ts b/src/point.ts index 6c1689c0..b25af064 100644 --- a/src/point.ts +++ b/src/point.ts @@ -1,4 +1,5 @@ import { Point as TkeyPoint } from "@tkey-mpc/common-types"; +import { BNString } from "@toruslabs/torus.js"; import BN from "bn.js"; import { curve } from "elliptic"; @@ -21,6 +22,11 @@ export class Point { this.p = p; } + public static fromPrivateKey(privateKey: BNString): Point { + const ep = CURVE.keyFromPrivate(privateKey.toString("hex")).getPublic(); + return new Point(ep); + } + /** * Creates a new Point from a TKey Point. * @param p - The TKey Point. diff --git a/src/utils.ts b/src/utils.ts index 5f6228b0..f59e862d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -59,7 +59,7 @@ export function storageAvailable(type: string): boolean { export function parseToken(token: string) { const base64Url = token.split(".")[1]; const base64 = base64Url.replace("-", "+").replace("_", "/"); - return JSON.parse(window.atob(base64 || "")); + return JSON.parse(atob(base64 || "")); } /** diff --git a/tests/factors.spec.ts b/tests/factors.spec.ts new file mode 100644 index 00000000..78d4771e --- /dev/null +++ b/tests/factors.spec.ts @@ -0,0 +1,141 @@ +/* eslint-disable mocha/handle-done-callback */ +import assert from "node:assert"; +import test from "node:test"; + +import { UX_MODE } from "@toruslabs/customauth"; +import BN from "bn.js"; + +import { COREKIT_STATUS, getWebBrowserFactor, Point, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src"; +import { criticalResetAccount, mockLogin } from "./setup"; + +type FactorTestVariable = { + types: TssShareType; + manualSync?: boolean; +}; + +// const { types } = factor; +export const FactorManipulationTest = async (newInstance: () => Promise, testVariable: FactorTestVariable) => { + test(`#Factor manipulation - ${testVariable.types} `, async function (t) { + await t.before(async function () { + const coreKitInstance = await newInstance(); + await criticalResetAccount(coreKitInstance); + await coreKitInstance.logout(); + }); + + await t.test("should able to create factor", async function () { + const coreKitInstance = await newInstance(); + const firstFactor = coreKitInstance.getCurrentFactorKey(); + // try delete hash factor factor + try { + const pt = Point.fromPrivateKey(firstFactor.factorKey); + await coreKitInstance.deleteFactor(pt.toTkeyPoint()); + throw new Error("should not reach here"); + } catch {} + + // create factor + const factorKey1 = await coreKitInstance.createFactor({ + shareType: TssShareType.DEVICE, + }); + + const factorKey2 = await coreKitInstance.createFactor({ + shareType: TssShareType.RECOVERY, + }); + + // sync + if (testVariable.manualSync) { + await coreKitInstance.commitChanges(); + } + // clear session prevent rehydration + await coreKitInstance.logout(); + + // new instance + const instance2 = await newInstance(); + assert.strictEqual(instance2.getTssFactorPub().length, 3); + + // try inputFactor ( set as active factor ) + + // delete factor + const pt = Point.fromPrivateKey(factorKey1); + await instance2.deleteFactor(pt.toTkeyPoint()); + + // delete factor + const pt2 = Point.fromPrivateKey(factorKey2); + await instance2.deleteFactor(pt2.toTkeyPoint()); + + if (testVariable.manualSync) { + await instance2.commitChanges(); + } + + // new instance + const instance3 = await newInstance(); + assert.strictEqual(instance3.getTssFactorPub().length, 1); + }); + + // enable mfa + + await t.test("enable MFA", async function () { + const instance = await newInstance(); + const recoverFactor = await instance.enableMFA({}); + + const browserFactor = await getWebBrowserFactor(instance, "mock"); + + if (testVariable.manualSync) { + await instance.commitChanges(); + } + + // to prevent rehydration ( rehydrate session id store in BrowserStorage) + await instance.logout(); + + // new instance + const instance2 = await newInstance(); + // try { + // checkLogin(instance2); + // } + + // login with mfa factor + await instance2.inputFactorKey(new BN(recoverFactor, "hex")); + assert.strictEqual(instance2.status, COREKIT_STATUS.LOGGED_IN); + await instance2.logout(); + + // new instance + const instance3 = await newInstance(); + assert.strictEqual(instance3.status, COREKIT_STATUS.REQUIRED_SHARE); + + await instance3.inputFactorKey(new BN(browserFactor, "hex")); + assert.strictEqual(instance3.status, COREKIT_STATUS.LOGGED_IN); + }); + }); +}; + +const variable: FactorTestVariable[] = [ + { types: TssShareType.DEVICE, manualSync: true }, + { types: TssShareType.RECOVERY, manualSync: true }, +]; + +const email = "testmail99"; +variable.forEach(async (testVariable) => { + const newCoreKitLogInInstance = async () => { + const instance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + baseUrl: "http://localhost:3000", + uxMode: UX_MODE.REDIRECT, + storageKey: "mock", + manualSync: testVariable.manualSync, + }); + + const { idToken, parsedToken } = await mockLogin(email); + await instance.init(); + try { + await instance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + } catch (error) {} + + return instance; + }; + + await FactorManipulationTest(newCoreKitLogInInstance, testVariable); +}); diff --git a/tests/importRecovery.spec.ts b/tests/importRecovery.spec.ts new file mode 100644 index 00000000..0987edf3 --- /dev/null +++ b/tests/importRecovery.spec.ts @@ -0,0 +1,103 @@ +/* eslint-disable mocha/handle-done-callback */ +import assert from "node:assert"; +import test from "node:test"; + +import { UX_MODE } from "@toruslabs/customauth"; +import { log } from "@web3auth/base"; + +import { IdTokenLoginParams, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src"; +import { criticalResetAccount, mockLogin, newCoreKitLogInInstance } from "./setup"; + +type ImportKeyTestVariable = { + manualSync?: boolean; + email: string; + importKeyEmail: string; +}; +export const ImportTest = async (testVariable: ImportKeyTestVariable) => { + test(`import recover tss key : ${testVariable.manualSync}`, async function (t) { + t.before(async () => { + const instance = await newCoreKitLogInInstance({ + network: WEB3AUTH_NETWORK.DEVNET, + manualSync: testVariable.manualSync, + email: testVariable.email, + }); + await criticalResetAccount(instance); + await instance.logout(); + }); + + await t.test("#recover Tss key using 2 factors key, import tss key to new oauth login", async function () { + const { idToken, parsedToken } = await mockLogin(testVariable.email); + + const idTokenLoginParams = { + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + } as IdTokenLoginParams; + + const coreKitInstance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + baseUrl: "http://localhost:3000", + uxMode: UX_MODE.REDIRECT, + storageKey: "mock", + manualSync: testVariable.manualSync, + }); + + await coreKitInstance.init(); + await coreKitInstance.loginWithJWT(idTokenLoginParams); + + const factorKeyDevice = await coreKitInstance.createFactor({ + shareType: TssShareType.DEVICE, + }); + + const factorKeyRecovery = await coreKitInstance.createFactor({ + shareType: TssShareType.RECOVERY, + }); + + // recover key + // reinitalize corekit + await coreKitInstance.logout(); + const recoveredTssKey = await coreKitInstance._UNSAFE_recoverTssKey([factorKeyDevice, factorKeyRecovery]); + + await criticalResetAccount(coreKitInstance); + // reinitialize corekit + const newEmail = testVariable.importKeyEmail; + const newLogin = await mockLogin(newEmail); + + const newIdTokenLoginParams = { + verifier: "torus-test-health", + verifierId: newLogin.parsedToken.email, + idToken: newLogin.idToken, + } as IdTokenLoginParams; + + const coreKitInstance2 = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + baseUrl: "http://localhost:3000", + uxMode: UX_MODE.REDIRECT, + storageKey: "mock", + }); + + await coreKitInstance2.init(); + await coreKitInstance2.loginWithJWT(newIdTokenLoginParams, recoveredTssKey); + + const exportedTssKey = await coreKitInstance2._UNSAFE_exportTssKey(); + criticalResetAccount(coreKitInstance2); + + assert.strictEqual(exportedTssKey, recoveredTssKey); + }); + + t.afterEach(function () { + return log.info("finished running recovery test"); + }); + t.after(function () { + return log.info("finished running recovery tests"); + }); + }); +}; + +const variable: ImportKeyTestVariable[] = [{ manualSync: false, email: "emailexport", importKeyEmail: "emailimport" }]; + +variable.forEach(async (testVariable) => { + await ImportTest(testVariable); +}); diff --git a/tests/login.spec.ts b/tests/login.spec.ts new file mode 100644 index 00000000..54c21b8e --- /dev/null +++ b/tests/login.spec.ts @@ -0,0 +1,107 @@ +/* eslint-disable mocha/handle-done-callback */ +import assert from "node:assert"; +import test from "node:test"; + +import { UX_MODE, UX_MODE_TYPE } from "@toruslabs/customauth"; +import BN from "bn.js"; + +import { COREKIT_STATUS, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src"; +import { criticalResetAccount, mockLogin } from "./setup"; + +type TestVariable = { + web3AuthNetwork: WEB3AUTH_NETWORK_TYPE; + uxMode: UX_MODE_TYPE; + manualSync?: boolean; + + email: string; +}; + +const defaultTestEmail = "testEmail1"; +const variable: TestVariable[] = [ + { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail }, + // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail }, + + { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail }, + // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail }, +]; + +const checkLogin = async (coreKitInstance: Web3AuthMPCCoreKit) => { + const keyDetails = coreKitInstance.getKeyDetails(); + assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN); + assert.strictEqual(keyDetails.requiredFactors, 0); + const factorkey = coreKitInstance.getCurrentFactorKey(); + await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex")); +}; + +variable.forEach((testVariable) => { + const { web3AuthNetwork, uxMode, manualSync, email } = testVariable; + const coreKitInstance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork, + baseUrl: "http://localhost:3000", + uxMode, + storageKey: "mock", + manualSync, + }); + + const testNameSuffix = JSON.stringify(testVariable); + test(`#Login Test with JWT + logout : ${testNameSuffix}`, async (t) => { + t.before(async function () { + if (coreKitInstance.status === COREKIT_STATUS.INITIALIZED) await criticalResetAccount(coreKitInstance); + }); + + t.after(async function () { + // after all test tear down + }); + + // t.skip("#Login with Oauth", async function () { + // // popup + // // redirect flow + // // not testable + // }); + await t.test("#Login ", async function () { + // mocklogin + const { idToken, parsedToken } = await mockLogin(email); + await coreKitInstance.init(); + await coreKitInstance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + + // get key details + await checkLogin(coreKitInstance); + }); + + await t.test("#relogin ", async function () { + // reload without rehydrate + // await coreKitInstance.init({ rehydrate: false }); + + // rehydrate + await coreKitInstance.init(); + await checkLogin(coreKitInstance); + + // logout + await coreKitInstance.logout(); + + // rehydrate should fail + await coreKitInstance.init(); + assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.INITIALIZED); + try { + coreKitInstance.getCurrentFactorKey(); + throw new Error("should not reach here"); + } catch (error) {} + + // relogin + const { idToken, parsedToken } = await mockLogin(email); + await coreKitInstance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + + // get key details + await checkLogin(coreKitInstance); + }); + }); +}); diff --git a/tests/securityQuestion.spec.ts b/tests/securityQuestion.spec.ts new file mode 100644 index 00000000..87fdd00d --- /dev/null +++ b/tests/securityQuestion.spec.ts @@ -0,0 +1,133 @@ +/* eslint-disable mocha/handle-done-callback */ +/* eslint-disable no-console */ +import assert from "node:assert"; +import test from "node:test"; + +// import { getPubKeyPoint } from "@tkey-mpc/common-types"; +import { UX_MODE, UX_MODE_TYPE } from "@toruslabs/customauth"; +import BN from "bn.js"; + +import { COREKIT_STATUS, TssSecurityQuestion, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src"; +import { criticalResetAccount, mockLogin } from "./setup"; + +type TestVariable = { + web3AuthNetwork: WEB3AUTH_NETWORK_TYPE; + uxMode: UX_MODE_TYPE; + manualSync?: boolean; +}; + +export const TssSecurityQuestionsTest = async (newInstance: () => Promise, testVariable: TestVariable) => { + test(`#Tss Security Question - ${testVariable.manualSync} `, async function (t) { + await t.before(async function () { + const coreKitInstance = await newInstance(); + if (coreKitInstance.status === COREKIT_STATUS.REQUIRED_SHARE) await criticalResetAccount(coreKitInstance); + await coreKitInstance.logout(); + }); + t.afterEach(function () { + return console.log("finished running test"); + }); + t.after(function () { + return console.log("finished running tests"); + }); + + await t.test("should work", async function () { + // set security question + const instance = await newInstance(); + const question = "test question"; + const answer = "test answer"; + const newQuestion = "new question"; + const newAnswer = "new answer"; + // const shareType = TssShareType.DEVICE; + + const securityQuestion = new TssSecurityQuestion(); + await securityQuestion.setSecurityQuestion({ + mpcCoreKit: instance, + question, + answer, + // shareType, + }); + + // recover factor + const factor = await securityQuestion.recoverFactor(instance, answer); + // check factor + await instance.tKey.getTSSShare(new BN(factor, "hex")); + // check wrong answer + try { + await securityQuestion.recoverFactor(instance, "wrong answer"); + throw new Error("should not reach here"); + } catch {} + + // change factor + await securityQuestion.changeSecurityQuestion({ + mpcCoreKit: instance, + newQuestion, + newAnswer, + answer, + }); + // recover factor + // check factor + const newFactor = await securityQuestion.recoverFactor(instance, newAnswer); + await instance.tKey.getTSSShare(new BN(newFactor, "hex")); + + try { + await instance.tKey.getTSSShare(new BN(factor, "hex")); + throw new Error("should not reach here"); + } catch {} + + // recover factor + // check wrong answer + try { + await securityQuestion.recoverFactor(instance, answer); + throw new Error("should not reach here"); + } catch {} + + // delete factor + await securityQuestion.deleteSecurityQuestion(instance); + // recover factor + try { + await securityQuestion.recoverFactor(instance, answer); + throw new Error("should not reach here"); + } catch {} + + // input factor + assert.strictEqual(true, true); + }); + }); +}; + +const variable: TestVariable[] = [ + // { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: UX_MODE.REDIRECT }, + // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT }, + + { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: UX_MODE.REDIRECT, manualSync: true }, + // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true }, +]; + +const email = "testmail99"; + +variable.forEach(async (testVariable) => { + const newCoreKitLogInInstance = async () => { + const instance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + baseUrl: "http://localhost:3000", + uxMode: UX_MODE.REDIRECT, + storageKey: "mock", + manualSync: testVariable.manualSync, + }); + + const { idToken, parsedToken } = await mockLogin(email); + await instance.init(); + try { + await instance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + } catch (error) {} + + return instance; + }; + + await TssSecurityQuestionsTest(newCoreKitLogInInstance, testVariable); +}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 00000000..96666471 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,98 @@ +import { UX_MODE } from "@toruslabs/customauth"; +import BN from "bn.js"; +import jwt, { Algorithm } from "jsonwebtoken"; + +import { parseToken, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src"; +if (global.navigator === undefined) { + const nav = global.navigator; + global.navigator = { ...nav, userAgent: "test" }; +} +global.window = undefined; + +export const mockLogin2 = async (email: string) => { + const req = new Request("https://li6lnimoyrwgn2iuqtgdwlrwvq0upwtr.lambda-url.eu-west-1.on.aws/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ verifier: "torus-key-test", scope: "email", extraPayload: { email }, alg: "ES256" }), + }); + + const resp = await fetch(req); + const bodyJson = (await resp.json()) as { token: string }; + const idToken = bodyJson.token; + const parsedToken = parseToken(idToken); + return { idToken, parsedToken }; +}; + +export const criticalResetAccount = async (coreKitInstance: Web3AuthMPCCoreKit): Promise => { + // This is a critical function that should only be used for testing purposes + // Resetting your account means clearing all the metadata associated with it from the metadata server + // The key details will be deleted from our server and you will not be able to recover your account + if (!coreKitInstance) { + throw new Error("coreKitInstance is not set"); + } + + await coreKitInstance.tKey.storageLayer.setMetadata({ + privKey: new BN(coreKitInstance.metadataKey!, "hex"), + input: { message: "KEY_NOT_FOUND" }, + }); +}; + +const privateKey = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCD7oLrcKae+jVZPGx52Cb/lKhdKxpXjl9eGNa1MlY57A=="; +const jwtPrivateKey = `-----BEGIN PRIVATE KEY-----\n${privateKey}\n-----END PRIVATE KEY-----`; +const alg: Algorithm = "ES256"; + +export const mockLogin = async (email: string) => { + const iat = Math.floor(Date.now() / 1000); + const payload = { + iss: "torus-key-test", + aud: "torus-key-test", + name: email, + email, + scope: "email", + iat, + eat: iat + 120, + }; + + const algo = { + expiresIn: 120, + algorithm: alg, + }; + + const token = jwt.sign(payload, jwtPrivateKey, algo); + const idToken = token; + const parsedToken = parseToken(idToken); + return { idToken, parsedToken }; +}; + +export const newCoreKitLogInInstance = async ({ + network, + manualSync, + email, +}: { + network: WEB3AUTH_NETWORK_TYPE; + manualSync: boolean; + email: string; +}) => { + const instance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: network, + baseUrl: "http://localhost:3000", + uxMode: UX_MODE.REDIRECT, + storageKey: "mock", + manualSync, + }); + + const { idToken, parsedToken } = await mockLogin(email); + await instance.init(); + try { + await instance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + } catch (error) {} + + return instance; +}; diff --git a/tests/signing.spec.ts b/tests/signing.spec.ts new file mode 100644 index 00000000..e69de29b