Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "zenstack-v3",
"displayName": "ZenStack",
"description": "ZenStack",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-adapters/better-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/better-auth",
"displayName": "ZenStack Better Auth Adapter",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/cli",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
52 changes: 11 additions & 41 deletions packages/cli/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export function normalizePublicKey(key: string): string {
return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----`;
}

export interface CreateProxyAppOptions {
client: ClientContract<SchemaDef>;
schema: SchemaDef;
authDb?: ClientContract<SchemaDef>;
export interface CreateProxyAppOptions<Schema extends SchemaDef = SchemaDef> {
client: ClientContract<Schema>;
schema: Schema;
authDb?: ClientContract<Schema>;
auth?: {
studioAuthKey?: string;
/** Seconds within which a signed request is considered valid. Defaults to 60. */
Expand All @@ -49,37 +49,7 @@ export interface CreateProxyAppOptions {
cors?: Parameters<typeof cors>[0];
}

export function createProxyApp(options: CreateProxyAppOptions): Hono;
export function createProxyApp(
client: ClientContract<SchemaDef>,
schema: SchemaDef,
authDb?: ClientContract<SchemaDef>,
auth?: {
studioAuthKey?: string;
signatureToleranceSecs?: number;
},
): Hono;
export function createProxyApp(
optionsOrClient: CreateProxyAppOptions | ClientContract<SchemaDef>,
schema?: SchemaDef,
authDb?: ClientContract<SchemaDef>,
auth?: {
studioAuthKey?: string;
signatureToleranceSecs?: number;
},
): Hono {
let options: CreateProxyAppOptions;
if ('client' in optionsOrClient && 'schema' in optionsOrClient) {
options = optionsOrClient as CreateProxyAppOptions;
} else {
options = {
client: optionsOrClient as ClientContract<SchemaDef>,
schema: schema!,
authDb,
auth,
};
}

export function createProxyApp<Schema extends SchemaDef = SchemaDef>(options: CreateProxyAppOptions<Schema>): Hono {
Comment thread
ymc9 marked this conversation as resolved.
const app = new Hono();
app.use('*', cors(options.cors));

Expand All @@ -93,7 +63,7 @@ export function createProxyApp(

app.use(
'/api/model/*',
createHonoHandler({
createHonoHandler<Schema>({
apiHandler: new RPCApiHandler({ schema: options.schema }),
getClient: (c) =>
resolveClient(options.client, options.authDb ?? options.client, c, !!options.auth?.studioAuthKey),
Expand Down Expand Up @@ -174,12 +144,12 @@ export function createSignatureMiddleware(publicKey: string, toleranceSeconds: n
};
}

export function resolveClient(
client: ClientContract<SchemaDef>,
authDb: ClientContract<SchemaDef>,
export function resolveClient<Schema extends SchemaDef = SchemaDef>(
client: ClientContract<Schema>,
authDb: ClientContract<Schema>,
c: Context,
isAuthKeyEnabled: boolean,
): ClientContract<SchemaDef> {
): ClientContract<Schema> {
const authHeader = c.req.header('authorization');

if (!isAuthKeyEnabled && !authHeader) {
Expand All @@ -204,6 +174,6 @@ export function resolveClient(
if (claim.type === 'superUser') {
return client;
} else {
return authDb.$setAuth(claim.data as any) as ClientContract<SchemaDef>;
return authDb.$setAuth(claim.data as any) as ClientContract<Schema>;
}
}
1 change: 1 addition & 0 deletions packages/cli/test/db/pull.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ enum Status {
score Float @gte(0.0)
rating Decimal @lt(10)
rank BigInt @lte(999)
extId String @uuid
}`,
);
runCli('db push', workDir);
Expand Down
123 changes: 92 additions & 31 deletions packages/cli/test/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ async function createPolicyApp(zmodel: string) {
const authDb = client.$use(new PolicyPlugin());
return {
client,
app: createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
app: createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
},
}),
};
}
Expand Down Expand Up @@ -106,7 +111,11 @@ describe('CLI proxy tests', () => {

const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb);
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
});
const baseUrl = await startAt(app);

