| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- package fwh_pro
- import (
- "encoding/json"
- "fmt"
- "fwh-toolbox-server/utils/httpClient"
- "strings"
- "time"
- "github.com/google/uuid"
- )
- var loginInfo LoginData = LoginData{
- UserId: 0,
- Token: "",
- }
- type LoginPayload struct {
- LoginName string `json:"loginName"`
- Password string `json:"password"`
- Type int `json:"type"`
- Timezone string `json:"timezone"`
- Lang string `json:"lang"`
- }
- type LoginData struct {
- UserId uint `json:"userId"`
- Token string `json:"token"`
- MfaRequired bool `json:"mfaRequired"`
- MfaToken string `json:"mfaToken"`
- MfaMethod string `json:"mfaMethod"`
- AvailableMfaMethods []string `json:"availableMfaMethods"`
- }
- type MfaVerifyPayload struct {
- MfaToken string `json:"mfaToken"`
- Code string `json:"code"`
- Method string `json:"method"`
- DeviceFingerprint string `json:"deviceFingerprint"`
- RememberDevice string `json:"rememberDevice"`
- Lang string `json:"lang"`
- }
- type LoginResponse struct {
- Code int `json:"code"`
- Msg string `json:"msg"`
- Data LoginData `json:"data"`
- Success bool `json:"success"`
- }
- type JwtPayload struct {
- AdminLevel int `json:"adminLevel"`
- LoginName string `json:"loginName"`
- AccountType int `json:"accountType"`
- GroupId int `json:"groupId"`
- Iss string `json:"iss"`
- UserManage int `json:"userManage"`
- Exp uint64 `json:"exp"`
- UserName string `json:"userName"`
- Iat uint64 `json:"iat"`
- UserId int `json:"userId"`
- }
- func Login() error {
- return loginWithPost(postFwhLoginRequest, time.Now, uuid.NewString())
- }
- type loginPostFunc func(path string, body any, clientID string) (*httpClient.Response, error)
- func loginWithPost(post loginPostFunc, now func() time.Time, clientID string) error {
- // A failed re-login must not leave a previously issued token available for reuse.
- ClearToken()
- payload := LoginPayload{
- LoginName: fwhUser,
- Password: fwhPassword,
- Type: 1,
- Timezone: "Asia/Shanghai",
- Lang: "ZH_CN",
- }
- response, err := post("/api-user/user/login", payload, clientID)
- if err != nil {
- return err
- }
- var loginRes LoginResponse
- if err := json.Unmarshal(response.Body, &loginRes); err != nil {
- return err
- }
- if response.StatusCode != 200 || !loginRes.Success || loginRes.Code != 200 {
- return fmt.Errorf("FWH login rejected (HTTP %d, code %d)", response.StatusCode, loginRes.Code)
- }
- if loginRes.Data.MfaRequired {
- if fwhTotpSecret == "" {
- return fmt.Errorf("FWH login requires TOTP; set FWH_TOTP_SECRET")
- }
- if loginRes.Data.MfaToken == "" {
- return fmt.Errorf("FWH login requires TOTP but returned no mfaToken")
- }
- if !supportsTOTP(loginRes.Data) {
- return fmt.Errorf("FWH login requires an unsupported MFA method")
- }
- code, err := generateTOTP(fwhTotpSecret, now())
- if err != nil {
- return err
- }
- verify := MfaVerifyPayload{
- MfaToken: loginRes.Data.MfaToken,
- Code: code,
- Method: "TOTP",
- DeviceFingerprint: clientID,
- RememberDevice: "",
- Lang: "ZH_CN",
- }
- response, err = post("/api-user/mfa/login/verify", verify, clientID)
- if err != nil {
- return err
- }
- loginRes = LoginResponse{}
- if err := json.Unmarshal(response.Body, &loginRes); err != nil {
- return err
- }
- if response.StatusCode != 200 || !loginRes.Success || loginRes.Code != 200 {
- return fmt.Errorf("FWH TOTP verification rejected (HTTP %d, code %d)", response.StatusCode, loginRes.Code)
- }
- }
- if loginRes.Data.Token == "" {
- return fmt.Errorf("FWH login returned no token")
- }
- loginInfo.UserId = loginRes.Data.UserId
- loginInfo.Token = loginRes.Data.Token
- return nil
- }
- func supportsTOTP(data LoginData) bool {
- if strings.EqualFold(data.MfaMethod, "TOTP") {
- return true
- }
- for _, method := range data.AvailableMfaMethods {
- if strings.EqualFold(method, "TOTP") {
- return true
- }
- }
- return false
- }
- // 弃用:token payload被加密
- // func isTokenExpired(token string) (bool, error) {
- // parts := strings.Split(token, ".")
- // if len(parts) != 3 {
- // return false, fmt.Errorf("token 格式错误")
- // }
- // payloadSegment := parts[1]
- // // JWT 使用 base64 URL encoding
- // payloadBytes, err := base64.RawURLEncoding.DecodeString(payloadSegment)
- // if err != nil {
- // return false, err
- // }
- // var payload map[string]interface{}
- // if err := json.Unmarshal(payloadBytes, &payload); err != nil {
- // zap.L().Error("token payload解析失败", zap.String("payload", string(payloadBytes)))
- // return false, err
- // }
- // expFloat, ok := payload["exp"].(float64)
- // if !ok {
- // return false, fmt.Errorf("token payload 中没有 exp")
- // }
- // expTime := time.Unix(int64(expFloat), 0)
- // return time.Now().After(expTime), nil
- // }
- func getLoginToken() string {
- // 判断是否已经有token
- if loginInfo.Token != "" {
- // 判断token是否过期
- // token.payload加密,无法判断过期,改为判断401
- // expired, err := isTokenExpired(loginInfo.Token)
- // if err != nil {
- // zap.L().Error("loginInfo.Token 解析出错.", zap.String("token", loginInfo.Token), zap.String("err", err.Error()))
- // return ""
- // }
- // if expired {
- // zap.L().Info("fwh Token 已过期")
- // } else {
- // Token 有效
- return loginInfo.Token
- // }
- }
- // 重新登录
- Login()
- return loginInfo.Token
- }
- // 弃用:token payload被加密
- // func getUserId(token string) (int, error) {
- // // JWT 由三部分组成: header.payload.signature
- // parts := strings.Split(token, ".")
- // if len(parts) < 2 {
- // return 0, fmt.Errorf("getUserId: invalid token")
- // }
- // // 解码 payload 部分
- // payload, err := base64.RawURLEncoding.DecodeString(parts[1])
- // if err != nil {
- // return 0, fmt.Errorf("getUserId: %s", err.Error())
- // }
- // // 解析 JSON
- // var claims JwtPayload
- // if err := json.Unmarshal(payload, &claims); err != nil {
- // return 0, fmt.Errorf("getUserId: %s", err.Error())
- // }
- // return claims.UserId, nil
- // }
- func getUserId() uint {
- return loginInfo.UserId
- }
- // 清空token
- func ClearToken() {
- loginInfo.UserId = 0
- loginInfo.Token = ""
- }
- // 过期重新登录 - 提供给httpClient
- func reLogin() string {
- // 重新登录
- Login()
- return loginInfo.Token
- }
|