mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-26 21:57:14 +08:00
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>
514 lines
16 KiB
Go
514 lines
16 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/dto"
|
|
"github.com/teamhanko/hanko/backend/dto/admin"
|
|
"github.com/teamhanko/hanko/backend/test"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestEmailAdminSuite(t *testing.T) {
|
|
t.Parallel()
|
|
suite.Run(t, new(emailAdminSuite))
|
|
}
|
|
|
|
type emailAdminSuite struct {
|
|
test.Suite
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_New() {
|
|
emailHandler := NewEmailAdminHandler(&config.Config{}, s.Storage)
|
|
s.NotEmpty(emailHandler)
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_List() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/email")
|
|
s.Require().NoError(err)
|
|
|
|
e := NewAdminRouter(&test.DefaultConfig, s.Storage, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
userId string
|
|
expectedCount int
|
|
expectedStatusCode int
|
|
}{
|
|
{
|
|
name: "should return all user assigned email addresses",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedCount: 3,
|
|
},
|
|
{
|
|
name: "should return an empty list when the user has no email addresses assigned",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
expectedCount: 0,
|
|
},
|
|
{
|
|
name: "should fail on non uuid",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail on empty",
|
|
userId: "",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to find non existing user",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032f",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
}
|
|
|
|
for _, currentTest := range tests {
|
|
s.Run(currentTest.name, func() {
|
|
testDto := admin.ListEmailRequestDto{
|
|
UserId: currentTest.userId,
|
|
}
|
|
testJson, err := json.Marshal(testDto)
|
|
s.Require().NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/emails", currentTest.userId), bytes.NewReader(testJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
if http.StatusOK == rec.Code {
|
|
var emails []*dto.EmailResponse
|
|
s.NoError(json.Unmarshal(rec.Body.Bytes(), &emails))
|
|
s.Equal(currentTest.expectedCount, len(emails))
|
|
} else {
|
|
s.Require().Equal(currentTest.expectedStatusCode, rec.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_Create() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/email")
|
|
s.Require().NoError(err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
email string
|
|
userId string
|
|
maxNumberOfAddresses int
|
|
isVerified bool
|
|
expectedStatusCode int
|
|
}{
|
|
{
|
|
name: "should reject the request when the user has already reached the maximum number of email addresses",
|
|
email: "rejection1@example.com",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
maxNumberOfAddresses: 0,
|
|
isVerified: false,
|
|
expectedStatusCode: http.StatusConflict,
|
|
},
|
|
{
|
|
name: "should error if email address is already in use",
|
|
email: "john.doe@example.com",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
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: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusCreated,
|
|
},
|
|
{
|
|
name: "should create email record with nil user ID if verification required",
|
|
email: "test.email.verification@example.com",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: true,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusCreated,
|
|
},
|
|
{
|
|
name: "should create email record with user ID if verification not required",
|
|
email: "test.email.noverification@example.com",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusCreated,
|
|
},
|
|
{
|
|
name: "should fail to create email record with missing user id",
|
|
email: "test.email.noverification@example.com",
|
|
userId: "",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to create email record with non uuid user id",
|
|
email: "test.email.noverification@example.com",
|
|
userId: "lorem",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to create email record with wrong user id",
|
|
email: "test.email.noverification@example.com",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032f",
|
|
isVerified: false,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should create verified email record with wrong user id",
|
|
email: "test.email.noverification@example.com",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: true,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to create email with missing email",
|
|
email: "",
|
|
userId: "d41df4b7-c055-45e6-9faf-61aa92a4032e",
|
|
isVerified: true,
|
|
maxNumberOfAddresses: 100,
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
}
|
|
|
|
for _, currentTest := range tests {
|
|
s.Run(currentTest.name, func() {
|
|
cfg := test.DefaultConfig
|
|
cfg.Email.Limit = currentTest.maxNumberOfAddresses
|
|
|
|
e := NewAdminRouter(&cfg, s.Storage, nil)
|
|
|
|
body := admin.CreateEmailRequestDto{
|
|
ListEmailRequestDto: admin.ListEmailRequestDto{
|
|
UserId: currentTest.userId,
|
|
},
|
|
CreateEmail: admin.CreateEmail{
|
|
Address: currentTest.email,
|
|
IsVerified: currentTest.isVerified,
|
|
},
|
|
}
|
|
bodyJson, err := json.Marshal(body)
|
|
s.Require().NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/emails", currentTest.userId), bytes.NewReader(bodyJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(currentTest.expectedStatusCode, rec.Code)
|
|
|
|
if rec.Code == http.StatusOK {
|
|
email, err := s.Storage.GetEmailPersister().FindByAddress(currentTest.email)
|
|
s.Require().NoError(err)
|
|
|
|
if email != nil {
|
|
s.Equal(currentTest.userId, email.UserID.String())
|
|
s.Equal(currentTest.isVerified, email.Verified)
|
|
}
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_Get() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/email")
|
|
s.Require().NoError(err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
emailId string
|
|
userId string
|
|
expectedStatusCode int
|
|
}{
|
|
{
|
|
name: "should get a single email",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusOK,
|
|
},
|
|
{
|
|
name: "should fail to get an email for a non existent user",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a6",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to get an email for a wrong user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "lorem",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to get an email for an empty user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to get a non existent email",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1ff",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to get an empty email uuid",
|
|
emailId: "",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to get a wrong email uuid",
|
|
emailId: "lorem",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
}
|
|
|
|
for _, currentTest := range tests {
|
|
s.Run(currentTest.name, func() {
|
|
cfg := test.DefaultConfig
|
|
|
|
e := NewAdminRouter(&cfg, s.Storage, nil)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/emails/%s", currentTest.userId, currentTest.emailId), nil)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(currentTest.expectedStatusCode, rec.Code)
|
|
|
|
if rec.Code == http.StatusOK {
|
|
var email admin.Email
|
|
err := json.Unmarshal(rec.Body.Bytes(), &email)
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().NotNil(email)
|
|
s.Require().Equal(currentTest.emailId, email.ID.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_Delete() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/email")
|
|
s.Require().NoError(err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
emailId string
|
|
userId string
|
|
expectedStatusCode int
|
|
}{
|
|
{
|
|
name: "should delete an email",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNoContent,
|
|
},
|
|
{
|
|
name: "should fail to delete an email for a non existent user",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a6",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to delete an email for a wrong user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "lorem",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to delete an email for an empty user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to delete a non existent email",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1ff",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to delete an empty email uuid",
|
|
emailId: "",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to delete a wrong email uuid",
|
|
emailId: "lorem",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to delete a primary email",
|
|
emailId: "51b7c175-ceb6-45ba-aae6-0092221c1b84",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusConflict,
|
|
},
|
|
}
|
|
|
|
for _, currentTest := range tests {
|
|
s.Run(currentTest.name, func() {
|
|
cfg := test.DefaultConfig
|
|
|
|
e := NewAdminRouter(&cfg, s.Storage, nil)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s/emails/%s", currentTest.userId, currentTest.emailId), nil)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(currentTest.expectedStatusCode, rec.Code)
|
|
|
|
if rec.Code == http.StatusOK {
|
|
var email admin.Email
|
|
err := json.Unmarshal(rec.Body.Bytes(), &email)
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().NotNil(email)
|
|
s.Require().Equal(currentTest.emailId, email.ID.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *emailAdminSuite) TestEmailAdminHandler_SetPrimaryEmail() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/email")
|
|
s.Require().NoError(err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
emailId string
|
|
userId string
|
|
isAlreadyPrimary bool
|
|
expectedStatusCode int
|
|
}{
|
|
{
|
|
name: "should set an email as primary",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
isAlreadyPrimary: false,
|
|
expectedStatusCode: http.StatusNoContent,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for a non existent user",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a6",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for a wrong user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "lorem",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for an empty user UUID",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1fe",
|
|
userId: "",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for a non existent email",
|
|
emailId: "f194ee0f-dd1a-48f7-8766-c67e4d6cd1ff",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for an empty email uuid",
|
|
emailId: "",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "should fail to set an email as primary for a wrong email uuid",
|
|
emailId: "lorem",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Should not change a primary email",
|
|
emailId: "51b7c175-ceb6-45ba-aae6-0092221c1b84",
|
|
userId: "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5",
|
|
expectedStatusCode: http.StatusNoContent,
|
|
},
|
|
}
|
|
|
|
for _, currentTest := range tests {
|
|
s.Run(currentTest.name, func() {
|
|
cfg := test.DefaultConfig
|
|
|
|
e := NewAdminRouter(&cfg, s.Storage, nil)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/emails/%s/set_primary", currentTest.userId, currentTest.emailId), nil)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(currentTest.expectedStatusCode, rec.Code)
|
|
|
|
if rec.Code == http.StatusNoContent {
|
|
userUuid, err := uuid.FromString(currentTest.userId)
|
|
s.Require().NoError(err)
|
|
|
|
emails, err := s.Storage.GetEmailPersister().FindByUserId(userUuid)
|
|
s.Require().NoError(err)
|
|
|
|
s.Equal(3, len(emails))
|
|
for _, email := range emails {
|
|
if email.ID.String() == currentTest.emailId {
|
|
s.True(email.IsPrimary())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|