tabby-ai-assistant 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -0
- package/dist/components/chat/chat-input.component.d.ts +65 -0
- package/dist/components/chat/chat-interface.component.d.ts +71 -0
- package/dist/components/chat/chat-message.component.d.ts +53 -0
- package/dist/components/chat/chat-settings.component.d.ts +62 -0
- package/dist/components/common/error-message.component.d.ts +11 -0
- package/dist/components/common/loading-spinner.component.d.ts +4 -0
- package/dist/components/security/consent-dialog.component.d.ts +11 -0
- package/dist/components/security/password-prompt.component.d.ts +10 -0
- package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
- package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
- package/dist/components/settings/general-settings.component.d.ts +60 -0
- package/dist/components/settings/provider-config.component.d.ts +182 -0
- package/dist/components/settings/security-settings.component.d.ts +23 -0
- package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
- package/dist/components/terminal/command-preview.component.d.ts +15 -0
- package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +18 -0
- package/dist/main.d.ts +8 -0
- package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
- package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
- package/dist/services/chat/chat-history.service.d.ts +67 -0
- package/dist/services/chat/chat-session.service.d.ts +58 -0
- package/dist/services/chat/command-generator.service.d.ts +49 -0
- package/dist/services/core/ai-assistant.service.d.ts +88 -0
- package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
- package/dist/services/core/config-provider.service.d.ts +137 -0
- package/dist/services/core/logger.service.d.ts +21 -0
- package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
- package/dist/services/providers/base-provider.service.d.ts +137 -0
- package/dist/services/providers/glm-provider.service.d.ts +91 -0
- package/dist/services/providers/minimax-provider.service.d.ts +93 -0
- package/dist/services/providers/openai-compatible.service.d.ts +39 -0
- package/dist/services/providers/openai-provider.service.d.ts +38 -0
- package/dist/services/security/consent-manager.service.d.ts +65 -0
- package/dist/services/security/password-manager.service.d.ts +67 -0
- package/dist/services/security/risk-assessment.service.d.ts +65 -0
- package/dist/services/security/security-validator.service.d.ts +36 -0
- package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
- package/dist/services/terminal/context-menu.service.d.ts +24 -0
- package/dist/services/terminal/hotkey.service.d.ts +28 -0
- package/dist/services/terminal/terminal-context.service.d.ts +100 -0
- package/dist/types/ai.types.d.ts +107 -0
- package/dist/types/provider.types.d.ts +105 -0
- package/dist/types/security.types.d.ts +85 -0
- package/dist/types/terminal.types.d.ts +150 -0
- package/dist/utils/encryption.utils.d.ts +83 -0
- package/dist/utils/formatting.utils.d.ts +106 -0
- package/dist/utils/validation.utils.d.ts +83 -0
- package/integration-test-output.txt +50 -0
- package/integration-tests/api-integration.test.ts +183 -0
- package/jest.config.js +47 -0
- package/package.json +73 -0
- package/setup-jest.ts +37 -0
- package/src/components/chat/chat-input.component.html +61 -0
- package/src/components/chat/chat-input.component.scss +183 -0
- package/src/components/chat/chat-input.component.ts +149 -0
- package/src/components/chat/chat-interface.component.html +119 -0
- package/src/components/chat/chat-interface.component.scss +354 -0
- package/src/components/chat/chat-interface.component.ts +224 -0
- package/src/components/chat/chat-message.component.html +65 -0
- package/src/components/chat/chat-message.component.scss +178 -0
- package/src/components/chat/chat-message.component.ts +93 -0
- package/src/components/chat/chat-settings.component.html +132 -0
- package/src/components/chat/chat-settings.component.scss +172 -0
- package/src/components/chat/chat-settings.component.ts +168 -0
- package/src/components/common/error-message.component.ts +124 -0
- package/src/components/common/loading-spinner.component.ts +72 -0
- package/src/components/security/consent-dialog.component.ts +77 -0
- package/src/components/security/password-prompt.component.ts +79 -0
- package/src/components/security/risk-confirm-dialog.component.html +87 -0
- package/src/components/security/risk-confirm-dialog.component.scss +360 -0
- package/src/components/security/risk-confirm-dialog.component.ts +96 -0
- package/src/components/settings/ai-settings-tab.component.html +140 -0
- package/src/components/settings/ai-settings-tab.component.scss +371 -0
- package/src/components/settings/ai-settings-tab.component.ts +193 -0
- package/src/components/settings/general-settings.component.html +103 -0
- package/src/components/settings/general-settings.component.scss +285 -0
- package/src/components/settings/general-settings.component.ts +123 -0
- package/src/components/settings/provider-config.component.html +95 -0
- package/src/components/settings/provider-config.component.scss +60 -0
- package/src/components/settings/provider-config.component.ts +206 -0
- package/src/components/settings/security-settings.component.html +51 -0
- package/src/components/settings/security-settings.component.scss +66 -0
- package/src/components/settings/security-settings.component.ts +71 -0
- package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
- package/src/components/terminal/command-preview.component.ts +185 -0
- package/src/components/terminal/command-suggestion.component.ts +128 -0
- package/src/index.ts +163 -0
- package/src/main.ts +16 -0
- package/src/providers/tabby/ai-config.provider.ts +70 -0
- package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
- package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
- package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
- package/src/services/chat/chat-history.service.ts +239 -0
- package/src/services/chat/chat-session.service.spec.ts +249 -0
- package/src/services/chat/chat-session.service.ts +180 -0
- package/src/services/chat/command-generator.service.ts +301 -0
- package/src/services/core/ai-assistant.service.ts +334 -0
- package/src/services/core/ai-provider-manager.service.ts +314 -0
- package/src/services/core/config-provider.service.ts +347 -0
- package/src/services/core/logger.service.ts +104 -0
- package/src/services/providers/anthropic-provider.service.ts +373 -0
- package/src/services/providers/base-provider.service.ts +369 -0
- package/src/services/providers/glm-provider.service.ts +467 -0
- package/src/services/providers/minimax-provider.service.ts +427 -0
- package/src/services/providers/openai-compatible.service.ts +394 -0
- package/src/services/providers/openai-provider.service.ts +376 -0
- package/src/services/security/consent-manager.service.ts +332 -0
- package/src/services/security/password-manager.service.ts +188 -0
- package/src/services/security/risk-assessment.service.ts +340 -0
- package/src/services/security/security-validator.service.ts +143 -0
- package/src/services/terminal/command-analyzer.service.ts +43 -0
- package/src/services/terminal/context-menu.service.ts +45 -0
- package/src/services/terminal/hotkey.service.ts +53 -0
- package/src/services/terminal/terminal-context.service.ts +317 -0
- package/src/styles/ai-assistant.scss +449 -0
- package/src/types/ai.types.ts +133 -0
- package/src/types/provider.types.ts +147 -0
- package/src/types/security.types.ts +103 -0
- package/src/types/terminal.types.ts +186 -0
- package/src/utils/encryption.utils.spec.ts +250 -0
- package/src/utils/encryption.utils.ts +271 -0
- package/src/utils/formatting.utils.ts +359 -0
- package/src/utils/validation.utils.spec.ts +225 -0
- package/src/utils/validation.utils.ts +314 -0
- package/tsconfig.json +45 -0
- package/webpack-docker.config.js +42 -0
- package/webpack.config.js +59 -0
- package/webpack.config.js.backup +57 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安全相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 风险级别
|
|
6
|
+
export enum RiskLevel {
|
|
7
|
+
LOW = 'low',
|
|
8
|
+
MEDIUM = 'medium',
|
|
9
|
+
HIGH = 'high',
|
|
10
|
+
CRITICAL = 'critical'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 风险评估结果
|
|
14
|
+
export interface RiskAssessment {
|
|
15
|
+
level: RiskLevel;
|
|
16
|
+
score: number; // 0-100
|
|
17
|
+
reasons: string[];
|
|
18
|
+
patterns: {
|
|
19
|
+
pattern: string;
|
|
20
|
+
match: string;
|
|
21
|
+
severity: RiskLevel;
|
|
22
|
+
}[];
|
|
23
|
+
suggestions?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 验证结果
|
|
27
|
+
export interface ValidationResult {
|
|
28
|
+
approved: boolean;
|
|
29
|
+
riskLevel: RiskLevel;
|
|
30
|
+
skipConfirmation?: boolean;
|
|
31
|
+
reason?: string;
|
|
32
|
+
timestamp?: Date;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 存储的同意
|
|
36
|
+
export interface StoredConsent {
|
|
37
|
+
commandHash: string;
|
|
38
|
+
riskLevel: RiskLevel;
|
|
39
|
+
timestamp: number;
|
|
40
|
+
expiry: number;
|
|
41
|
+
userId?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 密码验证结果
|
|
45
|
+
export interface PasswordValidationResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
attempts: number;
|
|
48
|
+
locked: boolean;
|
|
49
|
+
lockExpiry?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 安全配置
|
|
53
|
+
export interface SecurityConfig {
|
|
54
|
+
enablePasswordProtection: boolean;
|
|
55
|
+
passwordHash?: string;
|
|
56
|
+
consentExpiryDays: number;
|
|
57
|
+
maxConsentAge: number;
|
|
58
|
+
enableRiskAssessment: boolean;
|
|
59
|
+
autoApproveLowRisk: boolean;
|
|
60
|
+
promptForMediumRisk: boolean;
|
|
61
|
+
requirePasswordForHighRisk: boolean;
|
|
62
|
+
dangerousPatterns: string[];
|
|
63
|
+
allowedCommands: string[];
|
|
64
|
+
forbiddenCommands: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 安全事件
|
|
68
|
+
export interface SecurityEvent {
|
|
69
|
+
type: 'risk_assessed' | 'consent_given' | 'consent_rejected' | 'password_verified' | 'password_failed' | 'command_blocked' | 'command_allowed';
|
|
70
|
+
timestamp: Date;
|
|
71
|
+
command?: string;
|
|
72
|
+
riskLevel?: RiskLevel;
|
|
73
|
+
details?: Record<string, any>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 安全统计
|
|
77
|
+
export interface SecurityStats {
|
|
78
|
+
totalCommandsEvaluated: number;
|
|
79
|
+
totalCommandsBlocked: number;
|
|
80
|
+
totalConsentsGiven: number;
|
|
81
|
+
totalConsentsRejected: number;
|
|
82
|
+
totalPasswordAttempts: number;
|
|
83
|
+
failedPasswordAttempts: number;
|
|
84
|
+
averageRiskScore: number;
|
|
85
|
+
riskLevelDistribution: {
|
|
86
|
+
low: number;
|
|
87
|
+
medium: number;
|
|
88
|
+
high: number;
|
|
89
|
+
critical: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 密码策略
|
|
94
|
+
export interface PasswordPolicy {
|
|
95
|
+
minLength: number;
|
|
96
|
+
maxLength: number;
|
|
97
|
+
requireUppercase: boolean;
|
|
98
|
+
requireLowercase: boolean;
|
|
99
|
+
requireNumbers: boolean;
|
|
100
|
+
requireSpecialChars: boolean;
|
|
101
|
+
prohibitCommonPasswords: boolean;
|
|
102
|
+
prohibitReuse: number; // number of previous passwords to check
|
|
103
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 终端相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 终端会话信息
|
|
6
|
+
export interface TerminalSession {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
pid?: number;
|
|
9
|
+
cwd: string;
|
|
10
|
+
shell: string;
|
|
11
|
+
user?: string;
|
|
12
|
+
hostname?: string;
|
|
13
|
+
environment: Record<string, string>;
|
|
14
|
+
startTime: Date;
|
|
15
|
+
lastActivity: Date;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 终端上下文
|
|
19
|
+
export interface TerminalContext {
|
|
20
|
+
session: TerminalSession;
|
|
21
|
+
currentCommand?: string;
|
|
22
|
+
lastCommand?: string;
|
|
23
|
+
lastOutput?: string;
|
|
24
|
+
lastError?: string;
|
|
25
|
+
exitCode?: number;
|
|
26
|
+
isRunning: boolean;
|
|
27
|
+
runningProcess?: ProcessInfo;
|
|
28
|
+
recentCommands: string[];
|
|
29
|
+
systemInfo: SystemInfo;
|
|
30
|
+
projectInfo?: ProjectInfo;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 进程信息
|
|
34
|
+
export interface ProcessInfo {
|
|
35
|
+
pid: number;
|
|
36
|
+
name: string;
|
|
37
|
+
status: 'running' | 'sleeping' | 'stopped' | 'zombie';
|
|
38
|
+
cpu?: number;
|
|
39
|
+
memory?: number;
|
|
40
|
+
startTime?: Date;
|
|
41
|
+
command: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 系统信息
|
|
45
|
+
export interface SystemInfo {
|
|
46
|
+
platform: 'win32' | 'linux' | 'darwin' | 'freebsd' | 'sunos' | 'browser';
|
|
47
|
+
arch: string;
|
|
48
|
+
type: string;
|
|
49
|
+
release: string;
|
|
50
|
+
version?: string;
|
|
51
|
+
cpus: number;
|
|
52
|
+
totalMemory: number;
|
|
53
|
+
availableMemory?: number;
|
|
54
|
+
nodeVersion?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 项目信息(如果检测到)
|
|
58
|
+
export interface ProjectInfo {
|
|
59
|
+
type?: 'git' | 'npm' | 'yarn' | 'maven' | 'gradle' | 'pip' | 'cargo' | 'go' | 'rust';
|
|
60
|
+
root: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
version?: string;
|
|
63
|
+
dependencies?: string[];
|
|
64
|
+
scripts?: Record<string, string>;
|
|
65
|
+
description?: string;
|
|
66
|
+
language?: string;
|
|
67
|
+
framework?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 错误信息
|
|
71
|
+
export interface TerminalError {
|
|
72
|
+
type: 'command_not_found' | 'permission_denied' | 'file_not_found' | 'syntax_error' | 'runtime_error' | 'network_error' | 'unknown';
|
|
73
|
+
message: string;
|
|
74
|
+
command?: string;
|
|
75
|
+
exitCode?: number;
|
|
76
|
+
stack?: string;
|
|
77
|
+
suggestions?: string[];
|
|
78
|
+
timestamp: Date;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 缓冲区内容
|
|
82
|
+
export interface BufferContent {
|
|
83
|
+
content: string;
|
|
84
|
+
cursorPosition: number;
|
|
85
|
+
selectionStart?: number;
|
|
86
|
+
selectionEnd?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 命令执行结果
|
|
90
|
+
export interface CommandResult {
|
|
91
|
+
command: string;
|
|
92
|
+
exitCode: number;
|
|
93
|
+
stdout: string;
|
|
94
|
+
stderr: string;
|
|
95
|
+
duration: number;
|
|
96
|
+
timestamp: Date;
|
|
97
|
+
success: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 历史条目
|
|
101
|
+
export interface HistoryEntry {
|
|
102
|
+
command: string;
|
|
103
|
+
timestamp: Date;
|
|
104
|
+
exitCode?: number;
|
|
105
|
+
duration?: number;
|
|
106
|
+
cwd?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 环境变量变更
|
|
110
|
+
export interface EnvironmentChange {
|
|
111
|
+
key: string;
|
|
112
|
+
oldValue?: string;
|
|
113
|
+
newValue: string;
|
|
114
|
+
timestamp: Date;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 终端主题
|
|
118
|
+
export interface TerminalTheme {
|
|
119
|
+
name: string;
|
|
120
|
+
foreground: string;
|
|
121
|
+
background: string;
|
|
122
|
+
colors: string[];
|
|
123
|
+
cursor: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 文件系统状态
|
|
127
|
+
export interface FileSystemState {
|
|
128
|
+
currentPath: string;
|
|
129
|
+
files: FileInfo[];
|
|
130
|
+
permissions: Record<string, string>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 文件信息
|
|
134
|
+
export interface FileInfo {
|
|
135
|
+
name: string;
|
|
136
|
+
path: string;
|
|
137
|
+
type: 'file' | 'directory' | 'symlink' | 'device' | 'pipe' | 'socket';
|
|
138
|
+
size: number;
|
|
139
|
+
modified: Date;
|
|
140
|
+
permissions: string;
|
|
141
|
+
owner?: string;
|
|
142
|
+
group?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 快捷键定义
|
|
146
|
+
export interface Hotkey {
|
|
147
|
+
key: string;
|
|
148
|
+
description: string;
|
|
149
|
+
action: string;
|
|
150
|
+
scope?: 'global' | 'terminal' | 'chat';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 终端能力
|
|
154
|
+
export interface TerminalCapability {
|
|
155
|
+
name: string;
|
|
156
|
+
supported: boolean;
|
|
157
|
+
version?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 剪贴板内容
|
|
161
|
+
export interface ClipboardContent {
|
|
162
|
+
text: string;
|
|
163
|
+
type: 'plain' | 'rich' | 'image' | 'file';
|
|
164
|
+
timestamp: Date;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 自动补全候选
|
|
168
|
+
export interface AutoCompleteCandidate {
|
|
169
|
+
value: string;
|
|
170
|
+
description?: string;
|
|
171
|
+
type: 'command' | 'file' | 'directory' | 'variable' | 'function';
|
|
172
|
+
icon?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 终端警告/通知
|
|
176
|
+
export interface TerminalNotification {
|
|
177
|
+
type: 'warning' | 'info' | 'error' | 'success';
|
|
178
|
+
title: string;
|
|
179
|
+
message: string;
|
|
180
|
+
timestamp: Date;
|
|
181
|
+
persistent?: boolean;
|
|
182
|
+
actions?: {
|
|
183
|
+
label: string;
|
|
184
|
+
action: () => void;
|
|
185
|
+
}[];
|
|
186
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encrypt,
|
|
3
|
+
decrypt,
|
|
4
|
+
deriveKey,
|
|
5
|
+
generateSalt,
|
|
6
|
+
hash,
|
|
7
|
+
verifyHash,
|
|
8
|
+
encryptObject,
|
|
9
|
+
decryptObject,
|
|
10
|
+
generateToken,
|
|
11
|
+
base64Encode,
|
|
12
|
+
base64Decode,
|
|
13
|
+
secureCompare,
|
|
14
|
+
clearSensitiveData,
|
|
15
|
+
createSecureConfig,
|
|
16
|
+
parseSecureConfig,
|
|
17
|
+
hashPassword,
|
|
18
|
+
verifyPassword
|
|
19
|
+
} from './encryption.utils';
|
|
20
|
+
|
|
21
|
+
describe('EncryptionUtils', () => {
|
|
22
|
+
const testText = 'Hello, World!';
|
|
23
|
+
const testKey = 'my-secret-key-123';
|
|
24
|
+
const testPassword = 'SecurePassword123!';
|
|
25
|
+
const testObject = { name: 'John', age: 30, apiKey: 'secret-key' };
|
|
26
|
+
|
|
27
|
+
describe('encrypt and decrypt', () => {
|
|
28
|
+
it('should encrypt and decrypt text correctly', () => {
|
|
29
|
+
const encrypted = encrypt(testText, testKey);
|
|
30
|
+
expect(encrypted).not.toBe(testText);
|
|
31
|
+
|
|
32
|
+
const decrypted = decrypt(encrypted, testKey);
|
|
33
|
+
expect(decrypted).toBe(testText);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error for empty inputs', () => {
|
|
37
|
+
expect(() => encrypt('', testKey)).toThrow();
|
|
38
|
+
expect(() => encrypt(testText, '')).toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should throw error for invalid decryption', () => {
|
|
42
|
+
const encrypted = encrypt(testText, testKey);
|
|
43
|
+
expect(() => decrypt(encrypted, 'wrong-key')).toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('deriveKey', () => {
|
|
48
|
+
it('should derive key from password and salt', () => {
|
|
49
|
+
const salt = generateSalt();
|
|
50
|
+
const key1 = deriveKey(testPassword, salt);
|
|
51
|
+
const key2 = deriveKey(testPassword, salt);
|
|
52
|
+
|
|
53
|
+
expect(key1).toBe(key2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should produce different keys for different salts', () => {
|
|
57
|
+
const salt1 = generateSalt();
|
|
58
|
+
const salt2 = generateSalt();
|
|
59
|
+
const key1 = deriveKey(testPassword, salt1);
|
|
60
|
+
const key2 = deriveKey(testPassword, salt2);
|
|
61
|
+
|
|
62
|
+
expect(key1).not.toBe(key2);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('hash and verifyHash', () => {
|
|
67
|
+
it('should hash text correctly', () => {
|
|
68
|
+
const hash1 = hash(testText);
|
|
69
|
+
const hash2 = hash(testText);
|
|
70
|
+
|
|
71
|
+
expect(hash1).toBe(hash2);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should verify hash correctly', () => {
|
|
75
|
+
const salt = generateSalt();
|
|
76
|
+
const hashValue = hash(testText, salt);
|
|
77
|
+
|
|
78
|
+
expect(verifyHash(testText, hashValue, salt)).toBe(true);
|
|
79
|
+
expect(verifyHash('wrong-text', hashValue, salt)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('encryptObject and decryptObject', () => {
|
|
84
|
+
it('should encrypt and decrypt objects correctly', () => {
|
|
85
|
+
const encrypted = encryptObject(testObject, testKey);
|
|
86
|
+
const decrypted = decryptObject(encrypted, testKey);
|
|
87
|
+
|
|
88
|
+
expect(decrypted).toEqual(testObject);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should throw error for invalid decryption', () => {
|
|
92
|
+
const encrypted = encryptObject(testObject, testKey);
|
|
93
|
+
expect(() => decryptObject(encrypted, 'wrong-key')).toThrow();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('generateToken', () => {
|
|
98
|
+
it('should generate tokens of correct length', () => {
|
|
99
|
+
const token1 = generateToken(32);
|
|
100
|
+
const token2 = generateToken(64);
|
|
101
|
+
|
|
102
|
+
expect(token1.length).toBe(32);
|
|
103
|
+
expect(token2.length).toBe(64);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should generate different tokens', () => {
|
|
107
|
+
const token1 = generateToken();
|
|
108
|
+
const token2 = generateToken();
|
|
109
|
+
|
|
110
|
+
expect(token1).not.toBe(token2);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('base64Encode and base64Decode', () => {
|
|
115
|
+
it('should encode and decode text correctly', () => {
|
|
116
|
+
const encoded = base64Encode(testText);
|
|
117
|
+
const decoded = base64Decode(encoded);
|
|
118
|
+
|
|
119
|
+
expect(decoded).toBe(testText);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle special characters', () => {
|
|
123
|
+
const specialText = 'Hello 世界!@#$%';
|
|
124
|
+
const encoded = base64Encode(specialText);
|
|
125
|
+
const decoded = base64Decode(encoded);
|
|
126
|
+
|
|
127
|
+
expect(decoded).toBe(specialText);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('secureCompare', () => {
|
|
132
|
+
it('should return true for equal strings', () => {
|
|
133
|
+
expect(secureCompare('test', 'test')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return false for different strings', () => {
|
|
137
|
+
expect(secureCompare('test', 'Test')).toBe(false);
|
|
138
|
+
expect(secureCompare('test', 'testing')).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should prevent timing attacks', () => {
|
|
142
|
+
// This is a basic test - in reality, timing attack prevention
|
|
143
|
+
// would require more sophisticated testing
|
|
144
|
+
const start1 = performance.now();
|
|
145
|
+
secureCompare('a'.repeat(1000), 'b'.repeat(1000));
|
|
146
|
+
const time1 = performance.now() - start1;
|
|
147
|
+
|
|
148
|
+
const start2 = performance.now();
|
|
149
|
+
secureCompare('a', 'b');
|
|
150
|
+
const time2 = performance.now() - start2;
|
|
151
|
+
|
|
152
|
+
// Times should be relatively similar (within an order of magnitude)
|
|
153
|
+
expect(Math.abs(time1 - time2)).toBeLessThan(time2 * 10);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('clearSensitiveData', () => {
|
|
158
|
+
it('should clear sensitive fields', () => {
|
|
159
|
+
const data = { ...testObject };
|
|
160
|
+
clearSensitiveData(data);
|
|
161
|
+
|
|
162
|
+
expect(data.apiKey).toBe('********');
|
|
163
|
+
expect(data.name).toBe('John');
|
|
164
|
+
expect(data.age).toBe(30);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should recursively clear nested objects', () => {
|
|
168
|
+
const nestedData = {
|
|
169
|
+
user: {
|
|
170
|
+
name: 'John',
|
|
171
|
+
password: 'secret',
|
|
172
|
+
apiKey: 'key123'
|
|
173
|
+
},
|
|
174
|
+
normalField: 'value'
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
clearSensitiveData(nestedData);
|
|
178
|
+
|
|
179
|
+
expect(nestedData.user.password).toBe('********');
|
|
180
|
+
expect(nestedData.user.apiKey).toBe('********');
|
|
181
|
+
expect(nestedData.user.name).toBe('John');
|
|
182
|
+
expect(nestedData.normalField).toBe('value');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should handle arrays', () => {
|
|
186
|
+
const arrayData = [
|
|
187
|
+
{ apiKey: 'key1', name: 'Item 1' },
|
|
188
|
+
{ password: 'pass2', value: 'Item 2' }
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
clearSensitiveData(arrayData);
|
|
192
|
+
|
|
193
|
+
expect(arrayData[0].apiKey).toBe('********');
|
|
194
|
+
expect(arrayData[1].password).toBe('********');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('createSecureConfig and parseSecureConfig', () => {
|
|
199
|
+
it('should create and parse secure config', () => {
|
|
200
|
+
const masterPassword = 'master-password-123';
|
|
201
|
+
const configData = { apiKey: 'test-key', setting: 'value' };
|
|
202
|
+
|
|
203
|
+
const { encrypted, salt } = createSecureConfig(configData, masterPassword);
|
|
204
|
+
expect(encrypted).toBeDefined();
|
|
205
|
+
expect(salt).toBeDefined();
|
|
206
|
+
|
|
207
|
+
const parsed = parseSecureConfig(encrypted, salt, masterPassword);
|
|
208
|
+
expect(parsed).toEqual(configData);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should throw error with wrong master password', () => {
|
|
212
|
+
const masterPassword = 'master-password-123';
|
|
213
|
+
const wrongPassword = 'wrong-password';
|
|
214
|
+
const configData = { apiKey: 'test-key' };
|
|
215
|
+
|
|
216
|
+
const { encrypted, salt } = createSecureConfig(configData, masterPassword);
|
|
217
|
+
|
|
218
|
+
expect(() => parseSecureConfig(encrypted, salt, wrongPassword)).toThrow();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('hashPassword and verifyPassword', () => {
|
|
223
|
+
it('should hash and verify passwords correctly', () => {
|
|
224
|
+
const { hash: passwordHash, salt } = hashPassword(testPassword);
|
|
225
|
+
|
|
226
|
+
expect(passwordHash).toBeDefined();
|
|
227
|
+
expect(salt).toBeDefined();
|
|
228
|
+
expect(passwordHash.length).toBe(64); // SHA256 hex string length
|
|
229
|
+
|
|
230
|
+
expect(verifyPassword(testPassword, passwordHash, salt)).toBe(true);
|
|
231
|
+
expect(verifyPassword('wrong-password', passwordHash, salt)).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should handle custom salt', () => {
|
|
235
|
+
const customSalt = generateSalt();
|
|
236
|
+
const { hash: hash1 } = hashPassword(testPassword, customSalt);
|
|
237
|
+
const { hash: hash2 } = hashPassword(testPassword, customSalt);
|
|
238
|
+
|
|
239
|
+
expect(hash1).toBe(hash2);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should validate password strength', () => {
|
|
243
|
+
const weakResult = hashPassword('123');
|
|
244
|
+
expect(weakResult.valid).toBe(false);
|
|
245
|
+
|
|
246
|
+
const strongResult = hashPassword(testPassword);
|
|
247
|
+
expect(strongResult.valid).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|