mirror of https://github.com/veypi/OneAuth.git
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			259 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
		
		
			
		
	
	
			259 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
| 
											7 months ago
										 | package token | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"encoding/hex" | ||
|  | 	"encoding/json" | ||
|  | 	"net/http" | ||
|  | 	"oa/cfg" | ||
|  | 	"oa/errs" | ||
|  | 	"oa/libs/auth" | ||
|  | 	M "oa/models" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"github.com/golang-jwt/jwt/v5" | ||
|  | 	"github.com/veypi/OneBD/rest" | ||
|  | 	"github.com/veypi/utils" | ||
|  | 	"github.com/veypi/utils/logv" | ||
|  | ) | ||
|  | 
 | ||
|  | func useToken(r rest.Router) { | ||
|  | 	r.Post("/salt", tokenSalt) | ||
|  | 	r.Post("/", tokenPost) | ||
|  | 	r.Get("/:token_id", tokenGet) | ||
|  | 	r.Patch("/:token_id", tokenPatch) | ||
|  | 	r.Delete("/:token_id", tokenDelete) | ||
|  | 	r.Get("/", tokenList) | ||
|  | } | ||
|  | func tokenSalt(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenSalt{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	logv.Warn().Msg(opts.Username) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	data := &M.User{} | ||
|  | 	query := "username = ?" | ||
|  | 	if opts.Typ == nil { | ||
|  | 	} else if *opts.Typ == "email" { | ||
|  | 		query = "email = ?" | ||
|  | 	} else if *opts.Typ == "phone" { | ||
|  | 		query = "phone = ?" | ||
|  | 	} | ||
|  | 
 | ||
|  | 	err = cfg.DB().Where(query, opts.Username).First(data).Error | ||
|  | 	return map[string]string{"salt": data.Salt, "id": data.ID}, err | ||
|  | } | ||
|  | 
 | ||
|  | // for user login app
 | ||
|  | func tokenPost(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenPost{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	aid := cfg.Config.ID | ||
|  | 	if opts.AppID != nil && *opts.AppID != "" { | ||
|  | 		aid = *opts.AppID | ||
|  | 	} | ||
|  | 	data := &M.Token{} | ||
|  | 	claim := &auth.Claims{} | ||
|  | 	claim.IssuedAt = jwt.NewNumericDate(time.Now()) | ||
|  | 	claim.Issuer = cfg.Config.ID | ||
|  | 	if opts.Refresh != nil { | ||
|  | 		typ := "app" | ||
|  | 		if opts.Typ != nil { | ||
|  | 			typ = *opts.Typ | ||
|  | 		} | ||
|  | 		// for other app redirect
 | ||
|  | 		refresh, err := auth.ParseJwt(*opts.Refresh) | ||
|  | 		if err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 		if refresh.ID == "" { | ||
|  | 			return nil, errs.AuthInvalid | ||
|  | 		} | ||
|  | 		err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error | ||
|  | 		if err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 		claim.AID = aid | ||
|  | 		claim.UID = refresh.UID | ||
|  | 		claim.Name = refresh.Name | ||
|  | 		claim.Icon = refresh.Icon | ||
|  | 		claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10)) | ||
|  | 		if typ == "app" { | ||
|  | 			if refresh.AID == aid { | ||
|  | 				// refresh token
 | ||
|  | 				acList := make(auth.Access, 0, 10) | ||
|  | 				logv.AssertError(cfg.DB().Table("accesses a"). | ||
|  | 					Select("a.name, a.t_id, a.level"). | ||
|  | 					Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ? AND a.app_id = ?", refresh.UID, aid). | ||
|  | 					Scan(&acList).Error) | ||
|  | 				claim.Access = acList | ||
|  | 				if aid == cfg.Config.ID { | ||
|  | 					return auth.GenJwt(claim) | ||
|  | 				} | ||
|  | 				app := &M.App{} | ||
|  | 				err = cfg.DB().Where("id = ?", aid).First(app).Error | ||
|  | 				if err != nil { | ||
|  | 					return nil, err | ||
|  | 				} | ||
|  | 				return auth.GenJwtWithKey(claim, app.Key) | ||
|  | 			} else if aid != cfg.Config.ID { | ||
|  | 				// 只能生成其他应用的refresh token
 | ||
|  | 				newToken := &M.Token{} | ||
|  | 				newToken.UserID = refresh.UID | ||
|  | 				newToken.AppID = aid | ||
|  | 				newToken.ExpiredAt = time.Now().Add(time.Hour * 24) | ||
|  | 				if opts.OverPerm != nil { | ||
|  | 					newToken.OverPerm = *opts.OverPerm | ||
|  | 				} | ||
|  | 				if opts.Device != nil { | ||
|  | 					newToken.Device = *opts.Device | ||
|  | 				} | ||
|  | 				newToken.Ip = x.GetRemoteIp() | ||
|  | 				logv.AssertError(cfg.DB().Create(newToken).Error) | ||
|  | 				// gen other app token
 | ||
|  | 				claim.ID = newToken.ID | ||
|  | 				claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt) | ||
|  | 				return auth.GenJwt(claim) | ||
|  | 			} else { | ||
|  | 				// 其他应用获取访问oa的token
 | ||
|  | 				claim.Access = make(auth.Access, 0, 10) | ||
|  | 				if data.OverPerm != "" { | ||
|  | 					err = json.Unmarshal([]byte(data.OverPerm), &claim.Access) | ||
|  | 					if err != nil { | ||
|  | 						return nil, err | ||
|  | 					} | ||
|  | 				} | ||
|  | 				return auth.GenJwt(claim) | ||
|  | 			} | ||
|  | 		} else if typ == "ufs" { | ||
|  | 			claim.AID = refresh.AID | ||
|  | 			claim.UID = refresh.UID | ||
|  | 			claim.Name = refresh.Name | ||
|  | 			claim.Icon = refresh.Icon | ||
|  | 			claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10)) | ||
|  | 			claim.Access = auth.Access{ | ||
|  | 				{Name: "fs", TID: "/", Level: auth.Do}, | ||
|  | 			} | ||
|  | 			token := logv.AssertFuncErr(auth.GenJwt(claim)) | ||
|  | 
 | ||
