Files
website/internal/helpers/session/remember.go

70 lines
2.0 KiB
Go

package helpers
import (
"crypto/rand"
"crypto/sha256"
"database/sql"
"encoding/base64"
"time"
)
func randomBase64(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
func HashVerifier(verifier string) string {
sum := sha256.Sum256([]byte(verifier))
return base64.RawURLEncoding.EncodeToString(sum[:])
}
// StoreToken inserts a new token row
func StoreToken(db *sql.DB, userID int64, selector, verifierHash string, expiresAt time.Time) error {
_, err := db.Exec(`
INSERT INTO remember_tokens (user_id, selector, verifier_hash, issued_at, expires_at)
VALUES ($1,$2,$3,NOW(),$4)`, userID, selector, verifierHash, expiresAt)
return err
}
// FindToken fetches selector+hash
func FindToken(db *sql.DB, selector string) (userID int64, verifierHash string, expiresAt time.Time, revokedAt *time.Time, err error) {
err = db.QueryRow(`SELECT user_id, verifier_hash, expires_at, revoked_at FROM remember_tokens WHERE selector=$1`, selector).
Scan(&userID, &verifierHash, &expiresAt, &revokedAt)
return
}
// RevokeToken marks token as revoked
func RevokeToken(db *sql.DB, selector string) error {
_, err := db.Exec(`UPDATE remember_tokens SET revoked_at=NOW() WHERE selector=$1`, selector)
return err
}
// GenerateAndStore creates a new remember-me token, stores it server-side,
// and returns the cookie-safe plaintext value to set on the client
func GenerateAndStore(db *sql.DB, userID int64, duration time.Duration) (string, time.Time, error) {
selector, err := randomBase64(16)
if err != nil {
return "", time.Time{}, err
}
verifier, err := randomBase64(32)
if err != nil {
return "", time.Time{}, err
}
hash := HashVerifier(verifier)
expires := time.Now().Add(duration)
if err := StoreToken(db, userID, selector, hash, expires); err != nil {
return "", time.Time{}, err
}
// The client cookie value contains selector + verifier
cookieVal := selector + ":" + verifier
return cookieVal, expires, nil
}