Added noRedirects handling to the agentRemoteShare handling

This commit is contained in:
Alan Hazelton
2026-05-29 14:07:51 -04:00
parent 6829268a84
commit ca83bdca11
4 changed files with 64 additions and 14 deletions
+1
View File
@@ -76,6 +76,7 @@ func (h *agentRemoteShareHandler) publicShare(params agent.RemoteShareParams, cl
OauthProvider: params.Body.OauthProvider,
OauthEmailDomains: params.Body.OauthEmailDomains,
OauthRefreshInterval: params.Body.OauthRefreshInterval,
OauthNoRedirect: params.Body.OauthNoRedirect,
Closed: !params.Body.Open,
AccessGrants: params.Body.AccessGrants,
}
-2
View File
@@ -268,7 +268,6 @@ func (h *shareHandler) processNameSelections(selections []*rest_model_zrok.NameS
}
func (h *shareHandler) allocatePublicResources(envZId, shrToken string, frontendEndpoints []string, params share.ShareParams, interstitial bool, trx interface{}) (string, []string, error) {
dl.Infof("allocatePublicResources: called with authScheme=%q oauthProvider=%q oauthNoRedirect=%v", params.Body.AuthScheme, params.Body.OauthProvider, params.Body.OauthNoRedirect)
// get shared automation client
ziti, err := automation.NewZitiAutomation(cfg.Ziti)
if err != nil {
@@ -308,7 +307,6 @@ func (h *shareHandler) allocatePublicResources(envZId, shrToken string, frontend
AuthorizationCheckInterval: params.Body.OauthRefreshInterval,
NoRedirect: params.Body.OauthNoRedirect,
}
dl.Infof("allocatePublicResources: authScheme='%v' provider='%v' noRedirect=%v", authScheme, oauthCfg.Provider, oauthCfg.NoRedirect)
}
// create frontend config
@@ -103,6 +103,9 @@ type RemoteShareBody struct {
// oauth email domains
OauthEmailDomains []string `json:"oauthEmailDomains"`
// oauth no redirect
OauthNoRedirect bool `json:"oauthNoRedirect,omitempty"`
// oauth provider
OauthProvider string `json:"oauthProvider,omitempty"`
+60 -12
View File
@@ -221,13 +221,20 @@ CLI flag --oauth-no-redirect
| File | Purpose |
|---|---|
| `endpoints/proxyUi/token.go` | `WriteTokenDisplay(w, token, expiry)` — renders the token page |
| `endpoints/proxyUi/token.go` | `WriteTokenDisplay(w, token, expiry, targetHost)` — renders the token page |
| `endpoints/proxyUi/token.html` | HTML template: token text box, copy button, usage instructions, expiry |
`token.html` uses the same visual style as `template.html`: Poppins + JetBrains
Mono fonts from Google Fonts, dark purple (`#241775`) background, the zrok SVG
logo banner, and a centered white card for the content area.
When `targetHost` is non-empty, the page emits a small script that calls
`window.opener.postMessage({ xZrokSession: <token> }, 'https://<targetHost>')`
so that browser-based clients that open the login in a popup can receive the
token automatically. The script is suppressed entirely when `targetHost` is
empty. The `postMessage` origin is scoped to the share's host — not `'*'` — to
prevent token leakage to unrelated windows.
---
### `IntermediateJWT` — new `ReturnToken` field
@@ -261,7 +268,8 @@ All six callback / `loginHandler()` functions:
- After calling `setSessionCookie()` (which produces the signed JWT string):
- If `IntermediateJWT.ReturnToken == true` → call
`proxyUi.WriteTokenDisplay(w, sTkn, expiry)` instead of `http.Redirect`.
`proxyUi.WriteTokenDisplay(w, sTkn, expiry, intermediateJWT.TargetHost)`
instead of `http.Redirect`.
- Otherwise → existing redirect to `targetHost`.
- The cookie is set in both cases so the browser session also works.
@@ -396,30 +404,32 @@ case string(sdk.Oauth):
| File | Type of change |
|---|---|
| `endpoints/oauthCookies.go` | Add `SessionHeaderName`, `GetSessionHeader`, `StripSessionHeader` |
| `endpoints/proxyUi/token.go` | **New** — `WriteTokenDisplay` function |
| `endpoints/proxyUi/token.html` | **New** — token display page template (styled to match `template.html`) |
| `endpoints/proxyUi/token.go` | **New** — `WriteTokenDisplay(w, token, expiry, targetHost)` function |
| `endpoints/proxyUi/token.html` | **New** — token display page template (styled to match `template.html`); conditional `postMessage` script scoped to share host |
| `endpoints/dynamicProxy/auth.go` | Add `ReturnToken` to `IntermediateJWT`; add `sessionRefresher` interface |
| `endpoints/dynamicProxy/authOauth.go` | Rewrite `handleOAuth`, `validateOAuthToken`, `validateEmailDomain`; add `oauthUnauthorized`, `tryInlineRefresh` |
| `endpoints/dynamicProxy/authOauthRouter.go` | Add `GetProvider()` method |
| `endpoints/dynamicProxy/cookies.go` | Add `buildSessionJWT`; update `setSessionCookie` to return JWT string; update `filterSessionCookies` to strip header |
| `endpoints/dynamicProxy/http.go` | **Bug fix** — add `filterSessionCookies` call on OAuth auth-success path |
| `endpoints/dynamicProxy/providerOidc.go` | Add `ReturnToken` handling; add `RefreshSessionJWT` |
| `endpoints/dynamicProxy/providerGithub.go` | Add `ReturnToken` handling in `authHandler` / callback |
| `endpoints/dynamicProxy/providerGoogle.go` | Add `ReturnToken` handling in `authHandler` / callback |
| `endpoints/dynamicProxy/providerOidc.go` | Add `ReturnToken` handling; add `RefreshSessionJWT`; pass `targetHost` to `WriteTokenDisplay` |
| `endpoints/dynamicProxy/providerGithub.go` | Add `ReturnToken` handling; pass `targetHost` to `WriteTokenDisplay` |
| `endpoints/dynamicProxy/providerGoogle.go` | Add `ReturnToken` handling; pass `targetHost` to `WriteTokenDisplay` |
| `endpoints/publicProxy/auth.go` | Add `ReturnToken` to `IntermediateJWT`; add `sessionRefresher` interface |
| `endpoints/publicProxy/authOAuth.go` | Same changes as `dynamicProxy/authOauth.go` |
| `endpoints/publicProxy/cookies.go` | Add `buildSessionJWT`; update `setSessionCookie` to return JWT string; strip `X-Zrok-Session` header in `filterSessionCookies` |
| `endpoints/publicProxy/http.go` | **Bug fix** — add `filterSessionCookies` call on OAuth auth-success path |
| `endpoints/publicProxy/oidcRegistry.go` | **New** — package-level `oidcProviderRegistry` map for inline refresh lookups |
| `endpoints/publicProxy/providerOidc.go` | Add `ReturnToken` handling; add `RefreshSessionJWT`; register in `oidcProviderRegistry` |
| `endpoints/publicProxy/providerGithub.go` | Add `ReturnToken` handling |
| `endpoints/publicProxy/providerGoogle.go` | Add `ReturnToken` handling |
| `endpoints/publicProxy/providerOidc.go` | Add `ReturnToken` handling; add `RefreshSessionJWT`; register in `oidcProviderRegistry`; pass `targetHost` to `WriteTokenDisplay` |
| `endpoints/publicProxy/providerGithub.go` | Add `ReturnToken` handling; pass `targetHost` to `WriteTokenDisplay` |
| `endpoints/publicProxy/providerGoogle.go` | Add `ReturnToken` handling; pass `targetHost` to `WriteTokenDisplay` |
| `sdk/golang/sdk/config.go` | Add `NoRedirect bool` to `OauthConfig`; add `"no_redirect"` case in `OauthConfigFromMap` |
| `sdk/golang/sdk/model.go` | Add `OauthNoRedirect bool` to `ShareRequest` |
| `sdk/golang/sdk/share.go` | Pass `OauthNoRedirect` to REST body in `newPublicShare` |
| `specs/src/definitions.yml` | Add `oauthNoRedirect: boolean` to `shareRequest` definition |
| `rest_model_zrok/share_request.go` | Add `OauthNoRedirect bool` field |
| `controller/share.go` | Read `OauthNoRedirect` in both create-share and update-share paths |
| `rest_server_zrok/operations/agent/remote_share.go` | **Bug fix** — add `OauthNoRedirect bool` to `RemoteShareBody` (was missing, causing `/api/v2/agent/share` to silently drop the flag) |
| `controller/share.go` | Read `OauthNoRedirect` in both create-share and update-share paths; infer `authScheme` from `oauthProvider`/`basicAuthUsers` when absent |
| `controller/agentRemoteShare.go` | **Bug fix** — map `OauthNoRedirect` from request body to gRPC call in the `/api/v2/agent/share` handler |
| `cmd/zrok2/sharePublic.go` | Add `--oauth-no-redirect` flag; pass through local SDK and agent gRPC paths |
| `agent/share.go` | Add `OauthNoRedirect bool` to `SharePublicRequest` |
| `agent/commandBuilder.go` | Add `OauthNoRedirect()` builder method |
@@ -475,6 +485,8 @@ Mirror of the `dynamicProxy` tests above, adjusted for `publicProxy` types
| `TestWriteTokenDisplayContainsToken` | Token value appears in response body |
| `TestWriteTokenDisplayEscapesHTML` | Token containing `<script>` is HTML-escaped (XSS) |
| `TestWriteTokenDisplayContainsHeaderName` | Body references `X-Zrok-Session` |
| `TestWriteTokenDisplayPostMessageScriptPresentWithTargetHost` | Non-empty `targetHost` → `postMessage` script present in body |
| `TestWriteTokenDisplayNoPostMessageScriptWithoutTargetHost` | Empty `targetHost` → `postMessage` script absent from body |
---
@@ -495,11 +507,47 @@ Mirror of the `dynamicProxy` tests above, adjusted for `publicProxy` types
| `endpoints/oauthCookies_test.go` | Extended | `GetSessionHeader`, `StripSessionHeader` |
| `endpoints/dynamicProxy/http_test.go` | Extended | Header auth, 401 path, header stripping before proxy, OPTIONS + header stripping |
| `endpoints/publicProxy/http_test.go` | Extended | Same for `publicProxy` |
| `endpoints/proxyUi/token_test.go` | **New** | `WriteTokenDisplay` rendering and XSS escaping |
| `endpoints/proxyUi/token_test.go` | **New** | `WriteTokenDisplay` rendering, XSS escaping, `postMessage` script conditional |
| `endpoints/dynamicProxy/cookies_test.go` | **New** | `buildSessionJWT` error and round-trip cases |
---
## Post-implementation fixes
### `/api/v2/agent/share` — `oauthNoRedirect` silently dropped
The `/api/v2/agent/share` endpoint (`controller/agentRemoteShare.go`) proxies
share creation requests through the agent gRPC layer rather than calling
`allocatePublicResources` directly. Two gaps caused `oauthNoRedirect` to be
silently dropped for all requests via this path:
1. `RemoteShareBody` (the inline request struct in
`rest_server_zrok/operations/agent/remote_share.go`) was missing the
`OauthNoRedirect bool` field, so the value was never parsed from the JSON
body.
2. The `publicShare` helper in `agentRemoteShare.go` was not mapping
`params.Body.OauthNoRedirect` into the `agentGrpc.SharePublicRequest`.
Both gaps are now closed.
### `controller/share.go` — `authScheme` inference
The standard share endpoint requires `authScheme` to be set explicitly. The
`zrok-connector` and other API clients send `oauthProvider` without
`authScheme`, which previously caused `ParseAuthScheme("")` to return an error
and the oauth config (including `no_redirect`) to be silently omitted.
The controller now infers the auth scheme when `authScheme` is absent:
- `oauthProvider` non-empty → `oauth`
- `basicAuthUsers` non-empty → `basic`
- otherwise → `none`
Explicitly-set but unrecognized `authScheme` values are still rejected, so
typos still surface as errors rather than silently defaulting.
---
## Out of scope
- The `refreshHandler()` HTTP endpoint (used by browser-based OIDC refresh