Skip to content

Feat/resilience UI i18n model cooldown - [deferred to v4.0.0-rc1]#2145

Open
rafacpti23 wants to merge 28 commits into
diegosouzapw:release/v3.8.0from
rafacpti23:feat/resilience-ui-i18n-model-cooldown
Open

Feat/resilience UI i18n model cooldown - [deferred to v4.0.0-rc1]#2145
rafacpti23 wants to merge 28 commits into
diegosouzapw:release/v3.8.0from
rafacpti23:feat/resilience-ui-i18n-model-cooldown

Conversation

@rafacpti23
Copy link
Copy Markdown
Contributor

@rafacpti23 rafacpti23 commented May 11, 2026

Summary

  • Describe the user-facing or operational change.

Related Issues

  • Closes #
  • Related to #

Validation

  • npm run lint
  • npm run test:unit
  • npm run test:coverage
  • Coverage is still >= 60% for statements, lines, functions, and branches
  • SonarQube PR analysis is green or any remaining issues are explicitly documented below

Tests Added Or Updated

  • List every changed or added automated test file.
  • If no production code changed, state that here.

Coverage Notes

  • If this PR changes src/, open-sse/, electron/, or bin/, explain which tests cover the change.
  • If coverage moved down in any touched file, explain why and what follow-up task will recover it.

Reviewer Notes

  • Call out any risky areas, migrations, feature flags, or manual validation that reviewers should know about.

Deferred to v4.0.0-rc1 — This PR will be integrated in the v4.0.0 release cycle due to its scope and architectural impact.

rafacpti23 and others added 28 commits April 20, 2026 15:20
@rafacpti23 rafacpti23 requested a review from diegosouzapw as a code owner May 11, 2026 04:10
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a major expansion of the platform under the 'Easy IA' branding, adding a customer-facing landing site and portal, a SaaS management dashboard for plans and customers, and Qdrant integration for semantic memory. Feedback highlights critical security risks including insecure JWT secret fallbacks, hardcoded secrets, and missing API prefixes in the middleware matcher that could lead to broken access control. Further improvements are required for internationalization of error messages, cookie security, and replacing synchronous hashing with asynchronous methods to prevent event loop blocking.

