package user import ( "backend/dbRequest" "backend/models" "backend/utils" "database/sql" "encoding/json" "fmt" "io" "net/http" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" _ "modernc.org/sqlite" ) var JWT_SECRET = []byte("4h5Jza1Fn_zuzu&417%8nH*UH100+55-") var DOMAIN = "localhost" var ACCESS_TOKEN_TIME = 15 * time.Minute var REFRESH_TOKEN_TIME = 72 * time.Hour func (um *UserManager) AddUser(c *gin.Context) { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } user := models.User{} err = json.Unmarshal(body, &user) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } if !user.IsValid() { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) return } db, err := sql.Open(um.dbType, um.dbFile) if dbRequest.CheckDBError(c, user.Name, err) { return } defer db.Close() var exists bool if err := db.QueryRow(dbRequest.DBUserLookup, user.Name).Scan(&exists); dbRequest.CheckDBError(c, user.Name, err) { return } if exists { c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("user '%s' exists already", user.Name))) return } hash, err := utils.HashPassword(user.Password) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } if _, err := db.Exec(dbRequest.DBNewUser, user.Role, user.Name, hash, "{}"); dbRequest.CheckDBError(c, user.Name, err) { return } c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("user '%s' successfully added", user.Name), }) } func (um *UserManager) RemoveUser(c *gin.Context) { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } user := models.User{} err = json.Unmarshal(body, &user) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } if !user.IsValid() { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) return } db, err := sql.Open(um.dbType, um.dbFile) if dbRequest.CheckDBError(c, user.Name, err) { return } defer db.Close() var storedPassword string if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&storedPassword, &user.Role); dbRequest.CheckDBError(c, user.Name, err) { return } if !utils.CheckPassword(user.Password, storedPassword) { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password")) return } if _, err := db.Exec(dbRequest.DBRemoveUser, user.Name); dbRequest.CheckDBError(c, user.Name, err) { return } c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("user '%s' successfully removed", user.Name), }) } func (um *UserManager) Login(c *gin.Context) { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } user := models.User{} err = json.Unmarshal(body, &user) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } if !user.IsValid() { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) return } db, err := sql.Open(um.dbType, um.dbFile) if dbRequest.CheckDBError(c, user.Name, err) { return } defer db.Close() var storedPassword, settingsJsonString string if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&user.Role, &storedPassword, &settingsJsonString); dbRequest.CheckDBError(c, user.Name, err) { return } err = json.Unmarshal([]byte(settingsJsonString), &user.Settings) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse(err.Error())) return } if !utils.CheckPassword(user.Password, storedPassword) { c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password")) return } // ---- Create JWT tokens ---- accessTokenExp := time.Now().Add(ACCESS_TOKEN_TIME) refreshTokenExp := time.Now().Add(REFRESH_TOKEN_TIME) // Create token accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": user.Name, "role": user.Role, "type": "access", "exp": accessTokenExp.Unix(), }) refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": user.Name, "role": user.Role, "type": "refresh", "exp": refreshTokenExp.Unix(), }) accessString, err := accessToken.SignedString(JWT_SECRET) if err != nil { c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("could not create access token")) return } refreshString, err := refreshToken.SignedString(JWT_SECRET) if err != nil { c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("could not create refresh token")) return } // ---- Set secure cookies ---- secure := gin.Mode() == gin.ReleaseMode c.SetCookie("access_token", accessString, int(time.Until(accessTokenExp).Seconds()), "/", "", secure, true) // Path=/, Secure=true (only HTTPS), HttpOnly=true c.SetCookie("refresh_token", refreshString, int(time.Until(refreshTokenExp).Seconds()), "/", "", secure, true) fmt.Println(22, user.Settings) c.JSON(http.StatusOK, gin.H{ "message": "login successful", "user": user.Name, "role": user.Role, "settings": user.Settings, }) } func (um *UserManager) Refresh(c *gin.Context) { refreshCookie, err := c.Cookie("refresh_token") if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"message": "no refresh token"}) return } token, err := jwt.Parse(refreshCookie, func(token *jwt.Token) (any, error) { return JWT_SECRET, nil }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid refresh token"}) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok || claims["type"] != "refresh" { c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token type"}) return } username := claims["username"].(string) role := claims["role"].(string) // new access token accessExp := time.Now().Add(ACCESS_TOKEN_TIME) newAccess := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": username, "role": role, "exp": accessExp.Unix(), }) accessString, _ := newAccess.SignedString(JWT_SECRET) 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"}) } func (um *UserManager) Me(c *gin.Context) { // Read access token from cookie cookie, err := c.Cookie("access_token") if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"message": "not logged in"}) return } // Verify token token, err := jwt.Parse(cookie, func(t *jwt.Token) (any, error) { return JWT_SECRET, nil }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid token"}) return } claims := token.Claims.(jwt.MapClaims) c.JSON(http.StatusOK, gin.H{ "user": claims["username"], "role": claims["role"], }) } func (um *UserManager) Logout(c *gin.Context) { secure := gin.Mode() == gin.ReleaseMode 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"}) }