Adds temp_budget_increase and temp_budget_expiry to the team member edit form with pair validation,
seeds stored values into edit mode, sends both through /team/member_update, and adds cached-key auth
and reservation regression tests for active and expired increases
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds the mcp_allowed_clients general setting, enforced against the
clientInfo.name each MCP client sends in its initialize request. A client
not on the list, or one that does not identify itself, is rejected with
403 before any stateful session is created. The setting is configurable
from config.yaml and from the Admin UI MCP network settings page
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A blocked source, or source and username pair, is now refused with 429 before the database lookup and password check, in place of the soft block that held wrong guesses for 30 seconds and let a correct password through. The env admin credentials and the master key typed into the login form are refused like any other credential while blocked; recovery is the master key as an API bearer token, which never goes through the sign-in path
trusted_proxy_ranges: [] now means clients connect directly, so the peer address is the source and the per-source limit stays on. Only an unset or malformed value leaves the topology unknown, warns at startup and turns the per-source limit off
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds /azure_speech/{endpoint:path}, an authenticated pass-through for the Azure AI Speech REST APIs: short-audio recognition on <region>.stt.speech.microsoft.com and batch transcription on <region>.api.cognitive.microsoft.com. The proxy resolves the subscription key through PassthroughEndpointRouter (AZURE_SPEECH_API_KEY or an Admin UI credential), picks the host from AZURE_SPEECH_REGION or AZURE_SPEECH_API_BASE, injects Ocp-Apim-Subscription-Key, strips the caller's Authorization and subscription-key headers, forwards the raw audio body byte for byte, and records a zero-cost SpendLogs row tagged azure_speech since the price map has no Azure Speech STT entry
Resolves LIT-7939
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds both fields to the team admin editable allow-list and the dashboard's team admin form. The existing budget authority check still stops a team admin from raising or removing a standalone team's budget.
Keep /projects list paging as a pushed history entry, validate the
project key table page size against its offered options, and clear the
key table params through the table-state setters instead of a copied
key list.
The projects list search lives in ?project_search= and its pagination now goes through useUrlTableState, keeping the page and page_size keys. The key table inside a project reads keys_search, keys_page and keys_page_size, resets to its first page on a new search, and no longer snaps a deep-linked page while the key fetch is failing. Closing a project drops its keys_ params so they do not leak into the next project
The organizations list now reads its search (org_search), org ID filter (filter_org_id), sort (sort_by, sort_order) and pagination (page, page_size) from the URL through useUrlTableState. The detail view tabs are controlled by ?org_tab=, and the Edit row action opens ?org=<id>&org_tab=settings in one history entry instead of passing an editOrg flag
A column toggle that localStorage refused was kept in memory and read ahead of storage, so a later save from another tab stayed hidden until this tab saved again. A storage event now drops the in-memory copy for its key, or all of them when another tab clears storage
usePersistedColumnVisibility kept a useState copy seeded once at mount, so a later tableId or defaults change showed the old table's columns and saved them under the new key. It now reads localStorage through useSyncExternalStore, keeping only writes that storage refused in memory, so the hook has no copy to go stale. Stored choices are layered over the defaults on every read, and changes saved in another tab show up.
Controlled client-mode DataTables no longer let TanStack reset the page index when rows change, since the owner of the pagination state decides the page. Once rows settle, a page past the end snaps back to the last page, matching server mode
Add useUrlTableState (search, sort, page, page size and filter_<column>
in the query string via one nuqs useQueryStates call, with keyPrefix and
urlKeys for routes that host two tables or need legacy key names) and
useUrlTab (validated ?tab= param with a role-aware fallback). Migrate
the Virtual Keys table onto useUrlTableState with a byte-identical URL
contract and bind the Playground tab strip to ?tab=.
DataTable gains controlled columnVisibility/onColumnVisibilityChange
plus usePersistedColumnVisibility (localStorage per table id), and an
isError prop that keeps the server page clamp from rewriting a
deep-linked ?page= after a failed fetch. Virtual Keys uses both.
The expired-session redirect in handleError now keeps the query string
and hash so the return URL captured on re-login restores the filtered
view instead of the bare list.
Delete useTabRouting and tabRoutes, the pathname tab router left over
from the reverted path-per-tab attempt (#34327, reverted in #34629);
tab persistence has to be a query param on the static export.
Ticking a field only changes the draft. The allow-list is written when the proxy admin clicks Save, and the card sits next to UI Settings instead of inside its auto-saving toggles.
tpm_limit is the first field in the team-admin allow-list registry. The team
settings tab gives a team admin a form with only the enabled fields and sends
only those on save, and UI Settings labels the checkbox the same way
The dashboard gated the team settings form on a role it guessed from the
is_* props, the members list and the org list. The org list is premium
gated and empty while loading, so a team admin who is also an org admin
was told team admins cannot edit, although /team/update accepts them as
an org admin
/team/info now returns caller_edit_access, resolved by the same helper
/team/update uses, and TeamInfo keys the form and the toast off that
field. The org list is only read for the organization dropdown now, and
general_settings is read through one validated accessor in both handlers
Team admins could never reach POST /team/update: the route gate answered
401 before the handler's team-admin branch ran. This moves /team/update
into the self-managed routes, resolves proxy admin, org admin or team
admin inside the handler, and filters team admins through a new
proxy-wide UI setting, team_admin_editable_team_fields. The setting is
an allow-list of team fields. Empty means team admins cannot edit team
settings and get a 403 pointing at the proxy admin, and changing a field
outside the list fails 403 naming that field. Only values that differ
from what is stored count, since the dashboard resends the whole form.
The registry of fields the setting accepts ships empty on purpose. Each
field lands in its own follow-up PR with its value diff and dashboard
wiring. The Admin UI gains a "Team admin editable fields" section under
Settings > UI and a toast on the team page while editing is disabled.
Refs LIT-5722
Claude-Session: https://claude.ai/code/session_01A6SkwJdfZUmkzfUkrEkqX8
A registered gateway DCR client can now POST /token with
grant_type=urn:ietf:params:oauth:grant-type:token-exchange and an IdP
JWT as subject_token. The gateway proves the JWT the way its JWT auth
does, resolves the user and team, and answers with the proxy-API
credential and a refresh token, so a fresh laptop with only an IdP login
gets a gateway key without a browser round trip.
"/token" joins mcp_inference_routes so the default JWT team allowlist
reaches the exchange, and the JWT auth builder accepts any header
mapping so the request headers pass through unchanged.
The ssh parser rebuilt the clone url it was given, stripping a trailing .git and
appending one back. Git treats that suffix as optional, and the hosts whose clone
paths are not org/repo break when it is forced on, so an Azure DevOps v3 or a
CodeCommit v1/repos url registered through the form would no longer clone.
It also carried its own host pattern, which demanded an alphabetic final label and
so rejected internal hosts like gitlab.internal.k8s2 that the https path accepts.
Rewrite the scp form into an ssh:// url purely to validate it, reuse the https host
and credential checks through a shared isSafeHost, and store exactly what the user
typed. Only a url that survives the round trip unchanged is accepted, which is what
keeps traversal segments out of the feed, so the two ssh regexes, the dots-only
guard and the clone-url builders all collapse into one function.