diff --git a/.gitignore b/.gitignore index a1b106c..e4303c8 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,5 @@ dist .env.test **/*.DS_Store + +.npmrc diff --git a/package-lock.json b/package-lock.json index f3b537f..f663d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@toruslabs/torus.js", - "version": "17.2.2", + "version": "17.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@toruslabs/torus.js", - "version": "17.2.2", + "version": "17.2.3", "license": "MIT", "dependencies": { "@toruslabs/constants": "^16.1.1", diff --git a/package.json b/package.json index 92e4dc7..2ca0a71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@toruslabs/torus.js", - "version": "17.2.2", + "version": "17.2.3", "description": "Handle communication with torus nodes", "main": "dist/lib.cjs/index.js", "module": "dist/lib.esm/index.js", diff --git a/src/helpers/citadelUtils.ts b/src/helpers/citadelUtils.ts index 9796bf2..d431084 100644 --- a/src/helpers/citadelUtils.ts +++ b/src/helpers/citadelUtils.ts @@ -1,5 +1,21 @@ -import { BUILD_ENV_TYPE, CITADEL_SERVER_MAP } from "@toruslabs/constants"; -import { get } from "@toruslabs/http-helpers"; +import { BUILD_ENV_TYPE, CITADEL_SERVER_MAP, TORUS_NETWORK_TYPE } from "@toruslabs/constants"; +import { get, put } from "@toruslabs/http-helpers"; + +import { RetrieveSharesParams } from "../interfaces"; +import { isNullOrUndefined } from "./common"; + +export enum CitadelAllowParamsSetOrUnsetFlag { + SET = 1, + UNSET = 0, +} + +export interface CitadelAuthFlowAuditParams { + oauthInitiated?: boolean; + oauthVerified?: boolean; + oauthCompleted?: boolean; + oauthVerificationFailed?: boolean; + oauthFailed?: boolean; +} export interface CitadelAllowParams { buildEnv: BUILD_ENV_TYPE; @@ -9,9 +25,22 @@ export interface CitadelAllowParams { clientId: string; recordId: string; source?: string; - torusLoginInitiated?: boolean; - torusLoginSuccess?: boolean; - torusLoginFailed?: boolean; + // flags for auditing the auth flow + oauthInitiated?: CitadelAllowParamsSetOrUnsetFlag; + oauthVerified?: CitadelAllowParamsSetOrUnsetFlag; + oauthCompleted?: CitadelAllowParamsSetOrUnsetFlag; + oauthVerificationFailed?: CitadelAllowParamsSetOrUnsetFlag; + oauthFailed?: CitadelAllowParamsSetOrUnsetFlag; +} + +export interface CitadelAuditParams extends CitadelAuthFlowAuditParams { + recordId: string; + authConnection: string; + authConnectionId: string; + groupedAuthConnectionId: string; + oAuthUserId: string; + web3AuthNetwork: string; + web3AuthClientId: string; } export function buildAllowUrl(params: CitadelAllowParams): string { @@ -24,22 +53,55 @@ export function buildAllowUrl(params: CitadelAllowParams): string { if (params.source) { url.searchParams.set("source", params.source); } - if (typeof params.torusLoginInitiated !== "undefined") { - url.searchParams.set("toruslogininitiated", params.torusLoginInitiated.toString()); + if (!isNullOrUndefined(params.oauthInitiated)) { + url.searchParams.set("oauthInitiated", params.oauthInitiated.toString()); } - if (typeof params.torusLoginSuccess !== "undefined") { - url.searchParams.set("torusloginsuccess", params.torusLoginSuccess.toString()); + if (!isNullOrUndefined(params.oauthVerified)) { + url.searchParams.set("oauthVerified", params.oauthVerified.toString()); } - if (typeof params.torusLoginFailed !== "undefined") { - url.searchParams.set("torusloginfailed", params.torusLoginFailed.toString()); + if (!isNullOrUndefined(params.oauthCompleted)) { + url.searchParams.set("oauthCompleted", params.oauthCompleted.toString()); + } + if (!isNullOrUndefined(params.oauthVerificationFailed)) { + url.searchParams.set("oauthVerificationFailed", params.oauthVerificationFailed.toString()); + } + if (!isNullOrUndefined(params.oauthFailed)) { + url.searchParams.set("oauthFailed", params.oauthFailed.toString()); } return url.toString(); } +export function buildAuditPayload( + network: TORUS_NETWORK_TYPE, + clientId: string, + params: RetrieveSharesParams, + authFlowAuditParams: CitadelAuthFlowAuditParams +): CitadelAuditParams { + if (!params.recordId) { + params.recordId = generateRecordId(); + } + + return { + ...authFlowAuditParams, + recordId: params.recordId, + authConnection: params.authConnection || "", + authConnectionId: params.verifierParams.sub_verifier_ids?.[0] || "", + groupedAuthConnectionId: params.verifier || "", + oAuthUserId: params.verifierParams.verifier_id || "", + web3AuthNetwork: network, + web3AuthClientId: clientId, + }; +} + export async function callAllowApi(params: CitadelAllowParams): Promise { await get(buildAllowUrl(params)); } +export async function callAuditApi(buildEnv: BUILD_ENV_TYPE, params: CitadelAuditParams): Promise { + const url = new URL(`${CITADEL_SERVER_MAP[buildEnv]}/v1/auth/audit`); + await put(url.toString(), params); +} + export function generateRecordId(): string { const cr = typeof globalThis === "object" ? globalThis.crypto : null; if (typeof cr?.randomUUID !== "function") throw new Error("crypto.randomUUID must be defined"); diff --git a/src/helpers/common.ts b/src/helpers/common.ts index f41c8cf..ce4a68b 100644 --- a/src/helpers/common.ts +++ b/src/helpers/common.ts @@ -161,3 +161,7 @@ export function retryCommitment(executionPromise: () => Promise; @@ -283,6 +284,11 @@ export interface ImportKeyParams { newPrivateKey: string; extraParams?: TorusUtilsExtraParams; checkCommitment?: boolean; + + /** + * Optional recordId to used for the analytics tracking. + */ + recordId?: string; } export interface RetrieveSharesParams { @@ -295,4 +301,15 @@ export interface RetrieveSharesParams { extraParams?: TorusUtilsExtraParams; useDkg?: boolean; checkCommitment?: boolean; + + /** + * User social login provider name. + * This is used for the analytics tracking. + */ + authConnection?: string; + + /** + * Optional recordId to used for the analytics tracking. + */ + recordId?: string; } diff --git a/src/torus.ts b/src/torus.ts index c368b8e..931c4f5 100644 --- a/src/torus.ts +++ b/src/torus.ts @@ -4,9 +4,13 @@ import { setAPIKey, setEmbedHost } from "@toruslabs/http-helpers"; import { config } from "./config"; import { bigintToHex, + buildAuditPayload, bytesToHex, callAllowApi, + callAuditApi, CitadelAllowParams, + CitadelAllowParamsSetOrUnsetFlag, + CitadelAuthFlowAuditParams, Curve, encodeEd25519Point, generateAddressFromPubKey, @@ -150,6 +154,8 @@ class Torus { extraParams.session_token_exp_second = Torus.sessionTime; } + const recordId = params.recordId || generateRecordId(); + const allowParams = { buildEnv: this.buildEnv, verifier, @@ -157,10 +163,17 @@ class Torus { network: this.network, clientId: this.clientId, source: this.source, - recordId: generateRecordId(), + recordId, + }; + + // for auditing the auth flow + const auditParams: CitadelAuthFlowAuditParams = { + // at this point, user has completed the oauth login + oauthCompleted: true, }; let result: TorusKey; + try { result = await retrieveOrImportShare({ recordId: allowParams.recordId, @@ -186,11 +199,23 @@ class Torus { source: this.source, }); } catch (error) { - this.reportSignerAllow({ ...allowParams, torusLoginFailed: true }); + if (params.recordId) { + // report oauth verification failed, we won't await this call as it's only for analytics tracking + auditParams.oauthVerificationFailed = true; + this.reportUserAuthFlowAudit({ ...params, recordId }, auditParams); + } else { + this.reportSignerAllow({ ...allowParams, oauthVerificationFailed: CitadelAllowParamsSetOrUnsetFlag.SET }); + } throw error; } - this.reportSignerAllow({ ...allowParams, torusLoginSuccess: true }); + if (!params.recordId) { + this.reportSignerAllow({ ...allowParams, oauthVerified: CitadelAllowParamsSetOrUnsetFlag.SET }); + } else { + // report oauth verified, we won't await this call as it's only for analytics tracking + auditParams.oauthVerified = true; + this.reportUserAuthFlowAudit({ ...params, recordId }, auditParams); + } return result; } @@ -202,6 +227,21 @@ class Torus { } } + /** + * Report user auth flow audit to the citadel server. + * @param recordId - The record id to be used for the analytics tracking. + * @param params - The parameters for the retrieve shares operation. + * @param authStepStatus - The status of the authentication steps. + */ + async reportUserAuthFlowAudit(params: RetrieveSharesParams, authFlowAuditParams: CitadelAuthFlowAuditParams): Promise { + try { + const auditParams = buildAuditPayload(this.network, this.clientId, params, authFlowAuditParams); + await callAuditApi(this.buildEnv, auditParams); + } catch (error) { + log.error("Failed to log user auth flow audit", error); + } + } + async getPublicAddress( endpoints: string[], torusNodePubs: INodePub[], @@ -262,8 +302,10 @@ class Torus { } } + const recordId = params.recordId || generateRecordId(); + return retrieveOrImportShare({ - recordId: generateRecordId(), + recordId, legacyMetadataHost: this.legacyMetadataHost, serverTimeOffset: this.serverTimeOffset, enableOneKey: this.enableOneKey,