Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added network interceptor hook #386

Merged
merged 10 commits into from
Nov 6, 2023
2 changes: 2 additions & 0 deletions .github/workflows/enforce-go-mod-tidy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jobs:
# When using actions/checkout in a custom container, the directory is not treated as a git repo and does not have a .git directory, therefore we need to initialize it as a git repo. This will allows us to track changes made after go mod tidy runs
- name: Create a new git repository
run: git init && git add --all && git -c user.name='test' -c user.email='[email protected]' commit -m 'init for pr action'
- name: Install latest go
run: wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz && rm -rf /usr/local/go && tar -C /usr/local -xzf go*.tar.gz && export PATH=$PATH:/usr/local/go/bin && rm go1.21.3.linux-amd64.tar.gz
- name: Go mod tidy for root project
run: go mod tidy
- name: Go mod tidy for example apps
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pre-commit-hook-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ jobs:
with:
node-version: '12'
- run: git init && git add --all && git -c user.name='test' -c user.email='[email protected]' commit -m 'init for pr action'
- name: Install latest go
run: wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz && rm -rf /usr/local/go && tar -C /usr/local -xzf go*.tar.gz && export PATH=$PATH:/usr/local/go/bin && rm go1.21.3.linux-amd64.tar.gz
- run: ./hooks/pre-commit.sh
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ jobs:
run: cd ../supertokens-root && ./loadModules
- name: Setting up supertokens-root test environment
run: cd ../supertokens-root && bash ./utils/setupTestEnvLocal
- name: Install latest go
run: wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz && rm -rf /usr/local/go && tar -C /usr/local -xzf go*.tar.gz && export PATH=$PATH:/usr/local/go/bin && rm go1.21.3.linux-amd64.tar.gz
- name: Run tests
run: go test ./... -p 1 -v count=1
env:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.16.6] - 2023-11-3

- Added `NetworkInterceptor` to the `ConnectionInfo` config.
- This can be used to capture/modify all the HTTP requests sent to the core.
- Solves the issue - https://github.com/supertokens/supertokens-core/issues/865

## [0.16.5] - 2023-11-1

Expand Down
2 changes: 1 addition & 1 deletion recipe/dashboard/api/analyticsPOST.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func AnalyticsPost(apiInterface dashboardmodels.APIInterface, tenantId string, o
return analyticsPostResponse{}, err
}

response, err := querier.SendGetRequest("/telemetry", nil)
response, err := querier.SendGetRequest("/telemetry", nil, userContext)
if err != nil {
// We don't send telemetry events if this fails
return analyticsPostResponse{
Expand Down
2 changes: 1 addition & 1 deletion recipe/dashboard/api/search/tagsGet.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func SearchTagsGet(apiImplementation dashboardmodels.APIInterface, tenantId stri
return searchTagsResponse{}, querierErr
}

apiResponse, apiErr := querier.SendGetRequest("/user/search/tags", nil)
apiResponse, apiErr := querier.SendGetRequest("/user/search/tags", nil, userContext)
if apiErr != nil {
return searchTagsResponse{}, apiErr
}
Expand Down
2 changes: 1 addition & 1 deletion recipe/dashboard/api/signInPost.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func SignInPost(apiInterface dashboardmodels.APIInterface, options dashboardmode
apiResponse, apiErr := querier.SendPostRequest("/recipe/dashboard/signin", map[string]interface{}{
"email": *readBody.Email,
"password": *readBody.Password,
})
}, userContext)

if apiErr != nil {
return apiErr
Expand Down
2 changes: 1 addition & 1 deletion recipe/dashboard/api/signOutPost.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func SignOutPost(apiInterface dashboardmodels.APIInterface, tenantId string, opt

_, apiError := querier.SendDeleteRequest("/recipe/dashboard/session", map[string]interface{}{}, map[string]string{
"sessionId": sessionIdFromHeader,
})
}, userContext)

if apiError != nil {
return signOutPostResponse{}, apiError
Expand Down
2 changes: 1 addition & 1 deletion recipe/dashboard/recipeimplementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func makeRecipeImplementation(querier supertokens.Querier) dashboardmodels.Recip

verifyResponse, err := querier.SendPostRequest("/recipe/dashboard/session/verify", map[string]interface{}{
"sessionId": authHeaderValue,
})
}, userContext)

if err != nil {
return false, err
Expand Down
227 changes: 227 additions & 0 deletions recipe/emailpassword/network_interceptor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package emailpassword

import (
"bytes"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
"github.com/supertokens/supertokens-golang/recipe/userroles"
"github.com/supertokens/supertokens-golang/supertokens"
"github.com/supertokens/supertokens-golang/test/unittesting"
)

var isNetworkIntercepted = false

func TestNetworkInterceptorDuringSignIn(t *testing.T) {
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
NetworkInterceptor: func(request *http.Request, context supertokens.UserContext) *http.Request {
isNetworkIntercepted = true
return request
},
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "api.supertokens.io",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}
BeforeEach()

unittesting.StartUpST("localhost", "8080")

defer AfterEach()

err := supertokens.Init(configValue)

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

mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

res, err := unittesting.SignInRequest("[email protected]", "validpass123", testServer.URL)

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

assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, true, isNetworkIntercepted)
}

func TestNetworkInterceptorNotSet(t *testing.T) {
isNetworkIntercepted = false
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "api.supertokens.io",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}
BeforeEach()

