feat: support using user api key

This commit is contained in:
Yifei Zhang 2023-03-26 11:58:25 +00:00
parent 1e89fe14ac
commit df66eef919
7 changed files with 58 additions and 19 deletions

View File

@ -2,19 +2,25 @@ import type { ChatRequest } from "../chat/typing";
import { createParser } from "eventsource-parser"; import { createParser } from "eventsource-parser";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
const apiKey = process.env.OPENAI_API_KEY; async function createStream(req: NextRequest) {
async function createStream(payload: ReadableStream<Uint8Array>) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let apiKey = process.env.OPENAI_API_KEY;
const userApiKey = req.headers.get("token");
if (userApiKey) {
apiKey = userApiKey;
console.log("[Stream] using user api key");
}
const res = await fetch("https://api.openai.com/v1/chat/completions", { const res = await fetch("https://api.openai.com/v1/chat/completions", {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
method: "POST", method: "POST",
body: payload, body: req.body,
}); });
const stream = new ReadableStream({ const stream = new ReadableStream({
@ -49,7 +55,7 @@ async function createStream(payload: ReadableStream<Uint8Array>) {
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
try { try {
const stream = await createStream(req.body!); const stream = await createStream(req);
return new Response(stream); return new Response(stream);
} catch (error) { } catch (error) {
console.error("[Chat Stream]", error); console.error("[Chat Stream]", error);

View File

@ -1,23 +1,26 @@
import { OpenAIApi, Configuration } from "openai"; import { OpenAIApi, Configuration } from "openai";
import { ChatRequest } from "./typing"; import { ChatRequest } from "./typing";
const apiKey = process.env.OPENAI_API_KEY; export async function POST(req: Request) {
try {
let apiKey = process.env.OPENAI_API_KEY;
const openai = new OpenAIApi( const userApiKey = req.headers.get("token");
if (userApiKey) {
apiKey = userApiKey;
}
const openai = new OpenAIApi(
new Configuration({ new Configuration({
apiKey, apiKey,
}) })
);
export async function POST(req: Request) {
try {
const requestBody = (await req.json()) as ChatRequest;
const completion = await openai!.createChatCompletion(
{
...requestBody,
}
); );
const requestBody = (await req.json()) as ChatRequest;
const completion = await openai!.createChatCompletion({
...requestBody,
});
return new Response(JSON.stringify(completion.data)); return new Response(JSON.stringify(completion.data));
} catch (e) { } catch (e) {
console.error("[Chat] ", e); console.error("[Chat] ", e);

View File

@ -257,6 +257,20 @@ export function Settings(props: { closeSettings: () => void }) {
<></> <></>
)} )}
<SettingItem
title={Locale.Settings.Token.Title}
subTitle={Locale.Settings.Token.SubTitle}
>
<input
value={accessStore.token}
type="text"
placeholder={Locale.Settings.Token.Placeholder}
onChange={(e) => {
accessStore.updateToken(e.currentTarget.value);
}}
></input>
</SettingItem>
<SettingItem <SettingItem
title={Locale.Settings.HistoryCount.Title} title={Locale.Settings.HistoryCount.Title}
subTitle={Locale.Settings.HistoryCount.SubTitle} subTitle={Locale.Settings.HistoryCount.SubTitle}

View File

@ -69,6 +69,11 @@ const cn = {
Title: "历史消息长度压缩阈值", Title: "历史消息长度压缩阈值",
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩", SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
}, },
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可绕过受控访问限制",
Placeholder: "OpenAI API Key",
},
AccessCode: { AccessCode: {
Title: "访问码", Title: "访问码",
SubTitle: "现在是受控访问状态", SubTitle: "现在是受控访问状态",

View File

@ -35,6 +35,10 @@ function getHeaders() {
headers["access-code"] = accessStore.accessCode; headers["access-code"] = accessStore.accessCode;
} }
if (accessStore.token && accessStore.token.length > 0) {
headers["token"] = accessStore.token;
}
return headers; return headers;
} }

View File

@ -4,7 +4,9 @@ import { queryMeta } from "../utils";
export interface AccessControlStore { export interface AccessControlStore {
accessCode: string; accessCode: string;
token: string;
updateToken: (_: string) => void;
updateCode: (_: string) => void; updateCode: (_: string) => void;
enabledAccessControl: () => boolean; enabledAccessControl: () => boolean;
} }
@ -14,6 +16,7 @@ export const ACCESS_KEY = "access-control";
export const useAccessStore = create<AccessControlStore>()( export const useAccessStore = create<AccessControlStore>()(
persist( persist(
(set, get) => ({ (set, get) => ({
token: "",
accessCode: "", accessCode: "",
enabledAccessControl() { enabledAccessControl() {
return queryMeta("access") === "enabled"; return queryMeta("access") === "enabled";
@ -21,6 +24,9 @@ export const useAccessStore = create<AccessControlStore>()(
updateCode(code: string) { updateCode(code: string) {
set((state) => ({ accessCode: code })); set((state) => ({ accessCode: code }));
}, },
updateToken(token: string) {
set((state) => ({ token }));
},
}), }),
{ {
name: ACCESS_KEY, name: ACCESS_KEY,

View File

@ -8,13 +8,14 @@ export const config = {
export function middleware(req: NextRequest, res: NextResponse) { export function middleware(req: NextRequest, res: NextResponse) {
const accessCode = req.headers.get("access-code"); const accessCode = req.headers.get("access-code");
const token = req.headers.get("token");
const hashedCode = md5.hash(accessCode ?? "").trim(); const hashedCode = md5.hash(accessCode ?? "").trim();
console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]); console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]);
console.log("[Auth] got access code:", accessCode); console.log("[Auth] got access code:", accessCode);
console.log("[Auth] hashed access code:", hashedCode); console.log("[Auth] hashed access code:", hashedCode);
if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode)) { if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode) && !token) {
return NextResponse.json( return NextResponse.json(
{ {
needAccessCode: true, needAccessCode: true,