mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-03 22:33:19 +00:00
fix: Remove api key check in auth, ensure thrown in loadModel (#625)
* fix: Remove api key check in auth, ensure thrown in loadModel * cr * cr
This commit is contained in:
parent
7e9733a156
commit
7a23f5fdcf
3 changed files with 53 additions and 122 deletions
|
|
@ -5,7 +5,6 @@ import {
|
||||||
verifyGithubUserId,
|
verifyGithubUserId,
|
||||||
} from "@open-swe/shared/github/verify-user";
|
} from "@open-swe/shared/github/verify-user";
|
||||||
import {
|
import {
|
||||||
API_KEY_REQUIRED_MESSAGE,
|
|
||||||
GITHUB_INSTALLATION_ID,
|
GITHUB_INSTALLATION_ID,
|
||||||
GITHUB_INSTALLATION_NAME,
|
GITHUB_INSTALLATION_NAME,
|
||||||
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
GITHUB_INSTALLATION_TOKEN_COOKIE,
|
||||||
|
|
@ -18,11 +17,6 @@ import { verifyGitHubWebhookOrThrow } from "./github.js";
|
||||||
import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js";
|
import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js";
|
||||||
import { LANGGRAPH_USER_PERMISSIONS } from "../constants.js";
|
import { LANGGRAPH_USER_PERMISSIONS } from "../constants.js";
|
||||||
import { getGitHubPatFromRequest } from "../utils/github-pat.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
|
// TODO: Export from LangGraph SDK
|
||||||
export interface BaseAuthReturn {
|
export interface BaseAuthReturn {
|
||||||
|
|
@ -38,64 +32,6 @@ interface AuthenticateReturn extends BaseAuthReturn {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiKeysInRequestBody(
|
|
||||||
bodyStr: string | Record<string, unknown>,
|
|
||||||
): 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()
|
export const auth = new Auth()
|
||||||
.authenticate<AuthenticateReturn>(async (request: Request) => {
|
.authenticate<AuthenticateReturn>(async (request: Request) => {
|
||||||
const isProd = process.env.NODE_ENV === "production";
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
|
|
@ -208,26 +144,6 @@ export const auth = new Auth()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const reqCopy = request.clone();
|
|
||||||
let reqBody: string | Record<string, unknown>;
|
|
||||||
try {
|
|
||||||
reqBody = (await reqCopy.json()) as Record<string, unknown>;
|
|
||||||
} 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 {
|
return {
|
||||||
identity: user.id.toString(),
|
identity: user.id.toString(),
|
||||||
is_authenticated: true,
|
is_authenticated: true,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Task } from "./constants.js";
|
||||||
import { isAllowedUser } from "@open-swe/shared/github/allowed-users";
|
import { isAllowedUser } from "@open-swe/shared/github/allowed-users";
|
||||||
import { decryptSecret } from "@open-swe/shared/crypto";
|
import { decryptSecret } from "@open-swe/shared/crypto";
|
||||||
import { TASK_TO_CONFIG_DEFAULTS_MAP } from "./constants.js";
|
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");
|
const logger = createLogger(LogLevel.INFO, "ModelManager");
|
||||||
|
|
||||||
|
|
@ -105,12 +106,55 @@ export class ModelManager {
|
||||||
const model = await this.initializeModel(baseConfig, graphConfig);
|
const model = await this.initializeModel(baseConfig, graphConfig);
|
||||||
return model;
|
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
|
* Initialize the model instance
|
||||||
*/
|
*/
|
||||||
public async initializeModel(
|
public async initializeModel(
|
||||||
config: ModelLoadConfig,
|
config: ModelLoadConfig,
|
||||||
graphConfig?: GraphConfig,
|
graphConfig: GraphConfig,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
provider,
|
provider,
|
||||||
|
|
@ -130,42 +174,7 @@ export class ModelManager {
|
||||||
finalMaxTokens = finalMaxTokens > 8_192 ? 8_192 : finalMaxTokens;
|
finalMaxTokens = finalMaxTokens > 8_192 ? 8_192 : finalMaxTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
let apiKey: string | null = null;
|
const apiKey = this.getUserApiKey(graphConfig, provider);
|
||||||
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 modelOptions: InitChatModelArgs = {
|
const modelOptions: InitChatModelArgs = {
|
||||||
modelProvider: provider,
|
modelProvider: provider,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { ChatResult, ChatGeneration } from "@langchain/core/outputs";
|
||||||
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
|
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
|
||||||
import { BindToolsInput } from "@langchain/core/language_models/chat_models";
|
import { BindToolsInput } from "@langchain/core/language_models/chat_models";
|
||||||
import { getMessageContentString } from "@open-swe/shared/messages";
|
import { getMessageContentString } from "@open-swe/shared/messages";
|
||||||
|
import { getConfig } from "@langchain/langgraph";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.DEBUG, "FallbackRunnable");
|
const logger = createLogger(LogLevel.DEBUG, "FallbackRunnable");
|
||||||
|
|
||||||
|
|
@ -108,8 +109,13 @@ export class FallbackRunnable<
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const graphConfig = getConfig() as GraphConfig;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const model = await this.modelManager.initializeModel(modelConfig);
|
const model = await this.modelManager.initializeModel(
|
||||||
|
modelConfig,
|
||||||
|
graphConfig,
|
||||||
|
);
|
||||||
let runnableToUse: Runnable<BaseLanguageModelInput, AIMessageChunk> =
|
let runnableToUse: Runnable<BaseLanguageModelInput, AIMessageChunk> =
|
||||||
model;
|
model;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue