Repository: supabase/gotrue
Pull request #615 (2022-08-19 08:57:32)

Use semantic releaser action
- reduce some boilerplate with semantic releaser action
- disallow workflow dispatch


Patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 30e3b9c2..f5c58e28 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,12 +9,6 @@ on:
       version:
         required: true
         type: string
-  workflow_dispatch:
-    inputs:
-      version:
-        description: "Image tag"
-        required: true
-        type: string
 
 jobs:
   publish:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 730b3bbe..484cd372 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,57 +8,36 @@ on:
 
 jobs:
   release:
-    strategy:
-      matrix:
-        node: ["16"]
-
     outputs:
-      status: ${{ steps.pre-release.outputs.release != steps.post-release.outputs.release }}
-      version: ${{ steps.post-release.outputs.release }}
+      status: ${{ steps.semantic.outputs.new_release_published }}
+      version: ${{ steps.semantic.outputs.new_release_version }}
 
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     steps:
-      - uses: actions/checkout@v2
-
-      - id: pre-release
-        uses: pozetroninc/github-action-get-latest-release@master
-        with:
-          owner: supabase
-          repo: gotrue
-          excludes: prerelease, draft
-
-      - name: Set up Node
-        uses: actions/setup-node@v2
-        with:
-          node-version: ${{ matrix.node }}
-
+      - uses: actions/checkout@v3
       - name: Release on GitHub
-        id: github-release
-        run: npx semantic-release -p        \
-          @semantic-release/commit-analyzer \
-          @semantic-release/github         \
-          @semantic-release/release-notes-generator
+        id: semantic
+        uses: cycjimmy/semantic-release-action@v2
+        with:
+          semantic_version: 18
+          extra_plugins: |
+            @semantic-release/commit-analyzer
+            @semantic-release/release-notes-generator
+            @semantic-release/github
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
-      - id: post-release
-        uses: pozetroninc/github-action-get-latest-release@master
-        with:
-          owner: supabase
-          repo: gotrue
-          excludes: prerelease, draft
-
   deploy:
     needs: release
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     if: success() && needs.release.outputs.status == 'true'
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
-      - uses: actions/setup-go@v2
+      - uses: actions/setup-go@v3
         with:
           go-version: "^1.18.0" # The Go version to download (if necessary) and use.
 
diff --git a/api/admin.go b/api/admin.go
index 7671b271..24369af0 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -102,7 +102,7 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
 	adminUser := getAdminUser(ctx)
 	instanceID := getInstanceID(ctx)
 	params, err := a.getAdminParams(r)
-	config := getConfig(ctx)
+	config := a.config
 	if err != nil {
 		return err
 	}
@@ -202,7 +202,7 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
 // adminUserCreate creates a new user based on the provided data
 func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	instanceID := getInstanceID(ctx)
 	adminUser := getAdminUser(ctx)
