aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.com>2025-10-12 00:05:30 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.com>2025-10-12 00:05:30 +0530
commita356803594ab36fa69e7dbcbd79261d8b46f4262 (patch)
tree0146efe4c52b1c65c1967ab1f412306c410c10d4
parent193be465b21838d2796fafbe1c5d9854038a3f8c (diff)
removed useless user fields and functions, added rolesHEADv0.19.0master
-rw-r--r--auth/auth.go5
-rw-r--r--auth/controller.go36
-rw-r--r--auth/middleware.go3
-rw-r--r--conf/conf.go11
-rw-r--r--main.go4
-rw-r--r--openbills.toml5
-rw-r--r--user/controller.go116
-rw-r--r--user/router.go2
-rw-r--r--user/service.go35
-rw-r--r--user/user.go55
-rw-r--r--user/validators.go60
11 files changed, 90 insertions, 242 deletions
diff --git a/auth/auth.go b/auth/auth.go
index 4ac6445..0b28b57 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,8 @@ import (
type AuthClaims struct {
jwt.RegisteredClaims
- UserID uint `json:"userid"`
+ UserID uint `json:"userid"`
+ Roles []string `json:"roles"`
}
type RefreshClaims struct {
diff --git a/auth/controller.go b/auth/controller.go
index 961518a..8de7370 100644
--- a/auth/controller.go
+++ b/auth/controller.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,36 +39,37 @@ func init() {
}
func handleSignUp (ctx *gin.Context) {
- var user user.User
- ctx.Bind(&user)
+ var u user.User
+ ctx.Bind(&u)
var err error
// hash password
var bytes []byte
- bytes, err = bcrypt.GenerateFromPassword([]byte(user.Password), 14)
+ bytes, err = bcrypt.GenerateFromPassword([]byte(u.Password), 14)
if err != nil {
// TODO: handle potential errors
ctx.Error(err)
ctx.Abort()
return
}
- user.Password = string(bytes)
+ u.Password = string(bytes)
+
+ // for now everyone's an admin
+ // TODO: fix this shit
+ u.Roles = []user.Role{
+ {0, 0, "admin"},
+ }
- err = user.Create()
+ err = u.Create()
if err != nil {
ctx.Error(err)
ctx.Abort()
return
}
- // remove password hash from response
- user.Password = ""
-
- ctx.JSON(http.StatusOK, gin.H{
- "message": "success",
- "data": user,
- })
+ // TODO: email verification and shit before this
+ ctx.JSON(http.StatusOK, nil)
}
func handleSignIn (ctx *gin.Context) {
@@ -93,6 +94,7 @@ func handleSignIn (ctx *gin.Context) {
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 2)),
},
u.ID,
+ user.RolesToStringList(u.Roles),
},
).SignedString(AUTH_KEY)
if err != nil {
@@ -125,7 +127,6 @@ func handleSignIn (ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"auth_token": authToken,
"refresh_token": refreshToken,
- "message": "success",
"data": u,
})
}
@@ -147,9 +148,10 @@ func handleRefresh (ctx *gin.Context) {
// check token version
var u user.User
- err := user.GetUser(&u, claims.UserID)
+ err := user.GetUserById(&u, claims.UserID)
if err != nil {
if err == errors.ErrNotFound {
+ // user doesn't exist
ctx.Error(errors.ErrUnauthorized)
ctx.Abort()
return
@@ -184,7 +186,8 @@ func handleRefresh (ctx *gin.Context) {
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 2)),
},
- claims.UserID,
+ u.ID,
+ user.RolesToStringList(u.Roles),
},
).SignedString(AUTH_KEY)
if err != nil {
@@ -196,6 +199,5 @@ func handleRefresh (ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"auth_token": authToken,
- "message": "success",
})
}
diff --git a/auth/middleware.go b/auth/middleware.go
index 9ce5e12..80e512e 100644
--- a/auth/middleware.go
+++ b/auth/middleware.go
@@ -70,6 +70,9 @@ func Authorize() gin.HandlerFunc {
return
}
+ ctx.Set("UserID", claims.UserID)
+ ctx.Set("Roles", claims.Roles)
+
ctx.Next()
}
}
diff --git a/conf/conf.go b/conf/conf.go
index 843bbea..4aa2bcd 100644
--- a/conf/conf.go
+++ b/conf/conf.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,11 +35,6 @@ func validateConf() {
ok = false
}
- if viper.GetInt("username.min_username_length") < 1 {
- log.Println("\x1b[41m\x1b[30m[err]\x1b[0m Minimum username length must be greater than 0.")
- ok = false
- }
-
minPassLen := viper.GetInt("security.min_password_length")
maxPassLen := viper.GetInt("security.max_password_length")
@@ -95,10 +90,6 @@ func init() {
viper.SetDefault("instance.description", "Libre Billing Software")
viper.SetDefault("instance.url", "https://openbills.vidhukant.com")
- viper.SetDefault("username.allowed_characters", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_")
- viper.SetDefault("username.min_username_length", 2)
- viper.SetDefault("username.max_username_length", 20)
-
viper.SetDefault("cryptography.password_hashing_cost", bcrypt.DefaultCost)
viper.SetDefault("data.upload_dir", "./data/")
diff --git a/main.go b/main.go
index 7f57af9..d49579b 100644
--- a/main.go
+++ b/main.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ import (
"log"
)
-const OPENBILLS_VERSION = "v0.18.0"
+const OPENBILLS_VERSION = "v0.19.0"
func init() {
if !viper.GetBool("debug_mode") {
diff --git a/openbills.toml b/openbills.toml
index 0742aaf..c5b569e 100644
--- a/openbills.toml
+++ b/openbills.toml
@@ -17,11 +17,6 @@ url = "https://openbills.vidhukant.com/"
min_password_length = 12
max_password_length = 72
-[username]
-allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_"
-min_username_length = 2
-max_username_length = 32
-
[cryptography]
password_hashing_cost = 14
auth_key = "22ELiOfHn19s0z1WWgsOT9RupghRYrXm"
diff --git a/user/controller.go b/user/controller.go
index 1dc85da..7dd519a 100644
--- a/user/controller.go
+++ b/user/controller.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,8 +20,6 @@ package user
import (
e "vidhukant.com/openbills/errors"
"github.com/gin-gonic/gin"
- "github.com/google/uuid"
- "github.com/spf13/viper"
"net/http"
)
@@ -37,7 +35,7 @@ func handleGetUser (ctx *gin.Context) {
userId := uId.(uint)
- err := GetUser(&user, userId)
+ err := GetUserById(&user, userId)
if err != nil {
ctx.Error(err)
ctx.Abort()
@@ -48,116 +46,6 @@ func handleGetUser (ctx *gin.Context) {
user.Password = ""
ctx.JSON(http.StatusOK, gin.H{
- "message": "success",
"data": user,
})
}
-
-func handleUploadLogo(ctx *gin.Context) {
- var user User
-
- uId, ok := ctx.Get("UserID")
- if !ok {
- ctx.Error(e.ErrUnauthorized)
- ctx.Abort()
- return
- }
-
- userId := uId.(uint)
- user.ID = userId
-
- // TODO: handle potential errors
- file, err := ctx.FormFile("logo")
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- dest := uuid.New().String()
-
- // TODO: handle potential errors
- err = ctx.SaveUploadedFile(file, viper.GetString("data.upload_dir") + dest)
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- // TODO: delete old file (if any)
- err = user.update(map[string]interface{}{"logo_file": dest})
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- ctx.JSON(http.StatusOK, gin.H{
- "message": "success",
- })
-}
-
-func handleUploadSignature(ctx *gin.Context) {
- var user User
-
- uId, ok := ctx.Get("UserID")
- if !ok {
- ctx.Error(e.ErrUnauthorized)
- ctx.Abort()
- return
- }
-
- userId := uId.(uint)
- user.ID = userId
-
- // TODO: handle potential errors
- file, err := ctx.FormFile("signature")
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- dest := uuid.New().String()
-
- // TODO: handle potential errors
- err = ctx.SaveUploadedFile(file, viper.GetString("data.upload_dir") + dest)
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- // TODO: delete old file (if any)
- err = user.update(map[string]interface{}{"signature_file": dest})
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- ctx.JSON(http.StatusOK, gin.H{
- "message": "success",
- })
-}
-
-// TODO: fix this stuff
-// also add some kind of 2 factor verification
-func handleDelUser (ctx *gin.Context) {
- id := uint(1) // get from JWT
-
- var user User
- user.ID = id
-
- // TODO: add a verification mechanism
- err := user.del()
- if err != nil {
- ctx.Error(err)
- ctx.Abort()
- return
- }
-
- ctx.JSON(http.StatusOK, gin.H{
- "message": "success",
- })
-}
diff --git a/user/router.go b/user/router.go
index d9fa7e0..eb7270a 100644
--- a/user/router.go
+++ b/user/router.go
@@ -25,7 +25,5 @@ func Routes(route *gin.RouterGroup) {
g := route.Group("/user")
{
g.GET("/", handleGetUser)
- g.POST("/logo", handleUploadLogo)
- g.POST("/signature", handleUploadSignature)
}
}
diff --git a/user/service.go b/user/service.go
index 222df4a..4dec8bc 100644
--- a/user/service.go
+++ b/user/service.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,14 +27,12 @@ func (u *User) Create() error {
return res.Error
}
-func GetUserWithAccountName(user *User, accountName, method string) error {
+func GetUserByAccountName(user *User, accountName, method string) error {
if method != "username" && method != "email" {
return e.ErrInvalidLoginMethod
}
- res := db.Where(method + " = ?", accountName).Find(&user)
-
- // TODO: handle potential errors
+ res := db.Where(method + " = ?", accountName).Preload("Roles").Find(&user)
if res.Error != nil {
return res.Error
}
@@ -46,10 +44,8 @@ func GetUserWithAccountName(user *User, accountName, method string) error {
return nil
}
-func GetUser(user *User, id uint) error {
- res := db.Find(&user, id)
-
- // TODO: handle potential errors
+func GetUserById(user *User, id uint) error {
+ res := db.Preload("Roles").Find(&user, id)
if res.Error != nil {
return res.Error
}
@@ -63,8 +59,6 @@ func GetUser(user *User, id uint) error {
func (u *User) del() error {
res := db.Delete(u)
-
- // TODO: handle potential errors
if res.Error != nil {
return res.Error
}
@@ -76,21 +70,4 @@ func (u *User) del() error {
return nil
}
-func (u *User) update(changes map[string]interface{}) error {
- res := db.Model(&u).
- Omit("email").
- Omit("password").
- Omit("username").
- Updates(changes)
-
- // TODO: handle potential errors
- if res.Error != nil {
- return res.Error
- }
-
- if res.RowsAffected == 0 {
- return e.ErrNotFound
- }
-
- return nil
-}
+// TODO: email/password updation (no username changes) with OTP verification or something
diff --git a/user/user.go b/user/user.go
index dbcbad0..4d0ffcb 100644
--- a/user/user.go
+++ b/user/user.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@ package user
import (
d "vidhukant.com/openbills/db"
e "vidhukant.com/openbills/errors"
- u "vidhukant.com/openbills/util"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"github.com/spf13/viper"
@@ -32,35 +31,45 @@ var db *gorm.DB
func init() {
db = d.DB
- db.AutoMigrate(&User{})
+ db.AutoMigrate(&User{}, &Role{})
COST = viper.GetInt("cryptography.password_hashing_cost")
}
+var VALID_ROLES []string = []string {
+ "customer.*", "customer.read", "customer.write", "customer.delete",
+ "item.*", "item.read", "item.write", "item.delete",
+ "invoice.*", "invoice.read", "invoice.write", "invoice.delete",
+ "admin", "*.*",
+}
+
+type Role struct {
+ ID uint
+ UserID uint
+ Name string
+}
+
type User struct {
- gorm.Model
- u.Address
- TokenVersion uint // this can be incremented to disable existing refresh token(s)
- FullName string
- FirmName string
- Gstin string
- Phone string
- Email string
- Website string
- Username string
- Password string
- LogoFile string
- SignatureFile string
- IsVerified bool // this should be removed and tokens should be issued upon verification
- // will be printed with address on the invoice
- Details string
- // a note is printed on every invoice.
- // This is the default that gets automatically set
- DefaultInvoiceNote string
+ ID uint
+ TokenVersion uint // this can be incremented to disable existing refresh token(s)
+ Username string
+ Email string
+ Password string
+ Roles []Role `gorm:"constraint:OnDelete:CASCADE;"`
+}
+
+func RolesToStringList(roles []Role) []string {
+ x := []string{}
+
+ for _, i := range roles {
+ x = append(x, i.Name)
+ }
+
+ return x
}
func CheckPassword(user *User, accountName, method, pass string) error {
- err := GetUserWithAccountName(user, accountName, method)
+ err := GetUserByAccountName(user, accountName, method)
if err != nil {
return err
}
diff --git a/user/validators.go b/user/validators.go
index e497122..e9a894c 100644
--- a/user/validators.go
+++ b/user/validators.go
@@ -1,5 +1,5 @@
/* openbills - Server for web based Libre Billing Software
- * Copyright (C) 2023-2024 Vidhu Kant Sharma <vidhukant@vidhukant.com>
+ * Copyright (C) 2023-2025 Vidhu Kant Sharma <vidhukant@vidhukant.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,45 +40,40 @@ func validatePassword(pass string) error {
func validateUsername(username string) error {
// check if username is too short
- if len(username) < viper.GetInt("username.min_username_length") {
+ if len(username) < 2 {
return errors.ErrUsernameTooShort
}
// check if username is too long
- if len(username) > viper.GetInt("username.max_username_length") {
+ if len(username) > 32 {
return errors.ErrUsernameTooLong
}
-
- for _, char := range username {
- if !strings.Contains(username, string(char)) {
- return errors.ErrInvalidUsername
- }
- }
+
+ // (11th October 2025) what the fuck even is this
+ // I'm not even deleting this I can't stop laughing
+ //
+ // for _, char := range username {
+ // if !strings.Contains(username, string(char)) {
+ // return errors.ErrInvalidUsername
+ // }
+ // }
return nil
}
func (u *User) validate() error {
- u.Username = strings.TrimSpace(u.Username)
u.Email = strings.TrimSpace(u.Email)
- u.Phone = strings.TrimSpace(u.Phone)
- u.Website = strings.TrimSpace(u.Website)
- u.Gstin = strings.TrimSpace(u.Gstin)
- u.IsVerified = false
-
- // don't validate if GSTIN is empty
- if u.Gstin != "" && !util.ValidateGstin(u.Gstin) {
- return errors.ErrInvalidGSTIN
- }
-
- // don't validate if phone is empty
- if u.Phone != "" && !util.ValidatePhone(u.Phone) {
- return errors.ErrInvalidPhone
- }
+ u.Username = strings.TrimSpace(u.Username)
- // don't validate if website is empty
- if u.Website != "" && !util.ValidateWebsite(u.Website) {
- return errors.ErrInvalidWebsite
+ // don't accept empty username
+ if u.Username == "" {
+ return errors.ErrEmptyUsername
+ } else {
+ // validate username
+ err := validateUsername(u.Username)
+ if err != nil {
+ return err
+ }
}
// don't accept empty email
@@ -91,17 +86,6 @@ func (u *User) validate() error {
}
}
- // don't accept empty username
- if u.Username == "" {
- return errors.ErrEmptyUsername
- } else {
- // validate username
- err := validateUsername(u.Username)
- if err != nil {
- return err
- }
- }
-
// validate password
err := validatePassword(u.Password)
if err != nil {