package verificationimp import ( "context" "crypto/rand" "encoding/base64" "time" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mutil/mzap" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) const verificationTokenBytes = 32 func newVerificationToken( accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, ttl time.Duration, ) (*model.VerificationToken, string, error) { raw := make([]byte, verificationTokenBytes) if _, err := rand.Read(raw); err != nil { return nil, "", err } rawToken := base64.RawURLEncoding.EncodeToString(raw) hashStr := tokenHash(rawToken) now := time.Now().UTC() token := &model.VerificationToken{ AccountRef: accountRef, Purpose: purpose, Target: target, VerifyTokenHash: hashStr, UsedAt: nil, ExpiresAt: now.Add(ttl), } return token, rawToken, nil } func (db *verificationDB) Create( ctx context.Context, accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, ttl time.Duration, ) (string, error) { logFields := []zap.Field{ zap.String("purpose", string(purpose)), zap.Duration("ttl", ttl), mzap.AccRef(accountRef), zap.String("target", target), } token, raw, err := newVerificationToken(accountRef, purpose, target, ttl) if err != nil { db.Logger.Warn("Failed to generate verification token", append(logFields, zap.Error(err))...) return "", err } // Invalidate any active tokens for the same (accountRef, purpose, target). now := time.Now().UTC() invalidated, err := db.DBImp.PatchMany(ctx, repository.Query().And( repository.Filter("accountRef", accountRef), repository.Filter("purpose", purpose), repository.Filter("target", target), repository.Filter("usedAt", nil), repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now), ), repository.Patch().Set(repository.Field("usedAt"), now), ) if err != nil { db.Logger.Warn("Failed to invalidate previous tokens", append(logFields, zap.Error(err))...) return "", err } if invalidated > 0 { db.Logger.Debug("Invalidated previous tokens", append(logFields, zap.Int("count", invalidated))...) } if err := db.DBImp.Create(ctx, token); err != nil { db.Logger.Warn("Failed to persist verification token", append(logFields, zap.Error(err))...) return "", err } db.Logger.Debug("Verification token created", append(logFields, zap.String("hash", token.VerifyTokenHash))...) return raw, nil }