Spaces:
Runtime error
Runtime error
File size: 3,090 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | /**
* 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);
}
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({
provider,
authType: "oauth",
...tokenData,
expiresAt,
testStatus: "active",
});
}
await syncToCloudIfEnabled();
return connection;
}
|