wip: jwt
This commit is contained in:
@ -2,13 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"loveuer/utodo/internal/handler"
|
||||
"loveuer/utodo/internal/opt"
|
||||
g_handler "loveuer/utodo/pkg/handler"
|
||||
"loveuer/utodo/pkg/middleware/logger"
|
||||
"loveuer/utodo/pkg/middleware/trace"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
r3 "github.com/gofiber/fiber/v3/middleware/recover"
|
||||
)
|
||||
@ -29,13 +30,14 @@ func New(ctx context.Context) *fiber.App {
|
||||
app.Use(trace.New())
|
||||
// app.Use(l3.New())
|
||||
app.Use(logger.New())
|
||||
app.Use(r3.New())
|
||||
app.Use(r3.New(r3.Config{EnableStackTrace: true}))
|
||||
|
||||
app.Get("/healthz", g_handler.Healthz(opt.Cfg.Name, opt.Cfg.Version))
|
||||
|
||||
{
|
||||
api := app.Group("/api/v1/auth")
|
||||
api.Post("/login", handler.Login())
|
||||
api.Post("/verify", handler.Verify(""))
|
||||
}
|
||||
|
||||
return app
|
||||
|
@ -3,9 +3,9 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"loveuer/utodo/internal/opt"
|
||||
"loveuer/utodo/pkg/logger"
|
||||
"loveuer/utodo/pkg/tool"
|
||||
|
||||
"gitea.loveuer.com/yizhisec/packages/logger"
|
||||
"gitea.loveuer.com/yizhisec/packages/tool"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"loveuer/utodo/internal/opt"
|
||||
g_api "loveuer/utodo/pkg/api"
|
||||
"loveuer/utodo/pkg/database/cache"
|
||||
"loveuer/utodo/pkg/database/db"
|
||||
"loveuer/utodo/pkg/tool"
|
||||
|
||||
"gitea.loveuer.com/yizhisec/packages/database/db"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"loveuer/utodo/internal/model"
|
||||
"loveuer/utodo/internal/opt"
|
||||
"loveuer/utodo/pkg/database/db"
|
||||
"loveuer/utodo/pkg/resp"
|
||||
"loveuer/utodo/pkg/tool"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
@ -16,15 +23,93 @@ func Login() fiber.Handler {
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req Req
|
||||
err error
|
||||
req Req
|
||||
op = new(model.User)
|
||||
token string
|
||||
)
|
||||
|
||||
if err = c.Bind().JSON(&req); err != nil {
|
||||
return resp.R400(c, "", nil, err.Error())
|
||||
}
|
||||
|
||||
// TODO: 完成登录
|
||||
return nil
|
||||
tx := db.Default.Session(tool.TimeoutCtx(c.Context(), 5), db.Config{Debug: opt.Cfg.Debug}).
|
||||
Model(&model.User{})
|
||||
|
||||
conds := make([]string, 0)
|
||||
|
||||
if req.Username != "" {
|
||||
conds = append(conds, fmt.Sprintf("username = %q", req.Username))
|
||||
}
|
||||
|
||||
if req.Phone != "" {
|
||||
conds = append(conds, fmt.Sprintf("phone = %q", req.Phone))
|
||||
}
|
||||
|
||||
if req.Email != "" {
|
||||
conds = append(conds, fmt.Sprintf("email = %q", req.Email))
|
||||
}
|
||||
|
||||
if len(conds) == 0 {
|
||||
return resp.R400(c, "", nil, "username, phone or email is required")
|
||||
}
|
||||
|
||||
tx = tx.Where("deleted_at = 0 AND (" + strings.Join(conds, " OR ") + ")")
|
||||
|
||||
if err = tx.First(op).Error; err != nil {
|
||||
return resp.R400(c, "用户信息错误, 登录失败", nil, err.Error())
|
||||
}
|
||||
|
||||
if !tool.ComparePassword(req.Password, op.Password) {
|
||||
return resp.R400(c, "用户信息错误, 登录失败", nil, "密码错误")
|
||||
}
|
||||
|
||||
op.LastLoginAt = time.Now().UnixMilli()
|
||||
|
||||
if err = db.Default.Session(tool.TimeoutCtx(c.Context(), 5)).
|
||||
Model(&model.User{}).
|
||||
Where("id = ?", op.Id).
|
||||
Updates(map[string]any{
|
||||
"last_login_at": op.LastLoginAt,
|
||||
}).Error; err != nil {
|
||||
return resp.R500(c, "登录失败", nil, err.Error())
|
||||
}
|
||||
|
||||
if token, err = model.UserJWT.Generate(op); err != nil {
|
||||
return resp.R500(c, "登录失败", nil, err.Error())
|
||||
}
|
||||
|
||||
return resp.R200(c, map[string]any{
|
||||
"token": token,
|
||||
"user": op,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Verify(key string) fiber.Handler {
|
||||
if key == "" {
|
||||
key = "Authorization"
|
||||
}
|
||||
|
||||
return func(c fiber.Ctx) error {
|
||||
token := c.Get(key)
|
||||
|
||||
if token == "" {
|
||||
return resp.R401(c, "", nil, "token is required")
|
||||
}
|
||||
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
|
||||
user, err := model.UserJWT.Parse(token)
|
||||
if err != nil {
|
||||
return resp.R401(c, "", nil, err.Error())
|
||||
}
|
||||
|
||||
c.Locals("user", user)
|
||||
|
||||
return resp.R200(c, fiber.Map{
|
||||
"user": user,
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
11
internal/model/enum/privilege.go
Normal file
11
internal/model/enum/privilege.go
Normal file
@ -0,0 +1,11 @@
|
||||
package enum
|
||||
|
||||
type Privilege string
|
||||
|
||||
const (
|
||||
PrivilegeAll Privilege = "*"
|
||||
PrivilegeUserCreate Privilege = "user:create"
|
||||
PrivilegeUserRead Privilege = "user:read"
|
||||
PrivilegeUserUpdate Privilege = "user:update"
|
||||
PrivilegeUserDelete Privilege = "user:delete"
|
||||
)
|
8
internal/model/enum/role.go
Normal file
8
internal/model/enum/role.go
Normal file
@ -0,0 +1,8 @@
|
||||
package enum
|
||||
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
UserRoleAdmin UserRole = "admin"
|
||||
UserRoleUser UserRole = "user"
|
||||
)
|
9
internal/model/enum/twoFactor.go
Normal file
9
internal/model/enum/twoFactor.go
Normal file
@ -0,0 +1,9 @@
|
||||
package enum
|
||||
|
||||
type TwoFactor string
|
||||
|
||||
const (
|
||||
TwoFactorEmail TwoFactor = "email"
|
||||
TwoFactorSMS TwoFactor = "sms"
|
||||
TwoFactorGoogle TwoFactor = "google" // 2fa with google authenticator
|
||||
)
|
@ -2,14 +2,60 @@ package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"loveuer/utodo/internal/model/enum"
|
||||
"loveuer/utodo/pkg/logger"
|
||||
"loveuer/utodo/pkg/sqlType"
|
||||
"loveuer/utodo/pkg/tool"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func Init(ctx context.Context, tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
func Init(ctx context.Context, tx *gorm.DB) (err error) {
|
||||
if err = tx.AutoMigrate(
|
||||
&Todo{},
|
||||
&User{},
|
||||
&Object{},
|
||||
)
|
||||
&Privilege{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Model(&Privilege{}).
|
||||
Clauses(clause.OnConflict{
|
||||
DoNothing: true,
|
||||
}).
|
||||
Create(&Privilege{
|
||||
Role: enum.UserRoleAdmin,
|
||||
Privileges: sqlType.SliceStr[enum.Privilege]{enum.PrivilegeAll},
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var uc int64
|
||||
if err = tx.Model(&User{}).
|
||||
Select("COUNT(id)").
|
||||
Where("deleted_at", 0).
|
||||
Find(&uc).
|
||||
Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uc == 0 {
|
||||
password := tool.RandomString(16)
|
||||
if err = tx.Model(&User{}).
|
||||
Create(&User{
|
||||
Username: "admin",
|
||||
Nickname: "admin",
|
||||
Password: tool.NewPassword(password),
|
||||
Roles: sqlType.SliceStr[enum.UserRole]{enum.UserRoleAdmin},
|
||||
ResetPassword: 1,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.InfoCtx(ctx, "inited admin password: %s", password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
15
internal/model/privilege.go
Normal file
15
internal/model/privilege.go
Normal file
@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"loveuer/utodo/internal/model/enum"
|
||||
"loveuer/utodo/pkg/sqlType"
|
||||
)
|
||||
|
||||
type Privilege struct {
|
||||
Id int64 `json:"id" gorm:"column:id,primaryKey"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"column:created_at,autoCreateTime:milli"`
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at,autoUpdateTime:milli"`
|
||||
DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at"`
|
||||
Role enum.UserRole `json:"role" gorm:"column:role;type:varchar(32);not null;unique"`
|
||||
Privileges sqlType.SliceStr[enum.Privilege] `json:"privileges" gorm:"column:privileges"`
|
||||
}
|
@ -1,14 +1,40 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"loveuer/utodo/internal/model/enum"
|
||||
"loveuer/utodo/pkg/jwt"
|
||||
"loveuer/utodo/pkg/sqlType"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64 `json:"id" gorm:"column:id,primaryKey"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"column:created_at,autoCreateTime:milli"`
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at,autoUpdateTime:milli"`
|
||||
DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at"`
|
||||
Username string `json:"username" gorm:"column:username;type:varchar(255);not null"`
|
||||
Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(255);not null"`
|
||||
Password string `json:"-" gorm:"column:password;type:varchar(255);not null"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar;type:varchar(32);not null"`
|
||||
Phone string `json:"phone" gorm:"column:phone;type:varchar(16);not null"`
|
||||
Email string `json:"email" gorm:"column:email;type:varchar(255);not null"`
|
||||
Id int64 `json:"id" gorm:"column:id;primaryKey"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"`
|
||||
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"`
|
||||
DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at"`
|
||||
Username string `json:"username" gorm:"column:username;type:varchar(255);not null"`
|
||||
Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(255);not null"`
|
||||
Password string `json:"-" gorm:"column:password;type:varchar(255);not null"`
|
||||
ResetPassword uint8 `json:"reset_password" gorm:"column:reset_password;type:tinyint(1);default:0"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar;type:varchar(32);not null"`
|
||||
Phone string `json:"phone" gorm:"column:phone;type:varchar(16);not null"`
|
||||
Email string `json:"email" gorm:"column:email;type:varchar(255);not null"`
|
||||
TwoFactor enum.TwoFactor `json:"two_factor" gorm:"column:two_factor;type:varchar(16)"` // 2fa type
|
||||
Roles sqlType.SliceStr[enum.UserRole] `json:"roles" gorm:"column:roles;not null"`
|
||||
LastLoginAt int64 `json:"last_login_at" gorm:"column:last_login_at"`
|
||||
}
|
||||
|
||||
func UserJwtPayload(u *User) map[string]any {
|
||||
return map[string]any{
|
||||
"id": u.Id,
|
||||
"username": u.Username,
|
||||
"nickname": u.Nickname,
|
||||
"roles": u.Roles,
|
||||
"last_login_at": u.LastLoginAt,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
UserJWT = jwt.New(
|
||||
jwt.WithPayloadFn(UserJwtPayload),
|
||||
)
|
||||
)
|
||||
|
Reference in New Issue
Block a user