fix verification cache for redis-backed challenges
This commit is contained in:
@@ -379,7 +379,7 @@ func (a *audit) run() {
|
|||||||
|
|
||||||
func (a *audit) register(chapter, id, username, phone, password string, primary bool) {
|
func (a *audit) register(chapter, id, username, phone, password string, primary bool) {
|
||||||
var challenge, verification string
|
var challenge, verification string
|
||||||
a.testHTTP(chapter, id+"-code", "注册发码 "+username, "POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "phone": phone}, "", true, []int{200}, func(r imResp) string {
|
a.testHTTP(chapter, id+"-code", "注册发码 "+username, "POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "registration_type": 1, "phone": phone}, "", true, []int{200}, func(r imResp) string {
|
||||||
challenge = asString(r.Data["challenge_id"])
|
challenge = asString(r.Data["challenge_id"])
|
||||||
return "challenge_id=" + mask(challenge)
|
return "challenge_id=" + mask(challenge)
|
||||||
})
|
})
|
||||||
@@ -404,7 +404,7 @@ func (a *audit) register(chapter, id, username, phone, password string, primary
|
|||||||
|
|
||||||
func (a *audit) registerThird(chapter, id, username, phone, password string) {
|
func (a *audit) registerThird(chapter, id, username, phone, password string) {
|
||||||
var challenge, verification string
|
var challenge, verification string
|
||||||
a.testHTTP(chapter, id+"-code", "入群用户注册发码 "+username, "POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "phone": phone}, "", true, []int{200}, func(r imResp) string {
|
a.testHTTP(chapter, id+"-code", "入群用户注册发码 "+username, "POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "registration_type": 1, "phone": phone}, "", true, []int{200}, func(r imResp) string {
|
||||||
challenge = asString(r.Data["challenge_id"])
|
challenge = asString(r.Data["challenge_id"])
|
||||||
return "challenge_id=" + mask(challenge)
|
return "challenge_id=" + mask(challenge)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ func (a *audit) register(username, phone, password string) (string, string, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *audit) verificationCode(phone string) (string, error) {
|
func (a *audit) verificationCode(phone string) (string, error) {
|
||||||
status, r, err := a.call("POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "phone": phone}, "")
|
status, r, err := a.call("POST", "/v1/im/auth/verification-codes", map[string]any{"scene": "register", "registration_type": 1, "phone": phone}, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-3
@@ -4286,6 +4286,79 @@ type VerificationPersistenceInterface interface {
|
|||||||
|
|
||||||
type verificationMapper struct{}
|
type verificationMapper struct{}
|
||||||
|
|
||||||
|
type verificationCodeCache struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
TenantID types.TenantID `json:"tenant_id"`
|
||||||
|
ChallengeID string `json:"challenge_id"`
|
||||||
|
Scene string `json:"scene"`
|
||||||
|
Action string `json:"action,omitempty"`
|
||||||
|
PhoneHash string `json:"phone_hash"`
|
||||||
|
PhoneMask string `json:"phone_mask,omitempty"`
|
||||||
|
TargetUser types.Uid `json:"target_user,omitempty"`
|
||||||
|
CodeHash string `json:"code_hash"`
|
||||||
|
VerificationTokenHash *string `json:"verification_token_hash,omitempty"`
|
||||||
|
State types.VerificationState `json:"state"`
|
||||||
|
Attempts int `json:"attempts"`
|
||||||
|
MaxAttempts int `json:"max_attempts"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
ResendAfter time.Time `json:"resend_after"`
|
||||||
|
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||||
|
ConsumedAt *time.Time `json:"consumed_at,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func verificationCodeCacheFromType(vc *types.VerificationCode) verificationCodeCache {
|
||||||
|
if vc == nil {
|
||||||
|
return verificationCodeCache{}
|
||||||
|
}
|
||||||
|
return verificationCodeCache{
|
||||||
|
ID: vc.ID,
|
||||||
|
TenantID: vc.TenantID,
|
||||||
|
ChallengeID: vc.ChallengeID,
|
||||||
|
Scene: vc.Scene,
|
||||||
|
Action: vc.Action,
|
||||||
|
PhoneHash: vc.PhoneHash,
|
||||||
|
PhoneMask: vc.PhoneMask,
|
||||||
|
TargetUser: vc.TargetUser,
|
||||||
|
CodeHash: vc.CodeHash,
|
||||||
|
VerificationTokenHash: vc.VerificationTokenHash,
|
||||||
|
State: vc.State,
|
||||||
|
Attempts: vc.Attempts,
|
||||||
|
MaxAttempts: vc.MaxAttempts,
|
||||||
|
ExpiresAt: vc.ExpiresAt,
|
||||||
|
ResendAfter: vc.ResendAfter,
|
||||||
|
VerifiedAt: vc.VerifiedAt,
|
||||||
|
ConsumedAt: vc.ConsumedAt,
|
||||||
|
CreatedAt: vc.CreatedAt,
|
||||||
|
UpdatedAt: vc.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc verificationCodeCache) toType() *types.VerificationCode {
|
||||||
|
return &types.VerificationCode{
|
||||||
|
ID: vc.ID,
|
||||||
|
TenantID: vc.TenantID,
|
||||||
|
ChallengeID: vc.ChallengeID,
|
||||||
|
Scene: vc.Scene,
|
||||||
|
Action: vc.Action,
|
||||||
|
PhoneHash: vc.PhoneHash,
|
||||||
|
PhoneMask: vc.PhoneMask,
|
||||||
|
TargetUser: vc.TargetUser,
|
||||||
|
CodeHash: vc.CodeHash,
|
||||||
|
VerificationTokenHash: vc.VerificationTokenHash,
|
||||||
|
State: vc.State,
|
||||||
|
Attempts: vc.Attempts,
|
||||||
|
MaxAttempts: vc.MaxAttempts,
|
||||||
|
ExpiresAt: vc.ExpiresAt,
|
||||||
|
ResendAfter: vc.ResendAfter,
|
||||||
|
VerifiedAt: vc.VerifiedAt,
|
||||||
|
ConsumedAt: vc.ConsumedAt,
|
||||||
|
CreatedAt: vc.CreatedAt,
|
||||||
|
UpdatedAt: vc.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifications is a singleton for product verification challenges.
|
// Verifications is a singleton for product verification challenges.
|
||||||
var Verifications VerificationPersistenceInterface
|
var Verifications VerificationPersistenceInterface
|
||||||
|
|
||||||
@@ -4537,7 +4610,7 @@ func (verificationMapper) Create(tenantID types.TenantID, vc *types.Verification
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (verificationMapper) Get(tenantID types.TenantID, challengeID string) (*types.VerificationCode, error) {
|
func (verificationMapper) Get(tenantID types.TenantID, challengeID string) (*types.VerificationCode, error) {
|
||||||
var vc types.VerificationCode
|
var vc verificationCodeCache
|
||||||
ok, err := GetCachedObject(tenantID, cacheNamespaceVerification, challengeID, &vc)
|
ok, err := GetCachedObject(tenantID, cacheNamespaceVerification, challengeID, &vc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -4545,7 +4618,7 @@ func (verificationMapper) Get(tenantID types.TenantID, challengeID string) (*typ
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, types.ErrNotFound
|
return nil, types.ErrNotFound
|
||||||
}
|
}
|
||||||
return &vc, nil
|
return vc.toType(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (verificationMapper) GetActive(tenantID types.TenantID, scene, phoneHash string, now time.Time) (*types.VerificationCode, error) {
|
func (verificationMapper) GetActive(tenantID types.TenantID, scene, phoneHash string, now time.Time) (*types.VerificationCode, error) {
|
||||||
@@ -4611,7 +4684,7 @@ func cacheVerificationCode(tenantID types.TenantID, vc *types.VerificationCode)
|
|||||||
if vc == nil {
|
if vc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := CacheObject(tenantID, cacheNamespaceVerification, vc.ChallengeID, vc); err != nil {
|
if err := CacheObject(tenantID, cacheNamespaceVerification, vc.ChallengeID, verificationCodeCacheFromType(vc)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if vc.State == types.VerificationStatePending {
|
if vc.State == types.VerificationStatePending {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tinode/chat/server/store/types"
|
"github.com/tinode/chat/server/store/types"
|
||||||
)
|
)
|
||||||
@@ -41,3 +43,56 @@ func TestValidateMessageHeadersProductContract(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerificationCodeCachePreservesInternalFields(t *testing.T) {
|
||||||
|
tokenHash := "verify-token-hash"
|
||||||
|
now := time.Now().UTC().Round(time.Millisecond)
|
||||||
|
vc := &types.VerificationCode{
|
||||||
|
ID: 11,
|
||||||
|
TenantID: 7,
|
||||||
|
ChallengeID: "challenge-1",
|
||||||
|
Scene: "register",
|
||||||
|
Action: "signup",
|
||||||
|
PhoneHash: "phone-hash",
|
||||||
|
PhoneMask: "+861****000",
|
||||||
|
CodeHash: "code-hash",
|
||||||
|
VerificationTokenHash: &tokenHash,
|
||||||
|
State: types.VerificationStateVerified,
|
||||||
|
Attempts: 2,
|
||||||
|
MaxAttempts: 5,
|
||||||
|
ExpiresAt: now.Add(5 * time.Minute),
|
||||||
|
ResendAfter: now.Add(time.Minute),
|
||||||
|
VerifiedAt: &now,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
publicPayload, err := json.Marshal(vc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(publicPayload), "code-hash") || strings.Contains(string(publicPayload), "phone-hash") {
|
||||||
|
t.Fatalf("public verification JSON leaked internal fields: %s", publicPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
cachePayload, err := json.Marshal(verificationCodeCacheFromType(vc))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(cachePayload), `"code_hash":"code-hash"`) ||
|
||||||
|
!strings.Contains(string(cachePayload), `"phone_hash":"phone-hash"`) ||
|
||||||
|
!strings.Contains(string(cachePayload), `"max_attempts":5`) {
|
||||||
|
t.Fatalf("cache verification JSON missing internal fields: %s", cachePayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cached verificationCodeCache
|
||||||
|
if err := json.Unmarshal(cachePayload, &cached); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := cached.toType()
|
||||||
|
if got.CodeHash != vc.CodeHash || got.PhoneHash != vc.PhoneHash ||
|
||||||
|
got.State != vc.State || got.Attempts != vc.Attempts || got.MaxAttempts != vc.MaxAttempts ||
|
||||||
|
got.VerificationTokenHash == nil || *got.VerificationTokenHash != tokenHash {
|
||||||
|
t.Fatalf("cache round trip lost fields: got %#v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user