Skip to content

Commit d86ddb8

Browse files
Merge pull request #361 from supertokens/dashboard-user-permissions
feat: Allow users to set emails as admins for user dashboard
2 parents 0d35cb9 + f27831e commit d86ddb8

File tree

7 files changed

+95
-2
lines changed

7 files changed

+95
-2
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10+
## [0.14.0] - 2023-09-11
11+
12+
### Added
13+
14+
- The Dashboard recipe now accepts a new `Admins` property which can be used to give Dashboard Users write privileges for the user dashboard.
15+
16+
### Changes
17+
18+
- 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
19+
1020
## [0.13.2] - 2023-08-28
1121

1222
- Adds logic to retry network calls if the core returns status 429

recipe/dashboard/apiKeyProtector.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package dashboard
22

33
import (
4+
"errors"
45
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
6+
errors2 "github.com/supertokens/supertokens-golang/recipe/dashboard/errors"
57
"github.com/supertokens/supertokens-golang/supertokens"
68
)
79

810
func apiKeyProtector(apiImpl dashboardmodels.APIInterface, tenantId string, options dashboardmodels.APIOptions, userContext supertokens.UserContext, call func() (interface{}, error)) error {
911
shouldAllowAccess, err := (*options.RecipeImplementation.ShouldAllowAccess)(options.Req, options.Config, userContext)
1012
if err != nil {
13+
if errors.As(err, &errors2.ForbiddenAccessError{}) {
14+
return supertokens.SendNon200Response(options.Res, 403, map[string]interface{}{
15+
"message": err.Error(),
16+
})
17+
}
18+
1119
return err
1220
}
1321

recipe/dashboard/dashboardmodels/models.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package dashboardmodels
1717

1818
type TypeInput struct {
1919
ApiKey string
20+
Admins *[]string
2021
Override *OverrideStruct
2122
}
2223

@@ -29,6 +30,7 @@ const (
2930

3031
type TypeNormalisedInput struct {
3132
ApiKey string
33+
Admins *[]string
3234
AuthMode TypeAuthMode
3335
Override OverrideStruct
3436
}

recipe/dashboard/errors/errors.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package errors
2+
3+
type ForbiddenAccessError struct {
4+
Msg string
5+
}
6+
7+
func (err ForbiddenAccessError) Error() string {
8+
return err.Msg
9+
}

recipe/dashboard/recipeimplementation.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package dashboard
1717

1818
import (
1919
"fmt"
20+
"github.com/supertokens/supertokens-golang/recipe/dashboard/constants"
2021
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
22+
"github.com/supertokens/supertokens-golang/recipe/dashboard/errors"
2123
"github.com/supertokens/supertokens-golang/recipe/dashboard/validationUtils"
2224
"github.com/supertokens/supertokens-golang/supertokens"
2325
"net/http"
@@ -47,7 +49,51 @@ func makeRecipeImplementation(querier supertokens.Querier) dashboardmodels.Recip
4749

4850
status, ok := verifyResponse["status"]
4951

50-
return ok && status.(string) == "OK", nil
52+
if !ok || status != "OK" {
53+
return false, nil
54+
}
55+
56+
// For all non GET requests we also want to check if the user is allowed to perform this operation
57+
if req.Method != http.MethodGet {
58+
// We dont want to block the analytics API
59+
if strings.HasSuffix(req.RequestURI, constants.DashboardAnalyticsAPI) {
60+
return true, nil
61+
}
62+
63+
// We do not want to block the sign out request
64+
if strings.HasSuffix(req.RequestURI, constants.SignOutAPI) {
65+
return true, nil
66+
}
67+
68+
admins := config.Admins
69+
70+
if admins == nil {
71+
return true, nil
72+
}
73+
74+
if len(*admins) == 0 {
75+
supertokens.LogDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin")
76+
return false, errors.ForbiddenAccessError{
77+
Msg: "You are not permitted to perform this operation",
78+
}
79+
}
80+
81+
userEmail, emailOk := verifyResponse["email"]
82+
83+
if !emailOk || userEmail.(string) == "" {
84+
supertokens.LogDebugMessage("User Dashboard: Returning Unauthorised because no email was returned from the core. Should never come here")
85+
return false, nil
86+
}
87+
88+
if !supertokens.DoesSliceContainString(userEmail.(string), *admins) {
89+
supertokens.LogDebugMessage("User Dashboard: Throwing OPERATION_NOT_ALLOWED because user is not an admin")
90+
return false, errors.ForbiddenAccessError{
91+
Msg: "You are not permitted to perform this operation",
92+
}
93+
}
94+
}
95+
96+
return true, nil
5197
}
5298

5399
validateKeyResponse, err := validationUtils.ValidateApiKey(req, config, userContext)

recipe/dashboard/utils.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package dashboard
1818
import (
1919
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
2020
"github.com/supertokens/supertokens-golang/supertokens"
21+
"strings"
2122
)
2223

2324
func validateAndNormaliseUserInput(appInfo supertokens.NormalisedAppinfo, config *dashboardmodels.TypeInput) dashboardmodels.TypeNormalisedInput {
@@ -42,6 +43,17 @@ func validateAndNormaliseUserInput(appInfo supertokens.NormalisedAppinfo, config
4243
}
4344
}
4445

46+
if _config.ApiKey != "" && config.Admins != nil {
47+
supertokens.LogDebugMessage("User Dashboard: Providing 'Admins' has no effect when using an apiKey.")
48+
}
49+
50+
var admins *[]string
51+
if _config.Admins != nil {
52+
admins = _config.Admins
53+
}
54+
55+
typeNormalisedInput.Admins = admins
56+
4557
return typeNormalisedInput
4658
}
4759

@@ -58,3 +70,9 @@ func makeTypeNormalisedInput(appInfo supertokens.NormalisedAppinfo) dashboardmod
5870
},
5971
}
6072
}
73+
74+
func normaliseEmail(email string) string {
75+
_email := strings.TrimSpace(email)
76+
_email = strings.ToLower(_email)
77+
return _email
78+
}

supertokens/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121
)
2222

2323
// VERSION current version of the lib
24-
const VERSION = "0.13.2"
24+
const VERSION = "0.14.0"
2525

2626
var (
2727
cdiSupported = []string{"3.0"}

0 commit comments

Comments
 (0)