From fe441aea4d15ef7ebd1bc6711ca996d7937698ce Mon Sep 17 00:00:00 2001 From: zakhaev26 Date: Sun, 25 Feb 2024 01:21:22 +0530 Subject: [PATCH] feat/fix: adds rbac --- auth/internal/handlers/callback_signup.go | 28 +++------ auth/internal/handlers/login_handler.go | 67 +++++++++++++++++++++ auth/internal/handlers/signup_handler.go | 30 +++++---- auth/internal/router/routes.go | 1 + auth/internal/utils/extract_token.go | 17 ++++++ auth/internal/{ => utils}/otp_gen.go | 2 +- auth/internal/{ => utils}/unique_entries.go | 2 +- auth/pkg/security/jwt_impl.go | 9 ++- auth/pkg/security/rbac.go | 50 +++++++++++++++ go.mod | 2 +- go.sum | 4 +- schemas/pkg/models/user_model.go | 18 +----- 12 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 auth/internal/handlers/login_handler.go create mode 100644 auth/internal/utils/extract_token.go rename auth/internal/{ => utils}/otp_gen.go (93%) rename auth/internal/{ => utils}/unique_entries.go (97%) create mode 100644 auth/pkg/security/rbac.go diff --git a/auth/internal/handlers/callback_signup.go b/auth/internal/handlers/callback_signup.go index c232e3f..8c235ff 100644 --- a/auth/internal/handlers/callback_signup.go +++ b/auth/internal/handlers/callback_signup.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" - "github.com/p-society/gc-server/auth/internal" "github.com/p-society/gc-server/auth/internal/db" + "github.com/p-society/gc-server/auth/internal/utils" "github.com/p-society/gc-server/auth/pkg/security" model "github.com/p-society/gc-server/schemas/pkg/models" ) @@ -15,8 +15,7 @@ import ( func CallbackVerification(w http.ResponseWriter, r *http.Request) { var ( - p model.Player - pv *model.ValidationSchema + p *model.Player reqBody struct { OTP int `json:"otp"` } @@ -26,7 +25,7 @@ func CallbackVerification(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" && strings.Split(authHeader, " ")[0] != "Bearer" { - r.Header.Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "error": "Authorization Token Not found", }) @@ -34,35 +33,24 @@ func CallbackVerification(w http.ResponseWriter, r *http.Request) { } token := strings.Split(authHeader, " ")[1] - pv = security.ParseAccessToken(token) + p = security.ParseAccessToken(token) - if err := pv.Valid(); err != nil { - r.Header.Set("Content-Type", "application/json") + if err := p.Valid(); err != nil { + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "error": err.Error(), }) return } - if err := internal.CheckOTP(pv.OTP, reqBody.OTP); err != nil { - r.Header.Set("Content-Type", "application/json") + if err := utils.CheckOTP(p.OTP, reqBody.OTP); err != nil { + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "error": err.Error(), }) return } - p = model.Player{ - FirstName: pv.FirstName, - LastName: pv.LastName, - Role: pv.Role, - Email: pv.Email, - Branch: pv.Branch, - Year: pv.Branch, - ContactNo: pv.ContactNo, - Social: pv.Social, - } - res, _ := db.PlayerCollection.InsertOne(context.TODO(), p) json.NewEncoder(w).Encode(res.InsertedID) diff --git a/auth/internal/handlers/login_handler.go b/auth/internal/handlers/login_handler.go new file mode 100644 index 0000000..96328ec --- /dev/null +++ b/auth/internal/handlers/login_handler.go @@ -0,0 +1,67 @@ +package handlers + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "github.com/golang-jwt/jwt" + "github.com/p-society/gc-server/auth/internal/db" + "github.com/p-society/gc-server/auth/pkg/security" + model "github.com/p-society/gc-server/schemas/pkg/models" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "golang.org/x/crypto/bcrypt" +) + +func Login(w http.ResponseWriter, r *http.Request) { + var ( + p model.Player + reqBody struct { + Mail string `json:"mail"` + Password string `json:"password"` + } + ) + w.Header().Set("Content-Type", "application/json") + + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": err.Error(), + }) + } + + filter := bson.M{ + "email": reqBody.Mail, + } + + err := db.PlayerCollection.FindOne(context.TODO(), filter).Decode(&p) + if err == mongo.ErrNoDocuments { + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": err.Error(), + }) + return + } + + err = bcrypt.CompareHashAndPassword([]byte(p.Password), []byte(reqBody.Password)) + if err != nil { + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": err.Error(), + }) + return + } + + p.StandardClaims = jwt.StandardClaims{ + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(time.Minute * 60 * 24 * 12).Unix(), + } + + token, err := security.NewAccessToken(p) + if err != nil { + panic(err) + } + + json.NewEncoder(w).Encode(map[string]interface{}{ + "accessToken": token, + }) +} diff --git a/auth/internal/handlers/signup_handler.go b/auth/internal/handlers/signup_handler.go index 6748e74..55d19f6 100644 --- a/auth/internal/handlers/signup_handler.go +++ b/auth/internal/handlers/signup_handler.go @@ -7,22 +7,24 @@ import ( "time" "github.com/golang-jwt/jwt" - "github.com/p-society/gc-server/auth/internal" + "github.com/p-society/gc-server/auth/internal/utils" "github.com/p-society/gc-server/auth/pkg/security" model "github.com/p-society/gc-server/schemas/pkg/models" + "golang.org/x/crypto/bcrypt" ) func SignUpHandler(w http.ResponseWriter, r *http.Request) { - var pv model.ValidationSchema + var p model.Player - err := json.NewDecoder(r.Body).Decode(&pv) + w.Header().Set("Content-Type", "application/json") + err := json.NewDecoder(r.Body).Decode(&p) defer r.Body.Close() if err != nil { json.NewEncoder(w).Encode(err) return } - err = pv.Valid() + err = p.Valid() if err != nil { json.NewEncoder(w).Encode(map[string]interface{}{ @@ -31,10 +33,9 @@ func SignUpHandler(w http.ResponseWriter, r *http.Request) { return } - fmt.Println("pv.Email @signup", pv.Email) - err = internal.IsUniqueInDB(pv.Email) + fmt.Println("p.Email @signup", p.Email) + err = utils.IsUniqueInDB(p.Email) if err != nil { - r.Header.Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "error": err.Error(), }) @@ -43,14 +44,21 @@ func SignUpHandler(w http.ResponseWriter, r *http.Request) { // TODO:Send OTP Mail - pv.StandardClaims = jwt.StandardClaims{ + p.StandardClaims = jwt.StandardClaims{ IssuedAt: time.Now().Unix(), ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), } + // TODO : Check password to be + p.OTP = utils.GenerateOTP(6) + fmt.Println(p.OTP) + hashedPass, err := bcrypt.GenerateFromPassword([]byte(p.Password), 10) + if err != nil { + panic(err) + } + + p.Password = string(hashedPass) - pv.OTP = internal.GenerateOTP(6) - fmt.Println("uhTP : ", pv.OTP) - token, err := security.NewAccessToken(pv) + token, err := security.NewAccessToken(p) if err != nil { json.NewEncoder(w).Encode(map[string]interface{}{ "error": err.Error(), diff --git a/auth/internal/router/routes.go b/auth/internal/router/routes.go index dc66a26..463ad02 100644 --- a/auth/internal/router/routes.go +++ b/auth/internal/router/routes.go @@ -10,5 +10,6 @@ func AuthRouter() *mux.Router { r.HandleFunc("/v0/auth/signup", handlers.SignUpHandler).Methods("post") r.HandleFunc("/v0/auth/callback/signup", handlers.CallbackVerification).Methods("post") + r.HandleFunc("/v0/auth/login", handlers.Login).Methods("POST") return r } diff --git a/auth/internal/utils/extract_token.go b/auth/internal/utils/extract_token.go new file mode 100644 index 0000000..70a4d1e --- /dev/null +++ b/auth/internal/utils/extract_token.go @@ -0,0 +1,17 @@ +package utils + +import ( + "fmt" + "net/http" + "strings" +) + +func ExtractTokenFromHeader(r *http.Request) (string, error) { + + authHeader := r.Header.Get("Authorization") + if authHeader == "" && strings.Split(authHeader, " ")[0] == "Bearer" { + return "", fmt.Errorf("token not found") + } + + return strings.Split(authHeader, " ")[1], nil +} diff --git a/auth/internal/otp_gen.go b/auth/internal/utils/otp_gen.go similarity index 93% rename from auth/internal/otp_gen.go rename to auth/internal/utils/otp_gen.go index 2d78128..bffcefc 100644 --- a/auth/internal/otp_gen.go +++ b/auth/internal/utils/otp_gen.go @@ -1,4 +1,4 @@ -package internal +package utils import ( "fmt" diff --git a/auth/internal/unique_entries.go b/auth/internal/utils/unique_entries.go similarity index 97% rename from auth/internal/unique_entries.go rename to auth/internal/utils/unique_entries.go index 05eada4..3f0f4c1 100644 --- a/auth/internal/unique_entries.go +++ b/auth/internal/utils/unique_entries.go @@ -1,4 +1,4 @@ -package internal +package utils import ( "context" diff --git a/auth/pkg/security/jwt_impl.go b/auth/pkg/security/jwt_impl.go index 141ea87..22b12ea 100644 --- a/auth/pkg/security/jwt_impl.go +++ b/auth/pkg/security/jwt_impl.go @@ -5,15 +5,14 @@ import ( model "github.com/p-society/gc-server/schemas/pkg/models" ) -func NewAccessToken(claims model.ValidationSchema) (string, error) { +func NewAccessToken(claims model.Player) (string, error) { accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return accessToken.SignedString([]byte("saswat,tu23kranklekeiiitkyunaaya")) } -func ParseAccessToken(accessToken string) *model.ValidationSchema { - parsedAccessToken, _ := jwt.ParseWithClaims(accessToken, &model.ValidationSchema{}, func(token *jwt.Token) (interface{}, error) { +func ParseAccessToken(accessToken string) *model.Player { + parsedAccessToken, _ := jwt.ParseWithClaims(accessToken, &model.Player{}, func(token *jwt.Token) (interface{}, error) { return []byte([]byte("saswat,tu23kranklekeiiitkyunaaya")), nil }) - return parsedAccessToken.Claims.(*model.ValidationSchema) + return parsedAccessToken.Claims.(*model.Player) } diff --git a/auth/pkg/security/rbac.go b/auth/pkg/security/rbac.go new file mode 100644 index 0000000..5186b44 --- /dev/null +++ b/auth/pkg/security/rbac.go @@ -0,0 +1,50 @@ +package security + +import ( + "net/http" + + "github.com/p-society/gc-server/auth/internal/utils" +) + +// RoleGuard is a middleware for role-based access control +type RoleGuard struct { + AllowedRoles []string + Handler http.Handler +} + +// ServeHTTP implements the http.Handler interface for RoleGuard +func (rg *RoleGuard) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var ( + token string + err error + ) + + token, err = utils.ExtractTokenFromHeader(r) + if err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + return + } + + p := ParseAccessToken(token) + + // Check if user's role is allowed + if !contains(rg.AllowedRoles, p.Role) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusForbidden) + return + } + + // Call the next handler in the chain + rg.Handler.ServeHTTP(w, r) +} + +// contains checks if a string is present in a slice of strings +func contains(roles []string, role string) bool { + for _, r := range roles { + if r == role { + return true + } + } + return false +} diff --git a/go.mod b/go.mod index 52f550a..f53f996 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( require ( github.com/golang-jwt/jwt v3.2.2+incompatible go.mongodb.org/mongo-driver v1.14.0 + golang.org/x/crypto v0.19.0 ) require ( @@ -21,7 +22,6 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/crypto v0.17.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index ea8e799..b82cc27 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/schemas/pkg/models/user_model.go b/schemas/pkg/models/user_model.go index fc87c34..b576037 100644 --- a/schemas/pkg/models/user_model.go +++ b/schemas/pkg/models/user_model.go @@ -13,27 +13,17 @@ type Player struct { FirstName string `json:"firstName" bson:"firstName"` LastName string `json:"lastName" bson:"lastName"` Email string `json:"email" bson:"email"` + Password string `json:"password" bson:"password"` Role string `json:"role" bson:"role"` Branch string `json:"branch" bson:"branch"` Year string `json:"year" bson:"year"` ContactNo string `json:"contactNo" bson:"contactNo"` Social []string `json:"socials,omitempty" bson:"socials,omitempty"` -} - -type ValidationSchema struct { - ID primitive.ObjectID `json:"_id,omitempty"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Email string `json:"email"` - Role string `json:"role"` - Branch string `json:"branch"` - Year string `json:"year"` - ContactNo string `json:"contactNo"` - Social []string `json:"socials,omitempty"` OTP int `json:"otp"` jwt.StandardClaims } + func ValidateRole(p *Player) error { for _, role := range enum.Roles { @@ -83,7 +73,3 @@ func ValidatePlayer(p Player) error { return nil } - -func (p Player) Valid() error { - return ValidatePlayer(p) -}