diff options
-rw-r--r-- | auth/:w | 182 | ||||
-rw-r--r-- | auth/auth.go | 4 | ||||
-rw-r--r-- | auth/controller.go | 53 | ||||
-rw-r--r-- | auth/middleware.go | 2 | ||||
-rw-r--r-- | auth/router.go | 1 | ||||
-rw-r--r-- | main.go | 2 |
6 files changed, 242 insertions, 2 deletions
@@ -0,0 +1,182 @@ +/* openbills - Server for web based Libre Billing Software + * Copyright (C) 2023 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package auth + +import ( + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/spf13/viper" + "golang.org/x/crypto/bcrypt" + "vidhukant.com/openbills/user" + "net/http" + "time" + "vidhukant.com/openbills/errors" +) + +var ( + COST int + AUTH_KEY, REFRESH_KEY []byte +) +func init() { + COST = viper.GetInt("cryptography.password_hashing_cost") + AUTH_KEY = []byte(viper.GetString("cryptography.auth_key")) + REFRESH_KEY = []byte(viper.GetString("cryptography.refresh_key")) +} + +func handleSignUp (ctx *gin.Context) { + var user user.User + ctx.Bind(&user) + + var err error + + // hash password + var bytes []byte + bytes, err = bcrypt.GenerateFromPassword([]byte(user.Password), 14) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + user.Password = string(bytes) + + err = user.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, + }) +} + +func handleSignIn (ctx *gin.Context) { + var req LoginReq + ctx.Bind(&req) + + var err error + var u user.User + + err = user.CheckPassword(&u, req.AccountName, req.Method, req.Password) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + + authToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, + AuthClaims { + jwt.RegisteredClaims { + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 2)), + }, + u.ID, + }, + ).SignedString(AUTH_KEY) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + + refreshToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, + AuthClaims { + jwt.RegisteredClaims { + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 6)), + }, + u.ID, + }, + ).SignedString(REFRESH_KEY) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + + // remove password hash from response + u.Password = "" + + ctx.JSON(http.StatusOK, gin.H{ + "auth_token": authToken, + "refresh_token": refreshToken, + "message": "success", + "data": u, + }) +} + +func handleRefresh (ctx *gin.Context) { + var req RefreshReq + ctx.Bind(&req) + + tk, _ := jwt.ParseWithClaims(req.RefreshToken, &AuthClaims{}, func (token *jwt.Token) (interface{}, error) { + return []byte(REFRESH_KEY), nil + }) + + claims, ok := tk.Claims.(*AuthClaims) + if !ok { + ctx.Error(errors.ErrUnauthorized) + ctx.Abort() + return + } + + if !tk.Valid { + eat := claims.ExpiresAt.Unix() + if eat != 0 && eat < time.Now().Unix() { + ctx.Error(errors.ErrSessionExpired) + } else { + ctx.Error(errors.ErrUnauthorized) + } + + ctx.Abort() + return + } + + // TODO: if token is valid, check if user even exists before generating authToken + + authToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, + AuthClaims { + jwt.RegisteredClaims { + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 2)), + }, + claims.UserID, + }, + ).SignedString(AUTH_KEY) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "auth_token": authToken, + "message": "success", + //"data": u, + }) +} diff --git a/auth/auth.go b/auth/auth.go index ae2db9b..7116a2c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -31,3 +31,7 @@ type LoginReq struct { Method string Password string } + +type RefreshReq struct { + RefreshToken string +} diff --git a/auth/controller.go b/auth/controller.go index 5b18b64..7e3346e 100644 --- a/auth/controller.go +++ b/auth/controller.go @@ -25,6 +25,7 @@ import ( "vidhukant.com/openbills/user" "net/http" "time" + "vidhukant.com/openbills/errors" ) var ( @@ -127,3 +128,55 @@ func handleSignIn (ctx *gin.Context) { "data": u, }) } + +func handleRefresh (ctx *gin.Context) { + var req RefreshReq + ctx.Bind(&req) + + tk, _ := jwt.ParseWithClaims(req.RefreshToken, &AuthClaims{}, func (token *jwt.Token) (interface{}, error) { + return []byte(REFRESH_KEY), nil + }) + + claims, ok := tk.Claims.(*AuthClaims) + if !ok { + ctx.Error(errors.ErrUnauthorized) + ctx.Abort() + return + } + + if !tk.Valid { + eat := claims.ExpiresAt.Unix() + if eat != 0 && eat < time.Now().Unix() { + ctx.Error(errors.ErrSessionExpired) + } else { + ctx.Error(errors.ErrUnauthorized) + } + + ctx.Abort() + return + } + + // TODO: if token is valid, check if user even exists before generating authToken + + authToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, + AuthClaims { + jwt.RegisteredClaims { + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 2)), + }, + claims.UserID, + }, + ).SignedString(AUTH_KEY) + if err != nil { + // TODO: handle potential errors + ctx.Error(err) + ctx.Abort() + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "auth_token": authToken, + "message": "success", + //"data": u, + }) +} diff --git a/auth/middleware.go b/auth/middleware.go index 299a7be..9a065d5 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -47,7 +47,7 @@ func Authorize() gin.HandlerFunc { return } - tk, err := jwt.ParseWithClaims(bearerToken, &AuthClaims{}, func (token *jwt.Token) (interface{}, error) { + tk, _ := jwt.ParseWithClaims(bearerToken, &AuthClaims{}, func (token *jwt.Token) (interface{}, error) { return []byte(AUTH_KEY), nil }) diff --git a/auth/router.go b/auth/router.go index b8d5b0d..9b9d533 100644 --- a/auth/router.go +++ b/auth/router.go @@ -26,5 +26,6 @@ func Routes(route *gin.RouterGroup) { { g.POST("/signup", handleSignUp) g.POST("/signin", handleSignIn) + g.POST("/refresh", handleRefresh) } } @@ -37,7 +37,7 @@ import ( "log" ) -const OPENBILLS_VERSION = "v0.0.9" +const OPENBILLS_VERSION = "v0.0.10" func init() { if viper.GetBool("production_mode") { |