diff --git a/apps/open-swe/src/security/auth.ts b/apps/open-swe/src/security/auth.ts index 73d452bd..5974edd2 100644 --- a/apps/open-swe/src/security/auth.ts +++ b/apps/open-swe/src/security/auth.ts @@ -5,7 +5,6 @@ import { verifyGithubUserId, } from "@open-swe/shared/github/verify-user"; import { - API_KEY_REQUIRED_MESSAGE, GITHUB_INSTALLATION_ID, GITHUB_INSTALLATION_NAME, GITHUB_INSTALLATION_TOKEN_COOKIE, @@ -18,11 +17,6 @@ import { verifyGitHubWebhookOrThrow } from "./github.js"; import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js"; import { LANGGRAPH_USER_PERMISSIONS } from "../constants.js"; import { getGitHubPatFromRequest } from "../utils/github-pat.js"; -import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; -import { validate } from "uuid"; -import { createLogger, LogLevel } from "../utils/logger.js"; - -const logger = createLogger(LogLevel.INFO, "Auth"); // TODO: Export from LangGraph SDK export interface BaseAuthReturn { @@ -38,64 +32,6 @@ interface AuthenticateReturn extends BaseAuthReturn { }; } -function apiKeysInRequestBody( - bodyStr: string | Record, -): boolean { - logger.info("CHECKING RUN REQ BODY!!", bodyStr); - try { - const body = typeof bodyStr === "string" ? JSON.parse(bodyStr) : bodyStr; - if ( - body.config?.configurable && - ("anthropicApiKey" in body.config.configurable.apiKeys || - "openaiApiKey" in body.config.configurable.apiKeys || - "googleApiKey" in body.config.configurable.apiKeys) - ) { - logger.info("RUN REQ BODY CONTAINS API KEYS!!", bodyStr); - return true; - } - logger.info("RUN REQ BODY DOES NOT CONTAIN API KEYS!!", bodyStr); - return false; - } catch (e: any) { - logger.error("RUN REQ BODY DOES NOT CONTAIN API KEYS!!", { - bodyStr, - error: e, - }); - return false; - } -} - -function isRunReq(reqUrl: string): boolean { - try { - const url = new URL(reqUrl); - const pathnameParts = url.pathname.split("/"); - const isCreateAndWait = !!( - pathnameParts[1] === "threads" && - validate(pathnameParts[2]) && - pathnameParts[3] === "runs" && - pathnameParts[4] === "wait" && - pathnameParts.length === 5 - ); - const isCreateBackground = !!( - pathnameParts[1] === "threads" && - validate(pathnameParts[2]) && - pathnameParts[3] === "runs" && - pathnameParts.length === 4 - ); - const isCreateStream = !!( - pathnameParts[1] === "threads" && - validate(pathnameParts[2]) && - pathnameParts[3] === "runs" && - pathnameParts[4] === "stream" && - pathnameParts.length === 5 - ); - - return !!isCreateAndWait || !!isCreateBackground || !!isCreateStream; - } catch { - // no-op - return false; - } -} - export const auth = new Auth() .authenticate(async (request: Request) => { const isProd = process.env.NODE_ENV === "production"; @@ -208,26 +144,6 @@ export const auth = new Auth() }); } - const reqCopy = request.clone(); - let reqBody: string | Record; - try { - reqBody = (await reqCopy.json()) as Record; - } catch { - reqBody = await reqCopy.text(); - } - if (!isAllowedUser(user.login)) { - if (isRunReq(request.url)) { - if (!apiKeysInRequestBody(reqBody)) { - logger.warn("No API keys found in run request body", { - url: request.url, - }); - throw new HTTPException(401, { - message: API_KEY_REQUIRED_MESSAGE, - }); - } - } - } - return { identity: user.id.toString(), is_authenticated: true, diff --git a/apps/open-swe/src/utils/llms/model-manager.ts b/apps/open-swe/src/utils/llms/model-manager.ts index d0e8dcfb..3a86c635 100644 --- a/apps/open-swe/src/utils/llms/model-manager.ts +++ b/apps/open-swe/src/utils/llms/model-manager.ts @@ -8,6 +8,7 @@ import { Task } from "./constants.js"; import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; import { decryptSecret } from "@open-swe/shared/crypto"; import { TASK_TO_CONFIG_DEFAULTS_MAP } from "./constants.js"; +import { API_KEY_REQUIRED_MESSAGE } from "@open-swe/shared/constants"; const logger = createLogger(LogLevel.INFO, "ModelManager"); @@ -105,12 +106,55 @@ export class ModelManager { const model = await this.initializeModel(baseConfig, graphConfig); return model; } + + private getUserApiKey( + graphConfig: GraphConfig, + provider: Provider, + ): string | null { + const userLogin = (graphConfig.configurable as any)?.langgraph_auth_user + ?.display_name; + const secretsEncryptionKey = process.env.SECRETS_ENCRYPTION_KEY; + + if (!secretsEncryptionKey) { + throw new Error( + "SECRETS_ENCRYPTION_KEY environment variable is required", + ); + } + if (!userLogin) { + throw new Error("User login not found in config"); + } + + // If the user is allowed, we can return early + if (isAllowedUser(userLogin)) { + return null; + } + + const apiKeys = graphConfig.configurable?.apiKeys; + if (!apiKeys) { + throw new Error(API_KEY_REQUIRED_MESSAGE); + } + + const missingProviderKeyMessage = `No API key found for provider: ${provider}. Please add one in the settings page.`; + + const providerApiKey = providerToApiKey(provider, apiKeys); + if (!providerApiKey) { + throw new Error(missingProviderKeyMessage); + } + + const apiKey = decryptSecret(providerApiKey, secretsEncryptionKey); + if (!apiKey) { + throw new Error(missingProviderKeyMessage); + } + + return apiKey; + } + /** * Initialize the model instance */ public async initializeModel( config: ModelLoadConfig, - graphConfig?: GraphConfig, + graphConfig: GraphConfig, ) { const { provider, @@ -130,42 +174,7 @@ export class ModelManager { finalMaxTokens = finalMaxTokens > 8_192 ? 8_192 : finalMaxTokens; } - let apiKey: string | null = null; - if (graphConfig) { - const userLogin = (graphConfig.configurable as any)?.langgraph_auth_user - ?.display_name; - const secretsEncryptionKey = process.env.SECRETS_ENCRYPTION_KEY; - if (!secretsEncryptionKey) { - throw new Error( - "SECRETS_ENCRYPTION_KEY environment variable is required", - ); - } - if (!userLogin) { - throw new Error("User login not found in config"); - } - const apiKeys = graphConfig.configurable?.apiKeys; - if (!isAllowedUser(userLogin)) { - if (!apiKeys) { - throw new Error("API keys not found in config"); - } - const providerApiKey = providerToApiKey(provider, apiKeys); - if (!providerApiKey) { - throw new Error( - "No API key found for provider: " + - provider + - ". Please add one in the settings page.", - ); - } - apiKey = decryptSecret(providerApiKey, secretsEncryptionKey); - if (!apiKey) { - throw new Error( - "No API key found for provider: " + - provider + - ". Please add one in the settings page.", - ); - } - } - } + const apiKey = this.getUserApiKey(graphConfig, provider); const modelOptions: InitChatModelArgs = { modelProvider: provider, diff --git a/apps/open-swe/src/utils/runtime-fallback.ts b/apps/open-swe/src/utils/runtime-fallback.ts index 5f5a6b21..3dc4fbff 100644 --- a/apps/open-swe/src/utils/runtime-fallback.ts +++ b/apps/open-swe/src/utils/runtime-fallback.ts @@ -17,6 +17,7 @@ import { ChatResult, ChatGeneration } from "@langchain/core/outputs"; import { BaseLanguageModelInput } from "@langchain/core/language_models/base"; import { BindToolsInput } from "@langchain/core/language_models/chat_models"; import { getMessageContentString } from "@open-swe/shared/messages"; +import { getConfig } from "@langchain/langgraph"; const logger = createLogger(LogLevel.DEBUG, "FallbackRunnable"); @@ -108,8 +109,13 @@ export class FallbackRunnable< continue; } + const graphConfig = getConfig() as GraphConfig; + try { - const model = await this.modelManager.initializeModel(modelConfig); + const model = await this.modelManager.initializeModel( + modelConfig, + graphConfig, + ); let runnableToUse: Runnable = model;