From 4e75cfc65e736526d113b864068a1175804102d7 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Wed, 19 Aug 2026 01:17:09 +0800 Subject: [PATCH] fix: restore verification target user ids --- server/db/mysql/adapter.go | 26 ++++++++++++ server/db/mysql/tenant_adapter.go | 62 +++++++++++++++++++++++++---- server/db/mysql/tests/mysql_test.go | 49 +++++++++++++++++++++++ 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/server/db/mysql/adapter.go b/server/db/mysql/adapter.go index 1c007ca..7228a0c 100644 --- a/server/db/mysql/adapter.go +++ b/server/db/mysql/adapter.go @@ -1546,6 +1546,32 @@ func createIMRelationTables(exec sqlExecutor) error { } func createIMSecurityTables(tx *sql.Tx) error { + if _, err := tx.Exec( + `CREATE TABLE im_verification_codes( + id BIGINT NOT NULL AUTO_INCREMENT, + challenge_id VARCHAR(64) NOT NULL, + scene VARCHAR(32) NOT NULL, + action VARCHAR(64) NOT NULL DEFAULT '', + phone_hash CHAR(64) NOT NULL, + phone_mask VARCHAR(32) NOT NULL, + target_user BIGINT, + code_hash CHAR(64) NOT NULL, + verification_token_hash CHAR(64), + state SMALLINT NOT NULL, + attempts INT NOT NULL DEFAULT 0, + max_attempts INT NOT NULL, + expires_at DATETIME(3) NOT NULL, + resend_after DATETIME(3) NOT NULL, + verified_at DATETIME(3), + consumed_at DATETIME(3), + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + PRIMARY KEY(id), + UNIQUE INDEX uk_im_verification_challenge(challenge_id), + INDEX idx_im_verification_scene_phone_state(scene, phone_hash, state, expires_at) + )`); err != nil { + return err + } if _, err := tx.Exec( `CREATE TABLE im_realname_verifications( id BIGINT NOT NULL AUTO_INCREMENT, diff --git a/server/db/mysql/tenant_adapter.go b/server/db/mysql/tenant_adapter.go index 78aeff5..21dc234 100644 --- a/server/db/mysql/tenant_adapter.go +++ b/server/db/mysql/tenant_adapter.go @@ -5466,8 +5466,8 @@ func (a *adapter) TenantVerificationGet(id t.TenantID, challengeID string) (*t.V if cancel != nil { defer cancel() } - var vc t.VerificationCode - err := a.db.GetContext(ctx, &vc, + var row verificationCodeRow + err := a.db.GetContext(ctx, &row, `SELECT id,challenge_id,scene,action,phone_hash,phone_mask,IFNULL(target_user,0) AS target_user,code_hash,verification_token_hash, state,attempts,max_attempts,expires_at,resend_after,verified_at,consumed_at,created_at,updated_at FROM im_verification_codes @@ -5478,8 +5478,7 @@ func (a *adapter) TenantVerificationGet(id t.TenantID, challengeID string) (*t.V if err != nil { return nil, err } - vc.TenantID = id - return &vc, nil + return row.toVerificationCode(id), nil } func (a *adapter) TenantVerificationGetActive(id t.TenantID, scene, phoneHash string, now time.Time) (*t.VerificationCode, error) { @@ -5499,8 +5498,8 @@ func (a *adapter) TenantVerificationGetActive(id t.TenantID, scene, phoneHash st if cancel != nil { defer cancel() } - var vc t.VerificationCode - err := a.db.GetContext(ctx, &vc, + var row verificationCodeRow + err := a.db.GetContext(ctx, &row, `SELECT id,challenge_id,scene,action,phone_hash,phone_mask,IFNULL(target_user,0) AS target_user,code_hash,verification_token_hash, state,attempts,max_attempts,expires_at,resend_after,verified_at,consumed_at,created_at,updated_at FROM im_verification_codes @@ -5512,8 +5511,55 @@ func (a *adapter) TenantVerificationGetActive(id t.TenantID, scene, phoneHash st if err != nil { return nil, err } - vc.TenantID = id - return &vc, nil + return row.toVerificationCode(id), nil +} + +type verificationCodeRow struct { + ID int64 `db:"id"` + ChallengeID string `db:"challenge_id"` + Scene string `db:"scene"` + Action string `db:"action"` + PhoneHash string `db:"phone_hash"` + PhoneMask string `db:"phone_mask"` + TargetUser int64 `db:"target_user"` + CodeHash string `db:"code_hash"` + VerificationTokenHash *string `db:"verification_token_hash"` + State t.VerificationState `db:"state"` + Attempts int `db:"attempts"` + MaxAttempts int `db:"max_attempts"` + ExpiresAt time.Time `db:"expires_at"` + ResendAfter time.Time `db:"resend_after"` + VerifiedAt *time.Time `db:"verified_at"` + ConsumedAt *time.Time `db:"consumed_at"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r verificationCodeRow) toVerificationCode(tenantID t.TenantID) *t.VerificationCode { + vc := &t.VerificationCode{ + ID: r.ID, + TenantID: tenantID, + ChallengeID: r.ChallengeID, + Scene: r.Scene, + Action: r.Action, + PhoneHash: r.PhoneHash, + PhoneMask: r.PhoneMask, + CodeHash: r.CodeHash, + VerificationTokenHash: r.VerificationTokenHash, + State: r.State, + Attempts: r.Attempts, + MaxAttempts: r.MaxAttempts, + ExpiresAt: r.ExpiresAt, + ResendAfter: r.ResendAfter, + VerifiedAt: r.VerifiedAt, + ConsumedAt: r.ConsumedAt, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + } + if r.TargetUser > 0 { + vc.TargetUser = store.EncodeUid(r.TargetUser) + } + return vc } func (a *adapter) TenantVerificationFail(id t.TenantID, challengeID string, state t.VerificationState) error { diff --git a/server/db/mysql/tests/mysql_test.go b/server/db/mysql/tests/mysql_test.go index 10e795b..3c6005e 100644 --- a/server/db/mysql/tests/mysql_test.go +++ b/server/db/mysql/tests/mysql_test.go @@ -472,6 +472,55 @@ func TestRegistrationInviteReusableSchema(t *testing.T) { } } +func TestVerificationTargetUserRoundTrip(t *testing.T) { + tenantAdapter, ok := adp.(adapter.TenantBusinessAdapter) + if !ok { + t.Fatal("MySQL adapter does not implement TenantBusinessAdapter") + } + var tenantID types.TenantID + if err := db.Get(&tenantID, "SELECT id FROM im_tenant WHERE code='default'"); err != nil { + t.Fatal(err) + } + + uid := types.ParseUserId("usr" + testData.Users[0].Id) + now := types.TimeNow() + challengeID := "challenge-roundtrip-target-user" + phoneHash := "verification-target-user-phone-hash" + _, _ = db.Exec("DELETE FROM im_verification_codes WHERE challenge_id=? OR phone_hash=?", challengeID, phoneHash) + vc := &types.VerificationCode{ + TenantID: tenantID, + ChallengeID: challengeID, + Scene: "reset_password", + PhoneHash: phoneHash, + PhoneMask: "+86******0000", + TargetUser: uid, + CodeHash: strings.Repeat("a", 64), + State: types.VerificationStatePending, + MaxAttempts: 5, + ExpiresAt: now.Add(time.Hour), + ResendAfter: now.Add(time.Minute), + } + if err := tenantAdapter.TenantVerificationCreate(tenantID, vc); err != nil { + t.Fatal(err) + } + + stored, err := tenantAdapter.TenantVerificationGet(tenantID, challengeID) + if err != nil { + t.Fatal(err) + } + if stored.TargetUser != uid { + t.Fatalf("Get target user: got %s, want %s", stored.TargetUser.UserId(), uid.UserId()) + } + + active, err := tenantAdapter.TenantVerificationGetActive(tenantID, "reset_password", phoneHash, now) + if err != nil { + t.Fatal(err) + } + if active.TargetUser != uid { + t.Fatalf("GetActive target user: got %s, want %s", active.TargetUser.UserId(), uid.UserId()) + } +} + func TestTopicCreate(t *testing.T) { err := adp.TopicCreate(testData.Topics[0]) if err != nil {