mfa_test.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package mfa
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/pquerna/otp"
  6. "github.com/pquerna/otp/totp"
  7. "github.com/stretchr/testify/assert"
  8. )
  9. func TestMFAConfig(t *testing.T) {
  10. config := Config{
  11. TOTP: []TOTPConfig{
  12. {},
  13. },
  14. }
  15. configName1 := "config1"
  16. configName2 := "config2"
  17. configName3 := "config3"
  18. err := config.Initialize()
  19. assert.Error(t, err)
  20. config.TOTP[0].Name = configName1
  21. err = config.Initialize()
  22. assert.Error(t, err)
  23. config.TOTP[0].Issuer = "issuer"
  24. err = config.Initialize()
  25. assert.Error(t, err)
  26. config.TOTP[0].Algo = TOTPAlgoSHA1
  27. err = config.Initialize()
  28. assert.NoError(t, err)
  29. config.TOTP = append(config.TOTP, TOTPConfig{
  30. Name: configName1,
  31. Issuer: "SFTPGo",
  32. Algo: TOTPAlgoSHA512,
  33. })
  34. err = config.Initialize()
  35. assert.Error(t, err)
  36. config.TOTP[1].Name = configName2
  37. err = config.Initialize()
  38. assert.NoError(t, err)
  39. assert.Len(t, GetAvailableTOTPConfigs(), 2)
  40. assert.Len(t, GetAvailableTOTPConfigNames(), 2)
  41. config.TOTP = append(config.TOTP, TOTPConfig{
  42. Name: configName3,
  43. Issuer: "SFTPGo",
  44. Algo: TOTPAlgoSHA256,
  45. })
  46. err = config.Initialize()
  47. assert.NoError(t, err)
  48. assert.Len(t, GetAvailableTOTPConfigs(), 3)
  49. if assert.Len(t, GetAvailableTOTPConfigNames(), 3) {
  50. assert.Contains(t, GetAvailableTOTPConfigNames(), configName1)
  51. assert.Contains(t, GetAvailableTOTPConfigNames(), configName2)
  52. assert.Contains(t, GetAvailableTOTPConfigNames(), configName3)
  53. }
  54. status := GetStatus()
  55. assert.True(t, status.IsActive)
  56. if assert.Len(t, status.TOTPConfigs, 3) {
  57. assert.Equal(t, configName1, status.TOTPConfigs[0].Name)
  58. assert.Equal(t, configName2, status.TOTPConfigs[1].Name)
  59. assert.Equal(t, configName3, status.TOTPConfigs[2].Name)
  60. }
  61. // now generate some secrets and validate some passcodes
  62. _, _, _, _, err = GenerateTOTPSecret("", "") //nolint:dogsled
  63. assert.Error(t, err)
  64. match, err := ValidateTOTPPasscode("", "", "")
  65. assert.Error(t, err)
  66. assert.False(t, match)
  67. cfgName, _, secret, _, err := GenerateTOTPSecret(configName1, "user1")
  68. assert.NoError(t, err)
  69. assert.NotEmpty(t, secret)
  70. assert.Equal(t, configName1, cfgName)
  71. passcode, err := generatePasscode(secret, otp.AlgorithmSHA1)
  72. assert.NoError(t, err)
  73. match, err = ValidateTOTPPasscode(configName1, passcode, secret)
  74. assert.NoError(t, err)
  75. assert.True(t, match)
  76. match, err = ValidateTOTPPasscode(configName1, passcode, secret)
  77. assert.ErrorIs(t, err, errPasscodeUsed)
  78. assert.False(t, match)
  79. passcode, err = generatePasscode(secret, otp.AlgorithmSHA256)
  80. assert.NoError(t, err)
  81. // config1 uses sha1 algo
  82. match, err = ValidateTOTPPasscode(configName1, passcode, secret)
  83. assert.NoError(t, err)
  84. assert.False(t, match)
  85. // config3 use the expected algo
  86. match, err = ValidateTOTPPasscode(configName3, passcode, secret)
  87. assert.NoError(t, err)
  88. assert.True(t, match)
  89. stopCleanupTicker()
  90. }
  91. func TestCleanupPasscodes(t *testing.T) {
  92. usedPasscodes.Store("key", time.Now().Add(-24*time.Hour).UTC())
  93. startCleanupTicker(30 * time.Millisecond)
  94. assert.Eventually(t, func() bool {
  95. _, ok := usedPasscodes.Load("key")
  96. return !ok
  97. }, 1000*time.Millisecond, 100*time.Millisecond)
  98. stopCleanupTicker()
  99. }
  100. func TestTOTPGenerateErrors(t *testing.T) {
  101. config := TOTPConfig{
  102. Name: "name",
  103. Issuer: "",
  104. algo: otp.AlgorithmSHA1,
  105. }
  106. // issuer cannot be empty
  107. _, _, _, err := config.generate("username", 200, 200) //nolint:dogsled
  108. assert.Error(t, err)
  109. config.Issuer = "issuer"
  110. // we cannot encode an image smaller than 45x45
  111. _, _, _, err = config.generate("username", 30, 30) //nolint:dogsled
  112. assert.Error(t, err)
  113. }
  114. func generatePasscode(secret string, algo otp.Algorithm) (string, error) {
  115. return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
  116. Period: 30,
  117. Skew: 1,
  118. Digits: otp.DigitsSix,
  119. Algorithm: algo,
  120. })
  121. }