Skip to content

Commit

Permalink
v0.2.0 release
Browse files Browse the repository at this point in the history
  * take an arg and notify server is running
  * fixes dockerfile
  * gank code from deprecated hibp module
  * unexport functions and struct
  • Loading branch information
audibleblink committed Aug 19, 2020
1 parent 86c7909 commit f5e3696
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 26 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM golang:1.15

WORKDIR /app
COPY go.* .
COPY go.* ./
RUN go mod download

COPY . .
CMD ["go", "run", "main.go"]

# $ docker build -t passdb-server .
# $ docker run --env-file .env passdb-server
# $ docker run --env-file .env -p 3000:3000 passdb-server
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.15

require (
cloud.google.com/go/bigquery v1.10.0
github.com/audibleblink/haveibeenpwned v0.0.0-20200818214456-56e4be30fb8d
github.com/go-chi/chi v4.1.2+incompatible
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect
google.golang.org/api v0.29.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/audibleblink/haveibeenpwned v0.0.0-20200818214456-56e4be30fb8d h1:uyZwWOznDwSaK2dnhNcxp9Y71TNMxQ56u/lRCM1tGpo=
github.com/audibleblink/haveibeenpwned v0.0.0-20200818214456-56e4be30fb8d/go.mod h1:UnV/rC9/gIWgDPaLj/4qEOiVtwSS8xgofXGyvKcSuCg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down
102 changes: 102 additions & 0 deletions hibp/hibp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package hibp

import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"os"
)

//API URL of haveibeenpwned.com
const API = "https://haveibeenpwned.com/api/v3/"

//BreachModel Each breach contains a number of attributes describing the incident. In the future, these attributes may expand without the API being versioned.
type BreachModel struct {
Name string `json:"Name,omitempty"`
Title string `json:"Title,omitempty"`
Domain string `json:"Domain,omitempty"`
BreachDate string `json:"BreachDate,omitempty"`
AddedDate string `json:"AddedDate,omitempty"`
ModifiedDate string `json:"ModifiedDate,omitempty"`
PwnCount int `json:"PwnCount,omitempty"`
Description string `json:"Description,omitempty"`
DataClasses []string `json:"DataClasses,omitempty"`
IsVerified bool `json:"IsVerified,omitempty"`
IsFabricated bool `json:"IsFabricated,omitempty"`
IsSensitive bool `json:"IsSensitive,omitempty"`
IsRetired bool `json:"IsRetired,omitempty"`
IsSpamList bool `json:"IsSpamList,omitempty"`
LogoPath string `json:"LogoPath,omitempty"`
}

//BreachedAccount The most common use of the API is to return a list of all breaches a particular account has been involved in. The API takes a single parameter which is the account to be searched for. The account is not case sensitive and will be trimmed of leading or trailing white spaces. The account should always be URL encoded.
func BreachedAccount(account, domainFilter string, truncate, unverified bool) ([]BreachModel, error) {

res, err := callService("breachedaccount", account, domainFilter, truncate, unverified)
if err != nil {
return nil, err
}
if res.StatusCode == http.StatusNotFound {
return nil, nil
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()

breaches := make([]BreachModel, 0)
if err := json.Unmarshal(body, &breaches); err != nil {
return nil, err
}

return breaches, nil
}

func callService(service, account, domainFilter string, truncate, unverified bool) (*http.Response, error) {
client := &http.Client{}

u, err := url.Parse(API)
if err != nil {
return nil, err
}

u.Path += service + "/" + account
parameters := url.Values{}
if domainFilter != "" {
parameters.Add("domain", domainFilter)
}
if truncate == false {
parameters.Add("truncateResponse", "false")
}
if unverified {
parameters.Add("includeUnverified", "true")
}
u.RawQuery = parameters.Encode()

req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}

req.Header.Set("User-Agent", "Go/1.15")
req.Header.Set("hibp-api-key", os.Getenv("HIBP_API_KEY"))
res, err := client.Do(req)

switch res.StatusCode {
case http.StatusBadRequest:
return nil, errors.New("the account does not comply with an acceptable format")
case http.StatusTooManyRequests:
return nil, errors.New("too many requests — the rate limit has been exceeded")
case http.StatusUnauthorized:
return nil, errors.New("valid header `hibp-api-key` required")
}

if err != nil {
return nil, err
}
return res, nil
}
48 changes: 27 additions & 21 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ import (
"os"
"strings"

hibp "github.com/audibleblink/haveibeenpwned"

"google.golang.org/api/iterator"

"cloud.google.com/go/bigquery"
"google.golang.org/api/iterator"

"github.com/audibleblink/passdb/hibp"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
Expand All @@ -25,6 +23,8 @@ var (
googleCred = os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
hibpKey = os.Getenv("HIBP_API_KEY")

port = "3000"

bq *bigquery.Client
)

Expand All @@ -35,6 +35,10 @@ func init() {
log.Fatal(err)
}

if len(os.Args) > 1 {
port = os.Args[1]
}

ctx := context.Background()
bq, err = bigquery.NewClient(ctx, projectID)
if err != nil {
Expand All @@ -55,19 +59,21 @@ func main() {
r.Get("/emails/{email}", handleEmail)
r.Get("/breaches/{email}", handleBreaches)

err := http.ListenAndServe(":3000", r)
listen := fmt.Sprintf("127.0.0.1:%s", port)
log.Printf("Starting server on %s\n", listen)
err := http.ListenAndServe(listen, r)
if err != nil {
log.Fatal(err)
}
}

type Record struct {
type record struct {
Username string
Domain string
Password string
}

type Breach struct {
type breach struct {
Title string
Domain string
Date string
Expand Down Expand Up @@ -124,9 +130,9 @@ func handleBreaches(w http.ResponseWriter, r *http.Request) {
return
}

var breaches []*Breach
var breaches []*breach
for _, hibpBreach := range hibpBreaches {
breach := &Breach{
breach := &breach{
Title: hibpBreach.Title,
Domain: hibpBreach.Domain,
Date: hibpBreach.BreachDate,
Expand All @@ -145,19 +151,19 @@ func handleBreaches(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}

func recordsByUsername(username string) (records []*Record, err error) {
func recordsByUsername(username string) (records []*record, err error) {
return recordsBy("username", username)
}

func recordsByPassword(password string) (records []*Record, err error) {
func recordsByPassword(password string) (records []*record, err error) {
return recordsBy("password", password)
}

func recordsByDomain(domain string) (records []*Record, err error) {
func recordsByDomain(domain string) (records []*record, err error) {
return recordsBy("domain", domain)
}

func recordsByEmail(email string) (records []*Record, err error) {
func recordsByEmail(email string) (records []*record, err error) {
usernameAndDomain := strings.Split(email, "@")
if len(usernameAndDomain) != 2 {
err = fmt.Errorf("invalid email format")
Expand All @@ -169,7 +175,12 @@ func recordsByEmail(email string) (records []*Record, err error) {
return queryRecords(queryString)
}

func queryRecords(queryString string) (records []*Record, err error) {
func recordsBy(column, value string) (records []*record, err error) {
queryString := fmt.Sprintf(`SELECT DISTINCT * FROM %s WHERE %s = "%s"`, bigQueryTable, column, value)
return queryRecords(queryString)
}

func queryRecords(queryString string) (records []*record, err error) {
query := bq.Query(queryString)
ctx := context.Background()
results, err := query.Read(ctx)
Expand All @@ -178,7 +189,7 @@ func queryRecords(queryString string) (records []*Record, err error) {
}

for {
var r Record
var r record
err = results.Next(&r)
if err == iterator.Done {
err = nil
Expand All @@ -192,12 +203,7 @@ func queryRecords(queryString string) (records []*Record, err error) {
return
}

func recordsBy(column, value string) (records []*Record, err error) {
queryString := fmt.Sprintf(`SELECT DISTINCT * FROM %s WHERE %s = "%s"`, bigQueryTable, column, value)
return queryRecords(queryString)
}

func resultWriter(w http.ResponseWriter, records *[]*Record) {
func resultWriter(w http.ResponseWriter, records *[]*record) {
resultJson, err := json.Marshal(records)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down

0 comments on commit f5e3696

Please sign in to comment.