Skip to content

feat(legacy): session-auth write adapter — port-forwards, firewall, networks#12

Merged
ChrisonSimtian merged 1 commit into
mainfrom
feat/legacy-write-adapter
Jun 30, 2026
Merged

feat(legacy): session-auth write adapter — port-forwards, firewall, networks#12
ChrisonSimtian merged 1 commit into
mainfrom
feat/legacy-write-adapter

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Adds the legacy write path UnifiSharp has been missing — the #102 / ADR-0003 stopgap until Ubiquiti ships official integration-API write endpoints (rolling out through 2026).

What

UnifiLegacyClient — an [Obsolete], thin, deletion-ready write client over the classic controller REST API (/proxy/network/api/s/<site>/rest/...) for the resources the integration API can't write yet:

  • Port-forwardsList / Create / Delete
  • Firewall groupsList / Create / Delete
  • Networks / VLANsList / Create / Delete

The Kiota-generated integration client is untouched; this is an additive, isolated namespace (UnifiSharp.Legacy). When the official write endpoints land, delete the namespace and switch callers.

Auth: session, not X-API-KEY

The legacy API uses the classic controller sessionPOST /api/auth/loginTOKEN cookie + X-CSRF-Token, reused (and auto-rotated) on subsequent calls, with one transparent re-auth on a 401. This works against the .containers/unifi UniFi OS Server test container (which exposes no scriptable API-key mint) and any UniFi OS gateway. Config via UNIFI_LEGACY_BASE_URL / UNIFI_USERNAME / UNIFI_PASSWORD / UNIFI_VERIFY_TLS.

DTOs

Records matching the real API shapes (captured from live round-trips): snake_case [JsonPropertyName], WhenWritingNull so a create sends only set fields and omits server-assigned _id/site_id. Envelope {meta:{rc},data:[]} parsed centrally; non-ok rc surfaces the API's msg (e.g. api.err.InvalidPayload).

CLI

unifisharp legacy <list|create|delete> <portforward|firewallgroup|networkconf> [id] [--json '<body>']

Tests — all green

  • 14 unit (URL derivation, snake_case serialization + null-omission, envelope ok/error parsing, password redaction).
  • 3 skippable-live create→list→delete round-trips against the test container (auto-skip without UNIFI_LEGACY_*). One shared session per class via IClassFixture — the controller rate-limits logins (429), so we log in once.
Passed! - Failed: 0, Passed: 17, Skipped: 2 (integration-API discover, needs X-API-KEY)

Notes

  • Test target: Homelab/.containers/unifi./bootstrap.sh then set -a && . ./credentials.env.
  • Reserved VLAN ids (4010+) return InvalidPayload — live test uses 3990.
  • Downstream: the Homelab engine will consume this to codify the WAN :443 port-forward, closing the UniFi half of Homelab#221.

🤖 Generated with Claude Code

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator Author

Note: this branch also includes a bundled fix — fix(ci): restore kiota to the tools manifest. CI has been red on main since 2026-06-29 because #11 (husky autoinstall) overwrote .config/dotnet-tools.json with only husky, dropping the microsoft.openapi.kiota entry that UnifiSharp.Api's build-time dotnet kiota generate needs (dotnet-kiota does not exist / MSB3073). Re-pinned kiota 1.31.1. Verified with a full clean build. Happy to split it into its own PR if you'd prefer to fast-track the CI fix separately.

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator Author

Update: split the CI fix out into #13 as requested. This branch is now the legacy adapter only (force-pushed to drop the kiota commit).

⚠️ Merge #13 first — UnifiSharp CI is broken on main (kiota dropped from the tools manifest by #11), so this PR's CI will stay red until #13 lands. Once #13 is on main, rebase/re-run and this goes green (verified locally: 17 passed, 2 integration-skipped).

…etworks

Add UnifiLegacyClient, an [Obsolete], thin, deletion-ready write path over the
classic controller REST API (/proxy/network/api/s/<site>/rest/...) for the
resources the official integration API can't write yet (rolling out through
2026): port-forwards, firewall groups, and networks/VLANs. Per ADR-0003.

Auth is the classic controller SESSION (POST /api/auth/login → TOKEN cookie +
X-CSRF-Token), not X-API-KEY: it works against the .containers/unifi UniFi OS
Server (which exposes no scriptable key mint) and any UniFi OS gateway. The
generated integration client (X-API-KEY) is untouched.

- UnifiLegacyOptions: session config from UNIFI_LEGACY_BASE_URL / UNIFI_USERNAME
  / UNIFI_PASSWORD / UNIFI_VERIFY_TLS; ControllerRoot derived for login.
- UnifiLegacySession: login, cookie/CSRF management, auto re-auth on 401.
- UnifiLegacyClient: List/Create/Delete per resource over the {meta,data}
  envelope; DTOs match real API shapes (snake_case, create omits server fields).
- CLI: `unifisharp legacy <list|create|delete> <resource> [id] [--json '<body>']`.
- Tests: 14 unit (URL/serialization/envelope/redaction) + 3 skippable-live
  round-trips (create→list→delete) against the test container. All green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtian
ChrisonSimtian force-pushed the feat/legacy-write-adapter branch from ca9e1d7 to 251dde3 Compare June 30, 2026 02:05
@ChrisonSimtian
ChrisonSimtian merged commit 27d3bd1 into main Jun 30, 2026
1 check passed
@ChrisonSimtian
ChrisonSimtian deleted the feat/legacy-write-adapter branch June 30, 2026 02:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant