From 3ea549953245924ab97b0b33677d736df96d8561 Mon Sep 17 00:00:00 2001 From: veypi Date: Sun, 15 Mar 2026 05:48:12 +0800 Subject: [PATCH] refactor(auth): Migrate to new vigo auth.Auth and Provider pattern - Rename appAuth to vbaseProvider implementing auth.Provider interface - Replace auth.VBaseAuth with cfg.Auth (auth.Auth struct) for middleware - Add global cfg.Auth instance with SetProvider injection in init.go - Update all API handlers to use cfg.Auth.RequireXxx instead of PermXxx - Update tests to use cfg.Auth for permission checks - Remove Login/Perm methods from Provider (now in auth.Auth struct) --- api/auth/login.go | 8 +- api/auth/me.go | 7 +- api/auth/register.go | 3 +- api/auth/thirdparty.go | 9 +- api/init.go | 7 +- api/oauth/authorize.go | 3 +- api/oauth/client.go | 11 ++- api/oauth/init.go | 12 +-- api/oauth/oidc.go | 3 +- api/oauth/providers/init.go | 14 ++-- api/role/init.go | 16 ++-- api/settings/init.go | 6 +- api/user/create.go | 3 +- api/user/get.go | 5 +- api/user/init.go | 22 ++--- api/user/patch.go | 5 +- api/verification/send.go | 4 +- auth/auth.go | 155 ++++++++--------------------------- cfg/cfg.go | 4 + init.go | 13 +++ tests/oauth_client_test.go | 5 +- tests/oauth_security_test.go | 8 +- tests/role_access_test.go | 6 +- tests/scoped_auth_test.go | 4 +- 24 files changed, 129 insertions(+), 204 deletions(-) diff --git a/api/auth/login.go b/api/auth/login.go index 6594ecd..98d5d49 100644 --- a/api/auth/login.go +++ b/api/auth/login.go @@ -15,6 +15,7 @@ import ( "github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/models" "github.com/veypi/vigo" + "github.com/veypi/vigo/contrib/requestmeta" ) // LoginRequest 登录请求 @@ -191,8 +192,9 @@ func login(x *vigo.X, req *LoginRequest) (*AuthResponse, error) { TokenID: getJTI(tokenPair.AccessToken), Type: "access", DeviceInfo: x.Request.UserAgent(), - IP: x.GetRemoteIP(), - ExpiresAt: time.Now().Add(cfg.Global.JWT.AccessExpiry), + + IP: requestmeta.RemoteIP(x), + ExpiresAt: time.Now().Add(cfg.Global.JWT.AccessExpiry), } cfg.DB().Create(session) @@ -268,7 +270,7 @@ func refresh(x *vigo.X, req *RefreshRequest) (*AuthResponse, error) { TokenID: getJTI(tokenPair.AccessToken), Type: "access", DeviceInfo: x.Request.UserAgent(), - IP: x.GetRemoteIP(), + IP: requestmeta.RemoteIP(x), ExpiresAt: time.Now().Add(cfg.Global.JWT.AccessExpiry), } cfg.DB().Create(session) diff --git a/api/auth/me.go b/api/auth/me.go index d4cc8cf..d4f454a 100644 --- a/api/auth/me.go +++ b/api/auth/me.go @@ -7,7 +7,6 @@ package auth import ( - baseAuth "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/models" @@ -35,7 +34,7 @@ type UserInfoWithPerms struct { // me 获取当前用户信息 func me(x *vigo.X) (*UserInfoWithPerms, error) { - userID := baseAuth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized } @@ -96,7 +95,7 @@ type UpdateMeRequest struct { // updateMe 更新当前用户信息 func updateMe(x *vigo.X, req *UpdateMeRequest) (*UserInfoWithPerms, error) { - userID := baseAuth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized } @@ -136,7 +135,7 @@ type ChangePasswordRequest struct { // changePassword 修改密码 func changePassword(x *vigo.X, req *ChangePasswordRequest) error { - userID := baseAuth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return vigo.ErrUnauthorized } diff --git a/api/auth/register.go b/api/auth/register.go index 06a5bae..22f19bd 100644 --- a/api/auth/register.go +++ b/api/auth/register.go @@ -13,7 +13,6 @@ import ( "strings" "time" - baseauth "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/libs/jwt" @@ -151,7 +150,7 @@ func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) { roleCode = "admin" } - if err := baseauth.VBaseAuth.GrantRole(x.Context(), user.ID, roleCode); err != nil { + if err := cfg.Auth.GrantRole(x.Context(), user.ID, roleCode); err != nil { // 记录错误但允许注册继续,或者回滚 // 这里简单处理,继续流程,用户可能需要管理员手动授权 // 或者返回错误 diff --git a/api/auth/thirdparty.go b/api/auth/thirdparty.go index 5f819a2..5718140 100644 --- a/api/auth/thirdparty.go +++ b/api/auth/thirdparty.go @@ -16,7 +16,6 @@ import ( "strings" "time" - baseauth "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/libs/crypto" @@ -83,7 +82,7 @@ func authorizeThirdParty(x *vigo.X, req *AuthorizeRequest) (*AuthorizeResponse, // 如果是绑定模式,需要当前用户登录 if req.BindMode != nil && *req.BindMode { - userID := baseauth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized.WithString("login required for bind mode") } @@ -301,7 +300,7 @@ func bindWithRegister(x *vigo.X, req *BindWithRegisterRequest) (*AuthResponse, e } // 授予默认角色 "user" - if err := baseauth.VBaseAuth.GrantRole(x.Context(), user.ID, "user"); err != nil { + if err := cfg.Auth.GrantRole(x.Context(), user.ID, "user"); err != nil { // 记录错误但允许流程继续 } @@ -321,7 +320,7 @@ type UnbindRequest struct { // unbindThirdParty 解除第三方账号绑定 func unbindThirdParty(x *vigo.X, req *UnbindRequest) error { - userID := baseauth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return vigo.ErrUnauthorized } @@ -345,7 +344,7 @@ type BindingInfo struct { // listBindings 获取当前用户的第三方绑定列表 func listBindings(x *vigo.X) ([]BindingInfo, error) { - userID := baseauth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized } diff --git a/api/init.go b/api/init.go index 3bf8f4c..cb55bf7 100644 --- a/api/init.go +++ b/api/init.go @@ -13,7 +13,6 @@ import ( "github.com/veypi/vbase/api/settings" "github.com/veypi/vbase/api/user" "github.com/veypi/vbase/api/verification" - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/models" "github.com/veypi/vigo" @@ -47,12 +46,12 @@ type OAuthProviderInfo struct { func init() { // 注册全局中间件 - Router.Use(auth.VBaseAuth.Login()) + Router.Use(cfg.Auth.Login()) Router.After(common.JsonResponse, common.JsonErrorResponse) // 初始化角色 - auth.VBaseAuth.AddRole("admin", "管理员", "*:7") - auth.VBaseAuth.AddRole("user", "普通用户") + cfg.Auth.AddRole("admin", "管理员", "*:7") + cfg.Auth.AddRole("user", "普通用户") // 子路由挂载 Router.Extend("/auth", apiAuth.Router) diff --git a/api/oauth/authorize.go b/api/oauth/authorize.go index 5ef451f..0ca5783 100644 --- a/api/oauth/authorize.go +++ b/api/oauth/authorize.go @@ -7,7 +7,6 @@ package oauth import ( "time" - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/libs/crypto" @@ -40,7 +39,7 @@ func authorize(x *vigo.X, req *AuthorizeRequest) (*AuthorizeResponse, error) { } // 获取当前用户 - userID := auth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized } diff --git a/api/oauth/client.go b/api/oauth/client.go index 1d0fe15..799e9d3 100644 --- a/api/oauth/client.go +++ b/api/oauth/client.go @@ -5,7 +5,6 @@ package oauth import ( - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/models" @@ -66,7 +65,7 @@ type CreateClientResponse struct { } func createClient(x *vigo.X, req *CreateClientRequest) (*CreateClientResponse, error) { - ownerID := auth.VBaseAuth.UserID(x) + ownerID := cfg.Auth.UserID(x) if ownerID == "" { return nil, vigo.ErrUnauthorized } @@ -123,7 +122,7 @@ func updateClient(x *vigo.X, req *UpdateClientRequest) (*models.OAuthClient, err } // 检查权限:只有所有者或管理员可以修改 - currentUserID := auth.VBaseAuth.UserID(x) + currentUserID := cfg.Auth.UserID(x) if currentUserID == "" { return nil, vigo.ErrUnauthorized } @@ -132,7 +131,7 @@ func updateClient(x *vigo.X, req *UpdateClientRequest) (*models.OAuthClient, err isOwner := client.OwnerID == currentUserID // 检查是否是管理员(拥有 *:* 权限) - isAdmin := auth.VBaseAuth.Check(x.Context(), currentUserID, "*:*", auth.LevelAdmin) + isAdmin := cfg.Auth.Check(x.Context(), currentUserID, "*:*", 7) if !isOwner && !isAdmin { return nil, vigo.ErrForbidden.WithString("not the owner of this client") @@ -170,7 +169,7 @@ func deleteClient(x *vigo.X, req *DeleteClientRequest) error { } // 检查权限:只有所有者或管理员可以删除 - currentUserID := auth.VBaseAuth.UserID(x) + currentUserID := cfg.Auth.UserID(x) if currentUserID == "" { return vigo.ErrUnauthorized } @@ -179,7 +178,7 @@ func deleteClient(x *vigo.X, req *DeleteClientRequest) error { isOwner := client.OwnerID == currentUserID // 检查是否是管理员(拥有 *:* 权限) - isAdmin := auth.VBaseAuth.Check(x.Context(), currentUserID, "*:*", auth.LevelAdmin) + isAdmin := cfg.Auth.Check(x.Context(), currentUserID, "*:*", 7) if !isOwner && !isAdmin { return vigo.ErrForbidden.WithString("not the owner of this client") diff --git a/api/oauth/init.go b/api/oauth/init.go index 8108716..9a2e6a8 100644 --- a/api/oauth/init.go +++ b/api/oauth/init.go @@ -6,7 +6,7 @@ package oauth import ( "github.com/veypi/vbase/api/oauth/providers" - "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" "github.com/veypi/vigo" ) @@ -22,11 +22,11 @@ func init() { // === OAuth 客户端管理(需要认证)=== clientRouter := Router.SubRouter("/clients") - clientRouter.Get("/", "OAuth客户端列表", auth.VBaseAuth.PermRead("oauth-client:*"), listClients) - clientRouter.Post("/", "创建OAuth客户端", auth.VBaseAuth.PermCreate("oauth-client"), createClient) - clientRouter.Get("/{client_id}", "获取客户端详情", auth.VBaseAuth.PermRead("oauth-client:*"), getClient) - clientRouter.Patch("/{client_id}", "更新OAuth客户端", auth.VBaseAuth.PermWrite("oauth-client:*"), updateClient) - clientRouter.Delete("/{client_id}", "删除OAuth客户端", auth.VBaseAuth.PermWrite("oauth-client:*"), deleteClient) + clientRouter.Get("/", "OAuth客户端列表", cfg.Auth.RequireRead("oauth-client:*"), listClients) + clientRouter.Post("/", "创建OAuth客户端", cfg.Auth.RequireCreate("oauth-client"), createClient) + clientRouter.Get("/{client_id}", "获取客户端详情", cfg.Auth.RequireRead("oauth-client:*"), getClient) + clientRouter.Patch("/{client_id}", "更新OAuth客户端", cfg.Auth.RequireWrite("oauth-client:*"), updateClient) + clientRouter.Delete("/{client_id}", "删除OAuth客户端", cfg.Auth.RequireWrite("oauth-client:*"), deleteClient) // === OAuth 提供商管理 === Router.Extend("/providers", providers.Router) diff --git a/api/oauth/oidc.go b/api/oauth/oidc.go index 3e39cf4..d3e5d3c 100644 --- a/api/oauth/oidc.go +++ b/api/oauth/oidc.go @@ -5,7 +5,6 @@ package oauth import ( - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/models" "github.com/veypi/vigo" @@ -14,7 +13,7 @@ import ( // UserInfo OIDC用户信息 func userInfo(x *vigo.X) (map[string]any, error) { // 从token中解析用户ID - userID := auth.VBaseAuth.UserID(x) + userID := cfg.Auth.UserID(x) if userID == "" { return nil, vigo.ErrUnauthorized } diff --git a/api/oauth/providers/init.go b/api/oauth/providers/init.go index 828ea81..8153fa5 100644 --- a/api/oauth/providers/init.go +++ b/api/oauth/providers/init.go @@ -7,7 +7,7 @@ package providers import ( - "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" "github.com/veypi/vigo" ) @@ -15,15 +15,15 @@ var Router = vigo.NewRouter() func init() { // 获取所有提供商(包括禁用的,管理员用) - Router.Get("/", "获取 OAuth 提供商列表", auth.VBaseAuth.PermRead("oauth-provider:*"), list) + Router.Get("/", "获取 OAuth 提供商列表", cfg.Auth.RequireRead("oauth-provider:*"), list) // 获取单个提供商详情 - Router.Get("/{code}", "获取 OAuth 提供商详情", auth.VBaseAuth.PermRead("oauth-provider:*"), get) + Router.Get("/{code}", "获取 OAuth 提供商详情", cfg.Auth.RequireRead("oauth-provider:*"), get) // 创建新提供商 - Router.Post("/", "创建 OAuth 提供商", auth.VBaseAuth.PermCreate("oauth-provider"), create) + Router.Post("/", "创建 OAuth 提供商", cfg.Auth.RequireCreate("oauth-provider"), create) // 更新提供商 - Router.Patch("/{code}", "更新 OAuth 提供商", auth.VBaseAuth.PermWrite("oauth-provider:*"), update) + Router.Patch("/{code}", "更新 OAuth 提供商", cfg.Auth.RequireWrite("oauth-provider:*"), update) // 删除提供商(仅非内置) - Router.Delete("/{code}", "删除 OAuth 提供商", auth.VBaseAuth.PermWrite("oauth-provider:*"), del) + Router.Delete("/{code}", "删除 OAuth 提供商", cfg.Auth.RequireWrite("oauth-provider:*"), del) // 获取内置模板 - Router.Get("/templates", "获取内置 OAuth 模板", auth.VBaseAuth.PermRead("oauth-provider:*"), templates) + Router.Get("/templates", "获取内置 OAuth 模板", cfg.Auth.RequireRead("oauth-provider:*"), templates) } diff --git a/api/role/init.go b/api/role/init.go index 71eba71..a04e9c8 100644 --- a/api/role/init.go +++ b/api/role/init.go @@ -1,18 +1,18 @@ package role import ( - "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" "github.com/veypi/vigo" ) var Router = vigo.NewRouter() func init() { - Router.Get("/", "List Roles", auth.VBaseAuth.PermRead("role:*"), list) - Router.Get("/{id}", "Get Role Detail", auth.VBaseAuth.PermRead("role:*"), get) - Router.Post("/", "Create Role", auth.VBaseAuth.PermCreate("role"), create) - Router.Patch("/{id}", "Update Role", auth.VBaseAuth.PermWrite("role:*"), patch) - Router.Delete("/{id}", "Delete Role", auth.VBaseAuth.PermWrite("role:*"), del) - Router.Get("/{id}/permissions", "Get Role Permissions", auth.VBaseAuth.PermRead("role:*"), getPermissions) - Router.Put("/{id}/permissions", "Update Role Permissions", auth.VBaseAuth.PermWrite("role:*"), updatePermissions) + Router.Get("/", "List Roles", cfg.Auth.RequireRead("role:*"), list) + Router.Get("/{id}", "Get Role Detail", cfg.Auth.RequireRead("role:*"), get) + Router.Post("/", "Create Role", cfg.Auth.RequireCreate("role"), create) + Router.Patch("/{id}", "Update Role", cfg.Auth.RequireWrite("role:*"), patch) + Router.Delete("/{id}", "Delete Role", cfg.Auth.RequireWrite("role:*"), del) + Router.Get("/{id}/permissions", "Get Role Permissions", cfg.Auth.RequireRead("role:*"), getPermissions) + Router.Put("/{id}/permissions", "Update Role Permissions", cfg.Auth.RequireWrite("role:*"), updatePermissions) } diff --git a/api/settings/init.go b/api/settings/init.go index e0071bf..5c138c2 100644 --- a/api/settings/init.go +++ b/api/settings/init.go @@ -7,7 +7,7 @@ package settings import ( - "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" "github.com/veypi/vigo" ) @@ -16,7 +16,7 @@ var Router = vigo.NewRouter() func init() { // 获取设置列表(支持按分类过滤) // 只有拥有所有权限的可以修改配置表 - Router.Get("/", "获取设置列表", auth.VBaseAuth.PermRead("setting:*"), list) + Router.Get("/", "获取设置列表", cfg.Auth.RequireRead("setting:*"), list) // 批量更新设置 - Router.Put("/", "批量更新设置", auth.VBaseAuth.PermWrite("setting:*"), update) + Router.Put("/", "批量更新设置", cfg.Auth.RequireWrite("setting:*"), update) } diff --git a/api/user/create.go b/api/user/create.go index 719ebb0..a41d4c8 100644 --- a/api/user/create.go +++ b/api/user/create.go @@ -7,7 +7,6 @@ package user import ( - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/models" @@ -75,7 +74,7 @@ func create(x *vigo.X, req *CreateRequest) (*models.User, error) { } // 授予默认角色 "user" - if err := auth.VBaseAuth.GrantRole(x.Context(), user.ID, "user"); err != nil { + if err := cfg.Auth.GrantRole(x.Context(), user.ID, "user"); err != nil { // 记录错误但允许流程继续 } diff --git a/api/user/get.go b/api/user/get.go index 6120294..fef5b95 100644 --- a/api/user/get.go +++ b/api/user/get.go @@ -7,7 +7,6 @@ package user import ( - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/models" "github.com/veypi/vigo" @@ -21,9 +20,9 @@ type GetRequest struct { // get 获取用户详情 func get(x *vigo.X, req *GetRequest) (*models.User, error) { // 手动鉴权: 只能查看自己的信息,或者是管理员 - uid := auth.VBaseAuth.UserID(x) + uid := cfg.Auth.UserID(x) if uid != req.UserID { - if !auth.VBaseAuth.Check(x.Context(), uid, "user:read", auth.LevelRead) { + if !cfg.Auth.Check(x.Context(), uid, "user:read", 2) { return nil, vigo.ErrForbidden } } diff --git a/api/user/init.go b/api/user/init.go index 5637ed0..842fd5b 100644 --- a/api/user/init.go +++ b/api/user/init.go @@ -7,7 +7,7 @@ package user import ( - "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" "github.com/veypi/vigo" ) @@ -15,15 +15,15 @@ var Router = vigo.NewRouter() func init() { // 管理员 管理用户权限 (所有接口需要 user:admin 权限) - Router.Get("/", "用户列表", auth.VBaseAuth.PermRead("user:*"), list) - Router.Post("/", "创建用户", auth.VBaseAuth.PermCreate("user"), create) - Router.Get("/{user_id}", "获取用户详情", auth.VBaseAuth.PermRead("user:*"), get) - Router.Patch("/{user_id}", "更新用户", auth.VBaseAuth.PermWrite("user:*"), patch) - Router.Delete("/{user_id}", "删除用户", auth.VBaseAuth.PermWrite("user:*"), del) - Router.Patch("/{user_id}/status", "更新用户状态", auth.VBaseAuth.PermWrite("user:*"), updateStatus) + Router.Get("/", "用户列表", cfg.Auth.RequireRead("user:*"), list) + Router.Post("/", "创建用户", cfg.Auth.RequireCreate("user"), create) + Router.Get("/{user_id}", "获取用户详情", cfg.Auth.RequireRead("user:*"), get) + Router.Patch("/{user_id}", "更新用户", cfg.Auth.RequireWrite("user:*"), patch) + Router.Delete("/{user_id}", "删除用户", cfg.Auth.RequireWrite("user:*"), del) + Router.Patch("/{user_id}/status", "更新用户状态", cfg.Auth.RequireWrite("user:*"), updateStatus) - Router.Get("/{user_id}/roles", "Get User Roles", auth.VBaseAuth.PermRead("user:*"), getRoles) - Router.Put("/{user_id}/roles", "Update User Roles", auth.VBaseAuth.PermWrite("user:*"), updateRoles) - Router.Get("/{user_id}/permissions", "Get User Permissions", auth.VBaseAuth.PermRead("user:*"), getPermissions) - Router.Put("/{user_id}/permissions", "Update User Permissions", auth.VBaseAuth.PermWrite("user:*"), updatePermissions) + Router.Get("/{user_id}/roles", "Get User Roles", cfg.Auth.RequireRead("user:*"), getRoles) + Router.Put("/{user_id}/roles", "Update User Roles", cfg.Auth.RequireWrite("user:*"), updateRoles) + Router.Get("/{user_id}/permissions", "Get User Permissions", cfg.Auth.RequireRead("user:*"), getPermissions) + Router.Put("/{user_id}/permissions", "Update User Permissions", cfg.Auth.RequireWrite("user:*"), updatePermissions) } diff --git a/api/user/patch.go b/api/user/patch.go index e6fdfd1..c6da323 100644 --- a/api/user/patch.go +++ b/api/user/patch.go @@ -7,7 +7,6 @@ package user import ( - "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/models" "github.com/veypi/vigo" @@ -25,9 +24,9 @@ type PatchRequest struct { // patch 更新用户 func patch(x *vigo.X, req *PatchRequest) (*models.User, error) { // 手动鉴权: 只能修改自己的信息,或者是管理员 - uid := auth.VBaseAuth.UserID(x) + uid := cfg.Auth.UserID(x) if uid != req.UserID { - if !auth.VBaseAuth.Check(x.Context(), uid, "user:update", auth.LevelWrite) { + if !cfg.Auth.Check(x.Context(), uid, "user:update", 4) { return nil, vigo.ErrForbidden } } diff --git a/api/verification/send.go b/api/verification/send.go index d87facf..68ba838 100644 --- a/api/verification/send.go +++ b/api/verification/send.go @@ -16,6 +16,7 @@ import ( "github.com/veypi/vbase/libs/sms" "github.com/veypi/vbase/models" "github.com/veypi/vigo" + "github.com/veypi/vigo/contrib/requestmeta" ) // SendRequest 发送验证码请求 @@ -124,7 +125,7 @@ func sendCode(x *vigo.X, req *SendRequest) (*SendResponse, error) { Purpose: req.Purpose, Status: models.CodeStatusPending, ExpiresAt: expiresAt, - RemoteIP: x.GetRemoteIP(), + RemoteIP: requestmeta.RemoteIP(x), } if err := db.Create(verification).Error; err != nil { @@ -216,4 +217,3 @@ func generateCode(length int) string { } return string(code) } - diff --git a/auth/auth.go b/auth/auth.go index b8849c1..46a4774 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -13,8 +13,6 @@ import ( "strings" "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/cache" - "github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/models" "github.com/veypi/vigo" pub "github.com/veypi/vigo/contrib/auth" @@ -38,15 +36,18 @@ const ( // PermFunc 权限检查函数类型 type PermFunc = pub.PermFunc +// Provider 是 auth.Provider 的别名,用于实现端 +type Provider = pub.Provider + // Factory 全局 Auth 工厂 var Factory = &authFactory{ - apps: make(map[string]*appAuth), + apps: make(map[string]Provider), } -// VBaseAuth vbase 自身的权限管理实例 -var VBaseAuth = Factory.New("vb") +// VBaseProvider vbase 自身的权限 Provider 实例 +var VBaseProvider Provider -var _ pub.Auth = &appAuth{} +var _ pub.Provider = &vbaseProvider{} func init() { // 注册权限初始化回调 @@ -54,26 +55,28 @@ func init() { } type authFactory struct { - apps map[string]*appAuth + apps map[string]Provider } -// New 创建权限管理实例 -func (f *authFactory) New(scope string) pub.Auth { - if auth, exists := f.apps[scope]; exists { - return auth +// New 创建权限 Provider 实例 +func (f *authFactory) New(scope string) Provider { + if p, exists := f.apps[scope]; exists { + return p } - auth := &appAuth{ + p := &vbaseProvider{ scope: scope, roleDefs: make(map[string]roleDefinition), } - f.apps[scope] = auth - return auth + f.apps[scope] = p + return p } func (f *authFactory) init() error { - for appKey, auth := range f.apps { - if err := auth.init(); err != nil { - return fmt.Errorf("failed to init auth for %s: %w", appKey, err) + for appKey, p := range f.apps { + if vp, ok := p.(*vbaseProvider); ok { + if err := vp.init(); err != nil { + return fmt.Errorf("failed to init auth for %s: %w", appKey, err) + } } } return nil @@ -86,113 +89,23 @@ type roleDefinition struct { policies []string // 格式: "permissionID:level" } -// appAuth 实现 Auth 接口 -type appAuth struct { +// vbaseProvider 实现 Provider 接口 +type vbaseProvider struct { scope string roleDefs map[string]roleDefinition } -// ========== 接口实现 ========== +// ========== Provider 接口实现 ========== -func (a *appAuth) UserID(x *vigo.X) string { +func (a *vbaseProvider) UserID(x *vigo.X) string { if uid, ok := x.Get(CtxKeyUserID).(string); ok { return uid } return "" } -// Login 登录检查中间件 -func (a *appAuth) Login() PermFunc { - return func(x *vigo.X) error { - // 1. 提取 token - tokenString := extractToken(x) - if tokenString == "" { - return vigo.ErrUnauthorized.WithString("missing token") - } - - // 2. 解析 token - claims, err := jwt.ParseToken(tokenString) - if err != nil { - if err == jwt.ErrExpiredToken { - return vigo.ErrTokenExpired - } - return vigo.ErrTokenInvalid - } - - // 3. 检查黑名单 - if cache.IsEnabled() { - blacklisted, _ := cache.IsTokenBlacklisted(claims.ID) - if blacklisted { - return vigo.ErrUnauthorized.WithString("token has been revoked") - } - } - - // 4. 设置 UserID - x.Set(CtxKeyUserID, claims.UserID) - return nil - } -} - -func (a *appAuth) Perm(code string, level int) PermFunc { - return func(x *vigo.X) error { - userID := a.UserID(x) - if userID == "" { - // 尝试先运行 Login 逻辑 - if err := a.Login()(x); err != nil { - return err - } - userID = a.UserID(x) - } - - // 解析动态参数 - permID, err := parsePermissionID(x, code) - if err != nil { - return vigo.ErrInvalidArg.WithError(err) - } - - // 检查权限 - if err := validatePermission(permID, level); err != nil { - panic(err) - } - - if !a.Check(x.Context(), userID, permID, level) { - return vigo.ErrNoPermission.WithString(fmt.Sprintf("requires permission: %s (level %d)", permID, level)) - } - - return nil - } -} - -func (a *appAuth) PermCreate(code string) PermFunc { - if err := validatePermission(code, LevelCreate); err != nil { - panic(err) - } - return a.Perm(code, LevelCreate) -} - -func (a *appAuth) PermRead(code string) PermFunc { - if err := validatePermission(code, LevelRead); err != nil { - panic(err) - } - return a.Perm(code, LevelRead) -} - -func (a *appAuth) PermWrite(code string) PermFunc { - if err := validatePermission(code, LevelWrite); err != nil { - panic(err) - } - return a.Perm(code, LevelWrite) -} - -func (a *appAuth) PermAdmin(code string) PermFunc { - if err := validatePermission(code, LevelAdmin); err != nil { - panic(err) - } - return a.Perm(code, LevelAdmin) -} - // Grant 授予权限 -func (a *appAuth) Grant(ctx context.Context, userID, permissionID string, level int) error { +func (a *vbaseProvider) Grant(ctx context.Context, userID, permissionID string, level int) error { if err := validatePermission(permissionID, level); err != nil { return err } @@ -220,13 +133,13 @@ func (a *appAuth) Grant(ctx context.Context, userID, permissionID string, level } // Revoke 撤销权限 -func (a *appAuth) Revoke(ctx context.Context, userID, permissionID string) error { +func (a *vbaseProvider) Revoke(ctx context.Context, userID, permissionID string) error { return cfg.DB().Where("user_id = ? AND permission_id = ? AND scope = ?", userID, permissionID, a.scope). Delete(&models.Permission{}).Error } // GrantRole 授予角色 -func (a *appAuth) GrantRole(ctx context.Context, userID, roleCode string) error { +func (a *vbaseProvider) GrantRole(ctx context.Context, userID, roleCode string) error { var role models.Role if err := cfg.DB().Where("code = ?", roleCode).First(&role).Error; err != nil { return err @@ -248,7 +161,7 @@ func (a *appAuth) GrantRole(ctx context.Context, userID, roleCode string) error } // RevokeRole 撤销角色 -func (a *appAuth) RevokeRole(ctx context.Context, userID, roleCode string) error { +func (a *vbaseProvider) RevokeRole(ctx context.Context, userID, roleCode string) error { var role models.Role if err := cfg.DB().Where("code = ?", roleCode).First(&role).Error; err != nil { return err @@ -259,7 +172,7 @@ func (a *appAuth) RevokeRole(ctx context.Context, userID, roleCode string) error } // Check 检查权限 -func (a *appAuth) Check(ctx context.Context, userID, permissionID string, level int) bool { +func (a *vbaseProvider) Check(ctx context.Context, userID, permissionID string, level int) bool { if err := validatePermission(permissionID, level); err != nil { panic(err) } @@ -274,7 +187,7 @@ func (a *appAuth) Check(ctx context.Context, userID, permissionID string, level } // ListResources 查询用户在特定资源类型下的详细权限信息 -func (a *appAuth) ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) { +func (a *vbaseProvider) ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) { perms, err := a.getUserPermissions(userID) if err != nil { return nil, err @@ -309,7 +222,7 @@ func (a *appAuth) ListResources(ctx context.Context, userID, resourceType string } // ListUsers 查询特定资源的所有协作者及其权限 -func (a *appAuth) ListUsers(ctx context.Context, permissionID string) (map[string]int, error) { +func (a *vbaseProvider) ListUsers(ctx context.Context, permissionID string) (map[string]int, error) { parents := getAllParents(permissionID) var perms []models.Permission @@ -365,7 +278,7 @@ func getAllParents(permID string) []string { } // AddRole 添加角色定义 -func (a *appAuth) AddRole(code, name string, policies ...string) error { +func (a *vbaseProvider) AddRole(code, name string, policies ...string) error { a.roleDefs[code] = roleDefinition{ code: code, name: name, @@ -375,7 +288,7 @@ func (a *appAuth) AddRole(code, name string, policies ...string) error { } // init 初始化角色到数据库 -func (a *appAuth) init() error { +func (a *vbaseProvider) init() error { db := cfg.DB() for code, def := range a.roleDefs { // 1. 确保角色存在 @@ -462,7 +375,7 @@ func (a *appAuth) init() error { // ========== 内部辅助方法 ========== // getUserPermissions 获取用户的所有权限(聚合 Role 和 Direct Permission) -func (a *appAuth) getUserPermissions(userID string) ([]models.Permission, error) { +func (a *vbaseProvider) getUserPermissions(userID string) ([]models.Permission, error) { var perms []models.Permission db := cfg.DB() diff --git a/cfg/cfg.go b/cfg/cfg.go index 5a74951..3566d3b 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -13,6 +13,7 @@ package cfg import ( "time" + "github.com/veypi/vigo/contrib/auth" "github.com/veypi/vigo/contrib/config" ) @@ -78,3 +79,6 @@ var Global = &Options{ var DB = Global.DB.Client var Redis = Global.Redis.Client + +// Auth 是全局的权限检查对象,通过 SetProvider 注入 Provider 实现 +var Auth = auth.New() diff --git a/init.go b/init.go index 6aae945..a6d2c5d 100644 --- a/init.go +++ b/init.go @@ -18,12 +18,25 @@ import ( "github.com/veypi/vigo" ) +const Version = "v0.6.4" + var Router = vigo.NewRouter() var ( NewAuth = auth.Factory.New Config = cfg.Global ) +func init() { + // 初始化 VBaseProvider 并设置到全局 Auth + auth.VBaseProvider = auth.Factory.New("vb") + cfg.Auth.SetProvider(auth.VBaseProvider) + + Router.Extend("/api", api.Router) + Router.Extend("v", vhtmlui.Router) + Router.Extend("vhtml", vhtml.Router) + vhtml.WrapUI(Router, uifs) +} + type Options = cfg.Options func Init() error { diff --git a/tests/oauth_client_test.go b/tests/oauth_client_test.go index a15f3f8..793ade8 100644 --- a/tests/oauth_client_test.go +++ b/tests/oauth_client_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" ) // OAuthClientResp OAuth 客户端响应 @@ -106,10 +107,10 @@ func TestOAuthClientAccessControl(t *testing.T) { ensureUsers(t) // Grant permission to User1 - if err := auth.VBaseAuth.Grant(context.Background(), User1ID, "oauth-client:*", auth.LevelRead); err != nil { + if err := cfg.Auth.Grant(context.Background(), User1ID, "oauth-client:*", auth.LevelRead); err != nil { t.Fatalf("Failed to grant read permission: %v", err) } - if err := auth.VBaseAuth.Grant(context.Background(), User1ID, "oauth-client", auth.LevelCreate); err != nil { + if err := cfg.Auth.Grant(context.Background(), User1ID, "oauth-client", auth.LevelCreate); err != nil { t.Fatalf("Failed to grant create permission: %v", err) } diff --git a/tests/oauth_security_test.go b/tests/oauth_security_test.go index 483e4f8..6e30886 100644 --- a/tests/oauth_security_test.go +++ b/tests/oauth_security_test.go @@ -1,9 +1,11 @@ package tests import ( - "testing" "context" + "testing" + "github.com/veypi/vbase/auth" + "github.com/veypi/vbase/cfg" ) // TestOAuthClientSecretLeak 测试 OAuth 客户端密钥泄漏 @@ -11,7 +13,7 @@ func TestOAuthClientSecretLeak(t *testing.T) { ensureUsers(t) // Grant permission to User1 to see client details - auth.VBaseAuth.Grant(context.Background(), User1ID, "oauth-client:*", auth.LevelRead) + cfg.Auth.Grant(context.Background(), User1ID, "oauth-client:*", auth.LevelRead) var clientID string var clientSecret string @@ -81,7 +83,7 @@ func TestOAuthClientAccessControlSecurity(t *testing.T) { ensureUsers(t) // Grant permission to User1 to create clients - auth.VBaseAuth.Grant(context.Background(), User1ID, "oauth-client", auth.LevelCreate) + cfg.Auth.Grant(context.Background(), User1ID, "oauth-client", auth.LevelCreate) var clientID string diff --git a/tests/role_access_test.go b/tests/role_access_test.go index 9e898ca..8619213 100644 --- a/tests/role_access_test.go +++ b/tests/role_access_test.go @@ -17,9 +17,9 @@ func TestRoleApiAccess(t *testing.T) { // Ensure Admin has * permission // Clean up any previous permissions for Admin cfg.DB().Where("user_id = ?", AdminID).Delete(&models.Permission{}) - + // Grant Admin * permission - if err := auth.VBaseAuth.Grant(ctx, AdminID, "*", auth.LevelAdmin); err != nil { + if err := cfg.Auth.Grant(ctx, AdminID, "*", auth.LevelAdmin); err != nil { t.Fatalf("Failed to grant admin permission: %v", err) } @@ -44,7 +44,7 @@ func TestRoleApiAccess(t *testing.T) { // 3. User Access (With Permission) t.Run("User_WithPermission_Role_List", func(t *testing.T) { // Grant role:* (Read) to User1 - if err := auth.VBaseAuth.Grant(ctx, User1ID, "role:*", auth.LevelRead); err != nil { + if err := cfg.Auth.Grant(ctx, User1ID, "role:*", auth.LevelRead); err != nil { t.Fatalf("Failed to grant role permission: %v", err) } diff --git a/tests/scoped_auth_test.go b/tests/scoped_auth_test.go index 4abbc25..49ccd2b 100644 --- a/tests/scoped_auth_test.go +++ b/tests/scoped_auth_test.go @@ -30,8 +30,8 @@ func TestScopedAuth(t *testing.T) { t.Errorf("Expected User1 to have %s in %s scope", permID, scope) } - // Verify Check in global VBaseAuth scope (should be false) - if auth.VBaseAuth.Check(ctx, User1ID, permID, level) { + // Verify Check in global VBaseProvider scope (should be false) + if auth.VBaseProvider.Check(ctx, User1ID, permID, level) { t.Errorf("Expected User1 NOT to have %s in 'vb' scope", permID) } })