init
This commit is contained in:
@@ -0,0 +1,756 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/str"
|
||||
|
||||
apperrors "goravel/app/errors"
|
||||
"goravel/app/http/helpers"
|
||||
"goravel/app/http/requests/admin"
|
||||
"goravel/app/http/response"
|
||||
"goravel/app/models"
|
||||
"goravel/app/services"
|
||||
"goravel/app/utils"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
authService services.AuthService
|
||||
captchaService services.CaptchaService
|
||||
googleAuthenticatorService services.GoogleAuthenticatorService
|
||||
}
|
||||
|
||||
func NewAuthController() *AuthController {
|
||||
adminService := services.NewAdminServiceImpl()
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
authService := services.NewAuthServiceImpl(adminService, tokenService)
|
||||
return &AuthController{
|
||||
authService: authService,
|
||||
captchaService: services.NewCaptchaServiceImpl(),
|
||||
googleAuthenticatorService: services.NewGoogleAuthenticatorServiceImpl(),
|
||||
}
|
||||
}
|
||||
|
||||
// getLoginRequestData 获取登录请求数据(排除敏感信息)
|
||||
func (r *AuthController) getLoginRequestData(ctx http.Context) string {
|
||||
inputs := make(map[string]any)
|
||||
allInputs := ctx.Request().All()
|
||||
for key, value := range allInputs {
|
||||
// 使用工具函数检查是否是敏感字段
|
||||
if utils.IsSensitiveField(key) {
|
||||
inputs[key] = "***"
|
||||
} else {
|
||||
inputs[key] = value
|
||||
}
|
||||
}
|
||||
if data, err := json.Marshal(inputs); err == nil {
|
||||
return string(data)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Login 管理员登录
|
||||
func (r *AuthController) Login(ctx http.Context) http.Response {
|
||||
var loginRequest admin.Login
|
||||
errors, err := ctx.Request().ValidateRequest(&loginRequest)
|
||||
if err != nil {
|
||||
// 记录验证失败日志
|
||||
requestData := r.getLoginRequestData(ctx)
|
||||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "validation_failed", requestData)
|
||||
return response.Error(ctx, http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if errors != nil {
|
||||
// 记录验证失败日志
|
||||
requestData := r.getLoginRequestData(ctx)
|
||||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "validation_failed", requestData)
|
||||
return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All())
|
||||
}
|
||||
|
||||
// 获取请求数据用于日志记录
|
||||
requestData := r.getLoginRequestData(ctx)
|
||||
|
||||
// 先验证用户名是否存在
|
||||
exists, err := facades.Orm().Query().Model(&models.Admin{}).Where("username", loginRequest.Username).Exists()
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"username": loginRequest.Username,
|
||||
})
|
||||
}
|
||||
if !exists {
|
||||
// 记录用户名不存在日志
|
||||
r.authService.RecordLoginLog(ctx, 0, loginRequest.Username, 0, "username_not_found", requestData)
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUsernameOrPasswordErr.Code)
|
||||
}
|
||||
|
||||
// 获取管理员信息
|
||||
var admin models.Admin
|
||||
if err := facades.Orm().Query().Where("username", loginRequest.Username).FirstOrFail(&admin); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"username": loginRequest.Username,
|
||||
})
|
||||
}
|
||||
|
||||
if admin.Status == 0 {
|
||||
// 记录账号禁用日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "account_disabled", requestData)
|
||||
return response.Error(ctx, http.StatusForbidden, apperrors.ErrAccountDisabled.Code)
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !facades.Hash().Check(loginRequest.Password, admin.Password) {
|
||||
// 记录登录失败日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "password_error", requestData)
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUsernameOrPasswordErr.Code)
|
||||
}
|
||||
|
||||
// 检查是否绑定了谷歌验证码
|
||||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
// 如果绑定了谷歌验证码,验证谷歌验证码
|
||||
if isBound {
|
||||
googleCode := loginRequest.GoogleCode
|
||||
if googleCode == "" {
|
||||
// 记录谷歌验证码缺失日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "google_code_required", requestData)
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeRequired.Code)
|
||||
}
|
||||
|
||||
// 获取管理员的密钥
|
||||
secret, err := r.googleAuthenticatorService.GetSecret(admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
// 验证谷歌验证码
|
||||
if !r.googleAuthenticatorService.Verify(secret, googleCode) {
|
||||
// 记录登录失败日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, "google_code_error", requestData)
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||||
}
|
||||
} else {
|
||||
// 如果没有绑定谷歌验证码,验证图形验证码(如果启用了)
|
||||
if r.captchaService.Enabled() {
|
||||
captchaID := ctx.Request().Input("captcha_id")
|
||||
captchaAnswer := ctx.Request().Input("captcha_answer")
|
||||
if ok, messageKey := r.captchaService.Verify(captchaID, captchaAnswer); !ok {
|
||||
if messageKey == "" {
|
||||
messageKey = "captcha_invalid"
|
||||
}
|
||||
// 记录验证码错误日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 0, messageKey, requestData)
|
||||
return response.Error(ctx, http.StatusBadRequest, messageKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证通过,生成token并完成登录
|
||||
// 获取浏览器和操作系统信息
|
||||
browser, os := helpers.GetBrowserAndOS(ctx)
|
||||
// 获取真实IP地址
|
||||
ip := helpers.GetRealIP(ctx)
|
||||
|
||||
// 生成token
|
||||
var expiresAt *time.Time
|
||||
ttl := facades.Config().GetInt("jwt.ttl", 60) // 默认60分钟
|
||||
if ttl > 0 {
|
||||
exp := time.Now().Add(time.Duration(ttl) * time.Minute)
|
||||
expiresAt = &exp
|
||||
}
|
||||
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
plainToken, _, err := tokenService.CreateToken("admin", admin.ID, "admin-token", expiresAt, browser, ip, os, "")
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
token := plainToken
|
||||
|
||||
// 记录登录成功日志
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, loginRequest.Username, 1, "login_success", requestData)
|
||||
|
||||
// 更新最后登录时间(ORM会自动更新UpdatedAt)
|
||||
facades.Orm().Query().Save(&admin)
|
||||
|
||||
return response.SuccessWithHeader(ctx, "login_success", "Authorization", "Bearer "+token, http.Json{
|
||||
"token": token,
|
||||
"admin": http.Json{
|
||||
"id": admin.ID,
|
||||
"username": admin.Username,
|
||||
"nickname": admin.Nickname,
|
||||
"avatar": admin.Avatar,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Captcha 获取登录验证码
|
||||
func (r *AuthController) Captcha(ctx http.Context) http.Response {
|
||||
enabled := r.captchaService.Enabled()
|
||||
captchaData := http.Json{
|
||||
"enabled": enabled,
|
||||
}
|
||||
|
||||
if enabled {
|
||||
captchaID, image, err := r.captchaService.Generate()
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "captcha", err)
|
||||
}
|
||||
captchaData["captcha_id"] = captchaID
|
||||
captchaData["captcha_image"] = image
|
||||
// captchaData["captcha_image"] = "data:image/png;base64," + image
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"captcha": captchaData,
|
||||
})
|
||||
}
|
||||
|
||||
// Info 获取当前登录管理员信息
|
||||
func (r *AuthController) Info(ctx http.Context) http.Response {
|
||||
admin, permissions, menus, err := r.authService.GetAdminInfo(ctx)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 获取配置:是否显示无权限的按钮
|
||||
showButtonsWithoutPermission := facades.Config().GetBool("admin.show_buttons_without_permission", false)
|
||||
|
||||
// 检查是否是超级管理员
|
||||
const SuperAdminRoleSlug = "super-admin"
|
||||
isSuperAdmin := false
|
||||
for _, role := range admin.Roles {
|
||||
if role.Slug == SuperAdminRoleSlug && role.Status == 1 {
|
||||
isSuperAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"admin": http.Json{
|
||||
"id": admin.ID,
|
||||
"username": admin.Username,
|
||||
"nickname": admin.Nickname,
|
||||
"avatar": admin.Avatar,
|
||||
"email": admin.Email,
|
||||
"phone": admin.Phone,
|
||||
"department_id": admin.DepartmentID,
|
||||
"department": admin.Department,
|
||||
"roles": admin.Roles,
|
||||
"permissions": permissions,
|
||||
"menus": menus,
|
||||
"is_super_admin": isSuperAdmin,
|
||||
},
|
||||
"config": http.Json{
|
||||
"show_buttons_without_permission": showButtonsWithoutPermission,
|
||||
"monitor_hidden": facades.Config().GetString("admin.monitor_hidden", ""),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProfile 更新个人信息
|
||||
func (r *AuthController) UpdateProfile(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
// 尝试值类型
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
// 尝试指针类型
|
||||
if adminPtr == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 重新查询admin以确保获取最新数据
|
||||
if err := facades.Orm().Query().Where("id", admin.ID).FirstOrFail(&admin); err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrAdminNotFound.Code)
|
||||
}
|
||||
|
||||
nickname := ctx.Request().Input("nickname")
|
||||
email := ctx.Request().Input("email")
|
||||
phone := ctx.Request().Input("phone")
|
||||
avatar := ctx.Request().Input("avatar")
|
||||
|
||||
if nickname != "" {
|
||||
admin.Nickname = nickname
|
||||
}
|
||||
if email != "" {
|
||||
admin.Email = email
|
||||
}
|
||||
if phone != "" {
|
||||
admin.Phone = phone
|
||||
}
|
||||
if avatar != "" {
|
||||
admin.Avatar = avatar
|
||||
}
|
||||
|
||||
if err := facades.Orm().Query().Save(&admin); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
// 重新加载关联数据(确保部门和角色被正确加载)
|
||||
var adminWithRelations models.Admin
|
||||
if err := facades.Orm().Query().With("Department").With("Roles").Where("id", admin.ID).FirstOrFail(&adminWithRelations); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
admin = adminWithRelations
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"admin": http.Json{
|
||||
"id": admin.ID,
|
||||
"username": admin.Username,
|
||||
"nickname": admin.Nickname,
|
||||
"avatar": admin.Avatar,
|
||||
"email": admin.Email,
|
||||
"phone": admin.Phone,
|
||||
"department_id": admin.DepartmentID,
|
||||
"department": admin.Department,
|
||||
"roles": admin.Roles,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh 刷新Token
|
||||
// 注意:此接口需要在JWT中间件之前调用,或者使用特殊的中间件处理
|
||||
// 因为Refresh方法需要token过期但仍在刷新窗口内才能工作
|
||||
func (r *AuthController) Refresh(ctx http.Context) http.Response {
|
||||
// 从请求头获取token
|
||||
token := ctx.Request().Header("Authorization", "")
|
||||
if token == "" {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrUnauthorized.Code)
|
||||
}
|
||||
|
||||
// 移除Bearer前缀
|
||||
token = str.Of(token).ChopStart("Bearer ").Trim().String()
|
||||
|
||||
// 先尝试解析token,如果token有效,直接重新生成(滑动过期)
|
||||
if _, err := facades.Auth(ctx).Guard("admin").Parse(token); err == nil {
|
||||
// Token有效,重新生成新token(延长过期时间)
|
||||
if userID, err := facades.Auth(ctx).Guard("admin").ID(); err == nil {
|
||||
if newToken, err := facades.Auth(ctx).Guard("admin").LoginUsingID(userID); err == nil {
|
||||
return response.SuccessWithHeader(ctx, "token_refresh_success", "Authorization", "Bearer "+newToken, http.Json{
|
||||
"token": newToken,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果token已过期,尝试刷新(需要在刷新窗口内)
|
||||
newToken, err := facades.Auth(ctx).Guard("admin").Refresh()
|
||||
if err != nil {
|
||||
// 刷新失败,返回错误
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrTokenRefreshFailed.Code)
|
||||
}
|
||||
|
||||
// 刷新成功,返回新token
|
||||
return response.SuccessWithHeader(ctx, "token_refresh_success", "Authorization", "Bearer "+newToken, http.Json{
|
||||
"token": newToken,
|
||||
})
|
||||
}
|
||||
|
||||
// Heartbeat 心跳接口,用于更新用户的最后活跃时间
|
||||
// JWT中间件会自动更新 last_used_at,这个接口只是确保用户在线状态
|
||||
func (r *AuthController) Heartbeat(ctx http.Context) http.Response {
|
||||
// JWT中间件已经更新了 last_used_at,这里只需要返回成功即可
|
||||
return response.Success(ctx, "heartbeat_success")
|
||||
}
|
||||
|
||||
// Logout 退出登录
|
||||
func (r *AuthController) Logout(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue != nil {
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
if adminPtr != nil {
|
||||
admin = *adminPtr
|
||||
}
|
||||
}
|
||||
|
||||
if admin.ID > 0 {
|
||||
// 获取token
|
||||
token := ctx.Request().Header("Authorization", "")
|
||||
token = str.Of(token).ChopStart("Bearer ").Trim().String()
|
||||
|
||||
if token != "" {
|
||||
// 删除token
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
_ = tokenService.DeleteToken(token)
|
||||
}
|
||||
|
||||
// 记录退出日志
|
||||
logoutRequestData := r.getLoginRequestData(ctx)
|
||||
r.authService.RecordLoginLog(ctx, admin.ID, admin.Username, 1, "logout_success", logoutRequestData)
|
||||
}
|
||||
}
|
||||
|
||||
return response.Success(ctx, "logout_success")
|
||||
}
|
||||
|
||||
// Tokens 获取当前用户的所有token列表
|
||||
func (r *AuthController) Tokens(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
admin, ok := adminValue.(models.Admin)
|
||||
if !ok {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 获取用户的所有token
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
tokens, err := tokenService.GetTokensByUser("admin", admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前使用的token
|
||||
currentTokenValue := ctx.Value("token")
|
||||
var currentTokenID uint
|
||||
if currentTokenValue != nil {
|
||||
if currentToken, ok := currentTokenValue.(models.PersonalAccessToken); ok {
|
||||
currentTokenID = currentToken.ID
|
||||
} else if currentTokenPtr, ok := currentTokenValue.(*models.PersonalAccessToken); ok {
|
||||
if currentTokenPtr != nil {
|
||||
currentTokenID = currentTokenPtr.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化token列表
|
||||
var tokenList []http.Json
|
||||
for _, token := range tokens {
|
||||
tokenData := http.Json{
|
||||
"id": token.ID,
|
||||
"name": token.Name,
|
||||
"last_used_at": token.LastUsedAt,
|
||||
"expires_at": token.ExpiresAt,
|
||||
"created_at": token.CreatedAt,
|
||||
"is_current": token.ID == currentTokenID,
|
||||
}
|
||||
tokenList = append(tokenList, tokenData)
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"tokens": tokenList,
|
||||
})
|
||||
}
|
||||
|
||||
// RevokeToken 删除指定的token(踢出指定设备)
|
||||
func (r *AuthController) RevokeToken(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
admin, ok := adminValue.(models.Admin)
|
||||
if !ok {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 获取要删除的token ID
|
||||
tokenIDStr := ctx.Request().Route("id")
|
||||
if tokenIDStr == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrTokenIDRequired.Code)
|
||||
}
|
||||
|
||||
tokenID, err := strconv.ParseUint(tokenIDStr, 10, 32)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTokenID.Code)
|
||||
}
|
||||
|
||||
// 查询token是否存在且属于当前用户
|
||||
var token models.PersonalAccessToken
|
||||
if err := facades.Orm().Query().
|
||||
Where("id", tokenID).
|
||||
Where("tokenable_type", "admin").
|
||||
Where("tokenable_id", admin.ID).
|
||||
First(&token); err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrTokenNotFound.Code)
|
||||
}
|
||||
|
||||
// 删除token(直接通过ID删除,因为数据库中存储的是hash值,无法获取原始token)
|
||||
_, err = facades.Orm().Query().Delete(&token)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"token_id": token.ID,
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "revoke_success")
|
||||
}
|
||||
|
||||
// RevokeAllTokens 删除当前用户的所有token(踢出所有设备)
|
||||
func (r *AuthController) RevokeAllTokens(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
admin, ok := adminValue.(models.Admin)
|
||||
if !ok {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 删除用户的所有token
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
if err := tokenService.DeleteTokensByUser("admin", admin.ID); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "revoke_all_success")
|
||||
}
|
||||
|
||||
// KickOutUser 踢出指定用户的所有token(管理员操作)
|
||||
func (r *AuthController) KickOutUser(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 获取要踢出的用户ID
|
||||
userIDStr := ctx.Request().Route("id")
|
||||
if userIDStr == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrUserIDRequired.Code)
|
||||
}
|
||||
|
||||
userID, err := strconv.ParseUint(userIDStr, 10, 32)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidUserID.Code)
|
||||
}
|
||||
|
||||
// 查询用户是否存在
|
||||
var targetAdmin models.Admin
|
||||
if err := facades.Orm().Query().Where("id", userID).FirstOrFail(&targetAdmin); err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrUserNotFound.Code)
|
||||
}
|
||||
|
||||
// 删除用户的所有token
|
||||
tokenService := services.NewTokenServiceImpl()
|
||||
if err := tokenService.DeleteTokensByUser("admin", targetAdmin.ID); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"target_user_id": targetAdmin.ID,
|
||||
"operator_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "kick_out_success")
|
||||
}
|
||||
|
||||
// GetGoogleAuthenticatorQRCode 获取谷歌验证码二维码(用于绑定)
|
||||
func (r *AuthController) GetGoogleAuthenticatorQRCode(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 检查是否已经绑定
|
||||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
if isBound {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleAuthenticatorAlreadyBound.Code)
|
||||
}
|
||||
|
||||
// 生成密钥和二维码
|
||||
accountName := admin.Username
|
||||
if admin.Email != "" {
|
||||
accountName = admin.Email
|
||||
}
|
||||
secret, qrCodeURL, err := r.googleAuthenticatorService.GenerateSecret(accountName)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
// 生成二维码图片
|
||||
qrCodeImage, err := r.googleAuthenticatorService.GenerateQRCodeImage(accountName, secret)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"secret": secret,
|
||||
"qr_code_url": qrCodeURL,
|
||||
"qr_code_image": qrCodeImage,
|
||||
})
|
||||
}
|
||||
|
||||
// BindGoogleAuthenticator 绑定谷歌验证码
|
||||
func (r *AuthController) BindGoogleAuthenticator(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
secret := ctx.Request().Input("secret")
|
||||
code := ctx.Request().Input("code")
|
||||
|
||||
if secret == "" || code == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrSecretAndCodeRequired.Code)
|
||||
}
|
||||
|
||||
// 绑定谷歌验证码
|
||||
if err := r.googleAuthenticatorService.Bind(admin.ID, secret, code); err != nil {
|
||||
if err.Error() == "invalid_code" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||||
}
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "bind_success")
|
||||
}
|
||||
|
||||
// UnbindGoogleAuthenticator 解绑谷歌验证码
|
||||
func (r *AuthController) UnbindGoogleAuthenticator(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 需要验证码确认
|
||||
code := ctx.Request().Input("code")
|
||||
if code == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrCodeRequired.Code)
|
||||
}
|
||||
|
||||
// 获取管理员的密钥
|
||||
secret, err := r.googleAuthenticatorService.GetSecret(admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleAuthenticatorNotBound.Code)
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if !r.googleAuthenticatorService.Verify(secret, code) {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrGoogleCodeInvalid.Code)
|
||||
}
|
||||
|
||||
// 解绑谷歌验证码
|
||||
if err := r.googleAuthenticatorService.Unbind(admin.ID); err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "unbind_success")
|
||||
}
|
||||
|
||||
// GetGoogleAuthenticatorStatus 获取谷歌验证码绑定状态
|
||||
func (r *AuthController) GetGoogleAuthenticatorStatus(ctx http.Context) http.Response {
|
||||
// 从context中获取admin信息(由JWT中间件设置)
|
||||
adminValue := ctx.Value("admin")
|
||||
if adminValue == nil {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
var admin models.Admin
|
||||
if adminVal, ok := adminValue.(models.Admin); ok {
|
||||
admin = adminVal
|
||||
} else if adminPtr, ok := adminValue.(*models.Admin); ok {
|
||||
admin = *adminPtr
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
|
||||
}
|
||||
|
||||
// 检查是否绑定
|
||||
isBound, err := r.googleAuthenticatorService.IsBound(admin.ID)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "auth", err, map[string]any{
|
||||
"admin_id": admin.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"is_bound": isBound,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user