mirror of
https://github.com/wneessen/go-hibp.git
synced 2024-11-22 21:00:51 +01:00
Merge pull request #1 from wneessen/breaches
v0.1.2: Introducing Breaches
This commit is contained in:
commit
bde44da847
6 changed files with 347 additions and 18 deletions
184
breach.go
Normal file
184
breach.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package hibp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BreachApi is a HIBP breaches API client
|
||||||
|
type BreachApi struct {
|
||||||
|
hibp *Client // References back to the parent HIBP client
|
||||||
|
|
||||||
|
domain string // Filter for a specific breach domain
|
||||||
|
disableTrunc bool // Controls the truncateResponse parameter for the breaches API (defaults to false)
|
||||||
|
noUnverified bool // Controls the includeUnverified parameter for the breaches API (defaults to false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breach represents a JSON response structure of the breaches API
|
||||||
|
type Breach struct {
|
||||||
|
// Name is a pascal-cased name representing the breach which is unique across all other breaches.
|
||||||
|
// This value never changes and may be used to name dependent assets (such as images) but should not
|
||||||
|
// be shown directly to end users (see the "Title" attribute instead)
|
||||||
|
Name string `json:"Name"`
|
||||||
|
|
||||||
|
// Title is a descriptive title for the breach suitable for displaying to end users. It's unique across
|
||||||
|
// all breaches but individual values may change in the future (i.e. if another breach occurs against
|
||||||
|
// an organisation already in the system). If a stable value is required to reference the breach,
|
||||||
|
// refer to the "Name" attribute instead
|
||||||
|
Title string `json:"Title"`
|
||||||
|
|
||||||
|
// Domain of the primary website the breach occurred on. This may be used for identifying other
|
||||||
|
// assets external systems may have for the site
|
||||||
|
Domain string `json:"Domain"`
|
||||||
|
|
||||||
|
// BreachDate is the date (with no time) the breach originally occurred on in ISO 8601 format. This is not
|
||||||
|
// always accurate — frequently breaches are discovered and reported long after the original incident. Use
|
||||||
|
// this attribute as a guide only
|
||||||
|
BreachDate *ApiDate `json:"BreachDate,omitempty"`
|
||||||
|
|
||||||
|
// AddedDate represents the date and time (precision to the minute) the breach was added to the system
|
||||||
|
// in ISO 8601 format
|
||||||
|
AddedDate time.Time `json:"AddedDate"`
|
||||||
|
|
||||||
|
// ModifiedDate is the date and time (precision to the minute) the breach was modified in ISO 8601 format.
|
||||||
|
// This will only differ from the AddedDate attribute if other attributes represented here are changed or
|
||||||
|
// data in the breach itself is changed (i.e. additional data is identified and loaded). It is always
|
||||||
|
// either equal to or greater then the AddedDate attribute, never less than
|
||||||
|
ModifiedDate time.Time `json:"ModifiedDate"`
|
||||||
|
|
||||||
|
// PwnCount is the total number of accounts loaded into the system. This is usually less than the total
|
||||||
|
// number reported by the media due to duplication or other data integrity issues in the source data
|
||||||
|
PwnCount int `json:"PwnCount"`
|
||||||
|
|
||||||
|
// Description contains an overview of the breach represented in HTML markup. The description may include
|
||||||
|
// markup such as emphasis and strong tags as well as hyperlinks
|
||||||
|
Description string `json:"Description"`
|
||||||
|
|
||||||
|
// DataClasses describes the nature of the data compromised in the breach and contains an alphabetically ordered
|
||||||
|
// string array of impacted data classes
|
||||||
|
DataClasses []string `json:"DataClasses"`
|
||||||
|
|
||||||
|
// IsVerified indicates that the breach is considered unverified. An unverified breach may not have
|
||||||
|
// been hacked from the indicated website. An unverified breach is still loaded into HIBP when there's
|
||||||
|
// sufficient confidence that a significant portion of the data is legitimate
|
||||||
|
IsVerified bool `json:"IsVerified"`
|
||||||
|
|
||||||
|
// IsFabricated indicates that the breach is considered fabricated. A fabricated breach is unlikely
|
||||||
|
// to have been hacked from the indicated website and usually contains a large amount of manufactured
|
||||||
|
// data. However, it still contains legitimate email addresses and asserts that the account owners
|
||||||
|
// were compromised in the alleged breach
|
||||||
|
IsFabricated bool `json:"IsFabricated"`
|
||||||
|
|
||||||
|
// IsSensitive indicates if the breach is considered sensitive. The public API will not return any
|
||||||
|
// accounts for a breach flagged as sensitive
|
||||||
|
IsSensitive bool `json:"IsSensitive"`
|
||||||
|
|
||||||
|
// IsRetired indicates if the breach has been retired. This data has been permanently removed and
|
||||||
|
// will not be returned by the API
|
||||||
|
IsRetired bool `json:"IsRetired"`
|
||||||
|
|
||||||
|
// IsSpamList indicates
|
||||||
|
IsSpamList bool `json:"IsSpamList"`
|
||||||
|
|
||||||
|
// LogoPath represents a URI that specifies where a logo for the breached service can be found.
|
||||||
|
// Logos are always in PNG format
|
||||||
|
LogoPath string `json:"LogoPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreachOption is an additional option the can be set for the BreachApiClient
|
||||||
|
type BreachOption func(*BreachApi)
|
||||||
|
|
||||||
|
// ApiDate is a date string without time returned by the API represented as time.Time type
|
||||||
|
type ApiDate time.Time
|
||||||
|
|
||||||
|
// Breaches returns a list of all breaches in the HIBP system
|
||||||
|
func (b *BreachApi) Breaches(options ...BreachOption) ([]*Breach, *http.Response, error) {
|
||||||
|
queryParms := map[string]string{
|
||||||
|
"truncateResponse": "true",
|
||||||
|
"includeUnverified": "true",
|
||||||
|
}
|
||||||
|
apiUrl := fmt.Sprintf("%s/breaches", BaseUrl)
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.domain != "" {
|
||||||
|
queryParms["domain"] = b.domain
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.disableTrunc {
|
||||||
|
queryParms["truncateResponse"] = "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.noUnverified {
|
||||||
|
queryParms["includeUnverified"] = "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
hreq, err := b.hibp.HttpReq(http.MethodGet, apiUrl, queryParms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
hr, err := b.hibp.hc.Do(hreq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, hr, err
|
||||||
|
}
|
||||||
|
if hr.StatusCode != 200 {
|
||||||
|
return nil, hr, fmt.Errorf("API responded with non HTTP-200: %s", hr.Status)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = hr.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
hb, err := io.ReadAll(hr.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, hr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var breachList []*Breach
|
||||||
|
if err := json.Unmarshal(hb, &breachList); err != nil {
|
||||||
|
return nil, hr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return breachList, hr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDomain sets the domain filter for the breaches API
|
||||||
|
func WithDomain(d string) BreachOption {
|
||||||
|
return func(b *BreachApi) {
|
||||||
|
b.domain = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutTruncate disables the truncateResponse parameter in the breaches API
|
||||||
|
func WithoutTruncate() BreachOption {
|
||||||
|
return func(b *BreachApi) {
|
||||||
|
b.disableTrunc = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON for the ApiDate type converts a give date string into a time.Time type
|
||||||
|
func (d *ApiDate) UnmarshalJSON(s []byte) error {
|
||||||
|
ds := string(s)
|
||||||
|
ds = strings.ReplaceAll(ds, `"`, ``)
|
||||||
|
if ds == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pd, err := time.Parse("2006-01-02", ds)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert API date string to time.Time type: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*(*time.Time)(d) = pd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time adds a Time() method to the ApiDate converted time.Time type
|
||||||
|
func (d ApiDate) Time() time.Time {
|
||||||
|
return time.Time(d)
|
||||||
|
}
|
64
breach_test.go
Normal file
64
breach_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package hibp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBreaches tests the Breaches() method of the breaches API
|
||||||
|
func TestBreaches(t *testing.T) {
|
||||||
|
hc := New()
|
||||||
|
if hc == nil {
|
||||||
|
t.Errorf("hibp client creation failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
breachList, _, err := hc.BreachApi.Breaches()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if breachList != nil && len(breachList) <= 0 {
|
||||||
|
t.Error("breaches list returned 0 results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBreachesWithDomain tests the Breaches() method of the breaches API for a specific domain
|
||||||
|
func TestBreachesWithDomain(t *testing.T) {
|
||||||
|
testTable := []struct {
|
||||||
|
testName string
|
||||||
|
domain string
|
||||||
|
isBreached bool
|
||||||
|
}{
|
||||||
|
{"adobe.com is breached", "adobe.com", true},
|
||||||
|
{"example.com is not breached", "example.com", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := New()
|
||||||
|
if hc == nil {
|
||||||
|
t.Error("failed to create HIBP client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testTable {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
breachList, _, err := hc.BreachApi.Breaches(WithDomain(tc.domain))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if breachList == nil && tc.isBreached {
|
||||||
|
t.Errorf("domain %s is expected to be breached, but returned 0 results.",
|
||||||
|
tc.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
breachLen := len(breachList)
|
||||||
|
if tc.isBreached && breachLen <= 0 {
|
||||||
|
t.Errorf("domain %s is expected to be breached, but returned 0 results.",
|
||||||
|
tc.domain)
|
||||||
|
}
|
||||||
|
if !tc.isBreached && breachLen != 0 {
|
||||||
|
t.Errorf("domain %s is expected to be not breached, but returned %d results.",
|
||||||
|
tc.domain, breachLen)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
50
hibp.go
50
hibp.go
|
@ -1,15 +1,20 @@
|
||||||
package hibp
|
package hibp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version represents the version of this package
|
// Version represents the version of this package
|
||||||
const Version = "0.1.1"
|
const Version = "0.1.2"
|
||||||
|
|
||||||
|
// BaseUrl is the base URL for the majority of API calls
|
||||||
|
const BaseUrl = "https://haveibeenpwned.com/api/v3"
|
||||||
|
|
||||||
// Client is the HIBP client object
|
// Client is the HIBP client object
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
@ -17,7 +22,10 @@ type Client struct {
|
||||||
to time.Duration // HTTP client timeout
|
to time.Duration // HTTP client timeout
|
||||||
ak string // HIBP API key
|
ak string // HIBP API key
|
||||||
|
|
||||||
PwnedPassword *PwnedPassword // Reference to the PwnedPassword API
|
PwnedPassApi *PwnedPassApi // Reference to the PwnedPassApi API
|
||||||
|
PwnedPassApiOpts *PwnedPasswordOptions // Additional options for the PwnedPassApi API
|
||||||
|
|
||||||
|
BreachApi *BreachApi // Reference to the BreachApi API
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is a function that is used for grouping of Client options.
|
// Option is a function that is used for grouping of Client options.
|
||||||
|
@ -29,6 +37,7 @@ func New(options ...Option) *Client {
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
c.to = time.Second * 5
|
c.to = time.Second * 5
|
||||||
|
c.PwnedPassApiOpts = &PwnedPasswordOptions{}
|
||||||
|
|
||||||
// Set additional options
|
// Set additional options
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
|
@ -39,7 +48,8 @@ func New(options ...Option) *Client {
|
||||||
c.hc = httpClient(c.to)
|
c.hc = httpClient(c.to)
|
||||||
|
|
||||||
// Associate the different HIBP service APIs with the Client
|
// Associate the different HIBP service APIs with the Client
|
||||||
c.PwnedPassword = &PwnedPassword{hc: c}
|
c.PwnedPassApi = &PwnedPassApi{hibp: c}
|
||||||
|
c.BreachApi = &BreachApi{hibp: c}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
@ -58,22 +68,52 @@ func WithApiKey(k string) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithPwnedPadding enables padding-mode for the PwnedPasswords API client
|
||||||
|
func WithPwnedPadding() Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.PwnedPassApiOpts.WithPadding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HttpReq performs an HTTP request to the corresponding API
|
// HttpReq performs an HTTP request to the corresponding API
|
||||||
func (c *Client) HttpReq(m, p string) (*http.Request, error) {
|
func (c *Client) HttpReq(m, p string, q map[string]string) (*http.Request, error) {
|
||||||
u, err := url.Parse(p)
|
u, err := url.Parse(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m == http.MethodGet {
|
||||||
|
uq := u.Query()
|
||||||
|
for k, v := range q {
|
||||||
|
uq.Add(k, v)
|
||||||
|
}
|
||||||
|
u.RawQuery = uq.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
hr, err := http.NewRequest(m, u.String(), nil)
|
hr, err := http.NewRequest(m, u.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m == http.MethodPost {
|
||||||
|
pd := url.Values{}
|
||||||
|
for k, v := range q {
|
||||||
|
pd.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := io.NopCloser(bytes.NewBufferString(pd.Encode()))
|
||||||
|
hr.Body = rb
|
||||||
|
}
|
||||||
|
|
||||||
hr.Header.Set("Accept", "application/json")
|
hr.Header.Set("Accept", "application/json")
|
||||||
hr.Header.Set("User-Agent", fmt.Sprintf("go-hibp v%s - https://github.com/wneessen/go-hibp", Version))
|
hr.Header.Set("User-Agent", fmt.Sprintf("go-hibp v%s - https://github.com/wneessen/go-hibp", Version))
|
||||||
|
|
||||||
if c.ak != "" {
|
if c.ak != "" {
|
||||||
hr.Header["hibp-api-key"] = []string{c.ak}
|
hr.Header.Set("hibp-api-key", c.ak)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.PwnedPassApiOpts.WithPadding {
|
||||||
|
hr.Header.Set("Add-Padding", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
return hr, nil
|
return hr, nil
|
||||||
|
|
30
hibp_test.go
30
hibp_test.go
|
@ -1,6 +1,7 @@
|
||||||
package hibp
|
package hibp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -13,7 +14,7 @@ func TestNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNewWithHttpTimeout tests the New() function
|
// TestNewWithHttpTimeout tests the New() function with the http timeout option
|
||||||
func TestNewWithHttpTimeout(t *testing.T) {
|
func TestNewWithHttpTimeout(t *testing.T) {
|
||||||
hc := New(WithHttpTimeout(time.Second * 10))
|
hc := New(WithHttpTimeout(time.Second * 10))
|
||||||
if hc == nil {
|
if hc == nil {
|
||||||
|
@ -25,3 +26,30 @@ func TestNewWithHttpTimeout(t *testing.T) {
|
||||||
time.Second*10, hc.to)
|
time.Second*10, hc.to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewWithPwnedPadding tests the New() function with the PwnedPadding option
|
||||||
|
func TestNewWithPwnedPadding(t *testing.T) {
|
||||||
|
hc := New(WithPwnedPadding())
|
||||||
|
if hc == nil {
|
||||||
|
t.Errorf("hibp client creation failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !hc.PwnedPassApiOpts.WithPadding {
|
||||||
|
t.Errorf("hibp client pwned padding option was not set properly. Expected %v, got: %v",
|
||||||
|
true, hc.PwnedPassApiOpts.WithPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewWithApiKey tests the New() function with the API key set
|
||||||
|
func TestNewWithApiKey(t *testing.T) {
|
||||||
|
apiKey := os.Getenv("HIBP_API_KEY")
|
||||||
|
hc := New(WithApiKey(apiKey))
|
||||||
|
if hc == nil {
|
||||||
|
t.Errorf("hibp client creation failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hc.ak != apiKey {
|
||||||
|
t.Errorf("hibp client API key was not set properly. Expected %s, got: %s",
|
||||||
|
apiKey, hc.ak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
password.go
28
password.go
|
@ -9,25 +9,32 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PwnedPassword is a HIBP Pwned Passwords API client
|
// PwnedPassApi is a HIBP Pwned Passwords API client
|
||||||
type PwnedPassword struct {
|
type PwnedPassApi struct {
|
||||||
hc *Client
|
hibp *Client // References back to the parent HIBP client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match represents a match in the Pwned Passwords API
|
// Match represents a match in the Pwned Passwords API
|
||||||
type Match struct {
|
type Match struct {
|
||||||
Hash string
|
Hash string // SHA1 hash of the matching password
|
||||||
Count int64
|
Count int64 // Represents the number of leaked accounts that hold/held this password
|
||||||
|
}
|
||||||
|
|
||||||
|
// PwnedPasswordOptions is a struct of additional options for the PP API
|
||||||
|
type PwnedPasswordOptions struct {
|
||||||
|
// WithPadding controls if the PwnedPassword API returns with padding or not
|
||||||
|
// See: https://haveibeenpwned.com/API/v3#PwnedPasswordsPadding
|
||||||
|
WithPadding bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPassword checks the Pwned Passwords database against a given password string
|
// CheckPassword checks the Pwned Passwords database against a given password string
|
||||||
func (p *PwnedPassword) CheckPassword(pw string) (*Match, *http.Response, error) {
|
func (p *PwnedPassApi) CheckPassword(pw string) (*Match, *http.Response, error) {
|
||||||
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(pw)))
|
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(pw)))
|
||||||
return p.CheckSHA1(shaSum)
|
return p.CheckSHA1(shaSum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckSHA1 checks the Pwned Passwords database against a given SHA1 checksum of a password
|
// CheckSHA1 checks the Pwned Passwords database against a given SHA1 checksum of a password
|
||||||
func (p *PwnedPassword) CheckSHA1(h string) (*Match, *http.Response, error) {
|
func (p *PwnedPassApi) CheckSHA1(h string) (*Match, *http.Response, error) {
|
||||||
pwMatches, hr, err := p.apiCall(h)
|
pwMatches, hr, err := p.apiCall(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Match{}, hr, err
|
return &Match{}, hr, err
|
||||||
|
@ -44,13 +51,14 @@ func (p *PwnedPassword) CheckSHA1(h string) (*Match, *http.Response, error) {
|
||||||
|
|
||||||
// apiCall performs the API call to the Pwned Password API endpoint and returns
|
// apiCall performs the API call to the Pwned Password API endpoint and returns
|
||||||
// the http.Response
|
// the http.Response
|
||||||
func (p *PwnedPassword) apiCall(h string) ([]Match, *http.Response, error) {
|
func (p *PwnedPassApi) apiCall(h string) ([]Match, *http.Response, error) {
|
||||||
sh := h[:5]
|
sh := h[:5]
|
||||||
hreq, err := p.hc.HttpReq(http.MethodGet, fmt.Sprintf("https://api.pwnedpasswords.com/range/%s", sh))
|
hreq, err := p.hibp.HttpReq(http.MethodGet, fmt.Sprintf("https://api.pwnedpasswords.com/range/%s", sh),
|
||||||
|
nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
hr, err := p.hc.hc.Do(hreq)
|
hr, err := p.hibp.hc.Do(hreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestPwnedPasswordString(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
m, _, err := hc.PwnedPassword.CheckPassword(tc.pwString)
|
m, _, err := hc.PwnedPassApi.CheckPassword(tc.pwString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -47,12 +47,17 @@ func TestPwnedPasswordHash(t *testing.T) {
|
||||||
"90efc095c82eab44e882fda507cfab1a2cd31fc0", false},
|
"90efc095c82eab44e882fda507cfab1a2cd31fc0", false},
|
||||||
}
|
}
|
||||||
hc := New()
|
hc := New()
|
||||||
|
if hc == nil {
|
||||||
|
t.Error("failed to create HIBP client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
m, _, err := hc.PwnedPassword.CheckSHA1(tc.pwHash)
|
m, _, err := hc.PwnedPassApi.CheckSHA1(tc.pwHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if m == nil && tc.isLeaked {
|
if m == nil && tc.isLeaked {
|
||||||
t.Errorf("password is expected to be leaked but 0 leaks were returned in Pwned Passwords DB")
|
t.Errorf("password is expected to be leaked but 0 leaks were returned in Pwned Passwords DB")
|
||||||
|
|
Loading…
Reference in a new issue