74 lines
2.1 KiB
Go
74 lines
2.1 KiB
Go
// Package ids generates and validates the identifiers used across Relata.
|
|
//
|
|
// Every persisted record uses a UUIDv4 string primary key. Chunk IDs are used
|
|
// verbatim as Qdrant point IDs, which Qdrant accepts in UUID form; that removes
|
|
// any need for a separate ID mapping table.
|
|
package ids
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// New returns a random UUIDv4 string.
|
|
func New() string { return uuid.NewString() }
|
|
|
|
// Valid reports whether s is a well-formed UUID. Used to reject client-supplied
|
|
// identifiers before they reach a database query.
|
|
func Valid(s string) bool {
|
|
if len(s) != 36 {
|
|
return false
|
|
}
|
|
_, err := uuid.Parse(s)
|
|
return err == nil
|
|
}
|
|
|
|
// Token returns a URL-safe random string with n bytes of entropy, used for
|
|
// session and CSRF tokens.
|
|
func Token(n int) (string, error) {
|
|
b := make([]byte, n)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
// HashToken returns the hex SHA-256 of a token. Sessions store this rather than
|
|
// the token itself so that database contents cannot be replayed as credentials.
|
|
func HashToken(tok string) string {
|
|
sum := sha256.Sum256([]byte(tok))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
// Deterministic derives a stable UUID from parts, used for idempotency keys and
|
|
// dedupe keys so that reprocessing replaces rather than duplicates records.
|
|
func Deterministic(parts ...string) string {
|
|
h := sha256.New()
|
|
for _, p := range parts {
|
|
h.Write([]byte(p))
|
|
h.Write([]byte{0}) // separator prevents ("ab","c") colliding with ("a","bc")
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
// DeterministicUUID derives a stable UUIDv5-shaped identifier from parts. Used
|
|
// where a UUID is structurally required (Qdrant point IDs) but the value must be
|
|
// reproducible across runs.
|
|
func DeterministicUUID(parts ...string) string {
|
|
h := sha256.New()
|
|
for _, p := range parts {
|
|
h.Write([]byte(p))
|
|
h.Write([]byte{0})
|
|
}
|
|
sum := h.Sum(nil)
|
|
var u uuid.UUID
|
|
copy(u[:], sum[:16])
|
|
u[6] = (u[6] & 0x0f) | 0x50 // version 5
|
|
u[8] = (u[8] & 0x3f) | 0x80 // RFC 4122 variant
|
|
return u.String()
|
|
}
|