added cookie option partitioned, as well as cors origin to be array, option to enable credentials (#214)

* added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials`

* fix server test

* fix data api (updated jsonv-ts)
This commit is contained in:
dswbx
2025-07-22 11:46:11 +02:00
committed by GitHub
parent b4f024bef8
commit 847c264b35
10 changed files with 29 additions and 13 deletions
+1 -1
View File
@@ -202,7 +202,7 @@ describe("DataApi", () => {
{
// create many
const res = await api.createMany("posts", payload);
expect(res.data.length).toEqual(4);
expect(res.data?.length).toEqual(4);
expect(res.ok).toBeTrue();
}
+2
View File
@@ -8,6 +8,7 @@ describe("AppServer", () => {
expect(server).toBeDefined();
expect(server.config).toEqual({
cors: {
allow_credentials: true,
origin: "*",
allow_methods: ["GET", "POST", "PATCH", "PUT", "DELETE"],
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
@@ -25,6 +26,7 @@ describe("AppServer", () => {
expect(server).toBeDefined();
expect(server.config).toEqual({
cors: {
allow_credentials: true,
origin: "https",
allow_methods: ["GET", "POST"],
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
+1 -1
View File
@@ -99,7 +99,7 @@
"dotenv": "^16.4.7",
"jotai": "^2.12.2",
"jsdom": "^26.0.0",
"jsonv-ts": "^0.2.4",
"jsonv-ts": "^0.3.1",
"kysely-d1": "^0.3.0",
"kysely-generic-sqlite": "^1.2.1",
"libsql-stateless-easy": "^1.8.0",
+7 -1
View File
@@ -4,7 +4,7 @@ import { runtimeSupports, truncate, $console } from "core/utils";
import type { Context, Hono } from "hono";
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
import { sign, verify } from "hono/jwt";
import type { CookieOptions } from "hono/utils/cookie";
import { type CookieOptions, serializeSigned } from "hono/utils/cookie";
import type { ServerEnv } from "modules/Controller";
import { pick } from "lodash-es";
import { InvalidConditionsException } from "auth/errors";
@@ -58,6 +58,7 @@ export const cookieConfig = s
secure: s.boolean({ default: true }),
httpOnly: s.boolean({ default: true }),
expires: s.number({ default: defaultCookieExpires }), // seconds
partitioned: s.boolean({ default: false }),
renew: s.boolean({ default: true }),
pathSuccess: s.string({ default: "/" }),
pathLoggedOut: s.string({ default: "/" }),
@@ -334,6 +335,11 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
await setSignedCookie(c, "auth", token, secret, this.cookieOptions);
}
async unsafeGetAuthCookie(token: string): Promise<string | undefined> {
// this works for as long as cookieOptions.prefix is not set
return serializeSigned("auth", token, this.config.jwt.secret, this.cookieOptions);
}
private deleteAuthCookie(c: Context) {
$console.debug("deleting auth cookie");
deleteCookie(c, "auth", this.cookieOptions);
+5 -1
View File
@@ -42,6 +42,7 @@ export type ParseOptions = {
withDefaults?: boolean;
withExtendedDefaults?: boolean;
coerce?: boolean;
coerceDropUnknown?: boolean;
clone?: boolean;
skipMark?: boolean; // @todo: do something with this
forceParse?: boolean; // @todo: do something with this
@@ -59,7 +60,10 @@ export function parse<S extends s.Schema, Options extends ParseOptions = ParseOp
opts?: Options,
): Options extends { coerce: true } ? s.StaticCoerced<S> : s.Static<S> {
const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema;
let value = opts?.coerce !== false ? schema.coerce(v) : v;
let value =
opts?.coerce !== false
? schema.coerce(v, { dropUnknown: opts?.coerceDropUnknown ?? false })
: v;
if (opts?.withDefaults !== false) {
value = schema.template(value, {
withOptional: true,
+3 -3
View File
@@ -206,7 +206,7 @@ export class DataController extends Controller {
const entitiesEnum = this.getEntitiesEnum(this.em);
// @todo: make dynamic based on entity
const idType = s.anyOf([s.number(), s.string()], { coerce: (v) => v as any });
const idType = s.anyOf([s.number(), s.string()], { coerce: (v) => v as number | string });
/**
* Function endpoints
@@ -387,7 +387,7 @@ export class DataController extends Controller {
if (!this.entityExists(entity)) {
return this.notFound(c);
}
const options = (await c.req.json()) as RepoQuery;
const options = c.req.valid("json") as RepoQuery;
const result = await this.em.repository(entity).findMany(options);
return c.json(result, { status: result.data ? 200 : 404 });
@@ -397,7 +397,7 @@ export class DataController extends Controller {
/**
* Mutation endpoints
*/
// insert one
// insert one or many
hono.post(
"/:entity",
describeRoute({
+1 -1
View File
@@ -64,7 +64,7 @@ export class Controller {
return c.notFound();
}
protected getEntitiesEnum(em: EntityManager<any>) {
protected getEntitiesEnum(em: EntityManager<any>): s.StringSchema {
const entities = em.entities.map((e) => e.name);
// @todo: current workaround to allow strings (sometimes building is not fast enough to get the entities)
return entities.length > 0 ? s.anyOf([s.string({ enum: entities }), s.string()]) : s.string();
+4 -1
View File
@@ -17,6 +17,7 @@ export const serverConfigSchema = s.strictObject({
allow_headers: s.array(s.string(), {
default: ["Content-Type", "Content-Length", "Authorization", "Accept"],
}),
allow_credentials: s.boolean({ default: true }),
}),
});
@@ -36,12 +37,14 @@ export class AppServer extends Module<AppServerConfig> {
}
override async build() {
const origin = this.config.cors.origin ?? "";
this.client.use(
"*",
cors({
origin: this.config.cors.origin,
origin: origin.includes(",") ? origin.split(",").map((o) => o.trim()) : origin,
allowMethods: this.config.cors.allow_methods,
allowHeaders: this.config.cors.allow_headers,
credentials: this.config.cors.allow_credentials,
}),
);
@@ -323,6 +323,7 @@ export class SystemController extends Controller {
local: datetimeStringLocal(),
utc: datetimeStringUTC(),
},
origin: new URL(c.req.raw.url).origin,
plugins: Array.from(this.app.plugins.keys()),
walk: {
auth: [
+4 -4
View File
@@ -71,7 +71,7 @@
"dotenv": "^16.4.7",
"jotai": "^2.12.2",
"jsdom": "^26.0.0",
"jsonv-ts": "^0.2.4",
"jsonv-ts": "^0.3.1",
"kysely-d1": "^0.3.0",
"kysely-generic-sqlite": "^1.2.1",
"libsql-stateless-easy": "^1.8.0",
@@ -1216,7 +1216,7 @@
"@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="],
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
@@ -2496,7 +2496,7 @@
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
"jsonv-ts": ["jsonv-ts@0.2.4", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-GCbLh0udtsIvDeiLR09PGZo/roaTxxxxEYBqCjoMzfj8xvq5kV9x7uvMI9Qs6Ff9rboL5anOP4xKfAmZHAwJqA=="],
"jsonv-ts": ["jsonv-ts@0.3.1", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-MayDgM0+WprZsf0BaJjMPlp4Pk7WWlhzid0dMFSFsZ4p9Ax1I4m0wL+H5FdRiaVuSz5TfMlCQm/TzwiLx4DWBQ=="],
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
@@ -4020,7 +4020,7 @@
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"@types/bun/bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
"@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="],