Files
hanko/backend/handler/thirdparty_test.go
2025-09-25 19:15:20 +02:00

205 lines
5.7 KiB
Go

package handler
import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"time"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwa"
jwk2 "github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/stretchr/testify/suite"
auditlog "github.com/teamhanko/hanko/backend/v2/audit_log"
"github.com/teamhanko/hanko/backend/v2/config"
"github.com/teamhanko/hanko/backend/v2/crypto/jwk"
"github.com/teamhanko/hanko/backend/v2/dto"
"github.com/teamhanko/hanko/backend/v2/session"
"github.com/teamhanko/hanko/backend/v2/test"
"github.com/teamhanko/hanko/backend/v2/utils"
)
func TestThirdPartySuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(thirdPartySuite))
}
type thirdPartySuite struct {
test.Suite
}
func (s *thirdPartySuite) setUpContext(request *http.Request) (echo.Context, *httptest.ResponseRecorder) {
s.T().Helper()
e := echo.New()
e.Validator = dto.NewCustomValidator()
rec := httptest.NewRecorder()
c := e.NewContext(request, rec)
return c, rec
}
func (s *thirdPartySuite) setUpHandler(cfg *config.Config) *ThirdPartyHandler {
s.T().Helper()
auditLogger := auditlog.NewLogger(s.Storage, cfg.AuditLog)
jwkMngr, err := jwk.NewDefaultManager(cfg.Secrets.Keys, s.Storage.GetJwkPersister())
s.Require().NoError(err)
sessionMngr, err := session.NewManager(jwkMngr, *cfg)
s.Require().NoError(err)
handler := NewThirdPartyHandler(cfg, s.Storage, sessionMngr, auditLogger)
return handler
}
func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirectURLs []string) *config.Config {
s.T().Helper()
cfg := config.DefaultConfig()
cfg.ThirdParty = config.ThirdParty{
Providers: config.ThirdPartyProviders{
Apple: config.ThirdPartyProvider{
ID: "apple",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: true,
},
Google: config.ThirdPartyProvider{
ID: "google",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: true,
},
GitHub: config.ThirdPartyProvider{
ID: "github",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: true,
},
Discord: config.ThirdPartyProvider{
ID: "discord",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: true,
},
Microsoft: config.ThirdPartyProvider{
ID: "microsoft",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: false,
},
Facebook: config.ThirdPartyProvider{
ID: "facebook",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: false,
},
},
ErrorRedirectURL: "https://error.test.example",
RedirectURL: "https://api.test.example/callback",
AllowedRedirectURLS: allowedRedirectURLs,
}
cfg.AuditLog.Storage.Enabled = true
cfg.AuditLog.Mask = false
cfg.Email.Limit = 5
cfg.Account.AllowSignup = true
for _, provider := range enabledProviders {
switch provider {
case "apple":
cfg.ThirdParty.Providers.Apple.Enabled = true
case "google":
cfg.ThirdParty.Providers.Google.Enabled = true
case "github":
cfg.ThirdParty.Providers.GitHub.Enabled = true
case "discord":
cfg.ThirdParty.Providers.Discord.Enabled = true
case "microsoft":
cfg.ThirdParty.Providers.Microsoft.Enabled = true
case "facebook":
cfg.ThirdParty.Providers.Facebook.Enabled = true
}
}
err := cfg.PostProcess()
s.Require().NoError(err)
return cfg
}
func (s *thirdPartySuite) setUpFakeJwkSet() jwk2.Set {
s.T().Helper()
generator := test.JwkManager{}
keySet, err := generator.GetPublicKeys()
s.Require().NoError(err)
return keySet
}
func (s *thirdPartySuite) setUpAppleIdToken(sub, aud, email string, emailVerified bool, emailVerifiedTypeBool bool) string {
s.T().Helper()
token := jwt.New()
_ = token.Set(jwt.SubjectKey, sub)
_ = token.Set(jwt.IssuedAtKey, time.Now().UTC())
_ = token.Set(jwt.IssuerKey, "https://appleid.apple.com")
_ = token.Set(jwt.AudienceKey, aud)
_ = token.Set("email", email)
if emailVerifiedTypeBool {
_ = token.Set("email_verified", emailVerified)
} else {
_ = token.Set("email_verified", strconv.FormatBool(emailVerified))
}
generator := test.JwkManager{}
signingKey, err := generator.GetSigningKey()
s.Require().NoError(err)
signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, signingKey))
s.Require().NoError(err)
return string(signedToken)
}
func (s *thirdPartySuite) setUpMicrosoftIdToken(sub, aud, email string, edov bool) string {
s.T().Helper()
token := jwt.New()
_ = token.Set(jwt.SubjectKey, sub)
_ = token.Set(jwt.IssuedAtKey, time.Now().UTC())
_ = token.Set(jwt.IssuerKey, "https://login.microsoftonline.com/0ec22c9c-397e-484d-8edc-6212147ebe5b/v2.0")
_ = token.Set(jwt.AudienceKey, aud)
_ = token.Set("email", email)
_ = token.Set("xms_edov", edov)
generator := test.JwkManager{}
signingKey, err := generator.GetSigningKey()
s.Require().NoError(err)
signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, signingKey))
s.Require().NoError(err)
return string(signedToken)
}
func (s *thirdPartySuite) assertLocationHeaderHasToken(rec *httptest.ResponseRecorder) {
s.T().Helper()
location, err := url.Parse(rec.Header().Get("Location"))
s.NoError(err)
s.True(location.Query().Has(utils.HankoTokenQuery))
s.NotEmpty(location.Query().Get(utils.HankoTokenQuery))
}
func (s *thirdPartySuite) assertStateCookieRemoved(rec *httptest.ResponseRecorder) {
s.T().Helper()
cookies := rec.Result().Cookies()
s.Len(cookies, 1)
s.Equal(utils.HankoThirdpartyStateCookie, cookies[0].Name)
s.Equal(-1, cookies[0].MaxAge)
}