Skip to content

Commit

Permalink
Merge pull request #361 from supertokens/dashboard-user-permissions
Browse files Browse the repository at this point in the history
feat: Allow users to set emails as admins for user dashboard
  • Loading branch information
rishabhpoddar authored Sep 12, 2023
2 parents 0d35cb9 + f27831e commit d86ddb8
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 2 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.14.0] - 2023-09-11

### Added

- The Dashboard recipe now accepts a new `Admins` property which can be used to give Dashboard Users write privileges for the user dashboard.

### Changes

- Dashboard APIs now return a status code `403` for all non-GET requests if the currently logged in Dashboard User is not listed in the `admins` array

## [0.13.2] - 2023-08-28

- Adds logic to retry network calls if the core returns status 429
Expand Down
8 changes: 8 additions & 0 deletions recipe/dashboard/apiKeyProtector.go
Original file line number Diff line number Diff line change
@@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions recipe/dashboard/dashboardmodels/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package dashboardmodels

type TypeInput struct {
ApiKey string
Admins *[]string
Override *OverrideStruct
}

Expand All @@ -29,6 +30,7 @@ const (

type TypeNormalisedInput struct {
ApiKey string
Admins *[]string
AuthMode TypeAuthMode
Override OverrideStruct
}
Expand Down
9 changes: 9 additions & 0 deletions recipe/dashboard/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package errors

type ForbiddenAccessError struct {
Msg string
}

func (err ForbiddenAccessError) Error() string {
return err.Msg
}
48 changes: 47 additions & 1 deletion recipe/dashboard/recipeimplementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -47,7 +49,51 @@ 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 admins == nil {
return true, nil
}

if len(*admins) == 0 {
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",
}
}

userEmail, emailOk := verifyResponse["email"]

if !emailOk || userEmail.(string) == "" {
supertokens.LogDebugMessage("User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here")
return false, nil
}

if !supertokens.DoesSliceContainString(userEmail.(string), *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)
Expand Down
18 changes: 18 additions & 0 deletions recipe/dashboard/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.")
}

var admins *[]string
if _config.Admins != nil {
admins = _config.Admins
}

typeNormalisedInput.Admins = admins

return typeNormalisedInput
}

Expand All @@ -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
}
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.13.2"
const VERSION = "0.14.0"

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

0 comments on commit d86ddb8

Please sign in to comment.