mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-26 21:57:14 +08:00
224 lines
6.6 KiB
Go
224 lines
6.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"github.com/gofrs/uuid"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/teamhanko/hanko/backend/v2/config"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence/models"
|
|
"github.com/teamhanko/hanko/backend/v2/test"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestTokenSuite(t *testing.T) {
|
|
t.Parallel()
|
|
suite.Run(t, new(tokenSuite))
|
|
}
|
|
|
|
type tokenSuite struct {
|
|
test.Suite
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_TokenInCookie() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/token")
|
|
|
|
// must create and insert a valid token manually instead of using fixtures, because token
|
|
// validation is time sensitive (expiration is checked relative to current time)
|
|
uId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5")
|
|
token, err := models.NewToken(uId)
|
|
s.NoError(err)
|
|
err = s.Storage.GetTokenPersister().Create(*token)
|
|
s.NoError(err)
|
|
|
|
body := TokenValidationBody{Value: token.Value}
|
|
bodyJson, err := json.Marshal(body)
|
|
s.NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader(bodyJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
cfg := s.setupConfig()
|
|
cfg.Session.EnableAuthTokenHeader = false
|
|
e := NewPublicRouter(cfg, s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusOK)
|
|
t, err := s.Storage.GetTokenPersister().GetByValue(token.Value)
|
|
s.NoError(err)
|
|
s.Nil(t)
|
|
|
|
s.Empty(rec.Header().Get("X-Auth-Token"))
|
|
cookies := rec.Result().Cookies()
|
|
rec.Result().Cookies()
|
|
s.NotEmpty(cookies)
|
|
for _, cookie := range cookies {
|
|
if cookie.Name == "hanko" {
|
|
s.Regexp(".*\\..*\\..*", cookie.Value)
|
|
}
|
|
}
|
|
|
|
logs, err := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_succeeded"}, "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5", "", "", "")
|
|
s.Len(logs, 1)
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_TokenInHeader() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/token")
|
|
|
|
// must create and insert a valid token manually instead of using fixtures, because token
|
|
// validation is time sensitive (expiration is checked relative to current time)
|
|
uId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5")
|
|
token, err := models.NewToken(uId)
|
|
s.NoError(err)
|
|
err = s.Storage.GetTokenPersister().Create(*token)
|
|
s.NoError(err)
|
|
|
|
body := TokenValidationBody{Value: token.Value}
|
|
bodyJson, err := json.Marshal(body)
|
|
s.NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader(bodyJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
cfg := s.setupConfig()
|
|
e := NewPublicRouter(cfg, s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusOK)
|
|
t, err := s.Storage.GetTokenPersister().GetByValue(token.Value)
|
|
s.NoError(err)
|
|
s.Nil(t)
|
|
|
|
s.Empty(rec.Result().Cookies())
|
|
responseToken := rec.Header().Get("X-Auth-Token")
|
|
s.NotEmpty(responseToken)
|
|
s.Regexp(".*\\..*\\..*", responseToken)
|
|
|
|
logs, err := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_succeeded"}, "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5", "", "", "")
|
|
s.Len(logs, 1)
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_ExpiredToken() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
err := s.LoadFixtures("../test/fixtures/token")
|
|
|
|
expiredTokenValue := "Trkauhl3q7XVxw5JcDH80lTe1KxzydIw0OcizH7umWk="
|
|
body := TokenValidationBody{Value: expiredTokenValue}
|
|
bodyJson, err := json.Marshal(body)
|
|
s.NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader(bodyJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusUnprocessableEntity)
|
|
var errorResponse echo.HTTPError
|
|
marshalErr := json.Unmarshal(rec.Body.Bytes(), &errorResponse)
|
|
s.NoError(marshalErr)
|
|
s.Contains(errorResponse.Message, "expired")
|
|
|
|
logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_failed"}, "", "", "", "")
|
|
s.NoError(lerr)
|
|
s.Len(logs, 1)
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_MissingTokenFromRequest() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", nil)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusBadRequest)
|
|
var errorResponse echo.HTTPError
|
|
marshalErr := json.Unmarshal(rec.Body.Bytes(), &errorResponse)
|
|
s.NoError(marshalErr)
|
|
s.Contains("value is a required field", errorResponse.Message)
|
|
|
|
logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_failed"}, "", "", "", "")
|
|
s.NoError(lerr)
|
|
s.Len(logs, 1)
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_InvalidJson() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader([]byte("invalid")))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusBadRequest)
|
|
|
|
logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_failed"}, "", "", "", "")
|
|
s.NoError(lerr)
|
|
s.Len(logs, 1)
|
|
|
|
}
|
|
|
|
func (s *tokenSuite) TestToken_Validate_TokenNotFound() {
|
|
if testing.Short() {
|
|
s.T().Skip("skipping test in short mode.")
|
|
}
|
|
|
|
uId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5")
|
|
token, err := models.NewToken(uId)
|
|
s.NoError(err)
|
|
|
|
body := TokenValidationBody{Value: token.Value}
|
|
bodyJson, err := json.Marshal(body)
|
|
s.NoError(err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader(bodyJson))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil)
|
|
e.ServeHTTP(rec, req)
|
|
|
|
s.Equal(rec.Code, http.StatusNotFound)
|
|
var errorResponse echo.HTTPError
|
|
marshalErr := json.Unmarshal(rec.Body.Bytes(), &errorResponse)
|
|
s.NoError(marshalErr)
|
|
s.Contains("token not found", errorResponse.Message)
|
|
|
|
logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"token_exchange_failed"}, "", "", "", "")
|
|
s.NoError(lerr)
|
|
s.Len(logs, 1)
|
|
}
|
|
|
|
func (s *tokenSuite) setupConfig() *config.Config {
|
|
cfg := test.DefaultConfig
|
|
cfg.Session.EnableAuthTokenHeader = true
|
|
cfg.AuditLog.Storage.Enabled = true
|
|
return &cfg
|
|
}
|