Files
OmniRoute/src/lib/oauth/connectionPersistence.ts
Diego Rodrigues de Sa e Souza 78f09c8d9f Release v3.8.41 (#5327)
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors).

All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke.

Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
2026-06-29 16:51:03 -03:00

115 lines
4.0 KiB
TypeScript

/**
* Shared upsert for OAuth provider connections, used by both the authenticated
* OAuth route (`device-complete`) and the public Codex device-flow completion
* endpoint. Mirrors the exchange/poll/poll-callback persistence: normalize the
* display name, compute expiry, match an existing connection by id or email
* (+ Codex workspaceId) and update it, else create a new one, then sync to Cloud.
*/
import { timingSafeEqual } from "crypto";
import {
createProviderConnection,
updateProviderConnection,
getProviderConnections,
isCloudEnabled,
} from "@/models";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/lib/cloudSync";
/**
* Constant-time string comparison to prevent timing-oracle attacks (CWE-208).
* Handles null/undefined safely and different-length strings.
*/
function safeEqual(a: string | null | undefined, b: string | null | undefined): boolean {
if (a == null || b == null) return a === b;
const ba = Buffer.from(String(a));
const bb = Buffer.from(String(b));
if (ba.length !== bb.length) return false;
return timingSafeEqual(ba, bb);
}
/**
* Build the create payload for a brand-new OAuth connection.
*
* #5326: mirror the freshly computed `expiresAt` into `tokenExpiresAt` at creation
* time. The dashboard token-health badge prefers `tokenExpiresAt` over `expiresAt`
* (ConnectionRow.tsx: `connection.tokenExpiresAt || connection.expiresAt`). If
* `tokenExpiresAt` stays null on a freshly created connection, the badge falls back
* to the original grant clock and can flash a false amber/"Token Expired" until the
* first background refresh writes both fields together. All refresh paths already
* persist `expiresAt` and `tokenExpiresAt` in lockstep
* (tokenHealthCheck onPersist, tokenRefresh.updateProviderCredentials); this makes
* creation consistent with them.
*/
export function buildOAuthConnectionCreatePayload(
provider: string,
tokenData: Record<string, any>,
expiresAt: string | null
) {
return {
provider,
authType: "oauth" as const,
...tokenData,
expiresAt,
tokenExpiresAt: expiresAt,
testStatus: "active" as const,
};
}
async function syncToCloudIfEnabled(): Promise<void> {
try {
const cloudEnabled = await isCloudEnabled();
if (!cloudEnabled) return;
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
} catch (error) {
console.log("Error syncing to cloud after OAuth:", error);
}
}
export async function persistOAuthConnection(
provider: string,
tokenData: any,
connectionId?: string
) {
// Normalize: if name is missing, use email or displayName as fallback label.
if (!tokenData.name && (tokenData.email || tokenData.displayName)) {
tokenData.name = tokenData.email || tokenData.displayName;
}
const expiresAt = tokenData.expiresIn
? new Date(Date.now() + tokenData.expiresIn * 1000).toISOString()
: null;
let connection: any;
if (tokenData.email) {
const existing = await getProviderConnections({ provider });
const match = existing.find((c: any) => {
if (c.id && safeEqual(connectionId, c.id)) return true;
if (!safeEqual(c.email, tokenData.email) || c.authType !== "oauth") return false;
// For Codex, also check workspaceId to avoid overwriting a different workspace.
if (provider === "codex" && tokenData.providerSpecificData?.workspaceId) {
const existingWorkspace = c.providerSpecificData?.workspaceId;
return safeEqual(existingWorkspace, tokenData.providerSpecificData.workspaceId);
}
return true;
});
const matchId = typeof match?.id === "string" ? match.id : null;
if (matchId) {
connection = await updateProviderConnection(matchId, {
...tokenData,
expiresAt,
testStatus: "active",
isActive: true,
});
}
}
if (!connection) {
connection = await createProviderConnection(
buildOAuthConnectionCreatePayload(provider, tokenData, expiresAt)
);
}
await syncToCloudIfEnabled();
return connection;
}