/** * Auth Token 测试 * * 测试 Token 生成、验证、脱敏显示等功能 */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { Hono } from 'hono'; import { generateToken, maskToken, initAuth, getAuthConfig, addToken, removeToken, setAuthEnabled, validateToken, extractToken, authMiddleware, getAuthContext, } from '../../../src/auth/token.js'; import { createMockHonoContext } from '../../mocks/hono.mock.js'; describe('Auth Token', () => { beforeEach(() => { // 每个测试前重置配置 initAuth({ enabled: false, tokens: [], skipPaths: ['/health', '/api/health'], }); }); describe('generateToken - Token 生成', () => { it('生成默认长度(32)的 token', () => { const token = generateToken(); expect(token).toHaveLength(32); }); it('生成指定长度的 token', () => { const token = generateToken(16); expect(token).toHaveLength(16); }); it('token 只包含字母和数字', () => { const token = generateToken(100); expect(token).toMatch(/^[A-Za-z0-9]+$/); }); it('每次生成的 token 不同', () => { const token1 = generateToken(); const token2 = generateToken(); expect(token1).not.toBe(token2); }); }); describe('maskToken - Token 脱敏', () => { it('长 token 显示前4位和后4位', () => { const token = 'abcd1234567890efgh'; const masked = maskToken(token); expect(masked).toBe('abcd...efgh'); }); it('短 token (<=8) 完全隐藏', () => { expect(maskToken('12345678')).toBe('****'); expect(maskToken('1234567')).toBe('****'); expect(maskToken('abc')).toBe('****'); }); it('正好9位的 token 正常脱敏', () => { const token = '123456789'; const masked = maskToken(token); expect(masked).toBe('1234...6789'); }); }); describe('initAuth - 配置初始化', () => { it('使用默认配置初始化', () => { const config = initAuth(); expect(config.enabled).toBe(false); expect(config.tokens).toEqual([]); expect(config.skipPaths).toContain('/health'); }); it('合并自定义配置', () => { const config = initAuth({ enabled: true, tokens: ['test-token'], }); expect(config.enabled).toBe(true); expect(config.tokens).toEqual(['test-token']); expect(config.skipPaths).toContain('/health'); // 保留默认值 }); it('完全覆盖配置', () => { const config = initAuth({ enabled: true, tokens: ['token1', 'token2'], skipPaths: ['/custom-skip'], }); expect(config.skipPaths).toEqual(['/custom-skip']); }); }); describe('getAuthConfig - 获取配置', () => { it('返回当前配置', () => { initAuth({ enabled: true, tokens: ['test'] }); const config1 = getAuthConfig(); const config2 = getAuthConfig(); // 应该返回相同内容 expect(config1).toEqual(config2); expect(config1.enabled).toBe(true); expect(config1.tokens).toContain('test'); }); it('修改 enabled 不影响原配置', () => { initAuth({ enabled: true }); const config = getAuthConfig(); config.enabled = false; const freshConfig = getAuthConfig(); expect(freshConfig.enabled).toBe(true); }); }); describe('addToken / removeToken - Token 管理', () => { it('添加新 token', () => { addToken('new-token'); expect(getAuthConfig().tokens).toContain('new-token'); }); it('不重复添加相同 token', () => { addToken('same-token'); addToken('same-token'); const tokens = getAuthConfig().tokens; expect(tokens.filter((t) => t === 'same-token')).toHaveLength(1); }); it('移除存在的 token', () => { addToken('to-remove'); removeToken('to-remove'); expect(getAuthConfig().tokens).not.toContain('to-remove'); }); it('移除不存在的 token 无副作用', () => { const before = getAuthConfig().tokens.length; removeToken('non-existent'); expect(getAuthConfig().tokens.length).toBe(before); }); }); describe('setAuthEnabled - 启用/禁用认证', () => { it('启用认证', () => { setAuthEnabled(true); expect(getAuthConfig().enabled).toBe(true); }); it('禁用认证', () => { setAuthEnabled(true); setAuthEnabled(false); expect(getAuthConfig().enabled).toBe(false); }); }); describe('validateToken - Token 验证', () => { beforeEach(() => { initAuth({ tokens: ['valid-token-1', 'valid-token-2'] }); }); it('有效 token 返回 true', () => { expect(validateToken('valid-token-1')).toBe(true); expect(validateToken('valid-token-2')).toBe(true); }); it('无效 token 返回 false', () => { expect(validateToken('invalid-token')).toBe(false); expect(validateToken('')).toBe(false); }); }); describe('extractToken - 从请求提取 Token', () => { it('从 Authorization header 提取 Bearer token', () => { const c = createMockHonoContext({ headers: { authorization: 'Bearer my-secret-token' }, }); const token = extractToken(c as any); expect(token).toBe('my-secret-token'); }); it('从 query parameter 提取 token', () => { const c = createMockHonoContext({ query: { token: 'query-token' }, }); const token = extractToken(c as any); expect(token).toBe('query-token'); }); it('Authorization header 优先于 query', () => { const c = createMockHonoContext({ headers: { authorization: 'Bearer header-token' }, query: { token: 'query-token' }, }); const token = extractToken(c as any); expect(token).toBe('header-token'); }); it('无 token 时返回 null', () => { const c = createMockHonoContext(); const token = extractToken(c as any); expect(token).toBeNull(); }); it('非 Bearer 格式的 Authorization 返回 null', () => { const c = createMockHonoContext({ headers: { authorization: 'Basic abc123' }, }); const token = extractToken(c as any); expect(token).toBeNull(); }); }); describe('authMiddleware - 认证中间件', () => { let app: Hono; beforeEach(() => { app = new Hono(); app.use('*', authMiddleware); app.get('/test', (c) => { const auth = getAuthContext(c); return c.json({ auth }); }); app.get('/health', (c) => c.json({ status: 'ok' })); }); it('认证禁用时允许所有请求', async () => { initAuth({ enabled: false }); const res = await app.request('/test'); const json = await res.json(); expect(res.status).toBe(200); expect(json.auth.authenticated).toBe(true); }); it('跳过配置的路径', async () => { initAuth({ enabled: true, skipPaths: ['/health'] }); const res = await app.request('/health'); expect(res.status).toBe(200); }); it('本地请求 (x-forwarded-for: 127.0.0.1) 跳过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '127.0.0.1' }, }); const json = await res.json(); expect(res.status).toBe(200); expect(json.auth.authenticated).toBe(true); }); it('本地请求 (x-forwarded-for: ::1) 跳过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '::1' }, }); expect(res.status).toBe(200); }); it('本地请求 (x-real-ip: 192.168.1.1) 跳过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-real-ip': '192.168.1.1' }, }); expect(res.status).toBe(200); }); it('本地请求 (x-real-ip: 10.0.0.1) 跳过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-real-ip': '10.0.0.1' }, }); expect(res.status).toBe(200); }); it('本地请求 (x-forwarded-for: 172.16.0.1) 跳过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '172.16.0.1' }, }); expect(res.status).toBe(200); }); it('无代理头时默认为本地请求', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); // 不设置任何代理头,应该被视为本地请求 const res = await app.request('/test'); expect(res.status).toBe(200); }); it('远程请求无 token 时返回 401', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '8.8.8.8' }, }); const json = await res.json(); expect(res.status).toBe(401); expect(json.error).toBe('Authentication required'); }); it('远程请求无效 token 时返回 401', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '8.8.8.8', authorization: 'Bearer invalid-token', }, }); const json = await res.json(); expect(res.status).toBe(401); expect(json.error).toBe('Invalid token'); }); it('远程请求有效 token 时通过认证', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); const res = await app.request('/test', { headers: { 'x-forwarded-for': '8.8.8.8', authorization: 'Bearer valid-token', }, }); const json = await res.json(); expect(res.status).toBe(200); expect(json.auth.authenticated).toBe(true); expect(json.auth.tokenHint).toBe('vali...oken'); }); it('query parameter token 也可以认证', async () => { initAuth({ enabled: true, tokens: ['query-token-12345'] }); const res = await app.request('/test?token=query-token-12345', { headers: { 'x-forwarded-for': '8.8.8.8' }, }); const json = await res.json(); expect(res.status).toBe(200); expect(json.auth.authenticated).toBe(true); }); it('x-forwarded-for 多个 IP 时使用第一个', async () => { initAuth({ enabled: true, tokens: ['valid-token'] }); // 第一个是本地 IP const res = await app.request('/test', { headers: { 'x-forwarded-for': '127.0.0.1, 8.8.8.8, 1.1.1.1' }, }); expect(res.status).toBe(200); }); }); describe('getAuthContext - 获取认证上下文', () => { it('未设置时返回 authenticated: false', () => { const c = createMockHonoContext(); (c.get as any) = vi.fn().mockReturnValue(undefined); const auth = getAuthContext(c as any); expect(auth.authenticated).toBe(false); }); it('已设置时返回设置的值', () => { const c = createMockHonoContext(); const mockAuth = { authenticated: true, tokenHint: 'test...hint' }; (c.get as any) = vi.fn().mockReturnValue(mockAuth); const auth = getAuthContext(c as any); expect(auth).toEqual(mockAuth); }); }); });