From 50104548cfaae7cdbe95c6090a9aea431016d7d9 Mon Sep 17 00:00:00 2001 From: Kevin Marshall Date: Tue, 12 Aug 2025 20:41:12 +0000 Subject: [PATCH] AUTH-7260: Add support for login interstitial auto closure Adds a switch `--auto-close` which automatically closes Access login interstitial windows/tabs immediately after the user chooses Approve or Deny. --- carrier/carrier.go | 14 +++++++------- cmd/cloudflared/access/cmd.go | 10 +++++++--- cmd/cloudflared/flags/flags.go | 3 +++ cmd/cloudflared/tunnel/login.go | 2 ++ token/token.go | 17 +++++++++-------- token/transfer.go | 12 ++++++++---- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/carrier/carrier.go b/carrier/carrier.go index b44e1324..ed493953 100644 --- a/carrier/carrier.go +++ b/carrier/carrier.go @@ -26,11 +26,12 @@ const ( ) type StartOptions struct { - AppInfo *token.AppInfo - OriginURL string - Headers http.Header - Host string - TLSClientConfig *tls.Config + AppInfo *token.AppInfo + OriginURL string + Headers http.Header + Host string + TLSClientConfig *tls.Config + AutoCloseInterstitial bool } // Connection wraps up all the needed functions to forward over the tunnel @@ -46,7 +47,6 @@ type StdinoutStream struct{} // Read will read from Stdin func (c *StdinoutStream) Read(p []byte) (int, error) { return os.Stdin.Read(p) - } // Write will write to Stdout @@ -139,7 +139,7 @@ func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Reque return nil, err } - token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, log) + token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, options.AutoCloseInterstitial, log) if err != nil { return nil, err } diff --git a/cmd/cloudflared/access/cmd.go b/cmd/cloudflared/access/cmd.go index cb11aede..d491cf55 100644 --- a/cmd/cloudflared/access/cmd.go +++ b/cmd/cloudflared/access/cmd.go @@ -104,6 +104,10 @@ func Commands() []*cli.Command { Name: "no-verbose", Usage: "print only the jwt to stdout", }, + &cli.BoolFlag{ + Name: "auto-close", + Usage: "automatically close the auth interstitial after action", + }, &cli.StringFlag{ Name: appURLFlag, }, @@ -322,7 +326,7 @@ func curl(c *cli.Context) error { log.Info().Msg("You don't have an Access token set. Please run access token to fetch one.") return run("curl", cmdArgs...) } - tok, err = token.FetchToken(appURL, appInfo, log) + tok, err = token.FetchToken(appURL, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), log) if err != nil { log.Err(err).Msg("Failed to refresh token") return err @@ -442,7 +446,7 @@ func sshGen(c *cli.Context) error { if err != nil { return err } - cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log) + cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), log) if err != nil { return err } @@ -542,7 +546,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context, if c.IsSet(sshTokenSecretFlag) { headers.Add(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag)) } - options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers} + options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers, AutoCloseInterstitial: c.Bool(cfdflags.AutoCloseInterstitial)} if valid, err := isTokenValid(options, log); err != nil { return err diff --git a/cmd/cloudflared/flags/flags.go b/cmd/cloudflared/flags/flags.go index 1f8f2855..63af1dd8 100644 --- a/cmd/cloudflared/flags/flags.go +++ b/cmd/cloudflared/flags/flags.go @@ -163,4 +163,7 @@ const ( // Management hostname to signify incoming management requests ManagementHostname = "management-hostname" + + // Automatically close the login interstitial browser window after the user makes a decision. + AutoCloseInterstitial = "auto-close" ) diff --git a/cmd/cloudflared/tunnel/login.go b/cmd/cloudflared/tunnel/login.go index a5cf7813..d231e666 100644 --- a/cmd/cloudflared/tunnel/login.go +++ b/cmd/cloudflared/tunnel/login.go @@ -12,6 +12,7 @@ import ( "github.com/urfave/cli/v2" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" + cfdflags "github.com/cloudflare/cloudflared/cmd/cloudflared/flags" "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/credentials" "github.com/cloudflare/cloudflared/logger" @@ -97,6 +98,7 @@ func login(c *cli.Context) error { callbackStoreURL, false, false, + c.Bool(cfdflags.AutoCloseInterstitial), log, ) if err != nil { diff --git a/token/token.go b/token/token.go index 30ab9366..3966fe20 100644 --- a/token/token.go +++ b/token/token.go @@ -185,18 +185,18 @@ func Init(version string) { // FetchTokenWithRedirect will either load a stored token or generate a new one // it appends the full url as the redirect URL to the access cli request if opening the browser -func FetchTokenWithRedirect(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) { - return getToken(appURL, appInfo, false, log) +func FetchTokenWithRedirect(appURL *url.URL, appInfo *AppInfo, autoClose bool, log *zerolog.Logger) (string, error) { + return getToken(appURL, appInfo, false, autoClose, log) } // FetchToken will either load a stored token or generate a new one // it appends the host of the appURL as the redirect URL to the access cli request if opening the browser -func FetchToken(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) { - return getToken(appURL, appInfo, true, log) +func FetchToken(appURL *url.URL, appInfo *AppInfo, autoClose bool, log *zerolog.Logger) (string, error) { + return getToken(appURL, appInfo, true, autoClose, log) } // getToken will either load a stored token or generate a new one -func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.Logger) (string, error) { +func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, autoClose bool, log *zerolog.Logger) (string, error) { if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil { return token, nil } @@ -249,18 +249,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog. return appToken, nil } } - return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, log) + return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, autoClose, log) } // getTokensFromEdge will attempt to use the transfer service to retrieve an app and org token, save them to disk, // and return the app token. -func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, log *zerolog.Logger) (string, error) { +func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, autoClose bool, log *zerolog.Logger) (string, error) { + fmt.Println("Get tokens from edge ", autoClose) // If no org token exists or if it couldn't be exchanged for an app token, then run the transfer service flow. // this weird parameter is the resource name (token) and the key/value // we want to send to the transfer service. the key is token and the value // is blank (basically just the id generated in the transfer service) - resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, log) + resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, autoClose, log) if err != nil { return "", errors.Wrap(err, "failed to run transfer service") } diff --git a/token/transfer.go b/token/transfer.go index fd5d80ed..d5d0e1df 100644 --- a/token/transfer.go +++ b/token/transfer.go @@ -25,12 +25,12 @@ const ( // The "dance" we refer to is building a HTTP request, opening that in a browser waiting for // the user to complete an action, while it long polls in the background waiting for an // action to be completed to download the resource. -func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) { +func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, autoClose bool, log *zerolog.Logger) ([]byte, error) { encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem") if err != nil { return nil, err } - requestURL, err := buildRequestURL(transferURL, appAUD, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly) + requestURL, err := buildRequestURL(transferURL, appAUD, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly, autoClose) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, // BuildRequestURL creates a request suitable for a resource transfer. // it will return a constructed url based off the base url and query key/value provided. // cli will build a url for cli transfer request. -func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, useHostOnly bool) (string, error) { +func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, useHostOnly bool, autoClose bool) (string, error) { q := baseURL.Query() q.Set(key, value) q.Set("aud", appAUD) @@ -90,7 +90,11 @@ func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, us q.Set("redirect_url", baseURL.String()) // we add the token as a query param on both the redirect_url and the main url q.Set("send_org_token", "true") // indicates that the cli endpoint should return both the org and app token q.Set("edge_token_transfer", "true") // use new LoginHelper service built on workers - baseURL.RawQuery = q.Encode() // and this actual baseURL. + if autoClose { + q.Set("close_interstitial", "true") // Automatically close the success window. + } + + baseURL.RawQuery = q.Encode() // and this actual baseURL. baseURL.Path = "cdn-cgi/access/cli" return baseURL.String(), nil }