feat: support Web3-Onboard provider (#2209)

* feat: add Web3-Onboard idp

* feat: update Web3-Onboard logo

* feat: update package.json

* feat: remove unused package

* feat: add yarn build param --max_old_space_size=4096

* feat: remove log

* feat: add Wallet configure

* feat: remove hardware wallets
This commit is contained in:
haiwu
2023-08-13 23:58:57 +08:00
committed by GitHub
parent 80b0d26813
commit 891e8e21d8
15 changed files with 2799 additions and 70 deletions

View File

@ -351,7 +351,7 @@ class App extends Component {
}
 
 
{Setting.isMobile() ? null : Setting.getNameAtLeast(this.state.account.displayName)} &nbsp; <DownOutlined />
{Setting.isMobile() ? null : Setting.getShortText(Setting.getNameAtLeast(this.state.account.displayName), 30)} &nbsp; <DownOutlined />
&nbsp;
&nbsp;
&nbsp;

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import {Button, Card, Checkbox, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as ProviderBackend from "./backend/ProviderBackend";
import * as Setting from "./Setting";
@ -25,6 +25,7 @@ import copy from "copy-to-clipboard";
import {CaptchaPreview} from "./common/CaptchaPreview";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
import * as Web3Auth from "./auth/Web3Auth";
const {Option} = Select;
const {TextArea} = Input;
@ -1011,6 +1012,30 @@ class ProviderEditPage extends React.Component {
</Row>
) : null
}
{
this.state.provider.type === "Web3Onboard" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Wallets"), i18next.t("provider:Wallets - Tooltip"))} :
</Col>
<Col span={22}>
<Checkbox.Group
options={Web3Auth.getWeb3OnboardWalletsOptions()}
value={() => {
try {
return JSON.parse(this.state.provider.metadata);
} catch {
return ["injected"];
}
}}
onChange={options => {
this.updateProviderField("metadata", JSON.stringify(options));
}}
/>
</Col>
</Row>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :

View File

@ -259,6 +259,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_metamask.svg`,
url: "https://metamask.io/",
},
"Web3Onboard": {
logo: `${StaticBaseUrl}/img/social_web3onboard.svg`,
url: "https://onboard.blocknative.com/",
},
},
};
@ -943,6 +947,7 @@ export function getProviderTypeOptions(category) {
} else if (category === "Web3") {
return ([
{id: "MetaMask", name: "MetaMask"},
{id: "Web3Onboard", name: "Web3-Onboard"},
]);
} else {
return [];

View File

@ -95,7 +95,7 @@ class AuthCallback extends React.Component {
if (code === null) {
code = params.get("authCode");
}
// The code for Metamask is the JSON-serialized string of Web3AuthToken
// The code for Web3 is the JSON-serialized string of Web3AuthToken
// Due to the limited length of URLs, we only pass the web3AuthTokenKey
if (code === null) {
code = params.get("web3AuthTokenKey");

View File

@ -321,6 +321,10 @@ const authInfo = {
scope: "",
endpoint: "",
},
Web3Onboard: {
scope: "",
endpoint: "",
},
};
export function getProviderUrl(provider) {
@ -465,5 +469,7 @@ export function getAuthUrl(application, provider, method) {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
} else if (provider.type === "MetaMask") {
return `${redirectUri}?state=${state}`;
} else if (provider.type === "Web3Onboard") {
return `${redirectUri}?state=${state}`;
}
}

View File

@ -17,7 +17,7 @@ import i18next from "i18next";
import * as Provider from "./Provider";
import {getProviderLogoURL} from "../Setting";
import {GithubLoginButton, GoogleLoginButton} from "react-social-login-buttons";
import {authViaMetaMask} from "./Web3Auth";
import {authViaMetaMask, authViaWeb3Onboard} from "./Web3Auth";
import QqLoginButton from "./QqLoginButton";
import FacebookLoginButton from "./FacebookLoginButton";
import WeiboLoginButton from "./WeiboLoginButton";
@ -121,6 +121,8 @@ function goToSamlUrl(provider, location) {
export function goToWeb3Url(application, provider, method) {
if (provider.type === "MetaMask") {
authViaMetaMask(application, provider, method);
} else if (provider.type === "Web3Onboard") {
authViaWeb3Onboard(application, provider, method);
}
}

View File

@ -18,7 +18,25 @@ import {v4 as uuidv4} from "uuid";
import {SignTypedDataVersion, recoverTypedSignature} from "@metamask/eth-sig-util";
import {getAuthUrl} from "./Provider";
import {Buffer} from "buffer";
// import {toChecksumAddress} from "ethereumjs-util";
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() {
@ -147,3 +165,174 @@ export async function authViaMetaMask(application, provider, method) {
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://wallet.coinbase.com/"},
],
};
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}`);
}
}

View File

@ -97,7 +97,7 @@ class OAuthWidget extends React.Component {
// should add the unlink user's info, cause the user may not be logged in, but a admin want to unlink the user.
user: this.props.user,
};
if (providerType === "MetaMask") {
if (providerType === "MetaMask" || providerType === "Web3Onboard") {
delWeb3AuthToken(linkedValue);
}
AuthBackend.unlink(body)
@ -158,7 +158,12 @@ class OAuthWidget extends React.Component {
</Col>
<Col span={24 - this.props.labelSpan} >
<AccountAvatar style={{marginRight: "10px"}} size={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
<span style={{width: this.props.labelSpan === 3 ? "300px" : "200px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}>
<span style={{
width: this.props.labelSpan === 3 ? "300px" : "200px",
display: (Setting.isMobile()) ? "inline" : "inline-block",
overflow: "hidden",
textOverflow: "ellipsis",
}} title={name}>
{
linkedValue === "" ? (
`(${i18next.t("general:empty")})`