diff options
Diffstat (limited to 'auth')
| -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 | 
5 files changed, 241 insertions, 1 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)  	}  }  |