pkg/pairingcode/pairingcode.go
Rama master · réplica de lectura del repositorio original.
// Package pairingcode generates and validates short, human-readable codes — the
// kind a person reads off one screen and types on another (device pairing,
// invitation links). Codes use a Crockford-style alphabet with the visually
// ambiguous characters removed.
package pairingcode
import (
"crypto/rand"
"strings"
)
// Alphabet is a Crockford-style alphabet with the visually ambiguous characters
// removed (no 0/O, 1/I/L, or U), so a human can read a code off one screen and
// type it on another without confusion. 30 characters.
const Alphabet = "23456789ABCDEFGHJKMNPQRSTVWXYZ"
// Canonical code lengths. Device codes keep 8 (~2^39) because they live minutes
// and only a logged-in user redeems them; invitation codes use 12 (~2^59)
// because their link lives for days.
const (
DeviceCodeLength = 8
InviteCodeLength = 12
)
// New returns a fresh canonical code of the given length: uppercase, no
// separator. It draws from crypto/rand with rejection sampling so every
// character is uniform (a plain byte%30 would bias the first 256%30 characters).
func New(length int) string {
rejectAtOrAbove := byte(256 - (256 % len(Alphabet)))
out := make([]byte, 0, length)
buf := make([]byte, length)
for len(out) < length {
rand.Read(buf)
for _, b := range buf {
if b >= rejectAtOrAbove {
continue
}
out = append(out, Alphabet[int(b)%len(Alphabet)])
if len(out) == length {
break
}
}
}
return string(out)
}
// Normalize canonicalizes a code as typed by a user: uppercased, with the
// display grouping (hyphens) and any stray spaces removed. It does not validate
// — pair it with IsValid.
func Normalize(code string) string {
code = strings.ToUpper(code)
code = strings.ReplaceAll(code, "-", "")
code = strings.ReplaceAll(code, " ", "")
return code
}
// IsValid reports whether code is a well-formed canonical code (right length,
// only alphabet characters). Callers should Normalize first.
func IsValid(code string, length int) bool {
if len(code) != length {
return false
}
for _, r := range code {
if !strings.ContainsRune(Alphabet, r) {
return false
}
}
return true
}