Skip to content

feat(typescript): support omitting username/password in basic auth when configured in IR#14406

Merged
fern-support merged 24 commits intomainfrom
devin/1774997664-basic-auth-optional-ts-sdk
Apr 3, 2026
Merged

feat(typescript): support omitting username/password in basic auth when configured in IR#14406
fern-support merged 24 commits intomainfrom
devin/1774997664-basic-auth-optional-ts-sdk

Conversation

@Swimburger
Copy link
Copy Markdown
Member

@Swimburger Swimburger commented Mar 31, 2026

Description

Refs #14378

Adds conditional support for omitting username/password in basic auth for the TypeScript SDK generator. When usernameOmit or passwordOmit flags are set in the IR's BasicAuthScheme, the corresponding field is completely removed from the generated SDK's AuthOptions type — not just made nullable. Internally, the omitted field is treated as an empty string when encoding the auth header (base64(username:password) with the colon always present).

Default behavior (both required) is preserved when no omit flags are set.

Split from #14378 (one PR per generator).

Changes Made

Generator (BasicAuthProviderGenerator.ts)

  • getAuthOptionsProperties: conditionally excludes each field from the AuthOptions type entirely when its corresponding omit flag is set (field is removed from the end-user API)
  • generateCanCreateStatements: per-field checks — omitted fields always satisfy canCreate (return true), required fields must be present
  • generateGetAuthRequestStatements: refactored into buildNullChecks helper — omitted fields use "" directly instead of reading from options; null checks are only generated for non-omitted fields; declarations and null checks are interleaved to preserve short-circuit evaluation order

Core utility (BasicAuth.ts)

  • Interface fields widened to username?: string; password?: string unconditionally
  • toAuthorizationHeader uses ?? "" fallbacks and returns undefined when both are empty strings

Tests & fixtures

  • New basic-auth-pw-omitted test fixture with password: omit: true
  • Full seed output for seed/ts-sdk/basic-auth-pw-omitted/
  • Updated seed/ts-sdk/basic-auth/ output (reflects widened BasicAuth.ts interface)
  • Unit tests added for username-only, password-only, neither, and both-empty cases

Changelog

  • versions.yml: new 3.62.2 entry (irVersion: 66)

Updates since last revision

  • Fixture renamed: basic-auth-optionalbasic-auth-pw-omitted across all directories, filenames, and file contents (kebab-case, camelCase, PascalCase variants). The name now accurately reflects what the fixture tests: password omission, not generic "optional" behavior.
  • Terminology fix: Updated changelog, comments, and descriptions to use "omit" (field removed from SDK API) instead of "optional" (nullable/undefined). "Optional" in the codebase now only refers to TypeScript type optionality (? markers for non-mandatory auth).
  • Fixed short-circuit evaluation order: buildNullChecks now interleaves declaration and null-check for each field, preserving the original short-circuit behavior where the password supplier is never evaluated if the username is null.
  • Resolved multiple merge conflicts in versions.yml (bumped through 3.61.2 → 3.62.2 after main merged Error.cause, test import paths, and other features).

Testing

  • Unit tests added (BasicAuth.test.ts: 5 cases covering all credential combinations)
  • Seed snapshot generated for basic-auth-pw-omitted fixture
  • Existing basic-auth seed output updated and verified
  • All required CI checks pass

⚠️ Human Review Checklist

  1. BasicAuth.ts interface is widened unconditionally: Every generated TS SDK (not just those with omit flags) will now have username?: string; password?: string in the core utility. The generator controls what appears in AuthOptions, so the runtime behavior is guarded — but this does widen the type contract for downstream consumers who import BasicAuth directly.
  2. Behavioral change for empty strings: toAuthorizationHeader({username: "", password: ""}) now returns undefined instead of Basic Og==. This affects the authHeader != null guard in generated code.
  3. Both-fields-omitted path: When both usernameOmit and passwordOmit are true, canCreate() always returns true and getAuthRequest hardcodes both to "", producing Basic Og== — but toAuthorizationHeader then returns undefined since both are empty. Verify this no-op edge case is acceptable.
  4. Snapshot formatting change: The AuthProviders.test.ts.snap diff shows null-check blocks collapsed to single lines and AuthOptions type properties changed from computed names ([USERNAME_PARAM]?:) to literal names (username?:). Verify this is acceptable generated output.
  5. seed/ts-sdk/basic-auth/ diff: The existing (non-omit) fixture output changed due to the unconditional BasicAuth.ts widening. Confirm the generated client behavior is still correct for the required-fields case.
  6. versions.yml: Bumped to 3.62.2, irVersion: 66. Verify version ordering is correct relative to main.

