@oraichain/customauth
Version:
CustomAuth login with torus to get user private key
1,441 lines (1,422 loc) • 55.2 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { get, post, enableSentryTracing, generateJsonRPCObject } from '@toruslabs/http-helpers';
import deepmerge from 'lodash.merge';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import { BroadcastChannel } from '@toruslabs/broadcast-channel';
import Bowser from 'bowser';
import { serializeError } from 'serialize-error';
import loglevel from 'loglevel';
import { EventEmitter } from 'events';
import jwtDecode from 'jwt-decode';
import { Some, allSettled } from '@oraichain/multifactors.js';
import { keccak256 } from 'web3-utils';
import { register } from '@chaitanyapotti/register-service-worker';
const LOGIN = {
GOOGLE: "google",
FACEBOOK: "facebook",
REDDIT: "reddit",
DISCORD: "discord",
TWITCH: "twitch",
APPLE: "apple",
GITHUB: "github",
LINKEDIN: "linkedin",
TWITTER: "twitter",
WEIBO: "weibo",
LINE: "line",
EMAIL_PASSWORD: "email_password",
PASSWORDLESS: "passwordless",
JWT: "jwt",
WEBAUTHN: "webauthn"
};
const AGGREGATE_VERIFIER = {
SINGLE_VERIFIER_ID: "single_id_verifier"
// AND_AGGREGATE_VERIFIER : "and_aggregate_verifier",
// OR_AGGREGATE_VERIFIER : "or_aggregate_verifier",
};
const UX_MODE = {
POPUP: "popup"
};
const REDIRECT_PARAMS_STORAGE_METHOD = {
LOCAL_STORAGE: "localStorage",
SESSION_STORAGE: "sessionStorage",
SERVER: "server"
};
const TORUS_METHOD = {
TRIGGER_LOGIN: "triggerLogin"
};
var log = loglevel.getLogger("customauth");
function eventToPromise(emitter) {
return new Promise((resolve, reject) => {
const handler = ev => {
const {
error = "",
data
} = ev;
emitter.removeEventListener("message", handler);
if (error) return reject(new Error(error));
return resolve(data);
};
emitter.addEventListener("message", handler);
});
}
// These are the connection names used by auth0
const loginToConnectionMap = {
[LOGIN.APPLE]: "apple",
[LOGIN.GITHUB]: "github",
[LOGIN.LINKEDIN]: "linkedin",
[LOGIN.TWITTER]: "twitter",
[LOGIN.WEIBO]: "weibo",
[LOGIN.LINE]: "line",
[LOGIN.EMAIL_PASSWORD]: "Username-Password-Authentication",
[LOGIN.PASSWORDLESS]: "email"
};
const padUrlString = url => url.href.endsWith("/") ? url.href : `${url.href}/`;
/**
* Returns a random number. Don't use for cryptographic purposes.
* @returns a random number
*/
const randomId = () => Math.random().toString(36).slice(2);
const broadcastChannelOptions = {
// type: 'localstorage', // (optional) enforce a type, oneOf['native', 'idb', 'localstorage', 'node']
webWorkerSupport: false // (optional) set this to false if you know that your channel will never be used in a WebWorker (increases performance)
};
function caseSensitiveField(field, isCaseSensitive) {
return isCaseSensitive ? field : field.toLowerCase();
}
const getVerifierId = function (userInfo, typeOfLogin, verifierIdField) {
let isVerifierIdCaseSensitive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
const {
name,
email
} = userInfo;
if (verifierIdField) return caseSensitiveField(userInfo[verifierIdField], isVerifierIdCaseSensitive);
switch (typeOfLogin) {
case LOGIN.PASSWORDLESS:
case LOGIN.EMAIL_PASSWORD:
return caseSensitiveField(name, isVerifierIdCaseSensitive);
case LOGIN.WEIBO:
case LOGIN.GITHUB:
case LOGIN.TWITTER:
case LOGIN.APPLE:
case LOGIN.LINKEDIN:
case LOGIN.LINE:
case LOGIN.JWT:
return caseSensitiveField(email, isVerifierIdCaseSensitive);
default:
throw new Error("Invalid login type");
}
};
const handleRedirectParameters = (hash, queryParameters) => {
const hashParameters = hash.split("&").reduce((result, item) => {
const [part0, part1] = item.split("=");
result[part0] = part1;
return result;
}, {});
log.info(hashParameters, queryParameters);
let instanceParameters = {};
let error = "";
if (Object.keys(hashParameters).length > 0 && hashParameters.state) {
instanceParameters = JSON.parse(atob(decodeURIComponent(decodeURIComponent(hashParameters.state)))) || {};
error = hashParameters.error_description || hashParameters.error || error;
} else if (Object.keys(queryParameters).length > 0 && queryParameters.state) {
instanceParameters = JSON.parse(atob(decodeURIComponent(decodeURIComponent(queryParameters.state)))) || {};
if (queryParameters.error) error = queryParameters.error;
}
return {
error,
instanceParameters,
hashParameters
};
};
function storageAvailable(type) {
let storage;
try {
storage = window[type];
const x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return e && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" ||
// Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
// acknowledge QuotaExceededError only if there's something already stored
storage && storage.length !== 0;
}
}
function getPopupFeatures() {
// Fixes dual-screen position Most browsers Firefox
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
const w = 1200;
const h = 700;
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width;
const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height;
const systemZoom = 1; // No reliable estimate
const left = Math.abs((width - w) / 2 / systemZoom + dualScreenLeft);
const top = Math.abs((height - h) / 2 / systemZoom + dualScreenTop);
const features = `titlebar=0,toolbar=0,status=0,location=0,menubar=0,height=${h / systemZoom},width=${w / systemZoom},top=${top},left=${left}`;
return features;
}
const isFirefox = () => {
var _window;
return ((_window = window) === null || _window === void 0 || (_window = _window.navigator) === null || _window === void 0 ? void 0 : _window.userAgent.toLowerCase().indexOf("firefox")) > -1 || false;
};
function constructURL(params) {
const {
baseURL,
query,
hash
} = params;
const url = new URL(baseURL);
if (query) {
Object.keys(query).forEach(key => {
url.searchParams.append(key, query[key]);
});
}
if (hash) {
const h = new URL(constructURL({
baseURL,
query: hash
})).searchParams.toString();
url.hash = h;
}
return url.toString();
}
function are3PCSupported() {
var _navigator;
const browserInfo = Bowser.parse(navigator.userAgent);
log.info(JSON.stringify(browserInfo), "current browser info");
let thirdPartyCookieSupport = true;
// brave
if ((_navigator = navigator) !== null && _navigator !== void 0 && _navigator.brave) {
thirdPartyCookieSupport = false;
}
// All webkit & gecko engine instances use itp (intelligent tracking prevention -
// https://webkit.org/tracking-prevention/#intelligent-tracking-prevention-itp)
if (browserInfo.engine.name === Bowser.ENGINE_MAP.WebKit || browserInfo.engine.name === Bowser.ENGINE_MAP.Gecko) {
thirdPartyCookieSupport = false;
}
return thirdPartyCookieSupport;
}
const validateAndConstructUrl = domain => {
try {
const url = new URL(decodeURIComponent(domain));
return url;
} catch (error) {
throw new Error(`${(error === null || error === void 0 ? void 0 : error.message) || ""}, Note: Your jwt domain: (i.e ${domain}) must have http:// or https:// prefix`);
}
};
const wait = s => new Promise(resolve => {
setTimeout(resolve, s * 1000);
});
function stringtifyError(error) {
return JSON.stringify(serializeError(error));
}
class PopupHandler extends EventEmitter {
constructor(_ref) {
let {
url,
target,
features
} = _ref;
super();
this.url = url;
this.target = target || "_blank";
this.features = features || getPopupFeatures();
this.window = undefined;
this.windowTimer = undefined;
this.iClosedWindow = false;
this._setupTimer();
}
_setupTimer() {
this.windowTimer = Number(setInterval(() => {
if (this.window && this.window.closed) {
clearInterval(this.windowTimer);
if (!this.iClosedWindow) {
this.emit("close");
}
this.iClosedWindow = false;
this.window = undefined;
}
if (this.window === undefined) clearInterval(this.windowTimer);
}, 500));
}
open() {
var _this$window;
this.window = window.open(this.url.href, this.target, this.features);
if ((_this$window = this.window) !== null && _this$window !== void 0 && _this$window.focus) this.window.focus();
return Promise.resolve();
}
close() {
this.iClosedWindow = true;
if (this.window) this.window.close();
}
redirect(locationReplaceOnRedirect) {
if (locationReplaceOnRedirect) {
window.location.replace(this.url.href);
} else {
window.location.href = this.url.href;
}
}
}
const _excluded$2 = ["access_token", "id_token"];
function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
class AbstractLoginHandler {
// Not using object constructor because of this issue
// https://github.com/microsoft/TypeScript/issues/5326
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
_defineProperty(this, "nonce", randomId());
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
}
get state() {
return encodeURIComponent(window.btoa(JSON.stringify(_objectSpread$2(_objectSpread$2({}, this.customState || {}), {}, {
instanceId: this.nonce,
typeOfLogin: this.typeOfLogin,
redirectToOpener: this.redirectToOpener || false
}))));
}
handleLoginWindow(params) {
const verifierWindow = new PopupHandler({
url: this.finalURL,
features: params.popupFeatures
});
return new Promise((resolve, reject) => {
let bc;
const handleData = async ev => {
try {
const {
error,
data
} = ev;
const _ref = data || {},
{
instanceParams,
hashParams: {
access_token: accessToken,
id_token: idToken
}
} = _ref,
rest = _objectWithoutProperties(_ref.hashParams, _excluded$2);
if (error) {
log.error(ev);
reject(new Error(`Error: ${error}. Info: ${JSON.stringify(ev.data || {})}`));
return;
}
if (ev.data) {
if (!this.redirectToOpener && bc) await bc.postMessage({
success: true
});
resolve(_objectSpread$2(_objectSpread$2({
accessToken,
idToken: idToken || ""
}, rest), {}, {
// State has to be last here otherwise it will be overwritten
state: instanceParams
}));
}
} catch (error) {
log.error(error);
reject(error);
}
};
if (!this.redirectToOpener) {
bc = new BroadcastChannel(`redirect_channel_${this.nonce}`, broadcastChannelOptions);
bc.addEventListener("message", async ev => {
await handleData(ev);
bc.close();
verifierWindow.close();
});
} else {
const postMessageEventHandler = async postMessageEvent => {
if (!postMessageEvent.data) return;
const ev = postMessageEvent.data;
if (ev.channel !== `redirect_channel_${this.nonce}`) return;
window.removeEventListener("message", postMessageEventHandler);
handleData(ev);
verifierWindow.close();
};
window.addEventListener("message", postMessageEventHandler);
}
verifierWindow.open();
verifierWindow.once("close", () => {
if (bc) bc.close();
reject(new Error("user closed popup"));
});
});
}
}
class DiscordHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "RESPONSE_TYPE", "token");
_defineProperty(this, "SCOPE", "identify email");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const finalUrl = new URL("https://discord.com/api/oauth2/authorize");
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 => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
accessToken
} = params;
const userInfo = await get("https://discord.com/api/users/@me", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {
id,
avatar,
email = "",
username: name = "",
discriminator = ""
} = userInfo;
const profileImage = avatar === null ? `https://cdn.discord.com/embed/avatars/${Number(discriminator) % 5}.png` : `https://cdn.discord.com/avatars/${id}/${avatar}.png?size=2048`;
return {
profileImage,
name: `${name}#${discriminator}`,
email,
verifierId: id,
typeOfLogin: this.typeOfLogin
};
}
}
class FacebookHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "RESPONSE_TYPE", "token");
_defineProperty(this, "SCOPE", "public_profile email");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const finalUrl = new URL("https://www.facebook.com/v15.0/dialog/oauth");
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 => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
accessToken
} = params;
const userInfo = await get("https://graph.facebook.com/me?fields=name,email,picture.type(large)", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {
name = "",
id,
picture,
email = ""
} = userInfo;
return {
email,
name,
profileImage: picture.data.url || "",
verifierId: id,
typeOfLogin: this.typeOfLogin
};
}
}
class GoogleHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "RESPONSE_TYPE", "token id_token");
_defineProperty(this, "SCOPE", "profile email openid");
_defineProperty(this, "PROMPT", "consent select_account");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const finalUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth");
const clonedParams = JSON.parse(JSON.stringify(this.jwtParams || {}));
const finalJwtParams = deepmerge({
state: this.state,
response_type: this.RESPONSE_TYPE,
client_id: this.clientId,
prompt: this.PROMPT,
redirect_uri: this.redirect_uri,
scope: this.SCOPE,
nonce: this.nonce
}, clonedParams);
Object.keys(finalJwtParams).forEach(key => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
accessToken
} = params;
const userInfo = await get("https://www.googleapis.com/userinfo/v2/me", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {
picture: profileImage = "",
email = "",
name = ""
} = userInfo;
return {
email,
name,
profileImage,
verifierId: email.toLowerCase(),
typeOfLogin: this.typeOfLogin
};
}
}
let JwtHandler$1 = class JwtHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "SCOPE", "openid profile email");
_defineProperty(this, "RESPONSE_TYPE", "token id_token");
_defineProperty(this, "PROMPT", "login");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const {
domain
} = this.jwtParams;
const finalUrl = validateAndConstructUrl(domain);
finalUrl.pathname += finalUrl.pathname.endsWith("/") ? "authorize" : "/authorize";
const clonedParams = JSON.parse(JSON.stringify(this.jwtParams));
delete clonedParams.domain;
const finalJwtParams = deepmerge({
state: this.state,
response_type: this.RESPONSE_TYPE,
client_id: this.clientId,
prompt: this.PROMPT,
redirect_uri: this.redirect_uri,
scope: this.SCOPE,
connection: loginToConnectionMap[this.typeOfLogin],
nonce: this.nonce
}, clonedParams);
Object.keys(finalJwtParams).forEach(key => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
idToken,
accessToken
} = params;
const {
domain,
verifierIdField,
isVerifierIdCaseSensitive,
user_info_route = "userinfo"
} = this.jwtParams;
if (accessToken) {
try {
const domainUrl = new URL(domain);
const userInfo = await get(`${padUrlString(domainUrl)}${user_info_route}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {
picture,
name,
email
} = userInfo;
return {
email,
name,
profileImage: picture,
verifierId: getVerifierId(userInfo, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive),
typeOfLogin: this.typeOfLogin
};
} catch (error) {
// ignore
loglevel.warn(error, "Unable to get userinfo from endpoint");
}
}
if (idToken) {
const decodedToken = jwtDecode(idToken);
const {
name,
email,
picture
} = decodedToken;
return {
profileImage: picture,
name,
email,
verifierId: email,
typeOfLogin: this.typeOfLogin
};
}
throw new Error("Access/id token not available");
}
};
const _excluded$1 = ["access_token", "id_token"];
function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
class JwtHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "SCOPE", "openid profile email");
_defineProperty(this, "RESPONSE_TYPE", "token id_token");
_defineProperty(this, "PROMPT", "login");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const {
domain
} = this.jwtParams;
const domainUrl = validateAndConstructUrl(domain);
domainUrl.pathname = "/passwordless/start";
this.finalURL = domainUrl;
}
async getUserInfo(params) {
const {
idToken,
accessToken
} = params;
const {
domain,
verifierIdField,
isVerifierIdCaseSensitive
} = this.jwtParams;
try {
const domainUrl = new URL(domain);
const userInfo = await get(`${padUrlString(domainUrl)}userinfo`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const {
picture,
name,
email
} = userInfo;
return {
email,
name,
profileImage: picture,
verifierId: getVerifierId(userInfo, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive),
typeOfLogin: this.typeOfLogin
};
} catch (error) {
log.error(error);
const decodedToken = jwtDecode(idToken);
const {
name,
email,
picture
} = decodedToken;
return {
profileImage: picture,
name,
email,
verifierId: getVerifierId(decodedToken, this.typeOfLogin, verifierIdField, isVerifierIdCaseSensitive),
typeOfLogin: this.typeOfLogin
};
}
}
handleLoginWindow() {
return new Promise((resolve, reject) => {
if (this.redirectToOpener) {
reject(new Error("Cannot use redirect to opener for passwordless"));
return;
}
const handleData = ev => {
try {
const {
error,
data
} = ev;
const _ref = data || {},
{
instanceParams,
hashParams: {
access_token: accessToken,
id_token: idToken
}
} = _ref,
rest = _objectWithoutProperties(_ref.hashParams, _excluded$1);
if (error) {
log.error(ev.error);
reject(new Error(error));
return;
}
if (ev.data) {
log.info(ev.data);
resolve(_objectSpread$1(_objectSpread$1({
accessToken,
idToken: idToken || ""
}, rest), {}, {
state: instanceParams
}));
}
} catch (error) {
log.error(error);
reject(error);
}
};
const bc = new BroadcastChannel(`redirect_channel_${this.nonce}`, broadcastChannelOptions);
bc.addEventListener("message", async ev => {
handleData(ev);
bc.close();
});
try {
const {
connection = "email",
login_hint
} = this.jwtParams;
const finalJwtParams = deepmerge({
client_id: this.clientId,
connection,
email: connection === "email" ? login_hint : undefined,
phone_number: connection === "sms" ? login_hint : undefined,
send: "link",
authParams: {
scope: this.SCOPE,
state: this.state,
response_type: this.RESPONSE_TYPE,
redirect_uri: this.redirect_uri,
nonce: this.nonce,
prompt: this.PROMPT
}
}, {
authParams: this.jwtParams
});
// using stringify and parse to remove undefined params
// This method is only resolved when the user clicks the email link
post(this.finalURL.href, JSON.parse(JSON.stringify(finalJwtParams))).then(response => {
log.info("posted", response);
return undefined;
}).catch(error => {
log.error(error);
reject(error);
});
} catch (error) {
log.error(error);
reject(error);
}
});
}
}
class RedditHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "RESPONSE_TYPE", "token");
_defineProperty(this, "SCOPE", "identity");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
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 => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
accessToken
} = params;
const userInfo = await get("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,
verifierId: name.toLowerCase(),
typeOfLogin: this.typeOfLogin
};
}
}
class TwitchHandler extends AbstractLoginHandler {
constructor(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState) {
super(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
_defineProperty(this, "RESPONSE_TYPE", "token");
_defineProperty(this, "SCOPE", "user:read:email");
this.clientId = clientId;
this.redirect_uri = redirect_uri;
this.typeOfLogin = typeOfLogin;
this.uxMode = uxMode;
this.redirectToOpener = redirectToOpener;
this.jwtParams = jwtParams;
this.customState = customState;
this.setFinalUrl();
}
setFinalUrl() {
const finalUrl = new URL("https://id.twitch.tv/oauth2/authorize");
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,
force_verify: true
}, clonedParams);
Object.keys(finalJwtParams).forEach(key => {
if (finalJwtParams[key]) finalUrl.searchParams.append(key, finalJwtParams[key]);
});
this.finalURL = finalUrl;
}
async getUserInfo(params) {
const {
accessToken
} = params;
const userInfo = await get("https://api.twitch.tv/helix/users", {
headers: {
Authorization: `Bearer ${accessToken}`,
"Client-ID": this.clientId
}
});
const [{
profile_image_url: profileImage = "",
display_name: name = "",
email = "",
id: verifierId
}] = userInfo.data || [];
return {
profileImage,
name,
email,
verifierId,
typeOfLogin: this.typeOfLogin
};
}
}
const createHandler = _ref => {
let {
clientId,
redirect_uri,
typeOfLogin,
jwtParams,
redirectToOpener,
uxMode,
customState
} = _ref;
if (!typeOfLogin || !clientId) {
throw new Error("Invalid params");
}
const {
domain,
login_hint
} = jwtParams || {};
switch (typeOfLogin) {
case LOGIN.GOOGLE:
return new GoogleHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.FACEBOOK:
return new FacebookHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.TWITCH:
return new TwitchHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.REDDIT:
return new RedditHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.DISCORD:
return new DiscordHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.PASSWORDLESS:
if (!domain || !login_hint) throw new Error("Invalid params");
return new JwtHandler(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
case LOGIN.APPLE:
case LOGIN.GITHUB:
case LOGIN.LINKEDIN:
case LOGIN.TWITTER:
case LOGIN.WEIBO:
case LOGIN.LINE:
case LOGIN.EMAIL_PASSWORD:
case LOGIN.JWT:
if (!domain) throw new Error("Invalid params");
return new JwtHandler$1(clientId, redirect_uri, typeOfLogin, uxMode, redirectToOpener, jwtParams, customState);
default:
throw new Error("Invalid login type");
}
};
const registerServiceWorker = baseUrl => new Promise((resolve, reject) => {
const swUrl = `${baseUrl}sw.js`;
if ("serviceWorker" in window.navigator) {
// if swIntegrity is not calculated
register(swUrl, {
ready() {
log.info("App is being served from cache by a service worker.\n For more details, visit https://goo.gl/AFskqB");
resolve(undefined);
},
registered() {
log.info("Service worker has been registered.");
resolve(undefined);
},
cached() {
log.info("Content has been cached for offline use.");
resolve(undefined);
},
updatefound() {
log.info("New content is downloading.");
},
updated() {
log.info("New content is available; please refresh.");
},
offline() {
log.info("No internet connection found. App is running in offline mode.");
reject(new Error("App is offline"));
},
error(error) {
log.error("Error during service worker registration:", error);
reject(error);
}
});
} else {
reject(new Error("Service workers are not supported"));
}
});
var Network;
(function (Network) {
Network["DEV"] = "development";
Network["TESTNET"] = "testnet";
Network["MAINNET"] = "mainnet";
Network["STAGING"] = "staging";
Network["PRODUCTION"] = "production";
})(Network || (Network = {}));
const NetworkConfig = {
[Network.DEV]: {
rpc: "https://rpc.testnet.orai.io",
lcd: "https://lcd.testnet.orai.io",
chainId: "Oraichain-testnet",
contract: "orai182z6mxeta4dgaxu6qyuu5fywc3p7cdyz2udphd0nz5vnqkrdhzrscu8zan"
},
[Network.STAGING]: {
rpc: "https://rpc.testnet.orai.io",
lcd: "https://lcd.testnet.orai.io",
chainId: "Oraichain-testnet",
contract: "orai1j3ynfwl2gv7jujfhjqkwrgfwfsg2jth7fyv2g0ph9twc372ny7gqxhe5pj"
},
[Network.TESTNET]: {
rpc: "https://rpc.testnet.orai.io",
lcd: "https://lcd.testnet.orai.io",
chainId: "Oraichain-testnet",
contract: "orai1v5hwd3w4dx3628suz3lrhd9hr8ktdgjytu95kfsa0vxxmxj42rtsxv4sdn"
},
[Network.MAINNET]: {
rpc: "https://rpc.orai.io",
lcd: "https://lcd.orai.io",
chainId: "Oraichain",
contract: "orai1kvu7xclv2uvc5yl0mzgcux0cw40sjur2kksarva84376gq4qnnxqhk2hh5",
loadBalancerEndpoint: "https://social-login.orai.io/jrpc"
},
[Network.PRODUCTION]: {
rpc: "https://rpc.orai.io",
lcd: "https://lcd.orai.io",
chainId: "Oraichain",
contract: "orai1r7qwtfp7uc0jsemc8frnjgwc4gpspxnuhg7gjcv3slzul08gglds65tnrp",
loadBalancerEndpoint: "https://executor-multifactor.orai.io/jrpc"
}
};
const metadataUrl = {
[Network.DEV]: "http://127.0.0.1:5051",
[Network.STAGING]: "https://metadata.social-login-staging.orai.io",
[Network.TESTNET]: "https://metadata.social-login-testnet.orai.io",
[Network.MAINNET]: "https://metadata-social-login.orai.io",
[Network.PRODUCTION]: "https://metadata-social-login.orai.io"
};
const getQueryUrl = (config, params) => `${config.lcd}/cosmwasm/wasm/v1/contract/${config.contract}/smart/${params}`;
const query = async (config, input, requestInit, customOpts) => {
const param = Buffer.from(JSON.stringify(input)).toString("base64");
const queryUrl = getQueryUrl(config, param);
const resp = await get(queryUrl, requestInit, customOpts);
return resp.data;
};
var WEBSOCKET_CODE_ONCLOSE;
(function (WEBSOCKET_CODE_ONCLOSE) {
WEBSOCKET_CODE_ONCLOSE[WEBSOCKET_CODE_ONCLOSE["ERROR"] = 4000] = "ERROR";
WEBSOCKET_CODE_ONCLOSE[WEBSOCKET_CODE_ONCLOSE["SUCCESS"] = 3000] = "SUCCESS";
})(WEBSOCKET_CODE_ONCLOSE || (WEBSOCKET_CODE_ONCLOSE = {}));
function fromRPCtoWebsocket(url) {
const rpcURL = new URL(url);
rpcURL.protocol = "wss";
rpcURL.pathname = "/websocket";
return rpcURL.toString();
}
const conditionTransform = queryTags => {
let condition = "";
Object.keys(queryTags).forEach(key => {
condition += condition ? ` AND ${key} = '${queryTags[key]}'` : `AND ${key} = '${queryTags[key]}'`;
});
return condition;
};
const subscribeTx = async function (url, tags) {
let timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 15000;
const conditionString = conditionTransform(tags);
return new Promise((resolve, reject) => {
// Set up the timeout
const ws = new WebSocket(url);
const timer = setTimeout(() => {
reject(new Error(`subscribe timed out after ${timeout} ms`));
ws.send(JSON.stringify({
jrpc: "2.0",
method: "unsubscribe",
id: "99",
params: []
}));
return ws.close();
}, timeout);
ws.onopen = () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "subscribe",
params: [`tm.event = 'Tx' ${conditionString}`],
id: "1"
}));
};
ws.onmessage = message => {
const data = JSON.parse(message.data);
if ("events" in data.result) {
var _data$result;
const events = data === null || data === void 0 || (_data$result = data.result) === null || _data$result === void 0 ? void 0 : _data$result.events;
const keysFromTags = Object.keys(tags);
const extractValueFromEvent = keysFromTags.reduce((acc, key) => {
acc[key] = events[key];
return acc;
}, {});
extractValueFromEvent.txHash = events["tx.hash"][0];
clearTimeout(timer);
resolve(extractValueFromEvent);
ws.send(JSON.stringify({
jrpc: "2.0",
method: "unsubscribe",
id: "99",
params: []
}));
return ws.close(WEBSOCKET_CODE_ONCLOSE.SUCCESS);
}
};
ws.onerror = errorEvent => {
reject(new Error(`WebSocket error ${errorEvent.type}`));
ws.send(JSON.stringify({
jrpc: "2.0",
method: "unsubscribe",
id: "99",
params: []
}));
return ws.close(WEBSOCKET_CODE_ONCLOSE.ERROR);
};
ws.onclose = event => {
clearTimeout(timer);
if (event.code !== WEBSOCKET_CODE_ONCLOSE.ERROR && event.code !== WEBSOCKET_CODE_ONCLOSE.SUCCESS) {
return reject(new Error(`WebSocket closed unexpectedly with code ${event.code}`));
}
};
});
};
const _excluded = ["access_token", "id_token"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
class CustomAuth {
constructor(_ref) {
let {
baseUrl,
network = Network.MAINNET,
enableLogging = true,
redirectToOpener = false,
redirectPathName = "redirect",
uxMode = UX_MODE.POPUP,
locationReplaceOnRedirect = false,
popupFeatures,
multifactors,
networkConfig
} = _ref;
this.isInitialized = false;
const baseUri = new URL(baseUrl);
this.config = {
baseUrl: padUrlString(baseUri),
get redirect_uri() {
return `${this.baseUrl}${redirectPathName}`;
},
redirectToOpener,
uxMode,
locationReplaceOnRedirect,
popupFeatures
};
this.multifactors = multifactors;
this.networkConfig = networkConfig ? _objectSpread(_objectSpread({}, NetworkConfig[network]), networkConfig) : NetworkConfig[network];
if (enableLogging) log.enableAll();else log.disableAll();
}
async enableSentry(sentry) {
const {
members: nodes
} = await this.getContractConfig();
const endpoints = nodes.map(node => node.end_point);
enableSentryTracing(sentry, endpoints, ["/get", "/set"]);
}
async init() {
let {
skipSw = false,
skipInit = false,
skipPrefetch = false
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
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 {
verifier,
typeOfLogin
} = args;
const {
userInfo,
loginParams
} = await this.getLoginParamsAndUserInfo(args);
try {
const multifactorsKey = await this.getMultifactorsKey(typeOfLogin, {
verifier_id: userInfo === null || userInfo === void 0 ? void 0 : userInfo.verifierId,
verifier
}, loginParams.idToken || loginParams.accessToken, userInfo === null || userInfo === void 0 ? void 0 : userInfo.extraVerifierParams);
return _objectSpread(_objectSpread({}, multifactorsKey), {}, {
userInfo: _objectSpread(_objectSpread(_objectSpread({}, userInfo), loginParams), {}, {
verifierId: userInfo === null || userInfo === void 0 ? void 0 : userInfo.verifierId
})
});
} catch (error) {
log.debug(error);
throw new Error(`getMultifactorsKey::${stringtifyError(error)}`);
}
}
async triggerLoginMobile(args) {
const {
verifier,
typeOfLogin
} = args;
const {
userInfo,
loginParams
} = await this.getLoginParamsAndUserInfo(args);
try {
const {
sharesIndexes,
shares,
thresholdPublicKey
} = await this.getMultifactorsKeyMobile(typeOfLogin, {
verifier_id: userInfo === null || userInfo === void 0 ? void 0 : userInfo.verifierId,
verifier
}, loginParams.idToken || loginParams.accessToken, userInfo === null || userInfo === void 0 ? void 0 : userInfo.extraVerifierParams);
return {
sharesIndexes,
shares,
userInfo: _objectSpread(_objectSpread({}, userInfo), {}, {
verifierId: userInfo === null || userInfo === void 0 ? void 0 : userInfo.verifierId
}),
thresholdPublicKey
};
} catch (error) {
throw new Error(`getMultifactorsKeyMobile::${stringtifyError(error)}`);
}
}
/* istanbul ignore next @preserve */
async login(args) {
const {
typeOfLogin,
clientId,
jwtParams,
hash,
queryParameters,
customState
} = args;
let {
idToken,
accessToken
} = args;
if (!this.isInitialized) {
throw new Error("Not initialized yet");
}
const loginHandler = createHandler({
typeOfLogin,
clientId,
redirect_uri: this.config.redirect_uri,
redirectToOpener: this.config.redirectToOpener,
jwtParams,
uxMode: this.config.uxMode,
customState
});
let loginParams;
if (hash && queryParameters) {
const {
error,
hashParameters,
instanceParameters
} = handleRedirectParameters(hash, queryParameters);
if (error) throw new Error(error);
let rest;
var _hashParameters = hashParameters;
({
access_token: accessToken,
id_token: idToken
} = _hashParameters);
rest = _objectWithoutProperties(_hashParameters, _excluded);
loginParams = _objectSpread(_objectSpread({
accessToken,
idToken
}, rest), {}, {
state: instanceParameters
});
} else {
loginParams = await loginHandler.handleLoginWindow({
locationReplaceOnRedirect: this.config.locationReplaceOnRedirect,
popupFeatures: this.config.popupFeatures
});
}
const userInfo = await loginHandler.getUserInfo(loginParams);
return {
loginParams,
userInfo
};
}
async getMultifactorsKey(typeOfLogin, verifierParams, idToken, additionalParams) {
const [{
found,
verifierId
}, {
members: nodes
}] = await Promise.all([this.lookUpVerifierId(verifierParams.verifier_id, verifierParams.verifier), this.getContractConfig()]);
const endpoints = nodes.map(node => node.end_point);
if (!found) {
let nodeSignatures = await this.getKeyAssignCommitment(idToken, verifierId, verifierParams.verifier, endpoints);
nodeSignatures = nodeSignatures.map(i => i.result);
await this.assignKey(typeOfLogin, idToken, verifierId, verifierParams.verifier, endpoints, nodeSignatures);
}
const shares = await this.multifactors.retrieveShares({
typeOfLogin,
endpoints,
verifierParams: _objectSpread(_objectSpread({}, verifierParams), {}, {
verifier_id: verifierId
}),
idToken,
extraParams: additionalParams
});
log.debug("multifactors-direct/getMultifactorsKey", {
retrieveShares: shares
});
return {
privateKey: shares.privKey.toString()
};
}
async getMultifactorsKeyMobile(typeOfLogin, verifierParams, idToken, additionalParams) {
const [{
found,
verifierId
}, {
members: nodes
}] = await Promise.all([this.lookUpVerifierId(verifierParams.verifier_id, verifierParams.verifier), this.getContractConfig()]);
const endpoints = nodes.map(node => node.end_point);
if (!found) {
let nodeSignatures;
nodeSignatures = await this.getKeyAssignCommitment(idToken, verifierId, verifierParams.verifier, endpoints);
nodeSignatures = nodeSignatures.map(i => i && i.result);
await this.assignKey(typeOfLogin, idToken, verifierId, verifierParams.verifier, endpoints, nodeSignatures);
}
return this.multifactors.retrieveSharesMobile({
typeOfLogin,
endpoints,
verifierParams: _objectSpread(_objectSpread({}, verifierParams), {}, {
verifier_id: verifierId
}),
idToken,
extraParams: additionalParams
});
}
async getKeyAssignCommitment(idToken, verifierId, verifier, endpoints) {
const commitment = keccak256(idToken).slice(2);
const promiseArr = [];
for (let i = 0; i < endpoints.length; i += 1) {
const p = post(endpoints[i], generateJsonRPCObject("AssignKeyCommitmentRequest", {
tokencommitment: commitment,
verifier_id: verifierId,
verifier
})).catch(err => {
log.error("🚀 ~ file: login.ts:196 ~ CustomAuth ~ getKeyAssignCommitment ~ err:", err);
log.error("AssignKeyCommitmentRequest", err);
return undefined;
});
promiseArr.push(p);
}
return Some(promiseArr, resultArr => {
const completedRequests = resultArr.filter(x => {
if (!x || typeof x !== "object") {
return false;
}
if (x.error) {
return false;
}
return true;
});
if (completedRequests.length >= ~~(endpoints.length / 2) + 1) {
return Promise.resolve(completedRequests);
}
return Promise.reject(new Error(`Insufficent nodeSignatures to assign key`));
});
}
async assignKey(typeOfLogin, idToken, verifierId, verifier, endpoints, nodeSignatures) {
const queryTag = {
"wasm.verifier": verifier,
"wasm.verify_id": verifierId,
"wasm.action": "assign_key",
"wasm._contract_address": this.networkConfig.contract
};
const webSocketUrl = fromRPCtoWebsocket(this.networkConfig.rpc);
const promiseSubcribe = subscribeTx(webSocketUrl, queryTag, 20000);
const response = await post(this.networkConfig.loadBalancerEndpoint ? this.networkConfig.loadBalancerEndpoint : endpoints[3], generateJsonRPCObject("AssignKeyRequest", {
typeOfLogin,
idtoken: idToken,
verifier_id: verifierId,
verifier,
nodesignatures: nodeSignatures
})).catch(_error => undefined);
if (!response || typeof response !== "object") {
throw new Error("assign key fail");
}
const attributes = await promiseSubcribe;
return {
txHash: attributes.txHash
};
}
async mapNewVerifierId(mapNewVerifierIdParams) {
try {
const [{
members: nodes
}, {
found,
verifierId: newVerifierId
}] = await Promise.all([this.getContractConfig(), this.lookUpVerifierId(mapNewVerifierIdParams.newVerifierId, mapNewVerifierIdParams.newVerifier)]);
if (found) throw new Error("New verifier id is already exist in system");
const endpoints = nodes.map(node => node.end_point);
const queryTag = {
"wasm.verifier": mapNewVerifierIdParams.verifier,
"wasm.verifier_id": keccak256(mapNewVerifierIdParams.verifierId),
"wasm.new_verifier": mapNewVerifierIdParams.newVerifier,
"wasm.new_verifier_id": newVerifierId,
"wasm.action": "map_exist_verifier_id_to_another_permit",
"wasm._contract_address": this.networkConfig.contract
};
const webSocketUrl = fromRPCtoWebsocket(this.networkConfig.rpc);
const promiseSubcribe = subscribeTx(webSocketUrl, queryTag, 25000);
const nodeSignatures = await this.getMapNewVerifierIdCommitment(_objectSpread(_objectSpread({}, mapNewVerifierIdParams), {}, {
newVerifierId: newVerifierId
}), endpoints);
if (nodeSignatures.length < ~~(endpoints.length / 2) + 1) {
throw new Error("Insufficent nodeSignatures to mapping");
}
const response = await post(this.networkConfig.loadBalancerEndpoint ? this.networkConfig.loadBalancerEndpoint : e