fix: sync remote authorization revoke logout
This commit is contained in:
parent
b4aaf57d6b
commit
0dcfaf0a65
11 changed files with 216 additions and 142 deletions
|
|
@ -1328,18 +1328,7 @@ func (s *Service) ResetAuthorization(ctx context.Context, userID, hash int64) (d
|
||||||
if revoker, ok := s.auths.(authorizationRevoker); ok {
|
if revoker, ok := s.auths.(authorizationRevoker); ok {
|
||||||
return revoker.RevokeByHash(ctx, userID, hash)
|
return revoker.RevokeByHash(ctx, userID, hash)
|
||||||
}
|
}
|
||||||
target, found, err := s.authorizationByHash(ctx, userID, hash)
|
return s.auths.DeleteByHash(ctx, userID, hash)
|
||||||
if err != nil || !found {
|
|
||||||
return target, found, err
|
|
||||||
}
|
|
||||||
if err := s.deleteAuthKey(ctx, target.AuthKeyID); err != nil {
|
|
||||||
return target, true, err
|
|
||||||
}
|
|
||||||
deleted, found, err := s.auths.DeleteByHash(ctx, userID, hash)
|
|
||||||
if err != nil || !found {
|
|
||||||
return deleted, found, err
|
|
||||||
}
|
|
||||||
return deleted, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ResetAuthorizations(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
func (s *Service) ResetAuthorizations(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
||||||
|
|
@ -1349,54 +1338,7 @@ func (s *Service) ResetAuthorizations(ctx context.Context, userID int64, keepAut
|
||||||
if revoker, ok := s.auths.(authorizationRevoker); ok {
|
if revoker, ok := s.auths.(authorizationRevoker); ok {
|
||||||
return revoker.RevokeByUserExcept(ctx, userID, keepAuthKeyID)
|
return revoker.RevokeByUserExcept(ctx, userID, keepAuthKeyID)
|
||||||
}
|
}
|
||||||
targets, err := s.authorizationsByUserExcept(ctx, userID, keepAuthKeyID)
|
return s.auths.DeleteByUserExcept(ctx, userID, keepAuthKeyID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, a := range targets {
|
|
||||||
if err := s.deleteAuthKey(ctx, a.AuthKeyID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deleted, err := s.auths.DeleteByUserExcept(ctx, userID, keepAuthKeyID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return deleted, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) deleteAuthKey(ctx context.Context, authKeyID [8]byte) error {
|
|
||||||
if s == nil || s.authKeys == nil || authKeyID == ([8]byte{}) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.authKeys.Delete(ctx, authKeyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) authorizationByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error) {
|
|
||||||
items, err := s.auths.ListByUser(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return domain.Authorization{}, false, err
|
|
||||||
}
|
|
||||||
for _, a := range items {
|
|
||||||
if a.Hash == hash {
|
|
||||||
return a, true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domain.Authorization{}, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) authorizationsByUserExcept(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
|
||||||
items, err := s.auths.ListByUser(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out := make([]domain.Authorization, 0, len(items))
|
|
||||||
for _, a := range items {
|
|
||||||
if a.AuthKeyID != keepAuthKeyID {
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) bind(ctx context.Context, auth domain.Authorization, userID int64) error {
|
func (s *Service) bind(ctx context.Context, auth domain.Authorization, userID int64) error {
|
||||||
|
|
|
||||||
|
|
@ -535,7 +535,7 @@ func TestLogOutThenSignInSameAuthKeySwitchesUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetAuthorizationDeletesProtocolAuthKey(t *testing.T) {
|
func TestResetAuthorizationKeepsProtocolAuthKeyForRPCLogout(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
authz := memory.NewAuthorizationStore()
|
authz := memory.NewAuthorizationStore()
|
||||||
keys := memory.NewAuthKeyStore()
|
keys := memory.NewAuthKeyStore()
|
||||||
|
|
@ -562,15 +562,15 @@ func TestResetAuthorizationDeletesProtocolAuthKey(t *testing.T) {
|
||||||
if err != nil || !found || deleted.AuthKeyID != key {
|
if err != nil || !found || deleted.AuthKeyID != key {
|
||||||
t.Fatalf("ResetAuthorization deleted=%x found=%v err=%v, want key %x", deleted.AuthKeyID, found, err, key)
|
t.Fatalf("ResetAuthorization deleted=%x found=%v err=%v, want key %x", deleted.AuthKeyID, found, err, key)
|
||||||
}
|
}
|
||||||
if _, found, err := keys.Get(ctx, key); err != nil || found {
|
if _, found, err := keys.Get(ctx, key); err != nil || !found {
|
||||||
t.Fatalf("auth key after reset found=%v err=%v, want missing", found, err)
|
t.Fatalf("auth key after reset found=%v err=%v, want present for RPC 401", found, err)
|
||||||
}
|
}
|
||||||
if _, found, err := svc.UserID(ctx, key); err != nil || found {
|
if _, found, err := svc.UserID(ctx, key); err != nil || found {
|
||||||
t.Fatalf("user after reset found=%v err=%v, want missing", found, err)
|
t.Fatalf("user after reset found=%v err=%v, want missing", found, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetAuthorizationsDeletesOnlyRevokedProtocolAuthKeys(t *testing.T) {
|
func TestResetAuthorizationsKeepsRevokedProtocolAuthKeys(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
authz := memory.NewAuthorizationStore()
|
authz := memory.NewAuthorizationStore()
|
||||||
keys := memory.NewAuthKeyStore()
|
keys := memory.NewAuthKeyStore()
|
||||||
|
|
@ -600,12 +600,18 @@ func TestResetAuthorizationsDeletesOnlyRevokedProtocolAuthKeys(t *testing.T) {
|
||||||
if err != nil || len(deleted) != 1 || deleted[0].AuthKeyID != revoked {
|
if err != nil || len(deleted) != 1 || deleted[0].AuthKeyID != revoked {
|
||||||
t.Fatalf("ResetAuthorizations deleted=%v err=%v, want revoked key", deleted, err)
|
t.Fatalf("ResetAuthorizations deleted=%v err=%v, want revoked key", deleted, err)
|
||||||
}
|
}
|
||||||
if _, found, err := keys.Get(ctx, revoked); err != nil || found {
|
if _, found, err := keys.Get(ctx, revoked); err != nil || !found {
|
||||||
t.Fatalf("revoked auth key found=%v err=%v, want missing", found, err)
|
t.Fatalf("revoked auth key found=%v err=%v, want present for RPC 401", found, err)
|
||||||
}
|
}
|
||||||
if _, found, err := keys.Get(ctx, keep); err != nil || !found {
|
if _, found, err := keys.Get(ctx, keep); err != nil || !found {
|
||||||
t.Fatalf("kept auth key found=%v err=%v, want present", found, err)
|
t.Fatalf("kept auth key found=%v err=%v, want present", found, err)
|
||||||
}
|
}
|
||||||
|
if _, found, err := svc.UserID(ctx, revoked); err != nil || found {
|
||||||
|
t.Fatalf("revoked user found=%v err=%v, want missing", found, err)
|
||||||
|
}
|
||||||
|
if got, found, err := svc.UserID(ctx, keep); err != nil || !found || got != u.ID {
|
||||||
|
t.Fatalf("kept user=%d found=%v err=%v, want %d", got, found, err, u.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignUpWritesOfficialLoginMessage(t *testing.T) {
|
func TestSignUpWritesOfficialLoginMessage(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -593,7 +593,8 @@ func (r *Router) onAccountResetAuthorization(ctx context.Context, hash int64) (b
|
||||||
}
|
}
|
||||||
r.revokeAuthKeySessions(deleted.AuthKeyID)
|
r.revokeAuthKeySessions(deleted.AuthKeyID)
|
||||||
_ = r.clearAuthKeyState(ctx, deleted.AuthKeyID)
|
_ = r.clearAuthKeyState(ctx, deleted.AuthKeyID)
|
||||||
// P1 修复:撤销该会话销毁其 auth_key,级联 discard 该设备绑定的活跃密聊并通知对端。
|
// 撤销该设备的业务授权后,级联 discard 其绑定的活跃密聊并通知对端。
|
||||||
|
// 协议 auth key 必须保留,供客户端重连后取得 AUTH_KEY_UNREGISTERED。
|
||||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(deleted.AuthKeyID), userID)
|
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(deleted.AuthKeyID), userID)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,8 @@ func (r *Router) onAuthResetAuthorizations(ctx context.Context) (bool, error) {
|
||||||
for _, a := range deleted {
|
for _, a := range deleted {
|
||||||
r.revokeAuthKeySessions(a.AuthKeyID)
|
r.revokeAuthKeySessions(a.AuthKeyID)
|
||||||
_ = r.clearAuthKeyState(ctx, a.AuthKeyID)
|
_ = r.clearAuthKeyState(ctx, a.AuthKeyID)
|
||||||
// P1 修复:撤销其它会话同样销毁其 auth_key,级联 discard 该设备绑定的活跃密聊并通知对端。
|
// 撤销其它会话会删除其业务 authorization;协议 key 保留用于让客户端
|
||||||
|
// 重连后取得 AUTH_KEY_UNREGISTERED。密聊仍按设备授权边界 discard 并通知对端。
|
||||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(a.AuthKeyID), userID)
|
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(a.AuthKeyID), userID)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
@ -865,8 +866,8 @@ func (r *Router) onAuthLogOut(ctx context.Context) (*tg.AuthLoggedOut, error) {
|
||||||
r.presence.clearSession(key)
|
r.presence.clearSession(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// P1 修复:登出销毁本设备 perm auth_key 后,级联 discard 其绑定的活跃密聊并通知对端
|
// 登出撤销本设备 authorization 后,级联 discard 其绑定的活跃密聊并通知对端
|
||||||
//(否则对端继续往死 auth_key 投递成静默死链)。best-effort,不阻断登出。
|
//(否则对端继续往已退出设备投递成静默死链)。best-effort,不阻断登出。
|
||||||
if userErr == nil && userID != 0 {
|
if userErr == nil && userID != 0 {
|
||||||
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(id), userID)
|
r.discardSecretChatsForAuthKey(ctx, businessAuthKeyInt64(id), userID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ import "github.com/iamxvbaba/td/tg"
|
||||||
func rpcAllowedWithoutAuthorization(id uint32) bool {
|
func rpcAllowedWithoutAuthorization(id uint32) bool {
|
||||||
switch id {
|
switch id {
|
||||||
case tg.AuthBindTempAuthKeyRequestTypeID,
|
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.AuthExportLoginTokenRequestTypeID,
|
||||||
tg.AuthImportLoginTokenRequestTypeID,
|
tg.AuthImportLoginTokenRequestTypeID,
|
||||||
tg.AuthAcceptLoginTokenRequestTypeID,
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -461,8 +461,9 @@ func (s *AuthorizationStore) DeleteByHash(_ context.Context, userID, hash int64)
|
||||||
return domain.Authorization{}, false, nil
|
return domain.Authorization{}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeByHash mirrors PostgreSQL's protocol-key revocation boundary when this
|
// RevokeByHash removes only the business authorization. The protocol auth key
|
||||||
// authorization projection is linked to an in-memory auth-key authority.
|
// and any temp binding stay usable for MTProto decryption so a kicked client can
|
||||||
|
// reconnect and receive AUTH_KEY_UNREGISTERED from the RPC gate.
|
||||||
func (s *AuthorizationStore) RevokeByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error) {
|
func (s *AuthorizationStore) RevokeByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error) {
|
||||||
s.linkMu.RLock()
|
s.linkMu.RLock()
|
||||||
defer s.linkMu.RUnlock()
|
defer s.linkMu.RUnlock()
|
||||||
|
|
@ -471,27 +472,17 @@ func (s *AuthorizationStore) RevokeByHash(ctx context.Context, userID, hash int6
|
||||||
}
|
}
|
||||||
s.authKeys.mu.Lock()
|
s.authKeys.mu.Lock()
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
var (
|
|
||||||
targetID [8]byte
|
|
||||||
target domain.Authorization
|
|
||||||
found bool
|
|
||||||
)
|
|
||||||
for id, a := range s.m {
|
for id, a := range s.m {
|
||||||
if a.UserID == userID && a.Hash == hash {
|
if a.UserID == userID && a.Hash == hash {
|
||||||
targetID, target, found = id, a, true
|
delete(s.m, id)
|
||||||
break
|
s.mu.Unlock()
|
||||||
|
s.authKeys.mu.Unlock()
|
||||||
|
return a, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
|
||||||
s.mu.Unlock()
|
|
||||||
s.authKeys.mu.Unlock()
|
|
||||||
return domain.Authorization{}, false, nil
|
|
||||||
}
|
|
||||||
deletedIDs := s.authKeys.deleteProtocolAuthKeyLocked(targetID)
|
|
||||||
s.authKeys.deleteAuthorizationMirrorsWithHeldLocked(deletedIDs, s)
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.authKeys.mu.Unlock()
|
s.authKeys.mu.Unlock()
|
||||||
return target, true, nil
|
return domain.Authorization{}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthorizationStore) DeleteByUserExcept(_ context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
func (s *AuthorizationStore) DeleteByUserExcept(_ context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
||||||
|
|
@ -517,18 +508,12 @@ func (s *AuthorizationStore) RevokeByUserExcept(ctx context.Context, userID int6
|
||||||
s.authKeys.mu.Lock()
|
s.authKeys.mu.Lock()
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
out := make([]domain.Authorization, 0)
|
out := make([]domain.Authorization, 0)
|
||||||
targets := make([][8]byte, 0)
|
|
||||||
for id, a := range s.m {
|
for id, a := range s.m {
|
||||||
if a.UserID == userID && id != keepAuthKeyID {
|
if a.UserID == userID && id != keepAuthKeyID {
|
||||||
out = append(out, a)
|
out = append(out, a)
|
||||||
targets = append(targets, id)
|
delete(s.m, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deletedIDs := make([][8]byte, 0, len(targets))
|
|
||||||
for _, id := range targets {
|
|
||||||
deletedIDs = append(deletedIDs, s.authKeys.deleteProtocolAuthKeyLocked(id)...)
|
|
||||||
}
|
|
||||||
s.authKeys.deleteAuthorizationMirrorsWithHeldLocked(deletedIDs, s)
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.authKeys.mu.Unlock()
|
s.authKeys.mu.Unlock()
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,10 @@ FROM authorizations WHERE auth_key_id = $1 AND user_id = $2 FOR UPDATE`, id, use
|
||||||
if !found {
|
if !found {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if err := deleteRevocationTargetsTx(ctx, tx, []int64{id}); err != nil {
|
// Cancelling a pending account deletion deliberately retires the requester
|
||||||
|
// protocol identity; unlike remote device revocation, this path does not need
|
||||||
|
// to preserve the key for a client-visible RPC 401 transition.
|
||||||
|
if err := deleteProtocolAuthIdentitiesTx(ctx, tx, []int64{id}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return []domain.Authorization{a}, nil
|
return []domain.Authorization{a}, nil
|
||||||
|
|
|
||||||
|
|
@ -195,8 +195,9 @@ WHERE auth_key_id = $1
|
||||||
//
|
//
|
||||||
// 同时清理把本 key 当作 perm key 的 temp auth key 行:temp_auth_key_bindings.temp_auth_key_id
|
// 同时清理把本 key 当作 perm key 的 temp auth key 行:temp_auth_key_bindings.temp_auth_key_id
|
||||||
// 侧有外键 ON DELETE CASCADE,删除 temp key 会自动清绑定;perm_auth_key_id 侧由
|
// 侧有外键 ON DELETE CASCADE,删除 temp key 会自动清绑定;perm_auth_key_id 侧由
|
||||||
// RESTRICT FK 防止悬空,因此被踢/销毁 perm key 时必须先把关联 temp key 一并删掉。否则 Web/上传连接用
|
// RESTRICT FK 防止悬空,因此显式销毁 perm key 时必须先把关联 temp key 一并删掉。
|
||||||
// raw temp key 重连时仍能进入 RPC 层,只得到 AUTH_KEY_UNREGISTERED,而不是连接层 404。
|
// 远程撤销 authorization 不得调用本方法:被踢客户端必须保留协议 key,重连进入 RPC
|
||||||
|
// 层后取得 AUTH_KEY_UNREGISTERED,而不是只收到连接层 -404。
|
||||||
func (s *AuthKeyStore) Delete(ctx context.Context, id [8]byte) error {
|
func (s *AuthKeyStore) Delete(ctx context.Context, id [8]byte) error {
|
||||||
return withAuthIdentityTx(ctx, s.db, "delete auth key", func(tx pgx.Tx) error {
|
return withAuthIdentityTx(ctx, s.db, "delete auth key", func(tx pgx.Tx) error {
|
||||||
return deleteAuthKeyTx(ctx, tx, authKeyIDToInt64(id))
|
return deleteAuthKeyTx(ctx, tx, authKeyIDToInt64(id))
|
||||||
|
|
|
||||||
|
|
@ -252,9 +252,10 @@ RETURNING auth_key_id, user_id, hash, layer, device_model, platform, system_vers
|
||||||
return a, true, nil
|
return a, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeByHash 删除协议 auth_key 作为远程踢设备的持久化事实入口。
|
// RevokeByHash 是远程踢设备的持久化事实入口:删除业务 authorization 与
|
||||||
// authorizations 通过 FK cascade 删除;update_states 没有 auth_keys FK,必须显式清理;
|
// device update state,但保留 permanent/temp 协议 key 和 binding。这样被踢客户端
|
||||||
// 关联 temp auth key 也显式删除,避免 raw temp key 重连。
|
// 重连后仍可完成 MTProto 解密,并由 RPC gate 返回 AUTH_KEY_UNREGISTERED;若先删除
|
||||||
|
// 协议 key,客户端只能收到 transport -404,无法可靠清理本地登录态。
|
||||||
func (s *AuthorizationStore) RevokeByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error) {
|
func (s *AuthorizationStore) RevokeByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error) {
|
||||||
var (
|
var (
|
||||||
a domain.Authorization
|
a domain.Authorization
|
||||||
|
|
@ -315,7 +316,7 @@ FOR UPDATE`, candidate, userID, hash))
|
||||||
if !found {
|
if !found {
|
||||||
return domain.Authorization{}, false, nil
|
return domain.Authorization{}, false, nil
|
||||||
}
|
}
|
||||||
if err := deleteRevocationTargetsTx(ctx, tx, []int64{candidate}); err != nil {
|
if err := deleteRevokedAuthorizationStateTx(ctx, tx, []int64{candidate}); err != nil {
|
||||||
return domain.Authorization{}, false, err
|
return domain.Authorization{}, false, err
|
||||||
}
|
}
|
||||||
return a, true, nil
|
return a, true, nil
|
||||||
|
|
@ -349,7 +350,8 @@ RETURNING auth_key_id, user_id, hash, layer, device_model, platform, system_vers
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeByUserExcept 批量删除协议 auth_key,保留 keepAuthKeyID 对应的当前设备。
|
// RevokeByUserExcept 批量删除业务 authorization,保留 keepAuthKeyID 对应的当前设备;
|
||||||
|
// 被撤销设备的协议 key/binding 保留,以便重连后取得 RPC 401 并完成客户端退出。
|
||||||
func (s *AuthorizationStore) RevokeByUserExcept(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
func (s *AuthorizationStore) RevokeByUserExcept(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error) {
|
||||||
var out []domain.Authorization
|
var out []domain.Authorization
|
||||||
err := withAuthIdentityTx(ctx, s.db, "revoke authorizations by user", func(tx pgx.Tx) error {
|
err := withAuthIdentityTx(ctx, s.db, "revoke authorizations by user", func(tx pgx.Tx) error {
|
||||||
|
|
@ -449,13 +451,38 @@ FOR UPDATE`, userID, keepAuthKeyID, candidates)
|
||||||
for i := range out {
|
for i := range out {
|
||||||
targets[i] = authKeyIDToInt64(out[i].AuthKeyID)
|
targets[i] = authKeyIDToInt64(out[i].AuthKeyID)
|
||||||
}
|
}
|
||||||
if err := deleteRevocationTargetsTx(ctx, tx, targets); err != nil {
|
if err := deleteRevokedAuthorizationStateTx(ctx, tx, targets); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteRevocationTargetsTx(ctx context.Context, tx pgx.Tx, authKeyIDs []int64) error {
|
func deleteRevokedAuthorizationStateTx(ctx context.Context, tx pgx.Tx, authKeyIDs []int64) error {
|
||||||
|
if len(authKeyIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := tx.Exec(ctx, `
|
||||||
|
DELETE FROM update_states
|
||||||
|
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs); err != nil {
|
||||||
|
return fmt.Errorf("delete revoked update states: %w", err)
|
||||||
|
}
|
||||||
|
tag, err := tx.Exec(ctx, `
|
||||||
|
DELETE FROM authorizations
|
||||||
|
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete revoked authorizations: %w", err)
|
||||||
|
}
|
||||||
|
if tag.RowsAffected() != int64(len(authKeyIDs)) {
|
||||||
|
return fmt.Errorf("delete revoked authorizations: deleted %d of %d locked targets", tag.RowsAffected(), len(authKeyIDs))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteProtocolAuthIdentitiesTx permanently removes permanent identities and
|
||||||
|
// their derived temp keys. Remote account.resetAuthorization/
|
||||||
|
// auth.resetAuthorizations must not use this helper: those clients need the
|
||||||
|
// protocol key long enough to reconnect and receive an RPC-level 401.
|
||||||
|
func deleteProtocolAuthIdentitiesTx(ctx context.Context, tx pgx.Tx, authKeyIDs []int64) error {
|
||||||
if len(authKeyIDs) == 0 {
|
if len(authKeyIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -466,21 +493,21 @@ WHERE auth_key_id IN (
|
||||||
FROM temp_auth_key_bindings
|
FROM temp_auth_key_bindings
|
||||||
WHERE perm_auth_key_id = ANY($1::bigint[])
|
WHERE perm_auth_key_id = ANY($1::bigint[])
|
||||||
)`, authKeyIDs); err != nil {
|
)`, authKeyIDs); err != nil {
|
||||||
return fmt.Errorf("delete revoked temporary auth keys: %w", err)
|
return fmt.Errorf("delete temporary auth keys: %w", err)
|
||||||
}
|
}
|
||||||
if _, err := tx.Exec(ctx, `
|
if _, err := tx.Exec(ctx, `
|
||||||
DELETE FROM update_states
|
DELETE FROM update_states
|
||||||
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs); err != nil {
|
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs); err != nil {
|
||||||
return fmt.Errorf("delete revoked update states: %w", err)
|
return fmt.Errorf("delete protocol identity update states: %w", err)
|
||||||
}
|
}
|
||||||
tag, err := tx.Exec(ctx, `
|
tag, err := tx.Exec(ctx, `
|
||||||
DELETE FROM auth_keys
|
DELETE FROM auth_keys
|
||||||
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs)
|
WHERE auth_key_id = ANY($1::bigint[])`, authKeyIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("delete revoked permanent auth keys: %w", err)
|
return fmt.Errorf("delete permanent auth keys: %w", err)
|
||||||
}
|
}
|
||||||
if tag.RowsAffected() != int64(len(authKeyIDs)) {
|
if tag.RowsAffected() != int64(len(authKeyIDs)) {
|
||||||
return fmt.Errorf("delete revoked permanent auth keys: deleted %d of %d locked targets", tag.RowsAffected(), len(authKeyIDs))
|
return fmt.Errorf("delete permanent auth keys: deleted %d of %d locked targets", tag.RowsAffected(), len(authKeyIDs))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -13,7 +12,7 @@ import (
|
||||||
"telesrv/internal/store"
|
"telesrv/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthorizationStoreRevokeByHashDeletesProtocolKeyCascadePostgres(t *testing.T) {
|
func TestAuthorizationStoreRevokeByHashKeepsProtocolIdentityPostgres(t *testing.T) {
|
||||||
pool := testPool(t)
|
pool := testPool(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
userID := createRevokeTestUser(t, ctx, pool, "hash")
|
userID := createRevokeTestUser(t, ctx, pool, "hash")
|
||||||
|
|
@ -43,14 +42,15 @@ func TestAuthorizationStoreRevokeByHashDeletesProtocolKeyCascadePostgres(t *test
|
||||||
if err := NewUpdateStateStore(pool).Save(ctx, perm, userID, domain.UpdateState{Pts: 11, Date: int(time.Now().Unix())}); err != nil {
|
if err := NewUpdateStateStore(pool).Save(ctx, perm, userID, domain.UpdateState{Pts: 11, Date: int(time.Now().Unix())}); err != nil {
|
||||||
t.Fatalf("save update state: %v", err)
|
t.Fatalf("save update state: %v", err)
|
||||||
}
|
}
|
||||||
if err := NewTempAuthKeyBindingStore(pool).Save(ctx, domain.TempAuthKeyBinding{
|
binding := domain.TempAuthKeyBinding{
|
||||||
TempAuthKeyID: temp,
|
TempAuthKeyID: temp,
|
||||||
PermAuthKeyID: authKeyIDToInt64(perm),
|
PermAuthKeyID: authKeyIDToInt64(perm),
|
||||||
Nonce: 1,
|
Nonce: 1,
|
||||||
TempSessionID: 2,
|
TempSessionID: 2,
|
||||||
ExpiresAt: tempExpiry,
|
ExpiresAt: tempExpiry,
|
||||||
EncryptedMessage: []byte{1, 2, 3, 4},
|
EncryptedMessage: []byte{1, 2, 3, 4},
|
||||||
}); err != nil {
|
}
|
||||||
|
if err := NewTempAuthKeyBindingStore(pool).Save(ctx, binding); err != nil {
|
||||||
t.Fatalf("save temp binding: %v", err)
|
t.Fatalf("save temp binding: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,14 +61,14 @@ func TestAuthorizationStoreRevokeByHashDeletesProtocolKeyCascadePostgres(t *test
|
||||||
if deleted.AuthKeyID != perm || !deleted.PasswordPending {
|
if deleted.AuthKeyID != perm || !deleted.PasswordPending {
|
||||||
t.Fatalf("deleted authorization = %+v, want perm key and password_pending", deleted)
|
t.Fatalf("deleted authorization = %+v, want perm key and password_pending", deleted)
|
||||||
}
|
}
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, perm)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, perm)
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, temp)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, temp)
|
||||||
assertRevokeTestNoAuthorization(t, ctx, auths, perm)
|
assertRevokeTestNoAuthorization(t, ctx, auths, perm)
|
||||||
assertRevokeTestTableCount(t, ctx, pool, "update_states", "auth_key_id", authKeyIDToInt64(perm), 0)
|
assertRevokeTestTableCount(t, ctx, pool, "update_states", "auth_key_id", authKeyIDToInt64(perm), 0)
|
||||||
assertRevokeTestTableCount(t, ctx, pool, "temp_auth_key_bindings", "temp_auth_key_id", authKeyIDToInt64(temp), 0)
|
assertTempIdentityBinding(t, ctx, NewTempAuthKeyBindingStore(pool), binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizationStoreRevokeByUserExceptDeletesOnlyRevokedKeysPostgres(t *testing.T) {
|
func TestAuthorizationStoreRevokeByUserExceptKeepsRevokedProtocolIdentitiesPostgres(t *testing.T) {
|
||||||
pool := testPool(t)
|
pool := testPool(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
userID := createRevokeTestUser(t, ctx, pool, "bulk")
|
userID := createRevokeTestUser(t, ctx, pool, "bulk")
|
||||||
|
|
@ -87,14 +87,15 @@ func TestAuthorizationStoreRevokeByUserExceptDeletesOnlyRevokedKeysPostgres(t *t
|
||||||
}
|
}
|
||||||
tempExpiry := int(time.Now().Add(time.Hour).Unix())
|
tempExpiry := int(time.Now().Add(time.Hour).Unix())
|
||||||
saveRevokeTestAuthKey(t, ctx, keys, tempForTwo, tempExpiry)
|
saveRevokeTestAuthKey(t, ctx, keys, tempForTwo, tempExpiry)
|
||||||
if err := NewTempAuthKeyBindingStore(pool).Save(ctx, domain.TempAuthKeyBinding{
|
binding := domain.TempAuthKeyBinding{
|
||||||
TempAuthKeyID: tempForTwo,
|
TempAuthKeyID: tempForTwo,
|
||||||
PermAuthKeyID: authKeyIDToInt64(revokedTwo),
|
PermAuthKeyID: authKeyIDToInt64(revokedTwo),
|
||||||
Nonce: 3,
|
Nonce: 3,
|
||||||
TempSessionID: 4,
|
TempSessionID: 4,
|
||||||
ExpiresAt: tempExpiry,
|
ExpiresAt: tempExpiry,
|
||||||
EncryptedMessage: []byte{5, 6, 7, 8},
|
EncryptedMessage: []byte{5, 6, 7, 8},
|
||||||
}); err != nil {
|
}
|
||||||
|
if err := NewTempAuthKeyBindingStore(pool).Save(ctx, binding); err != nil {
|
||||||
t.Fatalf("save temp binding: %v", err)
|
t.Fatalf("save temp binding: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,9 +108,12 @@ func TestAuthorizationStoreRevokeByUserExceptDeletesOnlyRevokedKeysPostgres(t *t
|
||||||
}
|
}
|
||||||
assertRevokeTestPresentAuthKey(t, ctx, keys, keep)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, keep)
|
||||||
assertRevokeTestPresentAuthorization(t, ctx, auths, keep)
|
assertRevokeTestPresentAuthorization(t, ctx, auths, keep)
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, revokedOne)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, revokedOne)
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, revokedTwo)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, revokedTwo)
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, tempForTwo)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, tempForTwo)
|
||||||
|
assertRevokeTestNoAuthorization(t, ctx, auths, revokedOne)
|
||||||
|
assertRevokeTestNoAuthorization(t, ctx, auths, revokedTwo)
|
||||||
|
assertTempIdentityBinding(t, ctx, NewTempAuthKeyBindingStore(pool), binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizationStoreUpdateClientInfoMergesPostgres(t *testing.T) {
|
func TestAuthorizationStoreUpdateClientInfoMergesPostgres(t *testing.T) {
|
||||||
|
|
@ -155,7 +159,7 @@ func TestAuthorizationStoreUpdateClientInfoMergesPostgres(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizationStoreRevokeByHashConcurrentTempBindLeavesNoDanglingStatePostgres(t *testing.T) {
|
func TestAuthorizationStoreRevokeByHashConcurrentTempBindKeepsProtocolIdentityPostgres(t *testing.T) {
|
||||||
pool := testPool(t)
|
pool := testPool(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
userID := createRevokeTestUser(t, ctx, pool, "bind-revoke-race")
|
userID := createRevokeTestUser(t, ctx, pool, "bind-revoke-race")
|
||||||
|
|
@ -203,28 +207,17 @@ func TestAuthorizationStoreRevokeByHashConcurrentTempBindLeavesNoDanglingStatePo
|
||||||
close(start)
|
close(start)
|
||||||
|
|
||||||
bindErr := <-bindResult
|
bindErr := <-bindResult
|
||||||
if bindErr != nil && !errors.Is(bindErr, store.ErrAuthKeyBindingInvalid) {
|
if bindErr != nil {
|
||||||
t.Fatalf("attempt %d bind/revoke race bind error = %v", attempt, bindErr)
|
t.Fatalf("attempt %d bind/revoke race bind error = %v", attempt, bindErr)
|
||||||
}
|
}
|
||||||
revoked := <-revokeResults
|
revoked := <-revokeResults
|
||||||
if revoked.err != nil || !revoked.found {
|
if revoked.err != nil || !revoked.found {
|
||||||
t.Fatalf("attempt %d bind/revoke race found=%v err=%v", attempt, revoked.found, revoked.err)
|
t.Fatalf("attempt %d bind/revoke race found=%v err=%v", attempt, revoked.found, revoked.err)
|
||||||
}
|
}
|
||||||
if _, found, err := bindings.GetByTemp(ctx, temp); err != nil || found {
|
assertTempIdentityBinding(t, ctx, bindings, candidate)
|
||||||
t.Fatalf("attempt %d dangling binding found=%v err=%v", attempt, found, err)
|
assertRevokeTestPresentAuthKey(t, ctx, keys, perm)
|
||||||
}
|
assertRevokeTestPresentAuthKey(t, ctx, keys, temp)
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, perm)
|
|
||||||
assertRevokeTestNoAuthorization(t, ctx, auths, perm)
|
assertRevokeTestNoAuthorization(t, ctx, auths, perm)
|
||||||
if bindErr == nil {
|
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, temp)
|
|
||||||
} else {
|
|
||||||
assertTempIdentityAuthKeyExpiry(t, ctx, keys, temp, tempExpiry)
|
|
||||||
assertRevokeTestNoAuthorization(t, ctx, auths, temp)
|
|
||||||
if err := keys.Delete(ctx, temp); err != nil {
|
|
||||||
t.Fatalf("attempt %d clean unbound loser temp: %v", attempt, err)
|
|
||||||
}
|
|
||||||
assertRevokeTestMissingAuthKey(t, ctx, keys, temp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -520,12 +513,10 @@ func TestAuthorizationStoreRevokeByUserExceptPartiallySkipsTransferredCandidateP
|
||||||
t.Fatalf("old A state for transferred key found=%v err=%v, want absent", found, err)
|
t.Fatalf("old A state for transferred key found=%v err=%v, want absent", found, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertRevokeTestMissingAuthKey(t, testCtx, keys, revoked)
|
assertRevokeTestPresentAuthKey(t, testCtx, keys, revoked)
|
||||||
assertRevokeTestMissingAuthKey(t, testCtx, keys, revokedTemp)
|
assertRevokeTestPresentAuthKey(t, testCtx, keys, revokedTemp)
|
||||||
assertRevokeTestNoAuthorization(t, testCtx, auths, revoked)
|
assertRevokeTestNoAuthorization(t, testCtx, auths, revoked)
|
||||||
if _, found, err := bindings.GetByTemp(testCtx, revokedTemp); err != nil || found {
|
assertTempIdentityBinding(t, testCtx, bindings, revokedBinding)
|
||||||
t.Fatalf("revoked temp binding found=%v err=%v, want absent", found, err)
|
|
||||||
}
|
|
||||||
if _, found, err := states.Get(testCtx, revoked, userA); err != nil || found {
|
if _, found, err := states.Get(testCtx, revoked, userA); err != nil || found {
|
||||||
t.Fatalf("revoked A state found=%v err=%v, want absent", found, err)
|
t.Fatalf("revoked A state found=%v err=%v, want absent", found, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue