first commit
This commit is contained in:
41
backend/user/Middleware.go
Normal file
41
backend/user/Middleware.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Read access token from cookie
|
||||
cookie, err := c.Cookie("access_token")
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "not logged in"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(cookie, func(t *jwt.Token) (any, error) {
|
||||
return JWT_SECRET, nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "invalid token"})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func AuthorizeRole(roles ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userRole := c.GetString("role")
|
||||
for _, role := range roles {
|
||||
if userRole == role {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Forbidden"})
|
||||
}
|
||||
}
|
||||
}
|
274
backend/user/login.go
Normal file
274
backend/user/login.go
Normal file
@@ -0,0 +1,274 @@
|
||||
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"})
|
||||
}
|
49
backend/user/manager.go
Normal file
49
backend/user/manager.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"backend/dbRequest"
|
||||
"backend/utils"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type UserManager struct {
|
||||
dbType string
|
||||
dbFile string
|
||||
}
|
||||
|
||||
func NewUserManager(dir string) (*UserManager, error) {
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
var typ string = "sqlite"
|
||||
var file string = fmt.Sprintf("%s/user.db", dir)
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
db, err := sql.Open(typ, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec(dbRequest.DBCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash, err := utils.HashPassword("tecamino@2025")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = db.Exec(dbRequest.DBNewUser, "admin", "admin", hash, "{}")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &UserManager{
|
||||
dbType: typ,
|
||||
dbFile: file,
|
||||
}, nil
|
||||
}
|
49
backend/user/settings.go
Normal file
49
backend/user/settings.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"backend/dbRequest"
|
||||
"backend/models"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (um *UserManager) UpdateSettings(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
|
||||
}
|
||||
|
||||
db, err := sql.Open(um.dbType, um.dbFile)
|
||||
if dbRequest.CheckDBError(c, user.Name, err) {
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
jsonBytes, err := json.Marshal(user.Settings)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := db.Exec(dbRequest.DBUpdateSettings, string(jsonBytes), user.Name); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": fmt.Sprintf("user settings '%s' successfully updated", user.Name),
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user