mr-sliy 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.
Files changed (50) hide show
  1. package/.env.example +145 -0
  2. package/database/schema.sql +187 -0
  3. package/package.json +74 -0
  4. package/scripts/download-tree-sitter.js +171 -0
  5. package/scripts/postinstall.js +134 -0
  6. package/src/agent/agent.js +563 -0
  7. package/src/agent.js +87 -0
  8. package/src/cli/index.js +1643 -0
  9. package/src/config/index.js +232 -0
  10. package/src/engine/dualModeEngine.js +486 -0
  11. package/src/index.js +165 -0
  12. package/src/middlewares/errorHandler.js +166 -0
  13. package/src/middlewares/index.js +23 -0
  14. package/src/routes/aiRoutes.js +117 -0
  15. package/src/routes/configRoutes.js +31 -0
  16. package/src/routes/index.js +75 -0
  17. package/src/routes/issueRoutes.js +195 -0
  18. package/src/routes/projectRoutes.js +46 -0
  19. package/src/routes/reportRoutes.js +40 -0
  20. package/src/routes/scanRoutes.js +245 -0
  21. package/src/routes/userRoutes.js +47 -0
  22. package/src/services/ast/parser.js +503 -0
  23. package/src/services/detection/detector.js +934 -0
  24. package/src/services/llm/providers.js +1107 -0
  25. package/src/services/rag/agent.js +375 -0
  26. package/src/services/vector/knowledgeBase.js +863 -0
  27. package/src/skills/Skill.js +38 -0
  28. package/src/skills/code-analysis/index.js +272 -0
  29. package/src/skills/code-detection/index.js +166 -0
  30. package/src/skills/code-detection/rules/console-log.js +45 -0
  31. package/src/skills/code-detection/rules/deep-nesting.js +76 -0
  32. package/src/skills/code-detection/rules/duplicate-code.js +57 -0
  33. package/src/skills/code-detection/rules/high-complexity.js +109 -0
  34. package/src/skills/code-detection/rules/index.js +59 -0
  35. package/src/skills/code-detection/rules/long-functions.js +54 -0
  36. package/src/skills/code-detection/rules/magic-numbers.js +48 -0
  37. package/src/skills/code-detection/rules/missing-comment.js +64 -0
  38. package/src/skills/code-detection/rules/null-check.js +71 -0
  39. package/src/skills/code-detection/rules/unnecessary-else.js +46 -0
  40. package/src/skills/code-detection/rules/unused-functions.js +57 -0
  41. package/src/skills/code-detection/rules/unused-imports.js +57 -0
  42. package/src/skills/code-detection/rules/unused-variables.js +54 -0
  43. package/src/skills/code-optimization/index.js +319 -0
  44. package/src/skills/index.js +152 -0
  45. package/src/utils/crypto.js +212 -0
  46. package/src/utils/database.js +125 -0
  47. package/src/utils/helpers.js +226 -0
  48. package/src/utils/logger.js +202 -0
  49. package/src/utils/mysql.js +198 -0
  50. package/src/utils/response.js +124 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * 代码优化技能
3
+ * 提供多种代码优化策略,包括自动修复和改进建议
4
+ */
5
+
6
+ const Skill = require('../Skill');
7
+ const { logger } = require('../../utils/logger');
8
+ const { getFileLanguage } = require('../../utils/helpers');
9
+ const { knowledgeBase } = require('../../services/vector/knowledgeBase');
10
+
11
+ class CodeOptimizationSkill extends Skill {
12
+ constructor() {
13
+ super(
14
+ 'code-optimization',
15
+ '代码优化技能 - 提供代码优化建议和自动修复',
16
+ '1.0.0'
17
+ );
18
+ this.dependencies = ['code-detection', 'knowledge-base'];
19
+ this.optimizationStrategies = [
20
+ 'remove-unused-imports',
21
+ 'remove-unused-variables',
22
+ 'remove-console-log',
23
+ 'remove-unnecessary-else',
24
+ 'extract-magic-numbers',
25
+ 'add-null-checks',
26
+ 'add-comments',
27
+ 'simplify-deep-nesting',
28
+ 'split-long-functions'
29
+ ];
30
+ }
31
+
32
+ async init() {
33
+ logger.info('代码优化技能初始化完成');
34
+ return true;
35
+ }
36
+
37
+ canExecute(context = {}) {
38
+ return this.enabled && (context.sourceCode || context.filePath || context.issues);
39
+ }
40
+
41
+ async execute(context = {}) {
42
+ const { sourceCode, filePath, issues, options = {} } = context;
43
+
44
+ if (!sourceCode && !filePath) {
45
+ throw new Error('请提供源代码或文件路径');
46
+ }
47
+
48
+ const code = sourceCode || this._readFile(filePath);
49
+ const language = options.language || getFileLanguage(filePath || '');
50
+
51
+ return this._optimizeCode(code, filePath || 'unknown', language, issues, options);
52
+ }
53
+
54
+ async optimizeFile(filePath, options = {}) {
55
+ const fs = require('fs');
56
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
57
+ return this.execute({ sourceCode, filePath, options });
58
+ }
59
+
60
+ async getOptimizationSuggestions(issues, context = {}) {
61
+ const suggestions = [];
62
+ const language = context.language || 'javascript';
63
+
64
+ for (const issue of issues) {
65
+ const suggestion = await this._getSuggestionForIssue(issue, language);
66
+ if (suggestion) {
67
+ suggestions.push(suggestion);
68
+ }
69
+ }
70
+
71
+ return {
72
+ success: true,
73
+ totalSuggestions: suggestions.length,
74
+ suggestions
75
+ };
76
+ }
77
+
78
+ async applyAutoFix(issue, sourceCode) {
79
+ const fixStrategy = this._getFixStrategy(issue.issueType);
80
+ if (!fixStrategy) {
81
+ return {
82
+ success: false,
83
+ message: `没有可用的自动修复策略: ${issue.issueType}`
84
+ };
85
+ }
86
+
87
+ try {
88
+ const result = fixStrategy.apply(sourceCode, issue);
89
+ return {
90
+ success: true,
91
+ issueType: issue.issueType,
92
+ originalCode: issue.codeSnippet,
93
+ optimizedCode: result.fixedCode,
94
+ explanation: result.explanation
95
+ };
96
+ } catch (error) {
97
+ logger.error(`自动修复失败: ${issue.issueType}`, error);
98
+ return {
99
+ success: false,
100
+ issueType: issue.issueType,
101
+ error: error.message
102
+ };
103
+ }
104
+ }
105
+
106
+ getAvailableStrategies() {
107
+ return this.optimizationStrategies;
108
+ }
109
+
110
+ _readFile(filePath) {
111
+ const fs = require('fs');
112
+ return fs.readFileSync(filePath, 'utf-8');
113
+ }
114
+
115
+ async _optimizeCode(sourceCode, filePath, language, issues, options) {
116
+ const startTime = Date.now();
117
+
118
+ try {
119
+ logger.debug(`开始优化代码: ${filePath}`);
120
+
121
+ let detectionIssues = issues;
122
+
123
+ if (!detectionIssues || detectionIssues.length === 0) {
124
+ const codeDetection = require('../code-detection');
125
+ const detectResult = await codeDetection.execute({
126
+ sourceCode,
127
+ filePath,
128
+ options: { language }
129
+ });
130
+ detectionIssues = detectResult.issues || [];
131
+ }
132
+
133
+ const optimizableIssues = detectionIssues.filter(issue =>
134
+ this._canAutoFix(issue.issueType)
135
+ );
136
+
137
+ const suggestions = await this._generateSuggestions(detectionIssues, language);
138
+
139
+ let optimizedCode = sourceCode;
140
+ const appliedFixes = [];
141
+
142
+ if (options.autoFix !== false) {
143
+ for (const issue of optimizableIssues) {
144
+ const fixResult = await this.applyAutoFix(issue, optimizedCode);
145
+ if (fixResult.success) {
146
+ optimizedCode = fixResult.optimizedCode;
147
+ appliedFixes.push(fixResult);
148
+ }
149
+ }
150
+ }
151
+
152
+ const knowledgeSuggestions = await this._getKnowledgeBaseSuggestions(
153
+ detectionIssues,
154
+ language
155
+ );
156
+
157
+ return {
158
+ success: true,
159
+ filePath,
160
+ language,
161
+ totalIssues: detectionIssues.length,
162
+ autoFixable: optimizableIssues.length,
163
+ appliedFixes: appliedFixes.length,
164
+ suggestions: [...suggestions, ...knowledgeSuggestions],
165
+ originalCode: sourceCode,
166
+ optimizedCode,
167
+ issues: detectionIssues,
168
+ durationMs: Date.now() - startTime
169
+ };
170
+ } catch (error) {
171
+ logger.error(`代码优化失败: ${filePath}`, error);
172
+ return {
173
+ success: false,
174
+ message: error.message,
175
+ filePath,
176
+ durationMs: Date.now() - startTime
177
+ };
178
+ }
179
+ }
180
+
181
+ _canAutoFix(issueType) {
182
+ const autoFixable = [
183
+ 'unused_import',
184
+ 'unused_variable',
185
+ 'console_log',
186
+ 'unnecessary_else',
187
+ 'magic_number'
188
+ ];
189
+ return autoFixable.includes(issueType);
190
+ }
191
+
192
+ _getFixStrategy(issueType) {
193
+ const strategies = {
194
+ unused_import: {
195
+ apply: (code, issue) => {
196
+ const lines = code.split('\n');
197
+ const lineIndex = issue.lineStart - 1;
198
+ if (lineIndex >= 0 && lineIndex < lines.length) {
199
+ lines.splice(lineIndex, 1);
200
+ return {
201
+ fixedCode: lines.join('\n'),
202
+ explanation: '删除了未使用的导入语句'
203
+ };
204
+ }
205
+ return { fixedCode: code, explanation: '无法定位导入语句' };
206
+ }
207
+ },
208
+ console_log: {
209
+ apply: (code, issue) => {
210
+ const lines = code.split('\n');
211
+ const lineIndex = issue.lineStart - 1;
212
+ if (lineIndex >= 0 && lineIndex < lines.length) {
213
+ lines[lineIndex] = `// ${lines[lineIndex].trim()}`;
214
+ return {
215
+ fixedCode: lines.join('\n'),
216
+ explanation: '注释掉了调试用的 console.log'
217
+ };
218
+ }
219
+ return { fixedCode: code, explanation: '无法定位 console.log' };
220
+ }
221
+ },
222
+ unnecessary_else: {
223
+ apply: (code, issue) => {
224
+ return {
225
+ fixedCode: code,
226
+ explanation: '建议手动移除 return 后的 else 语句'
227
+ };
228
+ }
229
+ },
230
+ magic_number: {
231
+ apply: (code, issue) => {
232
+ return {
233
+ fixedCode: code,
234
+ explanation: '建议将魔法数字提取为具名常量'
235
+ };
236
+ }
237
+ }
238
+ };
239
+
240
+ return strategies[issueType] || null;
241
+ }
242
+
243
+ async _generateSuggestions(issues, language) {
244
+ const suggestions = [];
245
+ const seenTypes = new Set();
246
+
247
+ for (const issue of issues) {
248
+ if (!seenTypes.has(issue.issueType)) {
249
+ seenTypes.add(issue.issueType);
250
+ suggestions.push({
251
+ type: issue.issueType,
252
+ severity: issue.severity,
253
+ title: this._getIssueTitle(issue.issueType),
254
+ description: issue.suggestion,
255
+ count: issues.filter(i => i.issueType === issue.issueType).length,
256
+ autoFixable: this._canAutoFix(issue.issueType)
257
+ });
258
+ }
259
+ }
260
+
261
+ return suggestions.sort((a, b) => {
262
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
263
+ return severityOrder[a.severity] - severityOrder[b.severity];
264
+ });
265
+ }
266
+
267
+ _getIssueTitle(issueType) {
268
+ const titles = {
269
+ unused_variable: '未使用的变量',
270
+ unused_import: '未使用的导入',
271
+ unused_function: '未使用的函数',
272
+ magic_number: '魔法数字',
273
+ long_function: '过长函数',
274
+ high_complexity: '圈复杂度过高',
275
+ deep_nesting: '嵌套层级过深',
276
+ null_check: '缺少空值检查',
277
+ unnecessary_else: '不必要的 else 语句',
278
+ console_log: '调试 console.log',
279
+ duplicate_code: '重复代码',
280
+ missing_comment: '缺少注释'
281
+ };
282
+ return titles[issueType] || issueType;
283
+ }
284
+
285
+ async _getKnowledgeBaseSuggestions(issues, language) {
286
+ const suggestions = [];
287
+
288
+ try {
289
+ if (issues.length > 0) {
290
+ const topIssues = issues.slice(0, 3);
291
+ for (const issue of topIssues) {
292
+ const query = `${issue.issueType} ${issue.message} best practices`;
293
+ const knowledgeResults = await knowledgeBase.search(query, {
294
+ language,
295
+ limit: 2
296
+ });
297
+
298
+ if (knowledgeResults && knowledgeResults.length > 0) {
299
+ suggestions.push({
300
+ type: 'knowledge-base',
301
+ severity: 'info',
302
+ title: `知识库建议: ${this._getIssueTitle(issue.issueType)}`,
303
+ description: `找到 ${knowledgeResults.length} 条相关知识`,
304
+ relatedKnowledge: knowledgeResults
305
+ });
306
+ }
307
+ }
308
+ }
309
+ } catch (error) {
310
+ logger.debug('知识库建议获取失败:', error.message);
311
+ }
312
+
313
+ return suggestions;
314
+ }
315
+ }
316
+
317
+ const codeOptimizationSkill = new CodeOptimizationSkill();
318
+
319
+ module.exports = codeOptimizationSkill;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * 技能管理器
3
+ * 管理所有智能体技能的注册、加载和执行
4
+ */
5
+
6
+ const { logger } = require('../utils/logger');
7
+ const Skill = require('./Skill');
8
+
9
+ class SkillManager {
10
+ constructor() {
11
+ this.skills = new Map();
12
+ this.initialized = false;
13
+ }
14
+
15
+ async init() {
16
+ if (this.initialized) return;
17
+
18
+ logger.info('正在初始化技能管理器...');
19
+
20
+ const codeDetectionSkill = require('./code-detection');
21
+ this.register(codeDetectionSkill);
22
+
23
+ const codeOptimizationSkill = require('./code-optimization');
24
+ this.register(codeOptimizationSkill);
25
+
26
+ const codeAnalysisSkill = require('./code-analysis');
27
+ this.register(codeAnalysisSkill);
28
+
29
+ for (const skill of this.skills.values()) {
30
+ if (typeof skill.init === 'function') {
31
+ try {
32
+ await skill.init();
33
+ logger.debug(`技能初始化完成: ${skill.name}`);
34
+ } catch (error) {
35
+ logger.error(`技能初始化失败: ${skill.name}`, error);
36
+ }
37
+ }
38
+ }
39
+
40
+ this.initialized = true;
41
+ logger.info(`技能管理器初始化完成,共加载 ${this.skills.size} 个技能`);
42
+ }
43
+
44
+ register(skill) {
45
+ if (!(skill instanceof Skill)) {
46
+ throw new Error('注册的技能必须是 Skill 类的实例');
47
+ }
48
+
49
+ if (this.skills.has(skill.name)) {
50
+ logger.warn(`技能已存在,将被覆盖: ${skill.name}`);
51
+ }
52
+
53
+ this.skills.set(skill.name, skill);
54
+ logger.debug(`注册技能: ${skill.name} v${skill.version}`);
55
+ return true;
56
+ }
57
+
58
+ unregister(skillName) {
59
+ if (this.skills.has(skillName)) {
60
+ this.skills.delete(skillName);
61
+ logger.debug(`注销技能: ${skillName}`);
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ getSkill(skillName) {
68
+ return this.skills.get(skillName) || null;
69
+ }
70
+
71
+ hasSkill(skillName) {
72
+ return this.skills.has(skillName);
73
+ }
74
+
75
+ getAllSkills() {
76
+ return Array.from(this.skills.values()).map(skill => skill.getInfo());
77
+ }
78
+
79
+ getEnabledSkills() {
80
+ return Array.from(this.skills.values())
81
+ .filter(skill => skill.enabled)
82
+ .map(skill => skill.getInfo());
83
+ }
84
+
85
+ async executeSkill(skillName, context = {}) {
86
+ const skill = this.skills.get(skillName);
87
+
88
+ if (!skill) {
89
+ throw new Error(`未找到技能: ${skillName}`);
90
+ }
91
+
92
+ if (!skill.enabled) {
93
+ throw new Error(`技能已禁用: ${skillName}`);
94
+ }
95
+
96
+ if (!skill.canExecute(context)) {
97
+ throw new Error(`技能 ${skillName} 无法在当前上下文执行`);
98
+ }
99
+
100
+ logger.debug(`执行技能: ${skillName}`);
101
+ const startTime = Date.now();
102
+
103
+ try {
104
+ const result = await skill.execute(context);
105
+ const duration = Date.now() - startTime;
106
+ logger.debug(`技能执行完成: ${skillName} (${duration}ms)`);
107
+ return {
108
+ success: true,
109
+ skill: skillName,
110
+ result,
111
+ durationMs: duration
112
+ };
113
+ } catch (error) {
114
+ const duration = Date.now() - startTime;
115
+ logger.error(`技能执行失败: ${skillName}`, error);
116
+ return {
117
+ success: false,
118
+ skill: skillName,
119
+ error: error.message,
120
+ durationMs: duration
121
+ };
122
+ }
123
+ }
124
+
125
+ enableSkill(skillName) {
126
+ const skill = this.skills.get(skillName);
127
+ if (skill) {
128
+ skill.enabled = true;
129
+ logger.info(`启用技能: ${skillName}`);
130
+ return true;
131
+ }
132
+ return false;
133
+ }
134
+
135
+ disableSkill(skillName) {
136
+ const skill = this.skills.get(skillName);
137
+ if (skill) {
138
+ skill.enabled = false;
139
+ logger.info(`禁用技能: ${skillName}`);
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+ }
145
+
146
+ const skillManager = new SkillManager();
147
+
148
+ module.exports = {
149
+ SkillManager,
150
+ skillManager,
151
+ Skill
152
+ };
@@ -0,0 +1,212 @@
1
+ /**
2
+ * 加密工具模块
3
+ * 用于安全存储敏感配置数据(API Key、密码等)
4
+ * 使用 AES-256-GCM 加密算法
5
+ */
6
+
7
+ const crypto = require('crypto');
8
+ const os = require('os');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const ALGORITHM = 'aes-256-gcm';
13
+ const IV_LENGTH = 16;
14
+ const SALT_LENGTH = 64;
15
+ const TAG_LENGTH = 16;
16
+
17
+ /**
18
+ * 生成机器唯一密钥(基于机器特征派生)
19
+ * 这样每台机器的密钥不同,即使文件泄露也无法在其他机器解密
20
+ */
21
+ function getMachineKey() {
22
+ const hostname = os.hostname();
23
+ const username = os.userInfo().username;
24
+ const platform = os.platform();
25
+ const release = os.release();
26
+ const cpuCores = os.cpus().length;
27
+ const totalMem = os.totalmem();
28
+
29
+ const machineSeed = `${hostname}|${username}|${platform}|${release}|${cpuCores}|${totalMem}`;
30
+
31
+ return crypto.createHash('sha256')
32
+ .update(machineSeed)
33
+ .digest();
34
+ }
35
+
36
+ /**
37
+ * 加密数据
38
+ */
39
+ function encrypt(plaintext, key = null) {
40
+ if (!key) {
41
+ key = getMachineKey();
42
+ }
43
+
44
+ if (typeof plaintext === 'object') {
45
+ plaintext = JSON.stringify(plaintext);
46
+ }
47
+
48
+ const iv = crypto.randomBytes(IV_LENGTH);
49
+ const salt = crypto.randomBytes(SALT_LENGTH);
50
+
51
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
52
+ cipher.setAAD(salt);
53
+
54
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
55
+ encrypted += cipher.final('hex');
56
+
57
+ const tag = cipher.getAuthTag();
58
+
59
+ const result = {
60
+ iv: iv.toString('hex'),
61
+ salt: salt.toString('hex'),
62
+ tag: tag.toString('hex'),
63
+ encrypted: encrypted
64
+ };
65
+
66
+ return JSON.stringify(result);
67
+ }
68
+
69
+ /**
70
+ * 解密数据
71
+ */
72
+ function decrypt(encryptedData, key = null) {
73
+ if (!key) {
74
+ key = getMachineKey();
75
+ }
76
+
77
+ try {
78
+ const data = typeof encryptedData === 'string'
79
+ ? JSON.parse(encryptedData)
80
+ : encryptedData;
81
+
82
+ const iv = Buffer.from(data.iv, 'hex');
83
+ const salt = Buffer.from(data.salt, 'hex');
84
+ const tag = Buffer.from(data.tag, 'hex');
85
+ const encrypted = data.encrypted;
86
+
87
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
88
+ decipher.setAAD(salt);
89
+ decipher.setAuthTag(tag);
90
+
91
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
92
+ decrypted += decipher.final('utf8');
93
+
94
+ try {
95
+ return JSON.parse(decrypted);
96
+ } catch {
97
+ return decrypted;
98
+ }
99
+ } catch (error) {
100
+ throw new Error('解密失败:密钥不匹配或数据已损坏');
101
+ }
102
+ }
103
+
104
+ /**
105
+ * 加密存储到文件
106
+ */
107
+ function encryptToFile(filePath, data) {
108
+ const dir = path.dirname(filePath);
109
+ if (!fs.existsSync(dir)) {
110
+ fs.mkdirSync(dir, { recursive: true });
111
+ }
112
+
113
+ const encrypted = encrypt(data);
114
+ fs.writeFileSync(filePath, encrypted, 'utf8');
115
+
116
+ return true;
117
+ }
118
+
119
+ /**
120
+ * 从加密文件读取
121
+ */
122
+ function decryptFromFile(filePath) {
123
+ if (!fs.existsSync(filePath)) {
124
+ return null;
125
+ }
126
+
127
+ const encryptedData = fs.readFileSync(filePath, 'utf8');
128
+ return decrypt(encryptedData);
129
+ }
130
+
131
+ /**
132
+ * 哈希数据(用于校验,不可逆)
133
+ */
134
+ function hash(data, algorithm = 'sha256') {
135
+ return crypto.createHash(algorithm)
136
+ .update(typeof data === 'string' ? data : JSON.stringify(data))
137
+ .digest('hex');
138
+ }
139
+
140
+ /**
141
+ * 生成随机密钥
142
+ */
143
+ function generateKey(length = 32) {
144
+ return crypto.randomBytes(length).toString('hex');
145
+ }
146
+
147
+ /**
148
+ * 安全比较(防止时序攻击)
149
+ */
150
+ function safeCompare(a, b) {
151
+ return crypto.timingSafeEqual(
152
+ Buffer.from(a),
153
+ Buffer.from(b)
154
+ );
155
+ }
156
+
157
+ /**
158
+ * 密码哈希(使用 scrypt + salt)
159
+ * 返回格式: scrypt$N$r$p$salt$hash
160
+ */
161
+ function hashPassword(password) {
162
+ const salt = crypto.randomBytes(16).toString('hex');
163
+ const N = 16384;
164
+ const r = 8;
165
+ const p = 1;
166
+ const keyLength = 64;
167
+
168
+ const derivedKey = crypto.scryptSync(password, salt, keyLength, { N, r, p });
169
+
170
+ return `scrypt$${N}$${r}$${p}$${salt}$${derivedKey.toString('hex')}`;
171
+ }
172
+
173
+ /**
174
+ * 密码验证
175
+ */
176
+ function verifyPassword(password, hashedPassword) {
177
+ try {
178
+ const parts = hashedPassword.split('$');
179
+ if (parts.length !== 6 || parts[0] !== 'scrypt') {
180
+ return false;
181
+ }
182
+
183
+ const [, N, r, p, salt, hash] = parts;
184
+ const keyLength = Buffer.from(hash, 'hex').length;
185
+
186
+ const derivedKey = crypto.scryptSync(password, salt, keyLength, {
187
+ N: parseInt(N),
188
+ r: parseInt(r),
189
+ p: parseInt(p)
190
+ });
191
+
192
+ return crypto.timingSafeEqual(
193
+ Buffer.from(hash, 'hex'),
194
+ derivedKey
195
+ );
196
+ } catch (error) {
197
+ return false;
198
+ }
199
+ }
200
+
201
+ module.exports = {
202
+ encrypt,
203
+ decrypt,
204
+ encryptToFile,
205
+ decryptFromFile,
206
+ hash,
207
+ generateKey,
208
+ safeCompare,
209
+ getMachineKey,
210
+ hashPassword,
211
+ verifyPassword
212
+ };