diff --git a/examples/breaches-api/breached-account/main.go b/examples/breaches-api/breached-account/main.go new file mode 100644 index 0000000..a690394 --- /dev/null +++ b/examples/breaches-api/breached-account/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "github.com/wneessen/go-hibp" + "os" +) + +func main() { + apiKey := os.Getenv("HIBP_API_KEY") + if apiKey == "" { + panic("A API key is required for this API") + } + hc := hibp.New(hibp.WithApiKey(apiKey)) + bd, _, err := hc.BreachApi.BreachedAccount("multiple-breaches@hibp-integration-tests.com") + if err != nil { + panic(err) + } + for _, b := range bd { + fmt.Printf("Your account was part of the %q breach\n", b.Name) + } +} diff --git a/examples/pastes-api/pasted-account/main.go b/examples/pastes-api/pasted-account/main.go new file mode 100644 index 0000000..4fbe464 --- /dev/null +++ b/examples/pastes-api/pasted-account/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "github.com/wneessen/go-hibp" + "os" +) + +func main() { + apiKey := os.Getenv("HIBP_API_KEY") + if apiKey == "" { + panic("A API key is required for this API") + } + hc := hibp.New(hibp.WithApiKey(apiKey)) + pd, _, err := hc.PasteApi.PastedAccount("account-exists@hibp-integration-tests.com") + if err != nil { + panic(err) + } + for _, p := range pd { + fmt.Printf("Your account was part of the %q paste\n", p.Title) + } +} diff --git a/hibp.go b/hibp.go index 7cdbd44..20c015b 100644 --- a/hibp.go +++ b/hibp.go @@ -12,7 +12,7 @@ import ( ) // Version represents the version of this package -const Version = "0.1.5" +const Version = "1.0.0" // BaseUrl is the base URL for the majority of API calls const BaseUrl = "https://haveibeenpwned.com/api/v3" @@ -36,7 +36,8 @@ type Client struct { PwnedPassApi *PwnedPassApi // Reference to the PwnedPassApi API PwnedPassApiOpts *PwnedPasswordOptions // Additional options for the PwnedPassApi API - BreachApi *BreachApi // Reference to the BreachApi API + BreachApi *BreachApi // Reference to the BreachApi + PasteApi *PasteApi // Reference to the PasteApi } // Option is a function that is used for grouping of Client options. @@ -65,6 +66,7 @@ func New(options ...Option) Client { // Associate the different HIBP service APIs with the Client c.PwnedPassApi = &PwnedPassApi{hibp: &c} c.BreachApi = &BreachApi{hibp: &c} + c.PasteApi = &PasteApi{hibp: &c} return c } diff --git a/paste.go b/paste.go new file mode 100644 index 0000000..0832b72 --- /dev/null +++ b/paste.go @@ -0,0 +1,56 @@ +package hibp + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +// PasteApi is a HIBP pastes API client +type PasteApi struct { + hibp *Client // References back to the parent HIBP client +} + +// Paste represents a JSON response structure of the pastes API +type Paste struct { + // Source is the paste service the record was retrieved from. Current values are: Pastebin, + // Pastie, Slexy, Ghostbin, QuickLeak, JustPaste, AdHocUrl, PermanentOptOut, OptOut + Source string `json:"Source"` + + // Id of the paste as it was given at the source service. Combined with the "Source" attribute, this + // can be used to resolve the URL of the paste + Id string `json:"Id"` + + // Title of the paste as observed on the source site. This may be null and if so will be omitted from + // the response + Title string `json:"Title"` + + // Date is the date and time (precision to the second) that the paste was posted. This is taken directly + // from the paste site when this information is available but may be null if no date is published + Date time.Time `json:"Date"` + + // EmailCount is number of emails that were found when processing the paste. Emails are extracted by + // using the regular expression \b[a-zA-Z0-9\.\-_\+]+@[a-zA-Z0-9\.\-_]+\.[a-zA-Z]+\b + EmailCount int `json:"EmailCount"` +} + +// PastedAccount returns a single breached site based on its name +func (p *PasteApi) PastedAccount(a string) ([]*Paste, *http.Response, error) { + if a == "" { + return nil, nil, fmt.Errorf("no account id given") + } + + apiUrl := fmt.Sprintf("%s/pasteaccount/%s", BaseUrl, a) + hb, hr, err := p.hibp.HttpResBody(http.MethodGet, apiUrl, nil) + if err != nil { + return nil, nil, err + } + + var pasteDetails []*Paste + if err := json.Unmarshal(hb, &pasteDetails); err != nil { + return nil, hr, err + } + + return pasteDetails, hr, nil +} diff --git a/paste_test.go b/paste_test.go new file mode 100644 index 0000000..0244d83 --- /dev/null +++ b/paste_test.go @@ -0,0 +1,43 @@ +package hibp + +import ( + "fmt" + "os" + "testing" +) + +// TestPasteAccount tests the BreachedAccount() method of the breaches API +func TestPasteAccount(t *testing.T) { + testTable := []struct { + testName string + accountName string + isBreached bool + }{ + {"account-exists is breached once", "account-exists", true}, + {"opt-out is not breached", "opt-out", false}, + } + + apiKey := os.Getenv("HIBP_API_KEY") + if apiKey == "" { + t.SkipNow() + } + hc := New(WithApiKey(apiKey)) + for _, tc := range testTable { + t.Run(tc.testName, func(t *testing.T) { + pasteDetails, _, err := hc.PasteApi.PastedAccount( + fmt.Sprintf("%s@hibp-integration-tests.com", tc.accountName)) + if err != nil && tc.isBreached { + t.Error(err) + } + + if pasteDetails == nil && tc.isBreached { + t.Errorf("breach for the account %q is expected, but returned 0 results.", + tc.accountName) + } + if pasteDetails != nil && !tc.isBreached { + t.Errorf("breach for the account %q is expected to be not breached, but returned breach details.", + tc.accountName) + } + }) + } +}