diff --git a/api/api.go b/api/api.go
index 3321ca14..d6b778ab 100644
--- a/api/api.go
+++ b/api/api.go
@@ -12,7 +12,6 @@ import (
 	"github.com/didip/tollbooth/v5"
 	"github.com/didip/tollbooth/v5/limiter"
 	"github.com/go-chi/chi"
-	"github.com/gofrs/uuid"
 	"github.com/netlify/gotrue/conf"
 	"github.com/netlify/gotrue/logger"
 	"github.com/netlify/gotrue/mailer"
@@ -77,19 +76,19 @@ func NewAPI(globalConfig *conf.GlobalConfiguration, db *storage.Connection) *API
 }
 
 func (a *API) deprecationNotices(ctx context.Context) {
-	instanceConfig := a.getConfig(ctx)
+	config := a.config
 
 	log := logrus.WithField("component", "api")
 
-	if instanceConfig.JWT.AdminGroupName != "" {
+	if config.JWT.AdminGroupName != "" {
 		log.Warn("DEPRECATION NOTICE: GOTRUE_JWT_ADMIN_GROUP_NAME not supported by Supabase's GoTrue, will be removed soon")
 	}
 
-	if len(instanceConfig.JWT.AdminRoles) > 0 {
+	if len(config.JWT.AdminRoles) > 0 {
 		log.Warn("DEPRECATION NOTICE: GOTRUE_JWT_ADMIN_ROLES not supported by Supabase's GoTrue, will be removed soon")
 	}
 
-	if instanceConfig.JWT.DefaultGroupName != "" {
+	if config.JWT.DefaultGroupName != "" {
 		log.Warn("DEPRECATION NOTICE: GOTRUE_JWT_DEFAULT_GROUP_NAME not supported by Supabase's GoTrue, will be removed soon")
 	}
 }
@@ -199,28 +198,18 @@ func NewAPIWithVersion(ctx context.Context, globalConfig *conf.GlobalConfigurati
 }
 
 // NewAPIFromConfigFile creates a new REST API using the provided configuration file.
-func NewAPIFromConfigFile(filename string, version string) (*API, *conf.Configuration, error) {
-	globalConfig, err := conf.LoadGlobal(filename)
+func NewAPIFromConfigFile(filename string, version string) (*API, *conf.GlobalConfiguration, error) {
+	config, err := conf.LoadGlobal(filename)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	config, err := conf.LoadConfig(filename)
+	db, err := storage.Dial(config)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	ctx, err := WithInstanceConfig(context.Background(), config, uuid.Nil)
-	if err != nil {
-		logrus.Fatalf("Error loading instance config: %+v", err)
-	}
-
-	db, err := storage.Dial(globalConfig)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	return NewAPIWithVersion(ctx, globalConfig, db, version), config, nil
+	return NewAPIWithVersion(context.Background(), config, db, version), config, nil
 }
 
 // HealthCheck endpoint indicates if the gotrue api service is available
@@ -232,26 +221,8 @@ func (a *API) HealthCheck(w http.ResponseWriter, r *http.Request) error {
 	})
 }
 
-// WithInstanceConfig adds the instanceID and tenant config to the context
-func WithInstanceConfig(ctx context.Context, config *conf.Configuration, instanceID uuid.UUID) (context.Context, error) {
-	ctx = withConfig(ctx, config)
-	ctx = withInstanceID(ctx, instanceID)
-	return ctx, nil
-}
-
 // Mailer returns NewMailer with the current tenant config
 func (a *API) Mailer(ctx context.Context) mailer.Mailer {
-	config := a.getConfig(ctx)
+	config := a.config
 	return mailer.NewMailer(config)
 }
-
-func (a *API) getConfig(ctx context.Context) *conf.Configuration {
-	obj := ctx.Value(configKey)
-	if obj == nil {
-		return nil
-	}
-
-	config := obj.(*conf.Configuration)
-
-	return config
-}
diff --git a/api/auth.go b/api/auth.go
index 35d82444..bab0e1c0 100644
--- a/api/auth.go
+++ b/api/auth.go
@@ -14,7 +14,7 @@ import (
 // requireAuthentication checks incoming requests for tokens presented using the Authorization header
 func (a *API) requireAuthentication(w http.ResponseWriter, r *http.Request) (context.Context, error) {
 	token, err := a.extractBearerToken(w, r)
-	config := getConfig(r.Context())
+	config := a.config
 	if err != nil {
 		a.clearCookieTokens(config, w)
 		return nil, err
@@ -31,7 +31,7 @@ func (a *API) requireAdmin(ctx context.Context, w http.ResponseWriter, r *http.R
 		return nil, unauthorizedError("Invalid token")
 	}
 
-	adminRoles := a.getConfig(ctx).JWT.AdminRoles
+	adminRoles := a.config.JWT.AdminRoles
 
 	if isStringInSlice(claims.Role, adminRoles) {
 		// successful authentication
@@ -58,7 +58,7 @@ func (a *API) extractBearerToken(w http.ResponseWriter, r *http.Request) (string
 
 func (a *API) parseJWTClaims(bearer string, r *http.Request, w http.ResponseWriter) (context.Context, error) {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
 	token, err := p.ParseWithClaims(bearer, &GoTrueClaims{}, func(token *jwt.Token) (interface{}, error) {
diff --git a/api/context.go b/api/context.go
index 37e98019..3991070e 100644
--- a/api/context.go
+++ b/api/context.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/gofrs/uuid"
 	jwt "github.com/golang-jwt/jwt"
-	"github.com/netlify/gotrue/conf"
 	"github.com/netlify/gotrue/models"
 )
 
@@ -18,10 +17,7 @@ func (c contextKey) String() string {
 const (
 	tokenKey                = contextKey("jwt")
 	requestIDKey            = contextKey("request_id")
-	configKey               = contextKey("config")
 	inviteTokenKey          = contextKey("invite_token")
-	instanceIDKey           = contextKey("instance_id")
-	instanceKey             = contextKey("instance")
 	signatureKey            = contextKey("signature")
 	netlifyIDKey            = contextKey("netlify_id")
 	externalProviderTypeKey = contextKey("external_provider_type")
@@ -71,31 +67,10 @@ func getRequestID(ctx context.Context) string {
 	return obj.(string)
 }
 
-// withConfig adds the tenant configuration to the context.
-func withConfig(ctx context.Context, config *conf.Configuration) context.Context {
-	return context.WithValue(ctx, configKey, config)
-}
-
-func getConfig(ctx context.Context) *conf.Configuration {
-	obj := ctx.Value(configKey)
-	if obj == nil {
-		return nil
-	}
-	return obj.(*conf.Configuration)
-}
-
-// withInstanceID adds the instance id to the context.
-func withInstanceID(ctx context.Context, id uuid.UUID) context.Context {
-	return context.WithValue(ctx, instanceIDKey, id)
-}
-
 // getInstanceID reads the instance id from the context.
 func getInstanceID(ctx context.Context) uuid.UUID {
-	obj := ctx.Value(instanceIDKey)
-	if obj == nil {
-		return uuid.Nil
-	}
-	return obj.(uuid.UUID)
+	// TODO: remove this method, it's deprecated
+	return uuid.Nil
 }
 
 // withUser adds the user id to the context.
diff --git a/api/errors.go b/api/errors.go
index a75e4b08..e3e619c9 100644
--- a/api/errors.go
+++ b/api/errors.go
@@ -63,11 +63,11 @@ func (e *OAuthError) Cause() error {
 	return e
 }
 
-func invalidPasswordLengthError(config *conf.Configuration) *HTTPError {
+func invalidPasswordLengthError(config *conf.GlobalConfiguration) *HTTPError {
 	return unprocessableEntityError(fmt.Sprintf("Password should be at least %d characters", config.PasswordMinLength))
 }
 
-func invalidSignupError(config *conf.Configuration) *HTTPError {
+func invalidSignupError(config *conf.GlobalConfiguration) *HTTPError {
 	var msg string
 	if config.External.Email.Enabled && config.External.Phone.Enabled {
 		msg = "To signup, please provide your email or phone number"
diff --git a/api/external.go b/api/external.go
index 4fe12ad4..c9890596 100644
--- a/api/external.go
+++ b/api/external.go
@@ -38,7 +38,7 @@ type ExternalSignupParams struct {
 // ExternalProviderRedirect redirects the request to the corresponding oauth provider
 func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	query := r.URL.Query()
 	providerType := query.Get("provider")
@@ -106,7 +106,7 @@ func (a *API) ExternalProviderCallback(w http.ResponseWriter, r *http.Request) e
 
 func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 
 	providerType := getExternalProviderType(ctx)
@@ -307,7 +307,7 @@ func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Re
 }
 
 func (a *API) processInvite(r *http.Request, ctx context.Context, tx *storage.Connection, userData *provider.UserProvidedData, instanceID uuid.UUID, inviteToken, providerType string) (*models.User, error) {
-	config := a.getConfig(ctx)
+	config := a.config
 	user, err := models.FindUserByConfirmationToken(tx, inviteToken)
 	if err != nil {
 		if models.IsNotFoundError(err) {
@@ -369,7 +369,7 @@ func (a *API) processInvite(r *http.Request, ctx context.Context, tx *storage.Co
 }
 
 func (a *API) loadExternalState(ctx context.Context, state string) (context.Context, error) {
-	config := a.getConfig(ctx)
+	config := a.config
 	claims := ExternalProviderClaims{}
 	p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
 	_, err := p.ParseWithClaims(state, &claims, func(token *jwt.Token) (interface{}, error) {
@@ -391,7 +391,7 @@ func (a *API) loadExternalState(ctx context.Context, state string) (context.Cont
 
 // Provider returns a Provider interface for the given name.
 func (a *API) Provider(ctx context.Context, name string, scopes string, query *url.Values) (provider.Provider, error) {
-	config := a.getConfig(ctx)
+	config := a.config
 	name = strings.ToLower(name)
 
 	switch name {
@@ -487,7 +487,7 @@ func getErrorQueryString(err error, errorID string, log logrus.FieldLogger) *url
 
 func (a *API) getExternalRedirectURL(r *http.Request) string {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	if config.External.RedirectURL != "" {
 		return config.External.RedirectURL
 	}
@@ -518,7 +518,7 @@ func (a *API) createNewIdentity(conn *storage.Connection, user *models.User, pro
 }
 
 // getUserByVerifiedEmail checks if one of the verified emails already belongs to a user
-func (a *API) getUserByVerifiedEmail(tx *storage.Connection, config *conf.Configuration, emails []provider.Email, instanceID uuid.UUID, aud string) (*models.User, provider.Email, error) {
+func (a *API) getUserByVerifiedEmail(tx *storage.Connection, config *conf.GlobalConfiguration, emails []provider.Email, instanceID uuid.UUID, aud string) (*models.User, provider.Email, error) {
 	var user *models.User
 	var emailData provider.Email
 	var err error
diff --git a/api/helpers.go b/api/helpers.go
index 4c510e56..34ff4ef0 100644
--- a/api/helpers.go
+++ b/api/helpers.go
@@ -70,7 +70,7 @@ func getUserFromClaims(ctx context.Context, conn *storage.Connection) (*models.U
 }
 
 func (a *API) isAdmin(ctx context.Context, u *models.User, aud string) bool {
-	config := a.getConfig(ctx)
+	config := a.config
 	if aud == "" {
 		aud = config.JWT.Aud
 	}
@@ -78,7 +78,7 @@ func (a *API) isAdmin(ctx context.Context, u *models.User, aud string) bool {
 }
 
 func (a *API) requestAud(ctx context.Context, r *http.Request) string {
-	config := a.getConfig(ctx)
+	config := a.config
 	// First check for an audience in the header
 	if aud := r.Header.Get(audHeaderName); aud != "" {
 		return aud
@@ -108,7 +108,7 @@ func getRedirectTo(r *http.Request) (reqref string) {
 	return
 }
 
-func isRedirectURLValid(config *conf.Configuration, redirectURL string) bool {
+func isRedirectURLValid(config *conf.GlobalConfiguration, redirectURL string) bool {
 	if redirectURL == "" {
 		return false
 	}
@@ -137,8 +137,7 @@ func isRedirectURLValid(config *conf.Configuration, redirectURL string) bool {
 }
 
 func (a *API) getReferrer(r *http.Request) string {
-	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	// try get redirect url from query or post data first
 	reqref := getRedirectTo(r)
@@ -157,8 +156,7 @@ func (a *API) getReferrer(r *http.Request) string {
 
 // getRedirectURLOrReferrer ensures any redirect URL is from a safe origin
 func (a *API) getRedirectURLOrReferrer(r *http.Request, reqref string) string {
-	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	// if redirect url fails - try fill by extra variant
 	if isRedirectURLValid(config, reqref) {
diff --git a/api/hooks.go b/api/hooks.go
index 28f8ccc2..4f7903e4 100644
--- a/api/hooks.go
+++ b/api/hooks.go
@@ -153,7 +153,7 @@ func closeBody(rsp *http.Response) {
 	}
 }
 
-func triggerEventHooks(ctx context.Context, conn *storage.Connection, event HookEvent, user *models.User, instanceID uuid.UUID, config *conf.Configuration) error {
+func triggerEventHooks(ctx context.Context, conn *storage.Connection, event HookEvent, user *models.User, instanceID uuid.UUID, config *conf.GlobalConfiguration) error {
 	if config.Webhook.URL != "" {
 		hookURL, err := url.Parse(config.Webhook.URL)
 		if err != nil {
@@ -183,7 +183,7 @@ func triggerEventHooks(ctx context.Context, conn *storage.Connection, event Hook
 	return nil
 }
 
-func triggerHook(ctx context.Context, hookURL *url.URL, secret string, conn *storage.Connection, event HookEvent, user *models.User, instanceID uuid.UUID, config *conf.Configuration) error {
+func triggerHook(ctx context.Context, hookURL *url.URL, secret string, conn *storage.Connection, event HookEvent, user *models.User, instanceID uuid.UUID, config *conf.GlobalConfiguration) error {
 	if !hookURL.IsAbs() {
 		siteURL, err := url.Parse(config.SiteURL)
 		if err != nil {
diff --git a/api/invite.go b/api/invite.go
index 494f7b38..5446fa57 100644
--- a/api/invite.go
+++ b/api/invite.go
@@ -17,7 +17,7 @@ type InviteParams struct {
 // Invite is the endpoint for inviting a new user
 func (a *API) Invite(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 	adminUser := getAdminUser(ctx)
 	params := &InviteParams{}
diff --git a/api/logout.go b/api/logout.go
index f6465d3c..80fa65c9 100644
--- a/api/logout.go
+++ b/api/logout.go
@@ -11,7 +11,7 @@ import (
 func (a *API) Logout(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
 	instanceID := getInstanceID(ctx)
-	config := getConfig(ctx)
+	config := a.config
 
 	a.clearCookieTokens(config, w)
 
diff --git a/api/magic_link.go b/api/magic_link.go
index 60e95e37..51bd9e0f 100644
--- a/api/magic_link.go
+++ b/api/magic_link.go
@@ -22,7 +22,7 @@ type MagicLinkParams struct {
 // MagicLink sends a recovery email
 func (a *API) MagicLink(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if !config.External.Email.Enabled {
 		return badRequestError("Email logins are disabled")
diff --git a/api/mail.go b/api/mail.go
index 02725332..4d8dc453 100644
--- a/api/mail.go
+++ b/api/mail.go
@@ -33,7 +33,7 @@ type GenerateLinkParams struct {
 
 func (a *API) GenerateLink(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	mailer := a.Mailer(ctx)
 	instanceID := getInstanceID(ctx)
 	adminUser := getAdminUser(ctx)
@@ -306,7 +306,7 @@ func (a *API) sendMagicLink(tx *storage.Connection, u *models.User, mailer maile
 }
 
 // sendEmailChange sends out an email change token to the new email.
-func (a *API) sendEmailChange(tx *storage.Connection, config *conf.Configuration, u *models.User, mailer mailer.Mailer, email string, referrerURL string, otpLength int) error {
+func (a *API) sendEmailChange(tx *storage.Connection, config *conf.GlobalConfiguration, u *models.User, mailer mailer.Mailer, email string, referrerURL string, otpLength int) error {
 	var err error
 	otpNew, err := crypto.GenerateOtp(otpLength)
 	if err != nil {
diff --git a/api/middleware.go b/api/middleware.go
index e3d9ce0c..173d8f69 100644
--- a/api/middleware.go
+++ b/api/middleware.go
@@ -105,7 +105,7 @@ func (a *API) limitEmailSentHandler() middlewareHandler {
 	}).SetBurst(int(a.config.RateLimitEmailSent)).SetMethods([]string{"PUT", "POST"})
 	return func(w http.ResponseWriter, req *http.Request) (context.Context, error) {
 		c := req.Context()
-		config := a.getConfig(c)
+		config := a.config
 		if config.External.Email.Enabled && !config.Mailer.Autoconfirm {
 			if req.Method == "PUT" || req.Method == "POST" {
 				res := make(map[string]interface{})
@@ -152,7 +152,7 @@ func (a *API) requireAdminCredentials(w http.ResponseWriter, req *http.Request)
 
 func (a *API) requireEmailProvider(w http.ResponseWriter, req *http.Request) (context.Context, error) {
 	ctx := req.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if !config.External.Email.Enabled {
 		return nil, badRequestError("Email logins are disabled")
@@ -163,7 +163,7 @@ func (a *API) requireEmailProvider(w http.ResponseWriter, req *http.Request) (co
 
 func (a *API) verifyCaptcha(w http.ResponseWriter, req *http.Request) (context.Context, error) {
 	ctx := req.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	if !config.Security.Captcha.Enabled {
 		return ctx, nil
 	}
diff --git a/api/otp.go b/api/otp.go
index 8a204314..640b98d3 100644
--- a/api/otp.go
+++ b/api/otp.go
@@ -68,7 +68,7 @@ func (a *API) Otp(w http.ResponseWriter, r *http.Request) error {
 // SmsOtp sends the user an otp via sms
 func (a *API) SmsOtp(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if !config.External.Phone.Enabled {
 		return badRequestError("Unsupported phone provider")
diff --git a/api/phone.go b/api/phone.go
index e72e10bf..f776dd6d 100644
--- a/api/phone.go
+++ b/api/phone.go
@@ -45,7 +45,7 @@ func (a *API) formatPhoneNumber(phone string) string {
 
 // sendPhoneConfirmation sends an otp to the user's phone number
 func (a *API) sendPhoneConfirmation(ctx context.Context, tx *storage.Connection, user *models.User, phone, otpType string, smsProvider sms_provider.SmsProvider) error {
-	config := a.getConfig(ctx)
+	config := a.config
 
 	var token *string
 	var sentAt *time.Time
diff --git a/api/reauthenticate.go b/api/reauthenticate.go
index 85853149..e0293793 100644
--- a/api/reauthenticate.go
+++ b/api/reauthenticate.go
@@ -18,7 +18,7 @@ const InvalidNonceMessage = "Nonce has expired or is invalid"
 // Reauthenticate sends a reauthentication otp to either the user's email or phone
 func (a *API) Reauthenticate(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 
 	claims := getClaims(ctx)
@@ -77,7 +77,7 @@ func (a *API) Reauthenticate(w http.ResponseWriter, r *http.Request) error {
 }
 
 // verifyReauthentication checks if the nonce provided is valid
-func (a *API) verifyReauthentication(nonce string, tx *storage.Connection, config *conf.Configuration, user *models.User) error {
+func (a *API) verifyReauthentication(nonce string, tx *storage.Connection, config *conf.GlobalConfiguration, user *models.User) error {
 	if user.ReauthenticationToken == "" || user.ReauthenticationSentAt == nil {
 		return badRequestError(InvalidNonceMessage)
 	}
diff --git a/api/recover.go b/api/recover.go
index 1292940b..54e1001e 100644
--- a/api/recover.go
+++ b/api/recover.go
@@ -17,7 +17,7 @@ type RecoverParams struct {
 // Recover sends a recovery email
 func (a *API) Recover(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 	params := &RecoverParams{}
 	jsonDecoder := json.NewDecoder(r.Body)
diff --git a/api/settings.go b/api/settings.go
index 872b4aa4..4ba862cc 100644
--- a/api/settings.go
+++ b/api/settings.go
@@ -34,7 +34,7 @@ type Settings struct {
 }
 
 func (a *API) Settings(w http.ResponseWriter, r *http.Request) error {
-	config := a.getConfig(r.Context())
+	config := a.config
 
 	return sendJSON(w, http.StatusOK, &Settings{
 		ExternalProviders: ProviderSettings{
diff --git a/api/signup.go b/api/signup.go
index a57cde1e..8d10afe8 100644
--- a/api/signup.go
+++ b/api/signup.go
@@ -28,7 +28,7 @@ type SignupParams struct {
 // Signup is the endpoint for registering a new user
 func (a *API) Signup(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if config.DisableSignup {
 		return forbiddenError("Signups not allowed for this instance")
@@ -272,7 +272,7 @@ func sanitizeUser(u *models.User, params *SignupParams) (*models.User, error) {
 
 func (a *API) signupNewUser(ctx context.Context, conn *storage.Connection, params *SignupParams) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	var user *models.User
 	var err error
diff --git a/api/sms_provider/sms_provider.go b/api/sms_provider/sms_provider.go
index 7d535133..81cc02e2 100644
--- a/api/sms_provider/sms_provider.go
+++ b/api/sms_provider/sms_provider.go
@@ -26,7 +26,7 @@ type SmsProvider interface {
 	SendSms(phone, message string) error
 }
 
-func GetSmsProvider(config conf.Configuration) (SmsProvider, error) {
+func GetSmsProvider(config conf.GlobalConfiguration) (SmsProvider, error) {
 	switch name := config.Sms.Provider; name {
 	case "twilio":
 		return NewTwilioProvider(config.Sms.Twilio)
diff --git a/api/token.go b/api/token.go
index d6d72d1e..b6478e31 100644
--- a/api/token.go
+++ b/api/token.go
@@ -72,9 +72,7 @@ const useCookieHeader = "x-use-cookie"
 const useSessionCookie = "session"
 const InvalidLoginMessage = "Invalid login credentials"
 
-func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
-	config := getConfig(ctx)
-
+func (p *IdTokenGrantParams) getVerifier(ctx context.Context, config *conf.GlobalConfiguration) (*oidc.IDTokenVerifier, error) {
 	var provider *oidc.Provider
 	var err error
 	var oAuthProvider conf.OAuthProviderConfiguration
@@ -174,7 +172,7 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
 
 	aud := a.requestAud(ctx, r)
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if params.Email != "" && params.Phone != "" {
 		return unprocessableEntityError("Only an email address or phone number should be provided on login.")
@@ -247,7 +245,7 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
 
 // RefreshTokenGrant implements the refresh_token grant type flow
 func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 
 	params := &RefreshTokenGrantParams{}
@@ -359,7 +357,7 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
 
 // IdTokenGrant implements the id_token grant type flow
 func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 
 	params := &IdTokenGrantParams{}
@@ -380,7 +378,7 @@ func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.R
 	var verifier *oidc.IDTokenVerifier
 	var err error
 	if params.Provider != "" {
-		verifier, err = params.getVerifier(ctx)
+		verifier, err = params.getVerifier(ctx, a.config)
 	} else if params.ClientID != "" && params.Issuer != "" {
 		verifier, err = params.getVerifierFromClientIDandIssuer(ctx)
 	} else {
@@ -550,7 +548,7 @@ func generateAccessToken(user *models.User, expiresIn time.Duration, secret stri
 }
 
 func (a *API) issueRefreshToken(ctx context.Context, conn *storage.Connection, user *models.User) (*AccessTokenResponse, error) {
-	config := a.getConfig(ctx)
+	config := a.config
 
 	now := time.Now()
 	user.LastSignInAt = &now
@@ -585,14 +583,14 @@ func (a *API) issueRefreshToken(ctx context.Context, conn *storage.Connection, u
 }
 
 // setCookieTokens sets the access_token & refresh_token in the cookies
-func (a *API) setCookieTokens(config *conf.Configuration, token *AccessTokenResponse, session bool, w http.ResponseWriter) error {
+func (a *API) setCookieTokens(config *conf.GlobalConfiguration, token *AccessTokenResponse, session bool, w http.ResponseWriter) error {
 	// don't need to catch error here since we always set the cookie name
 	_ = a.setCookieToken(config, "access-token", token.Token, session, w)
 	_ = a.setCookieToken(config, "refresh-token", token.RefreshToken, session, w)
 	return nil
 }
 
-func (a *API) setCookieToken(config *conf.Configuration, name string, tokenString string, session bool, w http.ResponseWriter) error {
+func (a *API) setCookieToken(config *conf.GlobalConfiguration, name string, tokenString string, session bool, w http.ResponseWriter) error {
 	if name == "" {
 		return errors.New("Failed to set cookie, invalid name")
 	}
@@ -615,12 +613,12 @@ func (a *API) setCookieToken(config *conf.Configuration, name string, tokenStrin
 	return nil
 }
 
-func (a *API) clearCookieTokens(config *conf.Configuration, w http.ResponseWriter) {
+func (a *API) clearCookieTokens(config *conf.GlobalConfiguration, w http.ResponseWriter) {
 	a.clearCookieToken(config, "access-token", w)
 	a.clearCookieToken(config, "refresh-token", w)
 }
 
-func (a *API) clearCookieToken(config *conf.Configuration, name string, w http.ResponseWriter) {
+func (a *API) clearCookieToken(config *conf.GlobalConfiguration, name string, w http.ResponseWriter) {
 	cookieName := config.Cookie.Key
 	if name != "" {
 		cookieName += "-" + name
diff --git a/api/user.go b/api/user.go
index 445153dd..4a795df5 100644
--- a/api/user.go
+++ b/api/user.go
@@ -53,7 +53,7 @@ func (a *API) UserGet(w http.ResponseWriter, r *http.Request) error {
 // UserUpdate updates fields on a user
 func (a *API) UserUpdate(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	instanceID := getInstanceID(ctx)
 
 	params := &UserUpdateParams{}
diff --git a/api/verify.go b/api/verify.go
index 1900ebbb..a6b5088f 100644
--- a/api/verify.go
+++ b/api/verify.go
@@ -70,7 +70,7 @@ func (a *API) Verify(w http.ResponseWriter, r *http.Request) error {
 
 func (a *API) verifyGet(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	params := &VerifyParams{}
 	params.Token = r.FormValue("token")
 	params.Type = r.FormValue("type")
@@ -157,7 +157,7 @@ func (a *API) verifyGet(w http.ResponseWriter, r *http.Request) error {
 
 func (a *API) verifyPost(w http.ResponseWriter, r *http.Request) error {
 	ctx := r.Context()
-	config := a.getConfig(ctx)
+	config := a.config
 	params := &VerifyParams{}
 	jsonDecoder := json.NewDecoder(r.Body)
 	if err := jsonDecoder.Decode(params); err != nil {
@@ -232,7 +232,7 @@ func (a *API) verifyPost(w http.ResponseWriter, r *http.Request) error {
 
 func (a *API) signupVerify(r *http.Request, ctx context.Context, conn *storage.Connection, user *models.User) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	err := conn.Transaction(func(tx *storage.Connection) error {
 		var terr error
@@ -271,7 +271,7 @@ func (a *API) signupVerify(r *http.Request, ctx context.Context, conn *storage.C
 
 func (a *API) recoverVerify(r *http.Request, ctx context.Context, conn *storage.Connection, user *models.User) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	err := conn.Transaction(func(tx *storage.Connection) error {
 		var terr error
@@ -308,7 +308,7 @@ func (a *API) recoverVerify(r *http.Request, ctx context.Context, conn *storage.
 
 func (a *API) smsVerify(r *http.Request, ctx context.Context, conn *storage.Connection, user *models.User, otpType string) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	err := conn.Transaction(func(tx *storage.Connection) error {
 		var terr error
@@ -359,7 +359,7 @@ func (a *API) prepRedirectURL(message string, rurl string) string {
 
 func (a *API) emailChangeVerify(r *http.Request, ctx context.Context, conn *storage.Connection, params *VerifyParams, user *models.User) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := a.getConfig(ctx)
+	config := a.config
 
 	if config.Mailer.SecureEmailChangeEnabled && user.EmailChangeConfirmStatus == zeroConfirmation && user.GetEmail() != "" {
 		err := conn.Transaction(func(tx *storage.Connection) error {
@@ -406,7 +406,7 @@ func (a *API) emailChangeVerify(r *http.Request, ctx context.Context, conn *stor
 }
 
 func (a *API) verifyEmailLink(ctx context.Context, conn *storage.Connection, params *VerifyParams, aud string) (*models.User, error) {
-	config := getConfig(ctx)
+	config := a.config
 
 	var user *models.User
 	var err error
@@ -452,7 +452,7 @@ func (a *API) verifyEmailLink(ctx context.Context, conn *storage.Connection, par
 // verifyUserAndToken verifies the token associated to the user based on the verify type
 func (a *API) verifyUserAndToken(ctx context.Context, conn *storage.Connection, params *VerifyParams, aud string) (*models.User, error) {
 	instanceID := getInstanceID(ctx)
-	config := getConfig(ctx)
+	config := a.config
 
 	var user *models.User
 	var err error
diff --git a/cmd/admin_cmd.go b/cmd/admin_cmd.go
index d14d22cc..2256d4ed 100644
--- a/cmd/admin_cmd.go
+++ b/cmd/admin_cmd.go
@@ -12,7 +12,7 @@ import (
 var autoconfirm, isSuperAdmin, isAdmin bool
 var audience, instanceID string
 
-func getAudience(c *conf.Configuration) string {
+func getAudience(c *conf.GlobalConfiguration) string {
 	if audience == "" {
 		return c.JWT.Aud
 	}
@@ -67,10 +67,10 @@ var adminEditRoleCmd = cobra.Command{
 	},
 }
 
-func adminCreateUser(globalConfig *conf.GlobalConfiguration, config *conf.Configuration, args []string) {
+func adminCreateUser(config *conf.GlobalConfiguration, args []string) {
 	iid := uuid.Must(uuid.FromString(instanceID))
 
-	db, err := storage.Dial(globalConfig)
+	db, err := storage.Dial(config)
 	if err != nil {
 		logrus.Fatalf("Error opening database: %+v", err)
 	}
@@ -119,10 +119,10 @@ func adminCreateUser(globalConfig *conf.GlobalConfiguration, config *conf.Config
 	logrus.Infof("Created user: %s", args[0])
 }
 
-func adminDeleteUser(globalConfig *conf.GlobalConfiguration, config *conf.Configuration, args []string) {
+func adminDeleteUser(config *conf.GlobalConfiguration, args []string) {
 	iid := uuid.Must(uuid.FromString(instanceID))
 
-	db, err := storage.Dial(globalConfig)
+	db, err := storage.Dial(config)
 	if err != nil {
 		logrus.Fatalf("Error opening database: %+v", err)
 	}
@@ -144,10 +144,10 @@ func adminDeleteUser(globalConfig *conf.GlobalConfiguration, config *conf.Config
 	logrus.Infof("Removed user: %s", args[0])
 }
 
-func adminEditRole(globalConfig *conf.GlobalConfiguration, config *conf.Configuration, args []string) {
+func adminEditRole(config *conf.GlobalConfiguration, args []string) {
 	iid := uuid.Must(uuid.FromString(instanceID))
 
-	db, err := storage.Dial(globalConfig)
+	db, err := storage.Dial(config)
 	if err != nil {
 		logrus.Fatalf("Error opening database: %+v", err)
 	}
diff --git a/cmd/root_cmd.go b/cmd/root_cmd.go
index f102c066..3bdb43ff 100644
--- a/cmd/root_cmd.go
+++ b/cmd/root_cmd.go
@@ -25,28 +25,20 @@ func RootCommand() *cobra.Command {
 	return &rootCmd
 }
 
-func execWithConfig(cmd *cobra.Command, fn func(globalConfig *conf.GlobalConfiguration, config *conf.Configuration)) {
-	globalConfig, err := conf.LoadGlobal(configFile)
-	if err != nil {
-		logrus.Fatalf("Failed to load configuration: %+v", err)
-	}
-	config, err := conf.LoadConfig(configFile)
+func execWithConfig(cmd *cobra.Command, fn func(config *conf.GlobalConfiguration)) {
+	config, err := conf.LoadGlobal(configFile)
 	if err != nil {
 		logrus.Fatalf("Failed to load configuration: %+v", err)
 	}
 
-	fn(globalConfig, config)
+	fn(config)
 }
 
-func execWithConfigAndArgs(cmd *cobra.Command, fn func(globalConfig *conf.GlobalConfiguration, config *conf.Configuration, args []string), args []string) {
-	globalConfig, err := conf.LoadGlobal(configFile)
-	if err != nil {
-		logrus.Fatalf("Failed to load configuration: %+v", err)
-	}
-	config, err := conf.LoadConfig(configFile)
+func execWithConfigAndArgs(cmd *cobra.Command, fn func(config *conf.GlobalConfiguration, args []string), args []string) {
+	config, err := conf.LoadGlobal(configFile)
 	if err != nil {
 		logrus.Fatalf("Failed to load configuration: %+v", err)
 	}
 
-	fn(globalConfig, config, args)
+	fn(config, args)
 }
diff --git a/cmd/serve_cmd.go b/cmd/serve_cmd.go
index c76f8909..dc8a3431 100644
--- a/cmd/serve_cmd.go
+++ b/cmd/serve_cmd.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/gofrs/uuid"
 	"github.com/netlify/gotrue/api"
 	"github.com/netlify/gotrue/conf"
 	"github.com/netlify/gotrue/storage"
@@ -20,20 +19,16 @@ var serveCmd = cobra.Command{
 	},
 }
 
-func serve(globalConfig *conf.GlobalConfiguration, config *conf.Configuration) {
-	db, err := storage.Dial(globalConfig)
+func serve(config *conf.GlobalConfiguration) {
+	db, err := storage.Dial(config)
 	if err != nil {
 		logrus.Fatalf("Error opening database: %+v", err)
 	}
 	defer db.Close()
 
-	ctx, err := api.WithInstanceConfig(context.Background(), config, uuid.Nil)
-	if err != nil {
-		logrus.Fatalf("Error loading instance config: %+v", err)
-	}
-	api := api.NewAPIWithVersion(ctx, globalConfig, db, Version)
+	api := api.NewAPIWithVersion(context.Background(), config, db, Version)
 
-	l := fmt.Sprintf("%v:%v", globalConfig.API.Host, globalConfig.API.Port)
+	l := fmt.Sprintf("%v:%v", config.API.Host, config.API.Port)
 	logrus.Infof("GoTrue API started on: %s", l)
 	api.ListenAndServe(l)
 }
diff --git a/conf/configuration.go b/conf/configuration.go
index a3c15066..38e2ae7a 100644
--- a/conf/configuration.go
+++ b/conf/configuration.go
@@ -1,8 +1,6 @@
 package conf
 
 import (
-	"database/sql/driver"
-	"encoding/json"
 	"errors"
 	"os"
 	"time"
@@ -67,6 +65,22 @@ type GlobalConfiguration struct {
 	RateLimitEmailSent    float64 `split_words:"true" default:"30"`
 	RateLimitVerify       float64 `split_words:"true" default:"30"`
 	RateLimitTokenRefresh float64 `split_words:"true" default:"30"`
+
+	SiteURL           string   `json:"site_url" split_words:"true" required:"true"`
+	URIAllowList      []string `json:"uri_allow_list" split_words:"true"`
+	URIAllowListMap   map[string]glob.Glob
+	PasswordMinLength int                      `json:"password_min_length" split_words:"true"`
+	JWT               JWTConfiguration         `json:"jwt"`
+	Mailer            MailerConfiguration      `json:"mailer"`
+	Sms               SmsProviderConfiguration `json:"sms"`
+	DisableSignup     bool                     `json:"disable_signup" split_words:"true"`
+	Webhook           WebhookConfig            `json:"webhook" split_words:"true"`
+	Security          SecurityConfiguration    `json:"security"`
+	Cookie            struct {
+		Key      string `json:"key"`
+		Domain   string `json:"domain"`
+		Duration int    `json:"duration"`
+	} `json:"cookies"`
 }
 
 // EmailContentConfiguration holds the configuration for emails, both subjects and template URLs.
@@ -175,27 +189,6 @@ type SecurityConfiguration struct {
 	UpdatePasswordRequireReauthentication bool                 `json:"update_password_require_reauthentication" split_words:"true"`
 }
 
-// Configuration holds all the per-instance configuration.
-type Configuration struct {
-	SiteURL           string   `json:"site_url" split_words:"true" required:"true"`
-	URIAllowList      []string `json:"uri_allow_list" split_words:"true"`
-	URIAllowListMap   map[string]glob.Glob
-	PasswordMinLength int                      `json:"password_min_length" split_words:"true"`
-	JWT               JWTConfiguration         `json:"jwt"`
-	SMTP              SMTPConfiguration        `json:"smtp"`
-	Mailer            MailerConfiguration      `json:"mailer"`
-	External          ProviderConfiguration    `json:"external"`
-	Sms               SmsProviderConfiguration `json:"sms"`
-	DisableSignup     bool                     `json:"disable_signup" split_words:"true"`
-	Webhook           WebhookConfig            `json:"webhook" split_words:"true"`
-	Security          SecurityConfiguration    `json:"security"`
-	Cookie            struct {
-		Key      string `json:"key"`
-		Domain   string `json:"domain"`
-		Duration int    `json:"duration"`
-	} `json:"cookies"`
-}
-
 func loadEnvironment(filename string) error {
 	var err error
 	if filename != "" {
@@ -238,6 +231,8 @@ func LoadGlobal(filename string) (*GlobalConfiguration, error) {
 		return nil, err
 	}
 
+	config.ApplyDefaults()
+
 	if _, err := ConfigureLogging(&config.Logging); err != nil {
 		return nil, err
 	}
@@ -250,22 +245,8 @@ func LoadGlobal(filename string) (*GlobalConfiguration, error) {
 	return config, nil
 }
 
-// LoadConfig loads per-instance configuration.
-func LoadConfig(filename string) (*Configuration, error) {
-	if err := loadEnvironment(filename); err != nil {
-		return nil, err
-	}
-
-	config := new(Configuration)
-	if err := envconfig.Process("gotrue", config); err != nil {
-		return nil, err
-	}
-	config.ApplyDefaults()
-	return config, nil
-}
-
-// ApplyDefaults sets defaults for a Configuration
-func (config *Configuration) ApplyDefaults() {
+// ApplyDefaults sets defaults for a GlobalConfiguration
+func (config *GlobalConfiguration) ApplyDefaults() {
 	if config.JWT.AdminGroupName == "" {
 		config.JWT.AdminGroupName = "admin"
 	}
@@ -351,31 +332,6 @@ func (config *Configuration) ApplyDefaults() {
 	}
 }
 
-func (config *Configuration) Value() (driver.Value, error) {
-	data, err := json.Marshal(config)
-	if err != nil {
-		return driver.Value(""), err
-	}
-	return driver.Value(string(data)), nil
-}
-
-func (config *Configuration) Scan(src interface{}) error {
-	var source []byte
-	switch v := src.(type) {
-	case string:
-		source = []byte(v)
-	case []byte:
-		source = v
-	default:
-		return errors.New("Invalid data type for Configuration")
-	}
-
-	if len(source) == 0 {
-		source = []byte("{}")
-	}
-	return json.Unmarshal(source, &config)
-}
-
 func (o *OAuthProviderConfiguration) Validate() error {
 	if !o.Enabled {
 		return errors.New("Provider is not enabled")
diff --git a/mailer/mailer.go b/mailer/mailer.go
index 9db10bb3..0d63b9da 100644
--- a/mailer/mailer.go
+++ b/mailer/mailer.go
@@ -25,7 +25,7 @@ type Mailer interface {
 }
 
 // NewMailer returns a new gotrue mailer
-func NewMailer(instanceConfig *conf.Configuration) Mailer {
+func NewMailer(instanceConfig *conf.GlobalConfiguration) Mailer {
 	mail := gomail.NewMessage()
 	from := mail.FormatAddress(instanceConfig.SMTP.AdminEmail, instanceConfig.SMTP.SenderName)
 
diff --git a/mailer/template.go b/mailer/template.go
index ba65d2c0..c60dc86e 100644
--- a/mailer/template.go
+++ b/mailer/template.go
@@ -16,7 +16,7 @@ type MailClient interface {
 // TemplateMailer will send mail and use templates from the site for easy mail styling
 type TemplateMailer struct {
 	SiteURL string
-	Config  *conf.Configuration
+	Config  *conf.GlobalConfiguration
 	Mailer  MailClient
 }
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index dc03740a..4261573a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       matrix:
         go-version: [1.18.x]
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     services:
       postgres:
         image: postgres:13
diff --git a/api/admin_test.go b/api/admin_test.go
index 669756a0..8f406500 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -22,7 +22,7 @@ type AdminTestSuite struct {
 	suite.Suite
 	User   *models.User
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 
 	token string
 }
@@ -466,13 +466,13 @@ func (ts *AdminTestSuite) TestAdminUserDelete() {
 func (ts *AdminTestSuite) TestAdminUserCreateWithDisabledLogin() {
 	var cases = []struct {
 		desc         string
-		customConfig *conf.Configuration
+		customConfig *conf.GlobalConfiguration
 		userData     map[string]interface{}
 		expected     int
 	}{
 		{
 			"Email Signups Disabled",
-			&conf.Configuration{
+			&conf.GlobalConfiguration{
 				JWT: ts.Config.JWT,
 				External: conf.ProviderConfiguration{
 					Email: conf.EmailProviderConfiguration{
@@ -488,7 +488,7 @@ func (ts *AdminTestSuite) TestAdminUserCreateWithDisabledLogin() {
 		},
 		{
 			"Phone Signups Disabled",
-			&conf.Configuration{
+			&conf.GlobalConfiguration{
 				JWT: ts.Config.JWT,
 				External: conf.ProviderConfiguration{
 					Phone: conf.PhoneProviderConfiguration{
@@ -504,7 +504,7 @@ func (ts *AdminTestSuite) TestAdminUserCreateWithDisabledLogin() {
 		},
 		{
 			"All Signups Disabled",
-			&conf.Configuration{
+			&conf.GlobalConfiguration{
 				JWT:           ts.Config.JWT,
 				DisableSignup: true,
 			},
diff --git a/api/api_test.go b/api/api_test.go
index cb5af02a..67bc4d96 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -29,43 +29,22 @@ func init() {
 // setupAPIForTest creates a new API to run tests with.
 // Using this function allows us to keep track of the database connection
 // and cleaning up data between tests.
-func setupAPIForTest() (*API, *conf.Configuration, error) {
+func setupAPIForTest() (*API, *conf.GlobalConfiguration, error) {
 	return setupAPIForTestWithCallback(nil)
 }
 
-func setupAPIForTestWithCallback(cb func(*conf.GlobalConfiguration, *conf.Configuration, *storage.Connection) (uuid.UUID, error)) (*API, *conf.Configuration, error) {
-	globalConfig, err := conf.LoadGlobal(apiTestConfig)
+func setupAPIForTestWithCallback(cb func(*conf.GlobalConfiguration, *storage.Connection) (uuid.UUID, error)) (*API, *conf.GlobalConfiguration, error) {
+	config, err := conf.LoadGlobal(apiTestConfig)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	conn, err := test.SetupDBConnection(globalConfig)
+	conn, err := test.SetupDBConnection(config)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	config, err := conf.LoadConfig(apiTestConfig)
-	if err != nil {
-		conn.Close()
-		return nil, nil, err
-	}
-
-	instanceID := uuid.Nil
-	if cb != nil {
-		instanceID, err = cb(globalConfig, config, conn)
-		if err != nil {
-			conn.Close()
-			return nil, nil, err
-		}
-	}
-
-	ctx, err := WithInstanceConfig(context.Background(), config, instanceID)
-	if err != nil {
-		conn.Close()
-		return nil, nil, err
-	}
-
-	return NewAPIWithVersion(ctx, globalConfig, conn, apiTestVersion), config, nil
+	return NewAPIWithVersion(context.Background(), config, conn, apiTestVersion), config, nil
 }
 
 func TestEmailEnabledByDefault(t *testing.T) {
diff --git a/api/audit_test.go b/api/audit_test.go
index 78b767dc..a6c44084 100644
--- a/api/audit_test.go
+++ b/api/audit_test.go
@@ -20,7 +20,7 @@ import (
 type AuditTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 
 	token string
 }
diff --git a/api/external_test.go b/api/external_test.go
index b8719d4a..47f13e0b 100644
--- a/api/external_test.go
+++ b/api/external_test.go
@@ -16,7 +16,7 @@ import (
 type ExternalTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestExternal(t *testing.T) {
diff --git a/api/hook_test.go b/api/hook_test.go
index d93dc9b1..91ceca5e 100644
--- a/api/hook_test.go
+++ b/api/hook_test.go
@@ -49,7 +49,7 @@ func TestSignupHookSendInstanceID(t *testing.T) {
 	localhost := removeLocalhostFromPrivateIPBlock()
 	defer unshiftPrivateIPBlock(localhost)
 
-	config := &conf.Configuration{
+	config := &conf.GlobalConfiguration{
 		Webhook: conf.WebhookConfig{
 			URL:    svr.URL,
 			Events: []string{SignupEvent},
@@ -92,7 +92,7 @@ func TestSignupHookFromClaims(t *testing.T) {
 	localhost := removeLocalhostFromPrivateIPBlock()
 	defer unshiftPrivateIPBlock(localhost)
 
-	config := &conf.Configuration{
+	config := &conf.GlobalConfiguration{
 		Webhook: conf.WebhookConfig{
 			Events: []string{"signup"},
 		},
diff --git a/api/invite_test.go b/api/invite_test.go
index 095d692e..48bc72de 100644
--- a/api/invite_test.go
+++ b/api/invite_test.go
@@ -23,7 +23,7 @@ import (
 type InviteTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 
 	token string
 }
diff --git a/api/middleware_test.go b/api/middleware_test.go
index 6d8f2d2f..eff2bee2 100644
--- a/api/middleware_test.go
+++ b/api/middleware_test.go
@@ -2,13 +2,13 @@ package api
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"testing"
 
-	"github.com/gofrs/uuid"
 	"github.com/netlify/gotrue/conf"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -23,7 +23,7 @@ const (
 type MiddlewareTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestHCaptcha(t *testing.T) {
@@ -55,9 +55,8 @@ func (ts *MiddlewareTestSuite) TestVerifyCaptchaValid() {
 
 	req := httptest.NewRequest(http.MethodPost, "http://localhost", &buffer)
 	req.Header.Set("Content-Type", "application/json")
-	beforeCtx, err := WithInstanceConfig(req.Context(), ts.Config, uuid.Nil)
-	require.NoError(ts.T(), err)
 
+	beforeCtx := context.Background()
 	req = req.WithContext(beforeCtx)
 
 	w := httptest.NewRecorder()
@@ -131,14 +130,12 @@ func (ts *MiddlewareTestSuite) TestVerifyCaptchaInvalid() {
 			}))
 			req := httptest.NewRequest(http.MethodPost, "http://localhost", &buffer)
 			req.Header.Set("Content-Type", "application/json")
-			ctx, err := WithInstanceConfig(req.Context(), ts.Config, uuid.Nil)
-			require.NoError(ts.T(), err)
 
-			req = req.WithContext(ctx)
+			req = req.WithContext(context.Background())
 
 			w := httptest.NewRecorder()
 
-			_, err = ts.API.verifyCaptcha(w, req)
+			_, err := ts.API.verifyCaptcha(w, req)
 			require.Equal(ts.T(), c.expectedCode, err.(*HTTPError).Code)
 			require.Equal(ts.T(), c.expectedMsg, err.(*HTTPError).Message)
 		})
diff --git a/api/otp_test.go b/api/otp_test.go
index a820a706..db240282 100644
--- a/api/otp_test.go
+++ b/api/otp_test.go
@@ -17,7 +17,7 @@ import (
 type OtpTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestOtp(t *testing.T) {
diff --git a/api/phone_test.go b/api/phone_test.go
index 2425fc0b..ba633f20 100644
--- a/api/phone_test.go
+++ b/api/phone_test.go
@@ -23,7 +23,7 @@ import (
 type PhoneTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 type TestSmsProvider struct {
@@ -69,8 +69,7 @@ func (ts *PhoneTestSuite) TestFormatPhoneNumber() {
 func (ts *PhoneTestSuite) TestSendPhoneConfirmation() {
 	u, err := models.FindUserByPhoneAndAudience(ts.API.db, uuid.Nil, "123456789", ts.Config.JWT.Aud)
 	require.NoError(ts.T(), err)
-	ctx, err := WithInstanceConfig(context.Background(), ts.Config, uuid.Nil)
-	require.NoError(ts.T(), err)
+	ctx := context.Background()
 	cases := []struct {
 		desc     string
 		otpType  string
diff --git a/api/recover_test.go b/api/recover_test.go
index ef7dd760..c37c29f7 100644
--- a/api/recover_test.go
+++ b/api/recover_test.go
@@ -19,7 +19,7 @@ import (
 type RecoverTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestRecover(t *testing.T) {
diff --git a/api/settings_test.go b/api/settings_test.go
index 857d8bf8..58398266 100644
--- a/api/settings_test.go
+++ b/api/settings_test.go
@@ -7,7 +7,6 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"github.com/gofrs/uuid"
 	"github.com/stretchr/testify/require"
 )
 
@@ -56,8 +55,7 @@ func TestSettings_EmailDisabled(t *testing.T) {
 	req := httptest.NewRequest(http.MethodGet, "http://localhost/settings", nil)
 	req.Header.Set("Content-Type", "application/json")
 
-	ctx, err := WithInstanceConfig(context.Background(), config, uuid.Nil)
-	require.NoError(t, err)
+	ctx := context.Background()
 	req = req.WithContext(ctx)
 
 	w := httptest.NewRecorder()
diff --git a/api/signup_test.go b/api/signup_test.go
index 4dd0eec6..46a1a849 100644
--- a/api/signup_test.go
+++ b/api/signup_test.go
@@ -23,7 +23,7 @@ import (
 type SignupTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestSignup(t *testing.T) {
diff --git a/api/token_test.go b/api/token_test.go
index 20222155..76776446 100644
--- a/api/token_test.go
+++ b/api/token_test.go
@@ -20,7 +20,7 @@ import (
 type TokenTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 
 	RefreshToken *models.RefreshToken
 }
diff --git a/api/tracer_test.go b/api/tracer_test.go
index 38ce2d5c..5bac0dd8 100644
--- a/api/tracer_test.go
+++ b/api/tracer_test.go
@@ -16,7 +16,7 @@ import (
 type TracerTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestTracer(t *testing.T) {
diff --git a/api/user_test.go b/api/user_test.go
index a3838dbf..fce93c12 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -20,7 +20,7 @@ import (
 type UserTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestUser(t *testing.T) {
diff --git a/api/verify_test.go b/api/verify_test.go
index e5070604..c3bdd3f6 100644
--- a/api/verify_test.go
+++ b/api/verify_test.go
@@ -23,7 +23,7 @@ import (
 type VerifyTestSuite struct {
 	suite.Suite
 	API    *API
-	Config *conf.Configuration
+	Config *conf.GlobalConfiguration
 }
 
 func TestVerify(t *testing.T) {
diff --git a/conf/configuration_test.go b/conf/configuration_test.go
index 5573eb84..0e7aa1b1 100644
--- a/conf/configuration_test.go
+++ b/conf/configuration_test.go
@@ -15,10 +15,12 @@ func TestMain(m *testing.M) {
 }
 
 func TestGlobal(t *testing.T) {
-	os.Setenv("GOTRUE_DB_DRIVER", "postgres")
+	os.Setenv("GOTRUE_SITE_URL", "http://localhost:8080")
+	os.Setenv("GOTRUE_DB_DRIVER", "mysql")
 	os.Setenv("GOTRUE_DB_DATABASE_URL", "fake")
 	os.Setenv("GOTRUE_OPERATOR_TOKEN", "token")
 	os.Setenv("GOTRUE_API_REQUEST_ID_HEADER", "X-Request-ID")
+	os.Setenv("GOTRUE_JWT_SECRET", "secret")
 	gc, err := LoadGlobal("")
 	require.NoError(t, err)
 	require.NotNil(t, gc)
@@ -26,7 +28,13 @@ func TestGlobal(t *testing.T) {
 }
 
 func TestTracing(t *testing.T) {
-	os.Setenv("GOTRUE_DB_DRIVER", "postgres")
+	os.Setenv("GOTRUE_SITE_URL", "http://localhost:8080")
+	os.Setenv("GOTRUE_DB_DRIVER", "mysql")
+	os.Setenv("GOTRUE_DB_DATABASE_URL", "fake")
+	os.Setenv("GOTRUE_OPERATOR_TOKEN", "token")
+	os.Setenv("GOTRUE_API_REQUEST_ID_HEADER", "X-Request-ID")
+	os.Setenv("GOTRUE_JWT_SECRET", "secret")
+	os.Setenv("GOTRUE_DB_DRIVER", "mysql")
 	os.Setenv("GOTRUE_DB_DATABASE_URL", "fake")
 	os.Setenv("GOTRUE_OPERATOR_TOKEN", "token")
 	os.Setenv("GOTRUE_TRACING_SERVICE_NAME", "identity")
@@ -34,7 +42,8 @@ func TestTracing(t *testing.T) {
 	os.Setenv("GOTRUE_TRACING_HOST", "127.0.0.1")
 	os.Setenv("GOTRUE_TRACING_TAGS", "tag1:value1,tag2:value2")
 
-	gc, _ := LoadGlobal("")
+	gc, err := LoadGlobal("")
+	require.NoError(t, err)
 	tc := opentracing.GlobalTracer()
 
 	assert.Equal(t, opentracing.NoopTracer{}, tc)