新增:登录验证签功能

This commit is contained in:
ivan 2026-02-06 12:31:58 +08:00
parent 3e0bf253be
commit e882c74449
4 changed files with 2143 additions and 37 deletions

2018
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,10 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"buffer": "^6.0.3",
"ethers": "^6.16.0",
"pinia": "^3.0.4",
"siwe": "^3.0.0",
"vue": "^3.5.27",
"vue-router": "^5.0.1",
"vuetify": "^4.0.0-beta.0"
@ -46,6 +49,7 @@
"prettier": "3.8.1",
"typescript": "~5.9.3",
"vite": "^7.3.1",
"vite-plugin-node-polyfills": "^0.25.0",
"vite-plugin-vue-devtools": "^8.0.5",
"vitest": "^4.0.18",
"vue-tsc": "^3.2.4"

View File

@ -35,15 +35,15 @@
</div>
<v-btn
class="metamask-btn"
class="wallet-btn"
color="success"
block
@click="connectWithMetaMask"
@click="connectWithWallet"
:loading="isConnecting"
:disabled="isConnecting"
>
<span class="metamask-icon">🟡</span>
{{ isConnecting ? 'Connecting...' : 'Connect with MetaMask' }}
<span class="wallet-icon">🟡</span>
{{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }}
</v-btn>
<div v-if="errorMessage" class="error-message">
@ -64,6 +64,8 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { BrowserProvider } from 'ethers'
import { SiweMessage } from 'siwe'
const router = useRouter()
const email = ref('')
@ -79,14 +81,16 @@ const handleLogin = () => {
router.push('/')
}
const connectWithMetaMask = async () => {
const connectWithWallet = async () => {
isConnecting.value = true
errorMessage.value = ''
try {
// Check if MetaMask is installed
// Check if Ethereum wallet is installed
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
@ -95,36 +99,76 @@ const connectWithMetaMask = async () => {
})
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
const nonce = Math.floor(Math.random() * 1000000).toString()
const nonce = siwe.nonce //Math.floor(Math.random() * 1000000).toString()
// Create message to sign
const message = `happy new year`
// Construct Siwe message according to EIP-4361 standard
// 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
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message + nonce, walletAddress],
params: [message1, signer.address],
})
console.log('Signature:', signature)
console.log('Login data:', {
message,
nonce,
signature,
walletAddress,
})
// Call login API
const loginResponse = await fetch('https://api.xtrader.vip/base/walletLogin', {
//http://localhost:8080 //https://api.xtrader.vip
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
Message: message1,
nonce,
signature,
walletAddress,
@ -142,8 +186,9 @@ const connectWithMetaMask = async () => {
// In a real application, you would store the token or session data here
router.push('/')
} catch (error: any) {
console.error('Error connecting to MetaMask:', error)
errorMessage.value = error.message || 'Failed to connect with MetaMask'
console.error('Error connecting to wallet:', error)
errorMessage.value =
error.message || 'Failed to connect with wallet. Please check your wallet and try again.'
} finally {
isConnecting.value = false
}
@ -160,10 +205,34 @@ const connectWithMetaMask = async () => {
min-height: calc(100vh - 80px);
}
.login-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 24px;
/* PC端样式 - 固定大小 */
@media (min-width: 600px) {
.login-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
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 {
@ -200,7 +269,7 @@ const connectWithMetaMask = async () => {
font-size: 14px;
}
.metamask-btn {
.wallet-btn {
height: 48px;
display: flex;
align-items: center;
@ -208,7 +277,7 @@ const connectWithMetaMask = async () => {
gap: 8px;
}
.metamask-icon {
.wallet-icon {
font-size: 16px;
}

View File

@ -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 vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
// 1. 导入插件
import { nodePolyfills } from 'vite-plugin-node-polyfills'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
vueDevTools(),
// 2. 将此插件添加到插件数组中
nodePolyfills({
// 为了彻底解决SIWE等库的问题建议包含以下选项
protocolImports: true,
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
// 3. 可选但推荐:显式定义 process.env 以避免其他潜在错误
define: {
'process.env': {},
},
})