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