Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openlogin v6 upgrade #443

Merged
merged 15 commits into from
Jan 3, 2024
Merged
12 changes: 2 additions & 10 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,9 @@ module.exports = {
ecmaVersion: 2022,
project: "./tsconfig.json",
},
ignorePatterns: [
"*.cjs",
"*.config.js",
"vite.config.ts",
"importLocales.js",
"__generated__",
".eslintrc.js",
"crisp.js",
"tests/setup.js"
],
ignorePatterns: ["*.cjs", "*.config.js", "vite.config.ts", "importLocales.js", "__generated__", ".eslintrc.js", "crisp.js", "tests/setup.js"],
rules: {
"@typescript-eslint/no-explicit-any": 1,
camelcase: 0,
"no-new": 0,
"vue/multi-word-component-names": 0,
Expand Down
2,298 changes: 977 additions & 1,321 deletions package-lock.json

Large diffs are not rendered by default.

103 changes: 52 additions & 51 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,37 @@
"serve:prod": "vue-cli-service serve --mode production"
},
"dependencies": {
"@bonfida/spl-name-service": "^1.0.11",
"@gtm-support/vue-gtm": "^2.1.0",
"@headlessui/vue": "^1.6.7",
"@bonfida/spl-name-service": "^1.5.0",
"@gtm-support/vue-gtm": "^2.2.0",
"@headlessui/vue": "^1.7.16",
"@heroicons/vue": "^1.0.6",
"@project-serum/borsh": "^0.2.5",
"@sentry/browser": "^7.68.0",
"@sentry/tracing": "^7.68.0",
"@sentry/vue": "^7.68.0",
"@sentry/browser": "^7.77.0",
"@sentry/tracing": "^7.77.0",
"@sentry/vue": "^7.77.0",
"@solana/pay": "^0.2.5",
"@solana/qr-code-styling": "^1.6.0",
"@solana/spl-name-service": "^0.1.4",
"@solana/spl-name-service": "^0.2.0",
"@solana/spl-token": "0.3.8",
"@solana/web3.js": "^1.78.4",
"@toruslabs/base-controllers": "^3.0.3",
"@solana/web3.js": "^1.87.3",
"@toruslabs/base-controllers": "^4.11.0",
"@toruslabs/broadcast-channel": "^9.0.1",
"@toruslabs/eccrypto": "^4.0.0",
"@toruslabs/http-helpers": "^5.0.0",
"@toruslabs/http-helpers": "^6.0.0",
"@toruslabs/loglevel-sentry": "^6.0.1",
"@toruslabs/metadata-helpers": "^5.0.0",
"@toruslabs/openlogin": "^4.7.0",
"@toruslabs/openlogin-ed25519": "^2.0.0",
"@toruslabs/openlogin-jrpc": "^4.7.0",
"@toruslabs/openlogin-subkey": "^4.2.0",
"@toruslabs/openlogin-utils": "^4.7.0",
"@toruslabs/solana-controllers": "^3.0.3",
"@toruslabs/openlogin": "^6.0.0",
"@toruslabs/openlogin-ed25519": "^6.0.0",
"@toruslabs/openlogin-jrpc": "^6.1.0",
"@toruslabs/openlogin-session-manager": "^3.0.0",
"@toruslabs/openlogin-subkey": "^6.0.0",
"@toruslabs/openlogin-utils": "^6.1.0",
"@toruslabs/solana-controllers": "^4.11.0",
"@toruslabs/tweetnacl-js": "^1.0.4",
"@toruslabs/vue-components": "^2.0.1",
"@toruslabs/vue-icons": "^2.0.0",
"@types/bn.js": "^5.1.1",
"@types/elliptic": "^6.4.14",
"@types/bn.js": "^5.1.4",
"@types/elliptic": "^6.4.16",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"async-mutex": "^0.4.0",
Expand All @@ -70,68 +71,68 @@
"readable-stream": "^4.4.2",
"register-service-worker": "^1.7.2",
"safe-stable-stringify": "^2.4.3",
"vue": "^3.3.4",
"vue": "^3.3.7",
"vue-gtag": "^2.0.1",
"vue-i18n": "^9.3.0",
"vue-router": "^4.2.4",
"vue-i18n": "^9.6.5",
"vue-router": "^4.2.5",
"vuex": "^4.1.0",
"vuex-module-decorators": "^2.0.0"
},
"devDependencies": {
"@babel/register": "^7.22.15",
"@babel/runtime": "^7.22.15",
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@commitlint/is-ignored": "^17.7.0",
"@babel/runtime": "^7.23.2",
"@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^18.1.0",
"@commitlint/is-ignored": "^18.1.0",
"@istanbuljs/nyc-config-babel": "^3.0.0",
"@metaplex-foundation/mpl-token-metadata": "^1.1.0",
"@playwright/test": "^1.37.1",
"@sentry/cli": "^2.20.6",
"@playwright/test": "^1.39.0",
"@sentry/cli": "^2.21.2",
"@tailwindcss/forms": "^0.5.6",
"@toruslabs/config": "^2.0.2",
"@toruslabs/eslint-config-vue": "^3.0.1",
"@toruslabs/eslint-config-vue": "^3.0.2",
"@toruslabs/tweetnacl-js": "^1.0.4",
"@types/bs58": "^4.0.1",
"@types/color": "^3.0.4",
"@types/dateformat": "^5.0.0",
"@types/jest": "^29.5.4",
"@types/json-stable-stringify": "^1.0.34",
"@types/lodash-es": "^4.17.9",
"@types/mocha": "^10.0.1",
"@types/node": "^20.5.9",
"@types/pump": "^1.1.1",
"@types/readable-stream": "^4.0.2",
"@types/sinon": "^10.0.16",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@types/bs58": "^4.0.3",
"@types/color": "^3.0.5",
"@types/dateformat": "^5.0.1",
"@types/jest": "^29.5.7",
"@types/json-stable-stringify": "^1.0.35",
"@types/lodash-es": "^4.17.10",
"@types/mocha": "^10.0.3",
"@types/node": "^20.8.10",
"@types/pump": "^1.1.2",
"@types/readable-stream": "^4.0.4",
"@types/sinon": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-pwa": "^5.0.8",
"@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-typescript": "~5.0.8",
"@vue/cli-plugin-vuex": "^5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/compiler-sfc": "^3.3.4",
"@vue/compiler-sfc": "^3.3.7",
"assert": "^2.1.0",
"autoprefixer": "^10.4.15",
"axios": "^1.5.0",
"autoprefixer": "^10.4.16",
"axios": "^1.6.0",
"bn.js": "^5.2.1",
"browserstack-local": "^1.5.4",
"browserstack-local": "^1.5.5",
"dotenv": "^16.3.1",
"elliptic": "^6.5.4",
"eslint": "^8.48.0",
"eslint": "^8.53.0",
"husky": "^8.0.3",
"jsdom": "^22.1.0",
"jsdom-global": "^3.0.2",
"lint-staged": "^14.0.1",
"lint-staged": "^15.0.2",
"mocha": "^10.2.0",
"nock": "^13.3.3",
"nock": "^13.3.8",
"nyc": "^15.1.0",
"postcss": "^8.4.29",
"postcss": "^8.4.31",
"prettier": "^3.0.3",
"process": "^0.11.10",
"sinon": "^15.2.0",
"tailwindcss": "^3.3.3",
"sinon": "^17.0.1",
"tailwindcss": "^3.3.5",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
Expand Down
53 changes: 50 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,60 @@
<script setup lang="ts">
import log from "loglevel";
import { onBeforeMount } from "vue";

import { Toast } from "@/components/common";

import ControllerModule from "./modules/controllers";
import OpenLoginFactory from "./auth/OpenLogin";
import ControllerModule, { torus } from "./modules/controllers";
import { hideCrispButton, isMain } from "./utils/helpers";

onBeforeMount(() => {
if (isMain) ControllerModule.init({ origin: window.location.origin });
onBeforeMount(async () => {
if (isMain) {
ControllerModule.setIsRehydrating(true);
try {
const openloginInstance = await OpenLoginFactory.getInstance(true);
ControllerModule.init({ origin: window.location.origin });

const result = await OpenLoginFactory.computeAccount().catch((err) => {
log.error(err);
return null;
});

// rehydration
if (result?.accounts.length) {
const userDapp = new Map();
const addAccountPromises = result.accounts.map(async (account) => {
userDapp.set(account.address, account.app);

const address = await torus.addAccount(
account.solanaPrivKey,
{
email: "",
name: "",
profileImage: "",
...openloginInstance.getUserInfo(),
},
true
);
return address;
});
torus.update({
UserDapp: userDapp,
});
// await Promise.all(addAccountPromises);
try {
const address = await addAccountPromises[result.matchedDappHost];
await ControllerModule.setSelectedAccount(address);
} catch (error) {
log.error(error);
ControllerModule.setIsRehydrating(false);
}
}
} catch (error) {
log.error(error);
}
ControllerModule.setIsRehydrating(false);
}

// hide crispbutton on inital load
hideCrispButton();
Expand Down
129 changes: 128 additions & 1 deletion src/auth/OpenLogin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { Keypair } from "@solana/web3.js";
import { get } from "@toruslabs/http-helpers";
import OpenLogin from "@toruslabs/openlogin";
import { getED25519Key } from "@toruslabs/openlogin-ed25519";
import { OpenloginSessionManager } from "@toruslabs/openlogin-session-manager";
import { subkey } from "@toruslabs/openlogin-subkey";
import { Mutex } from "async-mutex";
import base58 from "bs58";
import log from "loglevel";

import config from "@/config";
import { APPLE, ProjectAccountType } from "@/utils/enums";
import { generateTorusAuthHeaders } from "@/utils/helpers";

class OpenLoginFactory {
private static instance: OpenLogin;

private static mutex = new Mutex();

public static async getInstance(): Promise<OpenLogin> {
public static async getInstance(reinitialize = false): Promise<OpenLogin> {
const releaseLock = await this.mutex.acquire();
try {
if (!OpenLoginFactory.instance) {
Expand All @@ -29,6 +37,9 @@

OpenLoginFactory.instance = instance;
}
if (reinitialize) {
await OpenLoginFactory.instance.init();
}
} catch (error) {
log.error(error, "Unable to create openlogin instance");
throw error;
Expand All @@ -37,5 +48,121 @@
}
return OpenLoginFactory.instance;
}

public static async computeAccount() {
const instance = await OpenLoginFactory.getInstance();
if (!instance || !instance.state.sessionId) {
throw new Error("Openlogin instance/session not found");
}

const openLoginState = instance.state;
const { tKey, oAuthPrivateKey, ed25519PrivKey } = openLoginState;

if (!ed25519PrivKey) {
throw new Error("Login unsuccessful");
}

const userInfo = instance.getUserInfo();

// const { sk: secretKey } = getED25519Key(privKey.padStart(64, "0"));
const secretKey = Buffer.from(ed25519PrivKey!, "hex");
const typeOfLoginDisplay = userInfo.typeOfLogin.charAt(0).toUpperCase() + userInfo.typeOfLogin.slice(1);
const accountDisplay = (userInfo.typeOfLogin !== APPLE && userInfo.email) || userInfo.name;
const mainKeyPair = Keypair.fromSecretKey(secretKey);
const accounts: ProjectAccountType[] = [];
accounts.push({
app: `${typeOfLoginDisplay} ${accountDisplay}`,
solanaPrivKey: base58.encode(mainKeyPair.secretKey),
privKey: ed25519PrivKey,
name: `Solana Wallet ${window.location.origin}`,
address: `${mainKeyPair.publicKey.toBase58()}`,
});

// derive app scoped keys from tkey
const userDapps: Record<string, string> = {};

// let matchedDappHost = -1;
let matchedDappHost = 0;
const dappOrigin = sessionStorage.getItem("dappOrigin") || window.location.origin;
const dappHost = new URL(dappOrigin);

if (tKey && oAuthPrivateKey) {
try {
// projects are stored on oAuthPrivateKey but subkey is derived from tkey

const headers = generateTorusAuthHeaders(oAuthPrivateKey);
log.info(headers, "headers");
const response = await get<{ user_projects: [{ last_login: string; project_id: string; hostname: string; name: string }] }>(
`${config.developerDashboardUrl}/projects/user-projects?chain_namespace=solana`,
{
headers,
}
);
log.info(response, "User projects from developer dashboard");
const userProjects = response.user_projects ?? [];
userProjects.sort((a, b) => (a.last_login < b.last_login ? 1 : -1));
userProjects.forEach((project, idx) => {
const subKey = subkey(tKey, Buffer.from(project.project_id, "base64"));
const paddedSubKey = subKey.padStart(64, "0");
const { sk } = getED25519Key(paddedSubKey);
const keyPair = Keypair.fromSecretKey(sk);
userDapps[keyPair.publicKey.toBase58()] = `${project.name} (${project.hostname})`;
accounts.push({
app: `${project.name}`,
solanaPrivKey: base58.encode(keyPair.secretKey),
privKey: paddedSubKey,
name: `${project.name} (${project.hostname})`,
address: keyPair.publicKey.toBase58(),
});

// eslint-disable-next-line no-console
console.log(project);
if (dappHost.host === project.hostname) matchedDappHost = idx + 1;
});
} catch (error2: unknown) {
log.error("Failed to derive app-scoped keys", error2);
}
}

return {
accounts,
userDapps,
matchedDappHost,
};
}
}
export async function updateSession(sessionData: any) {

Check warning on line 134 in src/auth/OpenLogin.ts

View workflow job for this annotation

GitHub Actions / build (20.x, ubuntu-latest)

Unexpected any. Specify a different type
try {
const instance = await OpenLoginFactory.getInstance();
if (instance.sessionId) {
const sessionManager = new OpenloginSessionManager({
sessionId: instance.sessionId,
sessionNamespace: instance.sessionNamespace,
});

await sessionManager.updateSession(sessionData);
}
} catch (error) {
log.warn(error);
}
}

export async function createSession(sessionId: string, data: any) {

Check warning on line 150 in src/auth/OpenLogin.ts

View workflow job for this annotation

GitHub Actions / build (20.x, ubuntu-latest)

Unexpected any. Specify a different type
if (!sessionId) throw new Error("Session Id is required");
try {
const sessionManager = new OpenloginSessionManager({
sessionId,
sessionNamespace: (await OpenLoginFactory.getInstance()).sessionNamespace,
});

await sessionManager.createSession(data);
} catch (error) {
log.error(error);
}
}

export async function invalidateSession() {
await (await OpenLoginFactory.getInstance()).logout();
}

export default OpenLoginFactory;
Loading
Loading