73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
package verificationimp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/tech/sendico/pkg/db/repository"
|
|
"github.com/tech/sendico/pkg/db/repository/builder"
|
|
"github.com/tech/sendico/pkg/db/verification"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (db *verificationDB) Consume(
|
|
ct context.Context,
|
|
rawToken string,
|
|
) (*model.VerificationToken, error) {
|
|
|
|
hash := tokenHash(rawToken)
|
|
now := time.Now().UTC()
|
|
|
|
// 1) Пытаемся атомарно использовать токен
|
|
filter := repository.Query().And(
|
|
repository.Filter("verifyTokenHash", hash),
|
|
repository.Filter("usedAt", nil),
|
|
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
|
|
)
|
|
|
|
t, err := db.tf.CreateTransaction().Execute(
|
|
ct,
|
|
func(ctx context.Context) (any, error) {
|
|
var existing model.VerificationToken
|
|
if err := db.DBImp.FindOne(ctx, filter, &existing); err != nil {
|
|
if errors.Is(err, merrors.ErrNoData) {
|
|
// normal behaviour
|
|
db.Logger.Debug("Token hash not found", zap.Error(err), zap.String("hash", hash))
|
|
return nil, wrap(verification.ErrTokenNotFound, err.Error())
|
|
}
|
|
db.Logger.Warn("Failed to check token", zap.Error(err), zap.String("hash", hash))
|
|
return nil, err
|
|
}
|
|
if existing.UsedAt != nil {
|
|
db.Logger.Debug("Token has already been used", zap.String("hash", hash), zap.Time("used_at", *existing.UsedAt))
|
|
return nil, wrap(verification.ErrTokenAlreadyUsed, "db: token already used")
|
|
}
|
|
|
|
if existing.ExpiresAt.Before(now) {
|
|
db.Logger.Debug("Token has already expired", zap.String("hash", hash), zap.Time("expired_at", existing.ExpiresAt))
|
|
return nil, wrap(verification.ErrTokenExpired, "db: token expired")
|
|
}
|
|
|
|
existing.UsedAt = &now
|
|
if err := db.DBImp.Update(ctx, &existing); err != nil {
|
|
db.Logger.Warn("Failed to consume token", zap.Error(err), zap.String("hash", hash))
|
|
return nil, err
|
|
}
|
|
|
|
return &existing, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, ok := t.(*model.VerificationToken)
|
|
if !ok {
|
|
return nil, merrors.Internal("unexpexted token type")
|
|
}
|
|
|
|
return res, nil
|
|
}
|