117 lines
3.2 KiB
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
|
|
}
|