Files
phylum/server/internal/core/user/reset.go
2025-06-05 20:53:29 +05:30

68 lines
1.9 KiB
Go

package user
import (
"net/http"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/shroff/phylum/server/internal/core/errors"
"github.com/shroff/phylum/server/internal/core/util/rand"
"github.com/shroff/phylum/server/internal/db"
)
var ErrTokenNotFound = errors.NewError(http.StatusBadRequest, "token_not_found", "Token Not Found")
const resetTokenLength = 64
const resetTokenDuration = 10 * time.Minute
func (m manager) CreateResetToken(user User) (string, error) {
const q = `INSERT INTO reset_tokens(user_id, token, expires)
VALUES (@user_id::INT, @token::TEXT, @expires::TIMESTAMP)
ON CONFLICT(user_id) DO UPDATE SET token = @token::TEXT, expires = @expires::TIMESTAMP`
token := rand.GenerateRandomString(resetTokenLength)
args := pgx.NamedArgs{
"user_id": user.ID,
"token": token,
"expires": time.Now().Add(resetTokenDuration),
}
if _, err := m.db.Exec(q, args); err != nil {
return "", err
}
return token, nil
}
func (m manager) ResetUserPassword(user User, token, password string) error {
const q = `DELETE FROM reset_tokens WHERE user_id = @user_id::INT AND token = @token::TEXT RETURNING expires`
args := pgx.NamedArgs{
"user_id": user.ID,
"token": token,
"expires": time.Now().Add(resetTokenDuration),
}
return m.db.RunInTx(func(d db.Handler) error {
m := m.withDb(d)
// UpdateUserPassword will ensure the password strength
// Not incorrect to do this before token verification because we are in a transaction.
// TODO: Are there perf implications for this in case of malicious actors?
err := m.UpdateUserPassword(user, password)
if err != nil {
return err
}
row := m.db.QueryRow(q, args)
var expires pgtype.Timestamp
if err := row.Scan(&expires); err == nil {
if time.Now().After(expires.Time) {
return ErrTokenNotFound
}
return nil
} else if errors.Is(err, pgx.ErrNoRows) {
return ErrTokenNotFound
} else {
return err
}
})
}