fix: sync temp auth key expiry boundaries
This commit is contained in:
parent
305e8a0008
commit
20a310f6ca
50 changed files with 3626 additions and 335 deletions
|
|
@ -27,6 +27,10 @@ var (
|
|||
ErrCodeExpired = errors.New("phone code expired or not found")
|
||||
ErrCodeInvalid = errors.New("phone code invalid")
|
||||
ErrEncryptedMessageInvalid = errors.New("encrypted message invalid")
|
||||
ErrExpiresAtInvalid = errors.New("temporary auth key request expiry invalid")
|
||||
ErrTempAuthKeyEmpty = errors.New("temporary auth key missing or expired")
|
||||
ErrTempAuthKeyAlreadyBound = errors.New("temporary auth key already bound")
|
||||
ErrAuthKeyPermEmpty = errors.New("permanent auth key required")
|
||||
// ErrLoginCodeDeliveryUnavailable 表示已有账号的 app-code 没有可用的
|
||||
// durable message/event/outbox 投递边界。这是服务端配置错误,不能降级成
|
||||
// “继续返回 sentCode,等 signIn 后补发”。
|
||||
|
|
@ -193,26 +197,44 @@ func NewService(users store.UserStore, auths store.AuthorizationStore, codes sto
|
|||
// BindTempAuthKey 校验并记录 TDesktop PFS temp→perm auth key 绑定。
|
||||
func (s *Service) BindTempAuthKey(ctx context.Context, sessionID int64, binding domain.TempAuthKeyBinding) error {
|
||||
if s.authKeys != nil {
|
||||
inner, err := s.validateBindTempAuthKey(ctx, sessionID, binding)
|
||||
inner, protocolExpiresAt, err := s.validateBindTempAuthKey(ctx, sessionID, binding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
binding.TempSessionID = inner.TempSessionID
|
||||
// The bind request's expires_at is a signed client assertion. TDesktop
|
||||
// intentionally adds a small grace interval, while Android derives its
|
||||
// value at handshake completion. Retention and edge admission must use the
|
||||
// server's p_q_inner_data_temp lifetime, never the client value.
|
||||
binding.ExpiresAt = protocolExpiresAt
|
||||
}
|
||||
if binding.ExpiresAt <= int(time.Now().Unix()) {
|
||||
// The edge may admit the frame immediately before the temporary key's
|
||||
// absolute boundary and the encrypted proof may cross it. This is a temp-key
|
||||
// rotation condition, never a destructive permanent-key proof failure.
|
||||
return ErrTempAuthKeyEmpty
|
||||
}
|
||||
if s.tempKeys == nil {
|
||||
return nil
|
||||
}
|
||||
return s.tempKeys.Save(ctx, binding)
|
||||
if err := s.tempKeys.Save(ctx, binding); err != nil {
|
||||
if errors.Is(err, store.ErrTempAuthKeyAlreadyBound) {
|
||||
return ErrTempAuthKeyAlreadyBound
|
||||
}
|
||||
if errors.Is(err, store.ErrAuthKeyBindingInvalid) {
|
||||
return s.classifyBindingStoreInvalid(ctx, binding)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveAuthKey 将已绑定的 temp auth_key 解析为对应 perm auth_key。
|
||||
//
|
||||
// 过期处理是有意的连续性权衡(见 TestResolveAuthKeyAllowsExpiredTempBindingForAuthorizedPermKey):
|
||||
// temp 绑定 expires_at 已过时,仅当 perm key 也未授权才拒绝;perm 仍授权则继续解析,
|
||||
// 避免已登录会话因 temp key 过期而被强制踢下线。严格 PFS 要求过期 temp key 一律失效
|
||||
// (不以 perm 授权豁免),但收紧前需先核实目标客户端(TDesktop/DrKLO)会在过期前主动
|
||||
// 轮换 temp key 并优雅处理拒绝,否则会造成在线会话掉线。RetentionWorker 的 DeleteExpired
|
||||
// 已把残留窗口限制在 expires_at + 宽限(约 24h)内。收紧为显式硬化任务,需客户端验证。
|
||||
// temp→perm 是握手/绑定形成的协议身份关系,与 perm 当前是否登录完全无关。即使
|
||||
// auth.logOut 已删除 authorization,只要绑定仍存在,后续登录 RPC 也必须继续落到同一
|
||||
// perm key,绝不能把 raw temp key 当成新的业务身份。协议过期由 mtprotoedge 在解密/RPC
|
||||
// 之前返回 -404 并关闭连接;这里不再用 authorization 状态猜测 key 类型。
|
||||
func (s *Service) ResolveAuthKey(ctx context.Context, authKeyID [8]byte) ([8]byte, bool, error) {
|
||||
if s == nil || s.tempKeys == nil {
|
||||
return [8]byte{}, false, nil
|
||||
|
|
@ -221,19 +243,7 @@ func (s *Service) ResolveAuthKey(ctx context.Context, authKeyID [8]byte) ([8]byt
|
|||
if err != nil || !found {
|
||||
return [8]byte{}, found, err
|
||||
}
|
||||
permID := authKeyIDFromInt64(binding.PermAuthKeyID)
|
||||
if binding.ExpiresAt <= int(time.Now().Unix()) && !s.permAuthKeyAuthorized(ctx, permID) {
|
||||
return [8]byte{}, false, nil
|
||||
}
|
||||
return permID, true, nil
|
||||
}
|
||||
|
||||
func (s *Service) permAuthKeyAuthorized(ctx context.Context, authKeyID [8]byte) bool {
|
||||
if s == nil || s.auths == nil {
|
||||
return false
|
||||
}
|
||||
_, found, err := s.auths.ByAuthKey(ctx, authKeyID)
|
||||
return err == nil && found
|
||||
return authKeyIDFromInt64(binding.PermAuthKeyID), true, nil
|
||||
}
|
||||
|
||||
// UserID 返回 auth_key 当前绑定的用户。未登录、或两步验证未完成时 found=false。
|
||||
|
|
@ -1228,11 +1238,29 @@ func (s *Service) authorizationsByUserExcept(ctx context.Context, userID int64,
|
|||
}
|
||||
|
||||
func (s *Service) bind(ctx context.Context, auth domain.Authorization, userID int64) error {
|
||||
if s.authKeys != nil {
|
||||
key, found, err := s.authKeys.Get(ctx, auth.AuthKeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Defense in depth: Router normally converts a bound temp key to its perm
|
||||
// identity and edge rejects expired temp keys. Never let an unbound/sticky
|
||||
// temp key create authorization even if either outer boundary regresses.
|
||||
if !found || key.ExpiresAt != 0 {
|
||||
return ErrAuthKeyPermEmpty
|
||||
}
|
||||
}
|
||||
auth.UserID = userID
|
||||
// Bind 是授权切换的持久化状态边界:生产 store 会先清同 auth key 的旧用户
|
||||
// update state,再原子建立新用户 baseline。RPC 层不得在 Bind 成功后清整个 key,
|
||||
// 否则会把刚建立的 retained-floor checkpoint 一并删除。
|
||||
return s.auths.Bind(ctx, auth)
|
||||
if err := s.auths.Bind(ctx, auth); err != nil {
|
||||
if errors.Is(err, store.ErrAuthKeyNotPermanent) {
|
||||
return ErrAuthKeyPermEmpty
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) passwordNeeded(ctx context.Context, userID int64) (bool, error) {
|
||||
|
|
@ -1282,32 +1310,60 @@ func (s *Service) recordLoginMessage(ctx context.Context, userID int64, code str
|
|||
return msg, nil
|
||||
}
|
||||
|
||||
func (s *Service) validateBindTempAuthKey(ctx context.Context, sessionID int64, binding domain.TempAuthKeyBinding) (mtcrypto.BindAuthKeyInner, error) {
|
||||
func (s *Service) validateBindTempAuthKey(ctx context.Context, sessionID int64, binding domain.TempAuthKeyBinding) (mtcrypto.BindAuthKeyInner, int, error) {
|
||||
if binding.ExpiresAt <= int(time.Now().Unix()) {
|
||||
return mtcrypto.BindAuthKeyInner{}, ErrEncryptedMessageInvalid
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrExpiresAtInvalid
|
||||
}
|
||||
temp, found, err := s.authKeys.Get(ctx, binding.TempAuthKeyID)
|
||||
if err != nil {
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, err
|
||||
}
|
||||
// expires_at in auth.bindTempAuthKey is client-supplied and must only attest
|
||||
// to a still-live binding. It may never create or reclassify a protocol key;
|
||||
// the caller normalizes durable retention to this handshake-authoritative
|
||||
// temp.ExpiresAt instead of trusting the client value.
|
||||
if !found || temp.ExpiresAt <= int(time.Now().Unix()) {
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrTempAuthKeyEmpty
|
||||
}
|
||||
|
||||
permID := authKeyIDFromInt64(binding.PermAuthKeyID)
|
||||
perm, found, err := s.authKeys.Get(ctx, permID)
|
||||
if err != nil {
|
||||
return mtcrypto.BindAuthKeyInner{}, err
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, err
|
||||
}
|
||||
if !found {
|
||||
return mtcrypto.BindAuthKeyInner{}, ErrEncryptedMessageInvalid
|
||||
if !found || perm.ExpiresAt != 0 {
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrEncryptedMessageInvalid
|
||||
}
|
||||
|
||||
inner, err := decryptBindAuthKeyInner(perm, binding.EncryptedMessage)
|
||||
if err != nil {
|
||||
return mtcrypto.BindAuthKeyInner{}, ErrEncryptedMessageInvalid
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrEncryptedMessageInvalid
|
||||
}
|
||||
if inner.Nonce != binding.Nonce ||
|
||||
inner.TempAuthKeyID != authKeyIDInt64(binding.TempAuthKeyID) ||
|
||||
inner.PermAuthKeyID != binding.PermAuthKeyID ||
|
||||
inner.TempSessionID != sessionID ||
|
||||
inner.ExpiresAt != binding.ExpiresAt {
|
||||
return mtcrypto.BindAuthKeyInner{}, ErrEncryptedMessageInvalid
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrEncryptedMessageInvalid
|
||||
}
|
||||
return inner, nil
|
||||
if temp.ExpiresAt <= int(time.Now().Unix()) {
|
||||
return mtcrypto.BindAuthKeyInner{}, 0, ErrTempAuthKeyEmpty
|
||||
}
|
||||
return inner, temp.ExpiresAt, nil
|
||||
}
|
||||
|
||||
func (s *Service) classifyBindingStoreInvalid(ctx context.Context, binding domain.TempAuthKeyBinding) error {
|
||||
if s == nil || s.authKeys == nil {
|
||||
return ErrEncryptedMessageInvalid
|
||||
}
|
||||
temp, found, err := s.authKeys.Get(ctx, binding.TempAuthKeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found || temp.ExpiresAt <= int(time.Now().Unix()) {
|
||||
return ErrTempAuthKeyEmpty
|
||||
}
|
||||
return ErrEncryptedMessageInvalid
|
||||
}
|
||||
|
||||
func decryptBindAuthKeyInner(perm store.AuthKeyData, encrypted []byte) (mtcrypto.BindAuthKeyInner, error) {
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@ import (
|
|||
func TestBindTempAuthKeyValidatesEncryptedMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
permKey := testAuthKey(0x11)
|
||||
tempKey := testAuthKey(0x55)
|
||||
expiresAt := int(time.Now().Add(time.Hour).Unix())
|
||||
saveAuthKey(t, keys, permKey)
|
||||
saveAuthKey(t, keys, tempKey)
|
||||
saveAuthKeyWithExpiry(t, keys, tempKey, expiresAt)
|
||||
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
|
||||
|
|
@ -31,7 +32,6 @@ func TestBindTempAuthKeyValidatesEncryptedMessage(t *testing.T) {
|
|||
sessionID = int64(0x1020304050)
|
||||
msgID = int64(0x0102030405060708)
|
||||
)
|
||||
expiresAt := int(time.Now().Add(time.Hour).Unix())
|
||||
encrypted, err := mtcrypto.EncryptBindMessage(
|
||||
bytes.NewReader(bytes.Repeat([]byte{0xCD}, 128)),
|
||||
permKey,
|
||||
|
|
@ -69,6 +69,70 @@ func TestBindTempAuthKeyValidatesEncryptedMessage(t *testing.T) {
|
|||
if !errors.Is(err, ErrEncryptedMessageInvalid) {
|
||||
t.Fatalf("BindTempAuthKey wrong session err = %v, want ErrEncryptedMessageInvalid", err)
|
||||
}
|
||||
|
||||
// TDesktop intentionally adds a 30-second bind grace to the expiry it
|
||||
// derived from p_q_inner_data_temp. The request is valid, but the durable
|
||||
// binding must be normalized back to the server handshake expiry.
|
||||
extendedExpiry := expiresAt + 30
|
||||
extendedEncrypted, err := mtcrypto.EncryptBindMessage(
|
||||
bytes.NewReader(bytes.Repeat([]byte{0xCE}, 128)),
|
||||
permKey,
|
||||
msgID+4,
|
||||
&mtcrypto.BindAuthKeyInner{
|
||||
Nonce: nonce,
|
||||
TempAuthKeyID: tempKey.IntID(),
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
TempSessionID: sessionID,
|
||||
ExpiresAt: extendedExpiry,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("encrypt extended bind message: %v", err)
|
||||
}
|
||||
err = svc.BindTempAuthKey(ctx, sessionID, domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
Nonce: nonce,
|
||||
ExpiresAt: extendedExpiry,
|
||||
EncryptedMessage: extendedEncrypted,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BindTempAuthKey TDesktop grace expiry: %v", err)
|
||||
}
|
||||
stored, found, getErr := tempBindings.GetByTemp(ctx, tempKey.ID)
|
||||
if getErr != nil || !found || stored.ExpiresAt != expiresAt {
|
||||
t.Fatalf("stored binding after extension attempt = %+v found=%v err=%v", stored, found, getErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindTempAuthKeyClassifiesExpiryWithoutDestroyingPermanentKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
permKey := testAuthKey(0x31)
|
||||
tempKey := testAuthKey(0x32)
|
||||
saveAuthKey(t, keys, permKey)
|
||||
saveAuthKeyWithExpiry(t, keys, tempKey, int(time.Now().Add(-time.Second).Unix()))
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
|
||||
request := domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
ExpiresAt: int(time.Now().Add(time.Hour).Unix()),
|
||||
}
|
||||
if err := svc.BindTempAuthKey(ctx, 1001, request); !errors.Is(err, ErrTempAuthKeyEmpty) {
|
||||
t.Fatalf("expired protocol temp key err = %v, want ErrTempAuthKeyEmpty", err)
|
||||
}
|
||||
|
||||
request.TempAuthKeyID = testAuthKey(0x33).ID
|
||||
if err := svc.BindTempAuthKey(ctx, 1001, request); !errors.Is(err, ErrTempAuthKeyEmpty) {
|
||||
t.Fatalf("missing protocol temp key err = %v, want ErrTempAuthKeyEmpty", err)
|
||||
}
|
||||
|
||||
request.ExpiresAt = int(time.Now().Add(-time.Second).Unix())
|
||||
if err := svc.BindTempAuthKey(ctx, 1001, request); !errors.Is(err, ErrExpiresAtInvalid) {
|
||||
t.Fatalf("expired request proof err = %v, want ErrExpiresAtInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAuthKeyClientInfoConvergesAuthorizationMetadata(t *testing.T) {
|
||||
|
|
@ -115,15 +179,19 @@ func TestUpdateAuthKeyClientInfoConvergesAuthorizationMetadata(t *testing.T) {
|
|||
|
||||
func TestResolveAuthKeyUsesValidTempBinding(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
permKey := testAuthKey(0x11)
|
||||
tempKey := testAuthKey(0x55)
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), nil, tempBindings, "12345")
|
||||
expiresAt := int(time.Now().Add(time.Hour).Unix())
|
||||
saveAuthKey(t, keys, permKey)
|
||||
saveAuthKeyWithExpiry(t, keys, tempKey, expiresAt)
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
|
||||
if err := tempBindings.Save(ctx, domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
ExpiresAt: int(time.Now().Add(time.Hour).Unix()),
|
||||
ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp binding: %v", err)
|
||||
}
|
||||
|
|
@ -139,11 +207,15 @@ func TestResolveAuthKeyUsesValidTempBinding(t *testing.T) {
|
|||
|
||||
func TestResolveAuthKeyAllowsExpiredTempBindingForAuthorizedPermKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
authz := memory.NewAuthorizationStore()
|
||||
permKey := testAuthKey(0x21)
|
||||
tempKey := testAuthKey(0x65)
|
||||
svc := NewService(memory.NewUserStore(), authz, memory.NewCodeStore(), nil, tempBindings, "12345")
|
||||
expiresAt := int(time.Now().Add(-time.Minute).Unix())
|
||||
saveAuthKey(t, keys, permKey)
|
||||
saveAuthKeyWithExpiry(t, keys, tempKey, expiresAt)
|
||||
svc := NewService(memory.NewUserStore(), authz, memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
|
||||
if err := authz.Bind(ctx, domain.Authorization{AuthKeyID: permKey.ID, UserID: 1000000001}); err != nil {
|
||||
t.Fatalf("bind authorization: %v", err)
|
||||
|
|
@ -151,7 +223,7 @@ func TestResolveAuthKeyAllowsExpiredTempBindingForAuthorizedPermKey(t *testing.T
|
|||
if err := tempBindings.Save(ctx, domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
ExpiresAt: int(time.Now().Add(-time.Minute).Unix()),
|
||||
ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp binding: %v", err)
|
||||
}
|
||||
|
|
@ -165,17 +237,21 @@ func TestResolveAuthKeyAllowsExpiredTempBindingForAuthorizedPermKey(t *testing.T
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveAuthKeyRejectsExpiredTempBindingWithoutAuthorizedPermKey(t *testing.T) {
|
||||
func TestResolveAuthKeyKeepsExpiredBindingCanonicalWithoutAuthorization(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
permKey := testAuthKey(0x31)
|
||||
tempKey := testAuthKey(0x75)
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), nil, tempBindings, "12345")
|
||||
expiresAt := int(time.Now().Add(-time.Minute).Unix())
|
||||
saveAuthKey(t, keys, permKey)
|
||||
saveAuthKeyWithExpiry(t, keys, tempKey, expiresAt)
|
||||
svc := NewService(memory.NewUserStore(), memory.NewAuthorizationStore(), memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
|
||||
if err := tempBindings.Save(ctx, domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
ExpiresAt: int(time.Now().Add(-time.Minute).Unix()),
|
||||
ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp binding: %v", err)
|
||||
}
|
||||
|
|
@ -184,8 +260,87 @@ func TestResolveAuthKeyRejectsExpiredTempBindingWithoutAuthorizedPermKey(t *test
|
|||
if err != nil {
|
||||
t.Fatalf("ResolveAuthKey: %v", err)
|
||||
}
|
||||
if ok || got != ([8]byte{}) {
|
||||
t.Fatalf("resolved = %x ok=%v, want expired unresolved", got, ok)
|
||||
if !ok || got != permKey.ID {
|
||||
t.Fatalf("resolved = %x ok=%v, want canonical perm %x even while logged out", got, ok, permKey.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpiredTempLogoutReloginNeverAuthorizesRawTempKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
users := memory.NewUserStore()
|
||||
authz := memory.NewAuthorizationStore()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempBindings := memory.NewTempAuthKeyBindingStore(keys)
|
||||
permKey := testAuthKey(0x41)
|
||||
tempKey := testAuthKey(0x81)
|
||||
expiresAt := int(time.Now().Add(-time.Minute).Unix())
|
||||
if err := keys.Save(ctx, store.AuthKeyData{ID: permKey.ID}); err != nil {
|
||||
t.Fatalf("save perm key: %v", err)
|
||||
}
|
||||
if err := keys.Save(ctx, store.AuthKeyData{
|
||||
ID: tempKey.ID, ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp key: %v", err)
|
||||
}
|
||||
if err := tempBindings.Save(ctx, domain.TempAuthKeyBinding{
|
||||
TempAuthKeyID: tempKey.ID,
|
||||
PermAuthKeyID: permKey.IntID(),
|
||||
ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp binding: %v", err)
|
||||
}
|
||||
bob, err := users.Create(ctx, domain.User{Phone: "15550008101", FirstName: "Bob"})
|
||||
if err != nil {
|
||||
t.Fatalf("create Bob: %v", err)
|
||||
}
|
||||
alice, err := users.Create(ctx, domain.User{Phone: "15550008102", FirstName: "Alice"})
|
||||
if err != nil {
|
||||
t.Fatalf("create Alice: %v", err)
|
||||
}
|
||||
if err := authz.Bind(ctx, domain.Authorization{AuthKeyID: permKey.ID, UserID: bob.ID}); err != nil {
|
||||
t.Fatalf("authorize Bob: %v", err)
|
||||
}
|
||||
|
||||
svc := NewService(users, authz, memory.NewCodeStore(), keys, tempBindings, "12345")
|
||||
if err := svc.LogOut(ctx, permKey.ID); err != nil {
|
||||
t.Fatalf("logout Bob: %v", err)
|
||||
}
|
||||
resolved, ok, err := svc.ResolveAuthKey(ctx, tempKey.ID)
|
||||
if err != nil || !ok || resolved != permKey.ID {
|
||||
t.Fatalf("resolve after logout = %x/%v/%v, want perm", resolved, ok, err)
|
||||
}
|
||||
if _, err := svc.BindVerifiedLogin(ctx, domain.Authorization{AuthKeyID: resolved}, alice.ID); err != nil {
|
||||
t.Fatalf("relogin Alice on canonical perm: %v", err)
|
||||
}
|
||||
if a, found, err := authz.ByAuthKey(ctx, permKey.ID); err != nil || !found || a.UserID != alice.ID {
|
||||
t.Fatalf("perm authorization = %+v found=%v err=%v, want Alice", a, found, err)
|
||||
}
|
||||
if a, found, err := authz.ByAuthKey(ctx, tempKey.ID); err != nil || found {
|
||||
t.Fatalf("temp authorization = %+v found=%v err=%v, want absent", a, found, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationBindRejectsTemporaryProtocolKey(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
users := memory.NewUserStore()
|
||||
authz := memory.NewAuthorizationStore()
|
||||
keys := memory.NewAuthKeyStore()
|
||||
tempKey := testAuthKey(0x82)
|
||||
if err := keys.Save(ctx, store.AuthKeyData{
|
||||
ID: tempKey.ID, ExpiresAt: int(time.Now().Add(time.Hour).Unix()),
|
||||
}); err != nil {
|
||||
t.Fatalf("save temp key: %v", err)
|
||||
}
|
||||
u, err := users.Create(ctx, domain.User{Phone: "15550008201", FirstName: "Alice"})
|
||||
if err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
svc := NewService(users, authz, memory.NewCodeStore(), keys, memory.NewTempAuthKeyBindingStore(keys), "12345")
|
||||
if _, err := svc.BindVerifiedLogin(ctx, domain.Authorization{AuthKeyID: tempKey.ID}, u.ID); !errors.Is(err, ErrAuthKeyPermEmpty) {
|
||||
t.Fatalf("bind temp authorization err = %v, want ErrAuthKeyPermEmpty", err)
|
||||
}
|
||||
if _, found, err := authz.ByAuthKey(ctx, tempKey.ID); err != nil || found {
|
||||
t.Fatalf("temp authorization found=%v err=%v, want absent", found, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -666,10 +821,14 @@ func testAuthKey(seed byte) mtcrypto.AuthKey {
|
|||
}
|
||||
|
||||
func saveAuthKey(t *testing.T, keys store.AuthKeyStore, key mtcrypto.AuthKey) {
|
||||
saveAuthKeyWithExpiry(t, keys, key, 0)
|
||||
}
|
||||
|
||||
func saveAuthKeyWithExpiry(t *testing.T, keys store.AuthKeyStore, key mtcrypto.AuthKey, expiresAt int) {
|
||||
t.Helper()
|
||||
var value [256]byte
|
||||
copy(value[:], key.Value[:])
|
||||
if err := keys.Save(context.Background(), store.AuthKeyData{ID: key.ID, Value: value}); err != nil {
|
||||
if err := keys.Save(context.Background(), store.AuthKeyData{ID: key.ID, Value: value, ExpiresAt: expiresAt}); err != nil {
|
||||
t.Fatalf("save auth key: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue