Repository: snowflakedb/gosnowflake
Pull request #1167 (2024-06-25 06:24:58)

SNOW-1503458 Allow using passcode with MFA token caching enabled
### Description

SNOW-1503458 Added possibility to specify passcode while using MFA token caching

### Checklist
- [x] Created tests which fail without the change (if possible)
- [x] Extended the README / documentation, if necessary


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1167?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) Report
Attention: Patch coverage is `28.57143%` with `5 lines` in your changes missing coverage. Please review.
> Project coverage is 71.58%. Comparing base [(`5d28db8`)](https://app.codecov.io/gh/snowflakedb/gosnowflake/commit/5d28db80c2ffac67e9e8991eb2d78c591faedb47?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) to head [(`358d281`)](https://app.codecov.io/gh/snowflakedb/gosnowflake/commit/358d281601bf0dacad10145c124bf0564b1cadc3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).
> Report is 4 commits behind head on master.

| [Files](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1167?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) | Patch % | Lines |
|---|---|---|
| [auth.go](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1167?src=pr&el=tree&filepath=auth.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb#diff-YXV0aC5nbw==) | 28.57% | [5 Missing :warning: ](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1167?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1167      +/-   ##
==========================================
- Coverage   72.34%   71.58%   -0.77%     
==========================================
  Files          53       54       +1     
  Lines       11909    12092     +183     
==========================================
+ Hits         8616     8656      +40     
- Misses       3293     3436     +143     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/snowflakedb/gosnowflake/pull/1167?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=snowflakedb).


Patch:
diff --git a/auth.go b/auth.go
index 832eeb6..1ada05f 100644
--- a/auth.go
+++ b/auth.go
@@ -457,8 +457,14 @@ func createRequestBody(sc *snowflakeConn, sessionParameters map[string]interface
 		logger.WithContext(sc.ctx).Info("Username and password MFA")
 		requestMain.LoginName = sc.cfg.User
 		requestMain.Password = sc.cfg.Password
-		if sc.cfg.MfaToken != "" {
+		switch {
+		case sc.cfg.MfaToken != "":
 			requestMain.Token = sc.cfg.MfaToken
+		case sc.cfg.PasscodeInPassword:
+			requestMain.ExtAuthnDuoMethod = "passcode"
+		case sc.cfg.Passcode != "":
+			requestMain.Passcode = sc.cfg.Passcode
+			requestMain.ExtAuthnDuoMethod = "passcode"
 		}
 	}
 
diff --git a/doc.go b/doc.go
index 353e560..9b38c94 100644
--- a/doc.go
+++ b/doc.go
@@ -96,7 +96,7 @@ The following connection parameters are supported:
 
   - authenticator: Specifies the authenticator to use for authenticating user credentials:
 
-  - To use the internal Snowflake authenticator, specify snowflake (Default).
+  - To use the internal Snowflake authenticator, specify snowflake (Default). If you want to cache your MFA logins, use AuthTypeUsernamePasswordMFA authenticator.
 
   - To authenticate through Okta, specify https://<okta_account_name>.okta.com (URL prefix for Okta).
 


Test patch:
diff --git a/auth_test.go b/auth_test.go
index 5ae84a0..e05a96d 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -812,6 +812,58 @@ func TestUsernamePasswordMfaCaching(t *testing.T) {
 	}
 }
 
+func TestUsernamePasswordMfaCachingWithPasscode(t *testing.T) {
+	t.Skip("manual test for MFA token caching")
+
+	config, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatal("Failed to parse dsn")
+	}
+	// connect with MFA authentication
+	user := os.Getenv("SNOWFLAKE_TEST_MFA_USER")
+	password := os.Getenv("SNOWFLAKE_TEST_MFA_PASSWORD")
+	config.User = user
+	config.Password = password
+	config.Passcode = "" // fill with your passcode from DUO app
+	config.Authenticator = AuthTypeUsernamePasswordMFA
+	if runtime.GOOS == "linux" {
+		config.ClientRequestMfaToken = ConfigBoolTrue
+	}
+	connector := NewConnector(SnowflakeDriver{}, *config)
+	db := sql.OpenDB(connector)
+	for i := 0; i < 3; i++ {
+		// should only be prompted to authenticate first time around.
+		_, err := db.Query("select current_user()")
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func TestUsernamePasswordMfaCachingWithPasscodeInPassword(t *testing.T) {
+	t.Skip("manual test for MFA token caching")
+
+	config, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatal("Failed to parse dsn")
+	}
+	// connect with MFA authentication
+	user := os.Getenv("SNOWFLAKE_TEST_MFA_USER")
+	password := os.Getenv("SNOWFLAKE_TEST_MFA_PASSWORD")
+	config.User = user
+	config.Password = password + "" // fill with your passcode from DUO app
+	config.PasscodeInPassword = true
+	connector := NewConnector(SnowflakeDriver{}, *config)
+	db := sql.OpenDB(connector)
+	for i := 0; i < 3; i++ {
+		// should only be prompted to authenticate first time around.
+		_, err := db.Query("select current_user()")
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
 // To run this test you need to set environment variables in parameters.json to a user with MFA authentication enabled
 // Set any other snowflake_test variables needed for database, schema, role for this user
 func TestDisableUsernamePasswordMfaCaching(t *testing.T) {