Skip to content

Commit 1493042

Browse files
committed
authentication added
1 parent 4327c9a commit 1493042

File tree

6 files changed

+86
-15
lines changed

6 files changed

+86
-15
lines changed

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Basic CRUD operations (Create-Read-Update-Delete).
88

99
## Database Scheme
1010

11+
Data table:
12+
1113
```sql
1214
create table products
1315
(
@@ -19,34 +21,48 @@ create table products
1921

2022
You can generate data from http://filldb.info/.
2123

24+
Users table:
25+
26+
```sql
27+
CREATE TABLE `users` (
28+
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
29+
`username` text NOT NULL,
30+
`saltedpassword` text NOT NULL,
31+
`salt` text NOT NULL,
32+
PRIMARY KEY (`id`)
33+
);
34+
```
35+
36+
````Password+Salt```` is encrypted with ``bcrypt``with 10 rounds and stored in ``saltedpassword``column.
37+
2238
## Example Requests
2339

2440
To get all entries from table:
2541
```
26-
curl 127.0.0.1:8000/api/products/list
42+
curl --user user1:pass1 127.0.0.1:8000/api/products/list
2743
```
2844

2945
To get an entry with `id` (where id equals 10):
3046
```
31-
curl 127.0.0.1:8000/api/products/10
47+
curl --user user1:pass1 127.0.0.1:8000/api/products/10
3248
```
3349

3450
To create an entry:
3551
```
3652
curl --header "Content-Type: application/json" \
3753
--request POST \
3854
--data '{"name": "ABC", "manufacturer": "ACME"}' \
39-
127.0.0.1:8000/api/products/new
55+
--user user1:pass1 127.0.0.1:8000/api/products/new
4056
```
4157

4258
To update an entry:
4359
```
4460
curl --request PUT \
4561
--data '{"name": "ABC", "manufacturer": "ACME"}' \
46-
127.0.0.1:8000/api/products/11
62+
--user user1:pass1 127.0.0.1:8000/api/products/11
4763
```
4864

4965
To delete an entry:
5066
```
51-
curl --request DELETE 127.0.0.1:8000/api/products/10
67+
curl --request DELETE --user user1:pass1 127.0.0.1:8000/api/products/10
5268
```

app.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ package main
2323

2424
import (
2525
"database/sql"
26+
"encoding/base64"
2627
"encoding/json"
2728
_ "github.com/go-sql-driver/mysql"
2829
"github.com/gorilla/handlers"
2930
"github.com/gorilla/mux"
3031
"github.com/spf13/viper"
32+
"golang.org/x/crypto/bcrypt"
3133
"log"
3234
"net/http"
3335
"os"
3436
"strconv"
37+
"strings"
3538
)
3639

3740
type App struct {
@@ -40,7 +43,7 @@ type App struct {
4043
DB *sql.DB
4144
}
4245

43-
// curl 127.0.0.1:8000/api/products/list
46+
// curl --user user1:pass1 127.0.0.1:8000/api/products/list
4447
func (a *App) getProducts(w http.ResponseWriter, r *http.Request) {
4548
rows, err := a.DB.Query("SELECT * FROM products")
4649
if err != nil {
@@ -63,7 +66,7 @@ func (a *App) getProducts(w http.ResponseWriter, r *http.Request) {
6366
}
6467

6568
// curl --header "Content-Type: application/json" --request POST --data '{"name": "ABC", "manufacturer": "ACME"}' \
66-
// 127.0.0.1:8000/api/products/new
69+
// --user user1:pass1 127.0.0.1:8000/api/products/new
6770
func (a *App) createProduct(w http.ResponseWriter, r *http.Request) {
6871
var p Product
6972
decoder := json.NewDecoder(r.Body)
@@ -82,7 +85,7 @@ func (a *App) createProduct(w http.ResponseWriter, r *http.Request) {
8285
respondWithMessage(w, http.StatusCreated, "New row added.")
8386
}
8487

85-
// curl 127.0.0.1:8000/api/products/10
88+
// curl --user user1:pass1 127.0.0.1:8000/api/products/10
8689
func (a *App) getProduct(w http.ResponseWriter, r *http.Request) {
8790
vars := mux.Vars(r)
8891
id, err := strconv.Atoi(vars["id"])
@@ -101,7 +104,7 @@ func (a *App) getProduct(w http.ResponseWriter, r *http.Request) {
101104
respondWithJSON(w, http.StatusOK, p)
102105
}
103106

104-
// curl --request PUT --data '{"name": "ABC", "manufacturer": "ACME"}' 127.0.0.1:8000/api/products/11
107+
// curl --request PUT --data '{"name": "ABC", "manufacturer": "ACME"}' --user user1:pass1 127.0.0.1:8000/api/products/11
105108
func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) {
106109
vars := mux.Vars(r)
107110
id, err := strconv.Atoi(vars["id"])
@@ -128,7 +131,7 @@ func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) {
128131
respondWithJSON(w, http.StatusOK, p)
129132
}
130133

131-
// curl --request DELETE 127.0.0.1:8000/api/products/10
134+
// curl --request DELETE --user user1:pass1 127.0.0.1:8000/api/products/10
132135
func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) {
133136
vars := mux.Vars(r)
134137
id, err := strconv.Atoi(vars["id"])
@@ -146,12 +149,48 @@ func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) {
146149
respondWithMessage(w, http.StatusOK, "Deleted.")
147150
}
148151

152+
func (a *App) authHandler(f http.HandlerFunc) http.HandlerFunc {
153+
return func(w http.ResponseWriter, r *http.Request) {
154+
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
155+
if len(s) != 2 {
156+
respondWithError(w, http.StatusUnauthorized, "Invalid/Missing Credentials.")
157+
return
158+
}
159+
160+
b, err := base64.StdEncoding.DecodeString(s[1])
161+
if err != nil {
162+
respondWithError(w, http.StatusUnauthorized, "Invalid/Missing Credentials.")
163+
return
164+
}
165+
166+
pair := strings.SplitN(string(b), ":", 2)
167+
if len(pair) != 2 {
168+
respondWithError(w, http.StatusUnauthorized, "Invalid/Missing Credentials.")
169+
return
170+
}
171+
172+
user := User{Username: pair[0]}
173+
row := a.DB.QueryRow("SELECT id, saltedpassword, salt FROM users WHERE username=?", user.Username)
174+
if err := row.Scan(&user.Id, &user.Saltedpassword, &user.Salt); err != nil {
175+
respondWithError(w, http.StatusUnauthorized, "Invalid/Missing Credentials.")
176+
return
177+
}
178+
179+
if err := bcrypt.CompareHashAndPassword([]byte(user.Saltedpassword), []byte(pair[1]+user.Salt)); err != nil {
180+
respondWithError(w, http.StatusUnauthorized, "Invalid/Missing Credentials.")
181+
return
182+
}
183+
184+
f(w, r)
185+
}
186+
}
187+
149188
func (a *App) InitializeRoutes() {
150-
a.Router.HandleFunc("/api/products/list", a.getProducts).Methods("GET")
151-
a.Router.HandleFunc("/api/products/new", a.createProduct).Methods("POST")
152-
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.getProduct).Methods("GET")
153-
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.updateProduct).Methods("PUT")
154-
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.deleteProduct).Methods("DELETE")
189+
a.Router.HandleFunc("/api/products/list", a.authHandler(a.getProducts)).Methods("GET")
190+
a.Router.HandleFunc("/api/products/new", a.authHandler(a.createProduct)).Methods("POST")
191+
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.authHandler(a.getProduct)).Methods("GET")
192+
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.authHandler(a.updateProduct)).Methods("PUT")
193+
a.Router.HandleFunc("/api/products/{id:[0-9]+}", a.authHandler(a.deleteProduct)).Methods("DELETE")
155194
}
156195

157196
func (a *App) Initialize(username, password, server, port, dbName string) {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ require (
99
github.com/gorilla/mux v1.7.0
1010
github.com/magiconair/properties v1.8.0
1111
github.com/spf13/viper v1.3.2
12+
golang.org/x/crypto v0.0.0-20190417174047-f416ebab96af
1213
google.golang.org/appengine v1.5.0 // indirect
1314
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
4040
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
4141
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
4242
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
43+
golang.org/x/crypto v0.0.0-20190417174047-f416ebab96af h1:6qGQw30u837TXZbCmLFR9AVA+RjJU1LIbvk0oIkDZGY=
44+
golang.org/x/crypto v0.0.0-20190417174047-f416ebab96af/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
4345
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
4446
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
4547
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
48+
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
49+
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4650
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
4751
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
4852
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=

main_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func clearTestTable() {
8787

8888
func TestGetProduct(t *testing.T) {
8989
request, _ := http.NewRequest("GET", "/api/products/2", nil)
90+
request.SetBasicAuth("user1", "pass1")
9091
response := httptest.NewRecorder()
9192
a.Router.ServeHTTP(response, request)
9293
assert.Equal(t, response.Code, http.StatusOK)
@@ -96,6 +97,7 @@ func TestCreateProduct(t *testing.T) {
9697
newProduct := main.Product{Id: 1, Name: "Earthquake Pills", Manufacturer: "ACME"}
9798
payload, _ := json.Marshal(newProduct)
9899
request, _ := http.NewRequest("POST", "/api/products/new", bytes.NewReader(payload))
100+
request.SetBasicAuth("user1", "pass1")
99101
response := httptest.NewRecorder()
100102
a.Router.ServeHTTP(response, request)
101103
assert.Equal(t, response.Code, http.StatusCreated)
@@ -105,6 +107,7 @@ func TestUpdateProduct(t *testing.T) {
105107
updatedProduct := main.Product{Id: 1, Name: "Do-It-Yourself Tornado Kit", Manufacturer: "ACME"}
106108
payload, _ := json.Marshal(updatedProduct)
107109
request, _ := http.NewRequest("PUT", "/api/products/1", bytes.NewReader(payload))
110+
request.SetBasicAuth("user1", "pass1")
108111
response := httptest.NewRecorder()
109112
a.Router.ServeHTTP(response, request)
110113
assert.Equal(t, response.Code, http.StatusOK)
@@ -117,6 +120,7 @@ func TestUpdateProduct(t *testing.T) {
117120

118121
func TestDeleteProduct(t *testing.T) {
119122
request, _ := http.NewRequest("DELETE", "/api/products/1", nil)
123+
request.SetBasicAuth("user1", "pass1")
120124
response := httptest.NewRecorder()
121125
a.Router.ServeHTTP(response, request)
122126
assert.Equal(t, response.Code, http.StatusOK)

models.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,10 @@ type Product struct {
2222
Name string `json:"name"`
2323
Manufacturer string `json:"manufacturer"`
2424
}
25+
26+
type User struct {
27+
Id int
28+
Username string
29+
Saltedpassword string
30+
Salt string
31+
}

0 commit comments

Comments
 (0)