mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #81 from bknd-io/feat/app-auth-context
feat/app-auth-context
This commit is contained in:
@@ -16,10 +16,8 @@ describe("authorize", () => {
|
||||
role: "admin"
|
||||
};
|
||||
|
||||
guard.setUserContext(user);
|
||||
|
||||
expect(guard.granted("read")).toBe(true);
|
||||
expect(guard.granted("write")).toBe(true);
|
||||
expect(guard.granted("read", user)).toBe(true);
|
||||
expect(guard.granted("write", user)).toBe(true);
|
||||
|
||||
expect(() => guard.granted("something")).toThrow();
|
||||
});
|
||||
@@ -46,10 +44,8 @@ describe("authorize", () => {
|
||||
role: "admin"
|
||||
};
|
||||
|
||||
guard.setUserContext(user);
|
||||
|
||||
expect(guard.granted("read")).toBe(true);
|
||||
expect(guard.granted("write")).toBe(true);
|
||||
expect(guard.granted("read", user)).toBe(true);
|
||||
expect(guard.granted("write", user)).toBe(true);
|
||||
});
|
||||
|
||||
test("guard implicit allow", async () => {
|
||||
@@ -66,12 +62,12 @@ describe("authorize", () => {
|
||||
}
|
||||
});
|
||||
|
||||
guard.setUserContext({
|
||||
const user = {
|
||||
role: "admin"
|
||||
});
|
||||
};
|
||||
|
||||
expect(guard.granted("read")).toBe(true);
|
||||
expect(guard.granted("write")).toBe(true);
|
||||
expect(guard.granted("read", user)).toBe(true);
|
||||
expect(guard.granted("write", user)).toBe(true);
|
||||
});
|
||||
|
||||
test("guard with guest role implicit allow", async () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||
import { App, createApp } from "../../src";
|
||||
import type { AuthResponse } from "../../src/auth";
|
||||
import { auth } from "../../src/auth/middlewares";
|
||||
import { randomString, secureRandomString, withDisabledConsole } from "../../src/core/utils";
|
||||
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
||||
|
||||
@@ -98,7 +99,7 @@ const fns = <Mode extends "cookie" | "token" = "token">(app: App, mode?: Mode) =
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Authorization: token ? `Bearer ${token}` : "",
|
||||
"Content-Type": "application/json",
|
||||
...additional
|
||||
};
|
||||
@@ -210,4 +211,36 @@ describe("integration auth", () => {
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
it("context is exclusive", async () => {
|
||||
const app = createAuthApp();
|
||||
await app.build();
|
||||
const $fns = fns(app);
|
||||
|
||||
app.server.get("/get", auth(), async (c) => {
|
||||
return c.json({
|
||||
user: c.get("auth").user ?? null
|
||||
});
|
||||
});
|
||||
app.server.get("/wait", auth(), async (c) => {
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
const { data } = await $fns.login(configs.users.normal);
|
||||
const me = await $fns.me(data.token);
|
||||
expect(me.user.email).toBe(configs.users.normal.email);
|
||||
|
||||
app.server.request("/wait", {
|
||||
headers: { Authorization: `Bearer ${data.token}` }
|
||||
});
|
||||
|
||||
{
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
const res = await app.server.request("/get");
|
||||
const data = await res.json();
|
||||
expect(data.user).toBe(null);
|
||||
expect(await $fns.me()).toEqual({ user: null as any });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,10 +104,9 @@ export class AuthController extends Controller {
|
||||
}
|
||||
|
||||
hono.get("/me", auth(), async (c) => {
|
||||
if (this.auth.authenticator.isUserLoggedIn()) {
|
||||
const claims = this.auth.authenticator.getUser()!;
|
||||
const claims = c.get("auth")?.user;
|
||||
if (claims) {
|
||||
const { data: user } = await this.userRepo.findId(claims.id);
|
||||
|
||||
return c.json({ user });
|
||||
}
|
||||
|
||||
|
||||
@@ -106,17 +106,15 @@ export type AuthUserResolver = (
|
||||
identifier: string,
|
||||
profile: ProfileExchange
|
||||
) => Promise<SafeUser | undefined>;
|
||||
type AuthClaims = SafeUser & {
|
||||
iat: number;
|
||||
iss?: string;
|
||||
exp?: number;
|
||||
};
|
||||
|
||||
export class Authenticator<Strategies extends Record<string, Strategy> = Record<string, Strategy>> {
|
||||
private readonly strategies: Strategies;
|
||||
private readonly config: AuthConfig;
|
||||
private _claims:
|
||||
| undefined
|
||||
| (SafeUser & {
|
||||
iat: number;
|
||||
iss?: string;
|
||||
exp?: number;
|
||||
});
|
||||
private readonly userResolver: AuthUserResolver;
|
||||
|
||||
constructor(strategies: Strategies, userResolver?: AuthUserResolver, config?: AuthConfig) {
|
||||
@@ -148,21 +146,6 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
return this.strategies;
|
||||
}
|
||||
|
||||
isUserLoggedIn(): boolean {
|
||||
return this._claims !== undefined;
|
||||
}
|
||||
|
||||
getUser(): SafeUser | undefined {
|
||||
if (!this._claims) return;
|
||||
|
||||
const { iat, exp, iss, ...user } = this._claims;
|
||||
return user;
|
||||
}
|
||||
|
||||
resetUser() {
|
||||
this._claims = undefined;
|
||||
}
|
||||
|
||||
strategy<
|
||||
StrategyName extends keyof Strategies,
|
||||
Strat extends Strategy = Strategies[StrategyName]
|
||||
@@ -206,7 +189,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
return sign(payload, secret, this.config.jwt?.alg ?? "HS256");
|
||||
}
|
||||
|
||||
async verify(jwt: string): Promise<boolean> {
|
||||
async verify(jwt: string): Promise<AuthClaims | undefined> {
|
||||
try {
|
||||
const payload = await verify(
|
||||
jwt,
|
||||
@@ -221,14 +204,10 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
}
|
||||
}
|
||||
|
||||
this._claims = payload as any;
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.resetUser();
|
||||
//console.error(e);
|
||||
}
|
||||
return payload as any;
|
||||
} catch (e) {}
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
private get cookieOptions(): CookieOptions {
|
||||
@@ -258,8 +237,8 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
}
|
||||
}
|
||||
|
||||
async requestCookieRefresh(c: Context) {
|
||||
if (this.config.cookie.renew && this.isUserLoggedIn()) {
|
||||
async requestCookieRefresh(c: Context<ServerEnv>) {
|
||||
if (this.config.cookie.renew && c.get("auth")?.user) {
|
||||
const token = await this.getAuthCookie(c);
|
||||
if (token) {
|
||||
await this.setAuthCookie(c, token);
|
||||
@@ -276,13 +255,14 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
await deleteCookie(c, "auth", this.cookieOptions);
|
||||
}
|
||||
|
||||
async logout(c: Context) {
|
||||
async logout(c: Context<ServerEnv>) {
|
||||
c.set("auth", undefined);
|
||||
|
||||
const cookie = await this.getAuthCookie(c);
|
||||
if (cookie) {
|
||||
await this.deleteAuthCookie(c);
|
||||
await addFlashMessage(c, "Signed out", "info");
|
||||
}
|
||||
this.resetUser();
|
||||
}
|
||||
|
||||
// @todo: move this to a server helper
|
||||
@@ -353,8 +333,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
}
|
||||
|
||||
if (token) {
|
||||
await this.verify(token);
|
||||
return this.getUser();
|
||||
return await this.verify(token);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { Exception, Permission } from "core";
|
||||
import { objectTransform } from "core/utils";
|
||||
import type { Context } from "hono";
|
||||
import type { ServerEnv } from "modules/Module";
|
||||
import { Role } from "./Role";
|
||||
|
||||
export type GuardUserContext = {
|
||||
role: string | null | undefined;
|
||||
role?: string | null;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type GuardConfig = {
|
||||
enabled?: boolean;
|
||||
};
|
||||
export type GuardContext = Context<ServerEnv> | GuardUserContext;
|
||||
|
||||
const debug = false;
|
||||
|
||||
export class Guard {
|
||||
permissions: Permission[];
|
||||
user?: GuardUserContext;
|
||||
roles?: Role[];
|
||||
config?: GuardConfig;
|
||||
|
||||
@@ -89,24 +91,19 @@ export class Guard {
|
||||
return this;
|
||||
}
|
||||
|
||||
setUserContext(user: GuardUserContext | undefined) {
|
||||
this.user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
getUserRole(): Role | undefined {
|
||||
if (this.user && typeof this.user.role === "string") {
|
||||
const role = this.roles?.find((role) => role.name === this.user?.role);
|
||||
getUserRole(user?: GuardUserContext): Role | undefined {
|
||||
if (user && typeof user.role === "string") {
|
||||
const role = this.roles?.find((role) => role.name === user?.role);
|
||||
if (role) {
|
||||
debug && console.log("guard: role found", [this.user.role]);
|
||||
debug && console.log("guard: role found", [user.role]);
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
||||
debug &&
|
||||
console.log("guard: role not found", {
|
||||
user: this.user,
|
||||
role: this.user?.role
|
||||
user: user,
|
||||
role: user?.role
|
||||
});
|
||||
return this.getDefaultRole();
|
||||
}
|
||||
@@ -119,9 +116,9 @@ export class Guard {
|
||||
return this.config?.enabled === true;
|
||||
}
|
||||
|
||||
hasPermission(permission: Permission): boolean;
|
||||
hasPermission(name: string): boolean;
|
||||
hasPermission(permissionOrName: Permission | string): boolean {
|
||||
hasPermission(permission: Permission, user?: GuardUserContext): boolean;
|
||||
hasPermission(name: string, user?: GuardUserContext): boolean;
|
||||
hasPermission(permissionOrName: Permission | string, user?: GuardUserContext): boolean {
|
||||
if (!this.isEnabled()) {
|
||||
//console.log("guard not enabled, allowing");
|
||||
return true;
|
||||
@@ -133,7 +130,7 @@ export class Guard {
|
||||
throw new Error(`Permission ${name} does not exist`);
|
||||
}
|
||||
|
||||
const role = this.getUserRole();
|
||||
const role = this.getUserRole(user);
|
||||
|
||||
if (!role) {
|
||||
debug && console.log("guard: role not found, denying");
|
||||
@@ -156,12 +153,13 @@ export class Guard {
|
||||
return !!rolePermission;
|
||||
}
|
||||
|
||||
granted(permission: Permission | string): boolean {
|
||||
return this.hasPermission(permission as any);
|
||||
granted(permission: Permission | string, c?: GuardContext): boolean {
|
||||
const user = c && "get" in c ? c.get("auth")?.user : c;
|
||||
return this.hasPermission(permission as any, user);
|
||||
}
|
||||
|
||||
throwUnlessGranted(permission: Permission | string) {
|
||||
if (!this.granted(permission)) {
|
||||
throwUnlessGranted(permission: Permission | string, c: GuardContext) {
|
||||
if (!this.granted(permission, c)) {
|
||||
throw new Exception(
|
||||
`Permission "${typeof permission === "string" ? permission : permission.name}" not granted`,
|
||||
403
|
||||
|
||||
@@ -10,7 +10,12 @@ function getPath(reqOrCtx: Request | Context) {
|
||||
}
|
||||
|
||||
export function shouldSkip(c: Context<ServerEnv>, skip?: (string | RegExp)[]) {
|
||||
if (c.get("auth_skip")) return true;
|
||||
const authCtx = c.get("auth");
|
||||
if (!authCtx) {
|
||||
throw new Error("auth ctx not found");
|
||||
}
|
||||
|
||||
if (authCtx.skip) return true;
|
||||
|
||||
const req = c.req.raw;
|
||||
if (!skip) return false;
|
||||
@@ -18,7 +23,7 @@ export function shouldSkip(c: Context<ServerEnv>, skip?: (string | RegExp)[]) {
|
||||
const path = getPath(req);
|
||||
const result = skip.some((s) => patternMatch(path, s));
|
||||
|
||||
c.set("auth_skip", result);
|
||||
authCtx.skip = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -26,29 +31,31 @@ export const auth = (options?: {
|
||||
skip?: (string | RegExp)[];
|
||||
}) =>
|
||||
createMiddleware<ServerEnv>(async (c, next) => {
|
||||
if (!c.get("auth")) {
|
||||
c.set("auth", {
|
||||
registered: false,
|
||||
resolved: false,
|
||||
skip: false,
|
||||
user: undefined
|
||||
});
|
||||
}
|
||||
|
||||
const app = c.get("app");
|
||||
const guard = app?.modules.ctx().guard;
|
||||
const authCtx = c.get("auth")!;
|
||||
const authenticator = app?.module.auth.authenticator;
|
||||
|
||||
let skipped = shouldSkip(c, options?.skip) || !app?.module.auth.enabled;
|
||||
|
||||
// make sure to only register once
|
||||
if (c.get("auth_registered")) {
|
||||
if (authCtx.registered) {
|
||||
skipped = true;
|
||||
console.warn(`auth middleware already registered for ${getPath(c)}`);
|
||||
} else {
|
||||
c.set("auth_registered", true);
|
||||
authCtx.registered = true;
|
||||
|
||||
if (!skipped) {
|
||||
const resolved = c.get("auth_resolved");
|
||||
if (!resolved) {
|
||||
if (!app?.module.auth.enabled) {
|
||||
guard?.setUserContext(undefined);
|
||||
} else {
|
||||
guard?.setUserContext(await authenticator?.resolveAuthFromRequest(c));
|
||||
c.set("auth_resolved", true);
|
||||
}
|
||||
}
|
||||
if (!skipped && !authCtx.resolved && app?.module.auth.enabled) {
|
||||
authCtx.user = await authenticator?.resolveAuthFromRequest(c);
|
||||
authCtx.resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +67,9 @@ export const auth = (options?: {
|
||||
}
|
||||
|
||||
// release
|
||||
guard?.setUserContext(undefined);
|
||||
authenticator?.resetUser();
|
||||
c.set("auth_resolved", false);
|
||||
authCtx.skip = false;
|
||||
authCtx.resolved = false;
|
||||
authCtx.user = undefined;
|
||||
});
|
||||
|
||||
export const permission = (
|
||||
@@ -75,23 +82,26 @@ export const permission = (
|
||||
// @ts-ignore
|
||||
createMiddleware<ServerEnv>(async (c, next) => {
|
||||
const app = c.get("app");
|
||||
//console.log("skip?", c.get("auth_skip"));
|
||||
const authCtx = c.get("auth");
|
||||
if (!authCtx) {
|
||||
throw new Error("auth ctx not found");
|
||||
}
|
||||
|
||||
// in tests, app is not defined
|
||||
if (!c.get("auth_registered") || !app) {
|
||||
if (!authCtx.registered || !app) {
|
||||
const msg = `auth middleware not registered, cannot check permissions for ${getPath(c)}`;
|
||||
if (app?.module.auth.enabled) {
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
console.warn(msg);
|
||||
}
|
||||
} else if (!c.get("auth_skip")) {
|
||||
} else if (!authCtx.skip) {
|
||||
const guard = app.modules.ctx().guard;
|
||||
const permissions = Array.isArray(permission) ? permission : [permission];
|
||||
|
||||
if (options?.onGranted || options?.onDenied) {
|
||||
let returned: undefined | void | Response;
|
||||
if (permissions.every((p) => guard.granted(p))) {
|
||||
if (permissions.every((p) => guard.granted(p, c))) {
|
||||
returned = await options?.onGranted?.(c);
|
||||
} else {
|
||||
returned = await options?.onDenied?.(c);
|
||||
@@ -100,7 +110,7 @@ export const permission = (
|
||||
return returned;
|
||||
}
|
||||
} else {
|
||||
permissions.some((p) => guard.throwUnlessGranted(p));
|
||||
permissions.some((p) => guard.throwUnlessGranted(p, c));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -327,12 +327,9 @@ export class DataController extends Controller {
|
||||
// delete one
|
||||
.delete(
|
||||
"/:entity/:id",
|
||||
|
||||
permission(DataPermissions.entityDelete),
|
||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
||||
async (c) => {
|
||||
this.guard.throwUnlessGranted(DataPermissions.entityDelete);
|
||||
|
||||
const { entity, id } = c.req.param();
|
||||
if (!this.entityExists(entity)) {
|
||||
return c.notFound();
|
||||
@@ -350,14 +347,11 @@ export class DataController extends Controller {
|
||||
tb("param", Type.Object({ entity: Type.String() })),
|
||||
tb("json", querySchema.properties.where),
|
||||
async (c) => {
|
||||
//console.log("request", c.req.raw);
|
||||
const { entity } = c.req.param();
|
||||
if (!this.entityExists(entity)) {
|
||||
return c.notFound();
|
||||
}
|
||||
const where = c.req.valid("json") as RepoQuery["where"];
|
||||
//console.log("where", where);
|
||||
|
||||
const result = await this.em.mutator(entity).deleteWhere(where);
|
||||
|
||||
return c.json(this.mutatorResult(result));
|
||||
|
||||
@@ -18,13 +18,14 @@ import { isEqual } from "lodash-es";
|
||||
|
||||
export type ServerEnv = {
|
||||
Variables: {
|
||||
app?: App;
|
||||
app: App;
|
||||
// to prevent resolving auth multiple times
|
||||
auth_resolved?: boolean;
|
||||
// to only register once
|
||||
auth_registered?: boolean;
|
||||
// whether or not to bypass auth
|
||||
auth_skip?: boolean;
|
||||
auth?: {
|
||||
resolved: boolean;
|
||||
registered: boolean;
|
||||
skip: boolean;
|
||||
user?: { id: any; role?: string; [key: string]: any };
|
||||
};
|
||||
html?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ export class AdminController extends Controller {
|
||||
|
||||
hono.use("*", async (c, next) => {
|
||||
const obj = {
|
||||
user: auth.authenticator?.getUser(),
|
||||
user: c.get("auth")?.user,
|
||||
logout_route: this.withBasePath(authRoutes.logout),
|
||||
color_scheme: configs.server.admin.color_scheme
|
||||
};
|
||||
@@ -91,7 +91,7 @@ export class AdminController extends Controller {
|
||||
// @ts-ignore
|
||||
onGranted: async (c) => {
|
||||
// @todo: add strict test to permissions middleware?
|
||||
if (auth.authenticator.isUserLoggedIn()) {
|
||||
if (c.get("auth")?.user) {
|
||||
console.log("redirecting to success");
|
||||
return c.redirect(authRoutes.success);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export class SystemController extends Controller {
|
||||
|
||||
private registerConfigController(client: Hono<any>): void {
|
||||
const { permission } = this.middlewares;
|
||||
// don't add auth again, it's already added in getController
|
||||
const hono = this.create();
|
||||
|
||||
hono.use(permission(SystemPermissions.configRead));
|
||||
@@ -63,7 +64,7 @@ export class SystemController extends Controller {
|
||||
const { secrets } = c.req.valid("query");
|
||||
const { module } = c.req.valid("param");
|
||||
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets, c);
|
||||
|
||||
const config = this.app.toJSON(secrets);
|
||||
|
||||
@@ -227,8 +228,8 @@ export class SystemController extends Controller {
|
||||
const module = c.req.param("module") as ModuleKey | undefined;
|
||||
const { config, secrets } = c.req.valid("query");
|
||||
|
||||
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead, c);
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets, c);
|
||||
|
||||
const { version, ...schema } = this.app.getSchema();
|
||||
|
||||
@@ -261,73 +262,27 @@ export class SystemController extends Controller {
|
||||
),
|
||||
async (c) => {
|
||||
const { sync } = c.req.valid("query") as Record<string, boolean>;
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.build);
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.build, c);
|
||||
|
||||
await this.app.build({ sync });
|
||||
return c.json({ success: true, options: { sync } });
|
||||
}
|
||||
);
|
||||
|
||||
hono.get("/ping", async (c) => {
|
||||
//console.log("c", c);
|
||||
try {
|
||||
// @ts-ignore @todo: fix with env
|
||||
const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf;
|
||||
const cf = {
|
||||
colo: context.colo,
|
||||
city: context.city,
|
||||
postal: context.postalCode,
|
||||
region: context.region,
|
||||
regionCode: context.regionCode,
|
||||
continent: context.continent,
|
||||
country: context.country,
|
||||
eu: context.isEUCountry,
|
||||
lat: context.latitude,
|
||||
lng: context.longitude,
|
||||
timezone: context.timezone
|
||||
};
|
||||
return c.json({ pong: true });
|
||||
} catch (e) {
|
||||
return c.json({ pong: true });
|
||||
}
|
||||
});
|
||||
hono.get("/ping", (c) => c.json({ pong: true }));
|
||||
|
||||
hono.get("/info", async (c) => {
|
||||
return c.json({
|
||||
version: this.app.version(),
|
||||
test: 2,
|
||||
app: c.get("app")?.version(),
|
||||
hono.get("/info", (c) =>
|
||||
c.json({
|
||||
version: c.get("app")?.version(),
|
||||
runtime: getRuntimeKey()
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
hono.get("/openapi.json", async (c) => {
|
||||
//const config = this.app.toJSON();
|
||||
const config = getDefaultConfig();
|
||||
return c.json(generateOpenAPI(config));
|
||||
});
|
||||
|
||||
/*hono.get("/test/sql", async (c) => {
|
||||
// @ts-ignore
|
||||
const ai = c.env?.AI as Ai;
|
||||
const messages = [
|
||||
{ role: "system", content: "You are a friendly assistant" },
|
||||
{
|
||||
role: "user",
|
||||
content: "just say hello"
|
||||
}
|
||||
];
|
||||
|
||||
const stream = await ai.run("@cf/meta/llama-3.1-8b-instruct", {
|
||||
messages,
|
||||
stream: true
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: { "content-type": "text/event-stream" }
|
||||
});
|
||||
});*/
|
||||
|
||||
return hono;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user