unified code verification service

This commit is contained in:
Stephan D
2026-02-10 01:55:33 +01:00
parent 76c3bfdea9
commit 7f540671c1
120 changed files with 1863 additions and 1394 deletions

View File

@@ -6,9 +6,12 @@ import (
)
var (
ErrTokenNotFound = errors.New("vtNotFound")
ErrTokenAlreadyUsed = errors.New("vtAlreadyUsed")
ErrTokenExpired = errors.New("vtExpired")
ErrTokenNotFound = errors.New("vtNotFound")
ErrTokenAlreadyUsed = errors.New("vtAlreadyUsed")
ErrTokenExpired = errors.New("vtExpired")
ErrTokenAttemptsExceeded = errors.New("vtAttemptsExceeded")
ErrCooldownActive = errors.New("vtCooldownActive")
ErrIdempotencyConflict = errors.New("vtIdempotencyConflict")
)
func wrap(err error, msg string) error {
@@ -26,3 +29,15 @@ func ErorrTokenAlreadyUsed() error {
func ErorrTokenExpired() error {
return wrap(ErrTokenExpired, "verification token expired")
}
func ErrorCooldownActive() error {
return wrap(ErrCooldownActive, "token creation cooldown is active")
}
func ErrorTokenAttemptsExceeded() error {
return wrap(ErrTokenAttemptsExceeded, "verification token max attempts exceeded")
}
func ErrorIdempotencyConflict() error {
return wrap(ErrIdempotencyConflict, "verification token request idempotency key has already been used")
}

View File

@@ -0,0 +1,70 @@
package verification
import (
"strings"
"time"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/v2/bson"
)
type TokenKind = string
const (
TokenKindOTP TokenKind = "otp"
TokenKindLink TokenKind = "link"
)
type Request struct {
AccountRef bson.ObjectID
Purpose model.VerificationPurpose
Target string
Ttl time.Duration
Kind TokenKind
MaxRetries *int
Cooldown *time.Duration
IdempotencyKey *string // Optional key to make Create idempotent for retries.
}
func newRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, kind TokenKind) *Request {
return &Request{
AccountRef: accountRef,
Purpose: purpose,
Target: target,
Kind: kind,
Ttl: 15 * time.Minute, // default TTL for verification tokens
}
}
func NewLinkRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request {
return newRequest(accountRef, purpose, target, TokenKindLink)
}
func NewOTPRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request {
return newRequest(accountRef, purpose, target, TokenKindOTP)
}
func (r *Request) WithTTL(ttl time.Duration) *Request {
r.Ttl = ttl
return r
}
func (r *Request) WithMaxRetries(maxRetries int) *Request {
r.MaxRetries = &maxRetries
return r
}
func (r *Request) WithCooldown(cooldown time.Duration) *Request {
r.Cooldown = &cooldown
return r
}
func (r *Request) WithIdempotencyKey(key string) *Request {
normalized := strings.TrimSpace(key)
if normalized == "" {
r.IdempotencyKey = nil
return r
}
r.IdempotencyKey = &normalized
return r
}

View File

@@ -2,7 +2,6 @@ package verification
import (
"context"
"time"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -10,12 +9,6 @@ import (
type DB interface {
// template.DB[*model.VerificationToken]
Create(
ctx context.Context,
accountRef bson.ObjectID,
purpose model.VerificationPurpose,
target string,
ttl time.Duration,
) (rawToken string, err error)
Consume(ctx context.Context, rawToken string) (*model.VerificationToken, error)
Create(ctx context.Context, request *Request) (verificationToken string, err error)
Consume(ctx context.Context, accountRef bson.ObjectID, purpose model.VerificationPurpose, verificationToken string) (*model.VerificationToken, error)
}