owpengram-server/internal/store/memory/passkey.go

117 lines
3.2 KiB
Go

package memory
import (
"context"
"sync"
"time"
"telesrv/internal/domain"
)
// PasskeyStore 是 store.PasskeyStore 的内存实现。
type PasskeyStore struct {
mu sync.RWMutex
byID map[string]domain.PasskeyCredential // key = string(credentialID)
}
// NewPasskeyStore 创建内存 passkey 凭据 store。
func NewPasskeyStore() *PasskeyStore {
return &PasskeyStore{byID: make(map[string]domain.PasskeyCredential)}
}
func (s *PasskeyStore) InsertPasskey(_ context.Context, cred domain.PasskeyCredential) error {
if len(cred.CredentialID) == 0 || cred.UserID == 0 {
return domain.ErrPasskeyInvalid
}
s.mu.Lock()
s.byID[string(cred.CredentialID)] = cred.Clone()
s.mu.Unlock()
return nil
}
func (s *PasskeyStore) GetPasskeyByCredentialID(_ context.Context, credentialID []byte) (domain.PasskeyCredential, bool, error) {
s.mu.RLock()
cred, ok := s.byID[string(credentialID)]
s.mu.RUnlock()
if !ok {
return domain.PasskeyCredential{}, false, nil
}
return cred.Clone(), true, nil
}
func (s *PasskeyStore) ListPasskeysByUser(_ context.Context, userID int64) ([]domain.PasskeyCredential, error) {
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]domain.PasskeyCredential, 0)
for _, cred := range s.byID {
if cred.UserID == userID {
out = append(out, cred.Clone())
}
}
return out, nil
}
func (s *PasskeyStore) UpdatePasskeyUsage(_ context.Context, credentialID []byte, signCount uint32, lastUsedAt int64) error {
s.mu.Lock()
defer s.mu.Unlock()
cred, ok := s.byID[string(credentialID)]
if !ok {
return domain.ErrPasskeyNotFound
}
cred.SignCount = signCount
cred.LastUsedAt = lastUsedAt
s.byID[string(credentialID)] = cred
return nil
}
func (s *PasskeyStore) DeletePasskey(_ context.Context, userID int64, credentialID []byte) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
cred, ok := s.byID[string(credentialID)]
if !ok || cred.UserID != userID {
return false, nil
}
delete(s.byID, string(credentialID))
return true, nil
}
// PasskeyChallengeStore 是 store.PasskeyChallengeStore 的内存实现(进程内、短 TTL)。
// 与 QR 登录 token 同属进程内一次性凭据,不跨实例。
type PasskeyChallengeStore struct {
mu sync.Mutex
m map[string]challengeEntry // key = string(challenge)
}
type challengeEntry struct {
c domain.PasskeyChallenge
expiresAt time.Time
}
// NewPasskeyChallengeStore 创建内存挑战 store。
func NewPasskeyChallengeStore() *PasskeyChallengeStore {
return &PasskeyChallengeStore{m: make(map[string]challengeEntry)}
}
func (s *PasskeyChallengeStore) SavePasskeyChallenge(_ context.Context, challenge []byte, c domain.PasskeyChallenge, ttl time.Duration) error {
if len(challenge) == 0 {
return domain.ErrPasskeyChallengeInvalid
}
s.mu.Lock()
s.m[string(challenge)] = challengeEntry{c: c, expiresAt: time.Now().Add(ttl)}
s.mu.Unlock()
return nil
}
func (s *PasskeyChallengeStore) ConsumePasskeyChallenge(_ context.Context, challenge []byte) (domain.PasskeyChallenge, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
entry, ok := s.m[string(challenge)]
if !ok {
return domain.PasskeyChallenge{}, false, nil
}
delete(s.m, string(challenge)) // 一次性:无论是否过期都删除
if time.Now().After(entry.expiresAt) {
return domain.PasskeyChallenge{}, false, nil
}
return entry.c, true, nil
}