diff --git a/controllers/auth.go b/controllers/auth.go index 07650d4b..400b4d17 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -422,7 +422,7 @@ func (c *ApiController) Login() { c.ResponseError(err.Error()) return } - } else if provider.Category == "OAuth" { + } else if provider.Category == "OAuth" || provider.Category == "Web3" { // OAuth idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri) @@ -465,7 +465,7 @@ func (c *ApiController) Login() { c.ResponseError(err.Error()) return } - } else if provider.Category == "OAuth" { + } else if provider.Category == "OAuth" || provider.Category == "Web3" { user, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id) if err != nil { c.ResponseError(err.Error()) @@ -486,7 +486,7 @@ func (c *ApiController) Login() { record.Organization = application.Organization record.User = user.Name util.SafeGoroutine(func() { object.AddRecord(record) }) - } else if provider.Category == "OAuth" { + } else if provider.Category == "OAuth" || provider.Category == "Web3" { // Sign up via OAuth if application.EnableLinkWithEmail { if userInfo.Email != "" { diff --git a/go.sum b/go.sum index 3abda261..9022cc3e 100644 --- a/go.sum +++ b/go.sum @@ -546,8 +546,6 @@ github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80 github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM= -github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.12.0 h1:aRNHH0gtVfrpIaEolD0sWrLLRnYQNK4cH/bIAHwL8Rk= github.com/sashabaranov/go-openai v1.12.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= diff --git a/idp/metamask.go b/idp/metamask.go new file mode 100644 index 00000000..fe120a09 --- /dev/null +++ b/idp/metamask.go @@ -0,0 +1,80 @@ +// 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. + +package idp + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "golang.org/x/oauth2" +) + +const Web3AuthTokenKey = "web3AuthToken" + +type MetaMaskIdProvider struct { + Client *http.Client +} + +type Web3AuthToken struct { + Address string `json:"address"` + Nonce string `json:"nonce"` + CreateAt uint64 `json:"createAt"` + TypedData string `json:"typedData"` + Signature string `json:"signature"` // signature for typed data +} + +func NewMetaMaskIdProvider() *MetaMaskIdProvider { + idp := &MetaMaskIdProvider{} + return idp +} + +func (idp *MetaMaskIdProvider) SetHttpClient(client *http.Client) { + idp.Client = client +} + +func (idp *MetaMaskIdProvider) GetToken(code string) (*oauth2.Token, error) { + web3AuthToken := Web3AuthToken{} + if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil { + return nil, err + } + token := &oauth2.Token{ + AccessToken: web3AuthToken.Signature, + TokenType: "Bearer", + Expiry: time.Now().AddDate(0, 1, 0), + } + + token = token.WithExtra(map[string]interface{}{ + Web3AuthTokenKey: web3AuthToken, + }) + return token, nil +} + +func (idp *MetaMaskIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { + // TODO use "github.com/ethereum/go-ethereum" to check address's eth balance or transaction + web3AuthToken, ok := token.Extra(Web3AuthTokenKey).(Web3AuthToken) + if !ok { + return nil, errors.New("invalid web3AuthToken") + } + userInfo := &UserInfo{ + Id: web3AuthToken.Address, + Username: web3AuthToken.Address, + DisplayName: web3AuthToken.Address, + AvatarUrl: fmt.Sprintf("metamask:%v", web3AuthToken.Address), + } + return userInfo, nil +} diff --git a/idp/provider.go b/idp/provider.go index 9a9dadd1..e475bc34 100644 --- a/idp/provider.go +++ b/idp/provider.go @@ -109,6 +109,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider { return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) case "Bilibili": return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "MetaMask": + return NewMetaMaskIdProvider() default: if isGothSupport(idpInfo.Type) { return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) diff --git a/object/provider_item.go b/object/provider_item.go index 28586b73..ba809fe1 100644 --- a/object/provider_item.go +++ b/object/provider_item.go @@ -49,7 +49,7 @@ func (pi *ProviderItem) IsProviderVisible() bool { if pi.Provider == nil { return false } - return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML" + return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML" || pi.Provider.Category == "Web3" } func (pi *ProviderItem) isProviderPrompted() bool { diff --git a/object/user.go b/object/user.go index 97ed52cc..198f1678 100644 --- a/object/user.go +++ b/object/user.go @@ -156,6 +156,7 @@ type User struct { Yammer string `xorm:"yammer varchar(100)" json:"yammer"` Yandex string `xorm:"yandex varchar(100)" json:"yandex"` Zoom string `xorm:"zoom varchar(100)" json:"zoom"` + MetaMask string `xorm:"metamask varchar(100)" json:"metamask"` Custom string `xorm:"custom varchar(100)" json:"custom"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` diff --git a/web/craco.config.js b/web/craco.config.js index b8703bf5..632b1616 100644 --- a/web/craco.config.js +++ b/web/craco.config.js @@ -50,4 +50,32 @@ module.exports = { }, }, ], + webpack: { + // use polyfill Buffer with Webpack 5 + // https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5 + // https://craco.js.org/docs/configuration/webpack/ + configure: (webpackConfig, { env, paths }) => { + webpackConfig.resolve.fallback = { + // "process": require.resolve('process/browser'), + // "util": require.resolve("util/"), + // "url": require.resolve("url/"), + // "zlib": require.resolve("browserify-zlib"), + // "stream": require.resolve("stream-browserify"), + // "http": require.resolve("stream-http"), + // "https": require.resolve("https-browserify"), + // "assert": require.resolve("assert/"), + "buffer": require.resolve('buffer/'), + "process": false, + "util": false, + "url": false, + "zlib": false, + "stream": false, + "http": false, + "https": false, + "assert": false, + "buffer": false, + }; + return webpackConfig; + }, + } }; diff --git a/web/package.json b/web/package.json index 1a7c364f..bd3e16e9 100644 --- a/web/package.json +++ b/web/package.json @@ -9,11 +9,13 @@ "@crowdin/cli": "^3.7.10", "@ctrl/tinycolor": "^3.5.0", "@emotion/react": "^11.10.5", + "@metamask/eth-sig-util": "^6.0.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "antd": "5.2.3", "antd-token-previewer": "^1.1.0-22", + "buffer": "^6.0.3", "codemirror": "^5.61.1", "copy-to-clipboard": "^3.3.1", "core-js": "^3.25.0", @@ -34,6 +36,7 @@ "react-helmet": "^6.1.0", "react-highlight-words": "^0.18.0", "react-i18next": "^11.8.7", + "react-metamask-avatar": "^1.2.1", "react-router-dom": "^5.3.3", "react-scripts": "5.0.1", "react-social-login-buttons": "^3.4.0" diff --git a/web/src/App.js b/web/src/App.js index 07a0d83f..3e982eac 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -89,6 +89,8 @@ import {withTranslation} from "react-i18next"; import LanguageSelect from "./common/select/LanguageSelect"; import ThemeSelect from "./common/select/ThemeSelect"; import OrganizationSelect from "./common/select/OrganizationSelect"; +import {clearWeb3AuthToken} from "./auth/Web3Auth"; +import AccountAvatar from "./account/AccountAvatar"; const {Header, Footer, Content} = Layout; @@ -312,12 +314,11 @@ class App extends Component { .then((res) => { if (res.status === "ok") { const owner = this.state.account.owner; - this.setState({ account: null, themeAlgorithm: ["default"], }); - + clearWeb3AuthToken(); Setting.showMessage("success", i18next.t("application:Logged out successfully")); const redirectUri = res.data2; if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { @@ -348,7 +349,9 @@ class App extends Component { ); } else { return ( - + } + > {Setting.getShortName(this.state.account.name)} ); diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 15c11a7a..04f370fb 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -358,6 +358,8 @@ class ProviderEditPage extends React.Component { this.updateProviderField("type", "Default"); } else if (value === "AI") { this.updateProviderField("type", "OpenAI API - GPT"); + } else if (value === "Web3") { + this.updateProviderField("type", "MetaMask"); } })}> { @@ -370,6 +372,7 @@ class ProviderEditPage extends React.Component { {id: "SAML", name: "SAML"}, {id: "SMS", name: "SMS"}, {id: "Storage", name: "Storage"}, + {id: "Web3", name: "Web3"}, ] .sort((a, b) => a.name.localeCompare(b.name)) .map((providerCategory, index) => ) @@ -524,7 +527,7 @@ class ProviderEditPage extends React.Component { ) } { - this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : ( + (this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || this.state.provider.category === "Web3" ? null : ( { this.state.provider.category === "AI" ? null : ( diff --git a/web/src/Setting.js b/web/src/Setting.js index 431cd52c..f2cf2633 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -216,6 +216,12 @@ export const OtherProviderInfo = { url: "https://platform.openai.com", }, }, + Web3: { + "MetaMask": { + logo: `${StaticBaseUrl}/img/social_metamask.svg`, + url: "https://metamask.io/", + }, + }, }; export function initCountries() { @@ -288,7 +294,7 @@ export function isProviderVisible(providerItem) { return false; } - if (providerItem.provider.category !== "OAuth" && providerItem.provider.category !== "SAML") { + if (!["OAuth", "SAML", "Web3"].includes(providerItem.provider.category)) { return false; } @@ -891,6 +897,10 @@ export function getProviderTypeOptions(category) { return ([ {id: "OpenAI API - GPT", name: "OpenAI API - GPT"}, ]); + } else if (category === "Web3") { + return ([ + {id: "MetaMask", name: "MetaMask"}, + ]); } else { return []; } diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index ba37d199..bec01e0c 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -36,6 +36,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal"; import {DeleteMfa} from "./backend/MfaBackend"; import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons"; import * as MfaBackend from "./backend/MfaBackend"; +import AccountAvatar from "./account/AccountAvatar"; const {Option} = Select; @@ -791,10 +792,23 @@ class UserEditPage extends React.Component { { (this.state.application === null || this.state.user === null) ? null : ( this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem) => - (providerItem.provider.category === "OAuth") ? ( - {return this.unlinked();}} /> + (providerItem.provider.category === "OAuth" || providerItem.provider.category === "Web3") ? ( + {return this.unlinked();}} /> ) : ( - {return this.unlinked();}} /> + {return this.unlinked();}} /> ) ) ) @@ -971,7 +985,7 @@ class UserEditPage extends React.Component { { imgUrl ? - {imgUrl} + : diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index abf14e18..bc46c196 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -23,6 +23,7 @@ import * as UserBackend from "./backend/UserBackend"; import i18next from "i18next"; import BaseListPage from "./BaseListPage"; import PopconfirmModal from "./common/modal/PopconfirmModal"; +import AccountAvatar from "./account/AccountAvatar"; class UserListPage extends BaseListPage { constructor(props) { @@ -270,7 +271,7 @@ class UserListPage extends BaseListPage { render: (text, record, index) => { return ( - {text} + ); }, diff --git a/web/src/account/AccountAvatar.js b/web/src/account/AccountAvatar.js new file mode 100644 index 00000000..23a87dc7 --- /dev/null +++ b/web/src/account/AccountAvatar.js @@ -0,0 +1,36 @@ +// 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 React from "react"; +import {MetaMaskAvatar} from "react-metamask-avatar"; + +class AccountAvatar extends React.Component { + render() { + const {src, size} = this.props; + // The avatar for Metamask account is directly generated by an algorithm based on the address + // src = "metamask:0xC304b2cC0Be8E9ce10fF3Afd34820Ed306A23600"; + const matchMetaMask = src.match(/^metamask:(\w+)$/); + if (matchMetaMask) { + const address = matchMetaMask[1]; + return ( + + ); + } + return ( + + ); + } +} + +export default AccountAvatar; diff --git a/web/src/auth/AuthCallback.js b/web/src/auth/AuthCallback.js index 84a969a8..fe9a98d1 100644 --- a/web/src/auth/AuthCallback.js +++ b/web/src/auth/AuthCallback.js @@ -95,6 +95,12 @@ class AuthCallback extends React.Component { if (code === null) { code = params.get("authCode"); } + // The code for Metamask 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"); + code = localStorage.getItem(code); + } // Steam don't use code, so we should use all params as code. if (isSteam !== null && code === null) { code = this.props.location.search; diff --git a/web/src/auth/Provider.js b/web/src/auth/Provider.js index 64cd0b44..2b654923 100644 --- a/web/src/auth/Provider.js +++ b/web/src/auth/Provider.js @@ -317,6 +317,10 @@ const authInfo = { scope: "user:read", endpoint: "https://zoom.us/oauth/authorize", }, + MetaMask: { + scope: "", + endpoint: "", + }, }; export function getProviderUrl(provider) { @@ -459,5 +463,7 @@ export function getAuthUrl(application, provider, method) { return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&grant_options[]=per-user`; } else if (provider.type === "Twitter" || provider.type === "Fitbit") { 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}`; } } diff --git a/web/src/auth/ProviderButton.js b/web/src/auth/ProviderButton.js index d3b5fd90..29c3e55e 100644 --- a/web/src/auth/ProviderButton.js +++ b/web/src/auth/ProviderButton.js @@ -17,6 +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 QqLoginButton from "./QqLoginButton"; import FacebookLoginButton from "./FacebookLoginButton"; import WeiboLoginButton from "./WeiboLoginButton"; @@ -117,6 +118,12 @@ function goToSamlUrl(provider, location) { }); } +export function goToWeb3Url(application, provider, method) { + if (provider.type === "MetaMask") { + authViaMetaMask(application, provider, method); + } +} + export function renderProviderLogo(provider, application, width, margin, size, location) { if (size === "small") { if (provider.category === "OAuth") { @@ -153,6 +160,12 @@ export function renderProviderLogo(provider, application, width, margin, size, l {provider.displayName} ); + } else if (provider.category === "Web3") { + return ( + goToWeb3Url(application, provider, "signup")}> + {provider.displayName} + + ); } } else if (provider.type === "Custom") { // style definition @@ -192,6 +205,16 @@ export function renderProviderLogo(provider, application, width, margin, size, l ); + } else if (provider.category === "Web3") { + return ( + + ); } else { return (
diff --git a/web/src/auth/Web3Auth.js b/web/src/auth/Web3Auth.js new file mode 100644 index 00000000..5c59cd8c --- /dev/null +++ b/web/src/auth/Web3Auth.js @@ -0,0 +1,149 @@ +// // 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 {toChecksumAddress} from "ethereumjs-util"; +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}`); + } +} diff --git a/web/src/common/OAuthWidget.js b/web/src/common/OAuthWidget.js index 5c93ea56..d61fc97f 100644 --- a/web/src/common/OAuthWidget.js +++ b/web/src/common/OAuthWidget.js @@ -19,6 +19,9 @@ import * as UserBackend from "../backend/UserBackend"; import * as Setting from "../Setting"; import * as Provider from "../auth/Provider"; import * as AuthBackend from "../auth/AuthBackend"; +import {goToWeb3Url} from "../auth/ProviderButton"; +import {delWeb3AuthToken} from "../auth/Web3Auth"; +import AccountAvatar from "../account/AccountAvatar"; class OAuthWidget extends React.Component { constructor(props) { @@ -88,12 +91,15 @@ class OAuthWidget extends React.Component { return user.properties[key]; } - unlinkUser(providerType) { + unlinkUser(providerType, linkedValue) { const body = { providerType: providerType, // 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") { + delWeb3AuthToken(linkedValue); + } AuthBackend.unlink(body) .then((res) => { if (res.status === "ok") { @@ -151,7 +157,7 @@ class OAuthWidget extends React.Component { - {name} + { linkedValue === "" ? ( @@ -169,11 +175,15 @@ class OAuthWidget extends React.Component { { linkedValue === "" ? ( - - - + provider.category === "Web3" ? ( + + ) : ( + + + + ) ) : ( - + ) } diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json index a5b8c3fd..8be8f12f 100644 --- a/web/src/locales/de/data.json +++ b/web/src/locales/de/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Automatische Anmeldung", "Continue with": "Weitermachen mit", "Email or phone": "E-Mail oder Telefon", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Passwort vergessen?", "Loading": "Laden", "Logging out...": "Ausloggen...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Kein Konto?", "Or sign in with another account": "Oder mit einem anderen Konto anmelden", "Please input your Email or Phone!": "Bitte geben Sie Ihre E-Mail oder Telefonnummer ein!", diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index a55d4936..3de3127e 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Auto sign in", "Continue with": "Continue with", "Email or phone": "Email or phone", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Forgot password?", "Loading": "Loading", "Logging out...": "Logging out...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "No account?", "Or sign in with another account": "Or sign in with another account", "Please input your Email or Phone!": "Please input your Email or Phone!", diff --git a/web/src/locales/es/data.json b/web/src/locales/es/data.json index 0450651e..2dd937ef 100644 --- a/web/src/locales/es/data.json +++ b/web/src/locales/es/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Inicio de sesión automático", "Continue with": "Continúe con", "Email or phone": "Correo electrónico o teléfono", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "¿Olvidaste tu contraseña?", "Loading": "Cargando", "Logging out...": "Cerrando sesión...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "¿No tienes cuenta?", "Or sign in with another account": "O inicia sesión con otra cuenta", "Please input your Email or Phone!": "¡Por favor introduzca su correo electrónico o teléfono!", diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json index fa962b96..c9353bc8 100644 --- a/web/src/locales/fr/data.json +++ b/web/src/locales/fr/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Connexion automatique", "Continue with": "Continuer avec", "Email or phone": "Email ou téléphone", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Mot de passe oublié ?", "Loading": "Chargement", "Logging out...": "Déconnexion...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Aucun compte ?", "Or sign in with another account": "Ou connectez-vous avec un autre compte", "Please input your Email or Phone!": "S'il vous plaît, entrez votre adresse e-mail ou votre numéro de téléphone !", diff --git a/web/src/locales/id/data.json b/web/src/locales/id/data.json index 91b03040..fde7488b 100644 --- a/web/src/locales/id/data.json +++ b/web/src/locales/id/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Masuk otomatis", "Continue with": "Lanjutkan dengan", "Email or phone": "Email atau telepon", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Lupa kata sandi?", "Loading": "Memuat", "Logging out...": "Keluar...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Tidak memiliki akun?", "Or sign in with another account": "Atau masuk dengan akun lain", "Please input your Email or Phone!": "Silahkan masukkan email atau nomor telepon Anda!", diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json index 078d76f8..17b14f99 100644 --- a/web/src/locales/ja/data.json +++ b/web/src/locales/ja/data.json @@ -392,9 +392,11 @@ "Auto sign in": "自動サインイン", "Continue with": "続ける", "Email or phone": "メールまたは電話", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "パスワードを忘れましたか?", "Loading": "ローディング", "Logging out...": "ログアウト中...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "アカウントがありませんか?", "Or sign in with another account": "別のアカウントでサインインする", "Please input your Email or Phone!": "あなたのメールアドレスまたは電話番号を入力してください!", diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json index 90716d0b..38c1747f 100644 --- a/web/src/locales/ko/data.json +++ b/web/src/locales/ko/data.json @@ -392,9 +392,11 @@ "Auto sign in": "자동 로그인", "Continue with": "계속하다", "Email or phone": "이메일 또는 전화", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "비밀번호를 잊으셨나요?", "Loading": "로딩 중입니다", "Logging out...": "로그아웃 중...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "계정이 없나요?", "Or sign in with another account": "다른 계정으로 로그인하세요", "Please input your Email or Phone!": "이메일 또는 전화번호를 입력해주세요!", diff --git a/web/src/locales/pt/data.json b/web/src/locales/pt/data.json index efa1779c..f1cb2506 100644 --- a/web/src/locales/pt/data.json +++ b/web/src/locales/pt/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Entrar automaticamente", "Continue with": "Continuar com", "Email or phone": "Email ou telefone", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Esqueceu a senha?", "Loading": "Carregando", "Logging out...": "Saindo...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Não possui uma conta?", "Or sign in with another account": "Ou entre com outra conta", "Please input your Email or Phone!": "Por favor, informe seu email ou telefone!", diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json index 49670c2f..5661e346 100644 --- a/web/src/locales/ru/data.json +++ b/web/src/locales/ru/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Автоматическая авторизация", "Continue with": "Продолжайте с", "Email or phone": "Электронная почта или телефон", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Забыли пароль?", "Loading": "Загрузка", "Logging out...": "Выход...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Нет аккаунта?", "Or sign in with another account": "Или войти с другой учетной записью", "Please input your Email or Phone!": "Пожалуйста, введите свой адрес электронной почты или номер телефона!", diff --git a/web/src/locales/vi/data.json b/web/src/locales/vi/data.json index 63b51429..696c6a62 100644 --- a/web/src/locales/vi/data.json +++ b/web/src/locales/vi/data.json @@ -392,9 +392,11 @@ "Auto sign in": "Tự động đăng nhập", "Continue with": "Tiếp tục với", "Email or phone": "Email hoặc điện thoại", + "Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization", "Forgot password?": "Quên mật khẩu?", "Loading": "Đang tải", "Logging out...": "Đăng xuất ...", + "MetaMask plugin not detected": "MetaMask plugin not detected", "No account?": "Không có tài khoản?", "Or sign in with another account": "Hoặc đăng nhập bằng tài khoản khác", "Please input your Email or Phone!": "Vui lòng nhập địa chỉ Email hoặc số điện thoại của bạn!", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index 5eb9f7b1..ab75d3a2 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -392,9 +392,11 @@ "Auto sign in": "下次自动登录", "Continue with": "使用以下账号继续", "Email or phone": "Email或手机号", + "Failed to obtain MetaMask authorization": "获取MetaMask授权失败", "Forgot password?": "忘记密码?", "Loading": "加载中", "Logging out...": "正在退出登录...", + "MetaMask plugin not detected": "未检测到MetaMask插件", "No account?": "没有账号?", "Or sign in with another account": "或者,登录其他账号", "Please input your Email or Phone!": "请输入您的Email或手机号!", @@ -746,6 +748,8 @@ "Token URL - Tooltip": "自定义OAuth的Token URL", "Type": "类型", "Type - Tooltip": "类型", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "UserInfo URL", "UserInfo URL - Tooltip": "自定义OAuth的UserInfo URL", "admin (Shared)": "admin(共享)" diff --git a/web/src/table/ProviderTable.js b/web/src/table/ProviderTable.js index 101683a8..6899816d 100644 --- a/web/src/table/ProviderTable.js +++ b/web/src/table/ProviderTable.js @@ -110,7 +110,7 @@ class ProviderTable extends React.Component { key: "canSignUp", width: "120px", render: (text, record, index) => { - if (record.provider?.category !== "OAuth") { + if (!["OAuth", "Web3"].includes(record.provider?.category)) { return null; } @@ -127,7 +127,7 @@ class ProviderTable extends React.Component { key: "canSignIn", width: "120px", render: (text, record, index) => { - if (record.provider?.category !== "OAuth") { + if (!["OAuth", "Web3"].includes(record.provider?.category)) { return null; } @@ -144,7 +144,7 @@ class ProviderTable extends React.Component { key: "canUnlink", width: "120px", render: (text, record, index) => { - if (record.provider?.category !== "OAuth") { + if (!["OAuth", "Web3"].includes(record.provider?.category)) { return null; } @@ -161,7 +161,7 @@ class ProviderTable extends React.Component { key: "prompted", width: "120px", render: (text, record, index) => { - if (record.provider?.category !== "OAuth") { + if (!["OAuth", "Web3"].includes(record.provider?.category)) { return null; } diff --git a/web/yarn.lock b/web/yarn.lock index 1e5aa7a8..5b868403 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -173,7 +173,7 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== @@ -433,16 +433,6 @@ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-proposal-private-property-in-object@^7.21.11": - version "7.21.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" - integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.21.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" @@ -1590,6 +1580,20 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/util@^8.0.6": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + "@humanwhocodes/config-array@^0.10.4": version "0.10.7" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc" @@ -1931,6 +1935,18 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@metamask/eth-sig-util@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-6.0.0.tgz#083321dc7285a9aa6e066db7c49be6e94c5e03a3" + integrity sha512-M0ezVz8lirXG1P6rHPzx+9i4zfhebCgVHE8XQT8VWxy/eUWllHQGcBcE8QmOusC7su55M4CMr9AyMIu0lx452g== + dependencies: + "@ethereumjs/util" "^8.0.6" + bn.js "^4.12.0" + ethereum-cryptography "^2.0.0" + ethjs-util "^0.1.6" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -1938,6 +1954,18 @@ dependencies: eslint-scope "5.1.1" +"@noble/curves@1.1.0", "@noble/curves@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + +"@noble/hashes@1.3.1", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2094,6 +2122,28 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf" integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw== +"@scure/base@~1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@scure/bip32@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" + integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== + dependencies: + "@noble/curves" "~1.1.0" + "@noble/hashes" "~1.3.1" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -3674,6 +3724,11 @@ bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bn.js@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -3769,6 +3824,14 @@ buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -5480,6 +5543,24 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +ethereum-cryptography@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" + integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== + dependencies: + "@noble/curves" "1.1.0" + "@noble/hashes" "1.3.1" + "@scure/bip32" "1.3.1" + "@scure/bip39" "1.2.1" + +ethjs-util@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + eventemitter2@6.4.7: version "6.4.7" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" @@ -6463,7 +6544,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6668,6 +6749,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + is-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" @@ -7978,6 +8064,11 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -10150,6 +10241,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-metamask-avatar@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-metamask-avatar/-/react-metamask-avatar-1.2.1.tgz#f0623e00ebc90ec24b8ac91cad3a25f653e7bc25" + integrity sha512-EQhaW27PdqGKLxCnDgpCTWnEs1bjba+l5b/ZQc1V/GSWrCznAcrQ2HrcjSPgmuud2rvDChYyrzgRB5sBU33gSw== + react-refresh@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" @@ -11194,6 +11290,13 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -11731,11 +11834,21 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tweetnacl-util@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"