cart and checkout
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package customerauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,14 +15,36 @@ import (
|
||||
// associated with a credential record.
|
||||
var ErrEmailExists = errors.New("customerauth: email already registered")
|
||||
|
||||
// Credentials is the email→credential index the auth server depends on. It is
|
||||
// satisfied by the file-backed CredentialStore (single-instance / dev) and by
|
||||
// RedisCredentialStore (shared, horizontally scalable). All methods normalize
|
||||
// the email key internally.
|
||||
type Credentials interface {
|
||||
// Get returns the record for email and whether it exists. A backing-store
|
||||
// failure is reported as "not found" so callers fail closed.
|
||||
Get(ctx context.Context, email string) (Record, bool)
|
||||
// Register adds a new credential, returning ErrEmailExists if taken.
|
||||
Register(ctx context.Context, email string, profileID uint64, hash, createdAt string) error
|
||||
// MarkVerified records email verification. The bool reports whether the
|
||||
// email was known; an unknown email is not an error (avoids enumeration).
|
||||
MarkVerified(ctx context.Context, email, verifiedAt string) (bool, error)
|
||||
// UpdateHash replaces the password hash. The bool reports whether the email
|
||||
// was known; an unknown email is not an error.
|
||||
UpdateHash(ctx context.Context, email, hash string) (bool, error)
|
||||
// Delete removes a credential record by email. The bool reports whether the email
|
||||
// was known.
|
||||
Delete(ctx context.Context, email string) (bool, error)
|
||||
}
|
||||
|
||||
// Record is a single stored credential: which profile an email belongs to and
|
||||
// its password hash. It deliberately does not embed any profile data — the
|
||||
// profile grain remains the source of truth for that.
|
||||
type Record struct {
|
||||
Email string `json:"email"`
|
||||
ProfileID uint64 `json:"profileId"`
|
||||
Hash string `json:"hash"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
Email string `json:"email"`
|
||||
ProfileID uint64 `json:"profileId"`
|
||||
Hash string `json:"hash"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
VerifiedAt string `json:"verifiedAt,omitempty"`
|
||||
}
|
||||
|
||||
// CredentialStore is a small email→credential index persisted to a JSON file.
|
||||
@@ -61,8 +84,9 @@ func NormalizeEmail(email string) string {
|
||||
return strings.ToLower(strings.TrimSpace(email))
|
||||
}
|
||||
|
||||
// Get returns the record for email and whether it exists.
|
||||
func (s *CredentialStore) Get(email string) (Record, bool) {
|
||||
// Get returns the record for email and whether it exists. The ctx is accepted
|
||||
// for interface parity with the Redis store; the file store ignores it.
|
||||
func (s *CredentialStore) Get(_ context.Context, email string) (Record, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
r, ok := s.byMail[NormalizeEmail(email)]
|
||||
@@ -71,7 +95,7 @@ func (s *CredentialStore) Get(email string) (Record, bool) {
|
||||
|
||||
// Register adds a new credential record and persists the store. It returns
|
||||
// ErrEmailExists if the email is already taken.
|
||||
func (s *CredentialStore) Register(email string, profileID uint64, hash, createdAt string) error {
|
||||
func (s *CredentialStore) Register(_ context.Context, email string, profileID uint64, hash, createdAt string) error {
|
||||
key := NormalizeEmail(email)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -82,6 +106,54 @@ func (s *CredentialStore) Register(email string, profileID uint64, hash, created
|
||||
return s.persistLocked()
|
||||
}
|
||||
|
||||
// MarkVerified records that email completed email verification and persists the
|
||||
// store. It is a no-op if already verified. The bool reports whether the email
|
||||
// was known; an unknown email is not an error (callers avoid leaking which
|
||||
// emails exist).
|
||||
func (s *CredentialStore) MarkVerified(_ context.Context, email, verifiedAt string) (bool, error) {
|
||||
key := NormalizeEmail(email)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
rec, ok := s.byMail[key]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if rec.VerifiedAt != "" {
|
||||
return true, nil
|
||||
}
|
||||
rec.VerifiedAt = verifiedAt
|
||||
s.byMail[key] = rec
|
||||
return true, s.persistLocked()
|
||||
}
|
||||
|
||||
// UpdateHash replaces the stored password hash for email and persists the store.
|
||||
// The bool reports whether the email was known; an unknown email is not an error.
|
||||
func (s *CredentialStore) UpdateHash(_ context.Context, email, hash string) (bool, error) {
|
||||
key := NormalizeEmail(email)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
rec, ok := s.byMail[key]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
rec.Hash = hash
|
||||
s.byMail[key] = rec
|
||||
return true, s.persistLocked()
|
||||
}
|
||||
|
||||
// Delete removes the credential record for email and persists the store.
|
||||
// The bool reports whether the email was known.
|
||||
func (s *CredentialStore) Delete(_ context.Context, email string) (bool, error) {
|
||||
key := NormalizeEmail(email)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.byMail[key]; !ok {
|
||||
return false, nil
|
||||
}
|
||||
delete(s.byMail, key)
|
||||
return true, s.persistLocked()
|
||||
}
|
||||
|
||||
// persistLocked writes the whole store to disk atomically. Caller holds s.mu.
|
||||
func (s *CredentialStore) persistLocked() error {
|
||||
records := make([]Record, 0, len(s.byMail))
|
||||
|
||||
Reference in New Issue
Block a user