Skip to content

Commit 164e628

Browse files
committed
fix(credentials): validate the saved-connection port in the form and carry TLS tradeoffs into the provider catalog
The connection modal now refuses a port that is not a whole number from 1 to 65535 and says so beside the field, instead of a generic verify failure. The v2 provider catalog hint for saved PostgreSQL, MySQL, and SQL Server connections now includes each transport-security field's tradeoff, so API callers see what Preferred, Encrypt, and Trust server certificate give up. The integration fixture decodes the database name from TEST_DATABASE_URL.
1 parent 206b807 commit 164e628

4 files changed

Lines changed: 27 additions & 4 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/database-connection-account-modal.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ interface DatabaseConnectionAccountModalProps {
4242
submitPending?: boolean
4343
}
4444

45+
const MAX_PORT = 65535
46+
4547
interface LoginFields {
4648
host: string
4749
port: string
@@ -98,10 +100,12 @@ function DatabaseConnectionAccountForm({
98100
createCredential.isPending || updateCredential.isPending || submitPending === true
99101
const action = credentialId ? 'Reconnect' : 'Add'
100102
const title = `${action} ${descriptor.label} connection`
103+
const port = login.port.trim()
104+
const portIsValid = /^\d+$/.test(port) && Number(port) >= 1 && Number(port) <= MAX_PORT
101105
const disabled =
102106
isPending ||
103107
!login.host.trim() ||
104-
!login.port.trim() ||
108+
!portIsValid ||
105109
!login.database.trim() ||
106110
!login.username.trim() ||
107111
!login.password
@@ -114,7 +118,7 @@ function DatabaseConnectionAccountForm({
114118
setError(null)
115119
const serviceAccountJson = JSON.stringify({
116120
host: login.host.trim(),
117-
port: Number(login.port),
121+
port: Number(port),
118122
database: login.database.trim(),
119123
username: login.username.trim(),
120124
password: login.password,
@@ -182,6 +186,9 @@ function DatabaseConnectionAccountForm({
182186
placeholder={String(descriptor.defaultPort)}
183187
required
184188
autoComplete='off'
189+
error={
190+
port !== '' && !portIsValid ? `Enter a whole number from 1 to ${MAX_PORT}.` : undefined
191+
}
185192
/>
186193
<ChipModalField
187194
type='input'

‎apps/sim/lib/credentials/__integration__/database-connection.integration.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const databaseUrl = new URL(readTestDatabaseUrl())
2626
const connection = {
2727
host: '127.0.0.1',
2828
port: Number(databaseUrl.port || 5432),
29-
database: databaseUrl.pathname.slice(1),
29+
database: decodeURIComponent(databaseUrl.pathname.slice(1)),
3030
username: decodeURIComponent(databaseUrl.username),
3131
// The disposable server trusts loopback logins, so any non-empty password is accepted.
3232
password: decodeURIComponent(databaseUrl.password) || 'unused-under-trust-auth',

‎apps/sim/lib/credentials/application/provider-catalog-contract.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { v2ListCredentialProvidersContract } from '@/lib/api/contracts/v2/creden
2525
import { env } from '@/lib/core/config/env'
2626
import { listCredentialProviders } from '@/lib/credentials/application/list-credential-providers'
2727
import { listCredentialProviderCatalog } from '@/lib/credentials/application/provider-catalog'
28+
import { DATABASE_CONNECTION_DESCRIPTORS } from '@/lib/credentials/database-connections/descriptors'
2829
import { getMissingServiceAccountFields } from '@/lib/credentials/service-account-fields'
2930
import {
3031
isWorkloadIdentityServiceAccountProviderId,
@@ -198,4 +199,18 @@ describe('v2 credential provider catalog contract', () => {
198199
expect.objectContaining({ providerId: CLAUDE_PROVIDER_ID, available: false })
199200
)
200201
})
202+
203+
it('tells API callers what each saved database connection transport-security mode trades away', async () => {
204+
const providers = await listProviders()
205+
206+
for (const descriptor of Object.values(DATABASE_CONNECTION_DESCRIPTORS)) {
207+
const provider = providers.find(
208+
(entry) => entry.type === 'service_account' && entry.providerId === descriptor.providerId
209+
)
210+
const hint = provider?.fields.find((field) => field.id === 'serviceAccountJson')?.hint
211+
for (const field of descriptor.tls) {
212+
expect(hint).toContain(field.hint)
213+
}
214+
}
215+
})
201216
})

‎apps/sim/lib/credentials/application/provider-catalog.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ function getServiceAccountDescriptor(providerId: string): ServiceAccountDescript
285285
`${field.id} (${field.options.map((option) => option.value).join(', ')}; default ${field.defaultValue})`
286286
)
287287
.join(' and ')
288+
const tlsTradeoffs = database.tls.map((field) => `${field.id}: ${field.hint}`).join(' ')
288289
return {
289290
name: `${database.label} connection`,
290291
description: `Connect to ${database.label} with a saved host, database, login, and transport-security settings.`,
@@ -304,7 +305,7 @@ function getServiceAccountDescriptor(providerId: string): ServiceAccountDescript
304305
required: true,
305306
secret: true,
306307
multiline: true,
307-
hint: `JSON object with host, port, database, username, password, and optional ${tls}. Sim verifies it with SELECT 1 before saving.`,
308+
hint: `JSON object with host, port, database, username, password, and optional ${tls}. Sim verifies it with SELECT 1 before saving. ${tlsTradeoffs}`,
308309
},
309310
],
310311
}

0 commit comments

Comments
 (0)