auth_service_captcha_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "errors"
  6. "testing"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func newAuthServiceForCaptchaRepoTest(repo *settingRepoStub, required bool, turnstileVerifier TurnstileVerifier, tencentVerifier TencentCaptchaVerifier) *AuthService {
  11. cfg := &config.Config{
  12. Server: config.ServerConfig{Mode: "release"},
  13. Turnstile: config.TurnstileConfig{Required: required},
  14. }
  15. settingService := NewSettingService(repo, cfg)
  16. turnstileService := NewTurnstileService(settingService, turnstileVerifier)
  17. tencentService := NewTencentCaptchaService(settingService, tencentVerifier)
  18. svc := NewAuthService(nil, &userRepoStub{}, nil, nil, cfg, settingService, nil, turnstileService, nil, nil, nil, nil, nil)
  19. svc.SetTencentCaptchaService(tencentService)
  20. return svc
  21. }
  22. func newAuthServiceForCaptchaTest(settings map[string]string, required bool, turnstileVerifier TurnstileVerifier, tencentVerifier TencentCaptchaVerifier) *AuthService {
  23. cfg := &config.Config{
  24. Server: config.ServerConfig{Mode: "release"},
  25. Turnstile: config.TurnstileConfig{Required: required},
  26. }
  27. settingService := NewSettingService(&settingRepoStub{values: settings}, cfg)
  28. var turnstileService *TurnstileService
  29. if turnstileVerifier != nil {
  30. turnstileService = NewTurnstileService(settingService, turnstileVerifier)
  31. }
  32. svc := NewAuthService(nil, &userRepoStub{}, nil, nil, cfg, settingService, nil, turnstileService, nil, nil, nil, nil, nil)
  33. if tencentVerifier != nil {
  34. svc.SetTencentCaptchaService(NewTencentCaptchaService(settingService, tencentVerifier))
  35. }
  36. return svc
  37. }
  38. func tencentCaptchaSettings() map[string]string {
  39. return map[string]string{
  40. SettingKeyTencentCaptchaEnabled: "true",
  41. SettingKeyTencentCaptchaAppID: "123456789",
  42. SettingKeyTencentCaptchaAppSecretKey: "app-secret",
  43. SettingKeyTencentCaptchaCloudSecretID: "cloud-secret-id",
  44. SettingKeyTencentCaptchaCloudSecretKey: "cloud-secret-key",
  45. }
  46. }
  47. func TestVerifyCaptchaUsesTencentWhenEnabled(t *testing.T) {
  48. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  49. svc := newAuthServiceForCaptchaTest(tencentCaptchaSettings(), false, nil, verifier)
  50. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{
  51. TencentTicket: "ticket",
  52. TencentRandstr: "@rand",
  53. }, "203.0.113.10")
  54. require.NoError(t, err)
  55. require.Equal(t, 1, verifier.calls)
  56. }
  57. func TestVerifyCaptchaRejectsDirtyDoubleEnabledSettings(t *testing.T) {
  58. settings := tencentCaptchaSettings()
  59. settings[SettingKeyTurnstileEnabled] = "true"
  60. settings[SettingKeyTurnstileSecretKey] = "turnstile-secret"
  61. turnstileVerifier := &turnstileVerifierSpy{}
  62. tencentVerifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  63. svc := newAuthServiceForCaptchaTest(settings, false, turnstileVerifier, tencentVerifier)
  64. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{
  65. TurnstileToken: "turnstile-token",
  66. TencentTicket: "ticket",
  67. TencentRandstr: "@rand",
  68. }, "203.0.113.10")
  69. require.ErrorIs(t, err, ErrCaptchaProviderConflict)
  70. require.Zero(t, turnstileVerifier.called)
  71. require.Zero(t, tencentVerifier.calls)
  72. }
  73. func TestVerifyCaptchaRequiredModeAcceptsCompleteTencentProvider(t *testing.T) {
  74. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  75. svc := newAuthServiceForCaptchaTest(tencentCaptchaSettings(), true, nil, verifier)
  76. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{
  77. TencentTicket: "ticket",
  78. TencentRandstr: "@rand",
  79. }, "203.0.113.10")
  80. require.NoError(t, err)
  81. }
  82. func TestVerifyCaptchaForRegisterSkipsDuplicateTencentTicketAfterEmailCode(t *testing.T) {
  83. settings := tencentCaptchaSettings()
  84. settings[SettingKeyEmailVerifyEnabled] = "true"
  85. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  86. svc := newAuthServiceForCaptchaTest(settings, true, nil, verifier)
  87. err := svc.VerifyCaptchaForRegister(context.Background(), CaptchaProof{}, "203.0.113.10", "123456")
  88. require.NoError(t, err)
  89. require.Zero(t, verifier.calls)
  90. }
  91. func TestVerifyCaptchaFailsClosedWhenProviderSettingsCannotBeRead(t *testing.T) {
  92. repo := &settingRepoStub{err: errors.New("settings unavailable")}
  93. svc := newAuthServiceForCaptchaRepoTest(repo, false, &turnstileVerifierSpy{}, &tencentCaptchaVerifierStub{})
  94. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{}, "203.0.113.10")
  95. require.ErrorIs(t, err, ErrServiceUnavailable)
  96. }
  97. func TestVerifyCaptchaReadsProviderConfigurationOnce(t *testing.T) {
  98. repo := &settingRepoStub{values: tencentCaptchaSettings()}
  99. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  100. svc := newAuthServiceForCaptchaRepoTest(repo, false, &turnstileVerifierSpy{}, verifier)
  101. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{
  102. TencentTicket: "ticket",
  103. TencentRandstr: "@rand",
  104. }, "203.0.113.10")
  105. require.NoError(t, err)
  106. require.Equal(t, 1, repo.getMultipleCalls)
  107. require.Zero(t, repo.getValueCalls)
  108. require.Equal(t, 1, verifier.calls)
  109. }
  110. func TestVerifyCaptchaRejectsEnabledTencentProviderWithIncompleteCredentials(t *testing.T) {
  111. repo := &settingRepoStub{values: map[string]string{
  112. SettingKeyTencentCaptchaEnabled: "true",
  113. SettingKeyTencentCaptchaAppID: "123456789",
  114. }}
  115. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  116. svc := newAuthServiceForCaptchaRepoTest(repo, false, &turnstileVerifierSpy{}, verifier)
  117. err := svc.VerifyCaptcha(context.Background(), CaptchaProof{
  118. TencentTicket: "ticket",
  119. TencentRandstr: "@rand",
  120. }, "203.0.113.10")
  121. require.ErrorIs(t, err, ErrTencentCaptchaNotConfigured)
  122. require.Equal(t, 1, repo.getMultipleCalls)
  123. require.Zero(t, verifier.calls)
  124. }
  125. func TestVerifyActionCaptchaIfEnabledVerifiesTencentProof(t *testing.T) {
  126. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  127. svc := newAuthServiceForCaptchaTest(tencentCaptchaSettings(), false, nil, verifier)
  128. err := svc.VerifyActionCaptchaIfEnabled(context.Background(), CaptchaProof{
  129. TencentTicket: "ticket",
  130. TencentRandstr: "@rand",
  131. }, "203.0.113.10")
  132. require.NoError(t, err)
  133. require.Equal(t, 1, verifier.calls)
  134. require.Equal(t, TencentCaptchaProof{Ticket: "ticket", Randstr: "@rand"}, verifier.proof)
  135. }
  136. func TestVerifyActionCaptchaIfEnabledDoesNotExpandTurnstileCoverage(t *testing.T) {
  137. settings := map[string]string{
  138. SettingKeyTurnstileEnabled: "true",
  139. SettingKeyTurnstileSecretKey: "turnstile-secret",
  140. }
  141. turnstileVerifier := &turnstileVerifierSpy{}
  142. svc := newAuthServiceForCaptchaTest(settings, false, turnstileVerifier, nil)
  143. err := svc.VerifyActionCaptchaIfEnabled(context.Background(), CaptchaProof{}, "203.0.113.10")
  144. require.NoError(t, err)
  145. require.Zero(t, turnstileVerifier.called)
  146. }
  147. func TestVerifyActionCaptchaIfEnabledFailsClosedOnSettingReadError(t *testing.T) {
  148. repo := &settingRepoStub{err: errors.New("settings unavailable")}
  149. svc := newAuthServiceForCaptchaRepoTest(repo, false, &turnstileVerifierSpy{}, &tencentCaptchaVerifierStub{})
  150. err := svc.VerifyActionCaptchaIfEnabled(context.Background(), CaptchaProof{}, "203.0.113.10")
  151. require.ErrorIs(t, err, ErrServiceUnavailable)
  152. }