From 572148c18696622309ff1ac710245835f825b3ac Mon Sep 17 00:00:00 2001 From: Dan Share Date: Tue, 31 Mar 2026 19:20:12 +0100 Subject: [PATCH] [Browser MFA] Fix formatting in moderated sessions --- lib/client/mfa/cli.go | 12 ++++---- lib/client/mfa/cli_test.go | 59 ++++++++++++++++++------------------ lib/client/sso/redirector.go | 6 ++-- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/lib/client/mfa/cli.go b/lib/client/mfa/cli.go index afbcc3ada5d4a..662aa35e58141 100644 --- a/lib/client/mfa/cli.go +++ b/lib/client/mfa/cli.go @@ -184,8 +184,8 @@ func (c *CLIPrompt) filterMFAMethods(state mfaPromptState, isPerSessionMFA bool, if len(availableMethods) > len(chosenMethods) && len(chosenMethods) > 0 && !userSpecifiedMethod { availableMethodsString := strings.ToLower(strings.Join(availableMethods, ",")) const msg = "" + - "Available MFA methods [%v]. Continuing with %v.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode=<%v> or environment variable TELEPORT_MFA_MODE=<%v>.\n\n" + "Available MFA methods [%v]. Continuing with %v.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode=<%v> or environment variable TELEPORT_MFA_MODE=<%v>.\r\n\r\n" fmt.Fprintf(c.writer(), msg, strings.Join(availableMethods, ", "), strings.Join(chosenMethods, " and "), availableMethodsString, availableMethodsString) } @@ -195,7 +195,7 @@ func (c *CLIPrompt) filterMFAMethods(state mfaPromptState, isPerSessionMFA bool, // Run prompts the user to complete an MFA authentication challenge. func (c *CLIPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { if c.cfg.PromptReason != "" { - fmt.Fprintln(c.writer(), c.cfg.PromptReason) + fmt.Fprintf(c.writer(), "%s\r\n", c.cfg.PromptReason) } // Initialize prompt state from the challenge. @@ -303,7 +303,7 @@ func (c *CLIPrompt) promptWithFallback(ctx context.Context, chal *proto.MFAAuthe // If we're retrying after a failure, inform the user. if lastErr != nil { - fmt.Fprintf(c.writer(), "Attempting MFA authentication with %s\n", currentMethod) + fmt.Fprintf(c.writer(), "Attempting MFA authentication with %s\r\n", currentMethod) } // Perform the chosen ceremony based on the filtered state. @@ -346,7 +346,7 @@ func (c *CLIPrompt) promptWithFallback(ctx context.Context, chal *proto.MFAAuthe } // Print error message about the failure. - fmt.Fprintf(c.writer(), "MFA authentication with %s failed, check logs for details\n", currentMethod) + fmt.Fprintf(c.writer(), "MFA authentication with %s failed, check logs for details\r\n", currentMethod) // Don't fall back if the context is done (e.g. user canceled or request timed out). if ctx.Err() != nil { @@ -426,7 +426,7 @@ func (c *CLIPrompt) promptWebauthnAndOTP(ctx context.Context, chal *proto.MFAAut } else { message = fmt.Sprintf("Tap any %ssecurity key or enter a code from a %sOTP device", c.promptDevicePrefix(), c.promptDevicePrefix()) } - fmt.Fprintln(c.writer(), message) + fmt.Fprintf(c.writer(), "%s\r\n", message) // Prepare to fire OTP goroutine. otpCtx, otpCancel := context.WithCancel(ctx) diff --git a/lib/client/mfa/cli_test.go b/lib/client/mfa/cli_test.go index ed22474dc7712..6db94d7f51b88 100644 --- a/lib/client/mfa/cli_test.go +++ b/lib/client/mfa/cli_test.go @@ -186,8 +186,8 @@ func TestCLIPrompt(t *testing.T) { { name: "OK prefer webauthn over sso", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, SSO]. Continuing with WEBAUTHN.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + + "Available MFA methods [WEBAUTHN, SSO]. Continuing with WEBAUTHN.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + "Tap any security key\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, @@ -202,9 +202,9 @@ func TestCLIPrompt(t *testing.T) { { name: "OK prefer webauthn+otp over sso", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + - "Tap any security key or enter a code from a OTP device\n", + "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + + "Tap any security key or enter a code from a OTP device\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, TOTP: &proto.TOTPChallenge{}, @@ -222,8 +222,8 @@ func TestCLIPrompt(t *testing.T) { { name: "OK prefer sso over otp", expectStdOut: "" + - "Available MFA methods [SSO, OTP]. Continuing with SSO.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n", + "Available MFA methods [SSO, OTP]. Continuing with SSO.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n", challenge: &proto.MFAAuthenticateChallenge{ TOTP: &proto.TOTPChallenge{}, SSOChallenge: &proto.SSOChallenge{}, @@ -240,8 +240,8 @@ func TestCLIPrompt(t *testing.T) { { name: "OK prefer webauthn over otp when stdin hijack disallowed", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, OTP]. Continuing with WEBAUTHN.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + + "Available MFA methods [WEBAUTHN, OTP]. Continuing with WEBAUTHN.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + "Tap any security key\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, @@ -256,9 +256,9 @@ func TestCLIPrompt(t *testing.T) { { name: "OK webauthn or otp with stdin hijack allowed, choose webauthn", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + - "Tap any security key or enter a code from a OTP device\n", + "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + + "Tap any security key or enter a code from a OTP device\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, TOTP: &proto.TOTPChallenge{}, @@ -276,9 +276,9 @@ func TestCLIPrompt(t *testing.T) { { name: "OK webauthn or otp with stdin hijack allowed, choose otp", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + - "Tap any security key or enter a code from a OTP device\n", + "Available MFA methods [WEBAUTHN, SSO, OTP]. Continuing with WEBAUTHN and OTP.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + + "Tap any security key or enter a code from a OTP device\r\n", stdin: "123456", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, @@ -298,7 +298,7 @@ func TestCLIPrompt(t *testing.T) { }, { name: "NOK no webauthn response", - expectStdOut: "Tap any security key\nMFA authentication with WEBAUTHN failed, check logs for details\n", + expectStdOut: "Tap any security key\nMFA authentication with WEBAUTHN failed, check logs for details\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, }, @@ -306,7 +306,7 @@ func TestCLIPrompt(t *testing.T) { }, { name: "NOK no sso response", - expectStdOut: "MFA authentication with SSO failed, check logs for details\n", + expectStdOut: "MFA authentication with SSO failed, check logs for details\r\n", challenge: &proto.MFAAuthenticateChallenge{ SSOChallenge: &proto.SSOChallenge{}, }, @@ -314,7 +314,7 @@ func TestCLIPrompt(t *testing.T) { }, { name: "NOK no otp response", - expectStdOut: "Enter an OTP code from a device:\nMFA authentication with OTP failed, check logs for details\n", + expectStdOut: "Enter an OTP code from a device:\nMFA authentication with OTP failed, check logs for details\r\n", challenge: &proto.MFAAuthenticateChallenge{ TOTP: &proto.TOTPChallenge{}, }, @@ -322,7 +322,7 @@ func TestCLIPrompt(t *testing.T) { }, { name: "NOK no webauthn or otp response", - expectStdOut: "Tap any security key or enter a code from a OTP device\nMFA authentication with WEBAUTHN failed, check logs for details\n", + expectStdOut: "Tap any security key or enter a code from a OTP device\r\nMFA authentication with WEBAUTHN failed, check logs for details\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, TOTP: &proto.TOTPChallenge{}, @@ -341,10 +341,9 @@ func TestCLIPrompt(t *testing.T) { modifyPromptConfig: func(cfg *mfa.CLIPromptConfig) { cfg.AllowStdinHijack = true }, - expectStdOut: `Tap any security key or enter a code from a OTP device -Detected security key tap -Enter your security key PIN: -`, + expectStdOut: "Tap any security key or enter a code from a OTP device\r\n" + + "Detected security key tap\n" + + "Enter your security key PIN:\n", expectResp: &proto.MFAAuthenticateResponse{ Response: &proto.MFAAuthenticateResponse_Webauthn{ Webauthn: &webauthnpb.CredentialAssertionResponse{ @@ -531,10 +530,10 @@ Enter your security key PIN: { name: "NOK browser fallback skipped on windows when not preferred", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, BROWSER]. Continuing with WEBAUTHN.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + + "Available MFA methods [WEBAUTHN, BROWSER]. Continuing with WEBAUTHN.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + "Tap any security key\n" + - "MFA authentication with WEBAUTHN failed, check logs for details\n", + "MFA authentication with WEBAUTHN failed, check logs for details\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, BrowserMFAChallenge: &proto.BrowserMFAChallenge{}, @@ -564,11 +563,11 @@ Enter your security key PIN: { name: "OK prompt fallback webauthn > SSO > browser MFA", expectStdOut: "" + - "Available MFA methods [WEBAUTHN, BROWSER]. Continuing with WEBAUTHN.\n" + - "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\n\n" + + "Available MFA methods [WEBAUTHN, BROWSER]. Continuing with WEBAUTHN.\r\n" + + "If you wish to perform MFA with another method, specify with flag --mfa-mode= or environment variable TELEPORT_MFA_MODE=.\r\n\r\n" + "Tap any security key\n" + - "MFA authentication with WEBAUTHN failed, check logs for details\n" + - "Attempting MFA authentication with BROWSER\n", + "MFA authentication with WEBAUTHN failed, check logs for details\r\n" + + "Attempting MFA authentication with BROWSER\r\n", challenge: &proto.MFAAuthenticateChallenge{ WebauthnChallenge: &webauthnpb.CredentialAssertion{}, BrowserMFAChallenge: &proto.BrowserMFAChallenge{}, diff --git a/lib/client/sso/redirector.go b/lib/client/sso/redirector.go index 898ecb538990d..d3ab201a0c861 100644 --- a/lib/client/sso/redirector.go +++ b/lib/client/sso/redirector.go @@ -271,16 +271,16 @@ func (rd *Redirector) processLoginURL(redirectURL, postForm string) error { // If a command was found to launch the browser, create and start it. if err := OpenURLInBrowser(rd.Browser, clickableURL); err != nil { - fmt.Fprintf(rd.Stderr, "Failed to open a browser window for login: %v\n", err) + fmt.Fprintf(rd.Stderr, "Failed to open a browser window for login: %v\r\n", err) } // Print the URL to the screen, in case the command that launches the browser did not run. // If Browser is set to the special string teleport.BrowserNone, no browser will be opened. if rd.Browser == teleport.BrowserNone { - fmt.Fprintf(rd.Stderr, "Use the following URL to authenticate:\n %v\n", clickableURL) + fmt.Fprintf(rd.Stderr, "Use the following URL to authenticate:\r\n %v\r\n", clickableURL) } else { fmt.Fprintf(rd.Stderr, "If browser window does not open automatically, open it by ") - fmt.Fprintf(rd.Stderr, "clicking on the link:\n %v\n", clickableURL) + fmt.Fprintf(rd.Stderr, "clicking on the link:\r\n %v\r\n", clickableURL) } return nil