{"record":{"id":"97705fb6f37f8961","repo":"Dokploy/dokploy","slug":"bad-request-97705f","errorCode":"BAD_REQUEST","errorMessage":"Credentials must be re-entered when changing the provider type","messagePattern":"Credentials must be re-entered when changing the provider type","errorType":"exception","errorClass":"TRPCError","httpStatus":400,"severity":"warning","filePath":"packages/server/src/services/dns-provider.ts","lineNumber":42,"sourceCode":"): DnsProviderConfig => {\n\tconst masked: Record<string, unknown> = { ...config };\n\tfor (const field of SENSITIVE_FIELDS[config.providerType]) {\n\t\tif (masked[field]) {\n\t\t\tmasked[field] = DNS_SECRET_MASK;\n\t\t}\n\t}\n\treturn masked as DnsProviderConfig;\n};\n\nexport const mergeDnsProviderConfig = (\n\tincoming: DnsProviderConfig,\n\texisting: DnsProviderConfig,\n): DnsProviderConfig => {\n\tconst merged: Record<string, unknown> = { ...incoming };\n\tfor (const field of SENSITIVE_FIELDS[incoming.providerType]) {\n\t\tif (merged[field] === DNS_SECRET_MASK) {\n\t\t\tif (incoming.providerType !== existing.providerType) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Credentials must be re-entered when changing the provider type\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tmerged[field] = (existing as Record<string, unknown>)[field];\n\t\t}\n\t}\n\treturn merged as DnsProviderConfig;\n};\n\nconst isUniqueNameViolation = (error: unknown) =>\n\terror instanceof Error && error.message.includes(\"dns_provider_org_name_idx\");\n\nexport const createDnsProvider = async (\n\tinput: z.infer<typeof apiCreateDnsProvider>,\n\torganizationId: string,\n) => {","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/Dokploy/dokploy/blob/546686ea3587f12ec5652217dedd9f7960fb6d15/packages/server/src/services/dns-provider.ts#L24-L60","documentation":"mergeDnsProviderConfig merges an incoming provider config with stored secrets: when a sensitive field arrives as the mask placeholder (DNS_SECRET_MASK) and the provider type is changing, it cannot backfill the old provider's secret, so it throws BAD_REQUEST. This prevents silently storing the mask or copying secrets between incompatible providers.","triggerScenarios":"Updating a DNS provider while changing providerType (e.g. Cloudflare → Route53) and leaving masked credential fields as-is (the UI sends the mask for unchanged secrets). Secrets for the new provider type must be re-entered.","commonSituations":"Editing a DNS provider in the UI and switching the provider dropdown without re-typing API credentials; API clients PATCHing providerType plus masked fields copied from a GET response.","solutions":["Re-enter the credentials (API key/token/secret) for the new provider type when switching it","Or keep the same providerType if you only meant to update non-secret fields","If building a client: omit sensitive fields entirely instead of sending the mask value when changing providerType"],"exampleFix":"// before (client sends mask while changing type)\n{ providerType: \"route53\", apiKey: \"********\" }\n// after (send real secret, or omit field)\n{ providerType: \"route53\", apiKey: \"AKIA...\" }","handlingStrategy":"validation","validationCode":"const changingType = input.providerType !== existing.providerType;\nconst masked = SENSITIVE_FIELDS[input.providerType].some((f) => input[f] === DNS_SECRET_MASK);\nif (changingType && masked) throw new Error(\"Re-enter credentials when changing provider type\");","typeGuard":"const isMaskedConfig = (cfg: Record<string, unknown>) =>\n  Object.values(cfg).some((v) => v === DNS_SECRET_MASK);","tryCatchPattern":"try { await updateDnsProviderConfig(input); } catch (e) { if (e instanceof TRPCError && e.code === \"BAD_REQUEST\") { /* prompt user for fresh credentials */ } }","preventionTips":["Omit masked secret fields instead of echoing them from GET responses","Always re-enter credentials when switching provider type","UI: clear secret inputs when the provider dropdown changes"],"tags":["dns","validation","secrets","provider-config"],"backgroundTag":"secret-mask-mismatch-on-provider-change","analyzedSha":"546686ea3587f12ec5652217dedd9f7960fb6d15","analyzedAt":"2026-08-27T05:18:58.095Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}