diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/db/mongo.go b/owasp-top10-2021-apps/a1/ecommerce-api/app/db/mongo.go index d5a1d097f..d60a4e5f4 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/db/mongo.go +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/db/mongo.go @@ -10,8 +10,7 @@ import ( // Collections names used in MongoDB. var ( - UserCollection = "users" - TicketsCollection = "tickets" // Adicione a coleção de tickets + UserCollection = "users" ) // DB is the struct that represents mongo session. @@ -35,7 +34,6 @@ type Database interface { UpdateAll(query, updateQuery bson.M, collection string) error Upsert(query bson.M, obj interface{}, collection string) (*mgo.ChangeInfo, error) SearchOne(query bson.M, selectors []string, collection string, obj interface{}) error - CheckUserPermission(userID, ticketID string) (bool, error) // Adicionado para refletir a sugestão } var config = &mongoConfig{ @@ -47,6 +45,8 @@ var config = &mongoConfig{ // Connect connects to mongo and returns the session. func Connect() (*DB, error) { + + // fmt.Printf("config:%#v", config) dialInfo := &mgo.DialInfo{ Addrs: []string{config.Address}, Timeout: time.Second * 60, @@ -64,36 +64,31 @@ func Connect() (*DB, error) { return nil, err } + //go autoReconnect(session) + return &DB{Session: session}, nil } -// CheckUserPermission verifies if a user has access to a specific ticket. -func (db *DB) CheckUserPermission(userID, ticketID string) (bool, error) { - session := db.Session.Clone() - defer session.Close() - c := session.DB(config.DatabaseName).C(TicketsCollection) - - query := bson.M{ - "userID": userID, - "ticketID": ticketID, - } - - var result bson.M - err := c.Find(query).One(&result) - if err != nil { - if err == mgo.ErrNotFound { - return false, nil +// autoReconnect checks mongo's connection each second and, if an error is found, reconnect to it. +func autoReconnect(session *mgo.Session) { + var err error + for { + err = session.Ping() + if err != nil { + session.Refresh() + err = session.Ping() + if err == nil { + } else { + } } - return false, err + time.Sleep(time.Second * 1) } - - return true, nil } // Insert inserts a new document. func (db *DB) Insert(obj interface{}, collection string) error { session := db.Session.Clone() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) defer session.Close() return c.Insert(obj) } @@ -101,25 +96,26 @@ func (db *DB) Insert(obj interface{}, collection string) error { // Update updates a single document. func (db *DB) Update(query, updateQuery interface{}, collection string) error { session := db.Session.Clone() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) defer session.Close() - return c.Update(query, updateQuery) + err := c.Update(query, updateQuery) + return err } // UpdateAll updates all documents that match the query. -func (db *DB) UpdateAll(query, updateQuery bson.M, collection string) error { +func (db *DB) UpdateAll(query, updateQuery interface{}, collection string) error { session := db.Session.Clone() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) defer session.Close() _, err := c.UpdateAll(query, updateQuery) return err } -// Search searches all documents that match the query. If selectors are present, the return will be only the chosen fields. +// Search searchs all documents that match the query. If selectors are present, the return will be only the chosen fields. func (db *DB) Search(query bson.M, selectors []string, collection string, obj interface{}) error { session := db.Session.Clone() defer session.Close() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) var err error if selectors != nil { @@ -137,11 +133,11 @@ func (db *DB) Search(query bson.M, selectors []string, collection string, obj in return err } -// SearchOne searches for the first element that matches with the given query. +// SearchOne searchs for the first element that matchs with the given query. func (db *DB) SearchOne(query bson.M, selectors []string, collection string, obj interface{}) error { session := db.Session.Clone() defer session.Close() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) var err error if selectors != nil { @@ -159,10 +155,10 @@ func (db *DB) SearchOne(query bson.M, selectors []string, collection string, obj return err } -// Upsert inserts a document or updates it if it already exists. +// Upsert inserts a document or update it if it already exists. func (db *DB) Upsert(query bson.M, obj interface{}, collection string) (*mgo.ChangeInfo, error) { session := db.Session.Clone() - c := session.DB(config.DatabaseName).C(collection) + c := session.DB("").C(collection) defer session.Close() return c.Upsert(query, obj) } diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/handlers.go b/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/handlers.go index 659220244..abaa5b037 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/handlers.go +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/handlers.go @@ -4,65 +4,69 @@ import ( "fmt" "net/http" + + "github.com/globocom/secDevLabs/owasp-top10-2021-apps/a1/ecommerce-api/app/db" "github.com/labstack/echo" + jwt "github.com/dgrijalva/jwt-go" ) -// HealthCheck is the health check function. +// HealthCheck is the heath check function. func HealthCheck(c echo.Context) error { return c.String(http.StatusOK, "WORKING\n") } // GetTicket returns the userID ticket. func GetTicket(c echo.Context) error { - // Obter o userID do contexto - userIDFromContext, ok := c.Get("userID").(string) - if !ok { - return c.JSON(http.StatusUnauthorized, map[string]string{ - "result": "error", - "details": "Invalid user authentication data.", - }) - } - // Obter o userID da URL + authHeader := c.Request().Header.Get("Authorization") id := c.Param("id") - if id == "" { - return c.JSON(http.StatusBadRequest, map[string]string{ - "result": "error", - "details": "User ID is required.", - }) - } - // Verificar se o userID autenticado corresponde ao userID fornecido - if userIDFromContext != id { - return c.JSON(http.StatusForbidden, map[string]string{ - "result": "error", - "details": "Access denied. You are not authorized to view this ticket.", + if authHeader == "" { + return c.JSON(http.StatusUnauthorized, map[string]string{ + "error": "Authorization header is missing", }) } - // Consultar o banco de dados com base no userID userDataQuery := map[string]interface{}{"userID": id} userDataResult, err := db.GetUserData(userDataQuery) if err != nil { - c.Logger().Errorf("Error querying user data: %v", err) - return c.JSON(http.StatusInternalServerError, map[string]string{ - "result": "error", - "details": "An internal error occurred. Please try again later.", - }) + // could not find this user in MongoDB (or MongoDB err connection) + return c.JSON(http.StatusBadRequest, map[string]string{"result": "error", "details": "Error finding this UserID."}) } - // Verificar o formato da resposta + format := c.QueryParam("format") if format == "json" { - return c.JSON(http.StatusOK, map[string]interface{}{ + return c.JSON(http.StatusOK, map[string]string{ "result": "success", "username": userDataResult.Username, + "userId" : userDataResult.UserID, "ticket": userDataResult.Ticket, }) } - // Resposta em texto simples msgTicket := fmt.Sprintf("Hey, %s! This is your ticket: %s\n", userDataResult.Username, userDataResult.Ticket) return c.String(http.StatusOK, msgTicket) +} + + +func parseToken(tokenString string) (*Claims, error) { + claims := &Claims{} + // Exemplo de parsing do JWT + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + return []byte("secret"), nil // sua chave secreta + }) + + if err != nil || !token.Valid { + return nil, fmt.Errorf("invalid token") + } + + return claims, nil +} + +// Claims define os dados que estarão no JWT +type Claims struct { + UserID string `json:"userId"` + jwt.StandardClaims } \ No newline at end of file diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/session.go b/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/session.go index 952aaad93..977666cd6 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/session.go +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers/session.go @@ -36,6 +36,7 @@ func ReadCookie(c echo.Context) error { // Login checks MongoDB if this user exists and then returns a JWT session cookie. func Login(c echo.Context) error { + loginAttempt := types.LoginAttempt{} err := c.Bind(&loginAttempt) if err != nil { @@ -43,6 +44,7 @@ func Login(c echo.Context) error { } // input validation missing! + userDataQuery := map[string]interface{}{"username": loginAttempt.Username} userDataResult, err := db.GetUserData(userDataQuery) if err != nil { @@ -67,6 +69,8 @@ func Login(c echo.Context) error { // Set claims claims := token.Claims.(jwt.MapClaims) claims["name"] = userDataResult.Username + claims["userId"]= userDataResult.UserID + claims["Ticket"]= userDataResult.Ticket claims["exp"] = time.Now().Add(time.Hour * 72).Unix() // Generate encoded token and send it as response. @@ -86,6 +90,8 @@ func Login(c echo.Context) error { "result": "success", "username": userDataResult.Username, "user_id": userDataResult.UserID, + "Ticket": userDataResult.Ticket, + "token": t, }) } diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/server.go b/owasp-top10-2021-apps/a1/ecommerce-api/app/server.go index 93a6d5bd4..b274f67c9 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/server.go +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/server.go @@ -5,122 +5,34 @@ import ( "fmt" "html/template" "io" - "net/http" "os" - "strings" apiContext "github.com/globocom/secDevLabs/owasp-top10-2021-apps/a1/ecommerce-api/app/context" - "github.com/globocom/secDevLabs/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers" "github.com/globocom/secDevLabs/owasp-top10-2021-apps/a1/ecommerce-api/app/db" + "github.com/globocom/secDevLabs/owasp-top10-2021-apps/a1/ecommerce-api/app/handlers" "github.com/labstack/echo" "github.com/labstack/echo/middleware" - "github.com/dgrijalva/jwt-go" ) // TemplateRegistry defines the template registry struct +// Ref: https://medium.freecodecamp.org/how-to-setup-a-nested-html-template-in-the-go-echo-web-framework-670f16244bb4 type TemplateRegistry struct { templates map[string]*template.Template } -// Render implements e.Renderer interface +// Render implement e.Renderer interface +// Ref: https://medium.freecodecamp.org/how-to-setup-a-nested-html-template-in-the-go-echo-web-framework-670f16244bb4 func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error { tmpl, ok := t.templates[name] if !ok { - return fmt.Errorf("template not found: %s", name) + err := errors.New("Template not found -> " + name) + return err } return tmpl.ExecuteTemplate(w, "base.html", data) } -// Middleware: Checks if a user is authorized for a specific ticket -func isAuthorized(dbInstance *db.DB) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - // Recuperar o token JWT do contexto - user, ok := c.Get("user").(*jwt.Token) - if !ok { - return echo.ErrUnauthorized // Retorna 401 se o token estiver ausente ou inválido - } - - // Extrair as claims do token - claims, ok := user.Claims.(jwt.MapClaims) - if !ok { - return echo.ErrUnauthorized // Retorna 401 se as claims não puderem ser extraídas - } - - // Garantir que a claim "id" exista - userID, ok := claims["id"].(string) - if !ok { - return echo.ErrUnauthorized // Retorna 401 se o campo "id" não existir ou estiver no formato errado - } - - // Recuperar o ID do ticket da rota - ticketID := c.Param("id") - if ticketID == "" { - return echo.ErrBadRequest // Retorna 400 se o ticket ID não for informado - } - - // Verificar permissão no banco de dados - if !userHasAccessToTicket(dbInstance, userID, ticketID) { - return echo.ErrUnauthorized // Retorna 401 se o usuário não tiver permissão - } - - // Prosseguir para o próximo handler - return next(c) - } - } -} - - -// Checks if a user has access to a specific ticket -func userHasAccessToTicket(dbInstance *db.DB, userID, ticketID string) bool { - hasPermission, err := dbInstance.CheckUserPermission(userID, ticketID) - if err != nil { - return false - } - return hasPermission -} - -// Middleware: Auth checks JWT token -func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - tokenHeader := c.Request().Header.Get("Authorization") - if !strings.HasPrefix(tokenHeader, "Bearer ") { - return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token format"}) - } - - token := strings.TrimPrefix(tokenHeader, "Bearer ") - userID, err := parseToken(token) - if err != nil { - return c.JSON(http.StatusUnauthorized, map[string]string{"error": err.Error()}) - } - - c.Set("userID", userID) - return next(c) - } -} - -func parseToken(token string) (string, error) { - parsedToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { - return []byte("secretKey"), nil - }) - if err != nil || !parsedToken.Valid { - return "", errors.New("invalid token") - } - - claims, ok := parsedToken.Claims.(jwt.MapClaims) - if !ok { - return "", errors.New("invalid token claims") - } - - userID, ok := claims["id"].(string) - if !ok { - return "", errors.New("invalid user ID in token") - } - return userID, nil -} - - func main() { + configAPI := apiContext.GetAPIConfig() if err := checkRequirements(configAPI); err != nil { @@ -128,20 +40,16 @@ func main() { os.Exit(1) } - // Inicializa conexão com o banco de dados - database, err := db.Connect() - if err != nil { - fmt.Printf("Erro ao conectar ao banco de dados: %v\n", err) - os.Exit(1) - } - defer database.Session.Close() - echoInstance := echo.New() echoInstance.HideBanner = true + // Instantiate a template registry with an array of template set + // Ref: https://medium.freecodecamp.org/how-to-setup-a-nested-html-template-in-the-go-echo-web-framework-670f16244bb4 templates := make(map[string]*template.Template) templates["form.html"] = template.Must(template.ParseFiles("views/form.html", "views/base.html")) - echoInstance.Renderer = &TemplateRegistry{templates: templates} + echoInstance.Renderer = &TemplateRegistry{ + templates: templates, + } echoInstance.Use(middleware.Logger()) echoInstance.Use(middleware.Recover()) @@ -149,31 +57,32 @@ func main() { echoInstance.GET("/", handlers.FormPage) echoInstance.GET("/healthcheck", handlers.HealthCheck) + echoInstance.GET("/ticket/:id", handlers.GetTicket) echoInstance.POST("/register", handlers.RegisterUser) echoInstance.POST("/login", handlers.Login) - - ticketGroup := echoInstance.Group("/ticket") - ticketGroup.Use(middleware.JWTWithConfig(middleware.JWTConfig{ - SigningKey: []byte("secretKey"), // Substitua por uma variável de ambiente - })) - ticketGroup.Use(isAuthorized(database)) - ticketGroup.GET("/:id", handlers.GetTicket) APIport := fmt.Sprintf(":%d", configAPI.APIPort) echoInstance.Logger.Fatal(echoInstance.Start(APIport)) + } func checkRequirements(configAPI *apiContext.APIConfig) error { + + // check if all environment variables are properly set. if err := checkEnvVars(); err != nil { return err } + + // check if MongoDB is accessible and credentials received are working. if err := checkMongoDB(); err != nil { return err } + return nil } func checkEnvVars() error { + envVars := []string{ "MONGO_HOST", "MONGO_DATABASE_NAME", @@ -181,24 +90,36 @@ func checkEnvVars() error { "MONGO_DATABASE_PASSWORD", } - var missingVars []string - for _, v := range envVars { - if _, exists := os.LookupEnv(v); !exists { - missingVars = append(missingVars, v) + var envIsSet bool + var allEnvIsSet bool + var errorString string + + env := make(map[string]string) + allEnvIsSet = true + for i := 0; i < len(envVars); i++ { + env[envVars[i]], envIsSet = os.LookupEnv(envVars[i]) + if !envIsSet { + errorString = errorString + envVars[i] + " " + allEnvIsSet = false } } - if len(missingVars) > 0 { - return fmt.Errorf("missing environment variables: %v", missingVars) + if allEnvIsSet == false { + finalError := fmt.Sprintf("check environment variables: %s", errorString) + return errors.New(finalError) } return nil } func checkMongoDB() error { + _, err := db.Connect() + if err != nil { - return fmt.Errorf("check MongoDB: %v", err) + mongoError := fmt.Sprintf("check mongoDB: %s", err) + return errors.New(mongoError) } + return nil } diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/views/base.html b/owasp-top10-2021-apps/a1/ecommerce-api/app/views/base.html index 330272a35..7b137133c 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/views/base.html +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/views/base.html @@ -37,4 +37,3 @@ {{end}} - diff --git a/owasp-top10-2021-apps/a1/ecommerce-api/app/views/form.html b/owasp-top10-2021-apps/a1/ecommerce-api/app/views/form.html index c87fd4069..90b460b68 100644 --- a/owasp-top10-2021-apps/a1/ecommerce-api/app/views/form.html +++ b/owasp-top10-2021-apps/a1/ecommerce-api/app/views/form.html @@ -87,8 +87,11 @@

{{index . "name"}}

XHR.onreadystatechange = function (event) { if (XHR.readyState == 4) { if (XHR.status == 200) { - document.getElementById('page-title').innerHTML = "Your orders" var jsonResponse = JSON.parse(XHR.responseText); + localStorage.setItem('auth_token', jsonResponse.token); + document.getElementById('page-title').innerHTML = "Your orders" + + console.log(jsonResponse) // get ticket's user var XHRticket = new XMLHttpRequest(); @@ -96,6 +99,7 @@

{{index . "name"}}

if (XHRticket.readyState == 4) { if (XHRticket.status == 200) { var jsonTicket = JSON.parse(XHRticket.responseText); + console.log(jsonTicket) document.getElementById('container-form').innerHTML = `
@@ -124,6 +128,8 @@

{{index . "name"}}

}; XHRticket.open('GET', '//localhost:10005/ticket/'+jsonResponse["user_id"]+'?format=json'); XHRticket.setRequestHeader('Content-Type','application/json' ); + XHRticket.setRequestHeader('Authorization', 'Bearer ' + jsonResponse.token); + XHRticket.send(); } else { // Define what happens in case of error