From 84180e3a7e494a8078bb23594a8234d1161941e2 Mon Sep 17 00:00:00 2001 From: stefanbaxter Date: Mon, 20 Apr 2026 16:13:15 +0000 Subject: [PATCH] fix(cubejs): match checkSqlAuth callback signature to Cube.js v1.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cube.js v1.6 invokes checkSqlAuth as (request, user, password) — three positional args — see @cubejs-backend/api-gateway/dist/src/sql-server.js:291,105. Our implementation declared (_, user) and did: password = typeof user === "string" ? user : user?.password username = typeof user === "string" ? _ : user?.username With the v1.6 wire server, user arrives as a plain string (the Postgres username), so the code took the username as the password AND used the request metadata object as the username. findSqlCredentials then received the {protocol, method, apiType} object, and Hasura rejected the query with: parsing Text failed, expected String, but encountered Object path: $.selectionSet.sql_credentials.args.where.username._eq Every SQL API login failed before any password comparison ran (reproduced via `psql -U -h ` → 28P01). Fix: match the documented v1.6 signature and keep a defensive branch for the legacy object-shape call. Also reject non-string username early so the Hasura GraphQL layer cannot receive a non-string variable. Co-Authored-By: Claude Opus 4.7 (1M context) --- services/cubejs/src/utils/checkSqlAuth.js | 42 ++++++++++++++++------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/services/cubejs/src/utils/checkSqlAuth.js b/services/cubejs/src/utils/checkSqlAuth.js index 31a2a973..18a8f786 100644 --- a/services/cubejs/src/utils/checkSqlAuth.js +++ b/services/cubejs/src/utils/checkSqlAuth.js @@ -36,19 +36,33 @@ const buildSqlSecurityContext = (sqlCredentials) => { /** * Check SQL authentication for a user. + * + * Cube.js v1.6 invokes this callback as `checkSqlAuth(request, user, password)` + * (see @cubejs-backend/api-gateway/dist/src/sql-server.js — the callback is + * wrapped with three positional args). Earlier builds passed a single object + * `{ username, password }`; the defensive branches below keep backward- + * compatibility with that older shape. + * * Supports two authentication methods: - * 1. WorkOS JWT as password (new): password is a JWT, username is datasource ID - * 2. Legacy sql_credentials lookup (existing): username/password from sql_credentials table + * 1. WorkOS / FraiOS JWT as password: password is a JWT, username is the datasource ID + * 2. Legacy sql_credentials lookup: username/password from the sql_credentials table * - * @param {null} _ - Unused parameter. - * @param {Object} user - The user object with username and password. - * @returns {Promise} - Resolves to { password, securityContext } + * @param {Object} request - Cube.js SQL request metadata (protocol, method, apiType) + * @param {string|Object} userArg - Username string (v1.6+) or legacy { username, password } object + * @param {string} [passwordArg] - Password string (v1.6+); absent in legacy object-shape calls + * @returns {Promise<{ password: string, securityContext: Object }>} */ -const checkSqlAuth = async (_, user) => { - const password = typeof user === "string" ? user : user?.password; - const username = typeof user === "string" ? _ : user?.username; - - // Detect if password looks like a JWT (WorkOS RS256) +const checkSqlAuth = async (request, userArg, passwordArg) => { + // Resolve the two shapes Cube has used for this callback: + // new: (request, username: string, password: string) + // legacy: (_req, { username, password }) + const username = + typeof userArg === "string" ? userArg : userArg?.username; + const password = + passwordArg ?? + (typeof userArg === "string" ? undefined : userArg?.password); + + // Detect if password looks like a JWT (WorkOS RS256 / FraiOS HS256) if (password && password.includes(".") && password.split(".").length === 3) { const tokenType = detectTokenType(password); @@ -134,8 +148,12 @@ const checkSqlAuth = async (_, user) => { } } - // Legacy sql_credentials path (unchanged) - const sqlCredentials = await findSqlCredentials(username || user); + // Legacy sql_credentials path — lookup by the plaintext username. + // Cube.js compares the supplied password against `password` in the return value. + if (!username || typeof username !== "string") { + throw new Error("Incorrect user name or password"); + } + const sqlCredentials = await findSqlCredentials(username); return { password: sqlCredentials?.password,