70 lines
2.0 KiB
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
|
|
}
|