UNPKG

@salad-labs/loopz-typescript

Version:
652 lines 33.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Client } from "./core/client"; import { Crypto } from "./core"; import { CLIENT_DB_KEY_LAST_USER_LOGGED } from "./constants/app"; import { Account, Chat, Serpens } from "."; import refreshToken from "./core/auth/refreshtoken"; /** * Represents an authentication client that interacts with a backend server for user authentication. */ export class Auth { static set authToken(authToken) { Auth._authToken = authToken; Auth._realtimeAuthorizationToken = authToken ? `${Auth._apiKey}##${authToken}` : null; } static set account(account) { Auth._account = account; if (account) Auth._emit("__onAccountReady"); } static get realtimeAuthorizationToken() { return Auth._realtimeAuthorizationToken; } static get apiKey() { return Auth._apiKey; } static get authToken() { return Auth._authToken; } static get account() { return Auth._account; } static get MAX_ATTEMPTS_REALTIME_FETCH_AUTH_TOKEN() { return 2; } constructor() { if (!Auth._config) throw new Error("Auth must be configured before getting the instance"); Auth._storage = Auth._config.storage; Auth._apiKey = Auth._config.apiKey; Auth._client = new Client(Auth._config.devMode); this.on("__onLoginError", (error) => { Auth._emit("onAuthError", error); }); Auth._instance = this; } /** static methods */ static _generateKeys() { return __awaiter(this, void 0, void 0, function* () { try { const keys = yield Crypto.generateKeys("HIGH"); if (!keys) throw new Error("Error during generation of public/private keys."); return keys; } catch (error) { throw error; } }); } static _getKeys() { return __awaiter(this, void 0, void 0, function* () { if (!Auth._config || !Auth._instance || !Auth._client) throw new Error("Auth has not been configured"); const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/auth/keys"), { method: "GET", }); if (!response || !response.data) throw new Error("Invalid response."); const { publicKeyPem, privateKeyPem } = response.data[0]; return { publicKeyPem, privateKeyPem }; }); } static _getOrCreateUserKeys(did, organizationId) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { Serpens.addAction(() => { Auth._storage.user .where("[did+organizationId]") .equals([did, organizationId]) .first() .then((existingUser) => __awaiter(this, void 0, void 0, function* () { if (existingUser) { console.log("_getOrCreateUserKeys, user exists", existingUser); resolve({ e2ePublicKeyPem: existingUser.e2ePublicKey, e2ePrivateKeyPem: existingUser.e2eEncryptedPrivateKey, }); } else { console.log("_getOrCreateUserKeys, user doesn't exists"); const keys = yield Auth._getKeys(); const { publicKeyPem, privateKeyPem } = keys; console.log("two pems", publicKeyPem, privateKeyPem); resolve({ e2ePublicKeyPem: publicKeyPem, e2ePrivateKeyPem: privateKeyPem, }); } })) .catch(reject); }); }); }); } static _storeUserOnLocalDB(keys) { return __awaiter(this, void 0, void 0, function* () { try { if (!Auth._account) throw new Error("Account is not set"); const existingUser = yield new Promise((resolve, reject) => { Serpens.addAction(() => { if (!Auth._account) throw new Error("Account is not setup correctly."); Auth._storage.user .where("[did+organizationId]") .equals([Auth._account.did, Auth._account.organizationId]) .first() .then(resolve) .catch(reject); }); }); if (existingUser) { if (existingUser.firstLogin) { yield new Promise((resolve, reject) => { Serpens.addAction(() => { if (!Auth._account) throw new Error("Account is not setup correctly."); Auth._storage.user .update(existingUser, { firstLogin: false, }) .then(resolve) .catch(reject); }); }); } return; } //save all the data related to this user into the db yield new Promise((resolve, reject) => { Serpens.addAction(() => { if (!Auth._account) throw new Error("Account is not setup correctly."); Auth._storage.user .add({ did: Auth._account.did, organizationId: Auth._account.organizationId, didPrivy: Auth._account.didPrivy, dynamoDBUserID: Auth._account.dynamoDBUserID, username: Auth._account.username, email: Auth._account.email, bio: Auth._account.bio, instagramPublicUrl: Auth._account.instagramPublicUrl, xPublicUrl: Auth._account.xPublicUrl, tiktokPublicUrl: Auth._account.tiktokPublicUrl, personalWebsiteUrl: Auth._account.personalWebsiteUrl, avatarUrl: Auth._account.avatarUrl, bannerImageUrl: Auth._account.bannerImageUrl, imageSettings: Auth._account.imageSettings ? Auth._account.imageSettings : null, city: Auth._account.city, country: Auth._account.country, lat: Auth._account.lat, lng: Auth._account.lng, isCreator: Auth._account.isCreator, gender: Auth._account.gender, isVerified: Auth._account.isVerified, signupCompleted: Auth._account.signupCompleted, wallet: { address: Auth._account.walletAddress, connectorType: Auth._account.walletConnectorType, imported: Auth._account.walletImported, recoveryMethod: Auth._account.walletRecoveryMethod, clientType: Auth._account.walletClientType, }, apple: Auth._account.appleSubject ? { subject: Auth._account.appleSubject, email: Auth._account.email, } : null, discord: Auth._account.discordSubject ? { subject: Auth._account.discordSubject, email: Auth._account.discordEmail, username: Auth._account.username, } : null, farcaster: Auth._account.farcasterFid ? { fid: Auth._account.farcasterFid, displayName: Auth._account.farcasterDisplayName, ownerAddress: Auth._account.farcasterOwnerAddress, pfp: Auth._account.farcasterPfp, username: Auth._account.farcasterUsername, signerPublicKey: Auth._account.farcasterSignerPublicKey, } : null, github: Auth._account.githubSubject ? { subject: Auth._account.githubSubject, email: Auth._account.githubEmail, name: Auth._account.githubName, username: Auth._account.githubUsername, } : null, google: Auth._account.googleSubject ? { subject: Auth._account.googleSubject, email: Auth._account.googleEmail, name: Auth._account.googleName, } : null, instagram: Auth._account.instagramSubject ? { subject: Auth._account.instagramSubject, username: Auth._account.instagramUsername, } : null, linkedin: Auth._account.linkedinSubject ? { subject: Auth._account.linkedinSubject, email: Auth._account.linkedinEmail, name: Auth._account.linkedinName, vanityName: Auth._account.linkedinVanityName, } : null, spotify: Auth._account.spotifySubject ? { subject: Auth._account.spotifySubject, email: Auth._account.spotifyEmail, name: Auth._account.spotifyName, } : null, telegram: Auth._account.telegramUserId ? { firstName: Auth._account.telegramFirstName, lastName: Auth._account.telegramLastName, photoUrl: Auth._account.telegramPhotoUrl, userId: Auth._account.telegramUserId, username: Auth._account.telegramUsername, } : null, tiktok: Auth._account.tiktokSubject ? { name: Auth._account.tiktokName, subject: Auth._account.tiktokSubject, username: Auth._account.tiktokUsername, } : null, twitter: Auth._account.twitterSubject ? { name: Auth._account.twitterName, subject: Auth._account.twitterSubject, profilePictureUrl: Auth._account.twitterProfilePictureUrl, username: Auth._account.twitterUsername, } : null, firstLogin: Auth._account.firstLogin, proposalNotificationPush: Auth._account.proposalNotificationPush, proposalNotificationSystem: Auth._account.proposalNotificationSystem, orderNotificationPush: Auth._account.orderNotificationPush, orderNotificationSystem: Auth._account.orderNotificationSystem, followNotificationPush: Auth._account.followNotificationPush, followNotificationSystem: Auth._account.followNotificationSystem, collectionNotificationPush: Auth._account.collectionNotificationPush, collectionNotificationSystem: Auth._account.collectionNotificationSystem, generalNotificationPush: Auth._account.generalNotificationPush, generalNotificationSystem: Auth._account.generalNotificationSystem, accountSuspended: Auth._account.accountSuspended, allowNotification: Auth._account.allowNotification, allowNotificationSound: Auth._account.allowNotificationSound, visibility: Auth._account.visibility, onlineStatus: Auth._account.onlineStatus, allowReadReceipt: Auth._account.allowReadReceipt, allowReceiveMessageFrom: Auth._account.allowReceiveMessageFrom, allowAddToGroupsFrom: Auth._account.allowAddToGroupsFrom, allowGroupsSuggestion: Auth._account.allowGroupsSuggestion, e2ePublicKey: keys.e2ePublicKeyPem, e2eEncryptedPrivateKey: keys.e2ePrivateKeyPem, createdAt: Auth._account.createdAt, updatedAt: Auth._account.updatedAt, lastSyncAt: null, }) .then(resolve) .catch(reject); }); }); } catch (error) { console.error(error); throw new Error("Error during setup of the local keys. Check the console to have more information."); } }); } static _callBackendAuth(authInfo) { return __awaiter(this, void 0, void 0, function* () { if (!Auth._config || !Auth._instance || !Auth._client) throw new Error("Auth has not been configured"); Auth.authToken = authInfo.authToken; try { const keys = yield Auth._getOrCreateUserKeys(authInfo.user.id, Auth._apiKey); const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/auth"), { method: "POST", body: Object.assign({}, Auth._formatAuthParams(authInfo)), }); if (!response || !response.data) throw new Error("Invalid response."); const { user } = response.data[0]; if (!user) throw new Error("Access not granted."); Auth._isAuthenticated = true; Auth._authInfo = Object.assign({}, authInfo); Auth.account = new Account(Object.assign({ enableDevMode: Auth._config.devMode, storage: Auth._config.storage }, user)); //store the key of the last user logged in the local storage, this allow to recover the user and rebuild the account object when //the user refresh the page Auth._account.storeLastUserLoggedKey(); //generation of the record in user table and local keys for e2e encryption yield Auth._storeUserOnLocalDB(keys); Chat.getInstance().setCanChat(true); //clear all the internal callbacks connected to the authentication... Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]); return { auth: Auth._authInfo, account: Auth._account, }; } catch (error) { console.error(error); //clear all the internal callbacks connected to the authentication... Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]); Auth._instance.logout(); Auth._emit("onAuthError", error); throw error; } }); } static _formatAuthParams(authInfo) { return { did: authInfo.user.id, email: authInfo.user.email, referralCode: authInfo.referralCode, }; } static _clearEventsCallbacks(events) { for (const event of events) { const index = Auth._eventsCallbacks.findIndex((item) => item.event === event); if (index < 0) return; Auth._eventsCallbacks[index].callbacks = []; } } static _handleDesktopAuthentication() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { if (!Auth._config || !Auth._instance) throw new Error("Auth has not been configured"); Auth._instance.on("__onLoginComplete", (authInfo) => { Auth._callBackendAuth(authInfo).then(resolve).catch(reject); }); Auth._instance.on("__onLoginError", reject); Auth._emit("__authenticate"); }); }); } static _emit(event, params) { const index = Auth._eventsCallbacks.findIndex((item) => item.event === event); if (index < 0) return; Auth._eventsCallbacks[index].callbacks.forEach((callback) => callback(params)); } static config(config) { if (Auth._config) throw new Error("Auth already configured"); Auth._config = config; } static getInstance() { var _a; return (_a = Auth._instance) !== null && _a !== void 0 ? _a : new Auth(); } /** public instance methods */ /** * Add a new event and the associated callback. * @param event - The event to listen. * @param callback - The callback related to this event. * @param onlyOnce - An optional flag, it allows the adding of only one callback associated to this event. */ on(event, callback, onlyOnce) { const index = Auth._eventsCallbacks.findIndex((item) => item.event === event); if (index < 0) Auth._eventsCallbacks.push({ event, callbacks: [callback], }); else { // this is wrong, if i flag onlyOnce: true i still want my callback to be executed, just once tho if (onlyOnce) return; Auth._eventsCallbacks[index].callbacks.push(callback); } } /** * Remove an event and the associated callback or all the callbacks associated to that event. * @param event - The event to unlisten. * @param callback - The callback related to this event. * @returns None */ off(event, callback) { const index = Auth._eventsCallbacks.findIndex((item) => { return item.event === event; }); if (index < 0) return; Auth._eventsCallbacks[index].callbacks = callback ? Auth._eventsCallbacks[index].callbacks.filter((cb) => cb !== callback) : []; } authenticate() { return Auth._handleDesktopAuthentication(); } dismiss() { Auth._emit("__dismiss"); } logout() { var _a, _b; Auth._isAuthenticated = false; Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]); (_a = Auth._account) === null || _a === void 0 ? void 0 : _a.destroyLastUserLoggedKey(); (_b = Auth._account) === null || _b === void 0 ? void 0 : _b.emptyActiveWallets(); Auth._account = null; Auth.authToken = null; Chat.getInstance().disconnect(); Auth._emit("logout"); //tells to Loopz listeners to logout } isAuthenticated() { return Auth._isAuthenticated; } getAuthInfo() { return Auth._authInfo; } getCurrentAccount() { return Auth.account; } static recoverAccountFromLocalDB() { return __awaiter(this, void 0, void 0, function* () { if (!Auth._config || !Auth._instance || !Auth._client) throw new Error("Auth has not been configured"); if (Auth._account) return; const lastUserLoggedKey = window.localStorage.getItem(CLIENT_DB_KEY_LAST_USER_LOGGED); if (!lastUserLoggedKey) return; try { const { did, organizationId, token } = JSON.parse(lastUserLoggedKey); const user = yield Auth._storage.get("user", "[did+organizationId]", [ did, organizationId, ]); Chat.getInstance().setCanChat(user.e2ePublicKey && user.e2ePublicKey.length > 0 && user.e2eEncryptedPrivateKey && user.e2eEncryptedPrivateKey.length > 0); Auth.authToken = token; Auth._isAuthenticated = true; Auth.account = new Account({ enableDevMode: Auth._config.devMode, storage: Auth._config.storage, did: user.did, organizationId: user.organizationId, didPrivy: user.didPrivy, token: Auth.authToken, //we use Auth.authToken and not token because in case we have a 401 error, variable 'token' has the value of the previous token. Instead Auth.authToken has the updated value. walletAddress: user.wallet.address, walletConnectorType: user.wallet.connectorType, walletImported: user.wallet.imported, walletRecoveryMethod: user.wallet.recoveryMethod, walletClientType: user.wallet.clientType, appleSubject: user.apple ? user.apple.subject : null, appleEmail: user.apple ? user.apple.email : null, discordSubject: user.discord ? user.discord.subject : null, discordEmail: user.discord ? user.discord.email : null, discordUsername: user.discord ? user.discord.username : null, farcasterFid: user.farcaster ? user.farcaster.fid : null, farcasterDisplayName: user.farcaster ? user.farcaster.displayName : null, farcasterOwnerAddress: user.farcaster ? user.farcaster.ownerAddress : null, farcasterPfp: user.farcaster ? user.farcaster.pfp : null, farcasterSignerPublicKey: user.farcaster ? user.farcaster.signerPublicKey : null, farcasterUrl: user.farcaster ? user.farcaster.url : null, farcasterUsername: user.farcaster ? user.farcaster.username : null, githubSubject: user.github ? user.github.subject : null, githubEmail: user.github ? user.github.email : null, githubName: user.github ? user.github.name : null, githubUsername: user.github ? user.github.username : null, googleEmail: user.google ? user.google.email : null, googleName: user.github ? user.google.name : null, googleSubject: user.github ? user.google.subject : null, instagramSubject: user.instagram ? user.instagram.subject : null, instagramUsername: user.instagram ? user.instagram.username : null, linkedinEmail: user.linkedin ? user.linkedin.email : null, linkedinName: user.linkedin ? user.linkedin.name : null, linkedinSubject: user.linkedin ? user.linkedin.subject : null, linkedinVanityName: user.linkedin ? user.linkedin.vanityName : null, spotifyEmail: user.spotify ? user.spotify.email : null, spotifyName: user.spotify ? user.spotify.name : null, spotifySubject: user.spotify ? user.spotifySubject : null, telegramFirstName: user.telegram ? user.telegram.firstName : null, telegramLastName: user.telegram ? user.telegram.lastName : null, telegramPhotoUrl: user.telegram ? user.telegram.photoUrl : null, telegramUserId: user.telegram ? user.telegram.userId : null, telegramUsername: user.telegram ? user.telegram.username : null, tiktokName: user.tiktok ? user.tiktok.name : null, tiktokSubject: user.tiktok ? user.tiktok.subject : null, tiktokUsername: user.tiktok ? user.tiktok.username : null, twitterName: user.twitter ? user.twitter.name : null, twitterSubject: user.twitter ? user.twitter.subject : null, twitterProfilePictureUrl: user.twitter ? user.twitter.profilePictureUrl : null, twitterUsername: user.twitter ? user.twitter.username : null, dynamoDBUserID: user.dynamoDBUserID, username: user.username, email: user.email, bio: user.bio, instagramPublicUrl: user.instagramPublicUrl, xPublicUrl: user.xPublicUrl, tiktokPublicUrl: user.tiktokPublicUrl, personalWebsiteUrl: user.personalWebsiteUrl, firstLogin: user.firstLogin, avatarUrl: user.avatarUrl, bannerImageUrl: user.bannerImageUrl, imageSettings: user.imageSettings, city: user.city, country: user.country, lat: user.lat, lng: user.lng, isCreator: user.isCreator, gender: user.gender, phone: user.phone ? user.phone : null, isVerified: user.isVerified, signupCompleted: user.signupCompleted, isPfpNft: user.isPfpNft, pfp: user.pfp ? user.pfp : null, proposalNotificationPush: user.proposalNotificationPush, proposalNotificationSystem: user.proposalNotificationSystem, orderNotificationPush: user.orderNotificationPush, orderNotificationSystem: user.orderNotificationSystem, followNotificationPush: user.followNotificationPush, followNotificationSystem: user.followNotificationSystem, collectionNotificationPush: user.collectionNotificationPush, collectionNotificationSystem: user.collectionNotificationSystem, generalNotificationPush: user.generalNotificationPush, generalNotificationSystem: user.generalNotificationSystem, accountSuspended: user.accountSuspended, e2ePublicKey: user.e2ePublicKey, e2eSecret: "", //deprecated e2eSecretIV: "", //deprecated createdAt: user.createdAt, updatedAt: user.updatedAt ? user.updatedAt : null, deletedAt: user.deletedAt ? user.deletedAt : null, allowNotification: user.allowNotification, allowNotificationSound: user.allowNotificationSound, visibility: user.visibility, onlineStatus: user.onlineStatus, allowReadReceipt: user.allowReadReceipt, allowReceiveMessageFrom: user.allowReceiveMessageFrom, allowAddToGroupsFrom: user.allowAddToGroupsFrom, allowGroupsSuggestion: user.allowGroupsSuggestion, }); } catch (error) { console.log("Error during rebuilding phase for account."); console.error(error); if ("statusCode" in error && error.statusCode === 401) Auth._instance.logout(); } }); } static fetchAuthToken() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; try { Auth._config && Auth._config.devMode && console.log("fetch new token"); const token = yield refreshToken(Auth._config.devMode); Auth._config && Auth._config.devMode && console.log("old real time auth token is", Auth._realtimeAuthorizationToken); Auth._config && Auth._config.devMode && console.log("old token is ", Auth.authToken); Auth._config && Auth._config.devMode && console.log("new token is ", token); Auth.authToken = token; Auth._config && Auth._config.devMode && console.log("confirmation for new token ", Auth.authToken); Auth._config && Auth._config.devMode && console.log("confirmation for new real time auth token ", Auth._realtimeAuthorizationToken); let lastUserLoggedKey = window.localStorage.getItem(CLIENT_DB_KEY_LAST_USER_LOGGED); if (!token) throw new Error("Impossible to refresh the token."); if (!lastUserLoggedKey) throw new Error("Impossible to detect a logged user key."); Auth._config && Auth._config.devMode && console.log("lastuserlogged key from local storage is ", JSON.parse(lastUserLoggedKey)); lastUserLoggedKey = JSON.parse(lastUserLoggedKey); lastUserLoggedKey.token = token; window.localStorage.setItem(CLIENT_DB_KEY_LAST_USER_LOGGED, JSON.stringify(lastUserLoggedKey)); Auth._config && Auth._config.devMode && console.log("setItem performed on local storage with value", lastUserLoggedKey); } catch (error) { console.log("[fetchAuthToken error]:", error); console.log("logging out..."); (_a = Auth._instance) === null || _a === void 0 ? void 0 : _a.logout(); (_b = Chat.getInstance().isConnected()) !== null && _b !== void 0 ? _b : Chat.getInstance().disconnect(); throw error; } }); } } Auth._config = null; Auth._instance = null; Auth._client = null; Auth._realtimeAuthorizationToken = null; Auth._authToken = null; Auth._account = null; Auth._authInfo = null; Auth._eventsCallbacks = []; Auth._isAuthenticated = false; Auth.fetchTokenAttemptsRealtime = 0; Auth.prevToken = null; //# sourceMappingURL=auth.js.map