diff --git a/recipe/dashboard/apiKeyProtector.go b/recipe/dashboard/apiKeyProtector.go index 3006b912..c09e1252 100644 --- a/recipe/dashboard/apiKeyProtector.go +++ b/recipe/dashboard/apiKeyProtector.go @@ -1,13 +1,21 @@ package dashboard import ( + "errors" "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" + errors2 "github.com/supertokens/supertokens-golang/recipe/dashboard/errors" "github.com/supertokens/supertokens-golang/supertokens" ) func apiKeyProtector(apiImpl dashboardmodels.APIInterface, tenantId string, options dashboardmodels.APIOptions, userContext supertokens.UserContext, call func() (interface{}, error)) error { shouldAllowAccess, err := (*options.RecipeImplementation.ShouldAllowAccess)(options.Req, options.Config, userContext) if err != nil { + if errors.As(err, &errors2.ForbiddenAccessError{}) { + return supertokens.SendNon200Response(options.Res, 403, map[string]interface{}{ + "message": err.Error(), + }) + } + return err } diff --git a/recipe/dashboard/dashboardmodels/models.go b/recipe/dashboard/dashboardmodels/models.go index 2e5fcdd2..51d56581 100644 --- a/recipe/dashboard/dashboardmodels/models.go +++ b/recipe/dashboard/dashboardmodels/models.go @@ -17,6 +17,7 @@ package dashboardmodels type TypeInput struct { ApiKey string + Admins *[]string Override *OverrideStruct } @@ -29,6 +30,7 @@ const ( type TypeNormalisedInput struct { ApiKey string + Admins []string AuthMode TypeAuthMode Override OverrideStruct } diff --git a/recipe/dashboard/errors/errors.go b/recipe/dashboard/errors/errors.go new file mode 100644 index 00000000..5f7a0c58 --- /dev/null +++ b/recipe/dashboard/errors/errors.go @@ -0,0 +1,9 @@ +package errors + +type ForbiddenAccessError struct { + Msg string +} + +func (err ForbiddenAccessError) Error() string { + return err.Msg +} diff --git a/recipe/dashboard/recipeimplementation.go b/recipe/dashboard/recipeimplementation.go index 265578af..9d202084 100644 --- a/recipe/dashboard/recipeimplementation.go +++ b/recipe/dashboard/recipeimplementation.go @@ -17,7 +17,9 @@ package dashboard import ( "fmt" + "github.com/supertokens/supertokens-golang/recipe/dashboard/constants" "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" + "github.com/supertokens/supertokens-golang/recipe/dashboard/errors" "github.com/supertokens/supertokens-golang/recipe/dashboard/validationUtils" "github.com/supertokens/supertokens-golang/supertokens" "net/http" @@ -47,7 +49,45 @@ func makeRecipeImplementation(querier supertokens.Querier) dashboardmodels.Recip status, ok := verifyResponse["status"] - return ok && status.(string) == "OK", nil + if !ok || status != "OK" { + return false, nil + } + + // For all non GET requests we also want to check if the user is allowed to perform this operation + if req.Method != http.MethodGet { + // We dont want to block the analytics API + if strings.HasSuffix(req.RequestURI, constants.DashboardAnalyticsAPI) { + return true, nil + } + + // We do not want to block the sign out request + if strings.HasSuffix(req.RequestURI, constants.SignOutAPI) { + return true, nil + } + + admins := config.Admins + + // If the user has provided no admins, allow + if len(admins) == 0 { + return true, nil + } + + emailInHeaders := req.Header.Get("email") + + if emailInHeaders == "" { + supertokens.LogDebugMessage("User Dashboard: Returning Unauthorised because no email was provided in headers") + return false, nil + } + + if supertokens.DoesSliceContainString(emailInHeaders, admins) { + supertokens.LogDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin") + return false, errors.ForbiddenAccessError{ + Msg: "You are not permitted to perform this operation", + } + } + } + + return true, nil } validateKeyResponse, err := validationUtils.ValidateApiKey(req, config, userContext) diff --git a/recipe/dashboard/utils.go b/recipe/dashboard/utils.go index 7662bda9..d126a907 100644 --- a/recipe/dashboard/utils.go +++ b/recipe/dashboard/utils.go @@ -18,6 +18,7 @@ package dashboard import ( "github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels" "github.com/supertokens/supertokens-golang/supertokens" + "strings" ) func validateAndNormaliseUserInput(appInfo supertokens.NormalisedAppinfo, config *dashboardmodels.TypeInput) dashboardmodels.TypeNormalisedInput { @@ -42,6 +43,17 @@ func validateAndNormaliseUserInput(appInfo supertokens.NormalisedAppinfo, config } } + if _config.ApiKey != "" && config.Admins != nil { + supertokens.LogDebugMessage("User Dashboard: Providing 'Admins' has no effect when using an apiKey.") + } + + admins := []string{} + if _config.Admins != nil { + admins = *_config.Admins + } + + typeNormalisedInput.Admins = admins + return typeNormalisedInput } @@ -58,3 +70,9 @@ func makeTypeNormalisedInput(appInfo supertokens.NormalisedAppinfo) dashboardmod }, } } + +func normaliseEmail(email string) string { + _email := strings.TrimSpace(email) + _email = strings.ToLower(_email) + return _email +}