82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
package verificationimp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/tech/sendico/pkg/db/repository"
|
|
"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) Find token by hash (do NOT filter by usedAt/expiresAt here),
|
|
// otherwise you can't distinguish "used/expired" from "not found".
|
|
filter := repository.Query().And(
|
|
repository.Filter("verifyTokenHash", hash),
|
|
)
|
|
|
|
t, e := 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) {
|
|
db.Logger.Debug("Token hash not found", zap.Error(err), zap.String("hash", hash))
|
|
return nil, verification.ErorrTokenNotFound()
|
|
}
|
|
db.Logger.Warn("Failed to check token", zap.Error(err), zap.String("hash", hash))
|
|
return nil, err
|
|
}
|
|
|
|
// 2) Semantic checks
|
|
if existing.UsedAt != nil {
|
|
db.Logger.Debug(
|
|
"Token has already been used",
|
|
zap.String("hash", hash),
|
|
zap.Time("used_at", *existing.UsedAt),
|
|
)
|
|
return nil, verification.ErorrTokenAlreadyUsed()
|
|
}
|
|
|
|
if !existing.ExpiresAt.After(now) { // includes equal time edge-case
|
|
db.Logger.Debug(
|
|
"Token has already expired",
|
|
zap.String("hash", hash),
|
|
zap.Time("expired_at", existing.ExpiresAt),
|
|
)
|
|
return nil, verification.ErorrTokenExpired()
|
|
}
|
|
|
|
// 3) Mark as used
|
|
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 e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
res, ok := t.(*model.VerificationToken)
|
|
if !ok {
|
|
return nil, merrors.Internal("unexpected token type")
|
|
}
|
|
|
|
return res, nil
|
|
}
|