Skip to content

Commit d84f8d0

Browse files
Merge pull request #241 from supertokens/feat/dashboard-email-login
feat: Add email password login for the dashboard
2 parents bdbe6d8 + 3351470 commit d84f8d0

File tree

17 files changed

+256
-44
lines changed

17 files changed

+256
-44
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ gin/
2020

2121
apiPassword
2222
releasePassword
23-
.vscode/
23+
.vscode/
24+
.idea/

CHANGELOG.md

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

88
## [unreleased]
99

10+
## [0.10.2] - 2023-02-24
11+
- Adds APIs and logic to the dashboard recipe to enable email password based login
12+
1013
## [0.10.1] - 2023-02-06
1114

1215
- Email template updates

coreDriverInterfaceSupported.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"2.12",
99
"2.13",
1010
"2.14",
11-
"2.15"
11+
"2.15",
12+
"2.16",
13+
"2.17",
14+
"2.18"
1215
]
1316
}

recipe/dashboard/api/implementation.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ func MakeAPIImplementation() dashboardmodels.APIInterface {
5050
}
5151
dashboardAppPath := options.AppInfo.APIBasePath.AppendPath(normalizedDashboardPath).GetAsStringDangerous()
5252

53+
authMode := string(options.Config.AuthMode)
54+
5355
return `
5456
<html>
5557
<head>
@@ -58,6 +60,7 @@ func MakeAPIImplementation() dashboardmodels.APIInterface {
5860
window.staticBasePath = "` + bundleDomain + `/static"
5961
window.dashboardAppPath = "` + dashboardAppPath + `"
6062
window.connectionURI = "` + connectionURI + `"
63+
window.authMode = "` + authMode + `"
6164
</script>
6265
<script defer src="` + bundleDomain + `/static/js/bundle.js"></script></head>
6366
<link href="` + bundleDomain + `/static/css/main.css" rel="stylesheet" type="text/css">

recipe/dashboard/api/signInPost.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
6+
"github.com/supertokens/supertokens-golang/supertokens"
7+
)
8+
9+
type signInPostResponse struct {
10+
Status string `json:"status"`
11+
SessionId string `json:"sessionId,omitempty"`
12+
Message string `json:"message,omitempty"`
13+
}
14+
15+
type signInRequestBody struct {
16+
Email *string `json:"email"`
17+
Password *string `json:"password"`
18+
}
19+
20+
func SignInPost(apiInterface dashboardmodels.APIInterface, options dashboardmodels.APIOptions) error {
21+
body, err := supertokens.ReadFromRequest(options.Req)
22+
23+
if err != nil {
24+
return err
25+
}
26+
27+
var readBody signInRequestBody
28+
err = json.Unmarshal(body, &readBody)
29+
if err != nil {
30+
return err
31+
}
32+
33+
if readBody.Email == nil {
34+
return supertokens.BadInputError{
35+
Msg: "Required parameter 'email' is missing",
36+
}
37+
}
38+
39+
if readBody.Password == nil {
40+
return supertokens.BadInputError{
41+
Msg: "Required parameter 'password' is missing",
42+
}
43+
}
44+
45+
querier, querierErr := supertokens.GetNewQuerierInstanceOrThrowError("dashboard")
46+
47+
if querierErr != nil {
48+
return querierErr
49+
}
50+
51+
apiResponse, apiErr := querier.SendPostRequest("/recipe/dashboard/signin", map[string]interface{}{
52+
"email": *readBody.Email,
53+
"password": *readBody.Password,
54+
})
55+
56+
if apiErr != nil {
57+
return apiErr
58+
}
59+
60+
status := apiResponse["status"]
61+
62+
if status == "OK" {
63+
return supertokens.Send200Response(options.Res, map[string]interface{}{
64+
"status": "OK",
65+
"sessionId": apiResponse["sessionId"].(string),
66+
})
67+
}
68+
69+
if status == "USER_SUSPENDED_ERROR" {
70+
return supertokens.Send200Response(options.Res, map[string]interface{}{
71+
"status": "USER_SUSPENDED_ERROR",
72+
"message": apiResponse["message"].(string),
73+
})
74+
}
75+
76+
return supertokens.Send200Response(options.Res, map[string]interface{}{
77+
"status": "INVAlID_CREDENTIALS_ERROR",
78+
})
79+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package api
2+
3+
import (
4+
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
5+
"github.com/supertokens/supertokens-golang/supertokens"
6+
"strings"
7+
)
8+
9+
type signOutPostResponse struct {
10+
Status string `json:"status"`
11+
}
12+
13+
func SignOutPost(apiInterface dashboardmodels.APIInterface, options dashboardmodels.APIOptions) (signOutPostResponse, error) {
14+
if options.Config.AuthMode == dashboardmodels.AuthModeAPIKey {
15+
return signOutPostResponse{
16+
Status: "OK",
17+
}, nil
18+
}
19+
20+
sessionIdFromHeader := options.Req.Header.Get("authorization")
21+
22+
// We receive the api key as `Bearer API_KEY`, this retrieves just the key
23+
keyParts := strings.Split(sessionIdFromHeader, " ")
24+
sessionIdFromHeader = keyParts[len(keyParts)-1]
25+
26+
querier, querierErr := supertokens.GetNewQuerierInstanceOrThrowError("dashboard")
27+
28+
if querierErr != nil {
29+
return signOutPostResponse{}, querierErr
30+
}
31+
32+
_, apiError := querier.SendDeleteRequest("/recipe/dashboard/session", map[string]interface{}{}, map[string]string{
33+
"sessionId": sessionIdFromHeader,
34+
})
35+
36+
if apiError != nil {
37+
return signOutPostResponse{}, apiError
38+
}
39+
40+
return signOutPostResponse{
41+
Status: "OK",
42+
}, nil
43+
}

recipe/dashboard/api/utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func GetUserForRecipeId(userId string, recipeId string) (user dashboardmodels.Us
6565
userToReturn.LastName = ""
6666
userToReturn.Email = response.Email
6767
userToReturn.ThirdParty = &dashboardmodels.ThirdParty{
68-
Id: response.ThirdParty.ID,
68+
Id: response.ThirdParty.ID,
6969
UserId: response.ThirdParty.UserID,
7070
}
7171
}
@@ -80,7 +80,7 @@ func GetUserForRecipeId(userId string, recipeId string) (user dashboardmodels.Us
8080
userToReturn.LastName = ""
8181
userToReturn.Email = tpepResponse.Email
8282
userToReturn.ThirdParty = &dashboardmodels.ThirdParty{
83-
Id: tpepResponse.ThirdParty.ID,
83+
Id: tpepResponse.ThirdParty.ID,
8484
UserId: tpepResponse.ThirdParty.UserID,
8585
}
8686
}

recipe/dashboard/api/validateKey.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ package api
22

33
import (
44
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
5+
"github.com/supertokens/supertokens-golang/recipe/dashboard/validationUtils"
56
"github.com/supertokens/supertokens-golang/supertokens"
67
)
78

89
func ValidateKey(apiImplementation dashboardmodels.APIInterface, options dashboardmodels.APIOptions) error {
9-
shouldAllowAccess, err := (*options.RecipeImplementation.ShouldAllowAccess)(options.Req, options.Config, supertokens.MakeDefaultUserContextFromAPI(options.Req))
10+
isKeyValid, err := validationUtils.ValidateApiKey(options.Req, options.Config, supertokens.MakeDefaultUserContextFromAPI(options.Req))
11+
1012
if err != nil {
1113
return err
1214
}
1315

14-
if !shouldAllowAccess {
16+
if isKeyValid {
17+
return supertokens.Send200Response(options.Res, map[string]interface{}{
18+
"status": "OK",
19+
})
20+
} else {
1521
return supertokens.SendUnauthorisedAccess(options.Res)
1622
}
17-
18-
return supertokens.Send200Response(options.Res, map[string]interface{}{
19-
"status": "OK",
20-
})
2123
}

recipe/dashboard/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ const userSessionsAPI = "/api/user/sessions"
1010
const userMetaDataAPI = "/api/user/metadata"
1111
const userEmailVerifyTokenAPI = "/api/user/email/verify/token"
1212
const userPasswordAPI = "/api/user/password"
13+
const signInAPI = "/api/signin"
14+
const signOutAPI = "/api/signout"

recipe/dashboard/dashboardmodels/models.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,16 @@ type TypeInput struct {
2020
Override *OverrideStruct
2121
}
2222

23+
type TypeAuthMode string
24+
25+
const (
26+
AuthModeEmailPassword TypeAuthMode = "email-password"
27+
AuthModeAPIKey TypeAuthMode = "api-key"
28+
)
29+
2330
type TypeNormalisedInput struct {
2431
ApiKey string
32+
AuthMode TypeAuthMode
2533
Override OverrideStruct
2634
}
2735

@@ -36,11 +44,11 @@ type ThirdParty struct {
3644
}
3745

3846
type UserType struct {
39-
Id string `json:"id,omitempty"`
40-
TimeJoined uint64 `json:"timeJoined,omitempty"`
41-
FirstName string `json:"firstName,omitempty"`
42-
LastName string `json:"lastName,omitempty"`
43-
Email string `json:"email,omitempty"`
47+
Id string `json:"id,omitempty"`
48+
TimeJoined uint64 `json:"timeJoined,omitempty"`
49+
FirstName string `json:"firstName,omitempty"`
50+
LastName string `json:"lastName,omitempty"`
51+
Email string `json:"email,omitempty"`
4452
ThirdParty *ThirdParty `json:"thirdParty,omitempty"`
45-
Phone string `json:"phoneNumber,omitempty"`
53+
Phone string `json:"phoneNumber,omitempty"`
4654
}

0 commit comments

Comments
 (0)