mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-21 21:20:19 +09:00
修改: app/api/bedrock.ts
修改: app/client/api.ts 修改: app/store/access.ts 新文件: app/utils/encryption.ts 修改: package.json
This commit is contained in:
parent
1f66d3779c
commit
cae20af24d
@ -1,6 +1,7 @@
|
||||
import { getServerSideConfig } from "../config/server";
|
||||
import { prettyObject } from "../utils/format";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { decrypt } from "../utils/encryption";
|
||||
import {
|
||||
BedrockRuntimeClient,
|
||||
ConverseStreamCommand,
|
||||
@ -12,12 +13,20 @@ import {
|
||||
|
||||
const ALLOWED_PATH = new Set(["converse"]);
|
||||
|
||||
function decrypt(str: string): string {
|
||||
try {
|
||||
return Buffer.from(str, "base64").toString().split("").reverse().join("");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
// AWS Credential Validation Function
|
||||
function validateAwsCredentials(
|
||||
region: string,
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
): boolean {
|
||||
const regionRegex = /^[a-z]{2}-[a-z]+-\d+$/;
|
||||
const accessKeyRegex = /^(AKIA|A3T|ASIA)[A-Z0-9]{16}$/;
|
||||
|
||||
return (
|
||||
regionRegex.test(region) &&
|
||||
accessKeyRegex.test(accessKeyId) &&
|
||||
secretAccessKey.length === 40
|
||||
);
|
||||
}
|
||||
|
||||
export interface ConverseRequest {
|
||||
@ -140,6 +149,7 @@ export async function handle(
|
||||
let secretAccessKey = serverConfig.awsSecretKey;
|
||||
let sessionToken = undefined;
|
||||
|
||||
// Attempt to get credentials from headers if not in server config
|
||||
if (!region || !accessKeyId || !secretAccessKey) {
|
||||
region = decrypt(req.headers.get("X-Region") ?? "");
|
||||
accessKeyId = decrypt(req.headers.get("X-Access-Key") ?? "");
|
||||
@ -149,9 +159,13 @@ export async function handle(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (!region || !accessKeyId || !secretAccessKey) {
|
||||
// Validate AWS credentials
|
||||
if (!validateAwsCredentials(region, accessKeyId, secretAccessKey)) {
|
||||
return NextResponse.json(
|
||||
{ error: true, msg: "Missing AWS credentials" },
|
||||
{
|
||||
error: true,
|
||||
msg: "Invalid AWS credentials. Please check your region, access key, and secret key.",
|
||||
},
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
@ -159,7 +173,11 @@ export async function handle(
|
||||
try {
|
||||
const client = new BedrockRuntimeClient({
|
||||
region,
|
||||
credentials: { accessKeyId, secretAccessKey, sessionToken },
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
sessionToken,
|
||||
},
|
||||
});
|
||||
|
||||
const body = (await req.json()) as ConverseRequest;
|
||||
|
@ -23,6 +23,7 @@ import { MoonshotApi } from "./platforms/moonshot";
|
||||
import { SparkApi } from "./platforms/iflytek";
|
||||
import { XAIApi } from "./platforms/xai";
|
||||
import { ChatGLMApi } from "./platforms/glm";
|
||||
import { encrypt } from "../utils/encryption";
|
||||
|
||||
export const ROLES = ["system", "user", "assistant"] as const;
|
||||
export type MessageRole = (typeof ROLES)[number];
|
||||
@ -330,13 +331,11 @@ export function getHeaders(ignoreHeaders: boolean = false) {
|
||||
const authHeader = getAuthHeader();
|
||||
|
||||
if (isBedrock) {
|
||||
// 简单加密 AWS credentials
|
||||
const encrypt = (str: string) =>
|
||||
Buffer.from(str.split("").reverse().join("")).toString("base64");
|
||||
|
||||
// Secure encryption of AWS credentials using the new encryption utility
|
||||
headers["X-Region"] = encrypt(accessStore.awsRegion);
|
||||
headers["X-Access-Key"] = encrypt(accessStore.awsAccessKey);
|
||||
headers["X-Secret-Key"] = encrypt(accessStore.awsSecretKey);
|
||||
|
||||
if (accessStore.awsSessionToken) {
|
||||
headers["X-Session-Token"] = encrypt(accessStore.awsSessionToken);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { getClientConfig } from "../config/client";
|
||||
import { createPersistStore } from "../utils/store";
|
||||
import { ensure } from "../utils/clone";
|
||||
import { DEFAULT_CONFIG } from "./config";
|
||||
import { encrypt, decrypt } from "../utils/encryption";
|
||||
|
||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||
|
||||
@ -137,6 +138,9 @@ const DEFAULT_ACCESS_STATE = {
|
||||
edgeTTSVoiceName: "zh-CN-YunxiNeural",
|
||||
};
|
||||
|
||||
type AccessState = typeof DEFAULT_ACCESS_STATE;
|
||||
type BedrockCredentialKey = "awsAccessKey" | "awsSecretKey" | "awsSessionToken";
|
||||
|
||||
export const useAccessStore = createPersistStore(
|
||||
{ ...DEFAULT_ACCESS_STATE },
|
||||
|
||||
@ -158,7 +162,43 @@ export const useAccessStore = createPersistStore(
|
||||
},
|
||||
|
||||
isValidBedrock() {
|
||||
return ensure(get(), ["awsAccessKey", "awsSecretKey", "awsRegion"]);
|
||||
const state = get();
|
||||
return (
|
||||
ensure(state, ["awsAccessKey", "awsSecretKey", "awsRegion"]) &&
|
||||
this.validateAwsCredentials(
|
||||
this.getDecryptedAwsCredential("awsAccessKey"),
|
||||
this.getDecryptedAwsCredential("awsSecretKey"),
|
||||
state.awsRegion,
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
validateAwsCredentials(
|
||||
accessKey: string,
|
||||
secretKey: string,
|
||||
region: string,
|
||||
) {
|
||||
// Comprehensive AWS credential validation
|
||||
const accessKeyRegex = /^(AKIA|A3T|ASIA)[A-Z0-9]{16}$/;
|
||||
const regionRegex = /^[a-z]{2}-[a-z]+-\d+$/;
|
||||
|
||||
return (
|
||||
accessKeyRegex.test(accessKey) && // Validate access key format
|
||||
secretKey.length === 40 && // Validate secret key length
|
||||
regionRegex.test(region) && // Validate region format
|
||||
accessKey !== "" &&
|
||||
secretKey !== "" &&
|
||||
region !== ""
|
||||
);
|
||||
},
|
||||
|
||||
setEncryptedAwsCredential(key: BedrockCredentialKey, value: string) {
|
||||
set({ [key]: encrypt(value) });
|
||||
},
|
||||
|
||||
getDecryptedAwsCredential(key: BedrockCredentialKey): string {
|
||||
const encryptedValue = get()[key];
|
||||
return encryptedValue ? decrypt(encryptedValue) : "";
|
||||
},
|
||||
|
||||
isValidAzure() {
|
||||
@ -226,6 +266,7 @@ export const useAccessStore = createPersistStore(
|
||||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
|
||||
);
|
||||
},
|
||||
|
||||
fetch() {
|
||||
if (fetchState > 0 || getClientConfig()?.buildMode === "export") return;
|
||||
fetchState = 1;
|
||||
@ -247,9 +288,25 @@ export const useAccessStore = createPersistStore(
|
||||
|
||||
return res;
|
||||
})
|
||||
.then((res: DangerConfig) => {
|
||||
|
||||
.then((res: Partial<AccessState>) => {
|
||||
console.log("[Config] got config from server", res);
|
||||
set(() => ({ ...res }));
|
||||
// Encrypt Bedrock-related sensitive data before storing
|
||||
const encryptedRes = { ...res };
|
||||
const keysToEncrypt: BedrockCredentialKey[] = [
|
||||
"awsAccessKey",
|
||||
"awsSecretKey",
|
||||
"awsSessionToken",
|
||||
];
|
||||
|
||||
keysToEncrypt.forEach((key) => {
|
||||
const value = encryptedRes[key];
|
||||
if (value) {
|
||||
(encryptedRes[key] as string) = encrypt(value as string);
|
||||
}
|
||||
});
|
||||
|
||||
set(() => ({ ...encryptedRes }));
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("[Config] failed to fetch config");
|
||||
|
22
app/utils/encryption.ts
Normal file
22
app/utils/encryption.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { AES, enc } from "crypto-js";
|
||||
|
||||
const SECRET_KEY = "your-secret-key"; // Replace this with a secure, randomly generated key
|
||||
|
||||
export function encrypt(data: string): string {
|
||||
try {
|
||||
return AES.encrypt(data, SECRET_KEY).toString();
|
||||
} catch (error) {
|
||||
console.error("Encryption failed:", error);
|
||||
return data; // Fallback to unencrypted data if encryption fails
|
||||
}
|
||||
}
|
||||
|
||||
export function decrypt(encryptedData: string): string {
|
||||
try {
|
||||
const bytes = AES.decrypt(encryptedData, SECRET_KEY);
|
||||
return bytes.toString(enc.Utf8);
|
||||
} catch (error) {
|
||||
console.error("Decryption failed:", error);
|
||||
return encryptedData; // Fallback to the original data if decryption fails
|
||||
}
|
||||
}
|
@ -20,13 +20,16 @@
|
||||
"test:ci": "jest --ci"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.679.0",
|
||||
"@fortaine/fetch-event-source": "^3.0.6",
|
||||
"@hello-pangea/dnd": "^16.5.0",
|
||||
"@next/third-parties": "^14.1.0",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"@vercel/speed-insights": "^1.0.2",
|
||||
"axios": "^1.7.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"emoji-picker-react": "^4.9.2",
|
||||
"fuse.js": "^7.0.0",
|
||||
"heic2any": "^0.0.4",
|
||||
@ -51,8 +54,7 @@
|
||||
"sass": "^1.59.2",
|
||||
"spark-md5": "^3.0.2",
|
||||
"use-debounce": "^9.0.4",
|
||||
"zustand": "^4.3.8",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.679.0"
|
||||
"zustand": "^4.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
|
Loading…
Reference in New Issue
Block a user