aboutsummaryrefslogtreecommitdiffstats
path: root/internal/authorization
diff options
context:
space:
mode:
authorLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 21:41:41 -0400
committerLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 21:41:41 -0400
commitc13dd9addbf89f716e4ef5cfdf1d673139ffcb68 (patch)
treebc09646ce1d6d3a402abb4694e19da51b57204f6 /internal/authorization
downloaddonetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.tar.gz
donetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.tar.bz2
donetick-c13dd9addbf89f716e4ef5cfdf1d673139ffcb68.zip
Move to Donetick Org, first commit
Diffstat (limited to '')
-rw-r--r--internal/authorization/middleware.go137
-rw-r--r--internal/authorization/password.go60
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
+}