Skip to content

Feat!: Account linking #391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 31 commits into
base: 0.17
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
81b48ca
adds global user type and also input type for account linking
rishabhpoddar Dec 8, 2023
e9db988
adds types for recipe interface
rishabhpoddar Dec 8, 2023
5403429
moves account linking into supertokens package
rishabhpoddar Dec 11, 2023
9b2b7dc
adds test for user struct jsonification
rishabhpoddar Dec 12, 2023
aa86b44
updates dashboard version
rishabhpoddar Dec 12, 2023
1cbd2aa
completes definition of new user object
rishabhpoddar Dec 12, 2023
8ef58ce
adds one test for user pagination
rishabhpoddar Dec 12, 2023
9187118
adds more tests
rishabhpoddar Dec 12, 2023
02d158e
removes unnecessary tests
rishabhpoddar Dec 12, 2023
5cfb55d
adds get user recipe function
rishabhpoddar Dec 12, 2023
2b727d0
adds funcsignature
rishabhpoddar Dec 12, 2023
2cf5ac9
merges with latest
rishabhpoddar Dec 12, 2023
0858299
adds more impl
rishabhpoddar Dec 12, 2023
8b6d1dd
exposes functions
rishabhpoddar Dec 12, 2023
8454f5c
adds one test for crating primarty user
rishabhpoddar Dec 12, 2023
d7c94be
more tests
rishabhpoddar Dec 13, 2023
9b7223c
adds more tests and linkAccounts recipe function
rishabhpoddar Dec 13, 2023
c112c8a
more tests
rishabhpoddar Dec 13, 2023
c6ffd38
adds email verificationproxy obj in supertokens package
rishabhpoddar Dec 13, 2023
0e17deb
adds more tests
rishabhpoddar Dec 13, 2023
ff917ce
adds more tests
rishabhpoddar Dec 13, 2023
67eded3
adds more tests and adds canlinkaccount recipe function
rishabhpoddar Dec 13, 2023
c7b7d50
adds unlink account in recipe impl - tests remaining
rishabhpoddar Dec 13, 2023
d9018de
adds more tests
rishabhpoddar Dec 14, 2023
061d0bb
adds more tests
rishabhpoddar Dec 14, 2023
5f1c20c
adds more tests
rishabhpoddar Dec 14, 2023
3e190bb
adds tests for delete user
rishabhpoddar Dec 15, 2023
b23246a
more tests
rishabhpoddar Dec 15, 2023
e7e2217
adds more function
rishabhpoddar Dec 15, 2023
135633d
more tests
rishabhpoddar Dec 15, 2023
b14d829
more tests
rishabhpoddar Dec 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.18.0] - TODO

### Added
- Account linking feature

### Breaking changes
- The SDK now supports CDI 4.0 and removed support for older CDIs.
- `supertokens.DeleteUser` function now takes an extra boolean called `removeAllLinkedAccounts`. You can pass in `true` here for most cases.
- Added `supertokens.GetUser` function which can be used instead of recipe level getUser functions.
- Output type of `supertokens.GetUsersNewestFirst` and `supertokens.GetUsersOldestFirst` now return `supertokens.User` object instead of a generic string to interface{} map.
- Third party sign in up recipe function now also marks email as verified directly via a core call instead of relying on the email verification recipe.

## [0.17.3] - 2023-12-12

- CI/CD changes
Expand Down
2 changes: 1 addition & 1 deletion coreDriverInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": "contains a list of core-driver interfaces branch names that this core supports",
"versions": [
"3.0"
"4.0"
]
}
3 changes: 2 additions & 1 deletion frontendDriverInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"_comment": "contains a list of frontend-driver interfaces branch names that this core supports",
"versions": [
"1.17"
"1.17",
"1.18"
]
}
3 changes: 2 additions & 1 deletion recipe/dashboard/api/userdetails/userDelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func UserDelete(apiInterface dashboardmodels.APIInterface, tenantId string, opti
}
}

deleteError := supertokens.DeleteUser(userId)
// TODO: pass in removeAllLinkedAccounts from the input to the API.
deleteError := supertokens.DeleteUser(userId, true)

if deleteError != nil {
return userDeleteResponse{}, deleteError
Expand Down
112 changes: 44 additions & 68 deletions recipe/dashboard/api/usersGet.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,20 @@ import (
)

type UsersGetResponse struct {
Status string `json:"status"`
NextPaginationToken *string `json:"nextPaginationToken,omitempty"`
Users []Users `json:"users"`
Status string `json:"status"`
NextPaginationToken *string `json:"nextPaginationToken,omitempty"`
Users []UserWithFirstAndLastName `json:"users"`
}

type Users struct {
RecipeId string `json:"recipeId"`
User User `json:"user"`
type UserWithFirstAndLastName struct {
supertokens.User
firstName string
lastName string
}

type User struct {
Id string `json:"id"`
TimeJoined float64 `json:"timeJoined"`
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
ThirdParty dashboardmodels.ThirdParty `json:"thirdParty,omitempty"`
TenantIds string `json:"tenantIds,omitempty"`
type UserPaginationResultWithFirstAndLastName struct {
Users []UserWithFirstAndLastName
NextPaginationToken *string
}

func UsersGet(apiImplementation dashboardmodels.APIInterface, tenantId string, options dashboardmodels.APIOptions, userContext supertokens.UserContext) (UsersGetResponse, error) {
Expand Down Expand Up @@ -83,7 +78,8 @@ func UsersGet(apiImplementation dashboardmodels.APIInterface, tenantId string, o
}

if len(queryParamsObject) != 0 {
usersResponse, err = supertokens.GetUsersWithSearchParams(tenantId, timeJoinedOrder, paginationTokenPtr, &limit, nil, queryParamsObject)
// the oder here doesn't matter cause in search, we return all users anyway.
usersResponse, err = supertokens.GetUsersNewestFirst(tenantId, paginationTokenPtr, &limit, nil, queryParamsObject)
} else if timeJoinedOrder == "ASC" {
usersResponse, err = supertokens.GetUsersOldestFirst(tenantId, paginationTokenPtr, &limit, nil, nil)
} else {
Expand All @@ -93,12 +89,28 @@ func UsersGet(apiImplementation dashboardmodels.APIInterface, tenantId string, o
return UsersGetResponse{}, err
}

var userResponseWithFirstAndLastName UserPaginationResultWithFirstAndLastName = UserPaginationResultWithFirstAndLastName{}

// copy userResponse into userResponseWithFirstAndLastName
userResponseWithFirstAndLastName.NextPaginationToken = usersResponse.NextPaginationToken
for _, userObj := range usersResponse.Users {
userResponseWithFirstAndLastName.Users = append(userResponseWithFirstAndLastName.Users, struct {
supertokens.User
firstName string
lastName string
}{
User: userObj,
firstName: "",
lastName: "",
})
}

_, err = usermetadata.GetRecipeInstanceOrThrowError()
if err != nil {
return UsersGetResponse{
Status: "OK",
NextPaginationToken: usersResponse.NextPaginationToken,
Users: getUsersTypeFromPaginationResult(usersResponse),
Users: userResponseWithFirstAndLastName.Users,
}, nil
}

Expand All @@ -109,26 +121,33 @@ func UsersGet(apiImplementation dashboardmodels.APIInterface, tenantId string, o
var sem = make(chan int, batchSize)
var errInBackground error

for i, userObj := range usersResponse.Users {
for i, userObj := range userResponseWithFirstAndLastName.Users {
sem <- 1

if errInBackground != nil {
return UsersGetResponse{}, errInBackground
}

go func(i int, userObj struct {
RecipeId string `json:"recipeId"`
User map[string]interface{} `json:"user"`
supertokens.User
firstName string
lastName string
}) {
defer processingGroup.Done()
userMetadataResponse, err := usermetadata.GetUserMetadata(userObj.User["id"].(string), userContext)
userMetadataResponse, err := usermetadata.GetUserMetadata(userObj.ID, userContext)
<-sem
if err != nil {
errInBackground = err
return
}
usersResponse.Users[i].User["firstName"] = userMetadataResponse["first_name"]
usersResponse.Users[i].User["lastName"] = userMetadataResponse["last_name"]
firstName, ok := userMetadataResponse["first_name"]
lastName, ok2 := userMetadataResponse["last_name"]
if ok {
userResponseWithFirstAndLastName.Users[i].firstName = firstName.(string)
}
if ok2 {
userResponseWithFirstAndLastName.Users[i].lastName = lastName.(string)
}
}(i, userObj)
}

Expand All @@ -140,50 +159,7 @@ func UsersGet(apiImplementation dashboardmodels.APIInterface, tenantId string, o

return UsersGetResponse{
Status: "OK",
NextPaginationToken: usersResponse.NextPaginationToken,
Users: getUsersTypeFromPaginationResult(usersResponse),
NextPaginationToken: userResponseWithFirstAndLastName.NextPaginationToken,
Users: userResponseWithFirstAndLastName.Users,
}, nil
}

func getUsersTypeFromPaginationResult(usersResponse supertokens.UserPaginationResult) []Users {
users := []Users{}
for _, v := range usersResponse.Users {
user := User{
Id: v.User["id"].(string),
TimeJoined: v.User["timeJoined"].(float64),
}
firstName := v.User["firstName"]
if firstName != nil {
user.FirstName = firstName.(string)
}
lastName := v.User["lastName"]
if lastName != nil {
user.LastName = lastName.(string)
}

if v.RecipeId == "emailpassword" {
user.Email = v.User["email"].(string)
} else if v.RecipeId == "thirdparty" {
user.Email = v.User["email"].(string)
user.ThirdParty = dashboardmodels.ThirdParty{
Id: v.User["thirdParty"].(map[string]interface{})["id"].(string),
UserId: v.User["thirdParty"].(map[string]interface{})["userId"].(string),
}
} else {
email := v.User["email"]
if email != nil {
user.Email = email.(string)
}
phoneNumber := v.User["phoneNumber"]
if phoneNumber != nil {
user.PhoneNumber = phoneNumber.(string)
}
}

users = append(users, Users{
RecipeId: v.RecipeId,
User: user,
})
}
return users
}
13 changes: 7 additions & 6 deletions recipe/dashboard/userGet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ package dashboard

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/supertokens/supertokens-golang/recipe/dashboard/api"
"github.com/supertokens/supertokens-golang/recipe/dashboard/api/userdetails"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
"github.com/supertokens/supertokens-golang/recipe/thirdpartypasswordless"
"github.com/supertokens/supertokens-golang/recipe/thirdpartypasswordless/tplmodels"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/supertokens/supertokens-golang/recipe/dashboard/dashboardmodels"
Expand Down Expand Up @@ -145,7 +146,7 @@ func TestThatUserGetReturnsValidUserForThirdPartyUserWhenUsingThirdPartyPassword

user := listResponse.Users[0].User

req, err = http.NewRequest(http.MethodGet, testServer.URL+"/auth/dashboard/api/user?userId="+user.Id+"&recipeId=thirdparty", strings.NewReader(`{}`))
req, err = http.NewRequest(http.MethodGet, testServer.URL+"/auth/dashboard/api/user?userId="+user.ID+"&recipeId=thirdparty", strings.NewReader(`{}`))
req.Header.Set("Authorization", "Bearer testapikey")
res, err = http.DefaultClient.Do(req)

Expand Down
Loading