Skip to content

Commit 5142237

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 506307d commit 5142237

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,
@@ -180,4 +181,18 @@ describe('v2 credential provider catalog contract', () => {
180181
expect.objectContaining({ providerId: CLAUDE_PROVIDER_ID, available: false })
181182
)
182183
})
184+
185+
it('tells API callers what each saved database connection transport-security mode trades away', async () => {
186+
const providers = await listProviders()
187+
188+
for (const descriptor of Object.values(DATABASE_CONNECTION_DESCRIPTORS)) {
189+
const provider = providers.find(
190+
(entry) => entry.type === 'service_account' && entry.providerId === descriptor.providerId
191+
)
192+
const hint = provider?.fields.find((field) => field.id === 'serviceAccountJson')?.hint
193+
for (const field of descriptor.tls) {
194+
expect(hint).toContain(field.hint)
195+
}
196+
}
197+
})
183198
})

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ function getServiceAccountDescriptor(providerId: string): ServiceAccountDescript
278278
`${field.id} (${field.options.map((option) => option.value).join(', ')}; default ${field.defaultValue})`
279279
)
280280
.join(' and ')
281+
const tlsTradeoffs = database.tls.map((field) => `${field.id}: ${field.hint}`).join(' ')
281282
return {
282283
name: `${database.label} connection`,
283284
description: `Connect to ${database.label} with a saved host, database, login, and transport-security settings.`,
@@ -297,7 +298,7 @@ function getServiceAccountDescriptor(providerId: string): ServiceAccountDescript
297298
required: true,
298299
secret: true,
299300
multiline: true,
300-
hint: `JSON object with host, port, database, username, password, and optional ${tls}. Sim verifies it with SELECT 1 before saving.`,
301+
hint: `JSON object with host, port, database, username, password, and optional ${tls}. Sim verifies it with SELECT 1 before saving. ${tlsTradeoffs}`,
301302
},
302303
],
303304
}

0 commit comments

Comments
 (0)