feat(cli): Add support for federated auth database integrations#327
feat(cli): Add support for federated auth database integrations#327tkislan wants to merge 11 commits intotk/cli-integrations-add-edit-fullfrom
Conversation
📝 WalkthroughWalkthroughThis PR adds federated authentication to the CLI: a new Sequence DiagramsequenceDiagram
participant User
participant CLI as "CLI (integrations auth)"
participant AuthCmd as "auth-integration.ts"
participant OAuthServer as "oauth-local-server.ts"
participant Browser
participant OAuthProvider
participant TokenStore as "federated-auth-tokens.ts"
participant Integrations as "integrations YAML"
User->>CLI: run `integrations auth [id]`
CLI->>AuthCmd: invoke action handler
AuthCmd->>Integrations: load integrations file
AuthCmd->>AuthCmd: select target federated integration (id or prompt)
AuthCmd->>OAuthServer: runOAuthFlow(params)
OAuthServer->>Browser: open /start URL
Browser->>OAuthProvider: auth request (redirect)
OAuthProvider->>Browser: redirect back with code
Browser->>OAuthServer: /callback?code=...
OAuthServer->>OAuthProvider: exchange code -> tokens
OAuthProvider-->>OAuthServer: access_token, refresh_token, expires_in
OAuthServer->>TokenStore: saveTokenForIntegration(tokenEntry)
TokenStore->>TokenStore: write federated-auth-tokens.yaml
OAuthServer-->>AuthCmd: return FederatedAuthTokenEntry
AuthCmd->>User: log success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## tk/cli-integrations-add-edit-full #327 +/- ##
=====================================================================
- Coverage 83.75% 82.18% -1.58%
=====================================================================
Files 140 146 +6
Lines 7707 7986 +279
Branches 2145 2140 -5
=====================================================================
+ Hits 6455 6563 +108
- Misses 1252 1423 +171 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/database-integrations/README.md (1)
160-162:⚠️ Potential issue | 🟡 MinorUpdate example to await the async API.
The sample still uses synchronous destructuring even though the function now returns a Promise.
💡 Suggested fix
-const { envVars } = getEnvironmentVariablesForIntegrations(integrations, { +const { envVars } = await getEnvironmentVariablesForIntegrations(integrations, { projectRootDirectory: "/path/to/project", });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/database-integrations/README.md` around lines 160 - 162, The example calls getEnvironmentVariablesForIntegrations synchronously but that function now returns a Promise; update the example to await the async API by awaiting the call (or using .then) before destructuring the returned object so envVars is obtained from the resolved value of getEnvironmentVariablesForIntegrations(integrations, { projectRootDirectory: "/path/to/project" }); reference getEnvironmentVariablesForIntegrations, envVars, integrations, and projectRootDirectory when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/integrations/auth-integration.ts`:
- Around line 145-149: The catch around resolveEnvVarRefsFromMap silently
swallows errors causing later cryptic validation failures; update the catch in
the block that calls resolveEnvVarRefsFromMap(integrationRawJson, envVars) so it
either re-throws a new Error with a clear message referencing the env:
resolution failure (including the original error) or emits a user-visible
warning via the CLI logger (e.g., logger.warn or console.error) that explains
env var resolution failed for integrationRawJson and advises the user to check
environment variables; ensure you reference the original error when re-throwing
or logging so the root cause is preserved.
- Line 1: The file imports debug from the wrong module ("node:console") which
bypasses the CLI's --quiet/--debug flags; replace the import statement `import {
debug } from 'node:console'` with an import from the CLI output wrapper (e.g.
`import { debug } from '../../output'`) and ensure all uses of the debug
identifier (the call sites in this module) now call the exported debug from
../../output so the CLI flags are respected.
In `@packages/cli/src/federated-auth/federated-auth-tokens-schema.test.ts`:
- Around line 8-69: The tests miss edge cases for invalid expiresAt and
empty-string credentials; update the
federated-auth/federated-auth-tokens-schema.test.ts to add tests that assert
federatedAuthTokenEntrySchema rejects entries with a non-ISO or invalid
expiresAt (e.g., 'not-a-date' or malformed timestamp) and rejects entries where
accessToken or refreshToken are empty strings (''), and similarly add a case to
baseTokensFileSchema to ensure tokens containing such invalid entries are
rejected; use the existing FederatedAuthTokenEntry shape and the
federatedAuthTokenEntrySchema and baseTokensFileSchema identifiers to locate
where to add these test cases.
In `@packages/cli/src/federated-auth/federated-auth-tokens-schema.ts`:
- Around line 10-14: The schema federatedAuthTokenEntrySchema currently allows
empty strings for integrationId, accessToken, refreshToken (and for expiresAt
when present); update those fields to disallow empty strings by using Zod's
nonempty (or .min(1)) validator: change integrationId, accessToken, and
refreshToken to z.string().nonempty(), and make expiresAt
z.string().datetime().nonempty().optional() so an empty string does not pass
validation when provided.
In `@packages/cli/src/federated-auth/federated-auth-tokens.test.ts`:
- Around line 15-89: Add two tests to federated-auth-tokens.test.ts that
exercise readTokensFile's error paths: one "malformed YAML" test that writes
syntactically invalid YAML to a test file (use fs.writeFile) then calls
readTokensFile(testPath) and asserts result.tokens is [] and result.issues
contains a parse/error entry; and one "invalid-entry schema" test that writes
YAML with entries missing required fields or wrong types (e.g., an object
without integrationId or with non-string accessToken), then calls
readTokensFile(testPath) and asserts result.tokens excludes the invalid entries
and result.issues lists the schema validation problems. Use the existing testDir
pattern and reference the readTokensFile (and writeTokensFile if helpful)
functions to locate the logic under test.
In `@packages/cli/src/federated-auth/federated-auth-tokens.ts`:
- Around line 132-143: saveTokenForIntegration currently calls readTokensFile
which returns only valid tokens, so when you upsert via saveTokenForIntegration
(function) any malformed/raw entries from the original file are dropped
silently; change saveTokenForIntegration to preserve non-parsed/raw entries or
at minimum warn the user when malformed entries exist: modify readTokensFile (or
call a new reader) to return both parsed tokens and rawEntries, then in
saveTokenForIntegration merge the updated/inserted FederatedAuthTokenEntry into
the parsed tokens list while reassembling the final file contents to include
untouched rawEntries (or log a warning if rawEntries are present) before calling
writeTokensFile; reference tokens, entry, writeTokensFile and
getDefaultTokensFilePath when implementing this behavior.
In `@packages/cli/src/federated-auth/oauth-local-server.ts`:
- Around line 125-134: The current app.listen(...) call that creates server does
not handle bind errors (e.g., EADDRINUSE) so failures are ambiguous; attach an
explicit error handler to the returned server from app.listen (the server
variable) and in that handler check err.code (e.g., 'EADDRINUSE') to log a clear
message including OAUTH_PORT and startURL and exit the process (or suggest an
alternative port) rather than letting the process crash silently; ensure any
cleanup (closing server) is done as needed and keep the existing browser-open
logic (open(startURL)) in the listen callback.
- Around line 114-115: The HTML response embeds err.name and err.message
unescaped (the html variable), which allows XSS; update the OAuth error handler
to HTML-escape these values before interpolation (e.g., implement/use an
escapeHtml helper that replaces &, <, >, ", ' and /) and use escapedName =
escapeHtml(err.name) and escapedMessage = escapeHtml(err.message) when building
html, or alternatively render the error as plain text/JSON via
res.status(400).send to avoid HTML interpolation.
- Around line 107-112: The Express app is missing Passport initialization; call
passport.initialize() on the Express instance before any route handlers to
ensure Passport sets up req._passport even when using session: false. Add
app.use(passport.initialize()) immediately after creating app (before the routes
that reference START_PATH and CALLBACK_PATH and before the
passport.authenticate(...) calls) so passport.authenticate('trino-oauth', {
session: false }) runs with Passport properly initialized.
In `@packages/cli/src/federated-auth/token-refresh.test.ts`:
- Around line 47-126: Add unit tests in
packages/cli/src/federated-auth/token-refresh.test.ts to cover the error paths
of refreshAccessToken: simulate token endpoint responses for (1) OAuth
"invalid_grant" body (ok: true but error payload), (2) OAuth "invalid_client"
body, (3) non-OK HTTP response (ok: false, status/text), and (4) invalid JSON
(mock fetch.json to throw). For each case call refreshAccessToken with a valid
FederatedAuthTokenEntry and DatabaseIntegrationConfig, stub global fetch with
vi.stubGlobal/vi.fn() (as in the existing test), assert that refreshAccessToken
rejects with an appropriate error message or type, and clean up globals with
vi.unstubAllGlobals(); keep test structure consistent with the existing "should
call token endpoint..." test and use the same tokenUrl/clientId/clientSecret
setup.
- Around line 96-125: The fetch stub created with vi.stubGlobal('fetch') in the
test around mockFetch can leak if assertions fail; wrap the test execution and
assertions in a try/finally so vi.unstubAllGlobals() always runs. Locate the
test in token-refresh.test.ts that calls refreshAccessToken(token, integration,
'/tmp/test-tokens.yaml') and replace the current linear setup/assert/cleanup
with a try { ... assertions including
expect(mockFetch).toHaveBeenCalledWith(...); ... } finally {
vi.unstubAllGlobals(); } to guarantee the global stub is removed even on
failure.
In `@packages/cli/src/federated-auth/token-refresh.ts`:
- Around line 75-77: The code computes expiresAt using responseBody.expires_in
without validating it; update the logic around responseBody/expires_in
(affecting variables newRefreshToken, expiresIn, expiresAt) to first coerce and
validate expires_in is a finite positive number (e.g.,
Number(responseBody.expires_in)), and if it is NaN, <=0, or not finite, fall
back to the default (3600) before computing expiresAt; then compute expiresAt
from the validated expiresIn so negative/invalid values cannot produce bad
timestamps.
- Around line 42-46: The fetch call that posts to tokenUrl (using
getOAuth2ClientBasicAuthHeaders and tokenEntry.refreshToken) needs a timeout to
avoid hanging; wrap the fetch in an AbortController, pass controller.signal into
fetch, start a timer (e.g., 5–10s) that calls controller.abort() on expiry, and
clear the timer after fetch completes; also catch/handle the abort error path so
token refresh fails fast and surfaces a clear timeout error instead of hanging.
- Around line 23-27: In refreshAccessToken ensure you validate that the
tokenEntry is intended for the provided integration before using integration
metadata: check a stable identifier (e.g. tokenEntry.integrationId or
tokenEntry.id) matches integration.id (or integration.integrationId) at the
start of refreshAccessToken, and if it does not match, abort the flow (throw an
error or return a rejected Promise) rather than sending tokenEntry.refreshToken
to the integration endpoint; update any callers/tests to handle the new error
path if needed.
- Around line 93-99: The isTokenExpired function can compute expiryTime as NaN
for invalid expiresAt values, causing the subsequent comparison to incorrectly
mark an invalid timestamp as not expired; update isTokenExpired to detect
invalid dates (e.g., check Number.isNaN(expiryTime) or isNaN(new
Date(expiresAt).getTime())) and return true when expiryTime is invalid so any
malformed or non-parsable expiresAt is treated as expired; keep the existing
bufferSeconds logic and references to tokenEntry.expiresAt and expiryTime when
implementing this guard.
In `@packages/database-integrations/src/database-integration-env-vars.ts`:
- Around line 73-93: The federated detection and unsafe cast are causing Trino
OAuth configs stored only in integration.metadata to be missed and bypassing
runtime validation; update the logic in the block around isFederated and the
call to getValidFederatedAuthToken so that you check both
integration.federated_auth_method and integration.metadata.authMethod (or
equivalent) for 'trino-oauth' instead of relying solely on the top-level field,
and validate metadata at runtime before calling buildTrinoOAuthSqlAlchemyInput:
fetch metadata as DatabaseIntegrationMetadataByType['trino'], assert/guard that
metadata.authMethod === TrinoAuthMethods.Oauth, then call
buildTrinoOAuthSqlAlchemyInput with that validated metadata and push the env var
via getSqlEnvVarName only when validation succeeds.
In `@packages/database-integrations/src/federated-auth-tokens.ts`:
- Around line 171-177: The current try/catch around
refreshAccessToken(tokenEntry, integration, tokensFilePath) swallows failures
and returns undefined; instead let refresh errors propagate (or rethrow with
context) so callers can handle/report them. Remove the empty catch block (or
change it to catch (err) { throw new Error(`refreshAccessToken failed for
${integration}: ${err}`); }) and keep finalToken assignment as before; keep
references to tokenEntry, isTokenExpired, refreshAccessToken, finalToken and
tokensFilePath to locate the change.
- Around line 107-114: The token refresh fetch to tokenUrl should use an
AbortController with a timeout to avoid hanging: create an AbortController, pass
controller.signal to fetch, start a setTimeout (e.g., 10s) that calls
controller.abort(), and clear the timer when the response arrives; ensure fetch
rejections (including AbortError) are caught and the error is thrown/rejected up
(do not suppress) so callers see failures. Update the refresh logic that uses
tokenEntry.refreshToken and the fetch call to propagate errors instead of
swallowing them so token refresh failures are visible to callers.
---
Outside diff comments:
In `@packages/database-integrations/README.md`:
- Around line 160-162: The example calls getEnvironmentVariablesForIntegrations
synchronously but that function now returns a Promise; update the example to
await the async API by awaiting the call (or using .then) before destructuring
the returned object so envVars is obtained from the resolved value of
getEnvironmentVariablesForIntegrations(integrations, { projectRootDirectory:
"/path/to/project" }); reference getEnvironmentVariablesForIntegrations,
envVars, integrations, and projectRootDirectory when making the change.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
packages/cli/package.jsonpackages/cli/src/cli.tspackages/cli/src/commands/integrations/auth-integration.tspackages/cli/src/commands/integrations/edit-integration.tspackages/cli/src/commands/run.tspackages/cli/src/constants.tspackages/cli/src/federated-auth/federated-auth-tokens-schema.test.tspackages/cli/src/federated-auth/federated-auth-tokens-schema.tspackages/cli/src/federated-auth/federated-auth-tokens.test.tspackages/cli/src/federated-auth/federated-auth-tokens.tspackages/cli/src/federated-auth/oauth-local-server.tspackages/cli/src/federated-auth/token-refresh.test.tspackages/cli/src/federated-auth/token-refresh.tspackages/database-integrations/README.mdpackages/database-integrations/package.jsonpackages/database-integrations/src/database-integration-env-vars.test.tspackages/database-integrations/src/database-integration-env-vars.tspackages/database-integrations/src/federated-auth-tokens.tspackages/database-integrations/src/index.ts
| describe('federatedAuthTokenEntrySchema', () => { | ||
| it('should accept valid token entry', () => { | ||
| const valid: FederatedAuthTokenEntry = { | ||
| integrationId: 'abc-123', | ||
| accessToken: 'eyJhbG...', | ||
| refreshToken: 'dGhpcyBpcyBhIHJl...', | ||
| expiresAt: '2026-02-25T15:30:00.000Z', | ||
| } | ||
| expect(federatedAuthTokenEntrySchema.parse(valid)).toEqual(valid) | ||
| }) | ||
|
|
||
| it('should accept entry without expiresAt', () => { | ||
| const valid = { | ||
| integrationId: 'abc-123', | ||
| accessToken: 'token', | ||
| refreshToken: 'refresh', | ||
| } | ||
| expect(federatedAuthTokenEntrySchema.parse(valid)).toEqual(valid) | ||
| }) | ||
|
|
||
| it('should reject missing integrationId', () => { | ||
| const invalid = { | ||
| accessToken: 'token', | ||
| refreshToken: 'refresh', | ||
| } | ||
| expect(() => federatedAuthTokenEntrySchema.parse(invalid)).toThrow() | ||
| }) | ||
|
|
||
| it('should reject missing accessToken', () => { | ||
| const invalid = { | ||
| integrationId: 'abc', | ||
| refreshToken: 'refresh', | ||
| } | ||
| expect(() => federatedAuthTokenEntrySchema.parse(invalid)).toThrow() | ||
| }) | ||
|
|
||
| it('should reject missing refreshToken', () => { | ||
| const invalid = { | ||
| integrationId: 'abc', | ||
| accessToken: 'token', | ||
| } | ||
| expect(() => federatedAuthTokenEntrySchema.parse(invalid)).toThrow() | ||
| }) | ||
| }) | ||
|
|
||
| describe('baseTokensFileSchema', () => { | ||
| it('should accept empty tokens array', () => { | ||
| expect(baseTokensFileSchema.parse({ tokens: [] })).toEqual({ tokens: [] }) | ||
| }) | ||
|
|
||
| it('should accept missing tokens (defaults to [])', () => { | ||
| expect(baseTokensFileSchema.parse({})).toEqual({ tokens: [] }) | ||
| }) | ||
|
|
||
| it('should accept array of records', () => { | ||
| const parsed = baseTokensFileSchema.parse({ | ||
| tokens: [{ integrationId: 'x', accessToken: 'a', refreshToken: 'r' }], | ||
| }) | ||
| expect(parsed.tokens).toHaveLength(1) | ||
| expect(parsed.tokens[0]).toEqual({ integrationId: 'x', accessToken: 'a', refreshToken: 'r' }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Add edge-case rejection tests for token schema.
Current suite misses invalid expiresAt and empty-string credential cases.
Suggested additions
describe('federatedAuthTokenEntrySchema', () => {
+ it('should reject invalid expiresAt format', () => {
+ const invalid = {
+ integrationId: 'abc-123',
+ accessToken: 'token',
+ refreshToken: 'refresh',
+ expiresAt: 'not-a-date',
+ }
+ expect(() => federatedAuthTokenEntrySchema.parse(invalid)).toThrow()
+ })
+
+ it('should reject empty required string fields', () => {
+ const invalid = {
+ integrationId: '',
+ accessToken: '',
+ refreshToken: '',
+ }
+ expect(() => federatedAuthTokenEntrySchema.parse(invalid)).toThrow()
+ })
+
it('should accept valid token entry', () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/federated-auth/federated-auth-tokens-schema.test.ts` around
lines 8 - 69, The tests miss edge cases for invalid expiresAt and empty-string
credentials; update the federated-auth/federated-auth-tokens-schema.test.ts to
add tests that assert federatedAuthTokenEntrySchema rejects entries with a
non-ISO or invalid expiresAt (e.g., 'not-a-date' or malformed timestamp) and
rejects entries where accessToken or refreshToken are empty strings (''), and
similarly add a case to baseTokensFileSchema to ensure tokens containing such
invalid entries are rejected; use the existing FederatedAuthTokenEntry shape and
the federatedAuthTokenEntrySchema and baseTokensFileSchema identifiers to locate
where to add these test cases.
| const newRefreshToken = responseBody.refresh_token ?? tokenEntry.refreshToken | ||
| const expiresIn = responseBody.expires_in ?? 3600 | ||
| const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString() |
There was a problem hiding this comment.
Validate expires_in before computing expiry timestamp.
Line 76 trusts expires_in as a valid positive number. Invalid/negative values can produce bad expiry behavior.
Suggested fix
- const expiresIn = responseBody.expires_in ?? 3600
+ const expiresIn =
+ typeof responseBody.expires_in === 'number' &&
+ Number.isFinite(responseBody.expires_in) &&
+ responseBody.expires_in > 0
+ ? responseBody.expires_in
+ : 3600
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const newRefreshToken = responseBody.refresh_token ?? tokenEntry.refreshToken | |
| const expiresIn = responseBody.expires_in ?? 3600 | |
| const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString() | |
| const newRefreshToken = responseBody.refresh_token ?? tokenEntry.refreshToken | |
| const expiresIn = | |
| typeof responseBody.expires_in === 'number' && | |
| Number.isFinite(responseBody.expires_in) && | |
| responseBody.expires_in > 0 | |
| ? responseBody.expires_in | |
| : 3600 | |
| const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString() |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/federated-auth/token-refresh.ts` around lines 75 - 77, The
code computes expiresAt using responseBody.expires_in without validating it;
update the logic around responseBody/expires_in (affecting variables
newRefreshToken, expiresIn, expiresAt) to first coerce and validate expires_in
is a finite positive number (e.g., Number(responseBody.expires_in)), and if it
is NaN, <=0, or not finite, fall back to the default (3600) before computing
expiresAt; then compute expiresAt from the validated expiresIn so
negative/invalid values cannot produce bad timestamps.
| const isFederated = | ||
| integration.federated_auth_method != null && isFederatedAuthMethod(integration.federated_auth_method) | ||
|
|
||
| if (isFederated && params.federatedAuthTokensFilePath) { | ||
| try { | ||
| const accessToken = await getValidFederatedAuthToken(integration, params.federatedAuthTokensFilePath) | ||
| if (accessToken && integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') { | ||
| const metadata = integration.metadata as Extract< | ||
| DatabaseIntegrationMetadataByType['trino'], | ||
| { authMethod: typeof TrinoAuthMethods.Oauth } | ||
| > | ||
| const sqlAlchemyInput = buildTrinoOAuthSqlAlchemyInput( | ||
| integration.id, | ||
| metadata, | ||
| accessToken, | ||
| params.projectRootDirectory | ||
| ) | ||
| envVarsForThisIntegration.push({ | ||
| name: getSqlEnvVarName(integration.id), | ||
| value: JSON.stringify({ integration_id: integration.id, ...sqlAlchemyInput }), | ||
| }) |
There was a problem hiding this comment.
Federated detection is incomplete and the OAuth cast is unsafe.
Current logic only trusts top-level federated_auth_method; metadata-only Trino OAuth configs won’t get token-backed SQL env vars. The forced cast also skips runtime validation of metadata auth method.
💡 Suggested fix
- const isFederated =
- integration.federated_auth_method != null && isFederatedAuthMethod(integration.federated_auth_method)
+ const topLevelFederated =
+ integration.federated_auth_method != null && isFederatedAuthMethod(integration.federated_auth_method)
+ const trinoOauthFromMetadata =
+ integration.type === 'trino' && integration.metadata.authMethod === TrinoAuthMethods.Oauth
+ const isFederated = topLevelFederated || trinoOauthFromMetadata
if (isFederated && params.federatedAuthTokensFilePath) {
try {
const accessToken = await getValidFederatedAuthToken(integration, params.federatedAuthTokensFilePath)
- if (accessToken && integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') {
- const metadata = integration.metadata as Extract<
- DatabaseIntegrationMetadataByType['trino'],
- { authMethod: typeof TrinoAuthMethods.Oauth }
- >
+ if (accessToken && integration.type === 'trino' && integration.metadata.authMethod === TrinoAuthMethods.Oauth) {
+ const metadata = integration.metadata
const sqlAlchemyInput = buildTrinoOAuthSqlAlchemyInput(
integration.id,
metadata,
accessToken,
params.projectRootDirectory
)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/database-integration-env-vars.ts` around
lines 73 - 93, The federated detection and unsafe cast are causing Trino OAuth
configs stored only in integration.metadata to be missed and bypassing runtime
validation; update the logic in the block around isFederated and the call to
getValidFederatedAuthToken so that you check both
integration.federated_auth_method and integration.metadata.authMethod (or
equivalent) for 'trino-oauth' instead of relying solely on the top-level field,
and validate metadata at runtime before calling buildTrinoOAuthSqlAlchemyInput:
fetch metadata as DatabaseIntegrationMetadataByType['trino'], assert/guard that
metadata.authMethod === TrinoAuthMethods.Oauth, then call
buildTrinoOAuthSqlAlchemyInput with that validated metadata and push the env var
via getSqlEnvVarName only when validation succeeds.
| const response = await fetch(tokenUrl, { | ||
| method: 'POST', | ||
| headers: { | ||
| Authorization: `Basic ${authString}`, | ||
| 'Content-Type': 'application/x-www-form-urlencoded', | ||
| }, | ||
| body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(tokenEntry.refreshToken)}`, | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify there is a fetch call and no abort signal wiring.
rg -nP -C2 "refreshAccessToken|fetch\(|AbortController|signal" packages/database-integrations/src/federated-auth-tokens.tsRepository: deepnote/deepnote
Length of output: 570
🏁 Script executed:
# Get full context of the file and identify the function
wc -l packages/database-integrations/src/federated-auth-tokens.tsRepository: deepnote/deepnote
Length of output: 124
🏁 Script executed:
# Read the entire file to understand the context
cat -n packages/database-integrations/src/federated-auth-tokens.tsRepository: deepnote/deepnote
Length of output: 6939
Add timeout/abort for token refresh HTTP call.
The fetch call at line 107 can hang indefinitely without a timeout mechanism, blocking token refresh operations. Additionally, errors are suppressed in the caller (lines 175–176), making failures invisible.
💡 Suggested fix
- const response = await fetch(tokenUrl, {
- method: 'POST',
- headers: {
- Authorization: `Basic ${authString}`,
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(tokenEntry.refreshToken)}`,
- })
+ const timeoutMs = 10_000
+ const controller = new AbortController()
+ const timeout = setTimeout(() => controller.abort(), timeoutMs)
+ let response: Response
+ try {
+ response = await fetch(tokenUrl, {
+ method: 'POST',
+ headers: {
+ Authorization: `Basic ${authString}`,
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(tokenEntry.refreshToken)}`,
+ signal: controller.signal,
+ })
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ throw new Error(`Token refresh timed out after ${timeoutMs}ms`)
+ }
+ throw error
+ } finally {
+ clearTimeout(timeout)
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/federated-auth-tokens.ts` around lines 107
- 114, The token refresh fetch to tokenUrl should use an AbortController with a
timeout to avoid hanging: create an AbortController, pass controller.signal to
fetch, start a setTimeout (e.g., 10s) that calls controller.abort(), and clear
the timer when the response arrives; ensure fetch rejections (including
AbortError) are caught and the error is thrown/rejected up (do not suppress) so
callers see failures. Update the refresh logic that uses tokenEntry.refreshToken
and the fetch call to propagate errors instead of swallowing them so token
refresh failures are visible to callers.
| let finalToken = tokenEntry | ||
| if (isTokenExpired(tokenEntry)) { | ||
| try { | ||
| finalToken = await refreshAccessToken(tokenEntry, integration, tokensFilePath) | ||
| } catch { | ||
| return undefined | ||
| } |
There was a problem hiding this comment.
Do not swallow refresh failures.
Returning undefined on refresh errors hides actionable auth failures and prevents callers from reporting them.
💡 Suggested fix
let finalToken = tokenEntry
if (isTokenExpired(tokenEntry)) {
- try {
- finalToken = await refreshAccessToken(tokenEntry, integration, tokensFilePath)
- } catch {
- return undefined
- }
+ finalToken = await refreshAccessToken(tokenEntry, integration, tokensFilePath)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let finalToken = tokenEntry | |
| if (isTokenExpired(tokenEntry)) { | |
| try { | |
| finalToken = await refreshAccessToken(tokenEntry, integration, tokensFilePath) | |
| } catch { | |
| return undefined | |
| } | |
| let finalToken = tokenEntry | |
| if (isTokenExpired(tokenEntry)) { | |
| finalToken = await refreshAccessToken(tokenEntry, integration, tokensFilePath) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/federated-auth-tokens.ts` around lines 171
- 177, The current try/catch around refreshAccessToken(tokenEntry, integration,
tokensFilePath) swallows failures and returns undefined; instead let refresh
errors propagate (or rethrow with context) so callers can handle/report them.
Remove the empty catch block (or change it to catch (err) { throw new
Error(`refreshAccessToken failed for ${integration}: ${err}`); }) and keep
finalToken assignment as before; keep references to tokenEntry, isTokenExpired,
refreshAccessToken, finalToken and tokensFilePath to locate the change.
- Updated `federatedAuthTokenEntrySchema` to enforce non-empty string fields. - Added tests for invalid `expiresAt` format and empty required fields. - Refactored `refreshAccessToken` to `refreshAccessTokenAndSave` for clarity and persistence. - Introduced timeout handling for token refresh requests. - Improved error handling for unsupported federated auth methods in environment variable retrieval.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (5)
packages/cli/src/federated-auth/oauth-local-server.ts (2)
116-117:⚠️ Potential issue | 🟠 MajorEscape OAuth error text before embedding in HTML.
err.nameanderr.messageare interpolated raw into HTML. Treat both as untrusted.Suggested fix
+function escapeHtml(value: string): string { + return value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", ''') +} + ... - const html = `<!DOCTYPE html><html><body><h2>${err.name}</h2><p>${err.message}</p></body></html>` + const html = `<!DOCTYPE html><html><body><h2>${escapeHtml(err.name)}</h2><p>${escapeHtml(err.message)}</p></body></html>`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/oauth-local-server.ts` around lines 116 - 117, The HTML response embeds untrusted err.name and err.message directly; escape both values before interpolation to prevent XSS. In oauth-local-server.ts where html is constructed (the const html = `...` line inside the error handler that uses err and res), replace the direct interpolation with escaped versions (use an HTML-escape helper function or small utility that replaces &, <, >, ", ' with entities) and then send the escaped content via res.status(400).send(html); ensure the helper is imported or defined near that handler and used for both err.name and err.message.
127-136:⚠️ Potential issue | 🟠 MajorHandle
app.listenerrors explicitly.Port bind failures (e.g.,
EADDRINUSE) are not handled. Add a servererrorhandler and reject the flow deterministically.Suggested fix
const server = app.listen(OAUTH_PORT, () => { log('Opening browser to authenticate...') log('If the browser does not open automatically, visit:') log(startURL) try { open(startURL).catch(() => {}) } catch { // ignore } }) + + server.on('error', err => { + const error = err instanceof Error ? err : new Error(String(err)) + rejectOnce(error) + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/oauth-local-server.ts` around lines 127 - 136, The current app.listen call creates server without handling bind errors; update the logic around server = app.listen(OAUTH_PORT, ...) to attach an 'error' handler on the returned server (server.on('error', ...)) that catches failures like EADDRINUSE and rejects/throw a deterministic error for the OAuth flow instead of letting it hang; ensure the same promise or control flow that resolves on successful listen is rejected when server emits an error, and include clear context (port/OAUTH_PORT and startURL) in the rejection path so callers can handle the failure.packages/cli/src/federated-auth/token-refresh.test.ts (1)
55-132:⚠️ Potential issue | 🟠 MajorExpand refresh error-path coverage (still missing).
Suite currently misses
invalid_grant,invalid_client/unauthorized_client, generic non-OK responses, invalid JSON, and save-failure assertions forrefreshAccessTokenAndSave.As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/token-refresh.test.ts` around lines 55 - 132, The test suite for refreshAccessTokenAndSave in token-refresh.test.ts lacks coverage for error paths (invalid_grant, invalid_client/unauthorized_client, non-OK HTTP responses, invalid JSON responses, and failures when saving tokens); add tests that stub global fetch (mockFetch) to return: 1) a 200 with { error: 'invalid_grant' } and assert the function rejects with a clear error, 2) a 400/401 with body indicating invalid_client/unauthorized_client and assert rejection, 3) a non-ok response (e.g., ok: false) and assert rejection, 4) a resolved fetch whose json() throws (invalid JSON) and assert rejection, and 5) simulate a successful token response but make the token-save path fail (mock file write or the internal save function) and assert the save error is propagated; reference refreshAccessTokenAndSave, mockFetch/vi.stubGlobal('fetch'), and the test token fixtures to locate where to insert each new it() case.packages/database-integrations/src/federated-auth-tokens.ts (2)
207-214:⚠️ Potential issue | 🟠 MajorDo not swallow refresh failures.
On expired tokens, any refresh/save failure currently returns
undefined, which hides actionable auth/config errors and makes troubleshooting opaque.Suggested fix
let finalToken = tokenEntry if (isTokenExpired(tokenEntry)) { - try { - finalToken = await refreshAccessToken(tokenEntry, integration) - await saveTokenForIntegration(finalToken, tokensFilePath) - } catch { - return undefined - } + finalToken = await refreshAccessToken(tokenEntry, integration) + await saveTokenForIntegration(finalToken, tokensFilePath) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/database-integrations/src/federated-auth-tokens.ts` around lines 207 - 214, The current block around isTokenExpired(tokenEntry) swallows any error from refreshAccessToken(tokenEntry, integration) or saveTokenForIntegration(finalToken, tokensFilePath) by returning undefined; change the catch to propagate the error (include the caught error details) instead of returning undefined so callers can handle or surface auth/config failures — for example rethrow the caught error or throw a new Error that wraps it; update the surrounding logic that expects undefined accordingly. Ensure this change is applied to the tokens refresh flow involving finalToken, refreshAccessToken, saveTokenForIntegration and tokenEntry.
137-152:⚠️ Potential issue | 🟠 MajorValidate integration/token binding before refresh.
refreshAccessTokenpoststokenEntry.refreshTokenusingintegrationmetadata without asserting they belong to the same integration. A mismatched call can leak refresh tokens to the wrong token endpoint.Suggested fix
export async function refreshAccessToken( tokenEntry: FederatedAuthTokenEntry, integration: DatabaseIntegrationConfig ): Promise<FederatedAuthTokenEntry> { + if (integration.id !== tokenEntry.integrationId) { + throw new Error( + `Token entry integration mismatch: expected "${tokenEntry.integrationId}", got "${integration.id}"` + ) + } + const metadata = integration.metadata as { tokenUrl: string clientId: string clientSecret: string }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/database-integrations/src/federated-auth-tokens.ts` around lines 137 - 152, In refreshAccessToken, ensure the provided tokenEntry is bound to the same integration before posting the refresh token: validate that tokenEntry's integration identifier (e.g., tokenEntry.integrationId or equivalent field on FederatedAuthTokenEntry) matches the integration passed in (integration.id or another unique integration key), and if not throw an error; only call fetchTokenWithTimeout(tokenUrl, clientId, clientSecret, tokenEntry.refreshToken) after this check to prevent sending a refreshToken to the wrong tokenUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/integrations/auth-integration.ts`:
- Around line 83-108: Replace the manual type-and-empty checks in
extractTrinoOAuthParams with a zod schema validation: define a
trinoOAuthParamsSchema using z.object({ authUrl: z.string().min(1, ...),
tokenUrl: z.string().min(1,...), clientId: z.string().min(1,...), clientSecret:
z.string().min(1,...) }), run safeParse(metadata) inside
extractTrinoOAuthParams, and on failure throw a single Error composed from
result.error.issues messages; on success return result.data (the typed authUrl,
tokenUrl, clientId, clientSecret). Ensure zod is imported.
- Around line 197-203: In the catch block handling errors in the auth
integration, stop execution after handling ExitPromptError by returning (or
using an else) right after the program.error(chalk.yellow('Cancelled.'), {
exitCode: ExitCode.Error }) call so the subsequent generic error handler
(program.error(chalk.red(...), { exitCode: ExitCode.Error })) is not executed
for ExitPromptError; update the catch in the function containing that try/catch
to either add a return after the ExitPromptError branch or convert it to an
if/else.
In `@packages/cli/src/federated-auth/oauth-local-server.ts`:
- Around line 52-58: The OAuth flow promise can hang if the browser callback
never arrives; add an overall timeout to reject the flow after a configurable
interval (e.g., 2 minutes) by wiring a setTimeout that calls rejectOnce (or
rejects the outer Promise) and clearing that timer on successful resolution;
implement this by racing the existing completion promise against a timeout
promise (or invoking rejectOnce from the timer) in the function that creates
resolveOnce/rejectOnce and do the same for the second identical block referenced
(the block around identifiers completion, resolveOnce, rejectOnce and the
Promise returned later at lines ~138-146) so the CLI never waits indefinitely.
In `@packages/database-integrations/src/database-integration-env-vars.ts`:
- Around line 79-96: The current branch treats a missing/undefined accessToken
the same as an unsupported federated_auth_method and pushes "Unsupported
federated auth method"; update the conditional in the block handling
integration.type === 'trino' && integration.federated_auth_method ===
'trino-oauth' to first check for a truthy accessToken and, if missing, push a
clear error describing the missing/expired token (include integration.id and
federated_auth_method for context) instead of falling through to the generic
unsupported-auth error; keep the existing path that calls
buildTrinoOAuthSqlAlchemyInput and pushes envVarsForThisIntegration when
accessToken is present, and leave errors.push(new Error(`Unsupported federated
auth method: ${integration.federated_auth_method}`)) only for truly unsupported
methods.
In `@packages/database-integrations/src/federated-auth-tokens.ts`:
- Around line 127-130: Token files are being created with default filesystem
permissions that can be group/world-readable; update the write path in the code
that builds and persists tokens (the block that computes dir, calls fs.mkdir,
builds content via stringify and calls fs.writeFile) to create the directory and
file with restrictive modes (e.g., mkdir with mode 0o700 and fs.writeFile with
mode 0o600) so only the owner can read/write the token file; ensure you pass the
mode option to fs.writeFile and to fs.mkdir (or perform a chmod after creation)
when writing the updatedTokens to filePath.
---
Duplicate comments:
In `@packages/cli/src/federated-auth/oauth-local-server.ts`:
- Around line 116-117: The HTML response embeds untrusted err.name and
err.message directly; escape both values before interpolation to prevent XSS. In
oauth-local-server.ts where html is constructed (the const html = `...` line
inside the error handler that uses err and res), replace the direct
interpolation with escaped versions (use an HTML-escape helper function or small
utility that replaces &, <, >, ", ' with entities) and then send the escaped
content via res.status(400).send(html); ensure the helper is imported or defined
near that handler and used for both err.name and err.message.
- Around line 127-136: The current app.listen call creates server without
handling bind errors; update the logic around server = app.listen(OAUTH_PORT,
...) to attach an 'error' handler on the returned server (server.on('error',
...)) that catches failures like EADDRINUSE and rejects/throw a deterministic
error for the OAuth flow instead of letting it hang; ensure the same promise or
control flow that resolves on successful listen is rejected when server emits an
error, and include clear context (port/OAUTH_PORT and startURL) in the rejection
path so callers can handle the failure.
In `@packages/cli/src/federated-auth/token-refresh.test.ts`:
- Around line 55-132: The test suite for refreshAccessTokenAndSave in
token-refresh.test.ts lacks coverage for error paths (invalid_grant,
invalid_client/unauthorized_client, non-OK HTTP responses, invalid JSON
responses, and failures when saving tokens); add tests that stub global fetch
(mockFetch) to return: 1) a 200 with { error: 'invalid_grant' } and assert the
function rejects with a clear error, 2) a 400/401 with body indicating
invalid_client/unauthorized_client and assert rejection, 3) a non-ok response
(e.g., ok: false) and assert rejection, 4) a resolved fetch whose json() throws
(invalid JSON) and assert rejection, and 5) simulate a successful token response
but make the token-save path fail (mock file write or the internal save
function) and assert the save error is propagated; reference
refreshAccessTokenAndSave, mockFetch/vi.stubGlobal('fetch'), and the test token
fixtures to locate where to insert each new it() case.
In `@packages/database-integrations/src/federated-auth-tokens.ts`:
- Around line 207-214: The current block around isTokenExpired(tokenEntry)
swallows any error from refreshAccessToken(tokenEntry, integration) or
saveTokenForIntegration(finalToken, tokensFilePath) by returning undefined;
change the catch to propagate the error (include the caught error details)
instead of returning undefined so callers can handle or surface auth/config
failures — for example rethrow the caught error or throw a new Error that wraps
it; update the surrounding logic that expects undefined accordingly. Ensure this
change is applied to the tokens refresh flow involving finalToken,
refreshAccessToken, saveTokenForIntegration and tokenEntry.
- Around line 137-152: In refreshAccessToken, ensure the provided tokenEntry is
bound to the same integration before posting the refresh token: validate that
tokenEntry's integration identifier (e.g., tokenEntry.integrationId or
equivalent field on FederatedAuthTokenEntry) matches the integration passed in
(integration.id or another unique integration key), and if not throw an error;
only call fetchTokenWithTimeout(tokenUrl, clientId, clientSecret,
tokenEntry.refreshToken) after this check to prevent sending a refreshToken to
the wrong tokenUrl.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
packages/cli/src/commands/integrations/auth-integration.tspackages/cli/src/federated-auth/federated-auth-tokens-schema.test.tspackages/cli/src/federated-auth/federated-auth-tokens-schema.tspackages/cli/src/federated-auth/federated-auth-tokens.test.tspackages/cli/src/federated-auth/oauth-local-server.tspackages/cli/src/federated-auth/token-refresh.test.tspackages/cli/src/federated-auth/token-refresh.tspackages/database-integrations/src/database-integration-env-vars.tspackages/database-integrations/src/federated-auth-tokens.tspackages/database-integrations/src/index.ts
| function extractTrinoOAuthParams(metadata: Record<string, unknown>): { | ||
| authUrl: string | ||
| tokenUrl: string | ||
| clientId: string | ||
| clientSecret: string | ||
| } { | ||
| const authUrl = metadata.authUrl | ||
| const tokenUrl = metadata.tokenUrl | ||
| const clientId = metadata.clientId | ||
| const clientSecret = metadata.clientSecret | ||
|
|
||
| if (typeof authUrl !== 'string' || !authUrl) { | ||
| throw new Error('Trino OAuth authUrl is required. Edit your integration and set authUrl in metadata.') | ||
| } | ||
| if (typeof tokenUrl !== 'string' || !tokenUrl) { | ||
| throw new Error('Trino OAuth tokenUrl is required. Edit your integration and set tokenUrl in metadata.') | ||
| } | ||
| if (typeof clientId !== 'string' || !clientId) { | ||
| throw new Error('Trino OAuth clientId is required. Edit your integration and set clientId in metadata.') | ||
| } | ||
| if (typeof clientSecret !== 'string' || !clientSecret) { | ||
| throw new Error('Trino OAuth clientSecret is required. Edit your integration and set clientSecret in metadata.') | ||
| } | ||
|
|
||
| return { authUrl, tokenUrl, clientId, clientSecret } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider zod schema for param extraction.
Manual checks work but a zod schema would be more declarative and provide better error messages automatically.
Alternative with zod
const trinoOAuthParamsSchema = z.object({
authUrl: z.string().min(1, 'Trino OAuth authUrl is required'),
tokenUrl: z.string().min(1, 'Trino OAuth tokenUrl is required'),
clientId: z.string().min(1, 'Trino OAuth clientId is required'),
clientSecret: z.string().min(1, 'Trino OAuth clientSecret is required'),
})
function extractTrinoOAuthParams(metadata: Record<string, unknown>) {
const result = trinoOAuthParamsSchema.safeParse(metadata)
if (!result.success) {
throw new Error(result.error.issues.map(i => i.message).join('; '))
}
return result.data
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/commands/integrations/auth-integration.ts` around lines 83 -
108, Replace the manual type-and-empty checks in extractTrinoOAuthParams with a
zod schema validation: define a trinoOAuthParamsSchema using z.object({ authUrl:
z.string().min(1, ...), tokenUrl: z.string().min(1,...), clientId:
z.string().min(1,...), clientSecret: z.string().min(1,...) }), run
safeParse(metadata) inside extractTrinoOAuthParams, and on failure throw a
single Error composed from result.error.issues messages; on success return
result.data (the typed authUrl, tokenUrl, clientId, clientSecret). Ensure zod is
imported.
| } catch (error) { | ||
| if (error instanceof Error && error.name === 'ExitPromptError') { | ||
| program.error(chalk.yellow('Cancelled.'), { exitCode: ExitCode.Error }) | ||
| } | ||
| const message = error instanceof Error ? error.message : String(error) | ||
| program.error(chalk.red(message), { exitCode: ExitCode.Error }) | ||
| } |
There was a problem hiding this comment.
Missing return after ExitPromptError handling.
After calling program.error() on line 199, execution continues to line 201-202, calling program.error() twice.
Fix
if (error instanceof Error && error.name === 'ExitPromptError') {
- program.error(chalk.yellow('Cancelled.'), { exitCode: ExitCode.Error })
+ return program.error(chalk.yellow('Cancelled.'), { exitCode: ExitCode.Error })
}
const message = error instanceof Error ? error.message : String(error)
program.error(chalk.red(message), { exitCode: ExitCode.Error })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/commands/integrations/auth-integration.ts` around lines 197
- 203, In the catch block handling errors in the auth integration, stop
execution after handling ExitPromptError by returning (or using an else) right
after the program.error(chalk.yellow('Cancelled.'), { exitCode: ExitCode.Error
}) call so the subsequent generic error handler (program.error(chalk.red(...), {
exitCode: ExitCode.Error })) is not executed for ExitPromptError; update the
catch in the function containing that try/catch to either add a return after the
ExitPromptError branch or convert it to an if/else.
| if (accessToken && integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') { | ||
| const metadata = integration.metadata as Extract< | ||
| DatabaseIntegrationMetadataByType['trino'], | ||
| { authMethod: typeof TrinoAuthMethods.Oauth } | ||
| > | ||
| const sqlAlchemyInput = buildTrinoOAuthSqlAlchemyInput( | ||
| integration.id, | ||
| metadata, | ||
| accessToken, | ||
| params.projectRootDirectory | ||
| ) | ||
| envVarsForThisIntegration.push({ | ||
| name: getSqlEnvVarName(integration.id), | ||
| value: JSON.stringify({ integration_id: integration.id, ...sqlAlchemyInput }), | ||
| }) | ||
| } else { | ||
| errors.push(new Error(`Unsupported federated auth method: ${integration.federated_auth_method}`)) | ||
| } |
There was a problem hiding this comment.
Misleading error when token is missing.
If accessToken is undefined (token not found/expired), code falls through to line 95 and throws "Unsupported federated auth method" — incorrect diagnosis.
Proposed fix
- if (accessToken && integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') {
+ if (integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') {
+ if (!accessToken) {
+ // Token not available; skip SQL env var injection (user needs to authenticate)
+ continue
+ }
const metadata = integration.metadata as Extract<
DatabaseIntegrationMetadataByType['trino'],
{ authMethod: typeof TrinoAuthMethods.Oauth }
>
const sqlAlchemyInput = buildTrinoOAuthSqlAlchemyInput(
integration.id,
metadata,
accessToken,
params.projectRootDirectory
)
envVarsForThisIntegration.push({
name: getSqlEnvVarName(integration.id),
value: JSON.stringify({ integration_id: integration.id, ...sqlAlchemyInput }),
})
} else {
errors.push(new Error(`Unsupported federated auth method: ${integration.federated_auth_method}`))
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/database-integration-env-vars.ts` around
lines 79 - 96, The current branch treats a missing/undefined accessToken the
same as an unsupported federated_auth_method and pushes "Unsupported federated
auth method"; update the conditional in the block handling integration.type ===
'trino' && integration.federated_auth_method === 'trino-oauth' to first check
for a truthy accessToken and, if missing, push a clear error describing the
missing/expired token (include integration.id and federated_auth_method for
context) instead of falling through to the generic unsupported-auth error; keep
the existing path that calls buildTrinoOAuthSqlAlchemyInput and pushes
envVarsForThisIntegration when accessToken is present, and leave errors.push(new
Error(`Unsupported federated auth method:
${integration.federated_auth_method}`)) only for truly unsupported methods.
| const dir = path.dirname(filePath) | ||
| await fs.mkdir(dir, { recursive: true }) | ||
| const content = stringify({ tokens: updatedTokens }, { lineWidth: 0 }) | ||
| await fs.writeFile(filePath, content, 'utf-8') |
There was a problem hiding this comment.
Persist token files with restrictive permissions.
Refresh/access tokens are written with default filesystem modes. On common umask settings, this can make secrets group/world-readable.
Suggested fix
const dir = path.dirname(filePath)
- await fs.mkdir(dir, { recursive: true })
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 })
const content = stringify({ tokens: updatedTokens }, { lineWidth: 0 })
- await fs.writeFile(filePath, content, 'utf-8')
+ await fs.writeFile(filePath, content, { encoding: 'utf-8', mode: 0o600 })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const dir = path.dirname(filePath) | |
| await fs.mkdir(dir, { recursive: true }) | |
| const content = stringify({ tokens: updatedTokens }, { lineWidth: 0 }) | |
| await fs.writeFile(filePath, content, 'utf-8') | |
| const dir = path.dirname(filePath) | |
| await fs.mkdir(dir, { recursive: true, mode: 0o700 }) | |
| const content = stringify({ tokens: updatedTokens }, { lineWidth: 0 }) | |
| await fs.writeFile(filePath, content, { encoding: 'utf-8', mode: 0o600 }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/federated-auth-tokens.ts` around lines 127
- 130, Token files are being created with default filesystem permissions that
can be group/world-readable; update the write path in the code that builds and
persists tokens (the block that computes dir, calls fs.mkdir, builds content via
stringify and calls fs.writeFile) to create the directory and file with
restrictive modes (e.g., mkdir with mode 0o700 and fs.writeFile with mode 0o600)
so only the owner can read/write the token file; ensure you pass the mode option
to fs.writeFile and to fs.mkdir (or perform a chmod after creation) when writing
the updatedTokens to filePath.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/integrations/edit-integration.ts`:
- Around line 294-297: The catch block after calling resolveEnvVarRefsFromMap
sets debug using an interpolated unknown error; update the catch to explicitly
stringify the caught value before logging (e.g., use String(error) or check
error instanceof Error and use error.message/stack) so the debug call for
integrationRawJson resolution includes a safe, explicit error string; modify the
debug invocation that references resolveEnvVarRefsFromMap and integrationRawJson
to use that stringified error.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/cli/src/commands/integrations/edit-integration.ts
| integrationRawJson = resolveEnvVarRefsFromMap(integrationRawJson, envVars) | ||
| } catch (error) { | ||
| debug('Failed to resolve env: refs in integration metadata:', error) | ||
| debug(`Failed to resolve env: refs in integration metadata: ${error}`) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider explicit error stringification.
error is unknown in catch blocks. Template interpolation works but explicit conversion is safer.
♻️ Suggested fix
} catch (error) {
- debug(`Failed to resolve env: refs in integration metadata: ${error}`)
+ debug(`Failed to resolve env: refs in integration metadata: ${error instanceof Error ? error.message : String(error)}`)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| integrationRawJson = resolveEnvVarRefsFromMap(integrationRawJson, envVars) | |
| } catch (error) { | |
| debug('Failed to resolve env: refs in integration metadata:', error) | |
| debug(`Failed to resolve env: refs in integration metadata: ${error}`) | |
| } | |
| integrationRawJson = resolveEnvVarRefsFromMap(integrationRawJson, envVars) | |
| } catch (error) { | |
| debug(`Failed to resolve env: refs in integration metadata: ${error instanceof Error ? error.message : String(error)}`) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/commands/integrations/edit-integration.ts` around lines 294
- 297, The catch block after calling resolveEnvVarRefsFromMap sets debug using
an interpolated unknown error; update the catch to explicitly stringify the
caught value before logging (e.g., use String(error) or check error instanceof
Error and use error.message/stack) so the debug call for integrationRawJson
resolution includes a safe, explicit error string; modify the debug invocation
that references resolveEnvVarRefsFromMap and integrationRawJson to use that
stringified error.
…l' into tk/cli-federated-auth
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
packages/cli/src/federated-auth/federated-auth-tokens.ts (1)
174-184:⚠️ Potential issue | 🟡 MinorUpsert can silently drop existing invalid entries
Line 176 ignores parse
issues; rewriting on Line 184 can discard malformed entries without warning.Suggested fix
export async function saveTokenForIntegration(entry: FederatedAuthTokenEntry, filePath?: string): Promise<void> { const resolvedPath = filePath ?? getDefaultTokensFilePath() - const { tokens } = await readTokensFile(resolvedPath) + const { tokens, issues } = await readTokensFile(resolvedPath) + if (issues.length > 0) { + throw new Error(`Cannot safely update tokens file at ${resolvedPath}: found ${issues.length} invalid entry issue(s).`) + } const existingIndex = tokens.findIndex(t => t.integrationId === entry.integrationId) const updatedTokens =🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/federated-auth-tokens.ts` around lines 174 - 184, The saveTokenForIntegration function currently ignores parse issues returned by readTokensFile and may overwrite the tokens file, discarding malformed entries; update saveTokenForIntegration to handle readTokensFile's issues: call readTokensFile(resolvedPath) and inspect the returned issues array, and if issues.length > 0 either (a) surface/log/throw a descriptive error including those issues so the user can fix the file, or (b) preserve the original raw/malformed entries when constructing updatedTokens by merging the new/updated entry only into the valid tokens array while keeping the raw invalid entries intact before calling writeTokensFile; reference the saveTokenForIntegration function, the readTokensFile result (tokens and issues), and the writeTokensFile call when implementing this change.packages/database-integrations/src/database-integration-env-vars.ts (1)
78-101:⚠️ Potential issue | 🟠 MajorFederated Trino branch still conflates valid cases
Lines 78-101 currently mix up:
- metadata-only Trino OAuth detection,
- missing/expired token, and
- truly unsupported auth method.
That causes misleading errors and can skip valid federated configs.
Suggested fix
- const isFederated = - integration.federated_auth_method != null && isFederatedAuthMethod(integration.federated_auth_method) + const topLevelFederated = + integration.federated_auth_method != null && isFederatedAuthMethod(integration.federated_auth_method) + const trinoOauthFromMetadata = + integration.type === 'trino' && + 'authMethod' in integration.metadata && + integration.metadata.authMethod === TrinoAuthMethods.Oauth + const isFederated = topLevelFederated || trinoOauthFromMetadata if (isFederated && params.federatedAuthTokenResolver) { try { const accessToken = await params.federatedAuthTokenResolver(integration) - if (accessToken && integration.type === 'trino' && integration.federated_auth_method === 'trino-oauth') { + if (integration.type === 'trino' && trinoOauthFromMetadata) { + if (!accessToken) { + errors.push(new Error(`Missing or expired federated token for integration: ${integration.id}`)) + continue + } const metadata = integration.metadata as Extract< DatabaseIntegrationMetadataByType['trino'], { authMethod: typeof TrinoAuthMethods.Oauth } > const sqlAlchemyInput = buildTrinoOAuthSqlAlchemyInput( integration.id, metadata, accessToken, params.projectRootDirectory ) envVarsForThisIntegration.push({ name: getSqlEnvVarName(integration.id), value: JSON.stringify({ integration_id: integration.id, ...sqlAlchemyInput }), }) } else { errors.push(new Error(`Unsupported federated auth method: ${integration.federated_auth_method}`)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/database-integrations/src/database-integration-env-vars.ts` around lines 78 - 101, The federated Trino branch conflates three cases (valid Trino OAuth with metadata, missing/expired accessToken, and truly unsupported auth method); update the logic in the block that calls params.federatedAuthTokenResolver so that: if integration.type === 'trino' and integration.federated_auth_method === 'trino-oauth' then validate metadata.authMethod (the extracted metadata variable) and only when metadata matches the expected OAuth shape call buildTrinoOAuthSqlAlchemyInput and push the env var; if accessToken is falsy push a clear token-specific error (or skip adding the unsupported-method error) instead of treating it as an unsupported auth method; otherwise (federated_auth_method present but not 'trino-oauth') push the unsupported-method error using integration.federated_auth_method. Ensure you reference isFederated, params.federatedAuthTokenResolver, accessToken, integration.federated_auth_method, integration.type, metadata, buildTrinoOAuthSqlAlchemyInput, and errors.push when implementing these branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/federated-auth/federated-auth-tokens.ts`:
- Around line 171-173: Fix the cspell warning by editing the comment block above
the token upsert function: change the word "Upserts" to singular "Upsert" in the
sentence that reads "Upserts by integrationId - replaces existing or appends."
(Look for the comment immediately above the save/update token logic or the
function that handles token upsert for integrations to update the text.)
In `@packages/cli/src/utils/secure-file.ts`:
- Around line 10-11: The current write step using fs.writeFile(filePath,
content, { mode: 0o600 }) only applies mode on newly created files; update the
logic in secure-file.ts to always harden permissions by calling await
fs.chmod(filePath, 0o600) after the write (after the existing await
fs.writeFile(...) line), ensuring filePath is set to 0o600 regardless of prior
existence; keep the mkdir(...) and writeFile(...) calls as-is and just add the
chmod call (optionally wrap chmod in a try/catch if you need to surface a clear
error).
In `@packages/database-integrations/src/database-integration-env-vars.ts`:
- Around line 102-113: Replace the unsafe casts of unknown to Error in the catch
blocks by normalizing the thrown value before pushing to errors: when catching
in the branches that reference isFederated and getEnvVarForSqlCells (the block
that pushes into envVarsForThisIntegration), convert the caught unknown into a
proper Error instance (e.g., use instanceof Error to keep Error objects or
create a new Error(String(error)) for non-Errors) and then push that normalized
Error into the errors array so errors always contains Error instances without
using `error as Error`.
---
Duplicate comments:
In `@packages/cli/src/federated-auth/federated-auth-tokens.ts`:
- Around line 174-184: The saveTokenForIntegration function currently ignores
parse issues returned by readTokensFile and may overwrite the tokens file,
discarding malformed entries; update saveTokenForIntegration to handle
readTokensFile's issues: call readTokensFile(resolvedPath) and inspect the
returned issues array, and if issues.length > 0 either (a) surface/log/throw a
descriptive error including those issues so the user can fix the file, or (b)
preserve the original raw/malformed entries when constructing updatedTokens by
merging the new/updated entry only into the valid tokens array while keeping the
raw invalid entries intact before calling writeTokensFile; reference the
saveTokenForIntegration function, the readTokensFile result (tokens and issues),
and the writeTokensFile call when implementing this change.
In `@packages/database-integrations/src/database-integration-env-vars.ts`:
- Around line 78-101: The federated Trino branch conflates three cases (valid
Trino OAuth with metadata, missing/expired accessToken, and truly unsupported
auth method); update the logic in the block that calls
params.federatedAuthTokenResolver so that: if integration.type === 'trino' and
integration.federated_auth_method === 'trino-oauth' then validate
metadata.authMethod (the extracted metadata variable) and only when metadata
matches the expected OAuth shape call buildTrinoOAuthSqlAlchemyInput and push
the env var; if accessToken is falsy push a clear token-specific error (or skip
adding the unsupported-method error) instead of treating it as an unsupported
auth method; otherwise (federated_auth_method present but not 'trino-oauth')
push the unsupported-method error using integration.federated_auth_method.
Ensure you reference isFederated, params.federatedAuthTokenResolver,
accessToken, integration.federated_auth_method, integration.type, metadata,
buildTrinoOAuthSqlAlchemyInput, and errors.push when implementing these
branches.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
packages/cli/src/cli.tspackages/cli/src/commands/integrations/edit-integration.tspackages/cli/src/commands/run.tspackages/cli/src/constants.tspackages/cli/src/federated-auth/federated-auth-tokens.tspackages/cli/src/federated-auth/token-refresh.tspackages/cli/src/integrations/inject-integration-env-vars.tspackages/cli/src/utils/secure-file.tspackages/database-integrations/src/database-integration-env-vars.tspackages/database-integrations/src/index.ts
| * Save or update a token entry for an integration. | ||
| * Upserts by integrationId - replaces existing or appends. | ||
| */ |
There was a problem hiding this comment.
Fix cspell warning in comment text
Line 172 uses “Upserts”, which is currently failing CSpell in CI. Rename to “Upsert” to clear the warning.
🧰 Tools
🪛 GitHub Actions: CI
[warning] 172-172: CSpell: Unknown word (Upserts).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/federated-auth/federated-auth-tokens.ts` around lines 171 -
173, Fix the cspell warning by editing the comment block above the token upsert
function: change the word "Upserts" to singular "Upsert" in the sentence that
reads "Upserts by integrationId - replaces existing or appends." (Look for the
comment immediately above the save/update token logic or the function that
handles token upsert for integrations to update the text.)
| await fs.mkdir(dir, { recursive: true }) | ||
| await fs.writeFile(filePath, content, { encoding: 'utf-8', mode: 0o600 }) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Node.js fs/promises.writeFile mode option behavior existing files
💡 Result:
In fs/promises.writeFile(path, data, { mode }), the mode is only applied if the file is newly created; it does not change permissions on an existing file. To change an existing file’s permissions, call chmod/fs.promises.chmod separately. [1]
Also, writeFile with the default flag: 'w' replaces/truncates the file if it already exists; use a different flag (e.g. 'a' to append, 'wx' to fail if the path already exists) if you don’t want that behavior. [2]
Sources:
[1] Node.js File system docs — fsPromises.writeFile: “The mode option only affects the newly created file.” (nodejs.org)
[2] Node.js File system docs — fs.writeFile options (flag default 'w') and “replacing the file if it already exists” (nodejs.org)
Citations:
mode: 0o600 does not apply to existing files
On Line 11, writeFile(..., { mode: 0o600 }) only sets permissions on newly created files—it leaves existing files with their original permissions intact. Add fs.chmod() to ensure permissions are always hardened.
Suggested fix
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(filePath, content, { encoding: 'utf-8', mode: 0o600 })
+await fs.chmod(filePath, 0o600)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/utils/secure-file.ts` around lines 10 - 11, The current
write step using fs.writeFile(filePath, content, { mode: 0o600 }) only applies
mode on newly created files; update the logic in secure-file.ts to always harden
permissions by calling await fs.chmod(filePath, 0o600) after the write (after
the existing await fs.writeFile(...) line), ensuring filePath is set to 0o600
regardless of prior existence; keep the mkdir(...) and writeFile(...) calls
as-is and just add the chmod call (optionally wrap chmod in a try/catch if you
need to surface a clear error).
| } catch (error) { | ||
| errors.push(error as Error) | ||
| } | ||
| } else if (!isFederated) { | ||
| try { | ||
| const envVar = getEnvVarForSqlCells(integration, params) | ||
| if (envVar) { | ||
| envVarsForThisIntegration.push(envVar) | ||
| } | ||
| } catch (error) { | ||
| errors.push(error as Error) | ||
| } |
There was a problem hiding this comment.
Avoid unchecked unknown→Error casts
Lines 103 and 112 use error as Error. Normalize first so non-Error throws are preserved safely.
Suggested fix
- } catch (error) {
- errors.push(error as Error)
+ } catch (error) {
+ errors.push(error instanceof Error ? error : new Error(String(error)))
}
} else if (!isFederated) {
try {
const envVar = getEnvVarForSqlCells(integration, params)
if (envVar) {
envVarsForThisIntegration.push(envVar)
}
} catch (error) {
- errors.push(error as Error)
+ errors.push(error instanceof Error ? error : new Error(String(error)))
}
}As per coding guidelines: "Prefer type safety over convenience in TypeScript".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (error) { | |
| errors.push(error as Error) | |
| } | |
| } else if (!isFederated) { | |
| try { | |
| const envVar = getEnvVarForSqlCells(integration, params) | |
| if (envVar) { | |
| envVarsForThisIntegration.push(envVar) | |
| } | |
| } catch (error) { | |
| errors.push(error as Error) | |
| } | |
| } catch (error) { | |
| errors.push(error instanceof Error ? error : new Error(String(error))) | |
| } | |
| } else if (!isFederated) { | |
| try { | |
| const envVar = getEnvVarForSqlCells(integration, params) | |
| if (envVar) { | |
| envVarsForThisIntegration.push(envVar) | |
| } | |
| } catch (error) { | |
| errors.push(error instanceof Error ? error : new Error(String(error))) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/database-integrations/src/database-integration-env-vars.ts` around
lines 102 - 113, Replace the unsafe casts of unknown to Error in the catch
blocks by normalizing the thrown value before pushing to errors: when catching
in the branches that reference isFederated and getEnvVarForSqlCells (the block
that pushes into envVarsForThisIntegration), convert the caught unknown into a
proper Error instance (e.g., use instanceof Error to keep Error objects or
create a new Error(String(error)) for non-Errors) and then push that normalized
Error into the errors array so errors always contains Error instances without
using `error as Error`.
- Introduced comprehensive tests for the `authIntegration` function, covering various scenarios including successful authentication, error handling for missing files, and validation of integration types. - Added tests for the `runOAuthFlow` function in the local OAuth server, ensuring end-to-end functionality and error handling. - Refactored the `auth-integration.ts` to improve type handling and integrate the new token saving functionality. - Enhanced the local OAuth server to better manage server lifecycle and error responses.
- Updated the `runOAuthFlow` function to accept a port parameter, allowing for dynamic configuration of the local server port. - Introduced `DEFAULT_OAUTH_PORT` constant for default port usage in the `authIntegration` function and local server tests. - Modified tests to utilize the new port configuration, enhancing flexibility and maintainability.
…l' into tk/cli-federated-auth
- Added a new `extractOAuthCredentials` function to handle OAuth credential extraction specifically for Trino integrations. - Updated the `authIntegration` function to utilize the new extraction method, improving code clarity and maintainability. - Removed outdated checks for federated authentication methods, streamlining the integration process.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
packages/cli/src/federated-auth/oauth-local-server.ts (1)
161-169:⚠️ Potential issue | 🟠 MajorHandle
app.listenbind errors explicitly.Lines 161-169 do not attach a server
'error'handler, so port bind failures are abrupt and unclear.🛠 Suggested fix
server = app.listen(port, () => { log('Opening browser to authenticate...') log('If the browser does not open automatically, visit:') log(startURL) open(startURL).catch(err => { log('Error opening browser:') log(err instanceof Error ? err.message : String(err)) }) }) + server.on('error', err => { + const listenError = err instanceof Error ? err : new Error(String(err)) + reject(listenError) + })#!/bin/bash rg -n "app\\.listen|server\\.on\\('error'" packages/cli/src/federated-auth/oauth-local-server.ts # Expected: app.listen plus a server.on('error', ...) handler.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/oauth-local-server.ts` around lines 161 - 169, The call to app.listen(...) assigns server but doesn’t attach an 'error' handler, so bind failures (e.g., EADDRINUSE) crash unclearly; update the server returned by app.listen (the server variable) to attach server.on('error', ...) immediately after creation (or supply an error callback to app.listen) and handle known errors by logging a clear message via log(...) and exiting/cleaning up (handle error.code like 'EADDRINUSE' and other errors by logging error.message/stack and process.exit(1) or rejecting the promise used by oauth-local-server.ts); reference server and app.listen in your change.packages/cli/src/federated-auth/federated-auth-tokens.ts (2)
171-173:⚠️ Potential issue | 🟡 MinorRename “Upserts” to clear CI.
Line 172 still fails CSpell (
Unknown word (Upserts)).🛠 Suggested fix
- * Upserts by integrationId - replaces existing or appends. + * Upsert by integrationId - replaces existing or appends.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/federated-auth-tokens.ts` around lines 171 - 173, Replace the word "Upserts" in the comment block that starts with "Save or update a token entry for an integration." with a CSpell-friendly phrase; for example change "Upserts by integrationId - replaces existing or appends." to "Performs an upsert by integrationId — replaces existing or appends." so the comment above the token save/update function uses common words and passes the spellchecker.
174-184:⚠️ Potential issue | 🟠 Major
saveTokenForIntegrationstill drops malformed entries on write.Line 176 reads only valid parsed entries; Line 184 rewrites the file from that subset, so malformed/raw entries are silently lost.
🛠 Minimal mitigation
export async function saveTokenForIntegration(entry: FederatedAuthTokenEntry, filePath?: string): Promise<void> { const resolvedPath = filePath ?? getDefaultTokensFilePath() - const { tokens } = await readTokensFile(resolvedPath) + const { tokens, issues } = await readTokensFile(resolvedPath) + if (issues.length > 0) { + console.warn(`Warning: ${issues.length} invalid token entries in ${resolvedPath} will be omitted.`) + } const existingIndex = tokens.findIndex(t => t.integrationId === entry.integrationId)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/federated-auth/federated-auth-tokens.ts` around lines 174 - 184, The current saveTokenForIntegration (which calls readTokensFile and writeTokensFile) overwrites the tokens file using only successfully parsed entries and thus drops any malformed/raw entries; change the flow so malformed/raw entries are preserved: update readTokensFile (or add a companion reader) to return both parsed tokens and the unparsed/raw entries (or raw file content), then in saveTokenForIntegration locate existingIndex within the parsed tokens, replace or append the parsed entry as you do now, and finally write back by merging the updated parsed tokens with the preserved raw/unparsed entries (so writeTokensFile receives a combined list or implement a writer that accepts parsed + raw) instead of discarding the raw entries. Use the symbols saveTokenForIntegration, readTokensFile, writeTokensFile, tokens, and entry to locate the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/commands/integrations/auth-integration.test.ts`:
- Around line 13-15: The test mock for '../../federated-auth/oauth-local-server'
is missing the DEFAULT_OAUTH_PORT export and the expectation for runOAuthFlow is
missing the port argument; update the vi.mock call to export both runOAuthFlow
and DEFAULT_OAUTH_PORT (matching the real module) and change the runOAuthFlow
expectation(s) (references to runOAuthFlow in the test) to include the port
parameter when comparing calls/args so the mocked shape and assertions match the
real call signature.
In `@packages/cli/src/federated-auth/oauth-local-server.ts`:
- Around line 74-83: The 5-minute flowTimeout is only cleared in one place,
leaving timers running on early errors; update the completion paths to always
clear the timer by calling clearTimeout(flowTimeout) wherever the flow ends: add
clearTimeout(flowTimeout) inside the reject function (alongside closeServer and
pReject) and also ensure any success path (where pResolve is called) and other
error handlers (the code around verify and the handlers referenced in the
comment) clearTimeout(flowTimeout) before closing the server or
resolving/rejecting so the timeout cannot leak.
- Around line 165-168: The error handler passed to open(startURL).catch uses an
invalid instanceof check (err instanceof err) which throws; update the catch
callback in oauth-local-server.ts to test the thrown value using "err instanceof
Error" and log err.message (or String(err) if not an Error) accordingly so the
handler for open(startURL) safely prints the error message.
---
Duplicate comments:
In `@packages/cli/src/federated-auth/federated-auth-tokens.ts`:
- Around line 171-173: Replace the word "Upserts" in the comment block that
starts with "Save or update a token entry for an integration." with a
CSpell-friendly phrase; for example change "Upserts by integrationId - replaces
existing or appends." to "Performs an upsert by integrationId — replaces
existing or appends." so the comment above the token save/update function uses
common words and passes the spellchecker.
- Around line 174-184: The current saveTokenForIntegration (which calls
readTokensFile and writeTokensFile) overwrites the tokens file using only
successfully parsed entries and thus drops any malformed/raw entries; change the
flow so malformed/raw entries are preserved: update readTokensFile (or add a
companion reader) to return both parsed tokens and the unparsed/raw entries (or
raw file content), then in saveTokenForIntegration locate existingIndex within
the parsed tokens, replace or append the parsed entry as you do now, and finally
write back by merging the updated parsed tokens with the preserved raw/unparsed
entries (so writeTokensFile receives a combined list or implement a writer that
accepts parsed + raw) instead of discarding the raw entries. Use the symbols
saveTokenForIntegration, readTokensFile, writeTokensFile, tokens, and entry to
locate the code to change.
In `@packages/cli/src/federated-auth/oauth-local-server.ts`:
- Around line 161-169: The call to app.listen(...) assigns server but doesn’t
attach an 'error' handler, so bind failures (e.g., EADDRINUSE) crash unclearly;
update the server returned by app.listen (the server variable) to attach
server.on('error', ...) immediately after creation (or supply an error callback
to app.listen) and handle known errors by logging a clear message via log(...)
and exiting/cleaning up (handle error.code like 'EADDRINUSE' and other errors by
logging error.message/stack and process.exit(1) or rejecting the promise used by
oauth-local-server.ts); reference server and app.listen in your change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d7c149f0-83a7-4107-8c4f-11de79da5ebb
📒 Files selected for processing (5)
packages/cli/src/commands/integrations/auth-integration.test.tspackages/cli/src/commands/integrations/auth-integration.tspackages/cli/src/federated-auth/federated-auth-tokens.tspackages/cli/src/federated-auth/oauth-local-server.test.tspackages/cli/src/federated-auth/oauth-local-server.ts
| vi.mock('../../federated-auth/oauth-local-server', () => ({ | ||
| runOAuthFlow: vi.fn(), | ||
| })) |
There was a problem hiding this comment.
Fix oauth-local-server mock exports and expected runOAuthFlow args.
The mock at Lines 13-15 omits DEFAULT_OAUTH_PORT, which breaks module loading in CI. Also, Line 97 expectation should include port to match the real call.
🛠 Suggested fix
-vi.mock('../../federated-auth/oauth-local-server', () => ({
- runOAuthFlow: vi.fn(),
-}))
+vi.mock('../../federated-auth/oauth-local-server', async importOriginal => {
+ const actual = await importOriginal<typeof import('../../federated-auth/oauth-local-server')>()
+ return {
+ ...actual,
+ runOAuthFlow: vi.fn(),
+ }
+})
-import { runOAuthFlow } from '../../federated-auth/oauth-local-server'
+import { DEFAULT_OAUTH_PORT, runOAuthFlow } from '../../federated-auth/oauth-local-server'
expect(mockRunOAuthFlow).toHaveBeenCalledWith({
integrationId: 'trino-prod',
authUrl: 'https://idp.example.com/authorize',
tokenUrl: 'https://idp.example.com/token',
clientId: 'my-client-id',
clientSecret: 'my-client-secret',
+ port: DEFAULT_OAUTH_PORT,
})Also applies to: 28-29, 97-103
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/commands/integrations/auth-integration.test.ts` around lines
13 - 15, The test mock for '../../federated-auth/oauth-local-server' is missing
the DEFAULT_OAUTH_PORT export and the expectation for runOAuthFlow is missing
the port argument; update the vi.mock call to export both runOAuthFlow and
DEFAULT_OAUTH_PORT (matching the real module) and change the runOAuthFlow
expectation(s) (references to runOAuthFlow in the test) to include the port
parameter when comparing calls/args so the mocked shape and assertions match the
real call signature.
| const reject = (err: Error) => { | ||
| closeServer() | ||
| pReject(err) | ||
| } | ||
|
|
||
| const flowTimeoutMs = 5 * 60 * 1000 | ||
| const flowTimeout = setTimeout(() => { | ||
| reject(new Error('OAuth flow timed out. Please try again.')) | ||
| }, flowTimeoutMs) | ||
|
|
There was a problem hiding this comment.
Clear flow timeout on every completion path.
The timer is cleared at Line 102 only. Errors before verify still leave a live 5-minute timeout.
🛠 Suggested fix
return new Promise<FederatedAuthTokenEntry>((pResolve, pReject) => {
let server: Server | null = null
+ let flowTimeout: NodeJS.Timeout | null = null
+
+ const clearFlowTimeout = () => {
+ if (flowTimeout != null) {
+ clearTimeout(flowTimeout)
+ flowTimeout = null
+ }
+ }
const closeServer = () => {
if (server != null) {
server.closeAllConnections()
server.close()
server = null
}
}
const resolve = (entry: FederatedAuthTokenEntry) => {
+ clearFlowTimeout()
closeServer()
pResolve(entry)
}
const reject = (err: Error) => {
+ clearFlowTimeout()
closeServer()
pReject(err)
}
const flowTimeoutMs = 5 * 60 * 1000
- const flowTimeout = setTimeout(() => {
+ flowTimeout = setTimeout(() => {
reject(new Error('OAuth flow timed out. Please try again.'))
}, flowTimeoutMs)Also applies to: 95-103, 143-152
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/federated-auth/oauth-local-server.ts` around lines 74 - 83,
The 5-minute flowTimeout is only cleared in one place, leaving timers running on
early errors; update the completion paths to always clear the timer by calling
clearTimeout(flowTimeout) wherever the flow ends: add clearTimeout(flowTimeout)
inside the reject function (alongside closeServer and pReject) and also ensure
any success path (where pResolve is called) and other error handlers (the code
around verify and the handlers referenced in the comment)
clearTimeout(flowTimeout) before closing the server or resolving/rejecting so
the timeout cannot leak.
| open(startURL).catch(err => { | ||
| log('Error opening browser:') | ||
| log(err instanceof err ? err.message : String(err)) | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/cli/src/federated-auth/oauth-local-server.ts | sed -n '165,168p'Repository: deepnote/deepnote
Length of output: 231
🏁 Script executed:
rg -n "instanceof err" packages/cli/src/federated-auth/oauth-local-server.tsRepository: deepnote/deepnote
Length of output: 124
Fix invalid instanceof check in error logging.
Line 167 uses err instanceof err, which throws TypeError at runtime since the right operand must be a constructor. Use err instanceof Error instead.
Fix
open(startURL).catch(err => {
log('Error opening browser:')
- log(err instanceof err ? err.message : String(err))
+ log(err instanceof Error ? err.message : String(err))
})🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli/src/federated-auth/oauth-local-server.ts` around lines 165 -
168, The error handler passed to open(startURL).catch uses an invalid instanceof
check (err instanceof err) which throws; update the catch callback in
oauth-local-server.ts to test the thrown value using "err instanceof Error" and
log err.message (or String(err) if not an Error) accordingly so the handler for
open(startURL) safely prints the error message.
Summary by CodeRabbit
New Features
Tests