// postLogin generates a new API key for this user func postLogin(session data.UserRecord) ([]byte, error) { // Create key for this user's session key := new(data.APIKey) if err := key.Create(session.ID); err != nil { return nil, err } // Store key in database if err := key.Save(); err != nil { return nil, err } // Convert key to JSON form jsonKey, err := key.ToJSON() if err != nil { return nil, err } // Marshal into JSON res, err := json.Marshal(jsonKey) if err != nil { return nil, err } return res, nil }
// 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 }
// TestBasicAuthenticator verifies that BasicAuthenticator.Auth works properly func TestBasicAuthenticator(t *testing.T) { log.Println("TestBasicAuthenticator()") // Load config config := common.LoadConfig() common.Static.Config = config // Generate mock user user := data.UserRecord{ Username: "******", Passkey: "abcdef0123456789", TorrentLimit: 10, } // Save mock user if !user.Save() { t.Fatalf("Failed to save mock user") } // Load mock user to fetch ID user = user.Load(user.Username, "username") if user == (data.UserRecord{}) { t.Fatalf("Failed to load mock user") } // Generate an API key and salt pass := "******" salt := "test" sha := sha1.New() sha.Write([]byte(pass + salt)) hash := fmt.Sprintf("%x", sha.Sum(nil)) // Generate mock API key key := data.APIKey{ UserID: user.ID, Key: hash, Salt: salt, } // Save mock API key if !key.Save() { t.Fatalf("Failed to save mock data.APIKey") } // Load mock data.APIKey to fetch ID key = key.Load(key.Key, "key") if key == (data.APIKey{}) { t.Fatalf("Failed to load mock data.APIKey") } // Generate mock HTTP request r := http.Request{} headers := map[string][]string{ "Authorization": {"Basic " + base64.URLEncoding.EncodeToString([]byte(user.Username+":"+pass))}, } r.Header = headers // Perform authentication request auth := new(BasicAuthenticator).Auth(&r) if !auth { t.Fatalf("Failed to authenticate using BasicAuthenticator") } // Delete mock user if !user.Delete() { t.Fatalf("Failed to delete mock user") } // Delete mock API key if !key.Delete() { t.Fatalf("Failed to delete mock data.APIKey") } }