Move to Donetick Org, first commit
This commit is contained in:
commit
c13dd9addb
42 changed files with 7463 additions and 0 deletions
137
internal/authorization/middleware.go
Normal file
137
internal/authorization/middleware.go
Normal 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,
|
||||
})
|
||||
}
|
60
internal/authorization/password.go
Normal file
60
internal/authorization/password.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue