fix: replace session handler with jwt authorization

This commit is contained in:
RockYang 2023-09-05 11:47:03 +08:00
parent 8d4fdaf902
commit c38035e25e
21 changed files with 217 additions and 205 deletions

View File

@ -7,16 +7,16 @@ import (
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"context" "context"
"github.com/gin-contrib/sessions" "fmt"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-contrib/sessions/redis"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"gorm.io/gorm" "gorm.io/gorm"
"io" "io"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"strings" "strings"
"time"
) )
type AppServer struct { type AppServer struct {
@ -53,14 +53,13 @@ func NewServer(appConfig *types.AppConfig, functions map[string]function.Functio
} }
} }
func (s *AppServer) Init(debug bool) { func (s *AppServer) Init(debug bool, client *redis.Client) {
if debug { // 调试模式允许跨域请求 API if debug { // 调试模式允许跨域请求 API
s.Debug = debug s.Debug = debug
logger.Info("Enabled debug mode") logger.Info("Enabled debug mode")
} }
s.Engine.Use(corsMiddleware()) s.Engine.Use(corsMiddleware())
s.Engine.Use(sessionMiddleware(s.Config)) s.Engine.Use(authorizeMiddleware(s, client))
s.Engine.Use(authorizeMiddleware(s))
s.Engine.Use(errorHandler) s.Engine.Use(errorHandler)
// 添加静态资源访问 // 添加静态资源访问
s.Engine.Static("/static", s.Config.StaticDir) s.Engine.Static("/static", s.Config.StaticDir)
@ -105,42 +104,6 @@ func errorHandler(c *gin.Context) {
c.Next() c.Next()
} }
// 会话处理
func sessionMiddleware(config *types.AppConfig) gin.HandlerFunc {
// encrypt the cookie
var store sessions.Store
var err error
switch config.Session.Driver {
case types.SessionDriverMem:
store = memstore.NewStore([]byte(config.Session.SecretKey))
break
case types.SessionDriverRedis:
store, err = redis.NewStore(10, "tcp", config.Redis.Url(), config.Redis.Password, []byte(config.Session.SecretKey))
if err != nil {
logger.Fatal(err)
}
break
case types.SessionDriverCookie:
store = cookie.NewStore([]byte(config.Session.SecretKey))
break
default:
config.Session.Driver = types.SessionDriverCookie
store = cookie.NewStore([]byte(config.Session.SecretKey))
}
logger.Info("Session driver: ", config.Session.Driver)
store.Options(sessions.Options{
Path: config.Session.Path,
Domain: config.Session.Domain,
MaxAge: config.Session.MaxAge,
Secure: config.Session.Secure,
HttpOnly: config.Session.HttpOnly,
SameSite: config.Session.SameSite,
})
return sessions.Sessions(config.Session.Name, store)
}
// 跨域中间件设置 // 跨域中间件设置
func corsMiddleware() gin.HandlerFunc { func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
@ -151,7 +114,7 @@ func corsMiddleware() gin.HandlerFunc {
c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
//允许跨域设置可以返回其他子段,可以自定义字段 //允许跨域设置可以返回其他子段,可以自定义字段
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, Chat-Token") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, Chat-Token, Admin-Authorization")
// 允许浏览器(客户端)可以解析的头部 (重要) // 允许浏览器(客户端)可以解析的头部 (重要)
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
//设置缓存时间 //设置缓存时间
@ -175,7 +138,7 @@ func corsMiddleware() gin.HandlerFunc {
} }
// 用户授权验证 // 用户授权验证
func authorizeMiddleware(s *AppServer) gin.HandlerFunc { func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
if c.Request.URL.Path == "/api/user/login" || if c.Request.URL.Path == "/api/user/login" ||
c.Request.URL.Path == "/api/admin/login" || c.Request.URL.Path == "/api/admin/login" ||
@ -190,29 +153,54 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc {
return return
} }
// WebSocket 连接请求验证 var tokenString string
if c.Request.URL.Path == "/api/chat" { if strings.Contains(c.Request.URL.Path, "/api/admin/") { // 后台管理 API
sessionId := c.Query("sessionId") tokenString = c.GetHeader(types.AdminAuthHeader)
session := s.ChatSession.Get(sessionId) } else if c.Request.URL.Path == "/api/chat/new" {
if session.ClientIP == c.ClientIP() { tokenString = c.Query("token")
c.Next()
} else { } else {
c.Abort() tokenString = c.GetHeader(types.UserAuthHeader)
} }
if tokenString == "" {
resp.ERROR(c, "You should put Authorization in request headers")
c.Abort()
return return
} }
session := sessions.Default(c)
var value interface{} token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if strings.Contains(c.Request.URL.Path, "/api/admin/") { // 后台管理 API if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
value = session.Get(types.SessionAdmin) return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
} else {
value = session.Get(types.SessionUser)
} }
if value != nil {
c.Next() return []byte(s.Config.Session.SecretKey), nil
} else { })
resp.NotAuth(c)
if err != nil {
resp.NotAuth(c, fmt.Sprintf("Error with parse auth token: %v", err))
c.Abort() c.Abort()
} return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "Token is invalid")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "Token is expired")
c.Abort()
return
}
key := fmt.Sprintf("users/%v", claims["user_id"])
if _, err := client.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "Token is not found in redis")
c.Abort()
return
}
c.Set(types.LoginUserID, claims["user_id"])
} }
} }

View File

@ -5,7 +5,6 @@ import (
"chatplus/core/types" "chatplus/core/types"
logger2 "chatplus/logger" logger2 "chatplus/logger"
"chatplus/utils" "chatplus/utils"
"net/http"
"os" "os"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
@ -23,15 +22,8 @@ func NewDefaultConfig() *types.AppConfig {
Redis: types.RedisConfig{Host: "localhost", Port: 6379, Password: ""}, Redis: types.RedisConfig{Host: "localhost", Port: 6379, Password: ""},
AesEncryptKey: utils.RandString(24), AesEncryptKey: utils.RandString(24),
Session: types.Session{ Session: types.Session{
Driver: types.SessionDriverCookie,
SecretKey: utils.RandString(64), SecretKey: utils.RandString(64),
Name: "CHAT_PLUS_SESSION",
Domain: "",
Path: "/",
MaxAge: 86400, MaxAge: 86400,
Secure: true,
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
}, },
ApiConfig: types.ChatPlusApiConfig{}, ApiConfig: types.ChatPlusApiConfig{},
ExtConfig: types.ChatPlusExtConfig{Token: utils.RandString(32)}, ExtConfig: types.ChatPlusExtConfig{Token: utils.RandString(32)},

View File

@ -2,7 +2,6 @@ package types
import ( import (
"fmt" "fmt"
"net/http"
) )
type AppConfig struct { type AppConfig struct {
@ -85,19 +84,6 @@ const (
SessionDriverCookie = SessionDriver("cookie") SessionDriverCookie = SessionDriver("cookie")
) )
// Session configs struct
type Session struct {
Driver SessionDriver // session 存储驱动 mem|cookie|redis
SecretKey string // session encryption key
Name string
Path string
Domain string
MaxAge int
Secure bool
HttpOnly bool
SameSite http.SameSite
}
// ChatConfig 系统默认的聊天配置 // ChatConfig 系统默认的聊天配置
type ChatConfig struct { type ChatConfig struct {
OpenAI ModelAPIConfig `json:"open_ai"` OpenAI ModelAPIConfig `json:"open_ai"`

View File

@ -1,6 +1,14 @@
package types package types
const SessionName = "ChatGPT-TOKEN" const LoginUserID = "LOGIN_USER_ID"
const SessionUser = "SESSION_USER" // 存储用户信息的 session key const LoginUserCache = "LOGIN_USER_CACHE"
const SessionAdmin = "SESSION_ADMIN" //存储管理员信息的 session key
const LoginUserCache = "LOGIN_USER_CACHE" // 已登录用户缓存 const UserAuthHeader = "Authorization"
const AdminAuthHeader = "Admin-Authorization"
const ChatTokenHeader = "Chat-Token"
// Session configs struct
type Session struct {
SecretKey string
MaxAge int
}

View File

@ -8,9 +8,11 @@ import (
"chatplus/store/model" "chatplus/store/model"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"context"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"strings" "strings"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
@ -21,10 +23,11 @@ var logger = logger2.GetLogger()
type ManagerHandler struct { type ManagerHandler struct {
handler.BaseHandler handler.BaseHandler
db *gorm.DB db *gorm.DB
redis *redis.Client
} }
func NewAdminHandler(app *core.AppServer, db *gorm.DB) *ManagerHandler { func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client) *ManagerHandler {
h := ManagerHandler{db: db} h := ManagerHandler{db: db, redis: client}
h.App = app h.App = app
return &h return &h
} }
@ -38,13 +41,22 @@ func (h *ManagerHandler) Login(c *gin.Context) {
} }
manager := h.App.Config.Manager manager := h.App.Config.Manager
if data.Username == manager.Username && data.Password == manager.Password { if data.Username == manager.Username && data.Password == manager.Password {
err := utils.SetLoginAdmin(c, manager) // 创建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": manager.Username,
"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)),
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
if err != nil { if err != nil {
resp.ERROR(c, "Save session failed") resp.ERROR(c, "Failed to generate token, "+err.Error())
return return
} }
manager.Password = "" // 清空密码] // 保存到 redis
resp.SUCCESS(c, manager) if _, err := h.redis.Set(context.Background(), "users/"+manager.Username, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
resp.SUCCESS(c, tokenString)
} else { } else {
resp.ERROR(c, "用户名或者密码错误") resp.ERROR(c, "用户名或者密码错误")
} }
@ -52,11 +64,9 @@ func (h *ManagerHandler) Login(c *gin.Context) {
// Logout 注销 // Logout 注销
func (h *ManagerHandler) Logout(c *gin.Context) { func (h *ManagerHandler) Logout(c *gin.Context) {
session := sessions.Default(c) token := c.GetHeader(types.AdminAuthHeader)
session.Delete(types.SessionAdmin) if _, err := h.redis.Del(c, token).Result(); err != nil {
err := session.Save() logger.Error("error with delete session: ", err)
if err != nil {
resp.ERROR(c, "Save session failed")
} else { } else {
resp.SUCCESS(c) resp.SUCCESS(c)
} }
@ -64,9 +74,8 @@ func (h *ManagerHandler) Logout(c *gin.Context) {
// Session 会话检测 // Session 会话检测
func (h *ManagerHandler) Session(c *gin.Context) { func (h *ManagerHandler) Session(c *gin.Context) {
session := sessions.Default(c) token := c.GetHeader(types.AdminAuthHeader)
admin := session.Get(types.SessionAdmin) if token == "" {
if admin == nil {
resp.NotAuth(c) resp.NotAuth(c)
} else { } else {
resp.SUCCESS(c) resp.SUCCESS(c)

View File

@ -3,7 +3,7 @@ package handler
import ( import (
"chatplus/core" "chatplus/core"
logger2 "chatplus/logger" logger2 "chatplus/logger"
"strconv" "chatplus/utils"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -20,47 +20,23 @@ func (h *BaseHandler) GetTrim(c *gin.Context, key string) string {
} }
func (h *BaseHandler) PostInt(c *gin.Context, key string, defaultValue int) int { func (h *BaseHandler) PostInt(c *gin.Context, key string, defaultValue int) int {
return intValue(c.PostForm(key), defaultValue) return utils.IntValue(c.PostForm(key), defaultValue)
} }
func (h *BaseHandler) GetInt(c *gin.Context, key string, defaultValue int) int { func (h *BaseHandler) GetInt(c *gin.Context, key string, defaultValue int) int {
return intValue(c.Query(key), defaultValue) return utils.IntValue(c.Query(key), defaultValue)
}
func intValue(str string, defaultValue int) int {
value, err := strconv.Atoi(str)
if err != nil {
return defaultValue
}
return value
} }
func (h *BaseHandler) GetFloat(c *gin.Context, key string) float64 { func (h *BaseHandler) GetFloat(c *gin.Context, key string) float64 {
return floatValue(c.Query(key)) return utils.FloatValue(c.Query(key))
} }
func (h *BaseHandler) PostFloat(c *gin.Context, key string) float64 { func (h *BaseHandler) PostFloat(c *gin.Context, key string) float64 {
return floatValue(c.PostForm(key)) return utils.FloatValue(c.PostForm(key))
}
func floatValue(str string) float64 {
value, err := strconv.ParseFloat(str, 64)
if err != nil {
return 0
}
return value
} }
func (h *BaseHandler) GetBool(c *gin.Context, key string) bool { func (h *BaseHandler) GetBool(c *gin.Context, key string) bool {
return boolValue(c.Query(key)) return utils.BoolValue(c.Query(key))
} }
func (h *BaseHandler) PostBool(c *gin.Context, key string) bool { func (h *BaseHandler) PostBool(c *gin.Context, key string) bool {
return boolValue(c.PostForm(key)) return utils.BoolValue(c.PostForm(key))
}
func boolValue(str string) bool {
value, err := strconv.ParseBool(str)
if err != nil {
return false
}
return value
} }

View File

@ -9,10 +9,12 @@ import (
"chatplus/store/vo" "chatplus/store/vo"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"strings" "strings"
"time" "time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lionsoul2014/ip2region/binding/golang/xdb" "github.com/lionsoul2014/ip2region/binding/golang/xdb"
"gorm.io/gorm" "gorm.io/gorm"
@ -23,6 +25,7 @@ type UserHandler struct {
db *gorm.DB db *gorm.DB
searcher *xdb.Searcher searcher *xdb.Searcher
leveldb *store.LevelDB leveldb *store.LevelDB
redis *redis.Client
uploadManager *oss.UploaderManager uploadManager *oss.UploaderManager
} }
@ -31,8 +34,9 @@ func NewUserHandler(
db *gorm.DB, db *gorm.DB,
searcher *xdb.Searcher, searcher *xdb.Searcher,
levelDB *store.LevelDB, levelDB *store.LevelDB,
client *redis.Client,
manager *oss.UploaderManager) *UserHandler { manager *oss.UploaderManager) *UserHandler {
handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB, uploadManager: manager} handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB, redis: client, uploadManager: manager}
handler.App = app handler.App = app
return handler return handler
} }
@ -122,7 +126,7 @@ func (h *UserHandler) Register(c *gin.Context) {
// Login 用户登录 // Login 用户登录
func (h *UserHandler) Login(c *gin.Context) { func (h *UserHandler) Login(c *gin.Context) {
var data struct { var data struct {
Username string `json:"mobile"` Mobile string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} }
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
@ -130,7 +134,7 @@ func (h *UserHandler) Login(c *gin.Context) {
return return
} }
var user model.User var user model.User
res := h.db.Where("mobile = ?", data.Username).First(&user) res := h.db.Where("mobile = ?", data.Mobile).First(&user)
if res.Error != nil { if res.Error != nil {
resp.ERROR(c, "用户名不存在") resp.ERROR(c, "用户名不存在")
return return
@ -152,13 +156,6 @@ func (h *UserHandler) Login(c *gin.Context) {
user.LastLoginAt = time.Now().Unix() user.LastLoginAt = time.Now().Unix()
h.db.Model(&user).Updates(user) h.db.Model(&user).Updates(user)
err := utils.SetLoginUser(c, user)
if err != nil {
resp.ERROR(c, "保存会话失败")
logger.Error("Error for save session: ", err)
return
}
h.db.Create(&model.UserLoginLog{ h.db.Create(&model.UserLoginLog{
UserId: user.Id, UserId: user.Id,
Username: user.Mobile, Username: user.Mobile,
@ -166,17 +163,32 @@ func (h *UserHandler) Login(c *gin.Context) {
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()), LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
}) })
resp.SUCCESS(c) // 创建 token
expired := time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge))
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
"expired": expired,
})
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
if err != nil {
resp.ERROR(c, "Failed to generate token, "+err.Error())
return
}
// 保存到 redis
key := fmt.Sprintf("users/%d", user.Id)
if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
resp.ERROR(c, "error with save token: "+err.Error())
return
}
resp.SUCCESS(c, tokenString)
} }
// Logout 注 销 // Logout 注 销
func (h *UserHandler) Logout(c *gin.Context) { func (h *UserHandler) Logout(c *gin.Context) {
sessionId := c.GetHeader(types.SessionName) sessionId := c.GetHeader(types.ChatTokenHeader)
session := sessions.Default(c) token := c.GetHeader(types.UserAuthHeader)
session.Delete(types.SessionUser) if _, err := h.redis.Del(c, token).Result(); err != nil {
err := session.Save() logger.Error("error with delete session: ", err)
if err != nil {
logger.Error("Error for save session: ", err)
} }
// 删除 websocket 会话列表 // 删除 websocket 会话列表
h.App.ChatSession.Delete(sessionId) h.App.ChatSession.Delete(sessionId)

View File

@ -12,6 +12,7 @@ import (
"chatplus/store" "chatplus/store"
"context" "context"
"embed" "embed"
"github.com/go-redis/redis/v8"
"io" "io"
"log" "log"
"os" "os"
@ -81,8 +82,8 @@ func main() {
// 创建应用服务 // 创建应用服务
fx.Provide(core.NewServer), fx.Provide(core.NewServer),
// 初始化 // 初始化
fx.Invoke(func(s *core.AppServer) { fx.Invoke(func(s *core.AppServer, client *redis.Client) {
s.Init(debug) s.Init(debug, client)
}), }),
// 初始化数据库 // 初始化数据库

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strconv"
"strings" "strings"
"github.com/lionsoul2014/ip2region/binding/golang/xdb" "github.com/lionsoul2014/ip2region/binding/golang/xdb"
@ -113,3 +114,27 @@ func IsEmptyValue(obj interface{}) bool {
return reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface()) return reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface())
} }
} }
func BoolValue(str string) bool {
value, err := strconv.ParseBool(str)
if err != nil {
return false
}
return value
}
func FloatValue(str string) float64 {
value, err := strconv.ParseFloat(str, 64)
if err != nil {
return 0
}
return value
}
func IntValue(str string, defaultValue int) int {
value, err := strconv.Atoi(str)
if err != nil {
return defaultValue
}
return value
}

View File

@ -27,6 +27,10 @@ func HACKER(c *gin.Context) {
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"}) c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
} }
func NotAuth(c *gin.Context) { func NotAuth(c *gin.Context, messages ...string) {
if messages != nil {
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
} else {
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"}) c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
} }
}

View File

@ -5,33 +5,18 @@ import (
"chatplus/store/model" "chatplus/store/model"
"errors" "errors"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
) )
func SetLoginUser(c *gin.Context, user model.User) error {
session := sessions.Default(c)
session.Set(types.SessionUser, user.Id)
// TODO: 后期用户数量增加,考虑将用户数据存储到 leveldb避免每次查询数据库
return session.Save()
}
func SetLoginAdmin(c *gin.Context, admin types.Manager) error {
session := sessions.Default(c)
session.Set(types.SessionAdmin, admin.Username)
return session.Save()
}
func GetLoginUser(c *gin.Context, db *gorm.DB) (model.User, error) { func GetLoginUser(c *gin.Context, db *gorm.DB) (model.User, error) {
value, exists := c.Get(types.LoginUserCache) value, exists := c.Get(types.LoginUserCache)
if exists { if exists {
return value.(model.User), nil return value.(model.User), nil
} }
session := sessions.Default(c) userId, ok := c.Get(types.LoginUserID)
userId := session.Get(types.SessionUser) if !ok {
if userId == nil {
return model.User{}, errors.New("user not login") return model.User{}, errors.New("user not login")
} }

View File

@ -1,7 +1,8 @@
VUE_APP_API_HOST=http://localhost:5678 VUE_APP_API_HOST=http://localhost:5678
VUE_APP_WS_HOST=ws://localhost:5678 VUE_APP_WS_HOST=ws://localhost:5678
VUE_APP_USER=geekmaster VUE_APP_USER=18575670125
VUE_APP_PASS=12345678 VUE_APP_PASS=12345678
VUE_APP_ADMIN_USER=admin VUE_APP_ADMIN_USER=admin
VUE_APP_ADMIN_PASS=admin123 VUE_APP_ADMIN_PASS=admin123
VUE_APP_KEY_PREFIX=ChatPLUS_
VUE_APP_TITLE="ChatGPT-PLUS V3" VUE_APP_TITLE="ChatGPT-PLUS V3"

View File

@ -1,7 +1,4 @@
VUE_APP_API_HOST= VUE_APP_API_HOST=
VUE_APP_WS_HOST= VUE_APP_WS_HOST=
VUE_APP_USER= VUE_APP_KEY_PREFIX=ChatPLUS_
VUE_APP_PASS=
VUE_APP_ADMIN_USER=
VUE_APP_ADMIN_PASS=
VUE_APP_TITLE="ChatGPT-PLUS V3" VUE_APP_TITLE="ChatGPT-PLUS V3"

View File

@ -1,6 +1,6 @@
import Storage from 'good-storage' import Storage from 'good-storage'
const CHAT_CONFIG_KEY = "chat_config" const CHAT_CONFIG_KEY = process.env.VUE_APP_KEY_PREFIX + "chat_config"
export function getChatConfig() { export function getChatConfig() {
return Storage.get(CHAT_CONFIG_KEY) return Storage.get(CHAT_CONFIG_KEY)

View File

@ -1,15 +1,16 @@
/* eslint-disable no-constant-condition */
import {randString} from "@/utils/libs"; import {randString} from "@/utils/libs";
import Storage from "good-storage";
/** /**
* storage handler * storage handler
*/ */
const SessionIDKey = 'SESSION_ID'; const SessionIDKey = process.env.VUE_APP_KEY_PREFIX + 'SESSION_ID';
const UserTokenKey = process.env.VUE_APP_KEY_PREFIX + "Authorization";
const AdminTokenKey = process.env.VUE_APP_KEY_PREFIX + "Admin-Authorization"
export function getSessionId() { export function getSessionId() {
let sessionId = sessionStorage.getItem(SessionIDKey) let sessionId = Storage.get(SessionIDKey)
if (!sessionId) { if (!sessionId) {
sessionId = randString(42) sessionId = randString(42)
setSessionId(sessionId) setSessionId(sessionId)
@ -17,10 +18,34 @@ export function getSessionId() {
return sessionId return sessionId
} }
export function removeLoginUser() { export function removeSessionId() {
sessionStorage.removeItem(SessionIDKey) Storage.remove(SessionIDKey)
} }
export function setSessionId(sessionId) { export function setSessionId(sessionId) {
sessionStorage.setItem(SessionIDKey, sessionId) Storage.set(SessionIDKey, sessionId)
}
export function getUserToken() {
return Storage.get(UserTokenKey)
}
export function setUserToken(token) {
Storage.set(UserTokenKey, token)
}
export function removeUserToken() {
Storage.remove(UserTokenKey)
}
export function getAdminToken() {
return Storage.get(AdminTokenKey)
}
export function setAdminToken(token) {
Storage.set(AdminTokenKey, token)
}
export function removeAdminToken() {
Storage.remove(AdminTokenKey)
} }

View File

@ -1,6 +1,6 @@
import Storage from "good-storage"; import Storage from "good-storage";
const MOBILE_THEME = "MOBILE_THEME" const MOBILE_THEME = process.env.VUE_APP_KEY_PREFIX + "MOBILE_THEME"
export function getMobileTheme() { export function getMobileTheme() {
return Storage.get(MOBILE_THEME) ? Storage.get(MOBILE_THEME) : 'light' return Storage.get(MOBILE_THEME) ? Storage.get(MOBILE_THEME) : 'light'

View File

@ -1,5 +1,5 @@
import axios from 'axios' import axios from 'axios'
import {getSessionId} from "@/store/session"; import {getAdminToken, getSessionId, getUserToken} from "@/store/session";
axios.defaults.timeout = 10000 axios.defaults.timeout = 10000
axios.defaults.baseURL = process.env.VUE_APP_API_HOST axios.defaults.baseURL = process.env.VUE_APP_API_HOST
@ -11,6 +11,8 @@ axios.interceptors.request.use(
config => { config => {
// set token // set token
config.headers['Chat-Token'] = getSessionId(); config.headers['Chat-Token'] = getSessionId();
config.headers['Authorization'] = getUserToken();
config.headers['Admin-Authorization'] = getAdminToken();
return config return config
}, error => { }, error => {
return Promise.reject(error) return Promise.reject(error)

View File

@ -271,7 +271,7 @@ import 'highlight.js/styles/a11y-dark.css'
import {dateFormat, isMobile, randString, removeArrayItem, renderInputText, UUID} from "@/utils/libs"; import {dateFormat, isMobile, randString, removeArrayItem, renderInputText, UUID} from "@/utils/libs";
import {ElMessage, ElMessageBox} from "element-plus"; import {ElMessage, ElMessageBox} from "element-plus";
import hl from "highlight.js"; import hl from "highlight.js";
import {getSessionId, removeLoginUser} from "@/store/session"; import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
@ -319,9 +319,6 @@ onMounted(() => {
checkSession().then((user) => { checkSession().then((user) => {
loginUser.value = user loginUser.value = user
isLogin.value = true isLogin.value = true
if (user.chat_config?.model !== '') {
modelID.value = user.chat_config.model
}
// //
httpGet(`/api/role/list?user_id=${user.id}`).then((res) => { httpGet(`/api/role/list?user_id=${user.id}`).then((res) => {
roles.value = res.data; roles.value = res.data;
@ -400,7 +397,7 @@ const newChat = function () {
chat_id: "", chat_id: "",
icon: icon, icon: icon,
role_id: roleId.value, role_id: roleId.value,
model: modelID.value, model_id: modelID.value,
title: '', title: '',
edit: false, edit: false,
removing: false, removing: false,
@ -419,7 +416,7 @@ const changeChat = function (chat) {
activeChat.value = chat activeChat.value = chat
newChatItem.value = null; newChatItem.value = null;
roleId.value = chat.role_id; roleId.value = chat.role_id;
modelID.value = chat.model; modelID.value = chat.model_id;
showStopGenerate.value = false; showStopGenerate.value = false;
showReGenerate.value = false; showReGenerate.value = false;
connect(chat.chat_id, chat.role_id) connect(chat.chat_id, chat.role_id)
@ -510,7 +507,7 @@ const connect = function (chat_id, role_id) {
host = 'ws://' + location.host; host = 'ws://' + location.host;
} }
} }
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}`); const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
_socket.addEventListener('open', () => { _socket.addEventListener('open', () => {
chatData.value = []; // chatData.value = []; //
previousText.value = ''; previousText.value = '';
@ -740,7 +737,7 @@ const clearAllChats = function () {
const logout = function () { const logout = function () {
activelyClose.value = true; activelyClose.value = true;
httpGet('/api/user/logout').then(() => { httpGet('/api/user/logout').then(() => {
removeLoginUser(); removeUserToken();
router.push('login'); router.push('login');
}).catch(() => { }).catch(() => {
ElMessage.error('注销失败!'); ElMessage.error('注销失败!');

View File

@ -56,6 +56,7 @@ import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue"; import FooterBar from "@/components/FooterBar.vue";
import {isMobile} from "@/utils/libs"; import {isMobile} from "@/utils/libs";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import {setUserToken} from "@/store/session";
const router = useRouter(); const router = useRouter();
const title = ref('ChatGPT-PLUS 用户登录'); const title = ref('ChatGPT-PLUS 用户登录');
@ -87,7 +88,8 @@ const login = function () {
return ElMessage.error('请输入密码'); return ElMessage.error('请输入密码');
} }
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then(() => { httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
setUserToken(res.data)
if (isMobile()) { if (isMobile()) {
router.push('/mobile') router.push('/mobile')
} else { } else {

View File

@ -46,6 +46,7 @@ import {httpPost} from "@/utils/http";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue"; import FooterBar from "@/components/FooterBar.vue";
import {setAdminToken} from "@/store/session";
const router = useRouter(); const router = useRouter();
const title = ref('ChatGPT Plus Admin'); const title = ref('ChatGPT Plus Admin');
@ -68,7 +69,8 @@ const login = function () {
return ElMessage.error('请输入密码'); return ElMessage.error('请输入密码');
} }
httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => { httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then(res => {
setAdminToken(res.data)
router.push("/admin") router.push("/admin")
}).catch((e) => { }).catch((e) => {
ElMessage.error('登录失败,' + e.message) ElMessage.error('登录失败,' + e.message)

View File

@ -200,7 +200,7 @@ const connect = function (chat_id, role_id) {
host = 'ws://' + location.host; host = 'ws://' + location.host;
} }
} }
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model}`); const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model}&token=${getUserToken()}`);
_socket.addEventListener('open', () => { _socket.addEventListener('open', () => {
loading.value = false loading.value = false
previousText.value = ''; previousText.value = '';