Linting #509

Merged
tech merged 46 commits from main into dis-474 2026-02-13 16:14:15 +00:00
2 changed files with 45 additions and 15 deletions
Showing only changes of commit e605c734ad - Show all commits

View File

@@ -29,6 +29,35 @@ func syntheticIdempotencyKey() string {
return "auto:" + bson.NewObjectID().Hex() return "auto:" + bson.NewObjectID().Hex()
} }
func verificationContextFilter(request *verification.Request) builder.Query {
return repository.Query().And(
repository.Filter("accountRef", request.AccountRef),
repository.Filter("purpose", request.Purpose),
repository.Filter("target", request.Target),
)
}
func activeContextFilter(request *verification.Request, now time.Time) builder.Query {
return repository.Query().And(
repository.Filter("accountRef", request.AccountRef),
repository.Filter("purpose", request.Purpose),
repository.Filter("target", request.Target),
repository.Filter("usedAt", nil),
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
)
}
func cooldownActiveContextFilter(request *verification.Request, now, cutoff time.Time) builder.Query {
return repository.Query().And(
repository.Filter("accountRef", request.AccountRef),
repository.Filter("purpose", request.Purpose),
repository.Filter("target", request.Target),
repository.Filter("usedAt", nil),
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
repository.Query().Comparison(repository.Field("createdAt"), builder.Gt, cutoff),
)
}
func idempotencyFilter( func idempotencyFilter(
request *verification.Request, request *verification.Request,
idempotencyKey string, idempotencyKey string,
@@ -140,13 +169,7 @@ func (db *verificationDB) Create(
_, err = db.tf.CreateTransaction().Execute(ctx, func(tx context.Context) (any, error) { _, err = db.tf.CreateTransaction().Execute(ctx, func(tx context.Context) (any, error) {
now := time.Now().UTC() now := time.Now().UTC()
baseFilter := repository.Query().And( activeFilter := activeContextFilter(request, now)
repository.Filter("accountRef", request.AccountRef),
repository.Filter("purpose", request.Purpose),
repository.Filter("target", request.Target),
repository.Filter("usedAt", nil),
repository.Query().Comparison(repository.Field("expiresAt"), builder.Gt, now),
)
// Optional idempotency key support for safe retries. // Optional idempotency key support for safe retries.
if hasIdempotency { if hasIdempotency {
@@ -177,12 +200,8 @@ func (db *verificationDB) Create(
if request.Cooldown != nil { if request.Cooldown != nil {
cutoff := now.Add(-*request.Cooldown) cutoff := now.Add(-*request.Cooldown)
cooldownFilter := baseFilter.And(
repository.Query().Comparison(repository.Field("createdAt"), builder.Gt, cutoff),
)
var recent model.VerificationToken var recent model.VerificationToken
err := db.DBImp.FindOne(tx, cooldownFilter, &recent) err := db.DBImp.FindOne(tx, cooldownActiveContextFilter(request, now, cutoff), &recent)
switch { switch {
case err == nil: case err == nil:
return nil, verification.ErrorCooldownActive() return nil, verification.ErrorCooldownActive()
@@ -195,7 +214,7 @@ func (db *verificationDB) Create(
// 2) Invalidate active tokens for this context // 2) Invalidate active tokens for this context
if _, err := db.DBImp.PatchMany( if _, err := db.DBImp.PatchMany(
tx, tx,
baseFilter, activeFilter,
repository.Patch().Set(repository.Field("usedAt"), now), repository.Patch().Set(repository.Field("usedAt"), now),
); err != nil { ); err != nil {
return nil, err return nil, err

View File

@@ -849,15 +849,26 @@ func TestCreate_CooldownExpiresAllowsCreation(t *testing.T) {
accountRef := bson.NewObjectID() accountRef := bson.NewObjectID()
// First creation without cooldown. // First creation without cooldown.
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) firstRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
require.NoError(t, err) require.NoError(t, err)
time.Sleep(2 * time.Millisecond) time.Sleep(2 * time.Millisecond)
// Re-create with short cooldown — the prior token is old enough to be invalidated. // Re-create with short cooldown — the prior token is old enough to be invalidated.
r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Millisecond) r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Millisecond)
_, err = db.Create(ctx, r2) secondRaw, err := db.Create(ctx, r2)
require.NoError(t, err) require.NoError(t, err)
assert.NotEqual(t, firstRaw, secondRaw)
// Old token should be rotated out after successful re-issue.
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, firstRaw)
require.Error(t, err)
assert.True(t, errors.Is(err, verification.ErrTokenAlreadyUsed))
// New token remains valid.
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, secondRaw)
require.NoError(t, err)
assert.Equal(t, accountRef, tok.AccountRef)
} }
func TestCreate_CooldownNilIgnored(t *testing.T) { func TestCreate_CooldownNilIgnored(t *testing.T) {