Move to Donetick Org, first commit

This commit is contained in:
Mo Tarbin 2024-06-30 21:41:41 -04:00
commit c13dd9addb
42 changed files with 7463 additions and 0 deletions

View file

@ -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,
})
}

View file

@ -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
}