97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
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
|
|
}
|