新增:登录验证签功能
This commit is contained in:
parent
3e0bf253be
commit
e882c74449
2018
package-lock.json
generated
2018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"ethers": "^6.16.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
"siwe": "^3.0.0",
|
||||||
"vue": "^3.5.27",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^5.0.1",
|
"vue-router": "^5.0.1",
|
||||||
"vuetify": "^4.0.0-beta.0"
|
"vuetify": "^4.0.0-beta.0"
|
||||||
@ -46,6 +49,7 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-node-polyfills": "^0.25.0",
|
||||||
"vite-plugin-vue-devtools": "^8.0.5",
|
"vite-plugin-vue-devtools": "^8.0.5",
|
||||||
"vitest": "^4.0.18",
|
"vitest": "^4.0.18",
|
||||||
"vue-tsc": "^3.2.4"
|
"vue-tsc": "^3.2.4"
|
||||||
|
|||||||
@ -35,15 +35,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
class="metamask-btn"
|
class="wallet-btn"
|
||||||
color="success"
|
color="success"
|
||||||
block
|
block
|
||||||
@click="connectWithMetaMask"
|
@click="connectWithWallet"
|
||||||
:loading="isConnecting"
|
:loading="isConnecting"
|
||||||
:disabled="isConnecting"
|
:disabled="isConnecting"
|
||||||
>
|
>
|
||||||
<span class="metamask-icon">🟡</span>
|
<span class="wallet-icon">🟡</span>
|
||||||
{{ isConnecting ? 'Connecting...' : 'Connect with MetaMask' }}
|
{{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<div v-if="errorMessage" class="error-message">
|
<div v-if="errorMessage" class="error-message">
|
||||||
@ -64,6 +64,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { BrowserProvider } from 'ethers'
|
||||||
|
import { SiweMessage } from 'siwe'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const email = ref('')
|
const email = ref('')
|
||||||
@ -79,14 +81,16 @@ const handleLogin = () => {
|
|||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectWithMetaMask = async () => {
|
const connectWithWallet = async () => {
|
||||||
isConnecting.value = true
|
isConnecting.value = true
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if MetaMask is installed
|
// Check if Ethereum wallet is installed
|
||||||
if (!window.ethereum) {
|
if (!window.ethereum) {
|
||||||
throw new Error('MetaMask is not installed. Please install it to continue.')
|
throw new Error(
|
||||||
|
'Ethereum wallet is not installed. Please install MetaMask, TokenPocket or another Ethereum wallet to continue.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request account access
|
// Request account access
|
||||||
@ -95,36 +99,76 @@ const connectWithMetaMask = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const walletAddress = accounts[0]
|
const walletAddress = accounts[0]
|
||||||
console.log('Connected to MetaMask with account:', walletAddress)
|
console.log('Connected to wallet with account:', walletAddress)
|
||||||
|
|
||||||
|
// Validate wallet address format
|
||||||
|
if (!walletAddress || !/^0x[0-9a-fA-F]{40}$/.test(walletAddress)) {
|
||||||
|
throw new Error('Invalid wallet address format. Please check your wallet connection.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get chain ID
|
||||||
|
const chainId = await window.ethereum.request({
|
||||||
|
method: 'eth_chainId',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Siwe-formatted message manually
|
||||||
|
// const scheme = window.location.protocol.slice(0, -1);
|
||||||
|
// const domain = 'polymarket.com';//window.location.host
|
||||||
|
// const uri = 'https://api.xtrader.vip/base/walletLogin';//window.location.origin
|
||||||
|
|
||||||
|
const scheme = window.location.protocol.slice(0, -1)
|
||||||
|
const domain = window.location.host
|
||||||
|
const origin = window.location.origin
|
||||||
|
|
||||||
|
const statement = 'Sign in to PolyMarket'
|
||||||
|
const version = '1'
|
||||||
|
|
||||||
|
const issuedAt = new Date().toISOString()
|
||||||
|
const provider = new BrowserProvider(window.ethereum)
|
||||||
|
const signer = await provider.getSigner()
|
||||||
|
const siwe = new SiweMessage({
|
||||||
|
scheme,
|
||||||
|
domain,
|
||||||
|
address: signer.address,
|
||||||
|
statement,
|
||||||
|
uri: origin,
|
||||||
|
version: '1',
|
||||||
|
chainId: chainId,
|
||||||
|
})
|
||||||
|
const message = siwe.prepareMessage()
|
||||||
|
// 去掉message 头部的http://
|
||||||
|
// 将 message 中匹配 signer.address 值的内容替换为小写
|
||||||
|
// const lowerAddress = signer.address.toLowerCase()
|
||||||
|
const message1 = message.replace(/^https?:\/\//, '')
|
||||||
|
// console.log('Cleaned Siwe message:', cleanedMessage)
|
||||||
|
// const cleanedMessage = message1.replace(/^https?:\/\//, '')
|
||||||
|
// console.log('Cleaned Siwe message:', cleanedMessage)
|
||||||
|
|
||||||
// Generate nonce for security
|
// Generate nonce for security
|
||||||
const nonce = Math.floor(Math.random() * 1000000).toString()
|
const nonce = siwe.nonce //Math.floor(Math.random() * 1000000).toString()
|
||||||
|
|
||||||
// Create message to sign
|
// Construct Siwe message according to EIP-4361 standard
|
||||||
const message = `happy new year`
|
// const message = `${domain} wants you to sign in with your Ethereum account:\n${walletAddress}\n\n${statement}\n\nURI: ${origin}\nVersion: ${version}\nChain ID: ${parseInt(chainId, 16)}\nNonce: ${nonce}\nIssued At: ${issuedAt}`
|
||||||
|
|
||||||
|
console.log('Generated Siwe-formatted message:', message1)
|
||||||
|
|
||||||
// Request signature
|
// Request signature
|
||||||
const signature = await window.ethereum.request({
|
const signature = await window.ethereum.request({
|
||||||
method: 'personal_sign',
|
method: 'personal_sign',
|
||||||
params: [message + nonce, walletAddress],
|
params: [message1, signer.address],
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Signature:', signature)
|
console.log('Signature:', signature)
|
||||||
console.log('Login data:', {
|
|
||||||
message,
|
|
||||||
nonce,
|
|
||||||
signature,
|
|
||||||
walletAddress,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Call login API
|
// Call login API
|
||||||
const loginResponse = await fetch('https://api.xtrader.vip/base/walletLogin', {
|
const loginResponse = await fetch('https://api.xtrader.vip/base/walletLogin', {
|
||||||
|
//http://localhost:8080 //https://api.xtrader.vip
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message,
|
Message: message1,
|
||||||
nonce,
|
nonce,
|
||||||
signature,
|
signature,
|
||||||
walletAddress,
|
walletAddress,
|
||||||
@ -142,8 +186,9 @@ const connectWithMetaMask = async () => {
|
|||||||
// In a real application, you would store the token or session data here
|
// In a real application, you would store the token or session data here
|
||||||
router.push('/')
|
router.push('/')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error connecting to MetaMask:', error)
|
console.error('Error connecting to wallet:', error)
|
||||||
errorMessage.value = error.message || 'Failed to connect with MetaMask'
|
errorMessage.value =
|
||||||
|
error.message || 'Failed to connect with wallet. Please check your wallet and try again.'
|
||||||
} finally {
|
} finally {
|
||||||
isConnecting.value = false
|
isConnecting.value = false
|
||||||
}
|
}
|
||||||
@ -160,10 +205,34 @@ const connectWithMetaMask = async () => {
|
|||||||
min-height: calc(100vh - 80px);
|
min-height: calc(100vh - 80px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PC端样式 - 固定大小 */
|
||||||
|
@media (min-width: 600px) {
|
||||||
.login-card {
|
.login-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机端样式 - 铺满页面 */
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
.login-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-row {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 32px 24px;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
@ -200,7 +269,7 @@ const connectWithMetaMask = async () => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metamask-btn {
|
.wallet-btn {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -208,7 +277,7 @@ const connectWithMetaMask = async () => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metamask-icon {
|
.wallet-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,35 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
// import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
// import { defineConfig } from 'vite'
|
||||||
|
// import vue from '@vitejs/plugin-vue'
|
||||||
|
// import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
// import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// // https://vite.dev/config/
|
||||||
|
// export default defineConfig({
|
||||||
|
// plugins: [vue(), vueJsx(), vueDevTools()],
|
||||||
|
// resolve: {
|
||||||
|
// alias: {
|
||||||
|
// '@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
// 1. 导入插件
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
vueJsx(),
|
// 2. 将此插件添加到插件数组中
|
||||||
vueDevTools(),
|
nodePolyfills({
|
||||||
|
// 为了彻底解决SIWE等库的问题,建议包含以下选项
|
||||||
|
protocolImports: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
// 3. 可选但推荐:显式定义 process.env 以避免其他潜在错误
|
||||||
alias: {
|
define: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'process.env': {},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user