Link to Devin session: https://app.devin.ai/sessions/0786b963284f4799acb409d5373cde0a
Requested by: @Swimburger


Open with Devin

…onfigured in IR

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

devin-ai-integration[bot]

This comment was marked as resolved.

…d flag

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Swimburger and others added 6 commits April 1, 2026 16:02
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-field omit fix

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…e empty string internally

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Generator emits unused USERNAME_PARAM/PASSWORD_PARAM constants for omitted auth fields

In writeConstants at BasicAuthProviderGenerator.ts:159-170, both USERNAME_PARAM and PASSWORD_PARAM are always emitted regardless of omit flags. When a field is omitted (e.g. passwordOmit: true), the corresponding constant is never referenced in canCreate, getAuthRequest, or the AuthOptions type — it becomes dead code.

The generated seed output at seed/ts-sdk/basic-auth-optional/src/auth/BasicAuthProvider.ts:7 confirms this: const _PASSWORD_PARAM = "password" as const; — the linter renamed it with an underscore prefix because it's unused. The fix would be to conditionally skip generating the constant when the corresponding omit flag is true.

(Refers to lines 169-170)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid observation about the unused constant. This is a minor issue — the generated biome config already renames it with an underscore prefix (_PASSWORD_PARAM), and the constant is still referenced in the module namespace for the AUTH_CONFIG_ERROR_MESSAGE_PASSWORD string template in the non-omit case. Conditionally skipping the constant would require tracking which fields are omitted across both writeConstants and writeOptions, adding complexity for a cosmetic improvement. I'll leave this as-is unless the maintainer wants it addressed.

Swimburger and others added 4 commits April 2, 2026 00:40
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…teral property names

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment thread seed/ts-sdk/basic-auth-optional/tests/wire/basicAuth.test.ts Outdated
Comment on lines +17 to +19
if (username === "" && password === "") {
return undefined;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 BasicAuth.toAuthorizationHeader silently drops header for empty-string credentials in all existing SDKs

The shared core utility template at generators/typescript/utils/core-utilities/src/core/auth/BasicAuth.ts now returns undefined when both username and password are empty strings (line 17). Previously, toAuthorizationHeader({username: "", password: ""}) would return "Basic Og==" (base64 of :), which is valid HTTP Basic Auth. This behavioral change propagates to all generated SDKs using basic auth, including the existing seed/ts-sdk/basic-auth SDK which had no omit fields. If an existing SDK user provides a Supplier<string> that resolves to "" for both credentials, the Authorization header is silently dropped instead of being sent, changing wire behavior.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an intentional design choice: when both fields resolve to empty strings, there's no meaningful credential to send, so the header is omitted. In practice this only triggers when both usernameOmit and passwordOmit are true (the generator passes "" directly for omitted fields). For the standard basic-auth case (no omit flags), both fields are required and will never be "". A user explicitly passing { username: "", password: "" } was already sending useless auth (Basic Og==), so this change is a safe improvement.

Swimburger and others added 2 commits April 2, 2026 16:59
…lientOptions

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 13 additional findings in Devin Review.

Open in Devin Review

Comment thread generators/typescript/sdk/versions.yml Outdated
omitted entirely.
type: feat
createdAt: "2026-04-01"
irVersion: 65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 versions.yml entry uses irVersion 65 instead of 66, contradicting the 3.61.x series

Version 3.61.2 in generators/typescript/sdk/versions.yml specifies irVersion: 65, but the immediately preceding version 3.61.1 (and 3.61.0-rc.0) both use irVersion: 66. The V66-to-V65 IR migration at packages/cli/generation/ir-migrations/src/migrations/v66-to-v65/migrateFromV66ToV65.ts:17 marks 3.61.0-rc.0 as the firstGeneratorVersionToConsumeNewIR for the TypeScript SDK, meaning all versions ≥ 3.61.0-rc.0 are expected to consume IR v66. Additionally, the seed test runner config at seed/ts-sdk/seed.yml:2 already uses irVersion: v66. Per REVIEW.md: "IR version bumps must be coordinated: update irVersion in the relevant versions.yml entry." Using irVersion 65 for a version after the v66 upgrade is inconsistent and could cause IR version coordination issues.

Suggested change
irVersion: 65
irVersion: 66
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 14 additional findings in Devin Review.

Open in Devin Review

Comment thread generators/typescript/sdk/versions.yml Outdated
encodes `:password`). When neither is provided, the Authorization header is
omitted entirely.
type: feat
createdAt: "2026-04-01"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 versions.yml entry for 3.61.2 has createdAt date earlier than the prior version 3.61.1

