Skip to content

Commit

Permalink
Merge pull request #384 from supertokens/headers-test
Browse files Browse the repository at this point in the history
fix: Fix an issue where there would be duplicate headers in the value of Access-Control-Expose-Headers
  • Loading branch information
rishabhpoddar authored Oct 20, 2023
2 parents 178929e + f8c0a44 commit 2fee7f8
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.16.4] - 2023-10-20

- Fixes an issue where sometimes the `Access-Control-Expose-Headers` header value would contain duplicates

## [0.16.3] - 2023-10-19

- Fixes an issue where trying to view details of a third party user would show an error for the user not being found when using the thirdpartypasswordless recipe
Expand Down
10 changes: 9 additions & 1 deletion recipe/session/cookieAndHeaders.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,15 @@ func setHeader(res http.ResponseWriter, key, value string, allowDuplicateKey boo
if existingValue == "" {
res.Header().Set(key, value)
} else if allowDuplicateKey {
res.Header().Set(key, existingValue+", "+value)
/**
We only want to append if it does not already exist
For example if the caller is trying to add front token to the access control exposed headers property
we do not want to append if something else had already added it
*/
if !strings.Contains(existingValue, value) {
res.Header().Set(key, existingValue+", "+value)
}
} else {
res.Header().Set(key, value)
}
Expand Down
223 changes: 223 additions & 0 deletions recipe/session/verifySession_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -976,6 +977,157 @@ func TestThatAntiCSRFCheckIsSkippedIfSessionRequiredIsFalseAndNoAccessTokenIsPas
assert.Equal(t, res.StatusCode, 200)
}

func TestThatResponseHeadersAreCorrectWhenUsingCookies(t *testing.T) {
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&sessmodels.TypeInput{}),
},
}
BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()
err := supertokens.Init(configValue)
if err != nil {
t.Error(err.Error())
}

app := getTestApp([]typeTestEndpoint{})
defer app.Close()

sessionCookies := createSessionWithCookies(app, map[string]interface{}{})
print(sessionCookies)

var accessToken string

for _, cookie := range sessionCookies {
if cookie.Name == "sAccessToken" {
accessToken = cookie.Value
}
}

assert.NotNil(t, accessToken)

req, err := http.NewRequest(http.MethodGet, app.URL+"/merge-payload", nil)

if err != nil {
t.Error(err.Error())
}

req.Header.Add("Cookie", "sAccessToken="+accessToken)

res, err := http.DefaultClient.Do(req)

if err != nil {
t.Error(err.Error())
}

cookiesHeaderValues := res.Header.Values("Set-Cookie")
accessTokenCount := 0

for _, cookieValue := range cookiesHeaderValues {
if strings.Contains(cookieValue, "sAccessToken") {
accessTokenCount += 1
}
}

assert.Equal(t, accessTokenCount, 1)

accessAllowHeaderValues := strings.Split(res.Header.Get("Access-Control-Expose-Headers"), ",")
frontTokenCount := 0

for _, value := range accessAllowHeaderValues {
if strings.Contains(value, "front-token") {
frontTokenCount += 1
}
}

assert.Equal(t, frontTokenCount, 1)
/**
Goland does not realise that the test passed because the start and end prints happen in the same line
This extra print adds a break line with the "--- PASS:" line being added in a new line making it clear
to the IDE that the test passed.
Leaving this in to avoid future confusion. Weirdly this happens intermittently for all tests.
*/
fmt.Println("")
}

func TestThatResponseHeadersAreCorrectWhenUsingHeaders(t *testing.T) {
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&sessmodels.TypeInput{}),
},
}
BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()
err := supertokens.Init(configValue)
if err != nil {
t.Error(err.Error())
}

app := getTestApp([]typeTestEndpoint{})
defer app.Close()

headers := createSessionWithHeaders(app, map[string]interface{}{})
accessToken := headers.Get("st-access-token")

assert.NotNil(t, accessToken)

req, err := http.NewRequest(http.MethodGet, app.URL+"/merge-payload", nil)

if err != nil {
t.Error(err.Error())
}

req.Header.Add("Authorization", "Bearer "+accessToken)

res, err := http.DefaultClient.Do(req)

if err != nil {
t.Error(err.Error())
}

accessTokenHeaderValues := res.Header.Values("st-access-token")
accessTokenCount := len(accessTokenHeaderValues)

assert.Equal(t, accessTokenCount, 1)

accessAllowHeaderValues := strings.Split(res.Header.Get("Access-Control-Expose-Headers"), ",")
frontTokenCount := 0
stAccessTokenCount := 0

for _, value := range accessAllowHeaderValues {
if strings.Contains(value, "front-token") {
frontTokenCount += 1
}

if strings.Contains(value, "st-access-token") {
stAccessTokenCount += 1
}
}

assert.Equal(t, frontTokenCount, 1)
assert.Equal(t, stAccessTokenCount, 1)
}

type typeTestEndpoint struct {
path string
overrideGlobalClaimValidators func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error)
Expand All @@ -993,6 +1145,46 @@ func createSession(app *httptest.Server, body map[string]interface{}) []*http.Co
return res.Cookies()
}

func createSessionWithCookies(app *httptest.Server, body map[string]interface{}) []*http.Cookie {
bodyBytes := []byte("{}")
if body != nil {
bodyBytes, _ = json.Marshal(body)
}
req, err := http.NewRequest(http.MethodPost, app.URL+"/create", bytes.NewBuffer(bodyBytes))
if err != nil {
return nil
}

req.Header.Set("st-auth-mode", "cookie")

res, err := http.DefaultClient.Do(req)
if err != nil {
return nil
}

return res.Cookies()
}

func createSessionWithHeaders(app *httptest.Server, body map[string]interface{}) http.Header {
bodyBytes := []byte("{}")
if body != nil {
bodyBytes, _ = json.Marshal(body)
}
req, err := http.NewRequest(http.MethodPost, app.URL+"/create", bytes.NewBuffer(bodyBytes))
if err != nil {
return nil
}

req.Header.Set("st-auth-mode", "header")

res, err := http.DefaultClient.Do(req)
if err != nil {
return nil
}

return res.Header
}

func getTestApp(endpoints []typeTestEndpoint) *httptest.Server {
mux := http.NewServeMux()

Expand Down Expand Up @@ -1035,6 +1227,37 @@ func getTestApp(endpoints []typeTestEndpoint) *httptest.Server {
GetSession(r, rw, &sessmodels.VerifySessionOptions{})
}))

mux.HandleFunc("/merge-payload", VerifySession(&sessmodels.VerifySessionOptions{}, func(rw http.ResponseWriter, r *http.Request) {
session, err := GetSession(r, rw, &sessmodels.VerifySessionOptions{})

if err != nil {
rw.WriteHeader(500)
return
}

session.MergeIntoAccessTokenPayload(map[string]interface{}{
"lastUpdate": "123",
})
session.MergeIntoAccessTokenPayload(map[string]interface{}{
"lastUpdate": "456",
})
session.MergeIntoAccessTokenPayload(map[string]interface{}{
"lastUpdate": "789",
})

resp := map[string]interface{}{
"status": "OK",
}
respBytes, err := json.Marshal(resp)
if err != nil {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("Content-Length", fmt.Sprintf("%d", (len(respBytes))))
rw.WriteHeader(http.StatusOK)
rw.Write(respBytes)
}))

mux.HandleFunc("/default-claims", VerifySession(nil, func(w http.ResponseWriter, r *http.Request) {
sessionContainer := GetSessionFromRequestContext(r.Context())
resp := map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion supertokens/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
)

// VERSION current version of the lib
const VERSION = "0.16.3"
const VERSION = "0.16.4"

var (
cdiSupported = []string{"3.0"}
Expand Down

0 comments on commit 2fee7f8

Please sign in to comment.