refactor: merge token and access code
This commit is contained in:
parent
ef5b7ce853
commit
48ebd74859
70
app/api/auth.ts
Normal file
70
app/api/auth.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
import md5 from "spark-md5";
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
function getIP(req: NextRequest) {
|
||||||
|
let ip = req.ip ?? req.headers.get("x-real-ip");
|
||||||
|
const forwardedFor = req.headers.get("x-forwarded-for");
|
||||||
|
|
||||||
|
if (!ip && forwardedFor) {
|
||||||
|
ip = forwardedFor.split(",").at(0) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseApiKey(bearToken: string) {
|
||||||
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
|
const isOpenAiKey = token.startsWith("sk-");
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessCode: isOpenAiKey ? "" : token,
|
||||||
|
apiKey: isOpenAiKey ? token : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function auth(req: NextRequest) {
|
||||||
|
const authToken = req.headers.get("Authorization") ?? "";
|
||||||
|
|
||||||
|
// check if it is openai api key or user token
|
||||||
|
const { accessCode, apiKey: token } = parseApiKey(authToken);
|
||||||
|
|
||||||
|
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||||
|
|
||||||
|
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
||||||
|
console.log("[Auth] got access code:", accessCode);
|
||||||
|
console.log("[Auth] hashed access code:", hashedCode);
|
||||||
|
console.log("[User IP] ", getIP(req));
|
||||||
|
console.log("[Time] ", new Date().toLocaleString());
|
||||||
|
|
||||||
|
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
needAccessCode: true,
|
||||||
|
msg: "Please go settings page and fill your access code.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user does not provide an api key, inject system api key
|
||||||
|
if (!token) {
|
||||||
|
const apiKey = serverConfig.apiKey;
|
||||||
|
if (apiKey) {
|
||||||
|
console.log("[Auth] use system api key");
|
||||||
|
req.headers.set("Authorization", `Bearer ${apiKey}`);
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] admin did not provide an api key");
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
msg: "Empty Api Key",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] use user api key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
}
|
@ -6,8 +6,11 @@ const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
|
|||||||
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
||||||
|
|
||||||
export async function requestOpenai(req: NextRequest) {
|
export async function requestOpenai(req: NextRequest) {
|
||||||
const apiKey = req.headers.get("token");
|
const authValue = req.headers.get("Authorization") ?? "";
|
||||||
const openaiPath = req.headers.get("path");
|
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
|
||||||
|
"/api/openai/",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
let baseUrl = BASE_URL;
|
let baseUrl = BASE_URL;
|
||||||
|
|
||||||
@ -22,10 +25,14 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
console.log("[Org ID]", process.env.OPENAI_ORG_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!authValue || !authValue.startsWith("Bearer sk-")) {
|
||||||
|
console.error("[OpenAI Request] invlid api key provided", authValue);
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(`${baseUrl}/${openaiPath}`, {
|
return fetch(`${baseUrl}/${openaiPath}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: authValue,
|
||||||
...(process.env.OPENAI_ORG_ID && {
|
...(process.env.OPENAI_ORG_ID && {
|
||||||
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
||||||
}),
|
}),
|
||||||
|
@ -14,7 +14,7 @@ declare global {
|
|||||||
type DangerConfig = typeof DANGER_CONFIG;
|
type DangerConfig = typeof DANGER_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST() {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
needCode: serverConfig.needCode,
|
needCode: serverConfig.needCode,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createParser } from "eventsource-parser";
|
import { createParser } from "eventsource-parser";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { requestOpenai } from "../common";
|
import { auth } from "../../auth";
|
||||||
|
import { requestOpenai } from "../../common";
|
||||||
|
|
||||||
async function createStream(res: Response) {
|
async function createStream(res: Response) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
@ -43,7 +44,19 @@ function formatResponse(msg: any) {
|
|||||||
return new Response(jsonMsg);
|
return new Response(jsonMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeRequest(req: NextRequest) {
|
async function handle(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { path: string[] } },
|
||||||
|
) {
|
||||||
|
console.log("[OpenAI Route] params ", params);
|
||||||
|
|
||||||
|
const authResult = auth(req);
|
||||||
|
if (authResult.error) {
|
||||||
|
return NextResponse.json(authResult, {
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const api = await requestOpenai(req);
|
const api = await requestOpenai(req);
|
||||||
|
|
||||||
@ -52,7 +65,9 @@ async function makeRequest(req: NextRequest) {
|
|||||||
// streaming response
|
// streaming response
|
||||||
if (contentType.includes("stream")) {
|
if (contentType.includes("stream")) {
|
||||||
const stream = await createStream(api);
|
const stream = await createStream(api);
|
||||||
return new Response(stream);
|
const res = new Response(stream);
|
||||||
|
res.headers.set("Content-Type", contentType);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to parse error msg
|
// try to parse error msg
|
||||||
@ -80,12 +95,7 @@ async function makeRequest(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export const GET = handle;
|
||||||
return makeRequest(req);
|
export const POST = handle;
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
return makeRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
@ -44,14 +44,21 @@ const makeRequestParam = (
|
|||||||
|
|
||||||
function getHeaders() {
|
function getHeaders() {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
let headers: Record<string, string> = {};
|
const headers = {
|
||||||
|
Authorization: "",
|
||||||
|
};
|
||||||
|
|
||||||
if (accessStore.enabledAccessControl()) {
|
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
|
||||||
headers["access-code"] = accessStore.accessCode;
|
const validString = (x: string) => x && x.length > 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (accessStore.token && accessStore.token.length > 0) {
|
// use user's api key first
|
||||||
headers["token"] = accessStore.token;
|
if (validString(accessStore.token)) {
|
||||||
|
headers.Authorization = makeBearer(accessStore.token);
|
||||||
|
} else if (
|
||||||
|
accessStore.enabledAccessControl() &&
|
||||||
|
validString(accessStore.accessCode)
|
||||||
|
) {
|
||||||
|
headers.Authorization = makeBearer(accessStore.accessCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
@ -59,13 +66,8 @@ function getHeaders() {
|
|||||||
|
|
||||||
export function requestOpenaiClient(path: string) {
|
export function requestOpenaiClient(path: string) {
|
||||||
return (body: any, method = "POST") =>
|
return (body: any, method = "POST") =>
|
||||||
fetch("/api/openai", {
|
fetch("/api/openai/" + path, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
path,
|
|
||||||
...getHeaders(),
|
|
||||||
},
|
|
||||||
body: body && JSON.stringify(body),
|
body: body && JSON.stringify(body),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -161,16 +163,16 @@ export async function requestChatStream(
|
|||||||
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS);
|
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/openai", {
|
const res = await fetch("/api/openai/v1/chat/completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
path: "v1/chat/completions",
|
|
||||||
...getHeaders(),
|
...getHeaders(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(reqTimeoutId);
|
clearTimeout(reqTimeoutId);
|
||||||
|
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
|
import { BOT_HELLO } from "./chat";
|
||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { getServerSideConfig } from "./app/config/server";
|
|
||||||
import md5 from "spark-md5";
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: ["/api/openai"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
function getIP(req: NextRequest) {
|
|
||||||
let ip = req.ip ?? req.headers.get("x-real-ip");
|
|
||||||
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
||||||
|
|
||||||
if (!ip && forwardedFor) {
|
|
||||||
ip = forwardedFor.split(",").at(0) ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
|
||||||
const accessCode = req.headers.get("access-code");
|
|
||||||
const token = req.headers.get("token");
|
|
||||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
|
||||||
|
|
||||||
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
|
||||||
console.log("[Auth] got access code:", accessCode);
|
|
||||||
console.log("[Auth] hashed access code:", hashedCode);
|
|
||||||
console.log("[User IP] ", getIP(req));
|
|
||||||
console.log("[Time] ", new Date().toLocaleString());
|
|
||||||
|
|
||||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
needAccessCode: true,
|
|
||||||
msg: "Please go settings page and fill your access code.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject api key
|
|
||||||
if (!token) {
|
|
||||||
const apiKey = serverConfig.apiKey;
|
|
||||||
if (apiKey) {
|
|
||||||
console.log("[Auth] set system token");
|
|
||||||
req.headers.set("token", apiKey);
|
|
||||||
} else {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
msg: "Empty Api Key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("[Auth] set user token");
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next({
|
|
||||||
request: {
|
|
||||||
headers: req.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
@ -15,4 +15,4 @@ const nextConfig = {
|
|||||||
output: "standalone",
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
export default nextConfig;
|
Loading…
x
Reference in New Issue
Block a user