const r = await fetch(`${baseUrl}/api/schema`);
Expand Down Expand Up @@ -169,7 +178,11 @@ describe('CLI proxy tests', () => {
});

const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb);
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
});
const baseUrl = await startAt(app);

// Create a user via the proxy API.
Expand Down Expand Up @@ -217,7 +230,11 @@ describe('CLI proxy tests', () => {

const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb);
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
});
const baseUrl = await startAt(app);

const txRes = await fetch(`${baseUrl}/api/model/$transaction/sequential`, {
Expand Down Expand Up @@ -397,7 +414,11 @@ describe('CLI proxy tests', () => {
// No studioAuthKey — backwards-compatible mode
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb);
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
});
const baseUrl = await startAt(app);

// No signature header — should still work
Expand Down Expand Up @@ -426,9 +447,14 @@ describe('CLI proxy tests', () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
// Pass the key as raw base64 DER — no PEM markers
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY_DER,
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY_DER,
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand All @@ -449,9 +475,14 @@ describe('CLI proxy tests', () => {
// No studioAuthKey option — would normally fall back to env var via run();
// here we verify the middleware still works when the resolved key is provided.
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: process.env['ZENSTACK_STUDIO_AUTH_KEY'],
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: process.env['ZENSTACK_STUDIO_AUTH_KEY'],
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand Down Expand Up @@ -480,9 +511,14 @@ describe('CLI proxy tests', () => {
it('should reject a request whose timestamp is older than the tolerance window', async () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand All @@ -507,9 +543,14 @@ describe('CLI proxy tests', () => {
it('should reject a request whose timestamp is too far in the future', async () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand All @@ -535,9 +576,14 @@ describe('CLI proxy tests', () => {
const client = await createTestClient(zmodel);
// Custom tolerance of 300 seconds
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 300,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 300,
},
});
const baseUrl = await startAt(app);

Expand All @@ -561,9 +607,14 @@ describe('CLI proxy tests', () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
// Very tight tolerance of 5 seconds
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 5,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 5,
},
});
const baseUrl = await startAt(app);

Expand Down Expand Up @@ -597,9 +648,14 @@ describe('CLI proxy tests', () => {
it('should reject a valid signature if it was produced without the Authorization token', async () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand Down Expand Up @@ -627,9 +683,14 @@ describe('CLI proxy tests', () => {
it('should accept a request where the signature covers the Authorization token', async () => {
const client = await createTestClient(zmodel);
const authDb = client.$use(new PolicyPlugin());
const app = createProxyApp(client, client.$schema, authDb, {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
const app = createProxyApp({
client,
schema: client.$schema,
authDb,
auth: {
studioAuthKey: TEST_PUBLIC_KEY,
signatureToleranceSecs: 60,
},
});
const baseUrl = await startAt(app);

Expand Down
2 changes: 1 addition & 1 deletion packages/clients/client-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/client-helpers",
"displayName": "ZenStack Client Helpers",
"description": "Helpers for implementing clients that consume ZenStack's CRUD service",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/clients/fetch-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/fetch-client",
"displayName": "ZenStack Fetch Client",
"description": "Simple fetch-based client for consuming ZenStack's RPC-style CRUD API",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/clients/tanstack-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack TanStack Query Integration",
"description": "TanStack Query Client for consuming ZenStack v3's CRUD service",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/common-helpers",
"displayName": "ZenStack Common Helpers",
"description": "ZenStack Common Helpers",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/config/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/eslint-config",
"version": "3.9.3",
"version": "3.9.4",
"type": "module",
"private": true,
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/config/tsdown-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/tsdown-config",
"version": "3.9.3",
"version": "3.9.4",
"private": true,
"type": "module",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/config/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/typescript-config",
"version": "3.9.3",
"version": "3.9.4",
"private": true,
"license": "MIT"
}
Loading
Loading