package movement import ( "crypto/rand" "crypto/sha256" "encoding/hex" "time" ) // TokenLifetime is how long a confirmation link stays good. Long enough for // somebody to come back to their inbox tomorrow; short enough that a link // sitting in an archive forever is not a live credential. const TokenLifetime = 24 * time.Hour // mintToken returns the token to mail and the hash to store. // // The token is random, not derived from the instance key. Deriving it would // mean a key rotation silently killed every link already in somebody's inbox, // and would make the key a second thing that has to survive for the app to // keep its promises. Only the hash is stored, so a database read does not // hand somebody the ability to confirm other people's addresses. func mintToken() (token string, hash []byte) { var raw [32]byte if _, err := rand.Read(raw[:]); err != nil { // Every property of the confirmation flow rests on this being // unguessable. There is no degraded mode worth having. panic("movement: crypto/rand unavailable") } token = hex.EncodeToString(raw[:]) return token, hashToken(token) } // hashToken is how a presented token is looked up. Plain SHA-256 is right // here and would be wrong for a password: the input is 256 bits of entropy we // generated, so there is nothing to brute-force and nothing a slow hash would // buy. func hashToken(token string) []byte { sum := sha256.Sum256([]byte(token)) return sum[:] }