From ada216cfd5ee2865c59e2d6cfea981fb514289a9 Mon Sep 17 00:00:00 2001 From: veypi Date: Wed, 30 Jul 2025 18:08:55 +0800 Subject: [PATCH] feat: change auth check --- api/app/init.go | 3 +- api/init.go | 3 +- api/token/base.go | 2 +- api/token/create.go | 2 +- api/token/get.go | 4 +- api/user/create.go | 2 +- api/user/init.go | 5 +- api/user/login.go | 11 +- api/user/user.go | 67 +---- cfg/cfg.go | 2 + libs/auth/jwt.go | 33 +- ui/env.js | 16 +- ui/page/index.html | 695 +----------------------------------------- ui/page/profile.html | 35 ++- ui/page/stats.html | 696 +++++++++++++++++++++++++++++++++++++++++++ ui/routes.js | 2 +- ui/token.js | 14 + 17 files changed, 800 insertions(+), 792 deletions(-) create mode 100644 ui/page/stats.html diff --git a/api/app/init.go b/api/app/init.go index 30ba67c..1caf969 100644 --- a/api/app/init.go +++ b/api/app/init.go @@ -9,13 +9,14 @@ package app import ( "github.com/veypi/OneAuth/cfg" + "github.com/veypi/OneAuth/libs/auth" "github.com/veypi/OneAuth/models" "github.com/vyes/vigo" "github.com/vyes/vigo/contrib/crud" ) var Router = vigo.NewRouter() -var appRouter = Router.SubRouter(":app_id") +var appRouter = Router.SubRouter(":app_id").UseBefore(auth.Check("app", ":app_id", 2)) func init() { crud.All(appRouter.SubRouter("resource"), cfg.DB, models.Resource{}) diff --git a/api/init.go b/api/init.go index c8f5ff7..b98a768 100644 --- a/api/init.go +++ b/api/init.go @@ -11,11 +11,12 @@ import ( "github.com/veypi/OneAuth/api/app" "github.com/veypi/OneAuth/api/token" "github.com/veypi/OneAuth/api/user" + "github.com/veypi/OneAuth/libs/auth" "github.com/vyes/vigo" "github.com/vyes/vigo/contrib/common" ) -var Router = vigo.NewRouter().UseAfter(common.JsonResponse, common.JsonErrorResponse) +var Router = vigo.NewRouter().UseBefore(auth.CheckJWT).UseAfter(common.JsonResponse, common.JsonErrorResponse) var ( _ = Router.Extend("user", user.Router) diff --git a/api/token/base.go b/api/token/base.go index 042108a..3d46d95 100644 --- a/api/token/base.go +++ b/api/token/base.go @@ -8,7 +8,7 @@ import ( "github.com/vyes/vigo" ) -var _ = Router.Patch("/:token_id", tokenPatch) +// var _ = Router.Patch("/:token_id", tokenPatch) type patchOpts struct { ID string `json:"id" parse:"path@token_id"` diff --git a/api/token/create.go b/api/token/create.go index c1ae401..4a1c742 100644 --- a/api/token/create.go +++ b/api/token/create.go @@ -30,7 +30,7 @@ type postOpts struct { Device *string `json:"device" parse:"json"` } -var _ = Router.Post("/", tokenPost) +var _ = Router.Post("/", vigo.SkipBefore, tokenPost) // for user login app func tokenPost(x *vigo.X) (any, error) { diff --git a/api/token/get.go b/api/token/get.go index d45d405..8f14aa9 100644 --- a/api/token/get.go +++ b/api/token/get.go @@ -17,7 +17,7 @@ type getOpts struct { ID string `json:"id" parse:"path@token_id"` } -var _ = Router.Get("/:token_id", tokenGet) +// var _ = Router.Get("/:token_id", tokenGet) func tokenGet(x *vigo.X) (any, error) { opts := &getOpts{} @@ -36,7 +36,7 @@ type listOpts struct { AppID string `json:"app_id" gorm:"index;type:varchar(32)" parse:"query"` } -var _ = Router.Get("/", tokenList) +// var _ = Router.Get("/", tokenList) func tokenList(x *vigo.X) (any, error) { opts := &listOpts{} diff --git a/api/user/create.go b/api/user/create.go index 954e857..3b5cd45 100644 --- a/api/user/create.go +++ b/api/user/create.go @@ -23,7 +23,7 @@ import ( "gorm.io/gorm" ) -var _ = Router.Post("/", userPost) +var _ = Router.Post("/", vigo.SkipBefore, publicLimits, userPost) type postOpts struct { Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"` diff --git a/api/user/init.go b/api/user/init.go index 6b3414c..74e278a 100644 --- a/api/user/init.go +++ b/api/user/init.go @@ -9,14 +9,15 @@ package user import ( "github.com/veypi/OneAuth/cfg" + "github.com/veypi/OneAuth/libs/auth" "github.com/veypi/OneAuth/models" "github.com/vyes/vigo" "github.com/vyes/vigo/contrib/crud" ) var Router = vigo.NewRouter() +var userRouter = Router.SubRouter("/:user_id").UseBefore(userGet) func init() { - crud.All(Router.SubRouter("/:user_id/user_role"), cfg.DB, models.UserRole{}) - + crud.All(Router.SubRouter("/:user_id/user_role"), cfg.DB, models.UserRole{}).UseBefore(auth.Check("user", "", 4)) } diff --git a/api/user/login.go b/api/user/login.go index eddfe60..e8f241b 100644 --- a/api/user/login.go +++ b/api/user/login.go @@ -17,10 +17,19 @@ import ( "github.com/veypi/OneAuth/libs/utils" "github.com/veypi/OneAuth/models" "github.com/vyes/vigo" + "github.com/vyes/vigo/contrib/limiter" "github.com/vyes/vigo/logv" ) -var _ = Router.Post("/login", userLogin) +var publicLimits = limiter.NewAdvancedRequestLimiter(limiter.LimiterConfig{ + Window: time.Minute * 5, + MaxRequests: 20, + MinInterval: time.Second * 3, +}).Limit + +var _ = Router.Post("/login", + vigo.SkipBefore, publicLimits, + userLogin) type loginOpts struct { UserName string `json:"username" parse:"json"` diff --git a/api/user/user.go b/api/user/user.go index 5585de5..b92be1f 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -8,7 +8,7 @@ import ( "github.com/vyes/vigo/contrib/crud" ) -var _ = Router.Delete("/:user_id", auth.Check("user", "user_id", auth.DoDelete), userDelete) +var _ = Router.Delete("/:user_id", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete) func userDelete(x *vigo.X) (any, error) { data := &models.User{} @@ -16,65 +16,22 @@ func userDelete(x *vigo.X) (any, error) { return data, err } -var _ = Router.Get("/:user_id", auth.Check("user", "user_id", auth.DoRead), userGet) +var _ = Router.Get("/:user_id", auth.Check("user", "user_id", auth.DoRead, checkOwner), vigo.DiliverData) -type getOpts struct { - ID string `json:"id" parse:"path@user_id"` +func checkOwner(x *vigo.X, data any) bool { + u, ok1 := data.(*models.User) + return ok1 && u.ID == x.Params.Get("user_id") } func userGet(x *vigo.X) (any, error) { - opts := &getOpts{} - err := x.Parse(opts) - if err != nil { - return nil, err - } data := &models.User{} - - err = cfg.DB().Where("id = ?", opts.ID).First(data).Error - + err := cfg.DB().Where("id = ?", x.Params.Get("user_id")).First(data).Error return data, err } -var _ = Router.Get("/", "list user", listOpts{}, auth.Check("user", "", auth.DoUpdate), crud.List(cfg.DB, &models.User{})) - -type listOpts struct { - Username *string `json:"username" parse:"query"` - Nickname *string `json:"nickname" parse:"query"` - Email *string `json:"email" parse:"query"` - Phone *string `json:"phone" parse:"query"` - Status *uint `json:"status" parse:"query"` -} +var _ = Router.Get("/", "list user", auth.Check("user", "", auth.DoUpdate), crud.List(cfg.DB, &models.User{})) -func userList(x *vigo.X) (any, error) { - opts := &listOpts{} - err := x.Parse(opts) - if err != nil { - return nil, err - } - data := make([]*models.User, 0, 10) - - query := cfg.DB() - if opts.Username != nil { - query = query.Where("username LIKE ?", opts.Username) - } - if opts.Nickname != nil { - query = query.Where("nickname LIKE ?", opts.Nickname) - } - if opts.Email != nil { - query = query.Where("email LIKE ?", opts.Email) - } - if opts.Phone != nil { - query = query.Where("phone LIKE ?", opts.Phone) - } - if opts.Status != nil { - query = query.Where("status = ?", opts.Status) - } - err = query.Find(&data).Error - - return data, err -} - -var _ = Router.Patch("/:user_id", auth.Check("user", "user_id", auth.DoUpdate), userPatch) +var _ = Router.Patch("/:user_id", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch) type patchOpts struct { ID string `json:"id" parse:"path@user_id"` @@ -87,18 +44,14 @@ type patchOpts struct { Status *uint `json:"status" parse:"json"` } -func userPatch(x *vigo.X) (any, error) { +func userPatch(x *vigo.X, args any) (any, error) { opts := &patchOpts{} err := x.Parse(opts) if err != nil { return nil, err } - data := &models.User{} + data := args.(*models.User) - err = cfg.DB().Where("id = ?", opts.ID).First(data).Error - if err != nil { - return nil, err - } optsMap := make(map[string]any) if opts.Username != nil { optsMap["username"] = opts.Username diff --git a/cfg/cfg.go b/cfg/cfg.go index ac88881..1a31546 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -21,4 +21,6 @@ var Config = &Options{ TokenExpire: time.Second * 10, ID: "test", Key: "asdfghjklqwertyuiopzxcvbnm1234567890", + DSN: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local", + DB: "mysql", } diff --git a/libs/auth/jwt.go b/libs/auth/jwt.go index 7136439..9d02ed9 100644 --- a/libs/auth/jwt.go +++ b/libs/auth/jwt.go @@ -86,20 +86,33 @@ func CheckJWT(x *vigo.X) (any, error) { return checkJWT(x) } -func Check(target string, pid string, l AuthLevel) func(x *vigo.X) (any, error) { - return func(x *vigo.X) (any, error) { - claims, err := checkJWT(x) - if err != nil { - return nil, err - // return nil, err +type CustomCheckFunc = func(x *vigo.X, data any) bool + +func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) func(x *vigo.X, data any) (any, error) { + return func(x *vigo.X, data any) (any, error) { + var err error + claims, ok := x.Get("token").(*Claims) + if !ok { + claims, err = checkJWT(x) + if err != nil { + return nil, err + } } tid := "" - if pid != "" { - tid = x.Params.Get(pid) + if strings.HasPrefix(pid, "@") { + tid, _ = x.Get(pid[1:]).(string) + } + if strings.HasPrefix(pid, ":") { + tid = x.Params.Get(pid[1:]) } if !claims.Access.Check(target, tid, l) { - return nil, AuthNoPerm + err = AuthNoPerm + } + for _, fn := range funcs { + if fn(x, data) { + return data, nil + } } - return claims, nil + return data, err } } diff --git a/ui/env.js b/ui/env.js index 79cc7b6..8066815 100644 --- a/ui/env.js +++ b/ui/env.js @@ -6,7 +6,8 @@ export default ($env) => { token.setBaseUrl($env.root) token.wrapAxios($env.$axios) $env.$G.token = token - $env.$G.user = token.body() + let user = token.body() + $env.$G.user = user $env.$router.addRoutes(routes) @@ -18,17 +19,18 @@ export default ($env) => { if (token.isExpired()) { token.logout(to.fullPath) } + if (!token.check('app', '', 2)) { + next('/') + } } else { next(); } }; $env.$axios.interceptors.response.use(function(response) { - if (response?.data) { - return response.data - } - return response; + return response?.data || response; }, function(error) { - let data = error.response ? error.response.data : error.response - return Promise.reject(data.message || data); + console.error('Axios Error:', error); + error = error?.response?.data || error?.response || error + return Promise.reject(error.message || error); }); } diff --git a/ui/page/index.html b/ui/page/index.html index cf41483..5ac2f5b 100644 --- a/ui/page/index.html +++ b/ui/page/index.html @@ -1,696 +1,9 @@ - + - - 项目与用户管理 Dashboard - - - -
-
-

项目与用户管理 Dashboard

-
{{ currentDate }}
-
- -
-
-
- -
-
{{ totalUsers }}
-
总用户数
-
- {{ userGrowthRate }}% 较上月 -
-
- -
-
- -
-
{{ activeProjects }}
-
活跃项目
-
- {{ projectGrowthRate }}% 较上月 -
-
- -
-
- -
-
{{ pendingTasks }}
-
待处理任务
-
- {{ taskChangeRate }}% 较上周 -
-
- -
-
- -
-
{{ completionRate }}%
-
项目完成率
-
- {{ completionRateChange }}% 较上月 -
-
-
- -
-
-

用户增长趋势

-
-
- -
-

项目状态分布

-
-
-
- -
-
-

用户活跃度

-
-
- -
-

任务完成情况

-
-
-
- -
-

最近活动

-
-
- -
-
-
- {{ activity.title }} - {{ activity.typeLabel }} -
-
{{ activity.time }}
-
-
-
-
- - - - + diff --git a/ui/page/profile.html b/ui/page/profile.html index 0cc80f8..1ab756a 100644 --- a/ui/page/profile.html +++ b/ui/page/profile.html @@ -508,23 +508,26 @@ // 加载用户数据 loadUserData = async () => { - try { - isLoading = true - const response = await $axios.get("/api/user/" + user.id) - if (response) { - user = { - id: response.id, - username: response.username || "", - nickname: response.nickname || "", - icon: response.icon || "", - email: response.email || "", - phone: response.phone || "", - status: response.status || 0 - } - // 保存原始数据 - originalUser = JSON.parse(JSON.stringify(user)) - hasChanges = false + isLoading = true + const response = await $axios.get("/api/user/" + user.id).catch(error => { + console.log(error) + }) + console.log(response) + if (response) { + user = { + id: response.id, + username: response.username || "", + nickname: response.nickname || "", + icon: response.icon || "", + email: response.email || "", + phone: response.phone || "", + status: response.status || 0 } + // 保存原始数据 + originalUser = JSON.parse(JSON.stringify(user)) + hasChanges = false + } + try { } catch (error) { showError("加载用户数据失败: " + error.message) } finally { diff --git a/ui/page/stats.html b/ui/page/stats.html new file mode 100644 index 0000000..cf41483 --- /dev/null +++ b/ui/page/stats.html @@ -0,0 +1,696 @@ + + + + + 项目与用户管理 Dashboard + + + + + +
+
+

项目与用户管理 Dashboard

+
{{ currentDate }}
+
+ +
+
+
+ +
+
{{ totalUsers }}
+
总用户数
+
+ {{ userGrowthRate }}% 较上月 +
+
+ +
+
+ +
+
{{ activeProjects }}
+
活跃项目
+
+ {{ projectGrowthRate }}% 较上月 +
+
+ +
+
+ +
+
{{ pendingTasks }}
+
待处理任务
+
+ {{ taskChangeRate }}% 较上周 +
+
+ +
+
+ +
+
{{ completionRate }}%
+
项目完成率
+
+ {{ completionRateChange }}% 较上月 +
+
+
+ +
+
+

用户增长趋势

+
+
+ +
+

项目状态分布

+
+
+
+ +
+
+

用户活跃度

+
+
+ +
+

任务完成情况

+
+
+
+ +
+

最近活动

+
+
+ +
+
+
+ {{ activity.title }} + {{ activity.typeLabel }} +
+
{{ activity.time }}
+
+
+
+
+ + + + diff --git a/ui/routes.js b/ui/routes.js index 1af7207..c99e213 100644 --- a/ui/routes.js +++ b/ui/routes.js @@ -6,7 +6,7 @@ */ const routes = [ - { path: '/', component: '/page/index.html', name: 'home', layout: 'default', meta: { auth: true } }, + { path: '/', component: '/page/index.html', name: 'home', layout: 'public' }, { path: '/login', component: '/page/login.html', name: 'login', meta: { auth: false } }, { path: '/profile', component: '/page/profile.html', layout: 'default', meta: { auth: true } }, { path: '/app', component: '/page/app.html', name: 'app', layout: 'default', meta: { auth: true } }, diff --git a/ui/token.js b/ui/token.js index 1c1bf2d..c1e2f06 100644 --- a/ui/token.js +++ b/ui/token.js @@ -11,6 +11,20 @@ class TokenService { this.tokenKey = 'access'; this.refreshTokenKey = 'refresh'; } + + check(domain, id, level) { + let body = this.body(); + if (!body || !body.access) { + return false; + } + for (let a of body.access) { + if (a.name === domain && (a.tid === '' || a.tid === id) && a.level >= level) { + return true + } + } + return false + } + setBaseUrl(url) { this.#url = url; }