Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
sattvikc authored Sep 27, 2023
2 parents a2b3f8d + 98b6cfe commit 5e75605
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 47 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Minimum golang version supported is 1.18

## [0.15.0] - 2023-09-26

- Adds Twitter/X as a default provider to the third party recipe
- Added a `Cache-Control` header to `/jwt/jwks.json` (`GetJWKSGET`)
- Added `ValidityInSeconds` to the return value of the overrideable `GetJWKS` function.
- This can be used to control the `Cache-Control` header mentioned above.
- It defaults to `60` or the value set in the cache-control header returned by the core
- This is optional (so you are not required to update your overrides). Returning undefined means that the header is not set.
- Handle AWS Public URLs (ending with `.amazonaws.com`) separately while extracting TLDs for SameSite attribute.
- Return `500` status instead of panic when `supertokens.Middleware` is used without initializing the SDK.
- Updates fiber adaptor package in the fiber example.

## [0.14.0] - 2023-09-11

### Added
Expand Down
4 changes: 1 addition & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ require (
github.com/gin-gonic/gin v1.7.4
github.com/go-chi/chi/v5 v5.0.4
github.com/go-chi/cors v1.2.0
github.com/gofiber/adaptor/v2 v2.1.18
github.com/gofiber/fiber/v2 v2.27.0
github.com/gofiber/fiber/v2 v2.49.2
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/klauspost/compress v1.14.4 // indirect
github.com/labstack/echo/v4 v4.6.1
github.com/osohq/go-oso v0.21.0
github.com/spf13/viper v1.8.1
Expand Down
69 changes: 45 additions & 24 deletions examples/go.sum

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions examples/with-fiber/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package main

import (
"github.com/supertokens/supertokens-golang/recipe/dashboard"
"log"
"net/http"

"github.com/gofiber/adaptor/v2"
"github.com/supertokens/supertokens-golang/recipe/dashboard"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/supertokens/supertokens-golang/recipe/emailverification"
"github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels"
Expand Down
33 changes: 33 additions & 0 deletions recipe/emailpassword/middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package emailpassword

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
"github.com/supertokens/supertokens-golang/supertokens"
)

func TestAPIWithSupertokensMiddlewareButNotInitialized(t *testing.T) {
BeforeEach()
defer AfterEach()

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

resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", nil)
if err != nil {
t.Error(err.Error())
}

assert.Equal(t, 500, resp.StatusCode)
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
assert.NoError(t, err)

bodyStr := string(bodyBytes)
assert.Equal(t, "initialisation not done. Did you forget to call the SuperTokens.init function?\n", bodyStr)
}
8 changes: 7 additions & 1 deletion recipe/jwt/api/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package api

import (
"fmt"
"github.com/supertokens/supertokens-golang/recipe/jwt/jwtmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
Expand All @@ -26,8 +27,13 @@ func MakeAPIImplementation() jwtmodels.APIInterface {
if err != nil {
return jwtmodels.GetJWKSAPIResponse{}, err
}
options.Res.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, must-revalidate", resp.OK.ValidityInSeconds))
return jwtmodels.GetJWKSAPIResponse{
OK: resp.OK,
OK: &struct {
Keys []jwtmodels.JsonWebKeys
}{
Keys: resp.OK.Keys,
},
}, nil
}
return jwtmodels.APIInterface{
Expand Down
80 changes: 80 additions & 0 deletions recipe/jwt/getJWKS_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,84 @@ func TestDefaultGetJWKSWorksFine(t *testing.T) {
result := *unittesting.HttpResponseToConsumableInformation(resp.Body)
assert.NotNil(t, result)
assert.Greater(t, len(result["keys"].([]interface{})), 0)

cacheControl := resp.Header.Get("Cache-Control")
assert.Equal(t, cacheControl, "max-age=60, must-revalidate")
}

func TestThatWeCanOverrideCacheControlThroughRecipeFunction(t *testing.T) {
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
APIDomain: "api.supertokens.io",
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&jwtmodels.TypeInput{
Override: &jwtmodels.OverrideStruct{
Functions: func(originalImplementation jwtmodels.RecipeInterface) jwtmodels.RecipeInterface {
originalGetJWKS := *originalImplementation.GetJWKS

getJWKs := func(userContext supertokens.UserContext) (jwtmodels.GetJWKSResponse, error) {
result, err := originalGetJWKS(userContext)

if err != nil {
return jwtmodels.GetJWKSResponse{}, err
}

return jwtmodels.GetJWKSResponse{
OK: &struct {
Keys []jwtmodels.JsonWebKeys
ValidityInSeconds int
}{Keys: result.OK.Keys, ValidityInSeconds: 1234},
}, nil
}

*originalImplementation.GetJWKS = getJWKs

return originalImplementation
},
},
}),
},
}

BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()
err := supertokens.Init(configValue)
if err != nil {
t.Error(err.Error())
}

q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
if err != nil {
t.Error(err.Error())
}
apiV, err := q.GetQuerierAPIVersion()
if err != nil {
t.Error(err.Error())
}

if unittesting.MaxVersion(apiV, "2.8") == "2.8" {
return
}
mux := http.NewServeMux()
testServer := httptest.NewServer(supertokens.Middleware(mux))
defer testServer.Close()

resp, err := http.Get(testServer.URL + "/auth/jwt/jwks.json")
if err != nil {
t.Error(err.Error())
}

result := *unittesting.HttpResponseToConsumableInformation(resp.Body)
assert.NotNil(t, result)
assert.Greater(t, len(result["keys"].([]interface{})), 0)

cacheControl := resp.Header.Get("Cache-Control")
assert.Equal(t, cacheControl, "max-age=1234, must-revalidate")
}
3 changes: 2 additions & 1 deletion recipe/jwt/jwtmodels/recipeInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type CreateJWTResponse struct {

type GetJWKSResponse struct {
OK *struct {
Keys []JsonWebKeys
Keys []JsonWebKeys
ValidityInSeconds int
}
}
30 changes: 27 additions & 3 deletions recipe/jwt/recipeimplementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ package jwt
import (
"github.com/supertokens/supertokens-golang/recipe/jwt/jwtmodels"
"github.com/supertokens/supertokens-golang/supertokens"
"regexp"
"strconv"
)

var defaultJWKSMaxAge = 60 // This corresponds to the dynamicSigningKeyOverlapMS in the core

func makeRecipeImplementation(querier supertokens.Querier, config jwtmodels.TypeNormalisedInput, appInfo supertokens.NormalisedAppinfo) jwtmodels.RecipeInterface {
createJWT := func(payload map[string]interface{}, validitySecondsPointer *uint64, useStaticSigningKey *bool, userContext supertokens.UserContext) (jwtmodels.CreateJWTResponse, error) {
validitySeconds := config.JwtValiditySeconds
Expand Down Expand Up @@ -61,7 +65,7 @@ func makeRecipeImplementation(querier supertokens.Querier, config jwtmodels.Type
}
}
getJWKS := func(userContext supertokens.UserContext) (jwtmodels.GetJWKSResponse, error) {
response, err := querier.SendGetRequest("/.well-known/jwks.json", map[string]string{})
response, headers, err := querier.SendGetRequestWithResponseHeaders("/.well-known/jwks.json", map[string]string{})
if err != nil {
return jwtmodels.GetJWKSResponse{}, err
}
Expand All @@ -79,9 +83,29 @@ func makeRecipeImplementation(querier supertokens.Querier, config jwtmodels.Type
})
}

validityInSeconds := defaultJWKSMaxAge
cacheControlHeader := headers.Get("Cache-Control")

if cacheControlHeader != "" {
regex := regexp.MustCompile(`/,?\s*max-age=(\d+)(?:,|$)/`)
maxAgeHeader := regex.FindAllString(cacheControlHeader, -1)

if maxAgeHeader != nil && len(maxAgeHeader) > 0 {
validityInSeconds, err = strconv.Atoi(maxAgeHeader[1])

if err != nil {
validityInSeconds = defaultJWKSMaxAge
}
}
}

return jwtmodels.GetJWKSResponse{
OK: &struct{ Keys []jwtmodels.JsonWebKeys }{
Keys: keys,
OK: &struct {
Keys []jwtmodels.JsonWebKeys
ValidityInSeconds int
}{
Keys: keys,
ValidityInSeconds: validityInSeconds,
},
}, nil
}
Expand Down
81 changes: 81 additions & 0 deletions recipe/session/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,84 @@ func TestThatJWKSAndOpenIdEndpointsAreExposed(t *testing.T) {
assert.NotNil(t, openIdAPI)
assert.Equal(t, openIdAPI.PathWithoutAPIBasePath.GetAsStringDangerous(), "/.well-known/openid-configuration")
}

func TestCookieSameSiteWithEC2PublicURL(t *testing.T) {
apiBasePath := "/"
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "https://ec2-xx-yyy-zzz-0.compute-1.amazonaws.com:3001",
WebsiteDomain: "https://blog.supertokens.com",
APIBasePath: &apiBasePath,
},
RecipeList: []supertokens.Recipe{
Init(&sessmodels.TypeInput{
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
return sessmodels.CookieTransferMethod
},
}),
},
}

