1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-10-22 02:03:11 +00:00
ClassworksKV/cli/get-token.js

234 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* 设备授权流程 - 命令行工具
*
* 用于演示设备授权流程,获取访问令牌
*
* 使用方法:
* node cli/get-token.js
* 或配置为可执行chmod +x cli/get-token.js && ./cli/get-token.js
*/
import readline from 'readline';
// 配置
const CONFIG = {
// API服务器地址
baseUrl: process.env.API_BASE_URL || 'http://localhost:3030',
// 站点密钥
siteKey: process.env.SITE_KEY || '',
// 应用ID
appId: process.env.APP_ID || '1',
// 授权页面地址Classworks前端
authPageUrl: process.env.FRONTEND_URL,
// 轮询间隔(秒)
pollInterval: 3,
// 最大轮询次数
maxPolls: 100,
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
function log(message, color = '') {
console.log(`${color}${message}${colors.reset}`);
}
function logSuccess(message) {
log(`${message}`, colors.green);
}
function logError(message) {
log(`${message}`, colors.red);
}
function logInfo(message) {
log(` ${message}`, colors.cyan);
}
function logWarning(message) {
log(`${message}`, colors.yellow);
}
// HTTP请求封装
async function request(path, options = {}) {
const url = `${CONFIG.baseUrl}${path}`;
const headers = {
'Content-Type': 'application/json',
...options.headers,
};
if (CONFIG.siteKey) {
headers['X-Site-Key'] = CONFIG.siteKey;
}
try {
const response = await fetch(url, {
...options,
headers,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || `HTTP ${response.status}`);
}
return data;
} catch (error) {
if (error.message.includes('fetch')) {
throw new Error(`无法连接到服务器: ${CONFIG.baseUrl}`);
}
throw error;
}
}
// 生成设备代码
async function generateDeviceCode() {
logInfo('正在生成设备授权码...');
const data = await request('/auth/device/code', {
method: 'POST',
});
return data;
}
// 轮询获取令牌
async function pollForToken(deviceCode) {
let polls = 0;
return new Promise((resolve, reject) => {
const poll = async () => {
polls++;
if (polls > CONFIG.maxPolls) {
reject(new Error('轮询超时,请重试'));
return;
}
try {
const data = await request(`/auth/device/token?device_code=${deviceCode}`);
if (data.status === 'success') {
resolve(data.token);
} else if (data.status === 'expired') {
reject(new Error('设备代码已过期'));
} else if (data.status === 'pending') {
// 继续轮询
log(`等待授权... (${polls}/${CONFIG.maxPolls})`, colors.dim);
setTimeout(poll, CONFIG.pollInterval * 1000);
}
} catch (error) {
reject(error);
}
};
// 开始轮询
poll();
});
}
// 显示设备代码和授权链接
function displayDeviceCode(deviceCode, expiresIn) {
console.log('\n' + '='.repeat(60));
log(` 请访问以下地址完成授权:`, colors.bright);
console.log('');
// 构建授权URL
const authUrl = `${CONFIG.authPageUrl}?app_id=${CONFIG.appId}&mode=devicecode&devicecode=${deviceCode}`;
log(` ${authUrl}`, colors.cyan + colors.bright);
console.log('');
log(` 设备授权码: ${deviceCode}`, colors.green + colors.bright);
console.log('='.repeat(60));
logInfo(`授权码有效期: ${Math.floor(expiresIn / 60)} 分钟`);
logInfo(`API服务器: ${CONFIG.baseUrl}`);
console.log('');
}
// 保存令牌到文件
async function saveToken(token) {
const fs = await import('fs');
const path = await import('path');
const os = await import('os');
const tokenDir = path.join(os.homedir(), '.classworks');
const tokenFile = path.join(tokenDir, 'token.txt');
try {
// 确保目录存在
if (!fs.existsSync(tokenDir)) {
fs.mkdirSync(tokenDir, { recursive: true });
}
// 写入令牌
fs.writeFileSync(tokenFile, token, 'utf8');
logSuccess(`令牌已保存到: ${tokenFile}`);
} catch (error) {
logWarning(`无法保存令牌到文件: ${error.message}`);
logInfo('您可以手动保存令牌');
}
}
// 主函数
async function main() {
console.log('\n' + colors.cyan + colors.bright + '设备授权流程 - 令牌获取工具' + colors.reset + '\n');
try {
// 检查配置
if (!CONFIG.siteKey) {
logWarning('未设置 SITE_KEY 环境变量,可能需要站点密钥才能访问');
logInfo('设置方法: export SITE_KEY=your-site-key');
console.log('');
}
// 1. 生成设备代码
const { device_code, expires_in } = await generateDeviceCode();
logSuccess('设备授权码生成成功!');
// 2. 显示设备代码和授权链接
displayDeviceCode(device_code, expires_in);
// 3. 提示用户授权
logInfo('请在浏览器中打开上述地址,或在授权页面手动输入设备代码');
logInfo('等待授权中...\n');
// 4. 轮询获取令牌
const token = await pollForToken(device_code);
// 5. 显示令牌
console.log('\n' + '='.repeat(50));
logSuccess('授权成功!令牌获取完成');
console.log('='.repeat(50));
console.log('\n' + colors.bright + '您的访问令牌:' + colors.reset);
log(token, colors.green);
console.log('');
// 6. 保存令牌
await saveToken(token);
// 7. 使用示例
console.log('\n' + colors.bright + '使用示例:' + colors.reset);
console.log(` curl -H "Authorization: Bearer ${token}" ${CONFIG.baseUrl}/kv`);
console.log('');
process.exit(0);
} catch (error) {
console.log('');
logError(`错误: ${error.message}`);
console.log('');
process.exit(1);
}
}
// 运行
main();