feat: sync iOS compatibility support

This commit is contained in:
A 2026-07-13 00:58:57 +08:00
parent 1f646ef024
commit 50803a604c
32 changed files with 871 additions and 78 deletions

View file

@ -11,6 +11,8 @@ type AuthorizationStore interface {
Bind(ctx context.Context, a domain.Authorization) error
ByAuthKey(ctx context.Context, authKeyID [8]byte) (domain.Authorization, bool, error)
UpdateLayer(ctx context.Context, authKeyID [8]byte, layer int) error
// UpdateClientInfo 合并更新已绑定授权的客户端元数据,使设备列表与 auth key 协商事实一致。
UpdateClientInfo(ctx context.Context, authKeyID [8]byte, info domain.AuthKeyClientInfo) error
ListByUser(ctx context.Context, userID int64) ([]domain.Authorization, error)
Delete(ctx context.Context, authKeyID [8]byte) error
DeleteByHash(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error)

View file

@ -172,6 +172,34 @@ func (s *AuthorizationStore) UpdateLayer(_ context.Context, id [8]byte, layer in
return nil
}
func (s *AuthorizationStore) UpdateClientInfo(_ context.Context, id [8]byte, info domain.AuthKeyClientInfo) error {
s.mu.Lock()
if a, ok := s.m[id]; ok {
if info.Layer > 0 {
a.Layer = info.Layer
}
if info.DeviceModel != "" {
a.DeviceModel = info.DeviceModel
}
if info.Platform != "" {
a.Platform = info.Platform
}
if info.SystemVersion != "" {
a.SystemVersion = info.SystemVersion
}
if info.APIID != 0 {
a.APIID = info.APIID
}
if info.AppVersion != "" {
a.AppVersion = info.AppVersion
}
a.ActiveAt = time.Now()
s.m[id] = a
}
s.mu.Unlock()
return nil
}
func (s *AuthorizationStore) MarkPasswordPassed(_ context.Context, id [8]byte) error {
s.mu.Lock()
if a, ok := s.m[id]; ok {

View file

@ -183,6 +183,25 @@ UPDATE authorizations SET layer = $2, active_at = now() WHERE auth_key_id = $1`,
return nil
}
func (s *AuthorizationStore) UpdateClientInfo(ctx context.Context, id [8]byte, info domain.AuthKeyClientInfo) error {
if _, err := s.db.Exec(ctx, `
UPDATE authorizations SET
layer = CASE WHEN $2 > 0 THEN $2 ELSE layer END,
device_model = CASE WHEN $3 <> '' THEN $3 ELSE device_model END,
platform = CASE WHEN $4 <> '' THEN $4 ELSE platform END,
system_version = CASE WHEN $5 <> '' THEN $5 ELSE system_version END,
api_id = CASE WHEN $6 <> 0 THEN $6 ELSE api_id END,
app_version = CASE WHEN $7 <> '' THEN $7 ELSE app_version END,
active_at = now()
WHERE auth_key_id = $1`,
authKeyIDToInt64(id), int32(info.Layer), info.DeviceModel, info.Platform,
info.SystemVersion, int32(info.APIID), info.AppVersion,
); err != nil {
return fmt.Errorf("update authorization client info: %w", err)
}
return nil
}
// MarkPasswordPassed 在两步验证通过后清除 password_pending,使 auth_key 转为完全授权。
func (s *AuthorizationStore) MarkPasswordPassed(ctx context.Context, id [8]byte) error {
if _, err := s.db.Exec(ctx, `

View file

@ -110,6 +110,49 @@ func TestAuthorizationStoreRevokeByUserExceptDeletesOnlyRevokedKeysPostgres(t *t
assertRevokeTestMissingAuthKey(t, ctx, keys, tempForTwo)
}
func TestAuthorizationStoreUpdateClientInfoMergesPostgres(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
userID := createRevokeTestUser(t, ctx, pool, "client-info")
id := revokeTestAuthKeyID(0xb1)
keys := NewAuthKeyStore(pool)
auths := NewAuthorizationStore(pool)
saveRevokeTestAuthKey(t, ctx, keys, id)
if err := auths.Bind(ctx, domain.Authorization{
AuthKeyID: id,
UserID: userID,
Hash: 9201,
Platform: "unknown",
DeviceModel: "legacy",
IP: "127.0.0.1",
}); err != nil {
t.Fatalf("bind authorization: %v", err)
}
if err := auths.UpdateClientInfo(ctx, id, domain.AuthKeyClientInfo{
Layer: 227,
DeviceModel: "iPhone Simulator",
Platform: "ios",
SystemVersion: "26.5",
APIID: 1,
AppVersion: "12.8 (10000)",
}); err != nil {
t.Fatalf("update client info: %v", err)
}
// Empty/zero values are a partial update and must not erase strong metadata.
if err := auths.UpdateClientInfo(ctx, id, domain.AuthKeyClientInfo{AppVersion: "12.8.1"}); err != nil {
t.Fatalf("partial update client info: %v", err)
}
got, found, err := auths.ByAuthKey(ctx, id)
if err != nil || !found {
t.Fatalf("get authorization: found=%v err=%v", found, err)
}
if got.Layer != 227 || got.DeviceModel != "iPhone Simulator" || got.Platform != "ios" ||
got.SystemVersion != "26.5" || got.APIID != 1 || got.AppVersion != "12.8.1" {
t.Fatalf("merged client info = %+v", got)
}
}
func createRevokeTestUser(t *testing.T, ctx context.Context, db *pgxpool.Pool, suffix string) int64 {
t.Helper()
phone := fmt.Sprintf("+1555%09d", time.Now().UnixNano()%1_000_000_000)