|  | 			cookie := &http.Cookie{ | ||
|  | 				Name:     "fstoken", // Cookie 的名称
 | ||
|  | 				Value:    token,     // Cookie 的值
 | ||
|  | 				Path:     "/fs/u/",  // Cookie 的路径,通常是根路径
 | ||
|  | 				MaxAge:   600,       // Cookie 的最大年龄,单位是秒
 | ||
|  | 				HttpOnly: true,      // 是否仅限 HTTP(S) 访问
 | ||
|  | 				Secure:   false,     // 是否通过安全连接传输 Cookie
 | ||
|  | 			} | ||
|  | 			http.SetCookie(x, cookie) | ||
|  | 
 | ||
|  | 			return token, nil | ||
|  | 		} else { | ||
|  | 			return nil, errs.ArgsInvalid | ||
|  | 		} | ||
|  | 	} else if opts.Code != nil && aid == cfg.Config.ID && opts.Salt != nil && opts.UserID != nil { | ||
|  | 		// for oa login
 | ||
|  | 		user := &M.User{} | ||
|  | 		err = cfg.DB().Where("id = ?", opts.UserID).Find(user).Error | ||
|  | 		if err != nil { | ||
|  | 			return nil, err | ||
|  | 		} | ||
|  | 		logv.Info().Str("user", user.ID).Msg("login") | ||
|  | 		code := *opts.Code | ||
|  | 		salt := logv.AssertFuncErr(hex.DecodeString(*opts.Salt)) | ||
|  | 		key := logv.AssertFuncErr(hex.DecodeString(user.Code)) | ||
|  | 		de, err := utils.AesDecrypt([]byte(code), key, salt) | ||
|  | 		if err != nil || de != user.ID { | ||
|  | 			return nil, errs.AuthFailed | ||
|  | 		} | ||
|  | 		data.UserID = *opts.UserID | ||
|  | 		data.AppID = aid | ||
|  | 		data.ExpiredAt = time.Now().Add(time.Hour * 72) | ||
|  | 		if opts.OverPerm != nil { | ||
|  | 			data.OverPerm = *opts.OverPerm | ||
|  | 		} | ||
|  | 		if opts.Device != nil { | ||
|  | 			data.Device = *opts.Device | ||
|  | 		} | ||
|  | 		data.Ip = x.GetRemoteIp() | ||
|  | 		logv.AssertError(cfg.DB().Create(data).Error) | ||
|  | 		claim.ID = data.ID | ||
|  | 		claim.AID = aid | ||
|  | 		claim.UID = user.ID | ||
|  | 		claim.Name = user.Username | ||
|  | 		claim.Icon = user.Icon | ||
|  | 		claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt) | ||
|  | 		if user.Nickname != "" { | ||
|  | 			claim.Name = user.Nickname | ||
|  | 		} | ||
|  | 		return auth.GenJwt(claim) | ||
|  | 	} else { | ||
|  | 		return nil, errs.ArgsInvalid | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func tokenGet(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenGet{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	data := &M.Token{} | ||
|  | 
 | ||
|  | 	err = cfg.DB().Where("id = ?", opts.ID).First(data).Error | ||
|  | 
 | ||
|  | 	return data, err | ||
|  | } | ||
|  | func tokenPatch(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenPatch{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	data := &M.Token{} | ||
|  | 
 | ||
|  | 	err = cfg.DB().Where("id = ?", opts.ID).First(data).Error | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	optsMap := make(map[string]interface{}) | ||
|  | 	if opts.ExpiredAt != nil { | ||
|  | 		optsMap["expired_at"] = opts.ExpiredAt | ||
|  | 	} | ||
|  | 	if opts.OverPerm != nil { | ||
|  | 		optsMap["over_perm"] = opts.OverPerm | ||
|  | 	} | ||
|  | 	err = cfg.DB().Model(data).Updates(optsMap).Error | ||
|  | 
 | ||
|  | 	return data, err | ||
|  | } | ||
|  | func tokenDelete(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenDelete{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	data := &M.Token{} | ||
|  | 
 | ||
|  | 	err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error | ||
|  | 
 | ||
|  | 	return data, err | ||
|  | } | ||
|  | func tokenList(x *rest.X) (any, error) { | ||
|  | 	opts := &M.TokenList{} | ||
|  | 	err := x.Parse(opts) | ||
|  | 	if err != nil { | ||
|  | 		return nil, err | ||
|  | 	} | ||
|  | 	data := make([]*M.Token, 0, 10) | ||
|  | 
 | ||
|  | 	query := cfg.DB() | ||
|  | 	query = query.Where("user_id = ?", opts.UserID) | ||
|  | 	query = query.Where("app_id = ?", opts.AppID) | ||
|  | 	err = query.Limit(opts.Limit).Find(&data).Error | ||
|  | 
 | ||
|  | 	return data, err | ||
|  | } |