Comment thread src/proxy.ts
Comment on lines +15 to +17
function getJwtSecret(): Uint8Array {
return new TextEncoder().encode(process.env.JWT_SECRET || "");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Using an empty string as a fallback for JWT_SECRET is a critical security risk. If the environment variable is missing, the application will use a predictable secret, allowing anyone to forge valid management tokens. The application should throw an error if the secret is not configured.

function getJwtSecret(): Uint8Array {
  const secret = process.env.JWT_SECRET;
  if (!secret) throw new Error("JWT_SECRET is not configured");
  return new TextEncoder().encode(secret);
}

Comment thread src/proxy.ts
"/codex",
"/models",
],
matcher: ["/", "/adm/:path*", "/dashboard/:path*", "/api/:path*"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The new middleware matcher is missing several critical API prefixes that were present in the previous version, such as /v1, /chat, /responses, /codex, and /models. Since these routes are often used as entry points (and then rewritten to /api/v1/...), excluding them from the matcher will cause requests to these endpoints to bypass middleware logic, including request ID generation, graceful shutdown checks, and body size limits.

Suggested change
matcher: ["/", "/adm/:path*", "/dashboard/:path*", "/api/:path*"],
matcher: ["/", "/adm/:path*", "/dashboard/:path*", "/api/:path*", "/v1/:path*", "/v1", "/chat/:path*", "/responses/:path*", "/responses", "/codex/:path*", "/codex", "/models"],

const store = await cookies();
store.set(getPortalCookieName(), token, {
httpOnly: true,
sameSite: "lax",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The session cookie is configured with secure: false. This allows the cookie to be transmitted over unencrypted HTTP connections, making it vulnerable to interception. It should be set to true in production environments.

Suggested change
sameSite: "lax",
secure: process.env.NODE_ENV === "production",

Comment thread site/lib/portalSession.ts

function getSecret() {
return new TextEncoder().encode(
process.env.SITE_PORTAL_SECRET || "easyia-local-portal-secret-change-me"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Hardcoding a fallback secret for JWT signing (easyia-local-portal-secret-change-me) is insecure. If the SITE_PORTAL_SECRET environment variable is missing, the application will use this known string, allowing attackers to forge portal session tokens. The application should require a secret from the environment.

Comment thread src/proxy.ts
return modelSyncModulePromise;
}

export async function proxy(request: any) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request parameter is typed as any. Using a specific type like NextRequest from next/server would provide better type safety and IDE support.

Suggested change
export async function proxy(request: any) {
export async function proxy(request: import("next/server").NextRequest) {

Comment thread open-sse/utils/error.ts
Comment on lines +87 to +135
return "Nao foi possivel validar os beneficios da assinatura desta conta agora. Confirme se a assinatura do provedor esta ativa e tente novamente em alguns instantes.";
}

if (
normalized.includes("chatgpt account") &&
normalized.includes("model is not supported") &&
normalized.includes("codex")
) {
return "Este modelo nao esta liberado para esta conta do Codex. Escolha outro modelo compativel ou deixe o fallback do OmniRoute seguir para a proxima opcao.";
}

if (normalized.includes("requested model is not supported")) {
return "O modelo solicitado nao esta disponivel neste provedor ou nesta conta. Escolha outro modelo ou use o fallback automatico para continuar.";
}

if (normalized.includes("customer api key is disabled")) {
return "Esta API key esta temporariamente indisponivel. Verifique no painel se o plano esta ativo, se a renovacao foi concluida ou se a chave foi bloqueada pelo administrador.";
}

if (normalized.includes("invalid json response from provider")) {
return "O provedor retornou uma resposta invalida nesta tentativa. Tente novamente em alguns segundos ou deixe o fallback usar outro provedor/modelo.";
}

if (normalized.includes("invalid api key")) {
return "A API key informada nao foi aceita. Revise a chave configurada e tente novamente.";
}

if (statusCode === 401) {
return "Nao foi possivel autenticar esta chamada no provedor configurado. Revise as credenciais e tente novamente.";
}

if (statusCode === 402) {
return "Esta chamada nao pode ser concluida agora porque a conta do provedor precisa de assinatura, creditos ou validacao de beneficios. Verifique o status da conta e tente novamente.";
}

if (statusCode === 403) {
return "Esta chamada foi recusada pelo provedor. Verifique se esta conta tem permissao para usar este recurso ou modelo.";
}

if (statusCode === 404) {
return "O recurso solicitado nao foi encontrado neste provedor. Confira o modelo, rota ou configuracao usada nesta chamada.";
}

if (statusCode === 429) {
return "O limite de uso desta conta foi atingido no momento. Aguarde alguns instantes ou deixe o fallback tentar outra conexao.";
}

if (statusCode && statusCode >= 500) {
return "O provedor retornou uma falha temporaria. Tente novamente em instantes ou deixe o fallback seguir para a proxima opcao.";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The normalizeUserFacingErrorMessage function uses hardcoded Portuguese strings for error messages. As the project supports multiple languages and uses an i18n system (next-intl), these messages should be externalized to the locale JSON files to ensure that users see errors in their preferred language.

paidUntil: data.paidUntil || null,
extraTokenCredits: data.extraTokenCredits || 0,
planId: data.planId || null,
passwordHash: data.password ? bcrypt.hashSync(data.password, 10) : null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using bcrypt.hashSync in an asynchronous route handler blocks the event loop, which can lead to performance bottlenecks. It is better to use the asynchronous bcrypt.hash method.

Comment on lines +69 to +71
passwordHash: validation.data.password
? bcrypt.hashSync(validation.data.password, 10)
: undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using bcrypt.hashSync in an asynchronous route handler blocks the event loop. It is recommended to use the asynchronous bcrypt.hash method instead.

@diegosouzapw diegosouzapw changed the base branch from main to release/v3.8.0 May 11, 2026 12:58
@diegosouzapw
Copy link
Copy Markdown
Owner

Hey @rafacpti23, thanks for this contribution! 🚀

We really appreciate the work you've put into these features — the SaaS module, Qdrant integration, model cooldowns, and resilience improvements are all excellent additions.

However, given the scope of the changes (~28K+ lines, 100 files), this PR is better suited for the next major release cycle. We don't want to rush such a large feature set into v3.8.0 where it could introduce regressions.

Plan: This PR will be deferred to v4.0.0-rc1, where we'll have a dedicated integration window for these features. We'll keep this PR open and track it for the v4.0.0 milestone.

In the meantime, the model cooldown feature (PR #2146) has been extracted as a focused PR and will be integrated into v3.8.0 separately.

Thanks again for the amazing work! 🙌

@diegosouzapw diegosouzapw changed the title Feat/resilience UI i18n model cooldown Feat/resilience UI i18n model cooldown - [deferred to v4.0.0-rc1] May 11, 2026
@diegosouzapw
Copy link
Copy Markdown
Owner

Thank you for this contribution! After review, this PR has been deferred to the v4.0.0-rc1 milestone as it introduces architectural changes that are better suited for the next major release. We'll revisit it there. We appreciate your work and will keep this open.

@diegosouzapw
Copy link
Copy Markdown
Owner

Thank you @rafacpti23! Deferred to v4.0.0-rc1 alongside #2143 and #2144 for the full resilience UI + i18n integration. Will be merged with full credit when that cycle opens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants