diff --git a/accessHandler.go b/accessHandler.go index 437b4dc..ccee3cd 100644 --- a/accessHandler.go +++ b/accessHandler.go @@ -1,127 +1,10 @@ package AccessHandler import ( - "gitea.tecamino.com/paadi/dbHandler" + "gitea.tecamino.com/paadi/access-handler/handlers" "gitea.tecamino.com/paadi/tecamino-logger/logging" ) -// AccessHandler -// -// Description: -// -// AccessHandler manages access-related functionality, including -// database operations for users and roles, as well as logging. -// It encapsulates a database handler and a logger so that -// authentication and authorization operations can be performed -// consistently across the application. -type AccessHandler struct { - dbHandler *dbHandler.DBHandler // Database handler used for managing users and roles - logger *logging.Logger // Centralized application logger -} - -// NewAccessHandler -// -// Description: -// -// Creates and initializes a new AccessHandler instance. -// -// Behavior: -// 1. If a logger is not provided (nil), it creates a new logger instance -// that writes to "accessHandler.log". -// 2. Initializes the AccessHandler struct. -// 3. Sets up the internal DBAHandler with the same logger. -// 4. Automatically creates required database tables and default data: -// - User table -// - Default user(s) -// - Role table -// - Default role(s) -// -// Parameters: -// - dbPath: The file path to the database. -// - logger: Optional pointer to a logging.Logger instance. If nil, a new one is created. -// -// Returns: -// - aH: A pointer to the fully initialized AccessHandler. -// - err: Any error that occurs during initialization. -// -// Example: -// -// handler, err := NewAccessHandler("data/app.db", appLogger) -// if err != nil { -// log.Fatal(err) -// } -func NewAccessHandler(path string, logger *logging.Logger) (aH *AccessHandler, err error) { - if logger == nil { - logger, err = logging.NewLogger("accessHandler.log", nil) - if err != nil { - return - } - } - - logger.Debug("NewAccessHandler", "initialize new access handler") - - // Initialize AccessHandler with logger - aH = &AccessHandler{ - logger: logger, - } - - logger.Debug("NewAccessHandler", "initialize db handler") - // Create a new DB handler instance - aH.dbHandler, err = dbHandler.NewDBHandler(path, "user", logger) - if err != nil { - aH.logger.Error("NewAccessHandler", err) - return - } - - logger.Debug("NewAccessHandler", "add user table") - // Add the user table to the database - err = aH.AddUserTable() - if err != nil { - aH.logger.Error("NewAccessHandler", err) - return - } - - logger.Debug("NewAccessHandler", "add default user") - // Add default users to the system - err = aH.AddDefaultUser() - if err != nil { - aH.logger.Error("NewAccessHandler", err) - return - } - - logger.Debug("NewAccessHandler", "add role table") - // Add the role table to the database - err = aH.AddRoleTable() - if err != nil { - aH.logger.Error("NewAccessHandler", err) - return - } - - logger.Debug("NewAccessHandler", "add default role") - // Add default roles to the system - err = aH.AddDefaultRole() - if err != nil { - aH.logger.Error("NewAccessHandler", err) - } - - return -} - -// GetLogger -// -// Description: -// -// Returns the logger associated with this AccessHandler instance. -// Useful when another component or handler needs to reuse the -// same logging instance for consistent log output. -// -// Returns: -// - *logging.Logger: The logger assigned to this AccessHandler. -// -// Example: -// -// log := accessHandler.GetLogger() -// log.Info("Some event") -func (aH *AccessHandler) GetLogger() *logging.Logger { - return aH.logger +func NewAccessHandler(path string, logger *logging.Logger) (aH *handlers.AccessHandler, err error) { + return handlers.NewAccessHandler(path, logger) } diff --git a/accessHandlerAPI.go b/accessHandlerAPI.go deleted file mode 100644 index 52c6724..0000000 --- a/accessHandlerAPI.go +++ /dev/null @@ -1,158 +0,0 @@ -package AccessHandler - -import ( - "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/dbHandler" - "gitea.tecamino.com/paadi/tecamino-logger/logging" -) - -// AccessHandler -// -// Description: -// -// AccessHandler manages access-related functionality, including -// database operations for users and roles, as well as logging. -// It encapsulates a database handler and a logger so that -// authentication and authorization operations can be performed -// consistently across the application. -type AccessHandlerAPI struct { - dbHandler *dbHandler.DBHandler // Database handler used for managing users and roles - logger *logging.Logger // Centralized application logger -} - -// NewAccessHandlerAPI -// -// Description: -// -// Creates and initializes a new AccessHandler instance. -// -// Behavior: -// 1. If a logger is not provided (nil), it creates a new logger instance -// that writes to "accessHandler.log". -// 2. Initializes the AccessHandler struct. -// 3. Sets up the internal DBAHandler with the same logger. -// 4. Automatically creates required database tables and default data: -// - User table -// - Default user(s) -// - Role table -// - Default role(s) -// -// Parameters: -// - dbPath: The file path to the database. -// - logger: Optional pointer to a logging.Logger instance. If nil, a new one is created. -// -// Returns: -// - aH: A pointer to the fully initialized AccessHandler. -// - err: Any error that occurs during initialization. -// -// Example: -// -// handler, err := NewAccessHandlerAPI("data/app.db", appLogger) -// if err != nil { -// log.Fatal(err) -// } -func NewAccessHandlerAPI(path string, logger *logging.Logger) (aH *AccessHandlerAPI, err error) { - if logger == nil { - logger, err = logging.NewLogger("accessHandler.log", nil) - if err != nil { - return - } - } - - logger.Debug("NewAccessHandlerAPI", "initialize new access handler") - - // Initialize AccessHandler with logger - aH = &AccessHandlerAPI{ - logger: logger, - } - - logger.Debug("NewAccessHandlerAPI", "initialize db handler") - // Create a new DB handler instance - aH.dbHandler, err = dbHandler.NewDBHandler(path, "user", logger) - if err != nil { - aH.logger.Error("NewAccessHandlerAPI", err) - return - } - - logger.Debug("NewAccessHandlerAPI", "add user table") - // Add the user table to the database - err = aH.dbHandler.AddNewTable(models.User{}) - if err != nil { - aH.logger.Error("NewAccessHandlerAPI", err) - return - } - - logger.Debug("NewAccessHandlerAPI", "add default user") - // Add default users to the system - name := "admin" - role := "admin" - email := "zuercher@tecamino.ch" - - // Check if a user with this email already exists - if err := aH.dbHandler.Exists(&models.User{}, "email", email, false); err == nil { - aH.logger.Debug("AddDefaultUser", "user email "+email+" exists already") - // Found a user β†’ skip create - } else { - // Create default settings for the new user - settings := models.Settings{} - aH.logger.Debug("AddDefaultUser", "set default quasar settings") - settings.DefaultQuasarSettings() - - // Insert default admin user into the database - aH.dbHandler.AddNewColum(&models.User{ - Name: name, - Role: role, - Email: email, - Password: "$2a$10$sZZOWBP8DSFLrLFQNoXw8OsEEr0tez1B8lPzKCHofaHg6PMNxx1pG", - Settings: settings, - }) - } - - logger.Debug("NewAccessHandlerAPI", "add role table") - // Add the role table to the database - err = aH.dbHandler.AddNewTable(models.Role{}) - if err != nil { - aH.logger.Error("NewAccessHandlerAPI", err) - return - } - - // Add default roles to the system - logger.Debug("NewAccessHandlerAPI", "add default role") - - // Check if a role with this name already exists - if err := aH.dbHandler.Exists(&models.Role{}, "role", role, false); err == nil { - // Found a role β†’ skip creation - aH.logger.Debug("AddDefaultRole", "role "+role+" exists already") - } else { - // Initialize default permissions for admin - permissions := models.Permissions{} - aH.logger.Debug("AddDefaultRole", "set default Permissions") - permissions.DefaultPermissions() - - // Create the default admin role - aH.dbHandler.AddNewColum(&models.Role{ - Role: role, - Permissions: permissions, - }) - } - return -} - -// GetLogger -// -// Description: -// -// Returns the logger associated with this AccessHandler instance. -// Useful when another component or handler needs to reuse the -// same logging instance for consistent log output. -// -// Returns: -// - *logging.Logger: The logger assigned to this AccessHandler. -// -// Example: -// -// log := accessHandler.GetLogger() -// log.Info("Some event") -func (aH *AccessHandlerAPI) GetLogger() *logging.Logger { - return aH.logger -} diff --git a/api.go b/api.go deleted file mode 100644 index bc4d7f3..0000000 --- a/api.go +++ /dev/null @@ -1,296 +0,0 @@ -package AccessHandler - -import ( - "errors" - "fmt" - "net/http" - "strconv" - - "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/access-handler/utils" - "github.com/gin-gonic/gin" -) - -func (aH *AccessHandlerAPI) AddUser(c *gin.Context) { - var user models.User - err := c.BindJSON(&user) - if err != nil { - aH.logger.Error("AddUser", err) - c.JSON(http.StatusInternalServerError, models.NewJsonErrorResponse(err)) - return - } - - if !user.IsValid() { - aH.logger.Error("AddUser", "user empty") - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("user empty")) - return - } - - // Check if a user with this email already exists - if err := aH.dbHandler.Exists(&models.User{}, "email", user.Email, false); err == nil { - // Found a user β†’ skip create - aH.logger.Error("AddUser", "user with email "+user.Email+" already exists") - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse(fmt.Sprintf("user with email %s already exists", user.Email))) - return - } - - if !utils.IsValidEmail(user.Email) { - aH.logger.Error("AddUser", "not valid email address") - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(errors.New("not valid email address"))) - return - } - - // Hash the provided password before saving - hash, err := utils.HashPassword(user.Password) - if err != nil { - aH.logger.Error("AddUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - - aH.logger.Debug("AddUser", "add new user "+user.Name+" with role "+user.Role) - - // Insert the new user record - aH.dbHandler.AddNewColum(&models.User{ - Name: user.Name, - Role: user.Role, - Email: user.Email, - Password: hash, - }) - - c.JSON(http.StatusOK, gin.H{ - "message": fmt.Sprintf("user '%s' successfully added", user.Name), - }) -} - -func (aH *AccessHandlerAPI) GetUser(c *gin.Context) { - var i int - var err error - - id := c.Query("id") - if id != "" { - i, err = strconv.Atoi(id) - if err != nil { - aH.logger.Error("GetUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - } - - var users []models.User - err = aH.dbHandler.GetById(&users, uint(i)) - if err != nil { - aH.logger.Error("GetUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - c.JSON(http.StatusOK, users) -} - -func (aH *AccessHandlerAPI) UpdateUser(c *gin.Context) { - var user models.User - if err := c.BindJSON(&user); err != nil { - aH.logger.Error("UpdateUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - err := aH.dbHandler.UpdateValuesById(&user, user.Id) - if err != nil { - aH.logger.Error("UpdateUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - c.JSON(http.StatusOK, models.NewJsonMessageResponse("successfully updated user '"+user.Email+"'")) - -} - -func (aH *AccessHandlerAPI) DeleteUser(c *gin.Context) { - queryId := c.Query("id") - - if queryId == "" || queryId == "null" || queryId == "undefined" { - aH.logger.Error("DeleteUser", "id query missing or wrong value: "+queryId) - c.JSON(http.StatusBadRequest, gin.H{ - "message": "id query missing or wrong value: " + queryId, - }) - return - } - - var request struct { - Ids []int `json:"ids"` - } - - err := c.BindJSON(&request) - if err != nil { - aH.logger.Error("DeleteUser", "id query missing or wrong value: "+queryId) - c.JSON(http.StatusInternalServerError, nil) - return - } - - if len(request.Ids) == 0 { - aH.logger.Error("DeleteUser", "no ids given to be deleted") - c.JSON(http.StatusBadRequest, gin.H{ - "message": "no ids given to be deleted", - }) - return - } - - var ownId string - removeIds := make([]uint, len(request.Ids)) - for i, id := range request.Ids { - if queryId == fmt.Sprint(id) { - ownId = queryId - continue - } - removeIds[i] = uint(id) - } - - if ownId != "" { - aH.logger.Error("DeleteUser", "can not delete logged in member id: "+queryId) - c.JSON(http.StatusBadRequest, gin.H{ - "message": "can not delete logged in member id: " + queryId, - "id": queryId, - }) - return - } - - err = aH.dbHandler.DeleteById(&models.User{}, removeIds...) - if err != nil { - aH.logger.Error("DeleteUser", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "member(s) deleted", - }) -} - -func (aH *AccessHandlerAPI) AddRole(c *gin.Context) { - var role models.Role - err := c.BindJSON(&role) - if err != nil { - aH.logger.Error("AddRole", err) - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - if !role.IsValid() { - aH.logger.Error("AddRole", "user empty") - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("user empty")) - return - } - - // Check if a role with this name already exists - if err := aH.dbHandler.Exists(&models.Role{}, "role", role.Role, false); err == nil { - aH.logger.Error("AddRole", fmt.Sprintf("role with name %s already exists", role.Role)) - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse(fmt.Sprintf("role with name %s already exists", role.Role))) - } - - // Insert new role with provided permissions - aH.dbHandler.AddNewColum(&models.Role{ - Role: role.Role, - Permissions: role.Permissions, - }) - c.JSON(http.StatusOK, gin.H{ - "message": fmt.Sprintf("role '%s' successfully added", role.Role), - }) -} - -func (aH *AccessHandlerAPI) GetRole(c *gin.Context) { - var i int - var err error - var roles []models.Role - - role := c.Query("role") - id := c.Query("id") - - if role != "" { - err = aH.dbHandler.GetByKey(&roles, "role", role, false) - } else if id != "" { - i, err = strconv.Atoi(id) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) - return - } - err = aH.dbHandler.GetById(&roles, uint(i)) - } - - if err != nil { - aH.logger.Error("GetRole", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - c.JSON(http.StatusOK, roles) -} - -func (aH *AccessHandlerAPI) UpdateRole(c *gin.Context) { - var role models.Role - if err := c.BindJSON(&role); err != nil { - aH.logger.Error("UpdateRole", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - err := aH.dbHandler.UpdateValuesById(&role, role.Id) - if err != nil { - aH.logger.Error("UpdateRole", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - c.JSON(http.StatusOK, models.NewJsonMessageResponse("successfully updated role '"+role.Role+"'")) -} - -func (aH *AccessHandlerAPI) DeleteRole(c *gin.Context) { - queryRole := c.Query("role") - if queryRole == "" || queryRole == "null" || queryRole == "undefined" { - aH.logger.Error("DeleteRole", "id query missing or wrong value: "+queryRole) - c.JSON(http.StatusInternalServerError, nil) - return - } - - var request struct { - Roles []string `json:"roles"` - } - - err := c.BindJSON(&request) - if err != nil { - aH.logger.Error("DeleteRole", err) - c.JSON(http.StatusBadRequest, nil) - return - } - - if len(request.Roles) == 0 { - aH.logger.Error("DeleteRole", "no ids given to be deleted") - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("no roles given to be deleted")) - return - } - - var ownRole string - - for _, role := range request.Roles { - if queryRole == role { - ownRole = role - continue - } - err = aH.dbHandler.DeleteByKey(&models.Role{}, "role", role, false) - if err != nil { - aH.logger.Error("DeleteRole", err) - c.JSON(http.StatusInternalServerError, nil) - return - } - } - - if ownRole != "" { - aH.logger.Error("DeleteRole", "can not delete logged in role id: "+ownRole) - c.JSON(http.StatusBadRequest, gin.H{ - "message": "can not delete logged in role id: " + ownRole, - "role": ownRole, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "role(s) deleted", - }) -} diff --git a/db_test.go b/db_test.go index 2a1f68b..d6f86fd 100644 --- a/db_test.go +++ b/db_test.go @@ -106,7 +106,7 @@ func TestLoginHandler(t *testing.T) { r := gin.Default() - SetMiddlewareLogger(r, aH.GetLogger()) + aH.SetMiddlewareLogger(r) r.POST("/login", aH.Login) r.POST("/login/refresh", aH.Refresh) diff --git a/handlers/access.go b/handlers/access.go new file mode 100644 index 0000000..da64dc7 --- /dev/null +++ b/handlers/access.go @@ -0,0 +1,127 @@ +package handlers + +import ( + "gitea.tecamino.com/paadi/dbHandler" + "gitea.tecamino.com/paadi/tecamino-logger/logging" +) + +// AccessHandler +// +// Description: +// +// AccessHandler manages access-related functionality, including +// database operations for users and roles, as well as logging. +// It encapsulates a database handler and a logger so that +// authentication and authorization operations can be performed +// consistently across the application. +type AccessHandler struct { + dbHandler *dbHandler.DBHandler // Database handler used for managing users and roles + logger *logging.Logger // Centralized application logger +} + +// NewAccessHandler +// +// Description: +// +// Creates and initializes a new AccessHandler instance. +// +// Behavior: +// 1. If a logger is not provided (nil), it creates a new logger instance +// that writes to "accessHandler.log". +// 2. Initializes the AccessHandler struct. +// 3. Sets up the internal DBAHandler with the same logger. +// 4. Automatically creates required database tables and default data: +// - User table +// - Default user(s) +// - Role table +// - Default role(s) +// +// Parameters: +// - dbPath: The file path to the database. +// - logger: Optional pointer to a logging.Logger instance. If nil, a new one is created. +// +// Returns: +// - aH: A pointer to the fully initialized AccessHandler. +// - err: Any error that occurs during initialization. +// +// Example: +// +// handler, err := NewAccessHandler("data/app.db", appLogger) +// if err != nil { +// log.Fatal(err) +// } +func NewAccessHandler(path string, logger *logging.Logger) (aH *AccessHandler, err error) { + if logger == nil { + logger, err = logging.NewLogger("accessHandler.log", nil) + if err != nil { + return + } + } + + logger.Debug("NewAccessHandler", "initialize new access handler") + + // Initialize AccessHandler with logger + aH = &AccessHandler{ + logger: logger, + } + + logger.Debug("NewAccessHandler", "initialize db handler") + // Create a new DB handler instance + aH.dbHandler, err = dbHandler.NewDBHandler("user", path, logger) + if err != nil { + aH.logger.Error("NewAccessHandler", err) + return + } + + logger.Debug("NewAccessHandler", "add user table") + // Add the user table to the database + err = aH.AddUserTable() + if err != nil { + aH.logger.Error("NewAccessHandler", err) + return + } + + logger.Debug("NewAccessHandler", "add default user") + // Add default users to the system + err = aH.AddDefaultUser() + if err != nil { + aH.logger.Error("NewAccessHandler", err) + return + } + + logger.Debug("NewAccessHandler", "add role table") + // Add the role table to the database + err = aH.AddRoleTable() + if err != nil { + aH.logger.Error("NewAccessHandler", err) + return + } + + logger.Debug("NewAccessHandler", "add default role") + // Add default roles to the system + err = aH.AddDefaultRole() + if err != nil { + aH.logger.Error("NewAccessHandler", err) + } + + return +} + +// GetLogger +// +// Description: +// +// Returns the logger associated with this AccessHandler instance. +// Useful when another component or handler needs to reuse the +// same logging instance for consistent log output. +// +// Returns: +// - *logging.Logger: The logger assigned to this AccessHandler. +// +// Example: +// +// log := accessHandler.GetLogger() +// log.Info("Some event") +func (aH *AccessHandler) GetLogger() *logging.Logger { + return aH.logger +} diff --git a/login.go b/handlers/login.go similarity index 94% rename from login.go rename to handlers/login.go index 473dbe6..c38f9cc 100644 --- a/login.go +++ b/handlers/login.go @@ -1,4 +1,4 @@ -package AccessHandler +package handlers import ( "fmt" @@ -6,8 +6,8 @@ import ( "net/http" "time" + "gitea.tecamino.com/paadi/access-handler/internal/utils" "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/access-handler/utils" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -53,7 +53,8 @@ func (aH *AccessHandler) Login(c *gin.Context) { } // Fetch user record from DB - dbRecord, err := aH.GetUserByKey("user_name", user.Name, false) + var dbRecord []models.User + err := aH.dbHandler.GetByKey(&dbRecord, "user_name", user.Name, false) if err != nil { aH.logger.Error("Login", err) c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) diff --git a/middleware.go b/handlers/middleware.go similarity index 92% rename from middleware.go rename to handlers/middleware.go index ab1ef23..0e52159 100644 --- a/middleware.go +++ b/handlers/middleware.go @@ -1,4 +1,4 @@ -package AccessHandler +package handlers import ( "fmt" @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "gitea.tecamino.com/paadi/access-handler/models" "gitea.tecamino.com/paadi/tecamino-logger/logging" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -28,10 +29,10 @@ import ( // Usage: // // SetMiddlewareLogger(router, logger) -func SetMiddlewareLogger(r *gin.Engine, logger *logging.Logger) { +func (aH *AccessHandler) SetMiddlewareLogger(r *gin.Engine) { // Add middleware that injects logger into context r.Use(func(c *gin.Context) { - c.Set("logger", logger) + c.Set("logger", aH.logger) c.Next() }) } @@ -130,7 +131,8 @@ func (aH *AccessHandler) AuthorizeRole(suffix string) gin.HandlerFunc { } // Fetch roles and associated permissions from the database or store - roles, err := aH.GetRoleByKey("role", role, false) + var roles []models.Role + err := aH.dbHandler.GetByKey(&roles, "role", role, false) if err != nil { aH.logger.Error("AuthorizeRole", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": http.StatusInternalServerError}) diff --git a/role.go b/handlers/role.go similarity index 96% rename from role.go rename to handlers/role.go index 30ea9da..179cf50 100644 --- a/role.go +++ b/handlers/role.go @@ -1,4 +1,4 @@ -package AccessHandler +package handlers import ( "fmt" diff --git a/user.go b/handlers/user.go similarity index 95% rename from user.go rename to handlers/user.go index a21db73..cffc487 100644 --- a/user.go +++ b/handlers/user.go @@ -1,10 +1,10 @@ -package AccessHandler +package handlers import ( "fmt" + "gitea.tecamino.com/paadi/access-handler/internal/utils" "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/access-handler/utils" ) // AddUserTable diff --git a/utils/hash.go b/internal/utils/hash.go similarity index 100% rename from utils/hash.go rename to internal/utils/hash.go diff --git a/utils/utils.go b/internal/utils/utils.go similarity index 100% rename from utils/utils.go rename to internal/utils/utils.go diff --git a/loginApi.go b/loginApi.go deleted file mode 100644 index b0ff730..0000000 --- a/loginApi.go +++ /dev/null @@ -1,227 +0,0 @@ -package AccessHandler - -import ( - "fmt" - "log" - "net/http" - "time" - - "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/access-handler/utils" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" -) - -// ----------------------------- -// 🧠 HANDLERS -// ----------------------------- - -// Login authenticates a user and returns JWT tokens (access + refresh). -func (aH *AccessHandlerAPI) Login(c *gin.Context) { - var user models.User - - aH.logger.Debug("Login", "bind JSON request") - if err := c.BindJSON(&user); err != nil { - aH.logger.Error("Login", err) - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - // Validate input - if !user.IsValid() { - aH.logger.Error("Login", "user empty") - c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("user empty")) - return - } - - // Fetch user record from DB - var dbRecord []models.User - err := aH.dbHandler.GetByKey(&dbRecord, "user_name", user.Name, false) - if err != nil { - aH.logger.Error("Login", err) - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - if len(dbRecord) > 1 { - log.Println("multiple users found") - aH.logger.Error("Login", "more than one record found") - c.JSON(http.StatusInternalServerError, models.NewJsonMessageResponse("internal error")) - return - } - - // Check password - if !utils.CheckPassword(user.Password, dbRecord[0].Password) { - aH.logger.Error("Login", "invalid password") - c.JSON(http.StatusUnauthorized, models.NewJsonMessageResponse("invalid credentials")) - return - } - user = dbRecord[0] - - // ----------------------------- - // πŸ”‘ TOKEN CREATION - // ----------------------------- - aH.logger.Debug("Login", "create tokens") - - accessTokenExp := time.Now().Add(ACCESS_TOKEN_TIME) - refreshTokenExp := time.Now().Add(REFRESH_TOKEN_TIME) - - // Create access token - accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "id": user.Id, - "username": user.Name, - "role": user.Role, - "type": "access", - "exp": accessTokenExp.Unix(), - }) - - // Create refresh token - refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "id": user.Id, - "username": user.Name, - "role": user.Role, - "type": "refresh", - "exp": refreshTokenExp.Unix(), - }) - - // Sign tokens - accessString, err := accessToken.SignedString(ACCESS_SECRET) - if err != nil { - aH.logger.Error("Login", "failed to sign access token") - c.JSON(http.StatusInternalServerError, models.NewJsonMessageResponse("could not create access token")) - return - } - - refreshString, err := refreshToken.SignedString(REFRESH_SECRET) - if err != nil { - aH.logger.Error("Login", "failed to sign refresh token") - c.JSON(http.StatusInternalServerError, models.NewJsonMessageResponse("could not create refresh token")) - return - } - - // ----------------------------- - // πŸͺ SET COOKIES - // ----------------------------- - aH.logger.Debug("Login", "set cookies") - - secure := gin.Mode() == gin.ReleaseMode - - c.SetCookie("access_token", accessString, int(time.Until(accessTokenExp).Seconds()), - "/", "", secure, true) - c.SetCookie("refresh_token", refreshString, int(time.Until(refreshTokenExp).Seconds()), - "/", "", secure, true) - - aH.logger.Info("Login", "user "+user.Name+" logged in successfully") - - c.JSON(http.StatusOK, gin.H{ - "message": "login successful", - "id": user.Id, - "user": user.Name, - "role": user.Role, - "settings": user.Settings, - }) -} - -// Refresh generates a new access token from a valid refresh token. -func (aH *AccessHandlerAPI) Refresh(c *gin.Context) { - aH.logger.Debug("Refresh", "get refresh cookie") - - refreshCookie, err := c.Cookie("refresh_token") - if err != nil { - aH.logger.Error("Refresh", "no refresh token") - c.JSON(http.StatusUnauthorized, gin.H{"message": "no refresh token"}) - return - } - - // Validate token - aH.logger.Debug("Refresh", "parse token") - token, err := jwt.Parse(refreshCookie, func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return REFRESH_SECRET, nil - }) - if err != nil || !token.Valid { - aH.logger.Error("Refresh", err) - c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid refresh token"}) - return - } - - // Extract claims - claims, ok := token.Claims.(jwt.MapClaims) - if !ok || claims["type"] != "refresh" { - aH.logger.Error("Refresh", "invalid token type") - c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token type"}) - return - } - - username := claims["username"].(string) - id := int(claims["id"].(float64)) - role := claims["role"].(string) - - // Create new access token - aH.logger.Debug("Refresh", "create new access token") - accessExp := time.Now().Add(ACCESS_TOKEN_TIME) - newAccess := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "id": id, - "username": username, - "role": role, - "exp": accessExp.Unix(), - }) - accessString, _ := newAccess.SignedString(ACCESS_SECRET) - - // Set new access cookie - aH.logger.Debug("Refresh", "set new access cookie") - c.SetCookie("access_token", accessString, int(time.Until(accessExp).Seconds()), - "/", DOMAIN, gin.Mode() == gin.ReleaseMode, true) - - c.JSON(http.StatusOK, gin.H{"message": "token refreshed"}) -} - -// Me returns information about the currently authenticated user. -func (aH *AccessHandlerAPI) Me(c *gin.Context) { - aH.logger.Debug("Me", "get access cookie") - - cookie, err := c.Cookie("access_token") - if err != nil { - aH.logger.Error("Me", err) - c.JSON(http.StatusUnauthorized, gin.H{"message": "not logged in"}) - return - } - - // Parse and validate access token - aH.logger.Debug("Me", "parse token") - token, err := jwt.Parse(cookie, func(t *jwt.Token) (any, error) { - if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) - } - return ACCESS_SECRET, nil - }) - if err != nil || !token.Valid { - aH.logger.Error("Me", err) - c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token"}) - return - } - - claims := token.Claims.(jwt.MapClaims) - c.JSON(http.StatusOK, gin.H{ - "id": claims["id"], - "user": claims["username"], - "role": claims["role"], - }) -} - -// Logout clears authentication cookies and ends the session. -func (aH *AccessHandlerAPI) Logout(c *gin.Context) { - aH.logger.Info("Logout", "logout user") - - secure := gin.Mode() == gin.ReleaseMode - aH.logger.Debug("Logout", fmt.Sprintf("domain=%s secure=%t", DOMAIN, secure)) - - // Clear cookies - c.SetCookie("access_token", "", -1, "/", DOMAIN, secure, true) - c.SetCookie("refresh_token", "", -1, "/", DOMAIN, secure, true) - - c.JSON(http.StatusOK, gin.H{"message": "logged out"}) -} diff --git a/middlewareApi.go b/middlewareApi.go deleted file mode 100644 index 2343f88..0000000 --- a/middlewareApi.go +++ /dev/null @@ -1,167 +0,0 @@ -package AccessHandler - -import ( - "fmt" - "log" - "net/http" - "strings" - - "gitea.tecamino.com/paadi/access-handler/models" - "gitea.tecamino.com/paadi/tecamino-logger/logging" - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" -) - -// SetMiddlewareLogger -// -// Description: -// -// Registers a Gin middleware that attaches a custom logger instance -// to every incoming request via the Gin context. This allows other -// middleware and handlers to access the logger using: -// -// logger := c.MustGet("logger").(*logging.Logger) -// -// Parameters: -// - r: The Gin engine to which the middleware is applied. -// - logger: A pointer to the application's custom logger. -// -// Usage: -// -// SetMiddlewareLogger(router, logger) -func (aH *AccessHandlerAPI) SetMiddlewareLogger(r *gin.Engine, logger *logging.Logger) { - // Add middleware that injects logger into context - r.Use(func(c *gin.Context) { - c.Set("logger", logger) - c.Next() - }) -} - -// AuthMiddleware -// -// Description: -// -// A Gin middleware that performs authentication using a JWT token stored -// in a cookie named "access_token". It validates the token and extracts -// the user's role from the claims, storing it in the Gin context. -// -// Behavior: -// - Requires that SetMiddlewareLogger was used earlier to inject the logger. -// - If the JWT cookie is missing or invalid, it aborts the request with -// an appropriate HTTP error (401 or 500). -// -// Returns: -// -// A Gin handler function that can be used as middleware. -// -// Usage: -// -// r.Use(AuthMiddleware()) -func (aH *AccessHandlerAPI) AuthMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Retrieve logger from Gin context - middlewareLogger, ok := c.Get("logger") - if !ok { - log.Fatal("middleware logger not set β€” use SetMiddlewareLogger first") - c.AbortWithStatusJSON(http.StatusInternalServerError, http.StatusInternalServerError) - return - } - logger := middlewareLogger.(*logging.Logger) - - // Read access token from cookie - cookie, err := c.Cookie("access_token") - if err != nil { - logger.Error("AuthMiddleware", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "not logged in"}) - return - } - - // Parse and validate JWT token - token, err := jwt.Parse(cookie, func(t *jwt.Token) (any, error) { - return ACCESS_SECRET, nil - }) - if err != nil || !token.Valid { - logger.Error("AuthMiddleware", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "invalid token"}) - return - } - - // Extract custom claims (role) - if claims, ok := token.Claims.(jwt.MapClaims); ok { - role, _ := claims["role"].(string) - c.Set("role", role) - } - c.Next() - } -} - -// (AccessHandler).AuthorizeRole -// -// Description: -// -// A role-based authorization middleware. It checks whether the authenticated -// user (based on the "role" set in AuthMiddleware) has permission to access -// the given route. The check is performed by comparing the requested URL -// path against the user’s allowed permissions. -// -// Parameters: -// - suffix: A URL prefix to trim from the request path before matching -// permissions (e.g., "/api/v1"). -// -// Behavior: -// - Fetches the user role from Gin context. -// - Uses aH.GetRoleByKey() to retrieve role records and permissions. -// - Grants access (calls c.Next()) if a matching permission is found. -// - Denies access (401 Unauthorized) if no permission matches. -// -// Usage: -// -// router.GET("/secure/:id", aH.AuthorizeRole("/api/v1")) -func (aH *AccessHandlerAPI) AuthorizeRole(suffix string) gin.HandlerFunc { - return func(c *gin.Context) { - aH.logger.Debug("AuthorizeRole", "permission path of url path") - permissionPath := strings.TrimPrefix(c.Request.URL.Path, suffix+"/") - - aH.logger.Debug("AuthorizeRole", "get set role") - role, ok := c.Get("role") - if !ok { - aH.logger.Error("AuthorizeRole", "no role set") - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": http.StatusInternalServerError}) - return - } - - // Fetch roles and associated permissions from the database or store - var roles []models.Role - err := aH.dbHandler.GetByKey(&roles, "role", role, false) - if err != nil { - aH.logger.Error("AuthorizeRole", err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": http.StatusInternalServerError}) - return - } - - // Validate that a role was found - if len(roles) == 0 { - log.Println("not logged in") - aH.logger.Error("AuthorizeRole", "no logged in") - c.JSON(http.StatusUnauthorized, http.StatusUnauthorized) - return - } else if len(roles) > 1 { - aH.logger.Error("AuthorizeRole", "more than one record found") - c.JSON(http.StatusInternalServerError, http.StatusInternalServerError) - return - } - - // Check permissions - for _, permission := range roles[0].Permissions { - fmt.Println(100, permissionPath, permission.Name) - if permission.Name == permissionPath { - c.Next() - return - } - } - - // Access denied - aH.logger.Error("AuthorizeRole", "Forbidden") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Forbidden"}) - } -}