all repos

onasty @ d631546375d459cdab680dd5fae617bf2fba94c4

a one-time notes service

onasty/internal/store/psql/passwordtokrepo/passwordtokrepo.go (view raw)

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