Skip to content

Commit

Permalink
Merge pull request #6 from mavjs/pwned-passwords
Browse files Browse the repository at this point in the history
Include Pwned Passwords range search API method
  • Loading branch information
mavjs committed Oct 19, 2018
2 parents b2a72bc + a260765 commit ee8b4fe
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 17 deletions.
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ goPwned
[![Build Status](https://travis-ci.org/mavjs/goPwned.svg?branch=master)](https://travis-ci.org/mavjs/goPwned)
[![Coverage Status](https://coveralls.io/repos/mavjs/goPwned/badge.svg?branch=master&service=github)](https://coveralls.io/github/mavjs/goPwned?branch=master)

A golang library for HaveIBeenPwned REST API -
A golang library for HaveIBeenPwned REST API -
[https://haveibeenpwned.com/](https://haveibeenpwned.com/)

Installation
Expand All @@ -15,7 +15,7 @@ Installation

Usage
-----
### Breaches
### Breaches

#### Getting all breaches for an account

Expand Down Expand Up @@ -107,6 +107,44 @@ func main() {
}
```

### Pwned Passwords

```go
import (
"github.com/mavjs/goPwned"
"crypto/sha1"
)
func fakeinput() {
inputPassword := "P@ssw0rd"
h := sha1.New()
h.Write([]byte(inputPassword))
password := fmt.Sprintf("%X", h.Sum(nil)) // hash = "21BD12DC183F740EE76F27B78EB39C8AD972A757"

return password
}

func main() {
pwdhash := fakeinput()
frange := pwdhash[0:5]

karray, err := gopwned.PwnedPasswords(frange)
str_karray := string(karray)
respArray := strings.Split(str_karray, "\n")

var result int
for r := 0, lrange := pwdhash[5:40]; r < len(respArray); r++ {
test, count := strings.Split(respArray[r], ":")
count = strconv.ParseInt(count)
if (test == lrange) {
result = count
}
break;
}

fmt.Println("This password has been seen: ", count)
}
```

License
-------
MIT
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/mavjs/goPwned

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
81 changes: 67 additions & 14 deletions gopwned.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package gopwned
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
Expand All @@ -12,6 +13,8 @@ type (
Client struct {
client *http.Client
UserAgent string
BaseURL *url.URL
PwnPwdURL *url.URL
}

Breach struct {
Expand Down Expand Up @@ -43,10 +46,11 @@ type (
)

const (
Version = "0.1"
UserAgent = "gopwned-api-client-" + Version
MediaTypeV2 = "application/vnd.haveibeenpwned.v2+json"
Endpoint = "https://haveibeenpwned.com/api/v2/"
Version = "0.1"
UserAgent = "gopwned-api-client-" + Version
MediaTypeV2 = "application/vnd.haveibeenpwned.v2+json"
Endpoint = "https://haveibeenpwned.com/api/v2/"
PwnPwdEndpoint = "https://api.pwnedpasswords.com/range/"
)

var (
Expand All @@ -57,19 +61,20 @@ var (
429: "Too many requests — the rate limit has been exceeded",
}

defaultClient, _ = NewClient(nil)
baseURL, _ = url.Parse(Endpoint)
defaultClient = NewClient(nil)
baseURL, _ = url.Parse(Endpoint)
pwnpwdURL, _ = url.Parse(PwnPwdEndpoint)
)

func NewClient(client *http.Client) (*Client, error) {
func NewClient(client *http.Client) *Client {
if client == nil {
client = http.DefaultClient
}
return &Client{client: client, UserAgent: UserAgent}, nil
return &Client{client: client, UserAgent: UserAgent, BaseURL: baseURL, PwnPwdURL: pwnpwdURL}
}

func (c *Client) do(resource string, opts url.Values) (*http.Response, error) {
u, err := baseURL.Parse(resource)
func (c *Client) newRequest(resource string, opts url.Values) (*http.Response, error) {
u, err := c.BaseURL.Parse(resource)
if err != nil {
return nil, err
}
Expand All @@ -80,15 +85,44 @@ func (c *Client) do(resource string, opts url.Values) (*http.Response, error) {
}

req, err := http.NewRequest("GET", target, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", MediaTypeV2)
req.Header.Set("User-Agent", c.UserAgent)
req.Close = true

return c.client.Do(req)
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}

return resp, nil
}

func (c *Client) newPwdRequest(resource string, opts url.Values) (*http.Response, error) {
target, err := c.PwnPwdURL.Parse(resource)
if err != nil {
return nil, err
}

req, err := http.NewRequest("GET", target.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.UserAgent)
req.Close = true

resp, err := c.client.Do(req)
if err != nil {
return nil, err
}

return resp, nil
}

func (c *Client) getBreaches(resource string, opts url.Values) ([]*Breach, error) {
resp, err := c.do(resource, opts)
resp, err := c.newRequest(resource, opts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -133,7 +167,7 @@ func (c *Client) GetBreachedSite(site string) ([]*Breach, error) {
func (c *Client) GetDataClasses() (*DataClasses, error) {
resource := "dataclasses"

resp, err := c.do(resource, nil)
resp, err := c.newRequest(resource, nil)
if err != nil {
return nil, err
}
Expand All @@ -147,7 +181,7 @@ func (c *Client) GetDataClasses() (*DataClasses, error) {
func (c *Client) GetAllPastesForAccount(account string) ([]*Paste, error) {
resource := fmt.Sprintf("pasteaccount/%s", account)

resp, err := c.do(resource, nil)
resp, err := c.newRequest(resource, nil)
if err != nil {
return nil, err
}
Expand All @@ -158,6 +192,21 @@ func (c *Client) GetAllPastesForAccount(account string) ([]*Paste, error) {
return pastes, err
}

func (c *Client) PwnedPasswords(chars string) ([]byte, error) {
resp, err := c.newPwdRequest(chars, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return respBody, nil
}

func GetAllBreachesForAccount(email, domain, truncateResponse string) ([]*Breach, error) {
return defaultClient.GetAllBreachesForAccount(email, domain, truncateResponse)
}
Expand All @@ -177,3 +226,7 @@ func GetDataClasses() (*DataClasses, error) {
func GetAllPastesForAccount(account string) ([]*Paste, error) {
return defaultClient.GetAllPastesForAccount(account)
}

func PwnedPasswords(chars string) ([]byte, error) {
return defaultClient.PwnedPasswords(chars)
}
38 changes: 37 additions & 1 deletion gopwned_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ func init() {
mockServer = httptest.NewServer(mockHandler)

// overwrite the package's default
baseURL, _ = url.Parse(mockServer.URL)
localURL, _ := url.Parse(mockServer.URL)

defaultClient.BaseURL = localURL
defaultClient.PwnPwdURL = localURL
}

func checkHeader(t *testing.T) http.HandlerFunc {
Expand All @@ -44,6 +47,22 @@ func checkHeader(t *testing.T) http.HandlerFunc {
}
}

func TestNewClient(t *testing.T) {
c := NewClient(nil)

if got, want := c.BaseURL.String(), Endpoint; got != want {
t.Errorf("NewClient BaseURL is %v, want %v", got, want)
}

if got, want := c.PwnPwdURL.String(), PwnPwdEndpoint; got != want {
t.Errorf("NewClient PwnPwdURL is %v, want %v", got, want)
}

if got, want := c.UserAgent, UserAgent; got != want {
t.Errorf("NewClient UserAgent is %v, want %v", got, want)
}
}

// XXX Test for *Client

func TestAllBreachesForAccount(t *testing.T) {
Expand Down Expand Up @@ -151,3 +170,20 @@ func TestGetAllPastesForAccount(t *testing.T) {
}
assert.Equal(want, account, "they should be the same output.")
}

func TestPwnedPasswords(t *testing.T) {
assert := assert.New(t)

want := "2D8D1B3FAACCA6A3C6A91617B2FA32E2F57:1\n2DC183F740EE76F27B78EB39C8AD972A757:49938"

mockHandler.HandleFunc("/21BD1", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "2D8D1B3FAACCA6A3C6A91617B2FA32E2F57:1\n2DC183F740EE76F27B78EB39C8AD972A757:49938")
})

pwds, err := PwnedPasswords("21BD1")
if err != nil {
t.Errorf("[PwnedPasswords] returned error: %v", err)
}

assert.Equal(want, string(pwds), "they should be the same output.")
}

0 comments on commit ee8b4fe

Please sign in to comment.