mirror of
https://github.com/wneessen/go-hibp.git
synced 2024-11-22 12:50:50 +01:00
Implement a golangci-lint workflow and the accordingly GH action
This commit is contained in:
parent
a931f4aef3
commit
20ebd4c965
10 changed files with 225 additions and 149 deletions
49
.github/workflows/golangci-lint.yml
vendored
Normal file
49
.github/workflows/golangci-lint.yml
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
name: golangci-lint
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
||||||
|
# pull-requests: read
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.19
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
# Optional: working directory, useful for monorepos
|
||||||
|
# working-directory: somedir
|
||||||
|
|
||||||
|
# Optional: golangci-lint command line arguments.
|
||||||
|
# args: --issues-exit-code=0
|
||||||
|
|
||||||
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
|
# only-new-issues: true
|
||||||
|
|
||||||
|
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||||
|
# takes precedence over all other caching options.
|
||||||
|
# skip-cache: true
|
||||||
|
|
||||||
|
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||||
|
# skip-pkg-cache: true
|
||||||
|
|
||||||
|
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||||
|
# skip-build-cache: true
|
11
.golangci.toml
Normal file
11
.golangci.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[run]
|
||||||
|
go = "1.16"
|
||||||
|
tests = true
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder",
|
||||||
|
"errname", "errorlint", "gofmt", "gofumpt"]
|
54
breach.go
54
breach.go
|
@ -8,8 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BreachApi is a HIBP breaches API client
|
// BreachAPI is a HIBP breaches API client
|
||||||
type BreachApi struct {
|
type BreachAPI struct {
|
||||||
hibp *Client // References back to the parent HIBP client
|
hibp *Client // References back to the parent HIBP client
|
||||||
|
|
||||||
domain string // Filter for a specific breach domain
|
domain string // Filter for a specific breach domain
|
||||||
|
@ -37,7 +37,7 @@ type Breach struct {
|
||||||
// BreachDate is the date (with no time) the breach originally occurred on in ISO 8601 format. This is not
|
// 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
|
// always accurate — frequently breaches are discovered and reported long after the original incident. Use
|
||||||
// this attribute as a guide only
|
// this attribute as a guide only
|
||||||
BreachDate *ApiDate `json:"BreachDate,omitempty"`
|
BreachDate *APIDate `json:"BreachDate,omitempty"`
|
||||||
|
|
||||||
// AddedDate represents the date and time (precision to the minute) the breach was added to the system
|
// AddedDate represents the date and time (precision to the minute) the breach was added to the system
|
||||||
// in ISO 8601 format
|
// in ISO 8601 format
|
||||||
|
@ -89,17 +89,17 @@ type Breach struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BreachOption is an additional option the can be set for the BreachApiClient
|
// BreachOption is an additional option the can be set for the BreachApiClient
|
||||||
type BreachOption func(*BreachApi)
|
type BreachOption func(*BreachAPI)
|
||||||
|
|
||||||
// ApiDate is a date string without time returned by the API represented as time.Time type
|
// APIDate is a date string without time returned by the API represented as time.Time type
|
||||||
type ApiDate time.Time
|
type APIDate time.Time
|
||||||
|
|
||||||
// Breaches returns a list of all breaches in the HIBP system
|
// Breaches returns a list of all breaches in the HIBP system
|
||||||
func (b *BreachApi) Breaches(options ...BreachOption) ([]*Breach, *http.Response, error) {
|
func (b *BreachAPI) Breaches(options ...BreachOption) ([]*Breach, *http.Response, error) {
|
||||||
queryParams := b.setBreachOpts(options...)
|
queryParams := b.setBreachOpts(options...)
|
||||||
apiUrl := fmt.Sprintf("%s/breaches", BaseUrl)
|
apiURL := fmt.Sprintf("%s/breaches", BaseURL)
|
||||||
|
|
||||||
hb, hr, err := b.hibp.HttpResBody(http.MethodGet, apiUrl, queryParams)
|
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -113,15 +113,15 @@ func (b *BreachApi) Breaches(options ...BreachOption) ([]*Breach, *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
// BreachByName returns a single breached site based on its name
|
// BreachByName returns a single breached site based on its name
|
||||||
func (b *BreachApi) BreachByName(n string, options ...BreachOption) (*Breach, *http.Response, error) {
|
func (b *BreachAPI) BreachByName(n string, options ...BreachOption) (*Breach, *http.Response, error) {
|
||||||
queryParams := b.setBreachOpts(options...)
|
queryParams := b.setBreachOpts(options...)
|
||||||
|
|
||||||
if n == "" {
|
if n == "" {
|
||||||
return nil, nil, fmt.Errorf("no breach name given")
|
return nil, nil, fmt.Errorf("no breach name given")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiUrl := fmt.Sprintf("%s/breach/%s", BaseUrl, n)
|
apiURL := fmt.Sprintf("%s/breach/%s", BaseURL, n)
|
||||||
hb, hr, err := b.hibp.HttpResBody(http.MethodGet, apiUrl, queryParams)
|
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -136,9 +136,9 @@ func (b *BreachApi) BreachByName(n string, options ...BreachOption) (*Breach, *h
|
||||||
|
|
||||||
// DataClasses are attribute of a record compromised in a breach. This method returns a list of strings
|
// DataClasses are attribute of a record compromised in a breach. This method returns a list of strings
|
||||||
// with all registered data classes known to HIBP
|
// with all registered data classes known to HIBP
|
||||||
func (b *BreachApi) DataClasses() ([]string, *http.Response, error) {
|
func (b *BreachAPI) DataClasses() ([]string, *http.Response, error) {
|
||||||
apiUrl := fmt.Sprintf("%s/dataclasses", BaseUrl)
|
apiURL := fmt.Sprintf("%s/dataclasses", BaseURL)
|
||||||
hb, hr, err := b.hibp.HttpResBody(http.MethodGet, apiUrl, nil)
|
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -152,15 +152,15 @@ func (b *BreachApi) DataClasses() ([]string, *http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BreachedAccount returns a single breached site based on its name
|
// BreachedAccount returns a single breached site based on its name
|
||||||
func (b *BreachApi) BreachedAccount(a string, options ...BreachOption) ([]*Breach, *http.Response, error) {
|
func (b *BreachAPI) BreachedAccount(a string, options ...BreachOption) ([]*Breach, *http.Response, error) {
|
||||||
queryParams := b.setBreachOpts(options...)
|
queryParams := b.setBreachOpts(options...)
|
||||||
|
|
||||||
if a == "" {
|
if a == "" {
|
||||||
return nil, nil, fmt.Errorf("no account id given")
|
return nil, nil, fmt.Errorf("no account id given")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiUrl := fmt.Sprintf("%s/breachedaccount/%s", BaseUrl, a)
|
apiURL := fmt.Sprintf("%s/breachedaccount/%s", BaseURL, a)
|
||||||
hb, hr, err := b.hibp.HttpResBody(http.MethodGet, apiUrl, queryParams)
|
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, apiURL, queryParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ func (b *BreachApi) BreachedAccount(a string, options ...BreachOption) ([]*Breac
|
||||||
|
|
||||||
// WithDomain sets the domain filter for the breaches API
|
// WithDomain sets the domain filter for the breaches API
|
||||||
func WithDomain(d string) BreachOption {
|
func WithDomain(d string) BreachOption {
|
||||||
return func(b *BreachApi) {
|
return func(b *BreachAPI) {
|
||||||
b.domain = d
|
b.domain = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,20 +183,20 @@ func WithDomain(d string) BreachOption {
|
||||||
// WithoutTruncate disables the truncateResponse parameter in the breaches API
|
// WithoutTruncate disables the truncateResponse parameter in the breaches API
|
||||||
// This option only influences the BreachedAccount method
|
// This option only influences the BreachedAccount method
|
||||||
func WithoutTruncate() BreachOption {
|
func WithoutTruncate() BreachOption {
|
||||||
return func(b *BreachApi) {
|
return func(b *BreachAPI) {
|
||||||
b.disableTrunc = true
|
b.disableTrunc = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithoutUnverified suppress unverified breaches from the query
|
// WithoutUnverified suppress unverified breaches from the query
|
||||||
func WithoutUnverified() BreachOption {
|
func WithoutUnverified() BreachOption {
|
||||||
return func(b *BreachApi) {
|
return func(b *BreachAPI) {
|
||||||
b.noUnverified = true
|
b.noUnverified = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON for the ApiDate type converts a give date string into a time.Time type
|
// UnmarshalJSON for the APIDate type converts a give date string into a time.Time type
|
||||||
func (d *ApiDate) UnmarshalJSON(s []byte) error {
|
func (d *APIDate) UnmarshalJSON(s []byte) error {
|
||||||
ds := string(s)
|
ds := string(s)
|
||||||
ds = strings.ReplaceAll(ds, `"`, ``)
|
ds = strings.ReplaceAll(ds, `"`, ``)
|
||||||
if ds == "null" {
|
if ds == "null" {
|
||||||
|
@ -205,21 +205,21 @@ func (d *ApiDate) UnmarshalJSON(s []byte) error {
|
||||||
|
|
||||||
pd, err := time.Parse("2006-01-02", ds)
|
pd, err := time.Parse("2006-01-02", ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert API date string to time.Time type: %s", err)
|
return fmt.Errorf("failed to convert API date string to time.Time type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*time.Time)(d) = pd
|
*(*time.Time)(d) = pd
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time adds a Time() method to the ApiDate converted time.Time type
|
// Time adds a Time() method to the APIDate converted time.Time type
|
||||||
func (d *ApiDate) Time() time.Time {
|
func (d *APIDate) Time() time.Time {
|
||||||
dp := *d
|
dp := *d
|
||||||
return time.Time(dp)
|
return time.Time(dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setBreachOpts returns a map of default settings and overridden values from different BreachOption
|
// setBreachOpts returns a map of default settings and overridden values from different BreachOption
|
||||||
func (b *BreachApi) setBreachOpts(options ...BreachOption) map[string]string {
|
func (b *BreachAPI) setBreachOpts(options ...BreachOption) map[string]string {
|
||||||
queryParams := map[string]string{
|
queryParams := map[string]string{
|
||||||
"truncateResponse": "true",
|
"truncateResponse": "true",
|
||||||
"includeUnverified": "true",
|
"includeUnverified": "true",
|
||||||
|
|
|
@ -17,7 +17,7 @@ const (
|
||||||
// TestBreaches tests the Breaches() method of the breaches API
|
// TestBreaches tests the Breaches() method of the breaches API
|
||||||
func TestBreaches(t *testing.T) {
|
func TestBreaches(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
breachList, _, err := hc.BreachApi.Breaches()
|
breachList, _, err := hc.BreachAPI.Breaches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func TestBreaches(t *testing.T) {
|
||||||
// TestBreachesWithNil tests the Breaches() method of the breaches API with a nil option
|
// TestBreachesWithNil tests the Breaches() method of the breaches API with a nil option
|
||||||
func TestBreachesWithNil(t *testing.T) {
|
func TestBreachesWithNil(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
breachList, _, err := hc.BreachApi.Breaches(nil)
|
breachList, _, err := hc.BreachAPI.Breaches(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func TestBreachesWithDomain(t *testing.T) {
|
||||||
hc := New(WithRateLimitSleep())
|
hc := New(WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
breachList, _, err := hc.BreachApi.Breaches(WithDomain(tc.domain))
|
breachList, _, err := hc.BreachAPI.Breaches(WithDomain(tc.domain))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func TestBreachesWithoutUnverified(t *testing.T) {
|
||||||
hc := New(WithRateLimitSleep())
|
hc := New(WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
breachList, _, err := hc.BreachApi.Breaches(WithDomain(tc.domain), WithoutUnverified())
|
breachList, _, err := hc.BreachAPI.Breaches(WithDomain(tc.domain), WithoutUnverified())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ func TestBreachByName(t *testing.T) {
|
||||||
hc := New(WithRateLimitSleep())
|
hc := New(WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
breachDetails, _, err := hc.BreachApi.BreachByName(tc.breachName)
|
breachDetails, _, err := hc.BreachAPI.BreachByName(tc.breachName)
|
||||||
if err != nil && !tc.shouldFail {
|
if err != nil && !tc.shouldFail {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ func TestBreachByName(t *testing.T) {
|
||||||
// TestDataClasses tests the DataClasses() method of the breaches API
|
// TestDataClasses tests the DataClasses() method of the breaches API
|
||||||
func TestDataClasses(t *testing.T) {
|
func TestDataClasses(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
classList, _, err := hc.BreachApi.DataClasses()
|
classList, _, err := hc.BreachAPI.DataClasses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -156,10 +156,14 @@ func TestBreachedAccount(t *testing.T) {
|
||||||
isBreached bool
|
isBreached bool
|
||||||
moreThanOneBreach bool
|
moreThanOneBreach bool
|
||||||
}{
|
}{
|
||||||
{"account-exists is breached once", "account-exists", true,
|
{
|
||||||
false},
|
"account-exists is breached once", "account-exists", true,
|
||||||
{"multiple-breaches is breached multiple times", "multiple-breaches",
|
false,
|
||||||
true, true},
|
},
|
||||||
|
{
|
||||||
|
"multiple-breaches is breached multiple times", "multiple-breaches",
|
||||||
|
true, true,
|
||||||
|
},
|
||||||
{"opt-out is not breached", "opt-out", false, false},
|
{"opt-out is not breached", "opt-out", false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,10 +171,10 @@ func TestBreachedAccount(t *testing.T) {
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
hc := New(WithApiKey(apiKey), WithRateLimitSleep())
|
hc := New(WithAPIKey(apiKey), WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
breachDetails, _, err := hc.BreachApi.BreachedAccount(
|
breachDetails, _, err := hc.BreachAPI.BreachedAccount(
|
||||||
fmt.Sprintf("%s@hibp-integration-tests.com", tc.accountName))
|
fmt.Sprintf("%s@hibp-integration-tests.com", tc.accountName))
|
||||||
if err != nil && tc.isBreached {
|
if err != nil && tc.isBreached {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -206,10 +210,14 @@ func TestBreachedAccountWithoutTruncate(t *testing.T) {
|
||||||
breachDomain string
|
breachDomain string
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{"account-exists is breached once", "account-exists@hibp-integration-tests.com", "Adobe",
|
{
|
||||||
"adobe.com", false},
|
"account-exists is breached once", "account-exists@hibp-integration-tests.com", "Adobe",
|
||||||
{"multiple-breaches is breached multiple times", "multiple-breaches@hibp-integration-tests.com", "Adobe",
|
"adobe.com", false,
|
||||||
"adobe.com", false},
|
},
|
||||||
|
{
|
||||||
|
"multiple-breaches is breached multiple times", "multiple-breaches@hibp-integration-tests.com", "Adobe",
|
||||||
|
"adobe.com", false,
|
||||||
|
},
|
||||||
{"opt-out is not breached", "opt-out@hibp-integration-tests.com", "", "", true},
|
{"opt-out is not breached", "opt-out@hibp-integration-tests.com", "", "", true},
|
||||||
{"empty string should fail", "", "", "", true},
|
{"empty string should fail", "", "", "", true},
|
||||||
}
|
}
|
||||||
|
@ -218,10 +226,10 @@ func TestBreachedAccountWithoutTruncate(t *testing.T) {
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
hc := New(WithApiKey(apiKey), WithRateLimitSleep())
|
hc := New(WithAPIKey(apiKey), WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
breachDetails, _, err := hc.BreachApi.BreachedAccount(tc.accountName, WithoutTruncate())
|
breachDetails, _, err := hc.BreachAPI.BreachedAccount(tc.accountName, WithoutTruncate())
|
||||||
if err != nil && !tc.shouldFail {
|
if err != nil && !tc.shouldFail {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -246,10 +254,10 @@ func TestBreachedAccountWithoutTruncate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestApiDate_UnmarshalJSON_Time tests the ApiDate type JSON unmarshalling
|
// TestAPIDate_UnmarshalJSON_Time tests the APIDate type JSON unmarshalling
|
||||||
func TestApiDate_UnmarshalJSON_Time(t *testing.T) {
|
func TestAPIDate_UnmarshalJSON_Time(t *testing.T) {
|
||||||
type testData struct {
|
type testData struct {
|
||||||
Date *ApiDate `json:"date"`
|
Date *APIDate `json:"date"`
|
||||||
}
|
}
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
n string
|
n string
|
||||||
|
@ -271,24 +279,24 @@ func TestApiDate_UnmarshalJSON_Time(t *testing.T) {
|
||||||
t.Errorf("failed to unmarshal test JSON: %s", err)
|
t.Errorf("failed to unmarshal test JSON: %s", err)
|
||||||
}
|
}
|
||||||
if td.Date == nil && !tc.nil {
|
if td.Date == nil && !tc.nil {
|
||||||
t.Errorf("unmarshal on ApiDate type failed. Expected data but got nil")
|
t.Errorf("unmarshal on APIDate type failed. Expected data but got nil")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !tc.nil {
|
if !tc.nil {
|
||||||
tdd := td.Date.Time().Format("2006-01-02")
|
tdd := td.Date.Time().Format("2006-01-02")
|
||||||
if tdd != tc.d && !tc.sf {
|
if tdd != tc.d && !tc.sf {
|
||||||
t.Errorf(`unmarshal of ApiDate type failed. Expected: %q, got %q"`, tc.d, tdd)
|
t.Errorf(`unmarshal of APIDate type failed. Expected: %q, got %q"`, tc.d, tdd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleBreachApi_Breaches_getAllBreaches is a code example to show how to fetch all breaches from the
|
// ExampleBreachAPI_Breaches_getAllBreaches is a code example to show how to fetch all breaches from the
|
||||||
// HIBP breaches API
|
// HIBP breaches API
|
||||||
func ExampleBreachApi_Breaches_getAllBreaches() {
|
func ExampleBreachAPI_Breaches_getAllBreaches() {
|
||||||
hc := New()
|
hc := New()
|
||||||
bl, _, err := hc.BreachApi.Breaches()
|
bl, _, err := hc.BreachAPI.Breaches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -300,11 +308,11 @@ func ExampleBreachApi_Breaches_getAllBreaches() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleBreachApi_Breaches_getAllBreachesNoUnverified is a code example to show how to fetch all breaches from the
|
// ExampleBreachAPI_Breaches_getAllBreachesNoUnverified is a code example to show how to fetch all breaches from the
|
||||||
// HIBP breaches API but ignoring unverified breaches
|
// HIBP breaches API but ignoring unverified breaches
|
||||||
func ExampleBreachApi_Breaches_getAllBreachesNoUnverified() {
|
func ExampleBreachAPI_Breaches_getAllBreachesNoUnverified() {
|
||||||
hc := New()
|
hc := New()
|
||||||
bl, _, err := hc.BreachApi.Breaches()
|
bl, _, err := hc.BreachAPI.Breaches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -312,7 +320,7 @@ func ExampleBreachApi_Breaches_getAllBreachesNoUnverified() {
|
||||||
fmt.Printf("Found %d breaches total.\n", len(bl))
|
fmt.Printf("Found %d breaches total.\n", len(bl))
|
||||||
}
|
}
|
||||||
|
|
||||||
bl, _, err = hc.BreachApi.Breaches(WithoutUnverified())
|
bl, _, err = hc.BreachAPI.Breaches(WithoutUnverified())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -321,11 +329,11 @@ func ExampleBreachApi_Breaches_getAllBreachesNoUnverified() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleBreachApi_BreachByName is a code example to show how to fetch a specific breach
|
// ExampleBreachAPI_BreachByName is a code example to show how to fetch a specific breach
|
||||||
// from the HIBP breaches API using the BreachByName method
|
// from the HIBP breaches API using the BreachByName method
|
||||||
func ExampleBreachApi_BreachByName() {
|
func ExampleBreachAPI_BreachByName() {
|
||||||
hc := New()
|
hc := New()
|
||||||
bd, _, err := hc.BreachApi.BreachByName("Adobe")
|
bd, _, err := hc.BreachAPI.BreachByName("Adobe")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -337,15 +345,15 @@ func ExampleBreachApi_BreachByName() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExampleBreachApi_BreachedAccount is a code example to show how to fetch a list of breaches
|
// ExampleBreachAPI_BreachedAccount is a code example to show how to fetch a list of breaches
|
||||||
// for a specific site/account from the HIBP breaches API using the BreachedAccount method
|
// for a specific site/account from the HIBP breaches API using the BreachedAccount method
|
||||||
func ExampleBreachApi_BreachedAccount() {
|
func ExampleBreachAPI_BreachedAccount() {
|
||||||
apiKey := os.Getenv("HIBP_API_KEY")
|
apiKey := os.Getenv("HIBP_API_KEY")
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
panic("A API key is required for this API")
|
panic("A API key is required for this API")
|
||||||
}
|
}
|
||||||
hc := New(WithApiKey(apiKey))
|
hc := New(WithAPIKey(apiKey))
|
||||||
bd, _, err := hc.BreachApi.BreachedAccount("multiple-breaches@hibp-integration-tests.com")
|
bd, _, err := hc.BreachAPI.BreachedAccount("multiple-breaches@hibp-integration-tests.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
44
hibp.go
44
hibp.go
|
@ -15,8 +15,8 @@ import (
|
||||||
// Version represents the version of this package
|
// Version represents the version of this package
|
||||||
const Version = "1.0.2"
|
const Version = "1.0.2"
|
||||||
|
|
||||||
// BaseUrl is the base URL for the majority of API calls
|
// BaseURL is the base URL for the majority of API calls
|
||||||
const BaseUrl = "https://haveibeenpwned.com/api/v3"
|
const BaseURL = "https://haveibeenpwned.com/api/v3"
|
||||||
|
|
||||||
// DefaultUserAgent defines the default UA string for the HTTP client
|
// DefaultUserAgent defines the default UA string for the HTTP client
|
||||||
// Currently the URL in the UA string is comment out, as there is a bug in the HIBP API
|
// Currently the URL in the UA string is comment out, as there is a bug in the HIBP API
|
||||||
|
@ -34,11 +34,11 @@ type Client struct {
|
||||||
// rate limit hits a request
|
// rate limit hits a request
|
||||||
rlSleep bool
|
rlSleep bool
|
||||||
|
|
||||||
PwnedPassApi *PwnedPassApi // Reference to the PwnedPassApi API
|
PwnedPassAPI *PwnedPassAPI // Reference to the PwnedPassAPI API
|
||||||
PwnedPassApiOpts *PwnedPasswordOptions // Additional options for the PwnedPassApi API
|
PwnedPassAPIOpts *PwnedPasswordOptions // Additional options for the PwnedPassAPI API
|
||||||
|
|
||||||
BreachApi *BreachApi // Reference to the BreachApi
|
BreachAPI *BreachAPI // Reference to the BreachAPI
|
||||||
PasteApi *PasteApi // Reference to the PasteApi
|
PasteAPI *PasteAPI // Reference to the PasteAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is a function that is used for grouping of Client options.
|
// Option is a function that is used for grouping of Client options.
|
||||||
|
@ -50,7 +50,7 @@ func New(options ...Option) Client {
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
c.to = time.Second * 5
|
c.to = time.Second * 5
|
||||||
c.PwnedPassApiOpts = &PwnedPasswordOptions{}
|
c.PwnedPassAPIOpts = &PwnedPasswordOptions{}
|
||||||
c.ua = DefaultUserAgent
|
c.ua = DefaultUserAgent
|
||||||
|
|
||||||
// Set additional options
|
// Set additional options
|
||||||
|
@ -65,22 +65,22 @@ 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.PwnedPassApi = &PwnedPassApi{hibp: &c}
|
c.PwnedPassAPI = &PwnedPassAPI{hibp: &c}
|
||||||
c.BreachApi = &BreachApi{hibp: &c}
|
c.BreachAPI = &BreachAPI{hibp: &c}
|
||||||
c.PasteApi = &PasteApi{hibp: &c}
|
c.PasteAPI = &PasteAPI{hibp: &c}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHttpTimeout overrides the default http client timeout
|
// WithHTTPTimeout overrides the default http client timeout
|
||||||
func WithHttpTimeout(t time.Duration) Option {
|
func WithHTTPTimeout(t time.Duration) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.to = t
|
c.to = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithApiKey set the optional API key to the Client object
|
// WithAPIKey set the optional API key to the Client object
|
||||||
func WithApiKey(k string) Option {
|
func WithAPIKey(k string) Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.ak = k
|
c.ak = k
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func WithApiKey(k string) Option {
|
||||||
// WithPwnedPadding enables padding-mode for the PwnedPasswords API client
|
// WithPwnedPadding enables padding-mode for the PwnedPasswords API client
|
||||||
func WithPwnedPadding() Option {
|
func WithPwnedPadding() Option {
|
||||||
return func(c *Client) {
|
return func(c *Client) {
|
||||||
c.PwnedPassApiOpts.WithPadding = true
|
c.PwnedPassAPIOpts.WithPadding = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +110,8 @@ func WithRateLimitSleep() Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, q map[string]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
|
||||||
|
@ -145,16 +145,16 @@ func (c *Client) HttpReq(m, p string, q map[string]string) (*http.Request, error
|
||||||
if c.ak != "" {
|
if c.ak != "" {
|
||||||
hr.Header.Set("hibp-api-key", c.ak)
|
hr.Header.Set("hibp-api-key", c.ak)
|
||||||
}
|
}
|
||||||
if c.PwnedPassApiOpts.WithPadding {
|
if c.PwnedPassAPIOpts.WithPadding {
|
||||||
hr.Header.Set("Add-Padding", "true")
|
hr.Header.Set("Add-Padding", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
return hr, nil
|
return hr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpResBody performs the API call to the given path and returns the response body as byte array
|
// HTTPResBody performs the API call to the given path and returns the response body as byte array
|
||||||
func (c *Client) HttpResBody(m string, p string, q map[string]string) ([]byte, *http.Response, error) {
|
func (c *Client) HTTPResBody(m string, p string, q map[string]string) ([]byte, *http.Response, error) {
|
||||||
hreq, err := c.HttpReq(m, p, q)
|
hreq, err := c.HTTPReq(m, p, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ func (c *Client) HttpResBody(m string, p string, q map[string]string) ([]byte, *
|
||||||
}
|
}
|
||||||
log.Printf("API rate limit hit. Retrying request in %s", delayTime.String())
|
log.Printf("API rate limit hit. Retrying request in %s", delayTime.String())
|
||||||
time.Sleep(delayTime)
|
time.Sleep(delayTime)
|
||||||
return c.HttpResBody(m, p, q)
|
return c.HTTPResBody(m, p, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hr.StatusCode != 200 {
|
if hr.StatusCode != 200 {
|
||||||
|
|
12
hibp_test.go
12
hibp_test.go
|
@ -10,7 +10,7 @@ import (
|
||||||
// TestNew tests the New() function
|
// TestNew tests the New() function
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
if *hc.PwnedPassApi.hibp != hc {
|
if *hc.PwnedPassAPI.hibp != hc {
|
||||||
t.Errorf("hibp client creation failed")
|
t.Errorf("hibp client creation failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,14 @@ func TestNew(t *testing.T) {
|
||||||
// TestNewWithNil tests the New() function with a nil option
|
// TestNewWithNil tests the New() function with a nil option
|
||||||
func TestNewWithNil(t *testing.T) {
|
func TestNewWithNil(t *testing.T) {
|
||||||
hc := New(nil)
|
hc := New(nil)
|
||||||
if *hc.PwnedPassApi.hibp != hc {
|
if *hc.PwnedPassAPI.hibp != hc {
|
||||||
t.Errorf("hibp client creation failed")
|
t.Errorf("hibp client creation failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNewWithHttpTimeout tests the New() function with the http timeout option
|
// 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.to != time.Second*10 {
|
if hc.to != time.Second*10 {
|
||||||
t.Errorf("hibp client timeout option was not set properly. Expected %d, got: %d",
|
t.Errorf("hibp client timeout option was not set properly. Expected %d, got: %d",
|
||||||
time.Second*10, hc.to)
|
time.Second*10, hc.to)
|
||||||
|
@ -35,16 +35,16 @@ func TestNewWithHttpTimeout(t *testing.T) {
|
||||||
// TestNewWithPwnedPadding tests the New() function with the PwnedPadding option
|
// TestNewWithPwnedPadding tests the New() function with the PwnedPadding option
|
||||||
func TestNewWithPwnedPadding(t *testing.T) {
|
func TestNewWithPwnedPadding(t *testing.T) {
|
||||||
hc := New(WithPwnedPadding())
|
hc := New(WithPwnedPadding())
|
||||||
if !hc.PwnedPassApiOpts.WithPadding {
|
if !hc.PwnedPassAPIOpts.WithPadding {
|
||||||
t.Errorf("hibp client pwned padding option was not set properly. Expected %v, got: %v",
|
t.Errorf("hibp client pwned padding option was not set properly. Expected %v, got: %v",
|
||||||
true, hc.PwnedPassApiOpts.WithPadding)
|
true, hc.PwnedPassAPIOpts.WithPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNewWithApiKey tests the New() function with the API key set
|
// TestNewWithApiKey tests the New() function with the API key set
|
||||||
func TestNewWithApiKey(t *testing.T) {
|
func TestNewWithApiKey(t *testing.T) {
|
||||||
apiKey := os.Getenv("HIBP_API_KEY")
|
apiKey := os.Getenv("HIBP_API_KEY")
|
||||||
hc := New(WithApiKey(apiKey), WithRateLimitSleep())
|
hc := New(WithAPIKey(apiKey), WithRateLimitSleep())
|
||||||
if hc.ak != apiKey {
|
if hc.ak != apiKey {
|
||||||
t.Errorf("hibp client API key was not set properly. Expected %s, got: %s",
|
t.Errorf("hibp client API key was not set properly. Expected %s, got: %s",
|
||||||
apiKey, hc.ak)
|
apiKey, hc.ak)
|
||||||
|
|
16
password.go
16
password.go
|
@ -19,8 +19,8 @@ const (
|
||||||
ErrSHA1LengthMismatch = "SHA1 hash size needs to be 160 bits"
|
ErrSHA1LengthMismatch = "SHA1 hash size needs to be 160 bits"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PwnedPassApi is a HIBP Pwned Passwords API client
|
// PwnedPassAPI is a HIBP Pwned Passwords API client
|
||||||
type PwnedPassApi struct {
|
type PwnedPassAPI struct {
|
||||||
hibp *Client // References back to the parent HIBP client
|
hibp *Client // References back to the parent HIBP client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ type PwnedPasswordOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPassword checks the Pwned Passwords database against a given password string
|
// CheckPassword checks the Pwned Passwords database against a given password string
|
||||||
func (p *PwnedPassApi) 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 string
|
// CheckSHA1 checks the Pwned Passwords database against a given SHA1 checksum of a password string
|
||||||
func (p *PwnedPassApi) CheckSHA1(h string) (*Match, *http.Response, error) {
|
func (p *PwnedPassAPI) CheckSHA1(h string) (*Match, *http.Response, error) {
|
||||||
if len(h) != 40 {
|
if len(h) != 40 {
|
||||||
return nil, nil, fmt.Errorf(ErrSHA1LengthMismatch)
|
return nil, nil, fmt.Errorf(ErrSHA1LengthMismatch)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func (p *PwnedPassApi) CheckSHA1(h string) (*Match, *http.Response, error) {
|
||||||
//
|
//
|
||||||
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
||||||
// contain junk data
|
// contain junk data
|
||||||
func (p *PwnedPassApi) ListHashesPassword(pw string) ([]Match, *http.Response, error) {
|
func (p *PwnedPassAPI) ListHashesPassword(pw string) ([]Match, *http.Response, error) {
|
||||||
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(pw)))
|
shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(pw)))
|
||||||
return p.ListHashesSHA1(shaSum)
|
return p.ListHashesSHA1(shaSum)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ func (p *PwnedPassApi) ListHashesPassword(pw string) ([]Match, *http.Response, e
|
||||||
//
|
//
|
||||||
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
||||||
// contain junk data
|
// contain junk data
|
||||||
func (p *PwnedPassApi) ListHashesSHA1(h string) ([]Match, *http.Response, error) {
|
func (p *PwnedPassAPI) ListHashesSHA1(h string) ([]Match, *http.Response, error) {
|
||||||
if len(h) != 40 {
|
if len(h) != 40 {
|
||||||
return nil, nil, fmt.Errorf(ErrSHA1LengthMismatch)
|
return nil, nil, fmt.Errorf(ErrSHA1LengthMismatch)
|
||||||
}
|
}
|
||||||
|
@ -89,11 +89,11 @@ func (p *PwnedPassApi) ListHashesSHA1(h string) ([]Match, *http.Response, error)
|
||||||
//
|
//
|
||||||
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
// NOTE: If the `WithPwnedPadding` option is set to true, the returned list will be padded and might
|
||||||
// contain junk data
|
// contain junk data
|
||||||
func (p *PwnedPassApi) ListHashesPrefix(pf string) ([]Match, *http.Response, error) {
|
func (p *PwnedPassAPI) ListHashesPrefix(pf string) ([]Match, *http.Response, error) {
|
||||||
if len(pf) != 5 {
|
if len(pf) != 5 {
|
||||||
return nil, nil, fmt.Errorf(ErrPrefixLengthMismatch)
|
return nil, nil, fmt.Errorf(ErrPrefixLengthMismatch)
|
||||||
}
|
}
|
||||||
hreq, err := p.hibp.HttpReq(http.MethodGet, fmt.Sprintf("https://api.pwnedpasswords.com/range/%s", pf),
|
hreq, err := p.hibp.HTTPReq(http.MethodGet, fmt.Sprintf("https://api.pwnedpasswords.com/range/%s", pf),
|
||||||
nil)
|
nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -21,21 +21,23 @@ const (
|
||||||
PwHashSecure = "90efc095c82eab44e882fda507cfab1a2cd31fc0"
|
PwHashSecure = "90efc095c82eab44e882fda507cfab1a2cd31fc0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestPwnedPassApi_CheckPassword verifies the Pwned Passwords API with the CheckPassword method
|
// TestPwnedPassAPI_CheckPassword verifies the Pwned Passwords API with the CheckPassword method
|
||||||
func TestPwnedPassApi_CheckPassword(t *testing.T) {
|
func TestPwnedPassAPI_CheckPassword(t *testing.T) {
|
||||||
testTable := []struct {
|
testTable := []struct {
|
||||||
testName string
|
testName string
|
||||||
pwString string
|
pwString string
|
||||||
isLeaked bool
|
isLeaked bool
|
||||||
}{
|
}{
|
||||||
{"weak password 'test123' is expected to be leaked", PwStringInsecure, true},
|
{"weak password 'test123' is expected to be leaked", PwStringInsecure, true},
|
||||||
{"strong, unknown password is expected to be not leaked",
|
{
|
||||||
PwStringSecure, false},
|
"strong, unknown password is expected to be not leaked",
|
||||||
|
PwStringSecure, false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
hc := New()
|
hc := New()
|
||||||
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.PwnedPassApi.CheckPassword(tc.pwString)
|
m, _, err := hc.PwnedPassAPI.CheckPassword(tc.pwString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -50,25 +52,31 @@ func TestPwnedPassApi_CheckPassword(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPwnedPassApi_CheckSHA1 verifies the Pwned Passwords API with the CheckSHA1 method
|
// TestPwnedPassAPI_CheckSHA1 verifies the Pwned Passwords API with the CheckSHA1 method
|
||||||
func TestPwnedPassApi_CheckSHA1(t *testing.T) {
|
func TestPwnedPassAPI_CheckSHA1(t *testing.T) {
|
||||||
testTable := []struct {
|
testTable := []struct {
|
||||||
testName string
|
testName string
|
||||||
pwHash string
|
pwHash string
|
||||||
isLeaked bool
|
isLeaked bool
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{"weak password 'test' is expected to be leaked",
|
{
|
||||||
PwHashInsecure, true, false},
|
"weak password 'test' is expected to be leaked",
|
||||||
{"strong, unknown password is expected to be not leaked",
|
PwHashInsecure, true, false,
|
||||||
PwHashSecure, false, false},
|
},
|
||||||
{"empty string should fail",
|
{
|
||||||
"", false, true},
|
"strong, unknown password is expected to be not leaked",
|
||||||
|
PwHashSecure, false, false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty string should fail",
|
||||||
|
"", false, true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
hc := New()
|
hc := New()
|
||||||
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.PwnedPassApi.CheckSHA1(tc.pwHash)
|
m, _, err := hc.PwnedPassAPI.CheckSHA1(tc.pwHash)
|
||||||
if err != nil && !tc.shouldFail {
|
if err != nil && !tc.shouldFail {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -84,13 +92,13 @@ func TestPwnedPassApi_CheckSHA1(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPwnedPassApi_ListHashesPrefix tests the ListHashesPrefix method (especially for failures that are not
|
// TestPwnedPassAPI_ListHashesPrefix tests the ListHashesPrefix method (especially for failures that are not
|
||||||
// tested by the other tests already)
|
// tested by the other tests already)
|
||||||
func TestPwnedPassApi_ListHashesPrefix(t *testing.T) {
|
func TestPwnedPassAPI_ListHashesPrefix(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
|
|
||||||
// Should return at least 1 restults
|
// Should return at least 1 restults
|
||||||
l, _, err := hc.PwnedPassApi.ListHashesPrefix("a94a8")
|
l, _, err := hc.PwnedPassAPI.ListHashesPrefix("a94a8")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ListHashesPrefix was not supposed to fail, but did: %s", err)
|
t.Errorf("ListHashesPrefix was not supposed to fail, but did: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -99,24 +107,24 @@ func TestPwnedPassApi_ListHashesPrefix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix has wrong size
|
// Prefix has wrong size
|
||||||
_, _, err = hc.PwnedPassApi.ListHashesPrefix("ZZZZZZZZZZZZZZ")
|
_, _, err = hc.PwnedPassAPI.ListHashesPrefix("ZZZZZZZZZZZZZZ")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("ListHashesPrefix was supposed to fail, but didn't")
|
t.Errorf("ListHashesPrefix was supposed to fail, but didn't")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non allowed characters
|
// Non allowed characters
|
||||||
_, _, err = hc.PwnedPassApi.ListHashesPrefix(string([]byte{0, 0, 0, 0, 0}))
|
_, _, err = hc.PwnedPassAPI.ListHashesPrefix(string([]byte{0, 0, 0, 0, 0}))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("ListHashesPrefix was supposed to fail, but didn't")
|
t.Errorf("ListHashesPrefix was supposed to fail, but didn't")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPwnedPassApi_ListHashesSHA1 tests the PwnedPassApi.ListHashesSHA1 metethod
|
// TestPwnedPassApi_ListHashesSHA1 tests the PwnedPassAPI.ListHashesSHA1 metethod
|
||||||
func TestPwnedPassApi_ListHashesSHA1(t *testing.T) {
|
func TestPwnedPassAPI_ListHashesSHA1(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
|
|
||||||
// List length should be >0
|
// List length should be >0
|
||||||
l, _, err := hc.PwnedPassApi.ListHashesSHA1(PwHashInsecure)
|
l, _, err := hc.PwnedPassAPI.ListHashesSHA1(PwHashInsecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ListHashesSHA1 was not supposed to fail, but did: %s", err)
|
t.Errorf("ListHashesSHA1 was not supposed to fail, but did: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -125,18 +133,18 @@ func TestPwnedPassApi_ListHashesSHA1(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash has wrong size
|
// Hash has wrong size
|
||||||
_, _, err = hc.PwnedPassApi.ListHashesSHA1(PwStringInsecure)
|
_, _, err = hc.PwnedPassAPI.ListHashesSHA1(PwStringInsecure)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("ListHashesSHA1 was supposed to fail, but didn't")
|
t.Errorf("ListHashesSHA1 was supposed to fail, but didn't")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPwnedPassApi_ListHashesPassword tests the PwnedPassApi.ListHashesPassword metethod
|
// TestPwnedPassAPI_ListHashesPassword tests the PwnedPassAPI.ListHashesPassword metethod
|
||||||
func TestPwnedPassApi_ListHashesPassword(t *testing.T) {
|
func TestPwnedPassAPI_ListHashesPassword(t *testing.T) {
|
||||||
hc := New()
|
hc := New()
|
||||||
|
|
||||||
// List length should be >0
|
// List length should be >0
|
||||||
l, _, err := hc.PwnedPassApi.ListHashesPassword(PwStringInsecure)
|
l, _, err := hc.PwnedPassAPI.ListHashesPassword(PwStringInsecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ListHashesPassword was not supposed to fail, but did: %s", err)
|
t.Errorf("ListHashesPassword was not supposed to fail, but did: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -145,17 +153,17 @@ func TestPwnedPassApi_ListHashesPassword(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty string has no checksum
|
// Empty string has no checksum
|
||||||
_, _, err = hc.PwnedPassApi.ListHashesSHA1("")
|
_, _, err = hc.PwnedPassAPI.ListHashesSHA1("")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("ListHashesPassword was supposed to fail, but didn't")
|
t.Errorf("ListHashesPassword was supposed to fail, but didn't")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExamplePwnedPassApi_CheckPassword is a code example to show how to check a given password
|
// ExamplePwnedPassAPI_CheckPassword is a code example to show how to check a given password
|
||||||
// against the HIBP passwords API
|
// against the HIBP passwords API
|
||||||
func ExamplePwnedPassApi_CheckPassword() {
|
func ExamplePwnedPassAPI_CheckPassword() {
|
||||||
hc := New()
|
hc := New()
|
||||||
m, _, err := hc.PwnedPassApi.CheckPassword("test")
|
m, _, err := hc.PwnedPassAPI.CheckPassword("test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -166,11 +174,11 @@ func ExamplePwnedPassApi_CheckPassword() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExamplePwnedPassApi_CheckPassword_withPadding is a code example to show how to check a given password
|
// ExamplePwnedPassAPI_CheckPassword_withPadding is a code example to show how to check a given password
|
||||||
// against the HIBP passwords API with the WithPadding() option set
|
// against the HIBP passwords API with the WithPadding() option set
|
||||||
func ExamplePwnedPassApi_CheckPassword_withPadding() {
|
func ExamplePwnedPassAPI_CheckPassword_withPadding() {
|
||||||
hc := New(WithPwnedPadding())
|
hc := New(WithPwnedPadding())
|
||||||
m, _, err := hc.PwnedPassApi.CheckPassword("test")
|
m, _, err := hc.PwnedPassAPI.CheckPassword("test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -181,12 +189,12 @@ func ExamplePwnedPassApi_CheckPassword_withPadding() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExamplePwnedPassApi_CheckSHA1 is a code example to show how to check a given password SHA1 hash
|
// ExamplePwnedPassAPI_checkSHA1 is a code example to show how to check a given password SHA1 hash
|
||||||
// against the HIBP passwords API using the CheckSHA1() method
|
// against the HIBP passwords API using the CheckSHA1() method
|
||||||
func ExamplePwnedPassApi_CheckSHA1() {
|
func ExamplePwnedPassAPI_checkSHA1() {
|
||||||
hc := New()
|
hc := New()
|
||||||
pwHash := "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" // represents the PW: "test"
|
pwHash := "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" // represents the PW: "test"
|
||||||
m, _, err := hc.PwnedPassApi.CheckSHA1(pwHash)
|
m, _, err := hc.PwnedPassAPI.CheckSHA1(pwHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
14
paste.go
14
paste.go
|
@ -7,8 +7,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasteApi is a HIBP pastes API client
|
// PasteAPI is a HIBP pastes API client
|
||||||
type PasteApi struct {
|
type PasteAPI struct {
|
||||||
hibp *Client // References back to the parent HIBP client
|
hibp *Client // References back to the parent HIBP client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ type Paste struct {
|
||||||
// Pastie, Slexy, Ghostbin, QuickLeak, JustPaste, AdHocUrl, PermanentOptOut, OptOut
|
// Pastie, Slexy, Ghostbin, QuickLeak, JustPaste, AdHocUrl, PermanentOptOut, OptOut
|
||||||
Source string `json:"Source"`
|
Source string `json:"Source"`
|
||||||
|
|
||||||
// Id of the paste as it was given at the source service. Combined with the "Source" attribute, this
|
// 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
|
// can be used to resolve the URL of the paste
|
||||||
Id string `json:"Id"`
|
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
|
// Title of the paste as observed on the source site. This may be null and if so will be omitted from
|
||||||
// the response
|
// the response
|
||||||
|
@ -36,13 +36,13 @@ type Paste struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PastedAccount returns a single breached site based on its name
|
// PastedAccount returns a single breached site based on its name
|
||||||
func (p *PasteApi) PastedAccount(a string) ([]*Paste, *http.Response, error) {
|
func (p *PasteAPI) PastedAccount(a string) ([]*Paste, *http.Response, error) {
|
||||||
if a == "" {
|
if a == "" {
|
||||||
return nil, nil, fmt.Errorf("no account id given")
|
return nil, nil, fmt.Errorf("no account id given")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiUrl := fmt.Sprintf("%s/pasteaccount/%s", BaseUrl, a)
|
apiURL := fmt.Sprintf("%s/pasteaccount/%s", BaseURL, a)
|
||||||
hb, hr, err := p.hibp.HttpResBody(http.MethodGet, apiUrl, nil)
|
hb, hr, err := p.hibp.HTTPResBody(http.MethodGet, apiURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ func TestPasteAccount(t *testing.T) {
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
hc := New(WithApiKey(apiKey), WithRateLimitSleep())
|
hc := New(WithAPIKey(apiKey), WithRateLimitSleep())
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
pasteDetails, _, err := hc.PasteApi.PastedAccount(tc.accountName)
|
pasteDetails, _, err := hc.PasteAPI.PastedAccount(tc.accountName)
|
||||||
if err != nil && !tc.shouldFail {
|
if err != nil && !tc.shouldFail {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -43,15 +43,15 @@ func TestPasteAccount(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExamplePasteApi_PastedAccount is a code example to show how to fetch a specific paste
|
// ExamplePasteAPI_pastedAccount is a code example to show how to fetch a specific paste
|
||||||
// based on its name from the HIBP pastes API using the PastedAccount() method
|
// based on its name from the HIBP pastes API using the PastedAccount() method
|
||||||
func ExamplePasteApi_PastedAccount() {
|
func ExamplePasteAPI_pastedAccount() {
|
||||||
apiKey := os.Getenv("HIBP_API_KEY")
|
apiKey := os.Getenv("HIBP_API_KEY")
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
panic("A API key is required for this API")
|
panic("A API key is required for this API")
|
||||||
}
|
}
|
||||||
hc := New(WithApiKey(apiKey))
|
hc := New(WithAPIKey(apiKey))
|
||||||
pd, _, err := hc.PasteApi.PastedAccount("account-exists@hibp-integration-tests.com")
|
pd, _, err := hc.PasteAPI.PastedAccount("account-exists@hibp-integration-tests.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue