Merge branch 'release/0.14' into feat/cf-sessions

This commit is contained in:
dswbx
2025-06-07 09:01:05 +02:00
committed by GitHub
9 changed files with 295 additions and 71 deletions

View File

@@ -153,6 +153,7 @@ describe("AppAuth", () => {
});
await app.build();
app.registerAdminController();
const spy = spyOn(app.module.auth.authenticator, "requestCookieRefresh");
// register custom route
@@ -162,6 +163,10 @@ describe("AppAuth", () => {
await app.server.request("/api/system/ping");
await app.server.request("/test");
expect(spy.mock.calls.length).toBe(0);
// admin route
await app.server.request("/");
expect(spy.mock.calls.length).toBe(1);
});

View File

@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"bin": "./dist/cli/index.js",
"version": "0.13.0",
"version": "0.14.0-rc.0",
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
"homepage": "https://bknd.io",
"repository": {
@@ -61,11 +61,11 @@
"bcryptjs": "^3.0.2",
"dayjs": "^1.11.13",
"fast-xml-parser": "^5.0.8",
"hono": "^4.7.4",
"json-schema-form-react": "^0.0.2",
"json-schema-library": "10.0.0-rc7",
"json-schema-to-ts": "^3.1.1",
"kysely": "^0.27.6",
"hono": "^4.7.11",
"lodash-es": "^4.17.21",
"oauth4webapi": "^2.11.1",
"object-path-immutable": "^4.1.2",
@@ -76,8 +76,8 @@
"@aws-sdk/client-s3": "^3.758.0",
"@bluwy/giget-core": "^0.1.2",
"@dagrejs/dagre": "^1.1.4",
"@hono/typebox-validator": "^0.3.2",
"@hono/vite-dev-server": "^0.19.0",
"@hono/typebox-validator": "^0.3.3",
"@hono/vite-dev-server": "^0.19.1",
"@hookform/resolvers": "^4.1.3",
"@libsql/kysely-libsql": "^0.4.1",
"@mantine/modals": "^7.17.1",
@@ -120,14 +120,14 @@
"tsc-alias": "^1.8.11",
"tsup": "^8.4.0",
"tsx": "^4.19.3",
"vite": "^6.2.1",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9",
"wouter": "^3.6.0",
"@cloudflare/workers-types": "^4.20250606.0"
},
"optionalDependencies": {
"@hono/node-server": "^1.13.8"
"@hono/node-server": "^1.14.3"
},
"peerDependencies": {
"react": ">=19",

View File

@@ -121,6 +121,7 @@ export class AuthController extends Controller {
const claims = c.get("auth")?.user;
if (claims) {
const { data: user } = await this.userRepo.findId(claims.id);
await this.auth.authenticator?.requestCookieRefresh(c);
return c.json({ user });
}

View File

@@ -347,6 +347,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
}
async logout(c: Context<ServerEnv>) {
$console.info("Logging out");
c.set("auth", undefined);
const cookie = await this.getAuthCookie(c);

View File

@@ -60,11 +60,7 @@ export const auth = (options?: {
}
await next();
if (!skipped) {
// renew cookie if applicable
authenticator?.requestCookieRefresh(c);
}
// @todo: potentially add cookie refresh if content-type html and about to expire
// release
authCtx.skip = false;

View File

@@ -117,7 +117,9 @@ async function detectMimeType(
return;
}
export async function getFileFromContext(c: Context<any>): Promise<File> {
type HonoAnyContext = Context<any, any, any>;
export async function getFileFromContext(c: HonoAnyContext): Promise<File> {
const contentType = c.req.header("Content-Type") ?? "application/octet-stream";
if (
@@ -149,7 +151,7 @@ export async function getFileFromContext(c: Context<any>): Promise<File> {
throw new Error("No file found in request");
}
export async function getBodyFromContext(c: Context<any>): Promise<ReadableStream | File> {
export async function getBodyFromContext(c: HonoAnyContext): Promise<ReadableStream | File> {
const contentType = c.req.header("Content-Type") ?? "application/octet-stream";
if (

View File

@@ -50,11 +50,11 @@ export class AdminController extends Controller {
}
get basepath() {
return this.options.basepath ?? "/";
return this.options.adminBasepath ?? "/";
}
private withBasePath(route: string = "") {
return (this.basepath + route).replace(/(?<!:)\/+/g, "/");
return (this.options.basepath + route).replace(/(?<!:)\/+/g, "/");
}
private withAdminBasePath(route: string = "") {
@@ -80,25 +80,48 @@ export class AdminController extends Controller {
loggedOut: configs.auth.cookie.pathLoggedOut ?? this.withAdminBasePath("/"),
login: this.withAdminBasePath("/auth/login"),
register: this.withAdminBasePath("/auth/register"),
logout: this.withAdminBasePath("/auth/logout"),
logout: "/api/auth/logout",
};
hono.use("*", async (c, next) => {
const obj = {
user: c.get("auth")?.user,
logout_route: authRoutes.logout,
admin_basepath: this.options.adminBasepath,
};
const html = await this.getHtml(obj);
if (!html) {
console.warn("Couldn't generate HTML for admin UI");
// re-casting to void as a return is not required
return c.notFound() as unknown as void;
}
c.set("html", html);
const paths = ["/", "/data/*", "/auth/*", "/media/*", "/flows/*", "/settings/*"];
if (isDebug()) {
paths.push("/test/*");
}
await next();
});
for (const path of paths) {
hono.get(
path,
permission(SystemPermissions.accessAdmin, {
onDenied: async (c) => {
addFlashMessage(c, "You are not authorized to access the Admin UI", "error");
$console.log("redirecting");
return c.redirect(authRoutes.login);
},
}),
permission(SystemPermissions.schemaRead, {
onDenied: async (c) => {
addFlashMessage(c, "You not allowed to read the schema", "warning");
},
}),
async (c) => {
const obj = {
user: c.get("auth")?.user,
logout_route: authRoutes.logout,
admin_basepath: this.options.adminBasepath,
};
const html = await this.getHtml(obj);
if (!html) {
console.warn("Couldn't generate HTML for admin UI");
// re-casting to void as a return is not required
return c.notFound() as unknown as void;
}
await auth.authenticator?.requestCookieRefresh(c);
return c.html(html);
},
);
}
if (auth_enabled) {
const redirectRouteParams = [
@@ -126,27 +149,6 @@ export class AdminController extends Controller {
});
}
// @todo: only load known paths
hono.get(
"/*",
permission(SystemPermissions.accessAdmin, {
onDenied: async (c) => {
addFlashMessage(c, "You are not authorized to access the Admin UI", "error");
$console.log("redirecting");
return c.redirect(authRoutes.login);
},
}),
permission(SystemPermissions.schemaRead, {
onDenied: async (c) => {
addFlashMessage(c, "You not allowed to read the schema", "warning");
},
}),
async (c) => {
return c.html(c.get("html")!);
},
);
return hono;
}
@@ -194,9 +196,13 @@ export class AdminController extends Controller {
}).then((res) => res.default);
}
// @todo: load all marked as entry (incl. css)
assets.js = manifest["src/ui/main.tsx"].file;
assets.css = manifest["src/ui/main.tsx"].css[0] as any;
try {
// @todo: load all marked as entry (incl. css)
assets.js = manifest["src/ui/main.tsx"].file;
assets.css = manifest["src/ui/main.tsx"].css[0] as any;
} catch (e) {
$console.warn("Couldn't find assets in manifest", e);
}
}
const favicon = isProd ? this.options.assetsPath + "favicon.ico" : "/favicon.ico";

View File

@@ -331,6 +331,6 @@ export class SystemController extends Controller {
);
hono.get("/swagger", swaggerUI({ url: "/api/system/openapi.json" }));
return hono.all("*", (c) => c.notFound());
return hono;
}
}