fix: recover stale IM member credentials
This commit is contained in:
@@ -381,3 +381,11 @@ func IsConflict(err error) bool {
|
|||||||
var target *Error
|
var target *Error
|
||||||
return errors.As(err, &target) && target.Status == http.StatusConflict
|
return errors.As(err, &target) && target.Status == http.StatusConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAuthenticationFailed reports whether the IM server rejected user
|
||||||
|
// credentials. Callers may use this to repair credentials which have drifted
|
||||||
|
// from the local encrypted copy, while leaving transport failures untouched.
|
||||||
|
func IsAuthenticationFailed(err error) bool {
|
||||||
|
var target *Error
|
||||||
|
return errors.As(err, &target) && target.Status == http.StatusUnauthorized
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,6 +102,17 @@ func (s *Service) OpenSession(ctx context.Context, tenantID, memberID, serviceID
|
|||||||
return nil, fmt.Errorf("decrypt IM credential: %w", err)
|
return nil, fmt.Errorf("decrypt IM credential: %w", err)
|
||||||
}
|
}
|
||||||
imSession, err := s.im.LoginBasic(ctx, identity.IMUsername, password, strings.TrimSpace(in.DeviceID), platform)
|
imSession, err := s.im.LoginBasic(ctx, identity.IMUsername, password, strings.TrimSpace(in.DeviceID), platform)
|
||||||
|
if imclient.IsAuthenticationFailed(err) {
|
||||||
|
identity, err = s.resetAndSaveCredential(ctx, identity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("repair IM credential: %w", err)
|
||||||
|
}
|
||||||
|
password, err = s.decryptCredential(identity.IMPasswordCipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypt repaired IM credential: %w", err)
|
||||||
|
}
|
||||||
|
imSession, err = s.im.LoginBasic(ctx, identity.IMUsername, password, strings.TrimSpace(in.DeviceID), platform)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("login IM session: %w", err)
|
return nil, fmt.Errorf("login IM session: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ func (r *repositoryStub) SaveMemberIMIdentity(context.Context, *store.MemberIMId
|
|||||||
|
|
||||||
type imStub struct {
|
type imStub struct {
|
||||||
friendRequests int
|
friendRequests int
|
||||||
|
loginAttempts int
|
||||||
|
resetPasswords int
|
||||||
|
loginError error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imStub) Ready() bool { return true }
|
func (i *imStub) Ready() bool { return true }
|
||||||
@@ -45,9 +48,18 @@ func (i *imStub) FindUserByUsername(context.Context, string) (*imclient.CreatedU
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (i *imStub) LoginBasic(context.Context, string, string, string, string) (*imclient.Session, error) {
|
func (i *imStub) LoginBasic(context.Context, string, string, string, string) (*imclient.Session, error) {
|
||||||
|
i.loginAttempts++
|
||||||
|
if i.loginError != nil {
|
||||||
|
err := i.loginError
|
||||||
|
i.loginError = nil
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &imclient.Session{UserID: "usr_member", Token: "token", ExpiresAt: time.Now().Add(time.Hour)}, nil
|
return &imclient.Session{UserID: "usr_member", Token: "token", ExpiresAt: time.Now().Add(time.Hour)}, nil
|
||||||
}
|
}
|
||||||
func (i *imStub) ResetPassword(context.Context, string, string) error { return nil }
|
func (i *imStub) ResetPassword(context.Context, string, string) error {
|
||||||
|
i.resetPasswords++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (i *imStub) UserProfile(context.Context, string, string) (*imclient.UserProfile, error) {
|
func (i *imStub) UserProfile(context.Context, string, string) (*imclient.UserProfile, error) {
|
||||||
return &imclient.UserProfile{UserID: "usr_agent", Relationship: "none"}, nil
|
return &imclient.UserProfile{UserID: "usr_agent", Relationship: "none"}, nil
|
||||||
}
|
}
|
||||||
@@ -79,6 +91,27 @@ func TestOpenSessionAutoAcceptsAndReturnsTopic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenSessionRepairsDriftedIMCredential(t *testing.T) {
|
||||||
|
repository := &repositoryStub{
|
||||||
|
agent: &store.CustomerService{ID: 3, Name: "客服01", IMTenantCode: "allrich", IMUserID: "usr_agent", OnlineStatus: 1},
|
||||||
|
identity: &store.MemberIMIdentity{TenantID: 0, MemberID: 9, IMTenantCode: "allrich", IMUserID: "usr_member", IMUsername: "member01"},
|
||||||
|
}
|
||||||
|
im := &imStub{loginError: &imclient.Error{Status: 401, Reason: "authentication failed"}}
|
||||||
|
service := New(repository, im, config.AuthConfig{FinanceMasterKey: strings.Repeat("k", 32)})
|
||||||
|
repository.identity.IMPasswordCipher, _ = service.encryptCredential("stale-password")
|
||||||
|
|
||||||
|
session, err := service.OpenSession(context.Background(), 0, 9, 3, SessionRequest{DeviceID: "web-1", Platform: "web"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if session.Topic != "p2p-topic" {
|
||||||
|
t.Fatalf("unexpected session: %+v", session)
|
||||||
|
}
|
||||||
|
if im.loginAttempts != 2 || im.resetPasswords != 1 {
|
||||||
|
t.Fatalf("login attempts=%d reset passwords=%d, want 2 and 1", im.loginAttempts, im.resetPasswords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMemberIMUsernameMatchesIMConstraint(t *testing.T) {
|
func TestMemberIMUsernameMatchesIMConstraint(t *testing.T) {
|
||||||
username := memberIMUsername(0, 9223372036854775807, "allrich")
|
username := memberIMUsername(0, 9223372036854775807, "allrich")
|
||||||
if len(username) != 10 {
|
if len(username) != 10 {
|
||||||
|
|||||||
Reference in New Issue
Block a user