package calls import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "strings" ) // sqidAlphabet is Docs' and Slides' alphabet, character for character: // no 0/O, no 1/l/I, so a link read aloud survives. const sqidAlphabet = "23456789abcdefghjkmnpqrstuvwxyz" const sqidLength = 10 const maxSqidAttempts = 5 // validSqid reports whether s is exactly the shape newSqid ever // produces: ten characters, every one of them in sqidAlphabet. A // caller that trusts a room id straight from a URL — a rate limiter's // key, a log line — must check this first: chi hands back whatever the // request line contained, unbounded (review Task 5 item 1). func validSqid(s string) bool { return len(s) == sqidLength && strings.Trim(s, sqidAlphabet) == "" } func newSqid() (string, error) { raw := make([]byte, sqidLength) if _, err := rand.Read(raw); err != nil { return "", fmt.Errorf("calls: generate sqid: %w", err) } out := make([]byte, sqidLength) for i, b := range raw { out[i] = sqidAlphabet[int(b)%len(sqidAlphabet)] } return string(out), nil } const linkTokenBytes = 32 // newLinkToken mints a token and its lookup hash. The store seals invitations // that need to be shared again; callers never persist the plaintext token. func newLinkToken() (token, hash string, err error) { buf := make([]byte, linkTokenBytes) if _, err := rand.Read(buf); err != nil { return "", "", err } token = base64.RawURLEncoding.EncodeToString(buf) return token, hashLinkToken(token), nil } func hashLinkToken(token string) string { sum := sha256.Sum256([]byte(token)) return hex.EncodeToString(sum[:]) }