64 lines
2.2 KiB
Go
64 lines
2.2 KiB
Go
package store
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
)
|
||
|
||
const PhoneCodePurposeChangePhone = "change_phone"
|
||
|
||
// PhoneCode 是一条验证码记录(与某次 sendCode 的 phone_code_hash 或邮箱验证键关联)。
|
||
// Purpose/UserID/AuthKeyID/SessionID 为已登录敏感操作提供作用域;登录验证码保持零值。
|
||
type PhoneCode struct {
|
||
Phone string
|
||
Code string
|
||
Channel string
|
||
Purpose string
|
||
UserID int64
|
||
AuthKeyID [8]byte
|
||
SessionID int64
|
||
Email string
|
||
PendingEmail string
|
||
Attempts int
|
||
MaxAttempts int
|
||
VerifiedEmail bool
|
||
RequireSignUp bool
|
||
LoginEmailHash string
|
||
}
|
||
|
||
// PhoneCodeScope 标识已登录敏感操作的一次性验证码作用域。SessionID 故意不在
|
||
// 作用域内:同一 perm auth key 等待验证码期间允许重建 MTProto session。
|
||
// 登录/注册验证码没有 Purpose/UserID/AuthKeyID,保持非 scoped 行为。
|
||
type PhoneCodeScope struct {
|
||
Purpose string
|
||
UserID int64
|
||
AuthKeyID [8]byte
|
||
Phone string
|
||
}
|
||
|
||
func (c PhoneCode) Scope() PhoneCodeScope {
|
||
return PhoneCodeScope{
|
||
Purpose: c.Purpose,
|
||
UserID: c.UserID,
|
||
AuthKeyID: c.AuthKeyID,
|
||
Phone: c.Phone,
|
||
}
|
||
}
|
||
|
||
func (s PhoneCodeScope) Valid() bool {
|
||
return s.Purpose != "" && s.UserID != 0 && s.AuthKeyID != ([8]byte{}) && s.Phone != ""
|
||
}
|
||
|
||
// CodeStore 暂存验证码:phone_code_hash → 作用域 + 手机号 + 验证码,带 TTL。
|
||
// 实现见 store/memory(测试替身)、store/redisstore。
|
||
type CodeStore interface {
|
||
// Set 对 scoped code 必须原子替换同作用域旧 hash,保证单作用域至多一个
|
||
// 活跃验证码;普通登录码仍按 hash 独立保存。
|
||
Set(ctx context.Context, phoneCodeHash string, code PhoneCode, ttl time.Duration) error
|
||
Get(ctx context.Context, phoneCodeHash string) (PhoneCode, bool, error)
|
||
Update(ctx context.Context, phoneCodeHash string, code PhoneCode) error
|
||
Del(ctx context.Context, phoneCodeHash string) error
|
||
// ConsumeScoped 仅当 hash 仍是 scope 的当前活跃 hash 时原子读取并删除;
|
||
// 并发调用至多一个返回 found=true。
|
||
ConsumeScoped(ctx context.Context, phoneCodeHash string, scope PhoneCodeScope) (PhoneCode, bool, error)
|
||
}
|