github melosso/portway v0.7.0

5 hours ago

Caution

Evaluate before upgrading production. This release targets .NET 11 RC1, which has Microsoft's go-live support license and is not GA.


Changes

This release adds OIDC sign-in, compliance with the current MCP specification, OpenAPI 3.2 support, and tenant headers. Telemetry configuration is now consolidated under a single key (Telemetry:Provider), routing traces and metrics to an OTLP collector or making them available for Prometheus scraping at /metrics. Request metrics now include the associated endpoint name.

Breaking changes

  1. .NET 11 runtime requirement: Upgraded target runtime to .NET 11 (RC1).
  2. License change (#19): Relicensed project from AGPL-3.0 to EUPL-1.2.
  3. Webhook routing structure: Replaced root route POST /api/{env}/webhook/{id} with namespaced routes (POST /api/{env}/{namespace}/{name}/{id}). Updated default configuration path to Integrations/Inbound.
  4. Proxy OData documentation visibility: Hidden OData query parameters on proxy endpoints by default. Set "SupportsOData": true in entity.json to expose them in documentation.
  5. Web UI authentication model: Replaced Admin API Key login with account-based authentication. Initial startup generates an admin account with a single-use password printed to logs; mandatory password update required on first login. The legacy API key is not reused. Reset passwords via portway accounts password <username> <new-password> (bare metal) or docker exec <container> dotnet /app/PortwayApi.dll accounts password <username> <new-password> (Docker).

New in v0.7.0

  • .NET 11: runtime target net11.0 (RC1, go-live support license).
  • Tenant headers: Tenancy on an endpoint maps a request header to a column, function parameter, upstream header or file directory placeholder. AllowedTenants on a token lists the permitted values per header; the request header selects one. SQL reads, $count, writes, table-valued functions, stored procedures, proxy requests and file directories are restricted to that value. Supported in the console, the OpenAPI document and the MCP tenants argument.
  • OIDC: OIDC providers are registered in the console and grant console sign-in.
  • PostgreSQL and MySQL parity (#29): SQL endpoints behave identically on SQL Server, PostgreSQL and MySQL/MariaDB.
  • Direct table write mode (#42): SQL endpoints write to a table with validation and full CRUD, without a stored procedure.
  • OData $expand (#43): a to-one relationship in entity.json joins the related record on Table and View endpoints, limited to the target endpoint's allowlist.
  • OData 8 query engine: the SQL query stack uses Microsoft.OData 8 and SqlKata 4 through the Melosso.DynamicODataToSQL fork.
  • OpenAPI 3.2: QUERY (RFC 10008) is documented as a native operation. Namespaces form a tag hierarchy. Endpoints support deprecated and configurable example payloads. File uploads document their multipart encoding. All errors share one response schema. /docs (Scalar 1.72.1) renders nested namespaces as a sidebar tree and documents QUERY and MERGE.
  • GraphQL over QUERY: method translation with "AllowedMethods": ["QUERY"] serves cached, read-only GraphQL proxying.
  • Telemetry providers (#33): Telemetry:Provider selects Otlp (push) or Prometheus (scrape endpoint, unauthenticated and rate-limit exempt like /health). Enabled/OtlpEndpoint configurations remain supported. The console settings page displays the active provider.
  • Per-endpoint metrics (#33): request duration histograms carry a portway.endpoint tag. New cache hit and miss counters.
  • Per-token rate limiting: each token enforces its own limit (RateLimitRequests, RateLimitWindowSeconds); tokens without one use the global RateLimiting:TokenLimit.
  • Response transforms (#34, #41): ResponseTransforms on proxy, composite and SQL endpoints applies Remove, Rename and Mask rules to top-level JSON fields. Masked values return ***; cached responses are stored after transformation.
  • MCP per endpoint: Mcp.Exposed opts an endpoint into MCP and Mcp.Instruction adds usage instructions to its tool description.
  • Namespaced File and Webhook endpoints: both types support namespaces.

Fixes

  • Security: file ids were unencrypted (base64(env:path)) and could be forged to read or delete any file in an environment. File ids are AES-GCM encrypted with PORTWAY_ENCRYPTION_KEY; downloads, deletes and listings are restricted to the endpoint's BaseDirectory.
  • Security: client headers named like an environment header or an HttpMethodAppendHeaders header were forwarded with the configured value. Configured headers replace them.
  • Security: client query parameters overrode credentials in an endpoint Url with the same name. Colliding client parameters are dropped.
  • Security: authenticated responses could be stored by shared caches. Proxy responses defaulted to Cache-Control: public and passed an upstream public through. Authenticated responses are private, and Vary lists Authorization and the endpoint's tenant headers.
  • Security: rate limit bucket keys are hashed; raw tokens are not stored.
  • Security: stored procedure names are excluded from the OpenAPI document.
  • Security: server headers are stripped from proxied responses.
  • Security: file uploads are checked for base directory containment and extension.
  • Security: failover candidates and composite steps are revalidated at call time.
  • Security: /docs makes no third-party requests. Scalar's hosted fonts, telemetry, AI agent (api.scalar.com), MCP generator and developer toolbar are disabled; fonts (Onest) are served by the gateway; the page sends the gateway Content-Security-Policy.
  • Security: various security fixes.
  • Fix: with FileStorage:UseMemoryCache (default) an upload was confirmed before it was saved to disk; a crash within 30 seconds lost it. Uploads are saved to a temporary file, flushed and renamed before the response. Concurrent uploads of a new name without overwrite return one 201 and 409 for the others. The memory cache serves reads only; temporary files left by a crash are deleted at startup.
  • Fix: overlapping timer ticks during file flush and indexing corrupted or truncated files under heavy disk activity.
  • Fix: relative BaseDirectory values and upload subpaths were ignored; files were saved to the environment root. Files are saved to the configured directory.
  • Fix: files uploaded to an absolute BaseDirectory could not be downloaded, deleted or listed. All three operations work within that directory.
  • Fix: two config saves within the same millisecond collided on the backup file name; one was lost.
  • Fix: saving an unchanged config file created a backup and displaced one of the ten kept versions.
  • Fix: the token column upgrade was skipped for auth.db files with a Tokens table and no TokenAudits table.
  • Fix: webhook inserts used SQL Server syntax and failed on PostgreSQL, MySQL/MariaDB and SQLite. The insert statement is generated per provider.
  • Fix: Properties in a SQL entity.json (MaxPageSize, DefaultSort, CacheEnabled) were not loaded; $top was not limited by MaxPageSize. The values are applied.
  • Fix: hot reload of settings.json on a live instance caused transient request rejections and authorization failures.
  • Fix: proxy requests with a charset in the Content-Type returned 500. The forwarded Content-Type is parsed.
  • Fix: XML and SOAP bodies to proxy endpoints returned 415. Content validation is selected by endpoint type instead of URL text.
  • Fix: path segments appended after a query string in an endpoint Url produced invalid upstream URLs.
  • Fix: malformed OData requests return 400 instead of 500.
  • Fix: MERGE is served end to end and documented under additionalOperations. It was rendered as PATCH without a route.
  • Fix: namespaces nested deeper than one level are routed. ParseEndpoint probed a single segment.
  • Fix: each namespace segment is validated separately; nested namespaces failed their own naming rules.
  • Fix: file listings returned 500 when an upload changed the file index during the listing. Listings read a snapshot taken under the index lock.
  • Fix: file endpoint namespace handling and error responses.
  • Fix: the identifier pattern for PostgreSQL stored procedure parameters accepts all valid parameter names.
  • Fix: webhook tag descriptions referenced a removed root configuration.
  • Fix: HMAC environment authentication blocked thread-pool threads while reading request bodies. Body reads are asynchronous.
  • Fix: requests with unique URL paths grew the cache lock table without bound. The table is bounded.
  • Fix: file cache byte counts drifted under concurrent traffic. Counts are atomic and the memory cap is enforced.
  • Fix: Redis connection state flags desynchronized across threads and delayed reconnection after network failures.
  • Fix: host shutdown and restart threw unhandled disposal exceptions or deadlocked. StopAsync and connection pool cleanup complete without errors.
  • Fix: a failing database maintenance service stopped the host. Maintenance errors are logged and retried.
  • Fix: file watcher and SQL pool shutdown swallowed errors; disposing either twice threw.
  • Fix: locked environment settings.json files are read again with retries on load.
  • Fix: the log was closed at the start of shutdown; messages written while requests drained were lost.
  • Fix: timeouts and cache limits in configuration are enforced at runtime.
  • Fix: the telemetry pipeline started only with an OTLP collector configured. Metrics work with either provider.
  • Fix: token authentication and rate limiting blocked the Prometheus scrape endpoint. It is exempt like /health.
  • Fix: MCP chat streamed no events. The chat delta record had an enum converter that failed serialization.
  • Fix: MCP page asset paths, script loading and the health endpoint in the console.
  • Fix: token creation and validation in the console ran blocking checks; both are asynchronous.
  • Fix: OData parameters are documented only on proxy endpoints with SupportsOData.
  • Fix: the bearer scheme is documented as http instead of apiKey, without the JWT claim.
  • Fix: intermediate tags are linked for namespaces nested three levels or more. The ancestor pass ran against a stale snapshot.
  • Fix: CSV and XML examples keep their serializedValue format instead of being converted to JSON strings.
  • Fix: the /docs breadcrumb bar displayed the first nested namespace (e.g. WMS > Inbound) in every section. The bar is hidden pending a Scalar fix.
  • Fix: a port in use fails the start before database and endpoint initialization.
  • Fix: startup exceptions were missing from console output. The log template includes exception details.
  • Fix: the startup environment warning ignored DOTNET_ENVIRONMENT and --environment; the Key Vault status ignored PORTWAY_KEYVAULT_URI.
  • Fix: unreachable backends logged an error per endpoint, environment and retry. Health checks and SQL metadata log one summary line; details are logged at Debug.
  • Fix: startup logging is reduced to a one-line title and hosting summary; the endpoint tree, pool, SQL provider and reload lines are logged at Debug.
  • Fix: the startup log listed localhost/127.0.0.1 twice under allowed hosts. Hosts are deduplicated.
  • Fix: the .backups folder is hidden on Windows, like .core.
  • Fix: unused and redundant configuration settings are removed.
  • Fix: unused code and NuGet packages are removed; the build output is smaller.
  • Fix: obsolete API warning during build.

Upgrade Notes

  • Header precedence: Environment headers (environments/{env}/settings.json Headers) and HttpMethodAppendHeaders override client headers of the same name. If Authorization is set at the environment level, client Authorization headers are dropped. Legacy Teable and AFAS X-API-Key configurations remain supported but are optional.
  • Database schema: Initial startup adds Tokens.AllowedTenants (default {}) to auth.db. Pre-existing tokens contain empty tenant arrays and will be rejected by endpoints requiring Tenancy.
  • File storage path: File endpoints configured with a relative BaseDirectory store new uploads in that specific folder. Existing files located in the environment root remain functional only after manual migration to the target directory.
  • File identifier format: Legacy file IDs remain valid on endpoints lacking a BaseDirectory. Endpoints specifying a BaseDirectory issue updated IDs, which depend on PORTWAY_ENCRYPTION_KEY and invalidate if the key rotates.
  • PostgreSQL webhook schema: Webhook tables in PostgreSQL must use quoted column names "Id", "WebhookId", "Payload", and "ReceivedAt" (TIMESTAMPTZ). Refer to the webhook guide for provider-specific table definitions.
  • Prometheus endpoint security: The Prometheus metrics endpoint exposes metrics only when using the Prometheus provider. The endpoint is publicly accessible by design and must be secured via firewall or reverse proxy in exposed environments.
  • Environment variable naming: Configuration variables now standardise on the PORTWAY_ prefix (e.g., PORTWAY_ALLOWED_HOSTS, PORTWAY_ADMIN_KEY, PORTWAY_PATH_BASE). Legacy unprefixed variables continue to function but emit deprecation warnings.
  • Telemetry configuration: Existing telemetry schemas remain backward-compatible; "Enabled": true defaults to OTLP and reads flat OtlpEndpoint properties. Migrating to Telemetry:Provider is recommended.

Setup guide: documentation.

Full Changelog: v0.6.1...v0.7.0

Don't miss a new portway release

NewReleases is sending notifications on new releases.