BeforeEach()

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

defer AfterEach()

err := supertokens.Init(configValue)

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

recipe, err := getRecipeInstanceOrThrowError()

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

assert.True(t, recipe.Config.CookieDomain == nil)
assert.Equal(t, recipe.Config.CookieSameSite, "none")
assert.True(t, recipe.Config.CookieSecure)

resetAll()

configValue = supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
APIDomain: "http://ec2-xx-yyy-zzz-0.compute-1.amazonaws.com:3001",
WebsiteDomain: "http://ec2-xx-yyy-zzz-0.compute-1.amazonaws.com:3000",
APIBasePath: &apiBasePath,
},
RecipeList: []supertokens.Recipe{
Init(&sessmodels.TypeInput{
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
return sessmodels.CookieTransferMethod
},
}),
},
}

err = supertokens.Init(configValue)

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

recipe, err = getRecipeInstanceOrThrowError()

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

assert.True(t, recipe.Config.CookieDomain == nil)
assert.Equal(t, recipe.Config.CookieSameSite, "lax")
assert.False(t, recipe.Config.CookieSecure)
}
2 changes: 2 additions & 0 deletions recipe/thirdparty/providers/config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func createProvider(input tpmodels.ProviderInput) *tpmodels.TypeProvider {
return Linkedin(input)
} else if strings.HasPrefix(input.Config.ThirdPartyId, "boxy-saml") {
return BoxySaml(input)
} else if strings.HasPrefix(input.Config.ThirdPartyId, "twitter") {
return Twitter(input)
}

return NewProvider(input)
Expand Down
Loading

0 comments on commit 5e75605

Please sign in to comment.