/* openbills - Server for web based Libre Billing Software * Copyright (C) 2023 Vidhu Kant Sharma * * 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 . */ 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, }) }