Files
hanko/backend/handler/email_test.go
bjoern-m 601ffaae92 Introduce Flowpilot - integration (#1532)
This pull request introduces the new Flowpilot system along with several new features and various improvements. The key enhancements include configurable authorization, registration, and profile flows, as well as the ability to enable and disable user identifiers (e.g., email addresses and usernames) and login methods.

---------

Co-authored-by: Frederic Jahn <frederic.jahn@hanko.io>
Co-authored-by: Lennart Fleischmann <lennart.fleischmann@hanko.io>
Co-authored-by: lfleischmann <67686424+lfleischmann@users.noreply.github.com>
Co-authored-by: merlindru <hello@merlindru.com>
2024-08-06 16:07:29 +02:00

335 lines
10 KiB
Go

package handler
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/suite"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/crypto/jwk"
"github.com/teamhanko/hanko/backend/dto"
"github.com/teamhanko/hanko/backend/session"
"github.com/teamhanko/hanko/backend/test"
"net/http"
"net/http/httptest"
"testing"
)
func TestEmailSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(emailSuite))
}
type emailSuite struct {
test.Suite
}
func (s *emailSuite) TestEmailHandler_New() {
emailHandler := NewEmailHandler(&config.Config{}, s.Storage, sessionManager{}, test.NewAuditLogger())
s.NotEmpty(emailHandler)
}
func (s *emailSuite) TestEmailHandler_List() {
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}
err := s.LoadFixtures("../test/fixtures/email")
s.Require().NoError(err)
e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil)
jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister())
s.Require().NoError(err)
sessionManager, err := session.NewManager(jwkManager, test.DefaultConfig)
s.Require().NoError(err)
tests := []struct {
name string
userId uuid.UUID
expectedCount int
}{
{
name: "should return all user assigned email addresses",
userId: uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5"),
expectedCount: 3,
},
{
name: "should return an empty list when the user has no email addresses assigned",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
expectedCount: 0,
},
}
for _, currentTest := range tests {
s.Run(currentTest.name, func() {
token, err := sessionManager.GenerateJWT(currentTest.userId, nil)
s.Require().NoError(err)
cookie, err := sessionManager.GenerateCookie(token)
s.Require().NoError(err)
req := httptest.NewRequest(http.MethodGet, "/emails", nil)
req.AddCookie(cookie)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
if s.Equal(http.StatusOK, rec.Code) {
var emails []*dto.EmailResponse
s.NoError(json.Unmarshal(rec.Body.Bytes(), &emails))
s.Equal(currentTest.expectedCount, len(emails))
}
})
}
}
func (s *emailSuite) TestEmailHandler_Create() {
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}
auditLogRecordsKey := "email_created"
err := s.LoadFixtures("../test/fixtures/email")
s.Require().NoError(err)
tests := []struct {
name string
email string
userId uuid.UUID
maxNumberOfAddresses int
requiresVerification bool
expectedStatusCode int
upsertsRecords bool
expectedEmailUserId uuid.UUID
}{
{
name: "should reject the request when the user has already reached the maximum number of email addresses",
email: "rejection1@example.com",
userId: uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5"),
maxNumberOfAddresses: 0,
requiresVerification: false,
expectedStatusCode: 409,
upsertsRecords: false,
},
{
name: "should error if email address is already in use",
email: "john.doe@example.com",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
requiresVerification: false,
maxNumberOfAddresses: 100,
expectedStatusCode: 400,
upsertsRecords: false,
expectedEmailUserId: uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5"),
},
{
name: "should assign the email address to the user if not yet assigned and does not require verification",
email: "john.doe+6@example.com",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
requiresVerification: false,
maxNumberOfAddresses: 100,
expectedStatusCode: 200,
upsertsRecords: true,
expectedEmailUserId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
},
{
name: "should not assign the email address to the user if not yet assigned and requires verification",
email: "john.doe+7@example.com",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
requiresVerification: true,
maxNumberOfAddresses: 100,
expectedStatusCode: 200,
upsertsRecords: false,
expectedEmailUserId: uuid.Nil,
},
{
name: "should create email record with nil user ID if verification required",
email: "test.email.verification@example.com",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
requiresVerification: true,
maxNumberOfAddresses: 100,
expectedStatusCode: 200,
upsertsRecords: true,
expectedEmailUserId: uuid.Nil,
},
{
name: "should create email record with user ID if verification not required",
email: "test.email.noverification@example.com",
userId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
requiresVerification: false,
maxNumberOfAddresses: 100,
expectedStatusCode: 200,
upsertsRecords: true,
expectedEmailUserId: uuid.FromStringOrNil("d41df4b7-c055-45e6-9faf-61aa92a4032e"),
},
}
for _, currentTest := range tests {
s.Run(currentTest.name, func() {
cfg := test.DefaultConfig
cfg.AuditLog.Storage.Enabled = true
cfg.Email.RequireVerification = currentTest.requiresVerification
cfg.Email.Limit = currentTest.maxNumberOfAddresses
e := NewPublicRouter(&cfg, s.Storage, nil, nil)
jwkManager, err := jwk.NewDefaultManager(cfg.Secrets.Keys, s.Storage.GetJwkPersister())
s.Require().NoError(err)
sessionManager, err := session.NewManager(jwkManager, cfg)
s.Require().NoError(err)
token, err := sessionManager.GenerateJWT(currentTest.userId, nil)
s.Require().NoError(err)
cookie, err := sessionManager.GenerateCookie(token)
s.Require().NoError(err)
body := dto.EmailCreateRequest{
Address: currentTest.email,
}
bodyJson, err := json.Marshal(body)
s.Require().NoError(err)
req := httptest.NewRequest(http.MethodPost, "/emails", bytes.NewReader(bodyJson))
req.Header.Set("Content-Type", "application/json")
req.AddCookie(cookie)
rec := httptest.NewRecorder()
auditLogsCountBefore := s.getAuditLogRecordsCount(auditLogRecordsKey)
e.ServeHTTP(rec, req)
auditLogsCountAfter := s.getAuditLogRecordsCount(auditLogRecordsKey)
s.Equal(currentTest.expectedStatusCode, rec.Code)
email, err := s.Storage.GetEmailPersister().FindByAddress(currentTest.email)
s.Require().NoError(err)
if currentTest.upsertsRecords {
s.NotNil(email)
}
if email != nil {
if currentTest.expectedEmailUserId != uuid.Nil {
s.Equal(currentTest.expectedEmailUserId, *email.UserID)
} else {
s.Nil(email.UserID)
}
}
if rec.Code == http.StatusOK {
s.Equal(auditLogsCountBefore+1, auditLogsCountAfter)
} else {
s.Equal(auditLogsCountBefore, auditLogsCountAfter)
}
})
}
}
func (s *emailSuite) TestEmailHandler_SetPrimaryEmail() {
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}
err := s.LoadFixtures("../test/fixtures/email")
s.Require().NoError(err)
e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil)
jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister())
s.Require().NoError(err)
sessionManager, err := session.NewManager(jwkManager, test.DefaultConfig)
s.Require().NoError(err)
oldPrimaryEmailId := uuid.FromStringOrNil("51b7c175-ceb6-45ba-aae6-0092221c1b84")
newPrimaryEmailId := uuid.FromStringOrNil("8bb4c8a7-a3e6-48bb-b54f-20e3b485ab33")
userId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5")
token, err := sessionManager.GenerateJWT(userId, nil)
s.NoError(err)
cookie, err := sessionManager.GenerateCookie(token)
s.NoError(err)
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/emails/%s/set_primary", newPrimaryEmailId), nil)
req.AddCookie(cookie)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
if s.Equal(http.StatusNoContent, rec.Code) {
emails, err := s.Storage.GetEmailPersister().FindByUserId(userId)
s.Require().NoError(err)
s.Equal(3, len(emails))
for _, email := range emails {
if email.ID == newPrimaryEmailId {
s.True(email.IsPrimary())
} else if email.ID == oldPrimaryEmailId {
s.False(email.IsPrimary())
}
}
}
}
func (s *emailSuite) TestEmailHandler_Delete() {
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}
err := s.LoadFixtures("../test/fixtures/email")
s.Require().NoError(err)
e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil)
userId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5")
jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister())
s.Require().NoError(err)
sessionManager, err := session.NewManager(jwkManager, test.DefaultConfig)
s.Require().NoError(err)
token, err := sessionManager.GenerateJWT(userId, nil)
s.NoError(err)
cookie, err := sessionManager.GenerateCookie(token)
s.NoError(err)
tests := []struct {
name string
emailId uuid.UUID
responseCode int
expectedCount int
}{
{
name: "should delete the email address",
emailId: uuid.FromStringOrNil("8bb4c8a7-a3e6-48bb-b54f-20e3b485ab33"),
responseCode: http.StatusNoContent,
expectedCount: 2,
},
{
name: "should not delete the primary email address",
emailId: uuid.FromStringOrNil("51b7c175-ceb6-45ba-aae6-0092221c1b84"),
responseCode: http.StatusConflict,
expectedCount: 2,
},
}
for _, currentTest := range tests {
s.Run(currentTest.name, func() {
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/emails/%s", currentTest.emailId), nil)
req.AddCookie(cookie)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
if s.Equal(currentTest.responseCode, rec.Code) {
emails, err := s.Storage.GetEmailPersister().FindByUserId(userId)
s.Require().NoError(err)
s.Equal(currentTest.expectedCount, len(emails))
}
})
}
}
func (s *emailSuite) getAuditLogRecordsCount(code string) int {
logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{code}, "", "", "", "")
s.Require().NoError(lerr)
return len(logs)
}