fix: sync remote authorization revoke logout
This commit is contained in:
parent
b4aaf57d6b
commit
0dcfaf0a65
11 changed files with 216 additions and 142 deletions
|
|
@ -593,7 +593,8 @@ func (r *Router) onAccountResetAuthorization(ctx context.Context, hash int64) (b
|
|||
}
|
||||
r.revokeAuthKeySessions(deleted.AuthKeyID)
|
||||
_ = r.clearAuthKeyState(ctx, deleted.AuthKeyID)
|
||||
// P1 修复:撤销该会话销毁其 auth_key,级联 discard 该设备绑定的活跃密聊并通知对端。
|
||||
// 撤销该设备的业务授权后,级联 discard 其绑定的活跃密聊并通知对端。
|
||||
// 协议 auth key 必须保留,供客户端重连后取得 AUTH_KEY_UNREGISTERED。
|
||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(deleted.AuthKeyID), userID)
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -594,7 +594,8 @@ func (r *Router) onAuthResetAuthorizations(ctx context.Context) (bool, error) {
|
|||
for _, a := range deleted {
|
||||
r.revokeAuthKeySessions(a.AuthKeyID)
|
||||
_ = r.clearAuthKeyState(ctx, a.AuthKeyID)
|
||||
// P1 修复:撤销其它会话同样销毁其 auth_key,级联 discard 该设备绑定的活跃密聊并通知对端。
|
||||
// 撤销其它会话会删除其业务 authorization;协议 key 保留用于让客户端
|
||||
// 重连后取得 AUTH_KEY_UNREGISTERED。密聊仍按设备授权边界 discard 并通知对端。
|
||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(a.AuthKeyID), userID)
|
||||
}
|
||||
return true, nil
|
||||
|
|
@ -865,8 +866,8 @@ func (r *Router) onAuthLogOut(ctx context.Context) (*tg.AuthLoggedOut, error) {
|
|||
r.presence.clearSession(key)
|
||||
}
|
||||
}
|
||||
// P1 修复:登出销毁本设备 perm auth_key 后,级联 discard 其绑定的活跃密聊并通知对端
|
||||
//(否则对端继续往死 auth_key 投递成静默死链)。best-effort,不阻断登出。
|
||||
// 登出撤销本设备 authorization 后,级联 discard 其绑定的活跃密聊并通知对端
|
||||
//(否则对端继续往已退出设备投递成静默死链)。best-effort,不阻断登出。
|
||||
if userErr == nil && userID != 0 {
|
||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(id), userID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ import "github.com/iamxvbaba/td/tg"
|
|||
func rpcAllowedWithoutAuthorization(id uint32) bool {
|
||||
switch id {
|
||||
case tg.AuthBindTempAuthKeyRequestTypeID,
|
||||
// TWeb handles a 401 from a remotely revoked session by sending
|
||||
// auth.logOut before it clears IndexedDB/local authorization state.
|
||||
// This cleanup RPC is idempotent when no authorization remains; rejecting
|
||||
// it with another 401 makes Web repeat its startup/logout cycle forever.
|
||||
tg.AuthLogOutRequestTypeID,
|
||||
tg.AuthExportLoginTokenRequestTypeID,
|
||||
tg.AuthImportLoginTokenRequestTypeID,
|
||||
tg.AuthAcceptLoginTokenRequestTypeID,
|
||||
|
|
|
|||
112
internal/rpc/remote_authorization_revoke_test.go
Normal file
112
internal/rpc/remote_authorization_revoke_test.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/iamxvbaba/td/bin"
|
||||
"github.com/iamxvbaba/td/clock"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"github.com/iamxvbaba/td/tgerr"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
appauth "telesrv/internal/app/auth"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
func TestAccountResetAuthorizationKeepsProtocolKeyAndReturnsRPC401(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
currentAuthKeyID := [8]byte{0x71}
|
||||
targetAuthKeyID := [8]byte{0x72}
|
||||
const (
|
||||
userID = int64(1000000001)
|
||||
targetHash = int64(2026072401)
|
||||
)
|
||||
|
||||
authKeys := memory.NewAuthKeyStore()
|
||||
authorizations := memory.NewAuthorizationStore()
|
||||
for _, authKeyID := range [][8]byte{currentAuthKeyID, targetAuthKeyID} {
|
||||
if err := authKeys.Save(ctx, store.AuthKeyData{ID: authKeyID}); err != nil {
|
||||
t.Fatalf("save auth key %x: %v", authKeyID, err)
|
||||
}
|
||||
}
|
||||
authService := appauth.NewService(nil, authorizations, nil, authKeys, nil, "12345")
|
||||
if err := authorizations.Bind(ctx, domain.Authorization{
|
||||
AuthKeyID: currentAuthKeyID,
|
||||
UserID: userID,
|
||||
Hash: 2026072400,
|
||||
}); err != nil {
|
||||
t.Fatalf("bind current authorization: %v", err)
|
||||
}
|
||||
if err := authorizations.Bind(ctx, domain.Authorization{
|
||||
AuthKeyID: targetAuthKeyID,
|
||||
UserID: userID,
|
||||
Hash: targetHash,
|
||||
}); err != nil {
|
||||
t.Fatalf("bind target authorization: %v", err)
|
||||
}
|
||||
|
||||
r := New(Config{}, Deps{
|
||||
Auth: authService,
|
||||
Files: &fakeFiles{},
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
|
||||
var warmTarget bin.Buffer
|
||||
if err := (&tg.UploadSaveFilePartRequest{
|
||||
FileID: 1,
|
||||
FilePart: 0,
|
||||
Bytes: []byte{1},
|
||||
}).Encode(&warmTarget); err != nil {
|
||||
t.Fatalf("encode target warm-up RPC: %v", err)
|
||||
}
|
||||
if _, err := r.Dispatch(ctx, targetAuthKeyID, 101, &warmTarget); err != nil {
|
||||
t.Fatalf("target warm-up RPC: %v", err)
|
||||
}
|
||||
|
||||
var reset bin.Buffer
|
||||
if err := (&tg.AccountResetAuthorizationRequest{Hash: targetHash}).Encode(&reset); err != nil {
|
||||
t.Fatalf("encode account.resetAuthorization: %v", err)
|
||||
}
|
||||
if result, err := r.Dispatch(ctx, currentAuthKeyID, 102, &reset); err != nil {
|
||||
t.Fatalf("account.resetAuthorization: %v", err)
|
||||
} else if value, ok := dispatchCanonicalValue(result).(bool); !ok || !value {
|
||||
t.Fatalf("account.resetAuthorization result = %#v, want true", dispatchCanonicalValue(result))
|
||||
}
|
||||
|
||||
if _, found, err := authKeys.Get(ctx, targetAuthKeyID); err != nil || !found {
|
||||
t.Fatalf("target protocol auth key found=%v err=%v, want retained", found, err)
|
||||
}
|
||||
if _, found, err := authorizations.ByAuthKey(ctx, targetAuthKeyID); err != nil || found {
|
||||
t.Fatalf("target business authorization found=%v err=%v, want removed", found, err)
|
||||
}
|
||||
if current, found, err := authorizations.ByAuthKey(ctx, currentAuthKeyID); err != nil || !found || current.UserID != userID {
|
||||
t.Fatalf("current authorization=%+v found=%v err=%v, want retained user %d", current, found, err, userID)
|
||||
}
|
||||
|
||||
var afterRevoke bin.Buffer
|
||||
if err := (&tg.UploadSaveFilePartRequest{
|
||||
FileID: 1,
|
||||
FilePart: 1,
|
||||
Bytes: []byte{2},
|
||||
}).Encode(&afterRevoke); err != nil {
|
||||
t.Fatalf("encode target post-revoke RPC: %v", err)
|
||||
}
|
||||
if _, err := r.Dispatch(ctx, targetAuthKeyID, 103, &afterRevoke); !tgerr.Is(err, "AUTH_KEY_UNREGISTERED") {
|
||||
t.Fatalf("target post-revoke RPC err=%v, want AUTH_KEY_UNREGISTERED", err)
|
||||
}
|
||||
|
||||
var logout bin.Buffer
|
||||
if err := (&tg.AuthLogOutRequest{}).Encode(&logout); err != nil {
|
||||
t.Fatalf("encode target auth.logOut cleanup: %v", err)
|
||||
}
|
||||
if result, err := r.Dispatch(ctx, targetAuthKeyID, 104, &logout); err != nil {
|
||||
t.Fatalf("target auth.logOut cleanup: %v", err)
|
||||
} else if _, ok := dispatchCanonicalValue(result).(*tg.AuthLoggedOut); !ok {
|
||||
t.Fatalf("target auth.logOut cleanup result=%#v, want *tg.AuthLoggedOut", dispatchCanonicalValue(result))
|
||||
}
|
||||
if _, found, err := authKeys.Get(ctx, targetAuthKeyID); err != nil || !found {
|
||||
t.Fatalf("target protocol auth key after logout cleanup found=%v err=%v, want retained", found, err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue