// // Copyright 2023 The Casdoor Authors. All Rights Reserved. // // // // Licensed under the Apache License, Version 2.0 (the "License"); // // you may not use this file except in compliance with the License. // // You may obtain a copy of the License at // // // // http://www.apache.org/licenses/LICENSE-2.0 // // // // Unless required by applicable law or agreed to in writing, software // // distributed under the License is distributed on an "AS IS" BASIS, // // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // // See the License for the specific language governing permissions and // // limitations under the License. import {goToLink, showMessage} from "../Setting"; import i18next from "i18next"; import {v4 as uuidv4} from "uuid"; import {SignTypedDataVersion, recoverTypedSignature} from "@metamask/eth-sig-util"; import {getAuthUrl} from "./Provider"; import {Buffer} from "buffer"; import Onboard from "@web3-onboard/core"; import injectedModule from "@web3-onboard/injected-wallets"; import infinityWalletModule from "@web3-onboard/infinity-wallet"; import sequenceModule from "@web3-onboard/sequence"; import trustModule from "@web3-onboard/trust"; import frontierModule from "@web3-onboard/frontier"; import tahoModule from "@web3-onboard/taho"; import coinbaseModule from "@web3-onboard/coinbase"; import gnosisModule from "@web3-onboard/gnosis"; // import keystoneModule from "@web3-onboard/keystone"; // import keepkeyModule from "@web3-onboard/keepkey"; // import dcentModule from "@web3-onboard/dcent"; // import ledgerModule from "@web3-onboard/ledger"; // import trezorModule from "@web3-onboard/trezor"; // import walletConnectModule from "@web3-onboard/walletconnect"; // import fortmaticModule from "@web3-onboard/fortmatic"; // import portisModule from "@web3-onboard/portis"; // import magicModule from "@web3-onboard/magic"; global.Buffer = Buffer; export function generateNonce() { const nonce = uuidv4(); return nonce; } export function getWeb3AuthTokenKey(address) { return `Web3AuthToken_${address}`; } export function setWeb3AuthToken(token) { const key = getWeb3AuthTokenKey(token.address); localStorage.setItem(key, JSON.stringify(token)); } export function getWeb3AuthToken(address) { const key = getWeb3AuthTokenKey(address); return JSON.parse(localStorage.getItem(key)); } export function delWeb3AuthToken(address) { const key = getWeb3AuthTokenKey(address); localStorage.removeItem(key); } export function clearWeb3AuthToken() { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith("Web3AuthToken_")) { localStorage.removeItem(key); } }); } export function detectMetaMaskPlugin() { // check if ethereum extension MetaMask is installed return window.ethereum && window.ethereum.isMetaMask; } export function requestEthereumAccount() { const method = "eth_requestAccounts"; const selectedAccount = window.ethereum.request({method}) .then((accounts) => { return accounts[0]; }); return selectedAccount; } export function signEthereumTypedData(from, nonce) { // https://docs.metamask.io/wallet/how-to/sign-data/ const date = new Date(); const typedData = JSON.stringify({ domain: { chainId: window.ethereum.chainId, name: "Casdoor", version: "1", }, message: { prompt: "In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.", nonce: nonce, createAt: `${date.toLocaleString()}`, }, primaryType: "AuthRequest", types: { EIP712Domain: [ {name: "name", type: "string"}, {name: "version", type: "string"}, {name: "chainId", type: "uint256"}, ], AuthRequest: [ {name: "prompt", type: "string"}, {name: "nonce", type: "string"}, {name: "createAt", type: "string"}, ], }, }); const method = "eth_signTypedData_v4"; const params = [from, typedData]; return window.ethereum.request({method, params}) .then((sign) => { return { address: from, createAt: Math.floor(date.getTime() / 1000), typedData: typedData, signature: sign, }; }); } export function checkEthereumSignedTypedData(token) { if (token === undefined || token === null) { return false; } if (token.address && token.typedData && token.signature) { const recoveredAddr = recoverTypedSignature({ data: JSON.parse(token.typedData), signature: token.signature, version: SignTypedDataVersion.V4, }); // const recoveredAddr = token.address; return recoveredAddr === token.address; // return toChecksumAddress(recoveredAddr) === toChecksumAddress(token.address); } return false; } export async function authViaMetaMask(application, provider, method) { if (!detectMetaMaskPlugin()) { showMessage("error", `${i18next.t("login:MetaMask plugin not detected")}`); return; } try { const account = await requestEthereumAccount(); let token = getWeb3AuthToken(account); if (!checkEthereumSignedTypedData(token)) { const nonce = generateNonce(); token = await signEthereumTypedData(account, nonce); setWeb3AuthToken(token); } const redirectUri = `${getAuthUrl(application, provider, method)}&web3AuthTokenKey=${getWeb3AuthTokenKey(account)}`; goToLink(redirectUri); } catch (err) { showMessage("error", `${i18next.t("login:Failed to obtain MetaMask authorization")}: ${err.message}`); } } const web3Wallets = { // injected wallets injected: { label: "Injected", wallet: injectedModule(), }, // sdk wallets coinbase: { label: "Coinbase", wallet: coinbaseModule(), }, trust: { label: "Trust", wallet: trustModule(), }, gnosis: { label: "Gnosis", wallet: gnosisModule(), }, sequence: { label: "Sequence", wallet: sequenceModule(), }, taho: { label: "Taho", wallet: tahoModule(), }, frontier: { label: "Frontier", wallet: frontierModule(), }, infinityWallet: { label: "Infinity Wallet", wallet: infinityWalletModule(), }, // hardware wallets // keystone: { // label: "Keystone", // wallet: keystoneModule(), // }, // keepkey: { // label: "KeepKey", // wallet: keepkeyModule(), // }, // dcent: { // label: "D'CENT", // wallet: dcentModule(), // }, // some wallet need custome `apiKey` or `projectId` configure item // const magic = magicModule({ // apiKey: "magicApiKey", // }); // const fortmatic = fortmaticModule({ // apiKey: "fortmaticApiKey", // }); // const portis = portisModule({ // apiKey: "portisApiKey", // }); // const ledger = ledgerModule({ // projectId: "ledgerProjectId" // }); // const walletConnect = walletConnectModule({ // projectId: "walletConnectProjectId", // }); }; export function getWeb3OnboardWalletsOptions() { return Object.entries(web3Wallets).map(([key, value]) => ({ label: value.label, value: key, })); } function getWeb3OnboardWallets(options) { if (options === null || options === undefined || !Array.isArray(options)) { return []; } return options.map(walletType => { if (walletType && web3Wallets[walletType]?.wallet) { return web3Wallets[walletType]?.wallet; } }); } export function initWeb3Onboard(application, provider) { // init wallet // options = ["injected","coinbase",...] const options = JSON.parse(provider.metadata); const wallets = getWeb3OnboardWallets(options); // init chain // const InfuraKey = "2fa45cbe531e4e65be4fcbf408e651a8"; const chains = [ // { // id: "0x1", // token: "ETH", // label: "Ethereum Mainnet", // rpcUrl: `https://mainnet.infura.io/v3/${InfuraKey}`, // }, // { // id: "0x5", // token: "ETH", // label: "Goerli", // rpcUrl: `https://goerli.infura.io/v3/${InfuraKey}`, // }, { id: "0x13881", token: "MATIC", label: "Polygon - Mumbai", rpcUrl: "https://matic-mumbai.chainstacklabs.com", }, { id: "0x38", token: "BNB", label: "Binance", rpcUrl: "https://bsc-dataseed.binance.org/", }, { id: "0xA", token: "OETH", label: "Optimism", rpcUrl: "https://mainnet.optimism.io", }, { id: "0xA4B1", token: "ARB-ETH", label: "Arbitrum", rpcUrl: "https://rpc.ankr.com/arbitrum", }, ]; const appMetadata = { name: "Casdoor", description: "Connect a wallet using Casdoor", recommendedInjectedWallets: [ {name: "MetaMask", url: "https://metamask.io"}, {name: "Coinbase", url: "https://www.coinbase.com/wallet"}, ], }; const web3Onboard = Onboard({ wallets, chains, appMetadata, }); return web3Onboard; } export async function authViaWeb3Onboard(application, provider, method) { try { const onboard = initWeb3Onboard(application, provider); const connectedWallets = await onboard.connectWallet(); if (connectedWallets.length > 0) { const wallet = connectedWallets[0]; const account = wallet.accounts[0]; const address = account.address; const token = { address: address, // e.g."0xbd5444d31fe4139ee36bea29e43d4ac67ae276de" walletType: wallet.label, // e.g."MetaMask" createAt: Math.floor(new Date().getTime() / 1000), }; setWeb3AuthToken(token); const redirectUri = `${getAuthUrl(application, provider, method)}&web3AuthTokenKey=${getWeb3AuthTokenKey(address)}`; goToLink(redirectUri); } } catch (err) { showMessage("error", `${i18next.t("login:Failed to obtain Web3-Onboard authorization")}: ${err}`); } }