console/internal/biz/account.go

265 lines
6.3 KiB
Go

package biz
import (
"errors"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/mitchellh/mapstructure"
"golang.org/x/crypto/bcrypt"
"infini.sh/console/internal/biz/enum"
"infini.sh/console/internal/dto"
"infini.sh/console/model/rbac"
"infini.sh/framework/core/event"
"infini.sh/framework/core/global"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"strings"
"time"
)
type UserClaims struct {
*jwt.RegisteredClaims
*User
}
type User struct {
Username string `json:"username"`
UserId string `json:"user_id"`
Roles []string `json:"roles"`
}
type Account struct {
ID string `json:"id,omitempty" `
Created string `json:"created,omitempty" `
Updated string `json:"updated,omitempty" `
Username string `json:"username" elastic_mapping:"username:{type:keyword}"`
Password string `json:"password" elastic_mapping:"password:{type:text}"`
Name string `json:"name" elastic_mapping:"name:{type:keyword}"`
Phone string `json:"phone" elastic_mapping:"phone:{type:keyword}"`
Email string `json:"email" elastic_mapping:"email:{type:keyword}"`
Tags []string `json:"tags" elastic_mapping:"tags:{type:text}"`
Roles []rbac.UserRole `json:"roles"`
}
const Secret = "console"
func authenticateUser(username string, password string) (user Account, err error) {
err, result := orm.GetBy("username", username, rbac.User{})
if err != nil {
err = ErrNotFound
return
}
if result.Total == 0 {
err = errors.New("user not found")
return
}
err = mapstructure.Decode(result.Result[0], &user)
if err != nil {
return
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err == bcrypt.ErrMismatchedHashAndPassword {
err = errors.New("password incorrect")
return
}
return
}
func authenticateAdmin(username string, password string) (user Account, err error) {
u, _ := global.Env().GetConfig("bootstrap.username", "admin")
p, _ := global.Env().GetConfig("bootstrap.password", "admin")
if u != username || p != password {
err = errors.New("invalid username or password")
return
}
user.ID = username
user.Username = username
user.Roles = []rbac.UserRole{{
Id: "admin", Name: "admin",
}}
return user, nil
}
func authorize(user Account) (m map[string]interface{}, err error) {
var roles, privilege []string
if user.Username == "admin" {
roles = append(roles, "admin")
privilege = append(privilege, enum.AdminPrivilege...)
} else {
for _, v := range user.Roles {
roles = append(roles, v.Name)
r, _ := GetRole(v.Id)
privilege = append(privilege, r.Platform...)
RolePermission[v.Name] = enum.Role{
Platform: r.Platform,
Cluster: r.Cluster,
ClusterPrivilege: r.ClusterPrivilege,
Index: r.Index,
}
}
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaims{
User: &User{
Username: user.Username,
UserId: user.ID,
Roles: roles,
},
RegisteredClaims: &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
},
})
tokenString, err := token.SignedString([]byte(Secret))
if err != nil {
return
}
m = util.MapStr{
"access_token": tokenString,
"username": user.Username,
"id": user.ID,
"expire_in": 86400,
"roles": roles,
"privilege": privilege,
}
return
}
func Login(username string, password string) (m map[string]interface{}, err error) {
var user Account
if username == "admin" {
user, err = authenticateAdmin(username, password)
if err != nil {
return nil, err
}
} else {
user, err = authenticateUser(username, password)
if err != nil {
return nil, err
}
}
m, err = authorize(user)
if err != nil {
return
}
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "login",
Type: "create",
Labels: util.MapStr{
"username": username,
"password": password,
},
User: util.MapStr{
"userid": user.ID,
"username": user.Username,
},
}, nil, nil))
return
}
func UpdatePassword(localUser *User, req dto.UpdatePassword) (err error) {
user := rbac.User{}
user.ID = localUser.UserId
_, err = orm.Get(&user)
if err != nil {
err = ErrNotFound
return
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword))
if err == bcrypt.ErrMismatchedHashAndPassword {
err = errors.New("old password is not correct")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
return
}
user.Password = string(hash)
err = orm.Save(&user)
if err != nil {
return
}
return
}
func ValidateLogin(authorizationHeader string) (clams *UserClaims, err error) {
if authorizationHeader == "" {
err = errors.New("authorization header is empty")
return
}
fields := strings.Fields(authorizationHeader)
if fields[0] != "Bearer" || len(fields) != 2 {
err = errors.New("authorization header is invalid")
return
}
tokenString := fields[1]
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(Secret), nil
})
if err != nil {
return
}
if clams, ok := token.Claims.(*UserClaims); ok && token.Valid {
return clams, nil
}
if clams.UserId == "" {
err = errors.New("user id is empty")
return
}
return
}
func ValidatePermission(claims *UserClaims, permissions []string) (err error) {
user := claims.User
if user.UserId == "" {
err = errors.New("user id is empty")
return
}
if user.Roles == nil {
err = errors.New("api permission is empty")
return
}
// 权限校验
userPermissionMap := make(map[string]struct{})
for _, role := range user.Roles {
if _, ok := RolePermission[role]; ok {
for _, v := range RolePermission[role].Platform {
userPermissionMap[v] = struct{}{}
//all include read
if strings.Contains(v, "all") {
key := v[:len(v)-3] + "read"
userPermissionMap[key] = struct{}{}
}
}
}
}
var count int
for _, v := range permissions {
if _, ok := userPermissionMap[v]; ok {
count++
continue
}
}
if count == len(permissions) {
return nil
}
err = errors.New("permission denied")
return
}