// Auth handles validation of HMAC-SHA1 authentication func (a *HMACAuthenticator) Auth(r *http.Request) (error, error) { // Check for Authorization header auth := r.Header.Get("Authorization") if auth == "" { // Check for X-Goat-Authorization header override auth = r.Header.Get("X-Goat-Authorization") } // Fetch credentials from HTTP Basic auth pubkey, credentials, err := basicCredentials(auth) if err != nil { return err, nil } // Split credentials into nonce and API signature pair := strings.Split(credentials, "/") if len(pair) < 2 { return errors.New("no nonce value"), nil } nonce := pair[0] signature := pair[1] // Check if nonce previously used, add it if it is not, to prevent replay attacks // note: bloom filter may report false positives, but better safe than sorry if nonceFilter.TestAndAdd([]byte(nonce)) { return errors.New("repeated API request"), nil } // Load API key by pubkey key, err := new(data.APIKey).Load(pubkey, "pubkey") if err != nil || key == (data.APIKey{}) { return errors.New("no such public key"), err } // Check if key is expired, delete it if it is if key.Expire <= time.Now().Unix() { go func(key data.APIKey) { if err := key.Delete(); err != nil { log.Println(err.Error()) } }(key) return errors.New("expired API key"), nil } // Generate API signature expected, err := apiSignature(key.UserID, nonce, r.Method, r.URL.Path, key.Secret) if err != nil { return nil, errors.New("failed to generate API signature") } // Verify that HMAC signature is correct if !hmac.Equal([]byte(signature), []byte(expected)) { return errors.New("invalid API signature"), nil } // Update API key expiration time key.Expire = time.Now().Add(7 * 24 * time.Hour).Unix() go func(key data.APIKey) { if err := key.Save(); err != nil { log.Println(err.Error()) } }(key) // Load user by user ID user, err := new(data.UserRecord).Load(key.UserID, "id") if err != nil || user == (data.UserRecord{}) { return errors.New("no such user"), err } // Store user for session a.session = user return nil, nil }