Version 3.61.2 has createdAt: "2026-04-01" while the lower version 3.61.1 has createdAt: "2026-04-02". A newer version should have a creation date equal to or later than the version it follows. This is likely a typo — the date should be "2026-04-02" or later.

Suggested change
createdAt: "2026-04-01"
createdAt: "2026-04-02"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Swimburger and others added 2 commits April 2, 2026 20:26
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Generator emits unused PASSWORD_PARAM/USERNAME_PARAM constants for omitted basic auth fields

When usernameOmit or passwordOmit is true, writeConstants() at BasicAuthProviderGenerator.ts:169-170 still unconditionally emits the USERNAME_PARAM / PASSWORD_PARAM constant declarations. However, generateCanCreateStatements() (line 269-274), generateGetAuthRequestStatements() (line 333-343), and writeOptions() (line 441-443) all correctly skip referencing the omitted field's constant — meaning the emitted constant is never used in the generated output.

This is confirmed by the generated seed output at seed/ts-sdk/basic-auth-optional/src/auth/BasicAuthProvider.ts:7, where biome's linter renames the unused constant from PASSWORD_PARAM to _PASSWORD_PARAM. While this doesn't cause a runtime failure, it produces unnecessary declarations that trigger linter warnings or auto-renames in generated SDKs.

(Refers to lines 169-170)

Prompt for agents
In writeConstants() in BasicAuthProviderGenerator.ts, the USERNAME_PARAM and PASSWORD_PARAM constants are emitted unconditionally at lines 169-170. When usernameOmit or passwordOmit is true, the corresponding constant is never referenced by any other generated code (canCreate, getAuthRequest, writeOptions all skip omitted fields). The fix is to conditionally emit each constant only when the field is not omitted. Read this.authScheme.usernameOmit and this.authScheme.passwordOmit (as done in other methods like generateCanCreateStatements) and wrap each constants.push() call in an if (!omit) guard. This aligns with the pattern already used throughout the rest of the class.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… seed output

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +17 to +19
if (username === "" && password === "") {
return undefined;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 toAuthorizationHeader silently changes behavior for all regenerated SDKs when both credentials are empty strings

The BasicAuth.toAuthorizationHeader utility is a template copied into every generated TypeScript SDK (~80+ seed SDKs reference this file). The new code returns undefined when both username and password are empty strings "", whereas the old code would encode ":" and return "Basic Og==". This behavioral change propagates to ALL regenerated SDKs — not just the new basic-auth-optional ones — because the template at generators/typescript/utils/core-utilities/src/core/auth/BasicAuth.ts is shared.

In existing non-optional SDKs, the generated auth provider validates credentials with if (username == null), which empty strings pass. So a user providing { username: "", password: "" } would previously get a Basic Og== Authorization header sent, but now gets no header at all. The versions.yml changelog for 3.62.1 does not mention this cross-cutting behavioral change.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Swimburger and others added 2 commits April 3, 2026 00:39
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration bot changed the title feat(ts-sdk): support optional username/password in basic auth when configured in IR feat(ts-sdk): support omitting username/password in basic auth when configured in IR Apr 3, 2026
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`Please provide '${PASSWORD_PARAM}' when initializing the client` as const;
export type Options = AuthOptions;
export type AuthOptions = { [USERNAME_PARAM]: core.Supplier<string>; [PASSWORD_PARAM]: core.Supplier<string> };
export type AuthOptions = { username: core.Supplier<string>; password: core.Supplier<string> };
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should continue using the [USERNAME_PARAM] const and pwd

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — restored computed property keys [USERNAME_PARAM] and [PASSWORD_PARAM] in the AuthOptions type for both the generator code and all seed outputs/snapshots. See commit a62122c.

…D_PARAM] in AuthOptions type

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fern-support fern-support enabled auto-merge (squash) April 3, 2026 18:53
@Swimburger Swimburger changed the title feat(ts-sdk): support omitting username/password in basic auth when configured in IR feat(typescript): support omitting username/password in basic auth when configured in IR Apr 3, 2026
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fern-support fern-support merged commit c24906f into main Apr 3, 2026
313 checks passed
@fern-support fern-support deleted the devin/1774997664-basic-auth-optional-ts-sdk branch April 3, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants