From 4d3292e1031038da9ca26af8eae8ff690b786f9c Mon Sep 17 00:00:00 2001
From: meskio <meskio@sindominio.net>
Date: Mon, 28 Sep 2020 21:00:35 +0200
Subject: [PATCH] Add admin authentication

---
 api/api.go         | 21 +++++++++++----------
 api/auth.go        | 40 +++++++++++++++++++++++++++++++++++++++-
 api/auth_test.go   |  1 +
 api/member.go      | 10 ++++++++--
 api/transaction.go |  8 +++++++-
 src/Dashboard.js   |  2 +-
 src/Head.js        |  1 -
 7 files changed, 67 insertions(+), 16 deletions(-)

diff --git a/api/api.go b/api/api.go
index 6c32111..065d611 100644
--- a/api/api.go
+++ b/api/api.go
@@ -36,21 +36,22 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 
 	r.HandleFunc("/signin", a.SignIn).Methods("POST")
 
-	r.HandleFunc("/member", a.auth(a.ListMembers)).Methods("GET")
-	r.HandleFunc("/member", a.auth(a.AddMember)).Methods("POST")
-	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.GetMember)).Methods("GET")
-	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.UpdateMember)).Methods("PUT")
-	r.HandleFunc("/member/{num:[0-9]+}", a.auth(a.DeleteMember)).Methods("DELETE")
-	r.HandleFunc("/member/{num:[0-9]+}/purchase", a.auth(a.GetMemberTransactions)).Methods("GET")
+	r.HandleFunc("/member", a.authAdmin(a.ListMembers)).Methods("GET")
+	r.HandleFunc("/member", a.authAdmin(a.AddMember)).Methods("POST")
+	r.HandleFunc("/member/me", a.authNum(a.getMemberNum)).Methods("GET")
+	r.HandleFunc("/member/{num:[0-9]+}", a.authAdmin(a.GetMember)).Methods("GET")
+	r.HandleFunc("/member/{num:[0-9]+}", a.authAdmin(a.UpdateMember)).Methods("PUT")
+	r.HandleFunc("/member/{num:[0-9]+}", a.authAdmin(a.DeleteMember)).Methods("DELETE")
+	r.HandleFunc("/member/{num:[0-9]+}/purchase", a.authAdmin(a.GetMemberTransactions)).Methods("GET")
 
 	r.HandleFunc("/product", a.auth(a.ListProducts)).Methods("GET")
-	r.HandleFunc("/product", a.auth(a.AddProduct)).Methods("POST")
+	r.HandleFunc("/product", a.authAdmin(a.AddProduct)).Methods("POST")
 	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.GetProduct)).Methods("GET")
-	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.UpdateProduct)).Methods("PUT")
-	r.HandleFunc("/product/{code:[0-9]+}", a.auth(a.DeleteProduct)).Methods("DELETE")
+	r.HandleFunc("/product/{code:[0-9]+}", a.authAdmin(a.UpdateProduct)).Methods("PUT")
+	r.HandleFunc("/product/{code:[0-9]+}", a.authAdmin(a.DeleteProduct)).Methods("DELETE")
 
 	r.HandleFunc("/transaction", a.auth(a.ListTransactions)).Methods("GET")
-	r.HandleFunc("/transaction/{id:[0-9]+}", a.auth(a.GetTransaction)).Methods("GET")
+	r.HandleFunc("/transaction/{id:[0-9]+}", a.authNumRole(a.GetTransaction)).Methods("GET")
 	r.HandleFunc("/transaction/mine", a.authNum(a.getTransactionsByMember)).Methods("GET")
 
 	r.HandleFunc("/purchase", a.authNum(a.AddPurchase)).Methods("POST")
diff --git a/api/auth.go b/api/auth.go
index 7f8e996..b8c747b 100644
--- a/api/auth.go
+++ b/api/auth.go
@@ -75,7 +75,6 @@ func (a *api) authNum(fn func(int, http.ResponseWriter, *http.Request)) func(htt
 		token := req.Header.Get("x-authentication")
 		ok, claims := a.validateToken(token)
 		if !ok {
-			log.Print("foo")
 			w.WriteHeader(http.StatusUnauthorized)
 			return
 		}
@@ -88,6 +87,45 @@ func (a *api) authNum(fn func(int, http.ResponseWriter, *http.Request)) func(htt
 	}
 }
 
+func (a *api) authAdmin(fn func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, req *http.Request) {
+		token := req.Header.Get("x-authentication")
+		ok, claims := a.validateToken(token)
+		if !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		role, ok := claims["role"].(string)
+		if !ok || role != "admin" {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		fn(w, req)
+	}
+}
+
+func (a *api) authNumRole(fn func(int, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, req *http.Request) {
+		token := req.Header.Get("x-authentication")
+		ok, claims := a.validateToken(token)
+		if !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		num, ok := claims["num"].(float64)
+		if !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		role, ok := claims["role"].(string)
+		if !ok {
+			w.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		fn(int(num), role, w, req)
+	}
+}
+
 func (a *api) newToken(num int, role string) (string, error) {
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 		"num":  num,
diff --git a/api/auth_test.go b/api/auth_test.go
index 534937f..3f5dbef 100644
--- a/api/auth_test.go
+++ b/api/auth_test.go
@@ -15,6 +15,7 @@ func TestSignIn(t *testing.T) {
 	}
 	member.Num = 10
 	member.Name = "foo"
+	member.Role = "admin"
 	member.Password = "password"
 	resp := tapi.do("POST", "/member", member, nil)
 	if resp.StatusCode != http.StatusCreated {
diff --git a/api/member.go b/api/member.go
index 60fa3e5..b274b90 100644
--- a/api/member.go
+++ b/api/member.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"log"
 	"net/http"
+	"strconv"
 
 	"github.com/gorilla/mux"
 	"gorm.io/gorm"
@@ -81,14 +82,19 @@ func (a *api) ListMembers(w http.ResponseWriter, req *http.Request) {
 
 func (a *api) GetMember(w http.ResponseWriter, req *http.Request) {
 	vars := mux.Vars(req)
+	num, _ := strconv.Atoi(vars["num"])
+	a.getMemberNum(num, w, req)
+}
+
+func (a *api) getMemberNum(num int, w http.ResponseWriter, req *http.Request) {
 	var member Member
-	err := a.db.Where("num = ?", vars["num"]).First(&member).Error
+	err := a.db.Where("num = ?", num).First(&member).Error
 	if err != nil {
 		if err.Error() == "record not found" {
 			w.WriteHeader(http.StatusNotFound)
 			return
 		}
-		log.Printf("Can't get member %s: %v", vars["num"], err)
+		log.Printf("Can't get member %d: %v", num, err)
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
diff --git a/api/transaction.go b/api/transaction.go
index ee73e30..59100dd 100644
--- a/api/transaction.go
+++ b/api/transaction.go
@@ -42,7 +42,7 @@ func (a *api) ListTransactions(w http.ResponseWriter, req *http.Request) {
 	}
 }
 
-func (a *api) GetTransaction(w http.ResponseWriter, req *http.Request) {
+func (a *api) GetTransaction(num int, role string, w http.ResponseWriter, req *http.Request) {
 	vars := mux.Vars(req)
 	var transaction Transaction
 	err := a.db.Preload("Purchase.Product").
@@ -57,6 +57,12 @@ func (a *api) GetTransaction(w http.ResponseWriter, req *http.Request) {
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
+
+	if transaction.MemberNum != num && role != "admin" {
+		w.WriteHeader(http.StatusUnauthorized)
+		return
+	}
+
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusOK)
 	err = json.NewEncoder(w).Encode(transaction)
diff --git a/src/Dashboard.js b/src/Dashboard.js
index c8b9aa0..d6ebe60 100644
--- a/src/Dashboard.js
+++ b/src/Dashboard.js
@@ -19,7 +19,7 @@ class Dashboard extends React.Component {
     render() {
         return (
             <Fetcher
-                    url={"/api/member/"+this.context.num.toString()}
+                    url={"/api/member/me"}
                     onFetch={member => this.setState({ balance: member.balance, name: member.name})} >
                 <Container>
                     <Row>
diff --git a/src/Head.js b/src/Head.js
index 7422b9a..fe5da85 100644
--- a/src/Head.js
+++ b/src/Head.js
@@ -13,7 +13,6 @@ function Head(props) {
                 <Nav className="mr-auto">
                     <Nav.Link href="/">Dashboard</Nav.Link>
                     <Nav.Link href="/products">Productos</Nav.Link>
-                    <Nav.Link href="/members">Socias</Nav.Link>
                 </Nav>
                 <Form inline>
                   <Button
-- 
GitLab