feat: use worker versionId as jwt_secret
This commit is contained in:
1 parent
6b99bec324
commit
3590fa7d58
12 files changed
+269
-222
No files matched your search
@@ -42,12 +42,35 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # 获取完整的 git 历史用于获取提交信息
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Get version info
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
# 从 GitHub 环境获取版本信息
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
else
|
||||
VERSION="dev-$(git rev-parse --short HEAD)"
|
||||
fi
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
||||
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$BUILD_DATE" >> $GITHUB_ENV
|
||||
|
||||
echo "版本: $VERSION"
|
||||
echo "提交哈希: $COMMIT_HASH"
|
||||
echo "构建日期: $BUILD_DATE"
|
||||
|
||||
- name: Build Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
@@ -55,7 +78,12 @@ jobs:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
cd agent
|
||||
go build -v -ldflags="-s -w -extldflags=-static" -tags netgo,osusergo -o ${{ matrix.artifact_name }} .
|
||||
go build -v \
|
||||
-ldflags="-s -w -extldflags=-static \
|
||||
-X github.com/xugou/agent/cmd/agent.Version=${{ env.VERSION }} \
|
||||
-X github.com/xugou/agent/cmd/agent.GitCommit=${{ env.COMMIT_HASH }} \
|
||||
-X github.com/xugou/agent/cmd/agent.BuildDate=${{ env.BUILD_DATE }}" \
|
||||
-tags netgo,osusergo -o ${{ matrix.artifact_name }} .
|
||||
|
||||
- name: Build macOS
|
||||
if: matrix.os == 'macos-latest'
|
||||
@@ -64,7 +92,12 @@ jobs:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
cd agent
|
||||
go build -v -ldflags="-s -w" -tags netgo,osusergo -o ${{ matrix.artifact_name }} .
|
||||
go build -v \
|
||||
-ldflags="-s -w \
|
||||
-X github.com/xugou/agent/cmd/agent.Version=${{ env.VERSION }} \
|
||||
-X github.com/xugou/agent/cmd/agent.GitCommit=${{ env.COMMIT_HASH }} \
|
||||
-X github.com/xugou/agent/cmd/agent.BuildDate=${{ env.BUILD_DATE }}" \
|
||||
-tags netgo,osusergo -o ${{ matrix.artifact_name }} .
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -73,7 +106,12 @@ jobs:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
cd agent
|
||||
go build -v -ldflags="-s -w" -o ${{ matrix.artifact_name }} .
|
||||
go build -v `
|
||||
-ldflags="-s -w `
|
||||
-X github.com/xugou/agent/cmd/agent.Version=${{ env.VERSION }} `
|
||||
-X github.com/xugou/agent/cmd/agent.GitCommit=${{ env.COMMIT_HASH }} `
|
||||
-X github.com/xugou/agent/cmd/agent.BuildDate=${{ env.BUILD_DATE }}" `
|
||||
-o ${{ matrix.artifact_name }} .
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -23,10 +23,6 @@ cd agent
|
||||
go build -o xugou-agent
|
||||
```
|
||||
|
||||
### 使用预编译二进制文件
|
||||
|
||||
从 [Releases](https://github.com/xugou/agent/releases) 页面下载适合您系统的预编译二进制文件。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本命令
|
||||
|
||||
@@ -36,7 +36,6 @@ func init() {
|
||||
viper.BindPFlag("server", rootCmd.PersistentFlags().Lookup("server"))
|
||||
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))
|
||||
viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level"))
|
||||
viper.BindPFlag("agent_id", rootCmd.PersistentFlags().Lookup("agent-id"))
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
|
||||
@@ -18,11 +18,11 @@ func init() {
|
||||
startCmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "启动 Xugou Agent",
|
||||
Long: `启动 Xugou Agent 开始收集系统信息并上报到服务器`,
|
||||
Long: `启动 Xugou Agent 开始采集系统信息并上报到服务器`,
|
||||
Run: runStart,
|
||||
}
|
||||
|
||||
startCmd.Flags().IntP("interval", "i", 60, "数据收集和上报间隔(秒)")
|
||||
startCmd.Flags().IntP("interval", "i", 60, "数据采集和上报间隔(秒)")
|
||||
viper.BindPFlag("interval", startCmd.Flags().Lookup("interval"))
|
||||
|
||||
rootCmd.AddCommand(startCmd)
|
||||
@@ -55,16 +55,8 @@ func runStart(cmd *cobra.Command, args []string) {
|
||||
|
||||
// 初始化数据收集器和上报器
|
||||
dataCollector := collector.NewCollector()
|
||||
var dataReporter reporter.Reporter
|
||||
|
||||
// 根据配置决定使用哪种上报器
|
||||
if server == "console" {
|
||||
dataReporter = reporter.NewConsoleReporter()
|
||||
fmt.Println("使用控制台上报器")
|
||||
} else {
|
||||
dataReporter = reporter.NewHTTPReporter(server, token)
|
||||
fmt.Println("使用HTTP上报器")
|
||||
}
|
||||
dataReporter := reporter.NewHTTPReporter(server, token)
|
||||
fmt.Println("使用HTTP上报器")
|
||||
|
||||
// 设置定时器,按指定间隔收集和上报数据
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
@@ -96,7 +88,7 @@ func collectAndReport(ctx context.Context, c collector.Collector, r reporter.Rep
|
||||
// 收集系统信息
|
||||
info, err := c.Collect(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("收集系统信息失败: %v\n", err)
|
||||
fmt.Printf("采集系统信息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,24 @@ func (r *HTTPReporter) ensureRegistered(ctx context.Context, info *collector.Sys
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查响应状态码
|
||||
// 检查是否收到"客户端已存在"的响应
|
||||
if resp.StatusCode == 400 {
|
||||
var errorResponse RegisterResponse
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
if err := decoder.Decode(&errorResponse); err != nil {
|
||||
return fmt.Errorf("解析错误响应失败: %w", err)
|
||||
}
|
||||
|
||||
if errorResponse.Message == "客户端已存在" {
|
||||
fmt.Printf("客户端已存在,将直接更新状态,token: %s\n", r.apiToken)
|
||||
r.registered = true
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("注册失败: %s", errorResponse.Message)
|
||||
}
|
||||
|
||||
// 检查其他错误状态码
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
|
||||
}
|
||||
@@ -145,13 +162,8 @@ func (r *HTTPReporter) ensureRegistered(ctx context.Context, info *collector.Sys
|
||||
return nil
|
||||
}
|
||||
|
||||
// Report 将系统信息上报到服务器
|
||||
func (r *HTTPReporter) Report(ctx context.Context, info *collector.SystemInfo) error {
|
||||
// 确保客户端已注册
|
||||
if err := r.ensureRegistered(ctx, info); err != nil {
|
||||
return fmt.Errorf("客户端注册或状态更新失败: %w", err)
|
||||
}
|
||||
|
||||
// reportStatus 将系统信息上报到服务器,不检查注册状态
|
||||
func (r *HTTPReporter) reportStatus(ctx context.Context, info *collector.SystemInfo) error {
|
||||
// 提取所有磁盘的总容量和使用量
|
||||
var diskTotal, diskUsed uint64
|
||||
for _, disk := range info.DiskInfo {
|
||||
@@ -241,29 +253,17 @@ func (r *HTTPReporter) Report(ctx context.Context, info *collector.SystemInfo) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConsoleReporter 是将数据输出到控制台的上报器实现
|
||||
type ConsoleReporter struct{}
|
||||
|
||||
// NewConsoleReporter 创建一个新的控制台数据上报器
|
||||
func NewConsoleReporter() Reporter {
|
||||
return &ConsoleReporter{}
|
||||
}
|
||||
|
||||
// Report 将系统信息输出到控制台
|
||||
func (r *ConsoleReporter) Report(ctx context.Context, info *collector.SystemInfo) error {
|
||||
data, err := json.MarshalIndent(info, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化系统信息失败: %w", err)
|
||||
// Report 将系统信息上报到服务器
|
||||
func (r *HTTPReporter) Report(ctx context.Context, info *collector.SystemInfo) error {
|
||||
// 确保客户端已注册
|
||||
if err := r.ensureRegistered(ctx, info); err != nil {
|
||||
return fmt.Errorf("客户端注册或状态更新失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("系统信息收集时间:", info.Timestamp.Format("2006-01-02 15:04:05"))
|
||||
fmt.Println("主机名:", info.Hostname)
|
||||
fmt.Println("平台:", info.Platform, info.OS)
|
||||
fmt.Println("CPU使用率:", info.CPUInfo.Usage, "%")
|
||||
fmt.Println("内存使用率:", info.MemoryInfo.UsageRate, "%")
|
||||
fmt.Println("系统负载:", info.LoadInfo.Load1, info.LoadInfo.Load5, info.LoadInfo.Load15)
|
||||
fmt.Println("详细信息:")
|
||||
fmt.Println(string(data))
|
||||
// 如果注册成功但未直接上报状态(通过客户端已存在的分支),则调用上报
|
||||
if r.registered {
|
||||
return r.reportStatus(ctx, info)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,36 +9,6 @@ import { ExecutionContext } from 'hono';
|
||||
// 添加全局变量声明
|
||||
declare global {
|
||||
var isInitialized: boolean;
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV?: string;
|
||||
PORT?: string;
|
||||
JWT_SECRET?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定义 D1 数据库类型
|
||||
interface D1Database {
|
||||
prepare(query: string): D1PreparedStatement;
|
||||
dump(): Promise<ArrayBuffer>;
|
||||
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
|
||||
exec<T = unknown>(query: string): Promise<D1Result<T>>;
|
||||
}
|
||||
|
||||
interface D1PreparedStatement {
|
||||
bind(...values: any[]): D1PreparedStatement;
|
||||
first<T = unknown>(colName?: string): Promise<T>;
|
||||
run<T = unknown>(): Promise<D1Result<T>>;
|
||||
all<T = unknown>(): Promise<D1Result<T>>;
|
||||
raw<T = unknown>(): Promise<T[]>;
|
||||
}
|
||||
|
||||
interface D1Result<T = unknown> {
|
||||
results?: T[];
|
||||
success: boolean;
|
||||
error?: string;
|
||||
meta?: object;
|
||||
}
|
||||
|
||||
// 导入路由
|
||||
@@ -76,16 +46,6 @@ app.use('*', async (c, next) => {
|
||||
// 公共路由
|
||||
app.get('/', (c) => c.json({ message: 'XUGOU API 服务正在运行' }));
|
||||
|
||||
// 获取 JWT 密钥
|
||||
const getJwtSecret = (c: any) => {
|
||||
// 在 Cloudflare Workers 环境中,使用 env 变量
|
||||
if (typeof process === 'undefined') {
|
||||
return c.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
}
|
||||
// 在 Node.js 环境中,使用 process.env
|
||||
return process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
};
|
||||
|
||||
// 路由注册
|
||||
app.route('/api/auth', authRoutes);
|
||||
app.route('/api/monitors', monitorRoutes);
|
||||
|
||||
@@ -24,6 +24,4 @@ export interface D1Result<T = unknown> {
|
||||
// 通用绑定类型
|
||||
export type Bindings = {
|
||||
DB: D1Database;
|
||||
JWT_SECRET: string;
|
||||
ENABLE_DB_INIT?: string;
|
||||
};
|
||||
+24
-104
@@ -3,7 +3,7 @@ import { jwt } from 'hono/jwt';
|
||||
import { Context, Next } from 'hono';
|
||||
import { Bindings } from '../models/db';
|
||||
import { Agent } from '../models/agent';
|
||||
import { getJwtSecret, generateToken } from '../utils/jwt';
|
||||
import { getJwtSecret, generateToken, verifyToken } from '../utils/jwt';
|
||||
import {
|
||||
getAllAgents,
|
||||
getAgentsByUser,
|
||||
@@ -62,7 +62,7 @@ agents.post('/', async (c) => {
|
||||
const { name } = await c.req.json();
|
||||
const payload = c.get('jwtPayload');
|
||||
|
||||
const token = await generateToken();
|
||||
const token = await generateToken(c.env);
|
||||
|
||||
// 创建新客户端
|
||||
const newAgent = await createAgent(c.env.DB, name, token, payload.id);
|
||||
@@ -161,49 +161,6 @@ agents.put('/:id', async (c) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 更新客户端状态
|
||||
agents.post('/:id/status', async (c) => {
|
||||
try {
|
||||
const agentId = Number(c.req.param('id'));
|
||||
const {
|
||||
cpu_usage,
|
||||
memory_total,
|
||||
memory_used,
|
||||
disk_total,
|
||||
disk_used,
|
||||
network_rx,
|
||||
network_tx,
|
||||
hostname,
|
||||
ip_address,
|
||||
os,
|
||||
version
|
||||
} = await c.req.json();
|
||||
|
||||
// 更新客户端状态和资源指标
|
||||
const result = await updateAgentStatus(c.env.DB, agentId, 'active', {
|
||||
cpu_usage,
|
||||
memory_total,
|
||||
memory_used,
|
||||
disk_total,
|
||||
disk_used,
|
||||
network_rx,
|
||||
network_tx,
|
||||
hostname,
|
||||
ip_address,
|
||||
os,
|
||||
version
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: '客户端状态已更新'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新客户端状态错误:', error);
|
||||
return c.json({ success: false, message: '更新客户端状态失败' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 删除客户端
|
||||
agents.delete('/:id', async (c) => {
|
||||
try {
|
||||
@@ -235,48 +192,11 @@ agents.delete('/:id', async (c) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 重新生成客户端令牌
|
||||
agents.post('/:id/token', async (c) => {
|
||||
try {
|
||||
const agentId = Number(c.req.param('id'));
|
||||
const payload = c.get('jwtPayload');
|
||||
|
||||
// 获取客户端信息
|
||||
const agent = await getAgentById(c.env.DB, agentId);
|
||||
|
||||
if (!agent) {
|
||||
return c.json({ success: false, message: '客户端不存在' }, 404);
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
if (payload.role !== 'admin' && agent.created_by !== payload.id) {
|
||||
return c.json({ success: false, message: '无权为此客户端重新生成令牌' }, 403);
|
||||
}
|
||||
|
||||
// 生成新令牌
|
||||
const newToken = await generateToken();
|
||||
|
||||
// 更新客户端令牌
|
||||
const result = await updateAgentToken(c.env.DB, agent.id, newToken);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: '客户端令牌已重新生成',
|
||||
token: newToken
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('重新生成客户端令牌错误:', error);
|
||||
return c.json({ success: false, message: '重新生成客户端令牌失败' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 生成临时注册Token
|
||||
// 生成客户端Token
|
||||
agents.post('/token/generate', async (c) => {
|
||||
try {
|
||||
const payload = c.get('jwtPayload');
|
||||
|
||||
// 生成新令牌
|
||||
const newToken = await generateToken();
|
||||
const newToken = await generateToken(c.env);
|
||||
|
||||
// 可以选择将此token存储在临时表中,或者使用其他方式验证(例如,设置过期时间)
|
||||
// 这里为简化操作,只返回令牌
|
||||
@@ -301,29 +221,29 @@ agents.post('/register', async (c) => {
|
||||
return c.json({ success: false, message: '缺少注册令牌' }, 400);
|
||||
}
|
||||
|
||||
|
||||
// 通过token查找客户端
|
||||
const existingAgent = await getAgentByToken(c.env.DB, token);
|
||||
|
||||
if (existingAgent) {
|
||||
return c.json({ success: false, message: '客户端已存在' }, 400);
|
||||
}
|
||||
|
||||
|
||||
// 验证令牌
|
||||
const tokenVerification = await verifyToken(token, c.env);
|
||||
|
||||
// 如果令牌无效,返回错误
|
||||
if (!tokenVerification.valid) {
|
||||
return c.json({
|
||||
success: false,
|
||||
message: `注册令牌无效: ${tokenVerification.message}`
|
||||
}, 401);
|
||||
}
|
||||
|
||||
// 查找管理员用户作为客户端创建者
|
||||
const adminId = await getAdminUserId(c.env.DB);
|
||||
|
||||
// 查找是否已存在使用相同token的客户端
|
||||
const existingAgent = await getAgentByToken(c.env.DB, token);
|
||||
|
||||
if (existingAgent) {
|
||||
// 如果客户端已存在,更新其状态信息
|
||||
await updateAgent(c.env.DB, existingAgent.id, {
|
||||
status: 'active',
|
||||
hostname,
|
||||
ip_address,
|
||||
os,
|
||||
version
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: '客户端状态更新成功',
|
||||
agent: { id: existingAgent.id }
|
||||
});
|
||||
}
|
||||
|
||||
// 如果客户端不存在,则创建新客户端
|
||||
const newAgent = await createAgent(
|
||||
c.env.DB,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { getUserByUsername, createUser, getUserById } from '../db/auth';
|
||||
// 导入 D1 数据库类型
|
||||
type Bindings = {
|
||||
DB: D1Database;
|
||||
JWT_SECRET: string;
|
||||
};
|
||||
|
||||
interface D1Database {
|
||||
|
||||
@@ -4,10 +4,8 @@ import { cors } from 'hono/cors';
|
||||
import { Bindings } from '../models/db';
|
||||
|
||||
import {
|
||||
StatusPageConfig as DbStatusPageConfig,
|
||||
Monitor,
|
||||
Agent,
|
||||
getUserStatusPageConfig,
|
||||
getConfigMonitors,
|
||||
getConfigAgents,
|
||||
getStatusPageConfigById,
|
||||
@@ -26,7 +24,7 @@ import {
|
||||
getAdminUserId,
|
||||
createDefaultConfig
|
||||
} from '../db/status';
|
||||
|
||||
import { getJwtSecret } from '../utils/jwt';
|
||||
// 状态页配置接口
|
||||
interface StatusPageConfig {
|
||||
title: string;
|
||||
@@ -48,18 +46,10 @@ const adminRoutes = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
// 使用中间件验证JWT(仅对管理员路由)
|
||||
adminRoutes.use('/*', async (c, next) => {
|
||||
try {
|
||||
// 使用环境变量中的JWT密钥进行验证
|
||||
const jwtMiddleware = jwt({
|
||||
secret: c.env.JWT_SECRET,
|
||||
});
|
||||
|
||||
// 执行JWT认证
|
||||
return await jwtMiddleware(c, next);
|
||||
} catch (error) {
|
||||
console.error('JWT验证失败:', error);
|
||||
return c.json({ error: '未授权' }, 401);
|
||||
}
|
||||
const jwtMiddleware = jwt({
|
||||
secret: getJwtSecret(c)
|
||||
});
|
||||
return jwtMiddleware(c, next);
|
||||
});
|
||||
|
||||
// 获取状态页配置(管理员)
|
||||
|
||||
+162
-10
@@ -10,22 +10,174 @@
|
||||
* @returns JWT密钥
|
||||
*/
|
||||
export const getJwtSecret = (c: any): string => {
|
||||
// 在Cloudflare Workers环境中,使用env变量
|
||||
if (typeof process === 'undefined') {
|
||||
return c.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
}
|
||||
// 在Node.js环境中,使用process.env
|
||||
return process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
const { id: versionId, tag: versionTag, timestamp: versionTimestamp } = c.env.CF_VERSION_METADATA;
|
||||
return versionId || 'your-secret-key-change-in-production';
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成随机令牌
|
||||
* 生成用于API密钥或认证令牌的随机字符串
|
||||
* 生成用于API密钥或认证令牌的随机字符串,包含时间戳、前缀和签名
|
||||
*
|
||||
* @param env 环境变量或上下文对象,用于获取密钥
|
||||
* @returns 生成的随机令牌
|
||||
*/
|
||||
export async function generateToken(): Promise<string> {
|
||||
const array = new Uint8Array(32);
|
||||
export async function generateToken(env?: any): Promise<string> {
|
||||
console.log('=== 开始生成令牌 ===');
|
||||
console.log('env 参数:', env ? '已提供' : '未提供');
|
||||
|
||||
// 生成随机部分
|
||||
const array = new Uint8Array(16);
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
const randomPart = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
console.log('生成的随机部分:', randomPart);
|
||||
|
||||
// 添加时间戳
|
||||
const timestamp = Date.now().toString(36);
|
||||
console.log('时间戳 (Base36):', timestamp, '原始时间戳:', Date.now());
|
||||
|
||||
// 添加特定前缀,用于标识这是一个XUGOU系统的令牌
|
||||
const prefix = 'xugou';
|
||||
console.log('使用前缀:', prefix);
|
||||
|
||||
// 创建基本令牌
|
||||
const baseToken = `${prefix}_${timestamp}_${randomPart}`;
|
||||
console.log('基本令牌 (无签名):', baseToken);
|
||||
|
||||
|
||||
// 获取密钥
|
||||
if (!env || !env.CF_VERSION_METADATA) {
|
||||
console.error('错误: env.CF_VERSION_METADATA 不存在,无法生成签名');
|
||||
return baseToken;
|
||||
}
|
||||
|
||||
const { id: versionId, tag: versionTag, timestamp: versionTimestamp } = env.CF_VERSION_METADATA;
|
||||
console.log("generateToken 使用的 versionId:", versionId);
|
||||
console.log("generateToken 版本信息:", { tag: versionTag, timestamp: versionTimestamp });
|
||||
|
||||
try {
|
||||
// 创建签名
|
||||
const msgUint8 = new TextEncoder().encode(baseToken + versionId);
|
||||
console.log('签名数据长度:', msgUint8.length, '字节');
|
||||
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const signature = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 16);
|
||||
console.log('生成的签名:', signature);
|
||||
|
||||
// 返回带签名的令牌
|
||||
const fullToken = `${baseToken}.${signature}`;
|
||||
console.log('完整令牌:', fullToken);
|
||||
console.log('=== 令牌生成完成 ===');
|
||||
return fullToken;
|
||||
} catch (error) {
|
||||
console.error('生成令牌签名时出错:', error);
|
||||
return baseToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌是否有效
|
||||
* 验证令牌的格式和签名是否正确
|
||||
*
|
||||
* @param token 需要验证的令牌
|
||||
* @param env 环境变量或上下文对象,用于获取密钥
|
||||
* @returns 验证结果对象,包含是否有效和相关信息
|
||||
*/
|
||||
export async function verifyToken(
|
||||
token: string,
|
||||
env: any,
|
||||
): Promise<{ valid: boolean; message?: string; timestamp?: number; payload?: any }> {
|
||||
console.log('=== 开始验证令牌 ===');
|
||||
console.log('待验证令牌:', token);
|
||||
|
||||
try {
|
||||
// 首先验证令牌格式
|
||||
const parts = token.split('.');
|
||||
console.log('令牌部分数量:', parts.length);
|
||||
|
||||
// 如果令牌没有签名部分(即不包含.),或者格式不正确,则无效
|
||||
if (parts.length !== 2) {
|
||||
console.log('验证失败: 令牌格式无效 (没有点号分隔符或分隔符数量不正确)');
|
||||
return { valid: false, message: '令牌格式无效' };
|
||||
}
|
||||
|
||||
// 解析令牌各部分
|
||||
const [baseToken, signature] = parts;
|
||||
console.log('基本令牌部分:', baseToken);
|
||||
console.log('签名部分:', signature);
|
||||
|
||||
const baseTokenParts = baseToken.split('_');
|
||||
console.log('基本令牌组件数量:', baseTokenParts.length);
|
||||
|
||||
// 验证基本令牌格式
|
||||
if (baseTokenParts.length !== 3) {
|
||||
console.log('验证失败: 基本令牌格式无效 (不包含3个由下划线分隔的部分)');
|
||||
return { valid: false, message: '令牌格式无效' };
|
||||
}
|
||||
|
||||
const [prefix, timestampStr, randomPart] = baseTokenParts;
|
||||
console.log('解析出的令牌组件:', { prefix, timestampStr, randomPart });
|
||||
|
||||
// 验证前缀
|
||||
if (prefix !== 'xugou') {
|
||||
console.log(`验证失败: 令牌前缀无效 (期望 'xugou', 实际为 '${prefix}')`);
|
||||
return { valid: false, message: '令牌前缀无效' };
|
||||
}
|
||||
|
||||
// 解析时间戳
|
||||
const timestamp = parseInt(timestampStr, 36);
|
||||
console.log('解析的时间戳 (Base36 -> 数字):', timestampStr, '->', timestamp);
|
||||
console.log('时间戳对应的日期:', new Date(timestamp).toISOString());
|
||||
|
||||
// 验证时间戳是否为有效数字
|
||||
if (isNaN(timestamp)) {
|
||||
console.log('验证失败: 时间戳无效 (不是有效的Base36数字)');
|
||||
return { valid: false, message: '令牌时间戳无效' };
|
||||
}
|
||||
|
||||
// 获取密钥
|
||||
if (!env || !env.CF_VERSION_METADATA) {
|
||||
console.error('错误: env.CF_VERSION_METADATA 不存在,无法验证签名');
|
||||
return { valid: false, message: '环境变量不完整,无法验证签名' };
|
||||
}
|
||||
|
||||
const { id: versionId, tag: versionTag, timestamp: versionTimestamp } = env.CF_VERSION_METADATA;
|
||||
console.log("verifyToken 使用的 versionId:", versionId);
|
||||
console.log("verifyToken 版本信息:", { tag: versionTag, timestamp: versionTimestamp });
|
||||
|
||||
// 重新计算签名并验证
|
||||
const msgUint8 = new TextEncoder().encode(baseToken + versionId);
|
||||
console.log('验证签名数据长度:', msgUint8.length, '字节');
|
||||
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const calculatedSignature = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 16);
|
||||
console.log('计算出的签名:', calculatedSignature);
|
||||
console.log('令牌中的签名:', signature);
|
||||
console.log('签名是否匹配:', calculatedSignature === signature);
|
||||
|
||||
// 比较计算出的签名和令牌中的签名
|
||||
if (calculatedSignature !== signature) {
|
||||
console.log('验证失败: 签名不匹配');
|
||||
return { valid: false, message: '令牌签名无效' };
|
||||
}
|
||||
|
||||
// 如果所有验证都通过,令牌有效
|
||||
console.log('验证成功: 令牌有效');
|
||||
const result = {
|
||||
valid: true,
|
||||
timestamp,
|
||||
payload: {
|
||||
prefix,
|
||||
timestamp,
|
||||
randomPart
|
||||
}
|
||||
};
|
||||
console.log('验证结果:', result);
|
||||
console.log('=== 令牌验证完成 ===');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('令牌验证过程出错:', error);
|
||||
return { valid: false, message: '令牌验证过程出错' };
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,6 @@ crons = ["* * * * *"]
|
||||
binding = "DB"
|
||||
database_name = "xugou_db"
|
||||
database_id = "32a2ca5f-3a77-4b08-965e-804612557880"
|
||||
|
||||
[version_metadata]
|
||||
binding = "CF_VERSION_METADATA"
|
||||
Reference in new issue
Block a user