新增:登录验证签功能

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": { "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"

View File

@ -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;
} }

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 { 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': {},
},
}, },
}) })