mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
144 lines
3.0 KiB
Go
144 lines
3.0 KiB
Go
package hibp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
apiURL = "https://haveibeenpwned.com/api/v3/breachedaccount/"
|
|
clientTimeoutSecs = 2
|
|
userAgent = "WTFUtil"
|
|
)
|
|
|
|
type hibpError struct {
|
|
StatusCode int `json:"statusCode"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (widget *Widget) fullURL(account string, truncated bool) string {
|
|
truncStr := "false"
|
|
if truncated {
|
|
truncStr = "true"
|
|
}
|
|
|
|
return apiURL + account + fmt.Sprintf("?truncateResponse=%s", truncStr)
|
|
}
|
|
|
|
func (widget *Widget) fetchForAccount(account string, since string) (*Status, error) {
|
|
if account == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
hibpClient := http.Client{
|
|
Timeout: time.Second * clientTimeoutSecs,
|
|
}
|
|
|
|
asTruncated := true
|
|
if since != "" {
|
|
asTruncated = false
|
|
}
|
|
|
|
request, err := http.NewRequest(http.MethodGet, widget.fullURL(account, asTruncated), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
request.Header.Set("User-Agent", userAgent)
|
|
request.Header.Set("hibp-api-key", widget.settings.apiKey)
|
|
|
|
response, getErr := hibpClient.Do(request)
|
|
if getErr != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body, readErr := ioutil.ReadAll(response.Body)
|
|
if readErr != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hibpErr := widget.validateHTTPResponse(response.StatusCode, body)
|
|
if hibpErr != nil {
|
|
return nil, errors.New(hibpErr.Message)
|
|
}
|
|
|
|
stat, err := widget.parseResponseBody(account, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return stat, nil
|
|
}
|
|
|
|
func (widget *Widget) parseResponseBody(account string, body []byte) (*Status, error) {
|
|
breaches := []Breach{}
|
|
stat := NewStatus(account, breaches)
|
|
|
|
if len(body) == 0 {
|
|
// If the body is empty then there's no breaches
|
|
return stat, nil
|
|
}
|
|
|
|
jsonErr := json.Unmarshal(body, &breaches)
|
|
if jsonErr != nil {
|
|
return stat, jsonErr
|
|
}
|
|
|
|
breaches = widget.filterBreaches(breaches)
|
|
stat.Breaches = breaches
|
|
|
|
return stat, nil
|
|
}
|
|
|
|
func (widget *Widget) filterBreaches(breaches []Breach) []Breach {
|
|
// If there's no valid since value in the settings, there's no point in trying to filter
|
|
// the breaches on that value, they'll all pass
|
|
if !widget.settings.HasSince() {
|
|
return breaches
|
|
}
|
|
|
|
sinceDate, err := widget.settings.SinceDate()
|
|
if err != nil {
|
|
return breaches
|
|
}
|
|
|
|
latestBreaches := []Breach{}
|
|
|
|
for _, breach := range breaches {
|
|
breachDate, err := breach.BreachDate()
|
|
if err != nil {
|
|
// Append the erring breach here because a failing breach date doesn't mean that
|
|
// the breach itself isn't applicable. The date could be missing or malformed,
|
|
// in which case we err on the side of caution and assume that the breach is valid
|
|
latestBreaches = append(latestBreaches, breach)
|
|
continue
|
|
}
|
|
|
|
if breachDate.After(sinceDate) {
|
|
latestBreaches = append(latestBreaches, breach)
|
|
}
|
|
}
|
|
|
|
return latestBreaches
|
|
}
|
|
|
|
func (widget *Widget) validateHTTPResponse(responseCode int, body []byte) *hibpError {
|
|
hibpErr := &hibpError{}
|
|
|
|
switch responseCode {
|
|
case 401, 402:
|
|
err := json.Unmarshal(body, hibpErr)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
default:
|
|
hibpErr = nil
|
|
}
|
|
|
|
return hibpErr
|
|
}
|