mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix: resolve skills, memory, and encryption system issues (#1456)
Integrated into release/v3.7.0
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { resolveDataDir } from "../../src/lib/dataPaths";
|
||||
|
||||
// Fields that can be overridden per provider
|
||||
const CREDENTIAL_FIELDS = [
|
||||
@@ -41,27 +40,23 @@ function credGlobals(): CredGlobals {
|
||||
return globalThis as CredGlobals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the path to provider-credentials.json using the application's
|
||||
* data directory. Delegates to resolveDataDir() which handles DATA_DIR env,
|
||||
* platform-specific defaults, and fallback logic.
|
||||
*
|
||||
* previous: Priority: DATA_DIR env → ./data (project root)
|
||||
*/
|
||||
function resolveCredentialsPath(): string {
|
||||
let resolveDataDir: (options?: { isCloud?: boolean }) => string;
|
||||
|
||||
try {
|
||||
resolveDataDir = require("@/lib/dataPaths").resolveDataDir;
|
||||
} catch (err) {
|
||||
const fallbackDataDir = process.env.DATA_DIR || join(process.cwd(), "data");
|
||||
console.warn(
|
||||
`[CREDENTIALS] Could not load dataPaths module, using fallback: ${fallbackDataDir}`
|
||||
);
|
||||
return join(fallbackDataDir, "provider-credentials.json");
|
||||
}
|
||||
|
||||
return join(resolveDataDir(), "provider-credentials.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and merge external credentials into the PROVIDERS object.
|
||||
* Uses TTL-based caching (60s) so credential file changes are picked up
|
||||
* without requiring a server restart.
|
||||
*
|
||||
* @param {object} providers - The PROVIDERS object from constants.js
|
||||
* @returns {object} The same PROVIDERS object (mutated in place)
|
||||
*/
|
||||
export function loadProviderCredentials<T extends Record<string, unknown>>(providers: T): T {
|
||||
// Return cached result if within TTL
|
||||
if (cachedProviders && Date.now() - lastLoadTime < CONFIG_TTL_MS) {
|
||||
return cachedProviders as T;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { resolveDataDir } from "../../../src/lib/dataPaths";
|
||||
import { resolveDataDir } from "@/lib/dataPaths";
|
||||
|
||||
export interface AdaptationState {
|
||||
comboId: string;
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -11187,6 +11187,7 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSettings } from "@/lib/db/settings";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { getSkillsProviderSetting } from "@/lib/skills/providerSettings";
|
||||
|
||||
const POPULAR_BY_PROVIDER = {
|
||||
skillsmp: ["web-search", "file-reader", "sql-assistant", "devops-helper", "docs-assistant"],
|
||||
skillssh: ["git", "terminal", "postgres", "kubernetes", "playwright"],
|
||||
} as const;
|
||||
|
||||
export async function GET(request: Request) {
|
||||
if (!(await isAuthenticated(request))) {
|
||||
@@ -8,7 +14,21 @@ export async function GET(request: Request) {
|
||||
}
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const q = searchParams.get("q") || "";
|
||||
const q = searchParams.get("q")?.trim() || "";
|
||||
const provider = await getSkillsProviderSetting();
|
||||
|
||||
// Return popular skills when query is empty
|
||||
if (!q) {
|
||||
const popularList = POPULAR_BY_PROVIDER[provider];
|
||||
const skills = popularList.map((name) => ({
|
||||
name,
|
||||
description: `Popular skill: ${name}`,
|
||||
installCount: 0,
|
||||
}));
|
||||
return NextResponse.json({ skills });
|
||||
}
|
||||
|
||||
// Search SkillsMP for non-empty queries
|
||||
const settings = await getSettings();
|
||||
const apiKey = (settings as Record<string, unknown>).skillsmpApiKey;
|
||||
|
||||
|
||||
@@ -129,7 +129,17 @@ export function decrypt(ciphertext: string | null | undefined): string | null |
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
|
||||
decrypted += decipher.final("utf8");
|
||||
try {
|
||||
decrypted += decipher.final("utf8");
|
||||
} catch (finalErr: unknown) {
|
||||
const finalMessage = finalErr instanceof Error ? finalErr.message : String(finalErr);
|
||||
console.error(
|
||||
`[Encryption] Decryption final() failed: ${finalMessage}. ` +
|
||||
`Ciphertext prefix: ${ciphertext.slice(0, 30)}... ` +
|
||||
`Auth tag validation likely failed.`
|
||||
);
|
||||
return ciphertext;
|
||||
}
|
||||
return decrypted;
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
||||
34
tests/unit/db/encryption-error-handling.test.mjs
Normal file
34
tests/unit/db/encryption-error-handling.test.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { decrypt } from "../../../src/lib/db/encryption.ts";
|
||||
|
||||
test("decrypt() with invalid auth tag should not crash and return ciphertext", () => {
|
||||
const invalidCiphertext = "enc:v1:0000:0000:0000";
|
||||
const result = decrypt(invalidCiphertext);
|
||||
|
||||
assert.strictEqual(result, invalidCiphertext, "Should return ciphertext unchanged on error");
|
||||
assert.strictEqual(typeof result, "string", "Result should be a string");
|
||||
});
|
||||
|
||||
test("decrypt() with malformed ciphertext should not crash", () => {
|
||||
const malformed = "enc:v1:invalid";
|
||||
const result = decrypt(malformed);
|
||||
|
||||
assert.strictEqual(result, malformed, "Should return malformed ciphertext unchanged");
|
||||
});
|
||||
|
||||
test("decrypt() with null should return null", () => {
|
||||
const result = decrypt(null);
|
||||
assert.strictEqual(result, null, "Should return null for null input");
|
||||
});
|
||||
|
||||
test("decrypt() with undefined should return undefined", () => {
|
||||
const result = decrypt(undefined);
|
||||
assert.strictEqual(result, undefined, "Should return undefined for undefined input");
|
||||
});
|
||||
|
||||
test("decrypt() with non-encrypted string should return as-is", () => {
|
||||
const plaintext = "this-is-not-encrypted";
|
||||
const result = decrypt(plaintext);
|
||||
assert.strictEqual(result, plaintext, "Should return plaintext unchanged");
|
||||
});
|
||||
Reference in New Issue
Block a user