Repository: caddyserver/caddy
File: modules/caddyhttp/caddyauth/basicauth.go (Go)

Code before:

	return User{ID: username}, true, nil
}

func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
	compare := func() (bool, error) {
		return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
	}

	// if no caching is enabled, simply return the result of hashing + comparing
	if hba.HashCache == nil {
		return compare()
	}

	// compute a cache key that is unique for these input parameters
	cacheKey := hex.EncodeToString(append(append(account.password, account.salt...), plaintextPassword...))

	// fast track: if the result of the input is already cached, use it
	hba.HashCache.mu.RLock()
	same, ok := hba.HashCache.cache[cacheKey]
	hba.HashCache.mu.RUnlock()
	if ok {
		return same, nil
	}
	// slow track: do the expensive op, then add it to the cache
	// but it occurred the thunder herd, thus it uses singleflight
	v, err, _ := hba.HashCache.g.Do(cacheKey, func() (any, error) {
		return compare()
	})
	if err != nil {
		return false, err
	}
	same = v.(bool)
	hba.HashCache.mu.Lock()
	if len(hba.HashCache.cache) >= 1000 {
		hba.HashCache.makeRoom() // keep cache size under control
	}
	hba.HashCache.cache[cacheKey] = same
	hba.HashCache.mu.Unlock()

	return same, nil
}

func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error) (User, bool, error) {
	// browsers show a message that says something like:
	// "The website says: <realm>"
	// which is kinda dumb, but whatever.
	realm := hba.Realm
	if realm == "" {
		realm = "restricted"
	}

Review comment (performance):
```suggestion
    // slow track: do the expensive op, then add it to the cache
	// but perform it in a singleflight group so that multiple
	// parallel requests using the same password don't cause a
	// thundering herd problem by all performing the same hashing
	// operation before the first one finishes and caches it.
```

Code after:

	return User{ID: username}, true, nil
}

func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
	compare := func() (bool, error) {
		return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
	}

	// if no caching is enabled, simply return the result of hashing + comparing
	if hba.HashCache == nil {
		return compare()
	}

	// compute a cache key that is unique for these input parameters
	cacheKey := hex.EncodeToString(append(append(account.password, account.salt...), plaintextPassword...))

	// fast track: if the result of the input is already cached, use it
	hba.HashCache.mu.RLock()
	same, ok := hba.HashCache.cache[cacheKey]
	hba.HashCache.mu.RUnlock()
	if ok {
		return same, nil
	}
	// slow track: do the expensive op, then add it to the cache
	// but perform it in a singleflight group so that multiple
	// parallel requests using the same password don't cause a
	// thundering herd problem by all performing the same hashing
	// operation before the first one finishes and caches it.
	v, err, _ := hba.HashCache.g.Do(cacheKey, func() (any, error) {
		return compare()
	})
	if err != nil {
		return false, err
	}
	same = v.(bool)
	hba.HashCache.mu.Lock()
	if len(hba.HashCache.cache) >= 1000 {
		hba.HashCache.makeRoom() // keep cache size under control
	}
	hba.HashCache.cache[cacheKey] = same
	hba.HashCache.mu.Unlock()

	return same, nil
}

func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error) (User, bool, error) {
	// browsers show a message that says something like:
	// "The website says: <realm>"
	// which is kinda dumb, but whatever.
	realm := hba.Realm