<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MySQL MCP Server Pro - 登录</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<!-- 添加 crypto-js 库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<style>
:root {
--primary-color: #2196F3;
--primary-dark: #1976D2;
--text-primary: #212121;
--text-secondary: #757575;
--background: #f5f5f5;
--surface: #FFFFFF;
--error: #F44336;
--success: #4CAF50;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--background);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* 登录表单样式 */
.login-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.title {
font-size: 1.5rem;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.author {
color: var(--text-secondary);
font-size: 0.9rem;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: var(--text-primary);
font-weight: 500;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 0.95rem;
}
input:focus {
outline: none;
border-color: var(--primary-color);
}
.submit-button {
width: 100%;
padding: 0.75rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
}
.submit-button:hover {
background: var(--primary-dark);
}
.error-message {
color: var(--error);
margin-top: 1rem;
text-align: center;
display: none;
}
/* Token展示界面样式 */
.token-container {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--background);
padding: 1.5rem;
}
.token-content {
max-width: 900px;
margin: 0 auto;
}
.token-header {
margin-bottom: 1rem;
text-align: center;
}
.token-header h1 {
font-size: 1.25rem;
margin-bottom: 0.25rem;
}
.token-header .subtitle {
font-size: 0.9rem;
color: var(--text-secondary);
}
.token-section {
background: white;
padding: 1.25rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 0.75rem;
}
.token-section h2 {
font-size: 1rem;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.token-value {
font-family: 'Monaco', 'Consolas', monospace;
word-break: break-all;
background: var(--background);
padding: 0.75rem;
border-radius: 6px;
border: 1px solid #ddd;
font-size: 0.85rem;
margin-bottom: 0.5rem;
line-height: 1.3;
}
.token-info {
margin: 0.5rem 0;
color: var(--text-secondary);
font-size: 0.85rem;
}
.token-info p {
margin: 0.25rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.copy-button {
background: var(--success);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.copy-button:hover {
opacity: 0.9;
}
.code-block {
background: #1E1E1E;
color: #E0E0E0;
padding: 0.75rem;
border-radius: 6px;
overflow-x: auto;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.8rem;
line-height: 1.3;
margin: 0.5rem 0;
}
.github-card {
text-align: center;
padding: 0.75rem;
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
color: white;
border-radius: 6px;
margin-top: 0.5rem;
font-size: 0.85rem;
}
.github-card a {
color: white;
text-decoration: none;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.github-card a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<!-- 登录表单 -->
<div class="login-container">
<div class="header">
<h1 class="title">MySQL MCP Server Pro</h1>
<div class="author">by wenb1n</div>
</div>
<form id="loginForm">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="submit-button">登录</button>
</form>
<div id="errorMessage" class="error-message"></div>
</div>
<!-- Token展示界面 -->
<div id="tokenContainer" class="token-container">
<div class="token-content">
<div class="token-header">
<h1>MySQL MCP Server Pro</h1>
<div class="subtitle">by wenb1n</div>
</div>
<div class="token-section">
<h2>🔑 访问令牌 (Access Token)</h2>
<div id="tokenValue" class="token-value"></div>
<div class="token-info">
<p>⏰ 过期时间:<span id="expireTime"></span></p>
</div>
<button id="copyButton" class="copy-button">📋 复制Token</button>
</div>
<div class="token-section">
<h2>🔄 刷新令牌 (Refresh Token)</h2>
<div id="refreshTokenValue" class="token-value"></div>
<div class="token-info">
<p>使用刷新令牌获取新的访问令牌:</p>
</div>
<div class="code-block">
<pre id="refreshTokenExample">curl -X POST http://localhost:3000/mcp/auth/login \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "your-refresh-token",
"client_id": "mysql-mcp-client",
"client_secret": "mysql-mcp-secret"
}'</pre>
</div>
</div>
<div class="github-card">
<a href="https://github.com/wenb1n-dev/mysql_mcp_server_pro" target="_blank">
⭐️ 如果觉得好用,请帮忙点个 Star 支持一下!
</a>
</div>
</div>
</div>
<script>
// 生成随机盐值
function generateSalt(length = 16) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let salt = '';
for (let i = 0; i < length; i++) {
salt += chars.charAt(Math.floor(Math.random() * chars.length));
}
return salt;
}
// 加密密码
function encryptPassword(password, salt, timestamp) {
// 第一次哈希:密码 + 盐
const firstHash = CryptoJS.SHA256(password + salt).toString();
// 第二次哈希:第一次哈希结果 + 时间戳
const finalHash = CryptoJS.SHA256(firstHash + timestamp).toString();
return finalHash;
}
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
// 生成安全参数
const salt = generateSalt();
const timestamp = Date.now().toString();
const encryptedPassword = encryptPassword(password, salt, timestamp);
const response = await fetch('/mcp/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Timestamp': timestamp, // 添加时间戳头
'X-Salt': salt, // 添加盐值头
},
body: JSON.stringify({
grant_type: 'password',
username: username,
password: encryptedPassword, // 发送加密后的密码
client_id: 'mysql-mcp-client',
client_secret: 'mysql-mcp-secret'
})
});
const data = await response.json();
if (response.ok) {
// 显示token信息
const tokenContainer = document.getElementById('tokenContainer');
const tokenValue = document.getElementById('tokenValue');
const expireTime = document.getElementById('expireTime');
const refreshTokenValue = document.getElementById('refreshTokenValue');
tokenValue.textContent = data.access_token;
expireTime.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
<div>🔑 访问令牌过期时间: ${data.expire_time}</div>
<div>🔄 刷新令牌过期时间: ${data.refresh_token_expire_time}</div>
</div>
`;
refreshTokenValue.textContent = data.refresh_token;
// 更新示例代码中的刷新令牌
const example = document.getElementById('refreshTokenExample');
example.textContent = example.textContent.replace('your-refresh-token', data.refresh_token);
tokenContainer.style.display = 'block';
document.querySelector('.login-container').style.display = 'none';
} else {
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = data.error_description || data.error || '登录失败';
errorMessage.style.display = 'block';
}
} catch (error) {
const errorMessage = document.getElementById('errorMessage');
errorMessage.textContent = '网络错误,请稍后重试';
errorMessage.style.display = 'block';
}
});
document.getElementById('copyButton').addEventListener('click', function() {
const token = document.getElementById('tokenValue').textContent;
if (token) {
navigator.clipboard.writeText(token).then(() => {
this.textContent = '✓ 已复制';
setTimeout(() => {
this.innerHTML = '📋 复制Token';
}, 2000);
}).catch(() => {
alert('复制失败,请手动复制');
});
}
});
</script>
</body>
</html>