@toruslabs/customauth
Version:
CustomAuth login with torus to get user private key
370 lines (367 loc) • 11.9 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { NodeDetailManager } from '@toruslabs/fetch-node-details';
import { Torus, keccak256 } from '@toruslabs/torus.js';
import { createHandler } from './handlers/HandlerFactory.js';
import { registerServiceWorker } from './registerServiceWorker.js';
import SentryHandler from './sentry.js';
import { UX_MODE, SENTRY_TXNS } from './utils/enums.js';
import { serializeError } from './utils/error.js';
import { padUrlString, isFirefox, handleRedirectParameters } from './utils/helpers.js';
import log from './utils/loglevel.js';
import { StorageHelper } from './utils/StorageHelper.js';
const _excluded = ["access_token", "id_token", "tgAuthResult"],
_excluded2 = ["args"];
class CustomAuth {
constructor({
baseUrl,
network,
enableLogging = false,
redirectToOpener = false,
redirectPathName = "redirect",
apiKey = "torus-default",
uxMode = UX_MODE.POPUP,
locationReplaceOnRedirect = false,
popupFeatures,
storageServerUrl = "https://session.web3auth.io",
sentry,
enableOneKey = false,
web3AuthClientId,
useDkg,
metadataUrl = "https://metadata.tor.us",
keyType = "secp256k1",
serverTimeOffset = 0,
nodeDetails,
checkCommitment = true
}) {
_defineProperty(this, "isInitialized", void 0);
_defineProperty(this, "config", void 0);
_defineProperty(this, "torus", void 0);
_defineProperty(this, "nodeDetailManager", void 0);
_defineProperty(this, "storageHelper", void 0);
_defineProperty(this, "sentryHandler", void 0);
if (!web3AuthClientId) throw new Error("Please provide a valid web3AuthClientId in constructor");
if (!network) throw new Error("Please provide a valid network in constructor");
this.isInitialized = false;
const baseUri = new URL(baseUrl);
this.config = {
baseUrl: padUrlString(baseUri),
get redirect_uri() {
return `${this.baseUrl}${redirectPathName}`;
},
redirectToOpener,
uxMode,
locationReplaceOnRedirect,
popupFeatures,
useDkg,
web3AuthClientId,
web3AuthNetwork: network,
keyType,
nodeDetails,
checkCommitment
};
const torus = new Torus({
network,
enableOneKey,
serverTimeOffset,
clientId: web3AuthClientId,
legacyMetadataHost: metadataUrl,
keyType
});
Torus.setAPIKey(apiKey);
this.torus = torus;
this.nodeDetailManager = new NodeDetailManager({
network
});
if (enableLogging) log.enableAll();else log.disableAll();
this.storageHelper = new StorageHelper(storageServerUrl);
this.sentryHandler = new SentryHandler(sentry);
}
async init({
skipSw = false,
skipInit = false,
skipPrefetch = false
} = {}) {
this.storageHelper.init();
if (skipInit) {
this.isInitialized = true;
return;
}
if (!skipSw) {
const fetchSwResponse = await fetch(`${this.config.baseUrl}sw.js`, {
cache: "reload"
});
if (fetchSwResponse.ok) {
try {
await registerServiceWorker(this.config.baseUrl);
this.isInitialized = true;
return;
} catch (error) {
log.warn(error);
}
} else {
throw new Error("Service worker is not being served. Please serve it");
}
}
if (!skipPrefetch) {
// Skip the redirect check for firefox
if (isFirefox()) {
this.isInitialized = true;
return;
}
await this.handlePrefetchRedirectUri();
return;
}
this.isInitialized = true;
}
async triggerLogin(args) {
const {
authConnectionId,
authConnection,
clientId,
jwtParams,
hash,
queryParameters,
customState,
groupedAuthConnectionId
} = args;
if (!this.isInitialized) {
throw new Error("Not initialized yet");
}
const loginHandler = createHandler({
authConnection,
clientId,
authConnectionId,
groupedAuthConnectionId,
redirect_uri: this.config.redirect_uri,
redirectToOpener: this.config.redirectToOpener,
jwtParams,
uxMode: this.config.uxMode,
customState,
web3AuthClientId: this.config.web3AuthClientId,
web3AuthNetwork: this.config.web3AuthNetwork
});
let loginParams;
if (hash && queryParameters) {
const {
error,
hashParameters,
instanceParameters
} = handleRedirectParameters(hash, queryParameters);
if (error) throw new Error(error);
const {
access_token: accessToken,
id_token: idToken,
tgAuthResult
} = hashParameters,
rest = _objectWithoutProperties(hashParameters, _excluded);
// State has to be last here otherwise it will be overwritten
loginParams = _objectSpread(_objectSpread({
accessToken,
idToken: idToken || tgAuthResult || ""
}, rest), {}, {
state: instanceParameters
});
} else {
this.storageHelper.clearOrphanedData(`torus_login_`);
if (this.config.uxMode === UX_MODE.REDIRECT) {
await this.storageHelper.storeData(`torus_login_${loginHandler.nonce}`, {
args
});
}
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 torusKey = await this.getTorusKey({
authConnectionId,
userId: userInfo.userId,
idToken: loginParams.idToken || loginParams.accessToken,
additionalParams: userInfo.extraConnectionParams,
groupedAuthConnectionId
});
return _objectSpread(_objectSpread({}, torusKey), {}, {
userInfo: _objectSpread(_objectSpread({}, userInfo), loginParams)
});
}
async getTorusKey(params) {
const {
authConnectionId,
userId,
idToken,
additionalParams,
groupedAuthConnectionId
} = params;
const verifier = groupedAuthConnectionId || authConnectionId;
const verifierId = userId;
const verifierParams = {
verifier_id: userId
};
let aggregateIdToken = "";
const finalIdToken = idToken;
if (groupedAuthConnectionId) {
verifierParams["verify_params"] = [{
verifier_id: userId,
idtoken: finalIdToken
}];
verifierParams["sub_verifier_ids"] = [authConnectionId];
aggregateIdToken = keccak256(Buffer.from(finalIdToken, "utf8")).slice(2);
}
const nodeDetails = await this.sentryHandler.startSpan({
name: SENTRY_TXNS.FETCH_NODE_DETAILS
}, async () => {
if (this.config.nodeDetails) {
return this.config.nodeDetails;
}
return this.nodeDetailManager.getNodeDetails({
verifier,
verifierId
});
});
log.debug("torus-direct/getTorusKey", {
torusNodeEndpoints: nodeDetails.torusNodeEndpoints
});
const sharesResponse = await this.sentryHandler.startSpan({
name: SENTRY_TXNS.FETCH_SHARES
}, async () => {
return this.torus.retrieveShares({
endpoints: nodeDetails.torusNodeEndpoints,
indexes: nodeDetails.torusIndexes,
verifier,
verifierParams,
idToken: aggregateIdToken || finalIdToken,
nodePubkeys: nodeDetails.torusNodePub,
extraParams: _objectSpread({}, additionalParams),
useDkg: this.config.useDkg,
checkCommitment: this.config.checkCommitment
});
});
log.debug("torus-direct/getTorusKey", {
retrieveShares: sharesResponse
});
return sharesResponse;
}
async getRedirectResult({
replaceUrl = true,
clearLoginDetails = true,
storageData = undefined
} = {}) {
await this.init({
skipInit: true
});
const url = new URL(window.location.href);
const hash = url.hash.substring(1);
const queryParams = {};
url.searchParams.forEach((value, key) => {
queryParams[key] = value;
});
if (!hash && Object.keys(queryParams).length === 0) {
throw new Error("Found Empty hash and query parameters. This can happen if user reloads the page");
}
const {
error,
instanceParameters,
hashParameters
} = handleRedirectParameters(hash, queryParams);
const {
instanceId
} = instanceParameters;
log.info(instanceId, "instanceId");
const loginDetails = storageData || (await this.storageHelper.retrieveData(`torus_login_${instanceId}`));
const _ref = loginDetails || {},
{
args
} = _ref,
rest = _objectWithoutProperties(_ref, _excluded2);
log.info(args, "args", this.storageHelper.storageMethodUsed);
let result;
if (error) {
return {
error,
state: instanceParameters || {},
result: {},
hashParameters,
args
};
}
try {
args.hash = hash;
args.queryParameters = queryParams;
result = await this.triggerLogin(args);
} catch (err) {
const serializedError = await serializeError(err);
log.error(serializedError);
if (clearLoginDetails) {
this.storageHelper.clearStorage(`torus_login_${instanceId}`);
}
return _objectSpread({
error: `${serializedError.message || ""}`,
state: instanceParameters || {},
result: {},
hashParameters,
args
}, rest);
}
if (!result) return _objectSpread({
error: `Init parameters not found. It might be because storage is not available. Please retry the login in a different browser. Used storage method: ${this.storageHelper.storageMethodUsed}`,
state: instanceParameters || {},
result: {},
hashParameters,
args
}, rest);
if (replaceUrl) {
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState(_objectSpread(_objectSpread({}, window.history.state), {}, {
as: cleanUrl,
url: cleanUrl
}), "", cleanUrl);
}
if (clearLoginDetails) {
this.storageHelper.clearStorage(`torus_login_${instanceId}`);
}
return _objectSpread({
result,
state: instanceParameters || {},
hashParameters,
args
}, rest);
}
async handlePrefetchRedirectUri() {
if (!document) return Promise.resolve();
return new Promise((resolve, reject) => {
const redirectHtml = document.createElement("link");
redirectHtml.href = this.config.redirect_uri;
if (window.location.origin !== new URL(this.config.redirect_uri).origin) redirectHtml.crossOrigin = "anonymous";
redirectHtml.type = "text/html";
redirectHtml.rel = "prefetch";
const resolveFn = () => {
this.isInitialized = true;
resolve();
};
try {
if (redirectHtml.relList && redirectHtml.relList.supports) {
if (redirectHtml.relList.supports("prefetch")) {
redirectHtml.onload = resolveFn;
redirectHtml.onerror = () => {
reject(new Error(`Please serve redirect.html present in serviceworker folder of this package on ${this.config.redirect_uri}`));
};
document.head.appendChild(redirectHtml);
} else {
// Link prefetch is not supported. pass through
resolveFn();
}
} else {
// Link prefetch is not detectable. pass through
resolveFn();
}
} catch {
resolveFn();
}
});
}
}
export { CustomAuth };