Skip to content

LEGLINK-620: Add-Secret-Key-to-Vendor - #1788

Merged
arianamihailescu merged 14 commits into
devfrom
LEGLINK-620
Aug 5, 2026
Merged

LEGLINK-620: Add-Secret-Key-to-Vendor#1788
arianamihailescu merged 14 commits into
devfrom
LEGLINK-620

Conversation

@arianamihailescu

@arianamihailescu arianamihailescu commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🛠️ Description of Changes

Lets an administrator associate a vendor with the Azure Key Vault secret holding its PEM signing key

🧪 Testing Performed

Tested locally

🧑‍🔬 Unit Testing

  • [ x] I have written or updated unit tests to cover my changes
  • Coverage: 4.9%

📓 Documentation Updated

Please update any relevant sections in the project documentation that were impacted by the changes in the PR.

Summary by CodeRabbit

  • New Features

    • Vendor authentication settings now support an optional signing key secret ID.
    • Secret IDs can be entered, cleared, and updated when creating or editing vendors.
    • Vendor details and versions now display the configured authentication settings.
    • Vendor edits are always available from the dashboard.
  • Bug Fixes

    • Vendor authentication settings are now preserved correctly during creation and updates, including when no new secret is provided.

Captures the decisions behind associating a Key Vault secret with a vendor,
so the reasoning survives outside a chat log: why the association is
vendor-scoped rather than facility-scoped (Veradigm needs a different key
generation algorithm than Epic and Cerner), and why the stored value is the
Key Vault secret id rather than a JWKS kid, which LEGLINK-63's title
conflates.

Also records what this ticket deliberately cannot do. The update endpoint
that persists a secret id is owned by neither LEGLINK-620 nor LEGLINK-743,
so the UI is built against a single isolated service method. LEGLINK-63's
audit-trail acceptance criterion is produced from backend managers onto
Kafka and no UI change can satisfy it.

Claude-Session: https://claude.ai/code/session_01STFKxmDaJenKVny6WUoWKo
Link signs the JWT that Data Acquisition presents to Epic and Cerner during
client-credentials auth. LEGLINK-14 moved the PEM into Key Vault; this makes
the association explicit and vendor-scoped, because Veradigm needs a
different key generation algorithm and so cannot share a key.

Add an edit path to Vendor Management. The dashboard grows a Secret ID
column that reads "Not set" when empty and a per-row edit action; the form
grows a JWT / Authentication panel holding the Key Vault Secret ID, expanded
when a vendor already has one so existing configuration is visible without
hunting. An emptied box travels as undefined rather than "", so clearing the
association reads as absent rather than set-to-empty.

No update endpoint exists yet. The Vendor model is moving out of
Normalization into Tenant under LEGLINK-743, whose acceptance criteria cover
list, add and delete but not update, so this operation is owned by neither
ticket. VendorService.updateVendor is the single place that knows the route:
when the contract lands, that method is the only edit required. A
config-flagged dual path was considered and rejected as permanent complexity
bought against a decision expected within days.

Two defects in files this already touches: createVendor was typed as
IVendorConfigModel while callers pass a name string, which interpolated
"[object Object]" into the URL for anything else; and getVendors never
cleared its loading flag on success.

Deferred: the mocked Playwright spec the design calls for. That harness
arrives with PR #1773, which is not yet merged into dev, so there is nowhere
on this branch for the spec to live. LEGLINK-63's audit-trail criterion is
also outstanding -- audit events are produced from backend managers onto
Kafka, so it belongs with the update endpoint rather than here.

16 unit specs pass; the app builds clean.

Claude-Session: https://claude.ai/code/session_01STFKxmDaJenKVny6WUoWKo
…itly

Two defects found reviewing 4b894e1 against the design.

ErrorHandlingService raises its own toastr before rethrowing, so once the
save paths began emitting failure to the dialog -- which shows a snackbar and
stays open so the admin's input survives -- one failed save reported itself
twice, toastr bottom-full-width and snackbar top-right. Saves now route
through handleSaveError, which suppresses the toastr and leaves the dialog as
the single surface. List and delete keep theirs, having no dialog to carry
the news. The rethrown error still carries the sanitized message either way.

Clearing a key sent secretId as undefined, which JSON.stringify drops, so the
field never reached the wire. An absent field reads as "leave unchanged" to
any endpoint with partial-update semantics, which would have made clearing an
association succeed visibly and do nothing. It now travels as an explicit
null, and the design's open items record that the backend must honour null as
"remove the association" when the contract is settled.

Adds vendor.service.spec.ts, the service having had no direct coverage: the
update route and body, a cleared key surviving serialization, name escaping
in the create route, and the toastr suppressed for saves but kept for list
and delete. Both new behaviours fail against the previous code -- args[1] was
absent rather than false, and secretId was undefined rather than null.

21 unit specs pass; the app builds clean.

Claude-Session: https://claude.ai/code/session_01CX9BzMrPhTzGSakXYDaAVa
A failed createVendor had no test. Assert it emits a single failure with
the error message and does not fall through into the update path.

Claude-Session: https://claude.ai/code/session_01TDPYGwkQDCXConLTuyU4is
VendorController exposes list, add and delete only -- no PUT -- so the edit
dialog added on this branch would save into a 404. Put the edit button and
onEdit behind a vendorEditEnabled config flag, shipped off, following the
existing AppConfig boolean pattern. Flip it once the update contract,
including clearing secretId with null, is confirmed.

Claude-Session: https://claude.ai/code/session_01TDPYGwkQDCXConLTuyU4is
Validation lives on Admin.BFF, which already holds an ISecretManager and is
independent of the Vendor model's move to Tenant. Adds ISecretInspector and
PemSigningKeyValidator to Shared; the UI warns inline on blur and on save
without ever blocking the save.

Claude-Session: https://claude.ai/code/session_01TDPYGwkQDCXConLTuyU4is
Eight tasks, TDD throughout: characterize EpicAuth's PKCS#8 behavior, then
PemSigningKeyValidator and ISecretInspector in Shared, the Admin.BFF endpoint,
the Angular service call, and the form's blur/save warnings.

Claude-Session: https://claude.ai/code/session_01TDPYGwkQDCXConLTuyU4is
Vendor moved into Tenant under LEGLINK-743 carrying only Id and Name, so there
was nowhere to record the Key Vault secret holding a vendor's PEM signing key.
LEGLINK-63 scopes that key to the vendor rather than the facility, because the
key generation algorithm differs by EHR.

Stored as a JSON column rather than a plain one so later vendor-level auth
settings need no migration, following the AuthenticationConfiguration precedent
in DataAcquisitionDbContext. Only the signing key lives here: TokenUrl, Audience
and ClientId are per-EHR-instance and stay on the facility's authentication
configuration, where EpicAuth already reads them.

The value converter carries an explicit ValueComparer. Without one EF snapshots
the property by reference, so mutating a field on the existing instance is never
detected and SaveChanges writes nothing -- a test covers that specifically.

Update treats a missing authentication object as "leave unchanged", matching how
Name already behaves, so a caller that omits it cannot wipe a configured key.
Clearing a key means sending the object with a null inside it.

Vendor versions expose the parent vendor's settings as a read-only projection,
so consumers holding only a vendor version id -- as Data Acquisition will -- can
resolve the key in one call while writes stay on the vendor.

EpicAuth is untouched: it still derives {facilityId}-pem, so nothing changes at
runtime until the fallback rule between vendor and facility keys is settled.

Claude-Session: https://claude.ai/code/session_01QoUHyt1ALkruCbuYxHSXiB
LEGLINK-743 deleted Normalization's VendorController when it moved the model to
Tenant, but left this service calling the old routes. Every vendor operation in
Admin.UI has been hitting a controller that no longer exists.

The API nests the signing key under authentication; the vendor screens work with
a flat secretId. Translating at the gateway keeps that difference out of the
components, so a second vendor-level setting only touches this file.

Create now carries the secret id. The add form has always shown the field, but
the create branch sent the name alone, so anything typed there was silently
discarded behind a success message. Both write paths build the same payload
before branching, which is what stops them drifting apart again.

authentication is always sent, including when the key is being cleared: the
Tenant manager reads an absent object as "leave unchanged", so omitting it would
make a clear no-op. Null inside the object is what removes the association.

The vendorEditEnabled flag is gone with it. It existed only to keep the edit
button hidden while no update endpoint existed, and PUT /api/vendor/{id} now
does, so the gate has nothing left to protect.

Claude-Session: https://claude.ai/code/session_01QoUHyt1ALkruCbuYxHSXiB
The vendor signing-key design and the secret-id validation design and plan were
working notes for this change, not reference material the repository needs to
carry. They stay recoverable through history.

Claude-Session: https://claude.ai/code/session_01QoUHyt1ALkruCbuYxHSXiB
The previous commit swept in five files that belong to a local development
setup rather than to this change. Both appsettings.Development.json files in
particular replaced the committed SQLEXPRESS defaults with a machine-specific
SQL Server instance and a plaintext sa password, which would have become the
checked-in default for everyone.

Restores all five to their dev contents so the branch carries only the vendor
signing key work. The AGENTS.md documentation, the check_health.sh compose fix
and the Admin.UI Dockerfile npm layer-caching fix are worth landing, but each
on its own terms rather than inside this ticket.

Claude-Session: https://claude.ai/code/session_01QoUHyt1ALkruCbuYxHSXiB
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fbd7fb98-0a0e-4da3-abad-2ae96a2d2cb8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Vendor authentication settings

Layer / File(s) Summary
Authentication contract and persistence
DotNet/Shared/Application/Models/Tenant/*, DotNet/Tenant/Data/*, DotNet/Tenant/Migrations/*, DotNet/ServiceTests/UnitTests/Tenant/VendorModelBindingTests.cs
Adds nullable signing-key secret settings to vendor models. Stores the settings as JSON in the Vendor table. Adds EF change tracking and binding tests.
Tenant API propagation
DotNet/Tenant/Business/*/Vendor*.cs, DotNet/Tenant/Controllers/VendorController.cs, DotNet/ServiceTests/IntegrationTests/Tenant/Vendor*Tests.cs
Passes authentication settings through create and update operations. Returns the settings from vendor and vendor-version queries. Adds persistence and controller integration tests.
Admin UI vendor API and form
Web/Admin.UI/src/app/interfaces/tenant/vendor-interface.ts, Web/Admin.UI/src/app/services/gateway/vendor/*, Web/Admin.UI/src/app/components/vendor/vendor-config-form/*
Switches vendor operations to tenant routes. Maps nested authentication data to secretId and sends it during create and update requests.
Vendor edit availability
Web/Admin.UI/src/app/components/vendor/vendor-dashboard/*, Web/Admin.UI/src/app/services/app-config.service.ts, Web/Admin.UI/src/assets/app.config.json
Always displays the edit action and removes the vendorEditEnabled dependency, interface field, and configuration setting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title refers to the main change (adding vendor signing-key configuration) but uses an abbreviated format that lacks clarity. Use a more descriptive title such as 'Add signing-key secret ID support for vendors' to clearly convey the feature without requiring domain-specific context.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description covers the main feature but omits specifics about testing scope and leaves the documentation section incomplete with no actual updates documented.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch LEGLINK-620

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@arianamihailescu arianamihailescu changed the title Leglink 620 LEGLINK-620: Add-Secret-Key-to-Vendor Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
DotNet/ServiceTests/IntegrationTests/Tenant/VendorManagerTests.cs (1)

114-131: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add an explicit-clear persistence test.

Set a signing key first. Then update the vendor with Authentication = new VendorAuthenticationSettings { SigningKeySecretId = null }. Assert that the reloaded vendor has no signing key. This verifies the clear operation that this change introduces.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/ServiceTests/IntegrationTests/Tenant/VendorManagerTests.cs` around
lines 114 - 131, Extend
UpdateVendor_WithoutAuthentication_LeavesTheConfiguredSecretIdIntact with a
separate explicit-clear scenario: configure a signing key, update the vendor
using VendorAuthenticationSettings with SigningKeySecretId set to null, reload
it, and assert that the persisted signing key is absent.
Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.html (1)

36-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Declare that the edit control is not a submit button.

Add type="button" to this mat-icon-button. The control calls onEdit(vendor) and does not submit a form. Without the native attribute, HTML defaults the button to submit when an ancestor form exists.

Proposed fix
               <button
                 mat-icon-button
+                type="button"
                 color="primary"

Based on learnings, Angular Material buttons do not automatically set type="button" on the underlying <button>; non-submit actions should declare it explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.html`
around lines 36 - 45, Add type="button" to the edit mat-icon-button invoking
onEdit(vendor), ensuring it remains a non-submit control when rendered inside an
ancestor form.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DotNet/Tenant/Controllers/VendorController.cs`:
- Around line 100-104: Validate every non-null Authentication.SigningKeySecretId
in VendorController before invoking CreateVendorAsync or UpdateVendorAsync,
rejecting empty, whitespace-only, oversized, and unsupported values with a 400
response containing validation details. Preserve null as a valid
explicit-clearing value, and add focused XUnit coverage for invalid
create/update input and null clearing; apply the changes at
DotNet/Tenant/Controllers/VendorController.cs lines 100-104 and 136-140.

---

Nitpick comments:
In `@DotNet/ServiceTests/IntegrationTests/Tenant/VendorManagerTests.cs`:
- Around line 114-131: Extend
UpdateVendor_WithoutAuthentication_LeavesTheConfiguredSecretIdIntact with a
separate explicit-clear scenario: configure a signing key, update the vendor
using VendorAuthenticationSettings with SigningKeySecretId set to null, reload
it, and assert that the persisted signing key is absent.

In
`@Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.html`:
- Around line 36-45: Add type="button" to the edit mat-icon-button invoking
onEdit(vendor), ensuring it remains a non-submit control when rendered inside an
ancestor form.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c57dd76-c16d-499a-86d6-cf8cd34dcd39

📥 Commits

Reviewing files that changed from the base of the PR and between fabd296 and e695d2e.

📒 Files selected for processing (29)
  • DotNet/ServiceTests/IntegrationTests/Tenant/VendorControllerTests.cs
  • DotNet/ServiceTests/IntegrationTests/Tenant/VendorManagerTests.cs
  • DotNet/ServiceTests/IntegrationTests/Tenant/VendorQueriesTests.cs
  • DotNet/ServiceTests/UnitTests/Tenant/VendorModelBindingTests.cs
  • DotNet/Shared/Application/Models/Tenant/CreateVendorModel.cs
  • DotNet/Shared/Application/Models/Tenant/UpdateVendorModel.cs
  • DotNet/Shared/Application/Models/Tenant/VendorAuthenticationSettings.cs
  • DotNet/Shared/Application/Models/Tenant/VendorModel.cs
  • DotNet/Shared/Application/Models/Tenant/VendorVersionModel.cs
  • DotNet/Tenant/Business/Managers/VendorManager.cs
  • DotNet/Tenant/Business/Queries/VendorQueries.cs
  • DotNet/Tenant/Controllers/VendorController.cs
  • DotNet/Tenant/Data/Entities/Vendor.cs
  • DotNet/Tenant/Data/Repository/TenantDbContext.cs
  • DotNet/Tenant/Migrations/20260804215404_AddVendorAuthentication.Designer.cs
  • DotNet/Tenant/Migrations/20260804215404_AddVendorAuthentication.cs
  • DotNet/Tenant/Migrations/TenantDbContextModelSnapshot.cs
  • Web/Admin.UI/src/app/components/vendor/vendor-config-form/vendor-config-form.component.spec.ts
  • Web/Admin.UI/src/app/components/vendor/vendor-config-form/vendor-config-form.component.ts
  • Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.html
  • Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.spec.ts
  • Web/Admin.UI/src/app/components/vendor/vendor-dashboard/vendor-dashboard.component.ts
  • Web/Admin.UI/src/app/interfaces/tenant/vendor-interface.ts
  • Web/Admin.UI/src/app/interfaces/vendor/vendor-config-model.interface.ts
  • Web/Admin.UI/src/app/services/app-config.service.ts
  • Web/Admin.UI/src/app/services/gateway/vendor/vendor.service.spec.ts
  • Web/Admin.UI/src/app/services/gateway/vendor/vendor.service.ts
  • Web/Admin.UI/src/assets/app.config.json
  • docs/superpowers/specs/2026-08-03-vendor-signing-key-secret-id-design.md
💤 Files with no reviewable changes (4)
  • Web/Admin.UI/src/assets/app.config.json
  • Web/Admin.UI/src/app/interfaces/vendor/vendor-config-model.interface.ts
  • docs/superpowers/specs/2026-08-03-vendor-signing-key-secret-id-design.md
  • Web/Admin.UI/src/app/services/app-config.service.ts

Comment thread DotNet/Tenant/Controllers/VendorController.cs
@arianamihailescu
arianamihailescu merged commit 728bd30 into dev Aug 5, 2026
18 checks passed
@arianamihailescu
arianamihailescu deleted the LEGLINK-620 branch August 5, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants