优化和重构代码,增加前端可以设置加密配置数据的密钥
This commit is contained in:
parent
bd68df1d9b
commit
b0c1ccd0a0
|
@ -70,8 +70,9 @@ WHITE_WEBDAV_ENDPOINTS=
|
||||||
|
|
||||||
### bedrock (optional)
|
### bedrock (optional)
|
||||||
AWS_REGION=
|
AWS_REGION=
|
||||||
AWS_ACCESS_KEY=
|
AWS_ACCESS_KEY=AKIA
|
||||||
AWS_SECRET_KEY=
|
AWS_SECRET_KEY=
|
||||||
|
### Assign this with a secure, randomly generated key;
|
||||||
### Assign this with a secure, randomly generated key
|
### Generate a secure, random key that is at least 32 characters long. You can use a password generator or a command like this:
|
||||||
|
### openssl rand -base64 32
|
||||||
ENCRYPTION_KEY=
|
ENCRYPTION_KEY=
|
|
@ -52,29 +52,6 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
|
||||||
msg: "you are not allowed to access with your own api key",
|
msg: "you are not allowed to access with your own api key",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Special handling for Bedrock
|
|
||||||
if (modelProvider === ModelProvider.Bedrock) {
|
|
||||||
const region = serverConfig.awsRegion;
|
|
||||||
const accessKeyId = serverConfig.awsAccessKey;
|
|
||||||
const secretAccessKey = serverConfig.awsSecretKey;
|
|
||||||
|
|
||||||
console.log("[Auth] Bedrock credentials:", {
|
|
||||||
region,
|
|
||||||
accessKeyId: accessKeyId ? "***" : undefined,
|
|
||||||
secretKey: secretAccessKey ? "***" : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if AWS credentials are provided
|
|
||||||
if (!region || !accessKeyId || !secretAccessKey) {
|
|
||||||
return {
|
|
||||||
error: true,
|
|
||||||
msg: "Missing AWS credentials. Please configure Region, Access Key ID, and Secret Access Key in settings.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { error: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user does not provide an api key, inject system api key
|
// if user does not provide an api key, inject system api key
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
@ -120,6 +97,14 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
|
||||||
case ModelProvider.ChatGLM:
|
case ModelProvider.ChatGLM:
|
||||||
systemApiKey = serverConfig.chatglmApiKey;
|
systemApiKey = serverConfig.chatglmApiKey;
|
||||||
break;
|
break;
|
||||||
|
case ModelProvider.Bedrock:
|
||||||
|
systemApiKey =
|
||||||
|
serverConfig.awsRegion +
|
||||||
|
":" +
|
||||||
|
serverConfig.awsAccessKey +
|
||||||
|
":" +
|
||||||
|
serverConfig.awsSecretKey;
|
||||||
|
break;
|
||||||
case ModelProvider.GPT:
|
case ModelProvider.GPT:
|
||||||
default:
|
default:
|
||||||
if (req.nextUrl.pathname.includes("azure/deployments")) {
|
if (req.nextUrl.pathname.includes("azure/deployments")) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { sign, decrypt } from "../utils/aws";
|
import { sign } from "../utils/aws";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
|
||||||
const ALLOWED_PATH = new Set(["chat", "models"]);
|
const ALLOWED_PATH = new Set(["chat", "models"]);
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ function parseEventData(chunk: Uint8Array): any {
|
||||||
}
|
}
|
||||||
return parsed.body || parsed;
|
return parsed.body || parsed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error parsing event data:", e);
|
// console.error("Error parsing event data:", e);
|
||||||
try {
|
try {
|
||||||
// Handle base64 encoded responses
|
// Handle base64 encoded responses
|
||||||
const base64Match = text.match(/:"([A-Za-z0-9+/=]+)"/);
|
const base64Match = text.match(/:"([A-Za-z0-9+/=]+)"/);
|
||||||
|
@ -76,7 +77,7 @@ async function* transformBedrockStream(
|
||||||
const parsed = parseEventData(value);
|
const parsed = parseEventData(value);
|
||||||
if (!parsed) continue;
|
if (!parsed) continue;
|
||||||
|
|
||||||
console.log("Parsed response:", JSON.stringify(parsed, null, 2));
|
// console.log("Parsed response:", JSON.stringify(parsed, null, 2));
|
||||||
|
|
||||||
// Handle Titan models
|
// Handle Titan models
|
||||||
if (modelId.startsWith("amazon.titan")) {
|
if (modelId.startsWith("amazon.titan")) {
|
||||||
|
@ -182,26 +183,38 @@ function validateRequest(body: any, modelId: string): void {
|
||||||
|
|
||||||
async function requestBedrock(req: NextRequest) {
|
async function requestBedrock(req: NextRequest) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const awsRegion = req.headers.get("X-Region") ?? "";
|
|
||||||
const awsAccessKey = req.headers.get("X-Access-Key") ?? "";
|
// Get AWS credentials from server config first
|
||||||
const awsSecretKey = req.headers.get("X-Secret-Key") ?? "";
|
const config = getServerSideConfig();
|
||||||
const awsSessionToken = req.headers.get("X-Session-Token");
|
let awsRegion = config.awsRegion;
|
||||||
const modelId = req.headers.get("X-Model-Id") ?? "";
|
let awsAccessKey = config.awsAccessKey;
|
||||||
|
let awsSecretKey = config.awsSecretKey;
|
||||||
|
let modelId = "";
|
||||||
|
|
||||||
|
// If server-side credentials are not available, parse from Authorization header
|
||||||
|
if (!awsRegion || !awsAccessKey || !awsSecretKey) {
|
||||||
|
const authHeader = req.headers.get("Authorization");
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||||
|
throw new Error("Missing or invalid Authorization header");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [_, credentials] = authHeader.split("Bearer ");
|
||||||
|
const [region, accessKey, secretKey, model] = credentials.split(",");
|
||||||
|
|
||||||
|
if (!region || !accessKey || !secretKey || !model) {
|
||||||
|
throw new Error("Invalid Authorization header format");
|
||||||
|
}
|
||||||
|
|
||||||
|
awsRegion = region;
|
||||||
|
awsAccessKey = accessKey;
|
||||||
|
awsSecretKey = secretKey;
|
||||||
|
modelId = model;
|
||||||
|
}
|
||||||
|
|
||||||
if (!awsRegion || !awsAccessKey || !awsSecretKey || !modelId) {
|
if (!awsRegion || !awsAccessKey || !awsSecretKey || !modelId) {
|
||||||
throw new Error("Missing required AWS credentials or model ID");
|
throw new Error("Missing required AWS credentials or model ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedAccessKey = decrypt(awsAccessKey);
|
|
||||||
const decryptedSecretKey = decrypt(awsSecretKey);
|
|
||||||
const decryptedSessionToken = awsSessionToken
|
|
||||||
? decrypt(awsSessionToken)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (!decryptedAccessKey || !decryptedSecretKey) {
|
|
||||||
throw new Error("Failed to decrypt AWS credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the base endpoint
|
// Construct the base endpoint
|
||||||
const baseEndpoint = `https://bedrock-runtime.${awsRegion}.amazonaws.com`;
|
const baseEndpoint = `https://bedrock-runtime.${awsRegion}.amazonaws.com`;
|
||||||
|
|
||||||
|
@ -236,9 +249,8 @@ async function requestBedrock(req: NextRequest) {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
region: awsRegion,
|
region: awsRegion,
|
||||||
accessKeyId: decryptedAccessKey,
|
accessKeyId: awsAccessKey,
|
||||||
secretAccessKey: decryptedSecretKey,
|
secretAccessKey: awsSecretKey,
|
||||||
sessionToken: decryptedSessionToken,
|
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
service: "bedrock",
|
service: "bedrock",
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { SparkApi } from "./platforms/iflytek";
|
||||||
import { XAIApi } from "./platforms/xai";
|
import { XAIApi } from "./platforms/xai";
|
||||||
import { ChatGLMApi } from "./platforms/glm";
|
import { ChatGLMApi } from "./platforms/glm";
|
||||||
import { BedrockApi } from "./platforms/bedrock";
|
import { BedrockApi } from "./platforms/bedrock";
|
||||||
import { encrypt } from "../utils/aws";
|
|
||||||
|
|
||||||
export const ROLES = ["system", "user", "assistant"] as const;
|
export const ROLES = ["system", "user", "assistant"] as const;
|
||||||
export type MessageRole = (typeof ROLES)[number];
|
export type MessageRole = (typeof ROLES)[number];
|
||||||
|
@ -258,8 +257,6 @@ export function getHeaders(ignoreHeaders: boolean = false) {
|
||||||
const isEnabledAccessControl = accessStore.enabledAccessControl();
|
const isEnabledAccessControl = accessStore.enabledAccessControl();
|
||||||
const apiKey = isGoogle
|
const apiKey = isGoogle
|
||||||
? accessStore.googleApiKey
|
? accessStore.googleApiKey
|
||||||
: isBedrock
|
|
||||||
? accessStore.awsAccessKey // Use AWS access key for Bedrock
|
|
||||||
: isAzure
|
: isAzure
|
||||||
? accessStore.azureApiKey
|
? accessStore.azureApiKey
|
||||||
: isAnthropic
|
: isAnthropic
|
||||||
|
@ -278,6 +275,18 @@ export function getHeaders(ignoreHeaders: boolean = false) {
|
||||||
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
|
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
|
||||||
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
|
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
|
||||||
: ""
|
: ""
|
||||||
|
: isBedrock
|
||||||
|
? accessStore.awsRegion &&
|
||||||
|
accessStore.awsAccessKey &&
|
||||||
|
accessStore.awsSecretKey
|
||||||
|
? accessStore.awsRegion +
|
||||||
|
"," +
|
||||||
|
accessStore.awsAccessKey +
|
||||||
|
"," +
|
||||||
|
accessStore.awsSecretKey +
|
||||||
|
"," +
|
||||||
|
modelConfig.model
|
||||||
|
: ""
|
||||||
: accessStore.openaiApiKey;
|
: accessStore.openaiApiKey;
|
||||||
return {
|
return {
|
||||||
isBedrock,
|
isBedrock,
|
||||||
|
@ -303,13 +312,10 @@ export function getHeaders(ignoreHeaders: boolean = false) {
|
||||||
? "x-api-key"
|
? "x-api-key"
|
||||||
: isGoogle
|
: isGoogle
|
||||||
? "x-goog-api-key"
|
? "x-goog-api-key"
|
||||||
: isBedrock
|
|
||||||
? "x-api-key"
|
|
||||||
: "Authorization";
|
: "Authorization";
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isBedrock,
|
|
||||||
isGoogle,
|
isGoogle,
|
||||||
isAzure,
|
isAzure,
|
||||||
isAnthropic,
|
isAnthropic,
|
||||||
|
@ -322,28 +328,23 @@ export function getHeaders(ignoreHeaders: boolean = false) {
|
||||||
|
|
||||||
const authHeader = getAuthHeader();
|
const authHeader = getAuthHeader();
|
||||||
|
|
||||||
if (isBedrock) {
|
// if (isBedrock) {
|
||||||
// Secure encryption of AWS credentials using the new encryption utility
|
// // Secure encryption of AWS credentials using the new encryption utility
|
||||||
headers["X-Region"] = encrypt(accessStore.awsRegion);
|
// headers["X-Region"] = encrypt(accessStore.awsRegion);
|
||||||
headers["X-Access-Key"] = encrypt(accessStore.awsAccessKey);
|
// headers["X-Access-Key"] = encrypt(accessStore.awsAccessKey);
|
||||||
headers["X-Secret-Key"] = encrypt(accessStore.awsSecretKey);
|
// headers["X-Secret-Key"] = encrypt(accessStore.awsSecretKey);
|
||||||
|
// } else {
|
||||||
|
const bearerToken = getBearerToken(
|
||||||
|
apiKey,
|
||||||
|
isAzure || isAnthropic || isGoogle,
|
||||||
|
);
|
||||||
|
|
||||||
if (accessStore.awsSessionToken) {
|
if (bearerToken) {
|
||||||
headers["X-Session-Token"] = encrypt(accessStore.awsSessionToken);
|
headers[authHeader] = bearerToken;
|
||||||
}
|
} else if (isEnabledAccessControl && validString(accessStore.accessCode)) {
|
||||||
} else {
|
headers["Authorization"] = getBearerToken(
|
||||||
const bearerToken = getBearerToken(
|
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||||
apiKey,
|
|
||||||
isAzure || isAnthropic || isGoogle,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bearerToken) {
|
|
||||||
headers[authHeader] = bearerToken;
|
|
||||||
} else if (isEnabledAccessControl && validString(accessStore.accessCode)) {
|
|
||||||
headers["Authorization"] = getBearerToken(
|
|
||||||
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
ChatOptions,
|
ChatOptions,
|
||||||
|
getHeaders,
|
||||||
LLMApi,
|
LLMApi,
|
||||||
SpeechOptions,
|
SpeechOptions,
|
||||||
RequestMessage,
|
RequestMessage,
|
||||||
|
@ -233,23 +234,22 @@ export class BedrockApi implements LLMApi {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
if (!accessStore.isValidBedrock()) {
|
if (!accessStore.isValidBedrock()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Invalid AWS credentials. Please check your configuration.",
|
"Invalid AWS credentials. Please check your configuration and ensure ENCRYPTION_KEY is set.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiEndpoint = "/api/bedrock/chat";
|
const apiEndpoint = "/api/bedrock/chat";
|
||||||
const headers = {
|
// const headers = {
|
||||||
"Content-Type": requestBody.contentType || "application/json",
|
// "Content-Type": requestBody.contentType || "application/json",
|
||||||
Accept: requestBody.accept || "application/json",
|
// Accept: requestBody.accept || "application/json",
|
||||||
"X-Region": accessStore.awsRegion,
|
// "X-Region": accessStore.awsRegion,
|
||||||
"X-Access-Key": accessStore.awsAccessKey,
|
// "X-Access-Key": accessStore.awsAccessKey,
|
||||||
"X-Secret-Key": accessStore.awsSecretKey,
|
// "X-Secret-Key": accessStore.awsSecretKey,
|
||||||
"X-Model-Id": modelConfig.model,
|
// "X-Model-Id": modelConfig.model,
|
||||||
...(accessStore.awsSessionToken && {
|
// "X-Encryption-Key": accessStore.bedrockEncryptionKey,
|
||||||
"X-Session-Token": accessStore.awsSessionToken,
|
// };
|
||||||
}),
|
const headers = getHeaders();
|
||||||
};
|
|
||||||
|
|
||||||
if (options.config.stream) {
|
if (options.config.stream) {
|
||||||
let index = -1;
|
let index = -1;
|
||||||
|
@ -274,7 +274,6 @@ export class BedrockApi implements LLMApi {
|
||||||
(text: string, runTools: ChatMessageTool[]) => {
|
(text: string, runTools: ChatMessageTool[]) => {
|
||||||
try {
|
try {
|
||||||
const chunkJson = JSON.parse(text);
|
const chunkJson = JSON.parse(text);
|
||||||
// console.log("Received chunk:", JSON.stringify(chunkJson, null, 2));
|
|
||||||
if (chunkJson?.content_block?.type === "tool_use") {
|
if (chunkJson?.content_block?.type === "tool_use") {
|
||||||
index += 1;
|
index += 1;
|
||||||
currentToolArgs = "";
|
currentToolArgs = "";
|
||||||
|
@ -375,7 +374,6 @@ export class BedrockApi implements LLMApi {
|
||||||
});
|
});
|
||||||
|
|
||||||
const resJson = await res.json();
|
const resJson = await res.json();
|
||||||
// console.log("Response:", JSON.stringify(resJson, null, 2));
|
|
||||||
const message = this.extractMessage(resJson, modelConfig.model);
|
const message = this.extractMessage(resJson, modelConfig.model);
|
||||||
// console.log("Extracted message:", message);
|
// console.log("Extracted message:", message);
|
||||||
options.onFinish(message, res);
|
options.onFinish(message, res);
|
||||||
|
|
|
@ -79,17 +79,6 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
--delay: 0.5s;
|
--delay: 0.5s;
|
||||||
width: var(--full-width);
|
width: var(--full-width);
|
||||||
|
@ -410,8 +399,8 @@
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Specific styles for iOS devices */
|
/* Specific styles for iOS devices */
|
||||||
@media screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2) {
|
@media screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2) {
|
||||||
|
@ -761,4 +750,4 @@
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -980,12 +980,12 @@ export function Settings() {
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
const region = e.currentTarget.value;
|
const region = e.currentTarget.value;
|
||||||
if (!/^[a-z]{2}-[a-z]+-\d+$/.test(region)) {
|
if (!/^[a-z]{2}-[a-z]+-\d+$/.test(region)) {
|
||||||
showToast(Locale.Settings.Access.Bedrock.Region.Invalid);
|
showToast(Locale.Settings.Access.Bedrock.Region.Invalid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
access.awsRegion = region;
|
access.awsRegion = region;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -999,7 +999,7 @@ export function Settings() {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={Locale.Settings.Access.Bedrock.AccessKey.Placeholder}
|
placeholder={Locale.Settings.Access.Bedrock.AccessKey.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
const accessKey = e.currentTarget.value;
|
const accessKey = e.currentTarget.value;
|
||||||
if (accessKey && accessKey.length !== 20) {
|
if (accessKey && accessKey.length !== 20) {
|
||||||
showToast(Locale.Settings.Access.Bedrock.AccessKey.Invalid);
|
showToast(Locale.Settings.Access.Bedrock.AccessKey.Invalid);
|
||||||
|
@ -1022,11 +1022,11 @@ export function Settings() {
|
||||||
placeholder={Locale.Settings.Access.Bedrock.SecretKey.Placeholder}
|
placeholder={Locale.Settings.Access.Bedrock.SecretKey.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update((access) => {
|
accessStore.update((access) => {
|
||||||
const secretKey = e.currentTarget.value;
|
const secretKey = e.currentTarget.value;
|
||||||
if (secretKey && secretKey.length !== 40) {
|
if (secretKey && secretKey.length !== 40) {
|
||||||
showToast(Locale.Settings.Access.Bedrock.SecretKey.Invalid);
|
showToast(Locale.Settings.Access.Bedrock.SecretKey.Invalid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
access.awsSecretKey = secretKey;
|
access.awsSecretKey = secretKey;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -1034,17 +1034,17 @@ export function Settings() {
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Settings.Access.Bedrock.SessionToken.Title}
|
title={Locale.Settings.Access.Bedrock.EncryptionKey.Title}
|
||||||
subTitle={Locale.Settings.Access.Bedrock.SessionToken.SubTitle}
|
subTitle={Locale.Settings.Access.Bedrock.EncryptionKey.SubTitle}
|
||||||
>
|
>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
aria-label={Locale.Settings.Access.Bedrock.SessionToken.Title}
|
aria-label={Locale.Settings.Access.Bedrock.EncryptionKey.Title}
|
||||||
value={accessStore.awsSessionToken}
|
value={accessStore.bedrockEncryptionKey}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={Locale.Settings.Access.Bedrock.SessionToken.Placeholder}
|
placeholder={Locale.Settings.Access.Bedrock.EncryptionKey.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update(
|
accessStore.update(
|
||||||
(access) => (access.awsSessionToken = e.currentTarget.value),
|
(access) => (access.bedrockEncryptionKey = e.currentTarget.value),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
maskWhenShow={true}
|
maskWhenShow={true}
|
||||||
|
|
|
@ -13,9 +13,10 @@ declare global {
|
||||||
OPENAI_ORG_ID?: string; // openai only
|
OPENAI_ORG_ID?: string; // openai only
|
||||||
|
|
||||||
// bedrock only
|
// bedrock only
|
||||||
BEDROCK_REGION?: string;
|
AWS_REGION?: string;
|
||||||
BEDROCK_API_KEY?: string;
|
AWS_ACCESS_KEY?: string;
|
||||||
BEDROCK_API_SECRET?: string;
|
AWS_SECRET_KEY?: string;
|
||||||
|
ENCRYPTION_KEY?: string;
|
||||||
|
|
||||||
VERCEL?: string;
|
VERCEL?: string;
|
||||||
BUILD_MODE?: "standalone" | "export";
|
BUILD_MODE?: "standalone" | "export";
|
||||||
|
@ -148,7 +149,10 @@ export const getServerSideConfig = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStability = !!process.env.STABILITY_API_KEY;
|
const isStability = !!process.env.STABILITY_API_KEY;
|
||||||
const isBedrock = !!process.env.BEDROCK_API_KEY;
|
const isBedrock =
|
||||||
|
!!process.env.AWS_REGION &&
|
||||||
|
!!process.env.AWS_ACCESS_KEY &&
|
||||||
|
!!process.env.AWS_SECRET_KEY;
|
||||||
const isAzure = !!process.env.AZURE_URL;
|
const isAzure = !!process.env.AZURE_URL;
|
||||||
const isGoogle = !!process.env.GOOGLE_API_KEY;
|
const isGoogle = !!process.env.GOOGLE_API_KEY;
|
||||||
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
||||||
|
@ -182,6 +186,7 @@ export const getServerSideConfig = () => {
|
||||||
awsRegion: process.env.AWS_REGION,
|
awsRegion: process.env.AWS_REGION,
|
||||||
awsAccessKey: process.env.AWS_ACCESS_KEY,
|
awsAccessKey: process.env.AWS_ACCESS_KEY,
|
||||||
awsSecretKey: process.env.AWS_SECRET_KEY,
|
awsSecretKey: process.env.AWS_SECRET_KEY,
|
||||||
|
bedrockEncryptionKey: process.env.ENCRYPTION_KEY,
|
||||||
|
|
||||||
isStability,
|
isStability,
|
||||||
stabilityUrl: process.env.STABILITY_URL,
|
stabilityUrl: process.env.STABILITY_URL,
|
||||||
|
|
|
@ -361,14 +361,10 @@ const cn = {
|
||||||
Placeholder: "****",
|
Placeholder: "****",
|
||||||
Invalid: "无效的 AWS Secret Key 格式。必须为40个字符。",
|
Invalid: "无效的 AWS Secret Key 格式。必须为40个字符。",
|
||||||
},
|
},
|
||||||
SessionToken: {
|
EncryptionKey: {
|
||||||
Title: "AWS Session Token (Optional)",
|
Title: "加密密钥",
|
||||||
SubTitle: "Your AWS session token if using temporary credentials",
|
SubTitle: "用于配置数据的加密密钥",
|
||||||
Placeholder: "Optional session token",
|
Placeholder: "输入加密密钥",
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "AWS Bedrock Endpoint",
|
|
||||||
SubTitle: "Custom endpoint for AWS Bedrock API. Default: ",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Azure: {
|
Azure: {
|
||||||
|
|
|
@ -365,14 +365,10 @@ const en: LocaleType = {
|
||||||
Placeholder: "****",
|
Placeholder: "****",
|
||||||
Invalid: "Invalid AWS secret key format. Must be 40 characters long.",
|
Invalid: "Invalid AWS secret key format. Must be 40 characters long.",
|
||||||
},
|
},
|
||||||
SessionToken: {
|
EncryptionKey: {
|
||||||
Title: "AWS Session Token (Optional)",
|
Title: "Encryption Key",
|
||||||
SubTitle: "Your AWS session token if using temporary credentials",
|
SubTitle: "Your encryption key for configuration data",
|
||||||
Placeholder: "Optional session token",
|
Placeholder: "Enter encryption key",
|
||||||
},
|
|
||||||
Endpoint: {
|
|
||||||
Title: "AWS Bedrock Endpoint",
|
|
||||||
SubTitle: "Custom endpoint for AWS Bedrock API. Default: ",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Azure: {
|
Azure: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
IFLYTEK_BASE_URL,
|
IFLYTEK_BASE_URL,
|
||||||
XAI_BASE_URL,
|
XAI_BASE_URL,
|
||||||
CHATGLM_BASE_URL,
|
CHATGLM_BASE_URL,
|
||||||
|
BEDROCK_BASE_URL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
import { getHeaders } from "../client/api";
|
import { getHeaders } from "../client/api";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
@ -51,6 +52,8 @@ const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI;
|
||||||
|
|
||||||
const DEFAULT_CHATGLM_URL = isApp ? CHATGLM_BASE_URL : ApiPath.ChatGLM;
|
const DEFAULT_CHATGLM_URL = isApp ? CHATGLM_BASE_URL : ApiPath.ChatGLM;
|
||||||
|
|
||||||
|
const DEFAULT_BEDROCK_URL = isApp ? BEDROCK_BASE_URL : ApiPath.Bedrock;
|
||||||
|
|
||||||
const DEFAULT_ACCESS_STATE = {
|
const DEFAULT_ACCESS_STATE = {
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
useCustomConfig: false,
|
useCustomConfig: false,
|
||||||
|
@ -117,10 +120,11 @@ const DEFAULT_ACCESS_STATE = {
|
||||||
chatglmApiKey: "",
|
chatglmApiKey: "",
|
||||||
|
|
||||||
// aws bedrock
|
// aws bedrock
|
||||||
|
bedrokUrl: DEFAULT_BEDROCK_URL,
|
||||||
awsRegion: "",
|
awsRegion: "",
|
||||||
awsAccessKey: "",
|
awsAccessKey: "",
|
||||||
awsSecretKey: "",
|
awsSecretKey: "",
|
||||||
awsSessionToken: "",
|
bedrockEncryptionKey: "",
|
||||||
|
|
||||||
// server config
|
// server config
|
||||||
needCode: true,
|
needCode: true,
|
||||||
|
@ -200,7 +204,12 @@ export const useAccessStore = createPersistStore(
|
||||||
},
|
},
|
||||||
|
|
||||||
isValidBedrock() {
|
isValidBedrock() {
|
||||||
return ensure(get(), ["awsRegion", "awsAccessKey", "awsSecretKey"]);
|
return ensure(get(), [
|
||||||
|
"awsRegion",
|
||||||
|
"awsAccessKey",
|
||||||
|
"awsSecretKey",
|
||||||
|
"bedrockEncryptionKey",
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
isAuthorized() {
|
isAuthorized() {
|
||||||
|
|
|
@ -3,14 +3,14 @@ import HmacSHA256 from "crypto-js/hmac-sha256";
|
||||||
import Hex from "crypto-js/enc-hex";
|
import Hex from "crypto-js/enc-hex";
|
||||||
import Utf8 from "crypto-js/enc-utf8";
|
import Utf8 from "crypto-js/enc-utf8";
|
||||||
import { AES, enc } from "crypto-js";
|
import { AES, enc } from "crypto-js";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
|
||||||
const SECRET_KEY =
|
const serverConfig = getServerSideConfig();
|
||||||
process.env.ENCRYPTION_KEY ||
|
// console.info(serverConfig);
|
||||||
"your-secret-key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
const SECRET_KEY = serverConfig.bedrockEncryptionKey || "";
|
||||||
if (!SECRET_KEY || SECRET_KEY.length < 32) {
|
// console.info("======SECRET_KEY:"+SECRET_KEY);
|
||||||
throw new Error(
|
if (serverConfig.isBedrock && !SECRET_KEY) {
|
||||||
"ENCRYPTION_KEY environment variable must be set with at least 32 characters",
|
console.error("When use Bedrock modle,ENCRYPTION_KEY should been set!");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encrypt(data: string): string {
|
export function encrypt(data: string): string {
|
||||||
|
@ -54,7 +54,6 @@ export interface SignParams {
|
||||||
region: string;
|
region: string;
|
||||||
accessKeyId: string;
|
accessKeyId: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
sessionToken?: string;
|
|
||||||
body: string;
|
body: string;
|
||||||
service: string;
|
service: string;
|
||||||
}
|
}
|
||||||
|
@ -143,7 +142,6 @@ export async function sign({
|
||||||
region,
|
region,
|
||||||
accessKeyId,
|
accessKeyId,
|
||||||
secretAccessKey,
|
secretAccessKey,
|
||||||
sessionToken,
|
|
||||||
body,
|
body,
|
||||||
service,
|
service,
|
||||||
}: SignParams): Promise<Record<string, string>> {
|
}: SignParams): Promise<Record<string, string>> {
|
||||||
|
@ -169,11 +167,6 @@ export async function sign({
|
||||||
"x-amzn-bedrock-accept": "*/*",
|
"x-amzn-bedrock-accept": "*/*",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add session token if present
|
|
||||||
if (sessionToken) {
|
|
||||||
headers["x-amz-security-token"] = sessionToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get sorted header keys (case-insensitive)
|
// Get sorted header keys (case-insensitive)
|
||||||
const sortedHeaderKeys = Object.keys(headers).sort((a, b) =>
|
const sortedHeaderKeys = Object.keys(headers).sort((a, b) =>
|
||||||
a.toLowerCase().localeCompare(b.toLowerCase()),
|
a.toLowerCase().localeCompare(b.toLowerCase()),
|
||||||
|
@ -230,7 +223,6 @@ export async function sign({
|
||||||
"X-Amz-Content-Sha256": headers["x-amz-content-sha256"],
|
"X-Amz-Content-Sha256": headers["x-amz-content-sha256"],
|
||||||
"X-Amz-Date": headers["x-amz-date"],
|
"X-Amz-Date": headers["x-amz-date"],
|
||||||
"X-Amzn-Bedrock-Accept": headers["x-amzn-bedrock-accept"],
|
"X-Amzn-Bedrock-Accept": headers["x-amzn-bedrock-accept"],
|
||||||
...(sessionToken && { "X-Amz-Security-Token": sessionToken }),
|
|
||||||
Authorization: authorization,
|
Authorization: authorization,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue