onasty/internal/store/psql/passwordtokrepo/passwordtokrepo.go(view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
package passwordtokrepo
import (
"context"
"errors"
"time"
"github.com/gofrs/uuid/v5"
"github.com/henvic/pgq"
"github.com/jackc/pgx/v5"
"github.com/olexsmir/onasty/internal/models"
"github.com/olexsmir/onasty/internal/store/psqlutil"
)
type PasswordResetTokenStorer interface {
// Create a new password reset token.
Create(ctx context.Context, input models.ResetPasswordToken) error
// GetUserIDByTokenAndMarkAsUsed gets the token, and marks it as used.
//
// In case the token is not found, returns [model.ErrResetPasswordTokenNotFound]
// If token if used, or expired, returns [model.ErrResetPasswordTokenAlreadyUsed],
// or [models.ErrResetPasswordTokenExpired].
GetUserIDByTokenAndMarkAsUsed(
ctx context.Context,
token string,
usedAT time.Time,
) (uuid.UUID, error)
}
var _ PasswordResetTokenStorer = (*PasswordResetTokenRepo)(nil)
type PasswordResetTokenRepo struct {
db *psqlutil.DB
}
func NewPasswordResetTokenRepo(db *psqlutil.DB) *PasswordResetTokenRepo {
return &PasswordResetTokenRepo{
db: db,
}
}
func (r *PasswordResetTokenRepo) Create(ctx context.Context, token models.ResetPasswordToken,
) error {
query, aggs, err := pgq.
Insert("password_reset_tokens").
Columns("user_id", "token", "created_at", "expires_at").
Values(token.UserID, token.Token, token.CreatedAt, token.ExpiresAt).
SQL()
if err != nil {
return err
}
_, err = r.db.Exec(ctx, query, aggs...)
return err
}
func (r *PasswordResetTokenRepo) GetUserIDByTokenAndMarkAsUsed(
ctx context.Context,
token string,
usedAt time.Time,
) (uuid.UUID, error) {
tx, err := r.db.Begin(ctx)
if err != nil {
return uuid.Nil, err
}
defer tx.Rollback(ctx) //nolint:errcheck
var isUsed bool
var expiresAt time.Time
err = tx.QueryRow(ctx, "select (used_at is not null), expires_at from password_reset_tokens where token = $1", token).
Scan(&isUsed, &expiresAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return uuid.Nil, models.ErrResetPasswordTokenNotFound
}
return uuid.Nil, err
}
if isUsed {
return uuid.Nil, models.ErrResetPasswordTokenAlreadyUsed
}
if time.Now().After(expiresAt) {
return uuid.Nil, models.ErrResetPasswordTokenExpired
}
query := `--sql
update password_reset_tokens
set used_at = $1
where token = $2
returning user_id`
var userID uuid.UUID
err = tx.QueryRow(ctx, query, usedAt, token).Scan(&userID)
if err != nil {
return uuid.Nil, err
}
return userID, tx.Commit(ctx)
}
|