fix(profile): stabilize identity binding management
This commit is contained in:
@@ -270,18 +270,19 @@ func TestUserHandlerGetProfileReturnsLegacyCompatibilityFields(t *testing.T) {
|
||||
AvatarURL: "https://cdn.example.com/linuxdo.png",
|
||||
AvatarSource: "remote_url",
|
||||
},
|
||||
identities: []service.UserAuthIdentityRecord{
|
||||
{
|
||||
ProviderType: "linuxdo",
|
||||
ProviderKey: "linuxdo",
|
||||
ProviderSubject: "linuxdo-subject-21",
|
||||
VerifiedAt: &verifiedAt,
|
||||
Metadata: map[string]any{
|
||||
"username": "linuxdo-handle",
|
||||
identities: []service.UserAuthIdentityRecord{
|
||||
{
|
||||
ProviderType: "linuxdo",
|
||||
ProviderKey: "linuxdo",
|
||||
ProviderSubject: "linuxdo-subject-21",
|
||||
VerifiedAt: &verifiedAt,
|
||||
Metadata: map[string]any{
|
||||
"username": "linuxdo-handle",
|
||||
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
handler := NewUserHandler(service.NewUserService(repo, nil, nil, nil), nil, nil, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
@@ -331,10 +332,102 @@ func TestUserHandlerGetProfileReturnsLegacyCompatibilityFields(t *testing.T) {
|
||||
require.Equal(t, "linuxdo", usernameSource["source"])
|
||||
}
|
||||
|
||||
func TestUserHandlerGetProfileDoesNotInferEditedProfileSourcesWithoutMatchingIdentityMetadata(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
repo := &userHandlerRepoStub{
|
||||
user: &service.User{
|
||||
ID: 22,
|
||||
Email: "edited-profile@example.com",
|
||||
Username: "custom-name",
|
||||
Role: service.RoleUser,
|
||||
Status: service.StatusActive,
|
||||
AvatarURL: "https://cdn.example.com/custom.png",
|
||||
AvatarSource: "remote_url",
|
||||
},
|
||||
identities: []service.UserAuthIdentityRecord{
|
||||
{
|
||||
ProviderType: "linuxdo",
|
||||
ProviderKey: "linuxdo",
|
||||
ProviderSubject: "linuxdo-subject-22",
|
||||
Metadata: map[string]any{
|
||||
"username": "linuxdo-handle",
|
||||
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
handler := NewUserHandler(service.NewUserService(repo, nil, nil, nil), nil, nil, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/user/profile", nil)
|
||||
c.Set(string(middleware2.ContextKeyUser), middleware2.AuthSubject{UserID: 22})
|
||||
|
||||
handler.GetProfile(c)
|
||||
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &resp))
|
||||
require.Equal(t, 0, resp.Code)
|
||||
require.NotContains(t, resp.Data, "avatar_source")
|
||||
require.NotContains(t, resp.Data, "username_source")
|
||||
require.NotContains(t, resp.Data, "profile_sources")
|
||||
}
|
||||
|
||||
type userHandlerEmailCacheStub struct {
|
||||
data *service.VerificationCodeData
|
||||
}
|
||||
|
||||
type userHandlerRefreshTokenCacheStub struct {
|
||||
revokedUserIDs []int64
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) StoreRefreshToken(context.Context, string, *service.RefreshTokenData, time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) GetRefreshToken(context.Context, string) (*service.RefreshTokenData, error) {
|
||||
return nil, service.ErrRefreshTokenNotFound
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) DeleteRefreshToken(context.Context, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) DeleteUserRefreshTokens(_ context.Context, userID int64) error {
|
||||
s.revokedUserIDs = append(s.revokedUserIDs, userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) DeleteTokenFamily(context.Context, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) AddToUserTokenSet(context.Context, int64, string, time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) AddToFamilyTokenSet(context.Context, string, string, time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) GetUserTokenHashes(context.Context, int64) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) GetFamilyTokenHashes(context.Context, string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *userHandlerRefreshTokenCacheStub) IsTokenInFamily(context.Context, string, string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *userHandlerEmailCacheStub) GetVerificationCode(context.Context, string) (*service.VerificationCodeData, error) {
|
||||
return s.data, nil
|
||||
}
|
||||
@@ -495,6 +588,52 @@ func TestUserHandlerUnbindIdentityReturnsUpdatedProfile(t *testing.T) {
|
||||
require.Equal(t, false, linuxdoBinding["bound"])
|
||||
}
|
||||
|
||||
func TestUserHandlerUnbindIdentityRevokesAllUserSessionsWhenAuthServiceConfigured(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
repo := &userHandlerRepoStub{
|
||||
user: &service.User{
|
||||
ID: 23,
|
||||
Email: "identity@example.com",
|
||||
Username: "identity-user",
|
||||
Role: service.RoleUser,
|
||||
Status: service.StatusActive,
|
||||
},
|
||||
identities: []service.UserAuthIdentityRecord{
|
||||
{
|
||||
ProviderType: "email",
|
||||
ProviderKey: "email",
|
||||
ProviderSubject: "identity@example.com",
|
||||
},
|
||||
{
|
||||
ProviderType: "linuxdo",
|
||||
ProviderKey: "linuxdo",
|
||||
ProviderSubject: "linuxdo-subject-23",
|
||||
},
|
||||
},
|
||||
}
|
||||
refreshTokenCache := &userHandlerRefreshTokenCacheStub{}
|
||||
cfg := &config.Config{
|
||||
JWT: config.JWTConfig{
|
||||
Secret: "test-secret",
|
||||
ExpireHour: 1,
|
||||
},
|
||||
}
|
||||
authService := service.NewAuthService(nil, repo, nil, refreshTokenCache, cfg, nil, nil, nil, nil, nil, nil)
|
||||
handler := NewUserHandler(service.NewUserService(repo, nil, nil, nil), authService, nil, nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
c.Request = httptest.NewRequest(http.MethodDelete, "/api/v1/user/account-bindings/linuxdo", nil)
|
||||
c.Set(string(middleware2.ContextKeyUser), middleware2.AuthSubject{UserID: 23})
|
||||
c.Params = gin.Params{{Key: "provider", Value: "linuxdo"}}
|
||||
|
||||
handler.UnbindIdentity(c)
|
||||
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
require.Equal(t, []int64{23}, refreshTokenCache.revokedUserIDs)
|
||||
}
|
||||
|
||||
func TestUserHandlerBindEmailIdentityRejectsWrongCurrentPasswordForBoundEmail(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user