diff --git a/pkg/oauth/interactive.go b/pkg/oauth/interactive.go index e745911a5..d9ee1234a 100644 --- a/pkg/oauth/interactive.go +++ b/pkg/oauth/interactive.go @@ -15,8 +15,120 @@ // Package oauth implements OAuth/OIDC support for device and token flows package oauth +import ( + "bytes" + "fmt" + "text/template" +) + +// GetInteractiveSuccessHTML is the page displayed upon success when using a web browser during an interactive Oauth token flow. +// The page will close automatically if autoclose is true with the timeout specified. +func GetInteractiveSuccessHTML(autoclose bool, timeout int) (string, error) { + const successTemplate = ` + + + Sigstore Authentication + + + + +
+
+ + + + + +
+
+
+ sigstore + authentication successful! +
+ {{ if .Autoclose -}} + + + {{- else -}} +
+ You may now close this page. +
+ {{- end }} +
+
+ +
+
+ + + {{ if .Autoclose -}} + + {{- end }} + + +` + // Parse the template + tmpl, err := template.New("success").Parse(successTemplate) + if err != nil { + return "", fmt.Errorf("error parsing success template: %w", err) + } + // Pass autoclose and timeout to the template + data := struct { + Autoclose bool + Timeout int + }{ + autoclose, + timeout, + } + var htmlPage bytes.Buffer + if err := tmpl.Execute(&htmlPage, data); err != nil { + return "", fmt.Errorf("error executing template: %w", err) + } + return htmlPage.String(), nil +} + const ( - // InteractiveSuccessHTML is the page displayed upon success when using a web browser during an interactive Oauth token flow. + // InteractiveSuccessHTML (deprecated) is the page displayed upon success when using a web browser during an interactive Oauth token flow. InteractiveSuccessHTML = ` diff --git a/pkg/oauth/oidc/interactive.go b/pkg/oauth/oidc/interactive.go index a3ef49da9..71e329f94 100644 --- a/pkg/oauth/oidc/interactive.go +++ b/pkg/oauth/oidc/interactive.go @@ -129,6 +129,8 @@ type interactiveIDTokenSource struct { oidp *coreoidc.Provider extraAuthCodeOpts []oauth2.AuthCodeOption browser browserOpener + autoclose bool // autoclose specifies whether to close window after successful authentication + autocloseTimeout int // autocloseTimeout specifies the time to wait before closing the window } var errWontOpenBrowser = errors.New("not opening that browser") @@ -147,8 +149,21 @@ func (idts *interactiveIDTokenSource) IDToken(ctx context.Context) (*IDToken, er codeCh := make(chan string) errCh := make(chan error) + + // get html success page with configured autoclose and autocloseTimeout settings + htmlPage, err := oauth.GetInteractiveSuccessHTML(idts.autoclose, idts.autocloseTimeout) + if err != nil { + return nil, err + } + // starts listener using the redirect_uri, otherwise starts on ephemeral port - redirectServer, redirectURL, err := startRedirectListener(stateToken, oauth.InteractiveSuccessHTML, cfg.RedirectURL, codeCh, errCh) + redirectServer, redirectURL, err := startRedirectListener( + stateToken, + htmlPage, + cfg.RedirectURL, + codeCh, + errCh, + ) if err != nil { close(codeCh) close(errCh) @@ -200,8 +215,8 @@ func (idts *interactiveIDTokenSource) IDToken(ctx context.Context) (*IDToken, er } // InteractiveIDTokenSource returns an `IDTokenSource` which performs an interactive Oauth token flow in order to retrieve an `IDToken`. -func InteractiveIDTokenSource(cfg oauth2.Config, oidp *coreoidc.Provider, extraAuthCodeOpts []oauth2.AuthCodeOption, allowBrowser bool) IDTokenSource { - ts := &interactiveIDTokenSource{cfg: cfg, oidp: oidp, extraAuthCodeOpts: extraAuthCodeOpts, browser: failBrowser} +func InteractiveIDTokenSource(cfg oauth2.Config, oidp *coreoidc.Provider, extraAuthCodeOpts []oauth2.AuthCodeOption, allowBrowser, autoclose bool, autocloseTimeout int) IDTokenSource { + ts := &interactiveIDTokenSource{cfg: cfg, oidp: oidp, extraAuthCodeOpts: extraAuthCodeOpts, browser: failBrowser, autoclose: autoclose, autocloseTimeout: autocloseTimeout} if allowBrowser { ts.browser = browser.OpenURL } diff --git a/pkg/oauth/oidc/interactive_e2e_test.go b/pkg/oauth/oidc/interactive_e2e_test.go index 9f8b58533..4d52c642b 100644 --- a/pkg/oauth/oidc/interactive_e2e_test.go +++ b/pkg/oauth/oidc/interactive_e2e_test.go @@ -84,10 +84,15 @@ func (suite *InteractiveOIDCSuite) TestInteractiveIDTokenSource() { Scopes: []string{coreoidc.ScopeOpenID, "email"}, } + autoclose := false + autocloseTimeout := 0 + ts := &interactiveIDTokenSource{ - cfg: cfg, - oidp: provider, - browser: browserOpener, + cfg: cfg, + oidp: provider, + browser: browserOpener, + autoclose: autoclose, + autocloseTimeout: autocloseTimeout, } go func() { diff --git a/pkg/oauthflow/flow.go b/pkg/oauthflow/flow.go index 38df9700e..28abcac50 100644 --- a/pkg/oauthflow/flow.go +++ b/pkg/oauthflow/flow.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "errors" + "log" "github.com/coreos/go-oidc/v3/oidc" "github.com/go-jose/go-jose/v3" @@ -46,6 +47,17 @@ type OIDCIDToken struct { Subject string // Subject is the extracted subject from the raw token } +// init +func init() { + // set the default HTML page for the DefaultIDTokenGetter + htmlPage, err := soauth.GetInteractiveSuccessHTML(false, 10) + if err != nil { + log.Print("failed to get interactive success html, defaulting to original static page") + } else { + DefaultIDTokenGetter.HTMLPage = htmlPage + } +} + // ConnectorIDOpt requests the value of prov as a the connector_id (either on URL or in form body) on the initial request; // this is used by Dex func ConnectorIDOpt(prov string) oauth2.AuthCodeOption {