From 5267c781881e20928686dd5a7611146940ec6219 Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Tue, 28 Apr 2026 18:06:26 +0300 Subject: [PATCH 1/7] Fix dev service role token Made-with: Cursor --- .../cli/dev/dev-server/routes/functions.ts | 5 +-- packages/cli/tests/cli/dev.spec.ts | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/cli/dev/dev-server/routes/functions.ts b/packages/cli/src/cli/dev/dev-server/routes/functions.ts index 17a534b9..ea0f9733 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/functions.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/functions.ts @@ -19,14 +19,11 @@ export function createFunctionRouter( on: { proxyReq: (proxyReq, req) => { const xAppId = req.headers["x-app-id"]; - const authorization = req.headers.authorization; if (xAppId) { proxyReq.setHeader("Base44-App-Id", xAppId as string); } - if (authorization) { - proxyReq.setHeader("Base44-Service-Authorization", authorization); - } + proxyReq.setHeader("Base44-Service-Authorization", "Bearer dev"); proxyReq.setHeader( "Base44-Api-Url", `${(req as unknown as Request).protocol}://${req.headers.host}`, diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 38d7d731..6a24e611 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -27,7 +27,7 @@ describe("dev command", () => { t.expectResult(result).toSucceed(); }); - it("forwards the service token header from Authorization to local functions", async () => { + it("sets a local service token header for functions", async () => { await t.givenLoggedInWithProject(fixture("full-project")); await writeFile( @@ -52,20 +52,27 @@ describe("dev command", () => { const handle = await t.runLive("dev"); const devServerUrl = await waitForDevServer(handle); - const response = await fetch( - `${devServerUrl}/api/apps/${t.api.appId}/functions/hello`, - { - headers: { - Authorization: "Bearer test-app-token", - "X-App-Id": t.api.appId, - }, - }, - ); + const functionUrl = `${devServerUrl}/api/apps/${t.api.appId}/functions/hello`; + const requestFunction = (headers: HeadersInit) => + fetch(functionUrl, { headers }); - expect(response.status).toBe(200); - await expect(response.json()).resolves.toEqual({ + const anonymousResponse = await requestFunction({ + "X-App-Id": t.api.appId, + }); + expect(anonymousResponse.status).toBe(200); + await expect(anonymousResponse.json()).resolves.toEqual({ + authorization: null, + serviceAuthorization: "Bearer dev", + }); + + const authenticatedResponse = await requestFunction({ + Authorization: "Bearer test-app-token", + "X-App-Id": t.api.appId, + }); + expect(authenticatedResponse.status).toBe(200); + await expect(authenticatedResponse.json()).resolves.toEqual({ authorization: "Bearer test-app-token", - serviceAuthorization: "Bearer test-app-token", + serviceAuthorization: "Bearer dev", }); const result = await handle.stop(); From d5c06696f2849ea8d40071ff720a34b31676be1d Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Tue, 28 Apr 2026 18:09:21 +0300 Subject: [PATCH 2/7] Set dev function authorization header Made-with: Cursor --- packages/cli/src/cli/dev/dev-server/routes/functions.ts | 8 +++++++- packages/cli/tests/cli/dev.spec.ts | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/cli/dev/dev-server/routes/functions.ts b/packages/cli/src/cli/dev/dev-server/routes/functions.ts index ea0f9733..f9041b2a 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/functions.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/functions.ts @@ -6,6 +6,8 @@ import { createProxyMiddleware } from "http-proxy-middleware"; import type { DevLogger } from "@/cli/dev/createDevLogger.js"; import type { FunctionManager } from "@/cli/dev/dev-server/function-manager.js"; +const LOCAL_DEV_AUTHORIZATION = "Bearer dev"; + export function createFunctionRouter( manager: FunctionManager, logger: DevLogger, @@ -23,7 +25,11 @@ export function createFunctionRouter( if (xAppId) { proxyReq.setHeader("Base44-App-Id", xAppId as string); } - proxyReq.setHeader("Base44-Service-Authorization", "Bearer dev"); + proxyReq.setHeader("Authorization", LOCAL_DEV_AUTHORIZATION); + proxyReq.setHeader( + "Base44-Service-Authorization", + LOCAL_DEV_AUTHORIZATION, + ); proxyReq.setHeader( "Base44-Api-Url", `${(req as unknown as Request).protocol}://${req.headers.host}`, diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 6a24e611..67550c69 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -27,7 +27,7 @@ describe("dev command", () => { t.expectResult(result).toSucceed(); }); - it("sets a local service token header for functions", async () => { + it("sets local dev authorization headers for functions", async () => { await t.givenLoggedInWithProject(fixture("full-project")); await writeFile( @@ -61,7 +61,7 @@ describe("dev command", () => { }); expect(anonymousResponse.status).toBe(200); await expect(anonymousResponse.json()).resolves.toEqual({ - authorization: null, + authorization: "Bearer dev", serviceAuthorization: "Bearer dev", }); @@ -71,7 +71,7 @@ describe("dev command", () => { }); expect(authenticatedResponse.status).toBe(200); await expect(authenticatedResponse.json()).resolves.toEqual({ - authorization: "Bearer test-app-token", + authorization: "Bearer dev", serviceAuthorization: "Bearer dev", }); From 8fe717d1af57026a395bc799878a0c38f4eb455b Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Tue, 28 Apr 2026 18:10:40 +0300 Subject: [PATCH 3/7] Rename dev service token constant Made-with: Cursor --- packages/cli/src/cli/dev/dev-server/routes/functions.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/cli/dev/dev-server/routes/functions.ts b/packages/cli/src/cli/dev/dev-server/routes/functions.ts index f9041b2a..ec0f2be7 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/functions.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/functions.ts @@ -6,7 +6,7 @@ import { createProxyMiddleware } from "http-proxy-middleware"; import type { DevLogger } from "@/cli/dev/createDevLogger.js"; import type { FunctionManager } from "@/cli/dev/dev-server/function-manager.js"; -const LOCAL_DEV_AUTHORIZATION = "Bearer dev"; +const LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN = "Bearer dev"; export function createFunctionRouter( manager: FunctionManager, @@ -25,10 +25,13 @@ export function createFunctionRouter( if (xAppId) { proxyReq.setHeader("Base44-App-Id", xAppId as string); } - proxyReq.setHeader("Authorization", LOCAL_DEV_AUTHORIZATION); + proxyReq.setHeader( + "Authorization", + LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN, + ); proxyReq.setHeader( "Base44-Service-Authorization", - LOCAL_DEV_AUTHORIZATION, + LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN, ); proxyReq.setHeader( "Base44-Api-Url", From 9347c030bf8b91ab9e4dc29eebe6a500b4a5efbb Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Tue, 28 Apr 2026 18:18:00 +0300 Subject: [PATCH 4/7] Scope dev service token header Made-with: Cursor --- packages/cli/src/cli/dev/dev-server/routes/functions.ts | 4 ---- packages/cli/tests/cli/dev.spec.ts | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/cli/dev/dev-server/routes/functions.ts b/packages/cli/src/cli/dev/dev-server/routes/functions.ts index ec0f2be7..cded9557 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/functions.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/functions.ts @@ -25,10 +25,6 @@ export function createFunctionRouter( if (xAppId) { proxyReq.setHeader("Base44-App-Id", xAppId as string); } - proxyReq.setHeader( - "Authorization", - LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN, - ); proxyReq.setHeader( "Base44-Service-Authorization", LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN, diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 67550c69..6bacbbe5 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -27,7 +27,7 @@ describe("dev command", () => { t.expectResult(result).toSucceed(); }); - it("sets local dev authorization headers for functions", async () => { + it("sets a local service token header for functions", async () => { await t.givenLoggedInWithProject(fixture("full-project")); await writeFile( @@ -53,7 +53,7 @@ describe("dev command", () => { const devServerUrl = await waitForDevServer(handle); const functionUrl = `${devServerUrl}/api/apps/${t.api.appId}/functions/hello`; - const requestFunction = (headers: HeadersInit) => + const requestFunction = (headers: Record) => fetch(functionUrl, { headers }); const anonymousResponse = await requestFunction({ @@ -61,7 +61,7 @@ describe("dev command", () => { }); expect(anonymousResponse.status).toBe(200); await expect(anonymousResponse.json()).resolves.toEqual({ - authorization: "Bearer dev", + authorization: null, serviceAuthorization: "Bearer dev", }); @@ -71,7 +71,7 @@ describe("dev command", () => { }); expect(authenticatedResponse.status).toBe(200); await expect(authenticatedResponse.json()).resolves.toEqual({ - authorization: "Bearer dev", + authorization: "Bearer test-app-token", serviceAuthorization: "Bearer dev", }); From 83a7269dbd4ae513e222854cea77308f745369cb Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Wed, 29 Apr 2026 14:55:23 +0300 Subject: [PATCH 5/7] Use valid dev service JWT --- .../cli/src/cli/dev/dev-server/auth-token.ts | 9 +++++++++ .../cli/dev/dev-server/routes/auth-router.ts | 9 +-------- .../cli/dev/dev-server/routes/functions.ts | 5 ++++- packages/cli/tests/cli/dev.spec.ts | 20 +++++++++++++++---- 4 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 packages/cli/src/cli/dev/dev-server/auth-token.ts diff --git a/packages/cli/src/cli/dev/dev-server/auth-token.ts b/packages/cli/src/cli/dev/dev-server/auth-token.ts new file mode 100644 index 00000000..ac4ae56c --- /dev/null +++ b/packages/cli/src/cli/dev/dev-server/auth-token.ts @@ -0,0 +1,9 @@ +import jwt from "jsonwebtoken"; + +const LOCAL_DEV_SECRET = "LOCAL_DEV_SECRET"; + +export const createJwtToken = (email: string) => { + return jwt.sign({ email, sub: email }, LOCAL_DEV_SECRET, { + expiresIn: "360d", + }); +}; diff --git a/packages/cli/src/cli/dev/dev-server/routes/auth-router.ts b/packages/cli/src/cli/dev/dev-server/routes/auth-router.ts index c62975c7..f6e3c0ea 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/auth-router.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/auth-router.ts @@ -1,11 +1,11 @@ import { randomInt } from "node:crypto"; import type { Request } from "express"; import { json, Router } from "express"; -import jwt from "jsonwebtoken"; import { nanoid } from "nanoid"; import * as z from "zod"; import { theme } from "@/cli/utils/theme.js"; import type { DevLogger } from "../../createDevLogger.js"; +import { createJwtToken } from "../auth-token.js"; import { type Database, PRIVATE_USER_COLLECTION, @@ -13,19 +13,12 @@ import { } from "../db/database.js"; import { getNowISOTimestamp } from "../utils.js"; -const LOCAL_DEV_SECRET = "LOCAL_DEV_SECRET"; const TEN_MINUTES = 10 * 60 * 1000; const generateCode = () => { return randomInt(100000, 1000000).toString(); }; -const createJwtToken = (email: string) => { - return jwt.sign({ sub: email }, LOCAL_DEV_SECRET, { - expiresIn: "360d", - }); -}; - const LoginBody = z.object({ email: z.email(), password: z.string() }); const VerifyOtpBody = z.object({ email: z.email(), otp_code: z.string() }); diff --git a/packages/cli/src/cli/dev/dev-server/routes/functions.ts b/packages/cli/src/cli/dev/dev-server/routes/functions.ts index cded9557..09bffdc0 100644 --- a/packages/cli/src/cli/dev/dev-server/routes/functions.ts +++ b/packages/cli/src/cli/dev/dev-server/routes/functions.ts @@ -4,9 +4,12 @@ import type { Request, Response } from "express"; import { Router } from "express"; import { createProxyMiddleware } from "http-proxy-middleware"; import type { DevLogger } from "@/cli/dev/createDevLogger.js"; +import { createJwtToken } from "@/cli/dev/dev-server/auth-token.js"; import type { FunctionManager } from "@/cli/dev/dev-server/function-manager.js"; -const LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN = "Bearer dev"; +const LOCAL_DEV_SERVICE_AUTHORIZATION_TOKEN = `Bearer ${createJwtToken( + "server@server.com", +)}`; export function createFunctionRouter( manager: FunctionManager, diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 6bacbbe5..7135c3f1 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -1,5 +1,6 @@ import { writeFile } from "node:fs/promises"; import { join } from "node:path"; +import jwt from "jsonwebtoken"; import { outdent } from "outdent"; import { describe, expect, it } from "vitest"; import { waitForDevServer } from "./testkit/dev-utils.js"; @@ -60,9 +61,19 @@ describe("dev command", () => { "X-App-Id": t.api.appId, }); expect(anonymousResponse.status).toBe(200); - await expect(anonymousResponse.json()).resolves.toEqual({ + const anonymousResult = await anonymousResponse.json(); + expect(anonymousResult.authorization).toBeNull(); + expect(anonymousResult.serviceAuthorization).toMatch(/^Bearer .+/); + expect( + jwt.decode(anonymousResult.serviceAuthorization.replace("Bearer ", "")), + ).toMatchObject({ + email: "server@server.com", + sub: "server@server.com", + }); + + expect(anonymousResult).toEqual({ authorization: null, - serviceAuthorization: "Bearer dev", + serviceAuthorization: anonymousResult.serviceAuthorization, }); const authenticatedResponse = await requestFunction({ @@ -70,9 +81,10 @@ describe("dev command", () => { "X-App-Id": t.api.appId, }); expect(authenticatedResponse.status).toBe(200); - await expect(authenticatedResponse.json()).resolves.toEqual({ + const authenticatedResult = await authenticatedResponse.json(); + expect(authenticatedResult).toEqual({ authorization: "Bearer test-app-token", - serviceAuthorization: "Bearer dev", + serviceAuthorization: anonymousResult.serviceAuthorization, }); const result = await handle.stop(); From a66f0319ed9dc93d5147602b6ac18dc3e640a09b Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Wed, 29 Apr 2026 15:07:52 +0300 Subject: [PATCH 6/7] Fix dev service JWT typing --- packages/cli/src/cli/dev/dev-server/auth-token.ts | 2 +- packages/cli/tests/cli/dev.spec.ts | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/cli/dev/dev-server/auth-token.ts b/packages/cli/src/cli/dev/dev-server/auth-token.ts index ac4ae56c..f5c33211 100644 --- a/packages/cli/src/cli/dev/dev-server/auth-token.ts +++ b/packages/cli/src/cli/dev/dev-server/auth-token.ts @@ -2,7 +2,7 @@ import jwt from "jsonwebtoken"; const LOCAL_DEV_SECRET = "LOCAL_DEV_SECRET"; -export const createJwtToken = (email: string) => { +export const createJwtToken = (email: string): string => { return jwt.sign({ email, sub: email }, LOCAL_DEV_SECRET, { expiresIn: "360d", }); diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 7135c3f1..98e4dd95 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -1,6 +1,6 @@ import { writeFile } from "node:fs/promises"; import { join } from "node:path"; -import jwt from "jsonwebtoken"; +import jwt, { type JwtPayload } from "jsonwebtoken"; import { outdent } from "outdent"; import { describe, expect, it } from "vitest"; import { waitForDevServer } from "./testkit/dev-utils.js"; @@ -64,12 +64,11 @@ describe("dev command", () => { const anonymousResult = await anonymousResponse.json(); expect(anonymousResult.authorization).toBeNull(); expect(anonymousResult.serviceAuthorization).toMatch(/^Bearer .+/); - expect( - jwt.decode(anonymousResult.serviceAuthorization.replace("Bearer ", "")), - ).toMatchObject({ - email: "server@server.com", - sub: "server@server.com", - }); + const serviceTokenPayload = jwt.decode( + anonymousResult.serviceAuthorization.replace("Bearer ", ""), + ) as JwtPayload | null; + expect(serviceTokenPayload?.email).toBe("server@server.com"); + expect(serviceTokenPayload?.sub).toBe("server@server.com"); expect(anonymousResult).toEqual({ authorization: null, From d96c19cdacbd5f4666263e1b9916c74124942a6d Mon Sep 17 00:00:00 2001 From: Maksym Anisimov Date: Wed, 29 Apr 2026 15:11:29 +0300 Subject: [PATCH 7/7] Type dev function header test response --- packages/cli/tests/cli/dev.spec.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 98e4dd95..675dd748 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -6,6 +6,11 @@ import { describe, expect, it } from "vitest"; import { waitForDevServer } from "./testkit/dev-utils.js"; import { fixture, setupCLITests } from "./testkit/index.js"; +type FunctionHeaderResponse = { + authorization: string | null; + serviceAuthorization: string; +}; + describe("dev command", () => { const t = setupCLITests(); @@ -61,7 +66,8 @@ describe("dev command", () => { "X-App-Id": t.api.appId, }); expect(anonymousResponse.status).toBe(200); - const anonymousResult = await anonymousResponse.json(); + const anonymousResult = + (await anonymousResponse.json()) as FunctionHeaderResponse; expect(anonymousResult.authorization).toBeNull(); expect(anonymousResult.serviceAuthorization).toMatch(/^Bearer .+/); const serviceTokenPayload = jwt.decode( @@ -80,7 +86,8 @@ describe("dev command", () => { "X-App-Id": t.api.appId, }); expect(authenticatedResponse.status).toBe(200); - const authenticatedResult = await authenticatedResponse.json(); + const authenticatedResult = + (await authenticatedResponse.json()) as FunctionHeaderResponse; expect(authenticatedResult).toEqual({ authorization: "Bearer test-app-token", serviceAuthorization: anonymousResult.serviceAuthorization,