mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-26 22:19:32 -06:00
68 lines
1.9 KiB
Go
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
|
|
}
|
|
})
|
|
|
|
}
|