diff options
author | Mo Tarbin <mhed.t91@gmail.com> | 2024-06-30 21:41:41 -0400 |
---|---|---|
committer | Mo Tarbin <mhed.t91@gmail.com> | 2024-06-30 21:41:41 -0400 |
commit | c13dd9addbf89f716e4ef5cfdf1d673139ffcb68 (patch) | |
tree | bc09646ce1d6d3a402abb4694e19da51b57204f6 /internal/authorization | |
download | donetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.tar.gz donetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.tar.bz2 donetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.zip |
Move to Donetick Org, first commit
Diffstat (limited to 'internal/authorization')
-rw-r--r-- | internal/authorization/middleware.go | 137 | ||||
-rw-r--r-- | internal/authorization/password.go | 60 |
2 files changed, 197 insertions, 0 deletions
diff --git a/internal/authorization/middleware.go b/internal/authorization/middleware.go new file mode 100644 index 0000000..18a7026 --- /dev/null +++ b/internal/authorization/middleware.go @@ -0,0 +1,137 @@ +package auth + +import ( + "net/http" + "time" + + "donetick.com/core/config" + uModel "donetick.com/core/internal/user/model" + uRepo "donetick.com/core/internal/user/repo" + "donetick.com/core/logging" + jwt "github.com/appleboy/gin-jwt/v2" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +var identityKey = "id" + +type signIn struct { + Username string `form:"username" json:"username" binding:"required"` + Password string `form:"password" json:"password" binding:"required"` +} + +func CurrentUser(c *gin.Context) (*uModel.User, bool) { + data, ok := c.Get(identityKey) + if !ok { + return nil, false + } + acc, ok := data.(*uModel.User) + return acc, ok +} + +func MustCurrentUser(c *gin.Context) *uModel.User { + acc, ok := CurrentUser(c) + if ok { + return acc + } + panic("no account in gin.Context") +} + +func NewAuthMiddleware(cfg *config.Config, userRepo *uRepo.UserRepository) (*jwt.GinJWTMiddleware, error) { + return jwt.New(&jwt.GinJWTMiddleware{ + Realm: "test zone", + Key: []byte(cfg.Jwt.Secret), + Timeout: cfg.Jwt.SessionTime, + MaxRefresh: cfg.Jwt.MaxRefresh, // 7 days as long as their token is valid they can refresh it + IdentityKey: identityKey, + PayloadFunc: func(data interface{}) jwt.MapClaims { + if u, ok := data.(*uModel.User); ok { + return jwt.MapClaims{ + identityKey: u.Username, + } + } + return jwt.MapClaims{} + }, + IdentityHandler: func(c *gin.Context) interface{} { + claims := jwt.ExtractClaims(c) + username, ok := claims[identityKey].(string) + if !ok { + return nil + } + user, err := userRepo.GetUserByUsername(c.Request.Context(), username) + if err != nil { + return nil + } + return user + }, + Authenticator: func(c *gin.Context) (interface{}, error) { + provider := c.Value("auth_provider") + switch provider { + case nil: + var req signIn + if err := c.ShouldBindJSON(&req); err != nil { + return "", jwt.ErrMissingLoginValues + } + + // ctx := cache.WithCacheSkip(c.Request.Context(), true) + user, err := userRepo.GetUserByUsername(c.Request.Context(), req.Username) + if err != nil || user.Disabled { + return nil, jwt.ErrFailedAuthentication + } + err = Matches(user.Password, req.Password) + if err != nil { + if err != bcrypt.ErrMismatchedHashAndPassword { + logging.FromContext(c).Warnw("middleware.jwt.Authenticator found unknown error when matches password", "err", err) + } + return nil, jwt.ErrFailedAuthentication + } + return &uModel.User{ + ID: user.ID, + Username: user.Username, + Password: "", + Image: user.Image, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + Disabled: user.Disabled, + CircleID: user.CircleID, + }, nil + case "3rdPartyAuth": + // we should only reach this stage if a handler mannually call authenticator with it's context: + + var authObject *uModel.User + v := c.Value("user_account") + authObject = v.(*uModel.User) + + return authObject, nil + + default: + return nil, jwt.ErrFailedAuthentication + } + }, + + Authorizator: func(data interface{}, c *gin.Context) bool { + + if _, ok := data.(*uModel.User); ok { + return true + } + return false + }, + Unauthorized: func(c *gin.Context, code int, message string) { + logging.FromContext(c).Info("middleware.jwt.Unauthorized", "code", code, "message", message) + c.JSON(code, gin.H{ + "code": code, + "message": message, + }) + }, + LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "token": token, + "expire": expire, + }) + }, + TokenLookup: "header: Authorization", + TokenHeadName: "Bearer", + TimeFunc: time.Now, + }) +} diff --git a/internal/authorization/password.go b/internal/authorization/password.go new file mode 100644 index 0000000..00cd222 --- /dev/null +++ b/internal/authorization/password.go @@ -0,0 +1,60 @@ +package auth + +import ( + "crypto/rand" + "encoding/base64" + "math/big" + + "donetick.com/core/logging" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;':,.<>?/~" + +func EncodePassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func Matches(hashedPassword, password string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +} + +func GenerateRandomPassword(length int) string { + // Create a buffer to hold the random bytes. + buffer := make([]byte, length) + + // Compute the maximum index for the characters. + maxIndex := big.NewInt(int64(len(chars))) + + // Generate random bytes and use them to select characters from the set. + for i := 0; i < length; i++ { + randomIndex, _ := rand.Int(rand.Reader, maxIndex) + buffer[i] = chars[randomIndex.Int64()] + } + + return string(buffer) +} + +func GenerateEmailResetToken(c *gin.Context) (string, error) { + logger := logging.FromContext(c) + // Define the length of the token (in bytes). For example, 32 bytes will result in a 44-character base64-encoded token. + tokenLength := 32 + + // Generate a random byte slice. + tokenBytes := make([]byte, tokenLength) + _, err := rand.Read(tokenBytes) + if err != nil { + logger.Errorw("password.GenerateEmailResetToken failed to generate random bytes", "err", err) + return "", err + } + + // Encode the byte slice to a base64 string. + token := base64.URLEncoding.EncodeToString(tokenBytes) + + return token, nil +} |