unittesting.StartUpST("localhost", "8080")

defer AfterEach()

err := supertokens.Init(configValue)

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

mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

res, err := unittesting.SignInRequest("[email protected]", "validpass123", testServer.URL)

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

assert.Equal(t, 200, res.StatusCode)
assert.Equal(t, false, isNetworkIntercepted)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • add a test where you modify the url of the request to the core, and then check that the function call to signIn returns an error with the right content (should have 404 status code, or the message should have a 404).

  • add a test where you modify the query params of the request to the core, and then check that the function to a GET request (like list roles for users), returns a 400 from the core

  • add a test where you modify the request body and then check that the function call to the core returns a 400


func TestNetworkInterceptorIncorrectCoreURL(t *testing.T) {
isNetworkIntercepted = false
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
NetworkInterceptor: func(request *http.Request, context supertokens.UserContext) *http.Request {
isNetworkIntercepted = true
newRequest := request
newRequest.URL.Path = "/public/recipe/incorrect/path"
return newRequest
},
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "api.supertokens.io",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}
BeforeEach()

unittesting.StartUpST("localhost", "8080")

defer AfterEach()

err := supertokens.Init(configValue)

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

mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

_, err = SignIn("public", "[email protected]", "validpass123")

assert.NotNil(t, err, "there should be an error")
assert.Contains(t, err.Error(), "status code: 404")
assert.Equal(t, true, isNetworkIntercepted)
}

func TestNetworkInterceptorIncorrectQueryParams(t *testing.T) {
isNetworkIntercepted = false
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
NetworkInterceptor: func(r *http.Request, context supertokens.UserContext) *http.Request {
isNetworkIntercepted = true
newRequest := r
q := url.Values{}
newRequest.URL.RawQuery = q.Encode()
return newRequest
},
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "api.supertokens.io",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
userroles.Init(nil),
},
}
BeforeEach()

unittesting.StartUpST("localhost", "8080")

defer AfterEach()

supertokens.Init(configValue)

mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

resp, _ := SignUp("public", "[email protected]", "validpass123")
_, err := userroles.GetRolesForUser("public", resp.OK.User.ID)
assert.NotNil(t, err, "should err, because userId is not passed")
assert.Contains(t, err.Error(), "status code: 400")
assert.Equal(t, true, isNetworkIntercepted)
}

func TestNetworkInterceptorRequestBody(t *testing.T) {
isNetworkIntercepted = false
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
NetworkInterceptor: func(r *http.Request, context supertokens.UserContext) *http.Request {
isNetworkIntercepted = true
newBody := bytes.NewReader([]byte(`{"newKey": "newValue"}`))
req, _ := http.NewRequest(r.Method, r.URL.String(), newBody)
req.Header = r.Header
return req
},
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "api.supertokens.io",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}
BeforeEach()

unittesting.StartUpST("localhost", "8080")

defer AfterEach()

supertokens.Init(configValue)

mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

_, err := SignIn("public", "[email protected]", "validpass123")
assert.NotNil(t, err, "should err, because request body is incorrect")
assert.Contains(t, err.Error(), "status code: 400")
assert.Equal(t, true, isNetworkIntercepted)
}
14 changes: 7 additions & 7 deletions recipe/emailpassword/recipeImplementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
response, err := querier.SendPostRequest(tenantId+"/recipe/signup", map[string]interface{}{
"email": email,
"password": password,
})
}, userContext)
if err != nil {
return epmodels.SignUpResponse{}, err
}
Expand All @@ -48,7 +48,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
response, err := querier.SendPostRequest(tenantId+"/recipe/signin", map[string]interface{}{
"email": email,
"password": password,
})
}, userContext)
if err != nil {
return epmodels.SignInResponse{}, err
}
Expand All @@ -70,7 +70,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
getUserByID := func(userID string, userContext supertokens.UserContext) (*epmodels.User, error) {
response, err := querier.SendGetRequest("/recipe/user", map[string]string{
"userId": userID,
})
}, userContext)
if err != nil {
return nil, err
}
Expand All @@ -88,7 +88,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
getUserByEmail := func(email string, tenantId string, userContext supertokens.UserContext) (*epmodels.User, error) {
response, err := querier.SendGetRequest(tenantId+"/recipe/user", map[string]string{
"email": email,
})
}, userContext)
if err != nil {
return nil, err
}
Expand All @@ -106,7 +106,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
createResetPasswordToken := func(userID string, tenantId string, userContext supertokens.UserContext) (epmodels.CreateResetPasswordTokenResponse, error) {
response, err := querier.SendPostRequest(tenantId+"/recipe/user/password/reset/token", map[string]interface{}{
"userId": userID,
})
}, userContext)
if err != nil {
return epmodels.CreateResetPasswordTokenResponse{}, err
}
Expand All @@ -126,7 +126,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
"method": "token",
"token": token,
"newPassword": newPassword,
})
}, userContext)
if err != nil {
return epmodels.ResetPasswordUsingTokenResponse{}, nil
}
Expand Down Expand Up @@ -182,7 +182,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, getEmailPasswordConfi
}
requestBody["password"] = password
}
response, err := querier.SendPutRequest("/recipe/user", requestBody)
response, err := querier.SendPutRequest("/recipe/user", requestBody, userContext)
if err != nil {
return epmodels.UpdateEmailOrPasswordResponse{}, nil
}
Expand Down
Loading
Loading