From 95c1f616bece48a7942b4db81f94d72eb1fe1f16 Mon Sep 17 00:00:00 2001 From: veypi Date: Sat, 6 Jun 2026 05:27:10 +0800 Subject: [PATCH] chore: bump version to v1.2.0 and update all docs - Bump version from v1.1.1 to v1.2.0 - Add CHANGELOG.md summarizing 20 commits since v1.1.1 - Rewrite docs to match current code: Session auth, Require* APIs, nested config (db.dsn/db.type/jwt.*), Cookie-based token delivery - Update method names uniformly: Perm* -> Require* - Fix README license badge (Apache -> MIT), port and build commands - Update auth design docs to reflect Provider + Auth SPI pattern - Update CLAUDE.md with current architecture and directory structure Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 34 +++++++ CLAUDE.md | 12 ++- README.md | 52 +++++----- agents.md | 44 ++++++--- auth/design.md | 173 +++++++++++++------------------- docs/auth.md | 181 ++++++++++++++-------------------- docs/configuration.md | 106 +++++++++++--------- docs/design.md | 65 +++++++----- docs/integration.md | 170 +++++++++++++++++-------------- init.go | 2 +- tests/README.md | 4 +- tests/SECURITY_TEST_REPORT.md | 12 ++- 12 files changed, 451 insertions(+), 404 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f6db5e9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +## v1.2.0 (2026-06-06) + +### Features + +- **auth**: 替换 user-level token version 为 session-based 认证机制 +- **auth**: 支持短信/邮箱验证码注册 +- **auth**: 登录时递增 token version 以吊销旧会话 +- **auth**: Token 传送方式迁移至 HttpOnly Cookie,支持 version-based 吊销 +- **api**: 启用 API 文档端点 +- **role**: 新增角色用户批量管理 API +- **role**: 支持增量式权限授予和移除 +- **cfg**: 新增用户创建钩子(user creation hook) +- **ui**: 支持 URL 路由中的 `@` 前缀 + +### Changed + +- **cfg**: 默认数据库切换为 SQLite +- **settings**: 用 GORM 结构体查询替换原始 SQL 条件 +- **ui**: 简化角色管理界面和认证流程 +- **ui**: 用 fetch 替换所有页面中的 axios,提取认证布局 +- **ui**: 重构 VBase 认证客户端,支持自动刷新和 onAuthSuccess 回调 +- **ui**: 前端统一将 `$vbase` 重命名为 `$auth` + +### Fixed + +- **ui**: 修复用户获取失败时的优雅降级处理 +- **ui**: 修复 vhtml script src 使用作用域路径前缀 +- **db**: 修复 longtext 字段类型问题 + +### Removed + +- 移除过期的 todo 和 UI 设计文档 diff --git a/CLAUDE.md b/CLAUDE.md index d872f9f..44e4698 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,14 +9,14 @@ VBase is a Go-based identity authentication and permission management framework - **Language**: Go 1.24+ - **Framework**: Vigo (onion-model middleware) - **ORM**: GORM (MySQL, PostgreSQL, SQLite supported) -- **Authentication**: JWT + OAuth2 +- **Authentication**: Session + JWT (Access/Refresh Token) + Cookie-based delivery + OAuth2 - **Permissions**: Scoped RBAC (Role-Based Access Control) - **Frontend**: vhtml (embedded HTML-based UI at `/vb/`) ## Common Commands ```bash -# Run development server (default port 4001) +# Run development server (default port 4000) make run # Database operations @@ -45,8 +45,10 @@ Response <- [Global After Middleware] <--------+ │ ├── oauth/ # OAuth2 provider endpoints │ ├── role/ # Role management │ ├── user/ # User management +│ ├── settings/ # System settings +│ ├── verification/ # SMS/email verification code │ └── init.go # Router aggregation -├── auth/ # Core Scoped RBAC permission system +├── auth/ # Scoped RBAC permission system + Session management │ ├── auth.go # Permission checking implementation │ └── design.md # Permission system design doc ├── cfg/ # Configuration (DB, Redis, JWT settings) @@ -74,8 +76,8 @@ The system uses a scoped permission model where permissions are isolated by `Sco **Key Interfaces**: - `auth.Factory.New(scope)`: Get scoped auth instance. -- `auth.PermRead(code)`, `auth.PermWrite(code)`: Middleware checks. -- `auth.Grant(ctx, userID, permID, level)`: Grant permission. +- `cfg.Auth.RequireRead(code)`, `cfg.Auth.RequireWrite(code)`: Middleware checks. +- `cfg.Auth.Grant(ctx, userID, permID, level)`: Grant permission. ### API Response Format diff --git a/README.md b/README.md index 7cab127..bd085f7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

Go Version - License + License Framework

@@ -22,18 +22,19 @@ ## 简介 -**VBase** 是一个基于 [Vigo](https://github.com/veypi/vigo) 框架开发的高性能后端基础框架,提供一套标准化的用户管理、作用域权限控制(Scoped RBAC)和 OAuth2 认证服务。可作为独立服务部署,也可作为基础库集成到你的 Go 项目中。 +**VBase** 是一个基于 [Vigo](https://github.com/veypi/vigo) 框架开发的高性能后端基础框架,提供一套标准化的用户管理、作用域权限控制(Scoped RBAC)和 OAuth2 认证服务。支持 Session + JWT 双 Token 认证、Cookie 令牌传送和短信/邮箱验证码注册。可作为独立服务部署,也可作为基础库集成到你的 Go 项目中。 ## 核心特性 -- 🔐 **完整认证体系** - JWT Token 认证(Access + Refresh)、多因素认证(短信验证码) -- 🛡️ **Scoped RBAC 权限控制** - 基于作用域(Scope)的角色访问控制,支持资源级权限和通配符权限 -- � **层级权限管理** - 支持资源类型(奇数层)和实例(偶数层)的精细化权限控制 -- 🔑 **OAuth2 Provider** - 完整的 OAuth2 服务端实现,支持第三方应用接入 -- 🗄️ **多数据库支持** - MySQL、PostgreSQL、SQLite 自动适配 -- 📱 **内置管理界面** - 基于 vhtml 的嵌入式管理后台 -- 🚀 **高性能** - 基于洋葱模型中间件架构,低延迟高并发 -- 🧪 **完整测试** - 集成测试脚本覆盖核心业务流程 +- **完整认证体系** — Session + JWT(Access + Refresh Token),HttpOnly Cookie / Header / Query 多通道传送 +- **Scoped RBAC 权限控制** — 基于作用域(Scope)的角色访问控制,支持多应用权限隔离 +- **层级权限管理** — 支持资源类型(奇数层)和实例(偶数层)的精细化权限控制 +- **OAuth2 Provider** — 完整的 OAuth2 服务端实现,支持第三方应用接入 +- **多因素认证** — 短信/邮箱验证码注册和验证 +- **多数据库支持** — SQLite(默认)、MySQL、PostgreSQL 自动适配 +- **嵌入式管理界面** — 基于 vhtml 的管理后台,开箱即用 +- **API 文档** — 内置 `/_api.json` 接口文档端点 +- **高性能** — 基于洋葱模型中间件架构,Redis 缓存 Session 验证 ## 快速开始 @@ -50,18 +51,14 @@ go get github.com/veypi/vbase git clone https://github.com/veypi/vbase.git cd vbase -# 配置数据库(编辑 dev.yaml 或使用环境变量) -export DSN="root:password@tcp(127.0.0.1:3306)/vbase?charset=utf8&parseTime=True" -export DB="mysql" +# 直接启动(默认 SQLite + 端口 4000) +go run ./cli/*.go -p 4000 -# 启动服务 -go run ./cli/main.go - -# 或使用 Makefile(默认端口 4001) +# 或使用 Makefile make run ``` -服务默认在 `http://localhost:4000` 启动,管理界面访问 `/vb/` 路径。 +服务默认在 `http://localhost:4000` 启动,管理界面访问 `/vb/` 路径,API 文档访问 `/_api.json`。 ### 作为库集成 @@ -69,15 +66,14 @@ make run package main import ( - "github.com/veypi/vbase" - "github.com/veypi/vigo" + "github.com/veypi/vbase" + "github.com/veypi/vbase/cfg" + "github.com/veypi/vigo" ) -var Router = vigo.NewRouter() - -func init() { - // 挂载 VBase 路由到 /vb 前缀 - Router.Extend("/vb", vbase.Router) +func main() { + app := vigo.New("myapp", vbase.Router, cfg.Global, vigo.WithInit(vbase.Init)) + panic(app.Run()) } ``` @@ -91,8 +87,10 @@ vbase/ │ ├── auth/ # 认证接口(登录、注册、Token 刷新) │ ├── oauth/ # OAuth2 Provider 接口 │ ├── role/ # 角色管理 -│ └── user/ # 用户管理 -├── auth/ # 核心权限控制模块(Scoped RBAC 实现) +│ ├── user/ # 用户管理 +│ ├── settings/ # 系统设置接口 +│ └── verification/ # 验证码接口 +├── auth/ # 核心权限控制模块(Scoped RBAC + Session 管理) ├── cfg/ # 配置管理 ├── cli/ # 命令行入口 ├── models/ # 数据模型(GORM) diff --git a/agents.md b/agents.md index ae99319..3ef29c7 100644 --- a/agents.md +++ b/agents.md @@ -2,35 +2,47 @@ ## 注意 -如果开发中发现什么开发规则或者技巧,你可以更新在这个文档,供其他人看 -如果下面某些端口或者路径访问失败,请询问用户是否启动应用 +如果开发中发现什么开发规则或者技巧,你可以更新在这个文档,供其他人看。 +如果下面某些端口或者路径访问失败,请询问用户是否启动应用。 ## 后端开发 +### 运行 + +```bash +# 使用 SQLite 数据库(默认) +go run ./cli/*.go -p 4000 + +# 或指定配置文件 +go run ./cli/*.go -f ./cfg/dev.yml -l debug -p 4000 +``` + ### 测试 ```bash -//重置数据库 +# 重置数据库 rm /tmp/vb.sqlite -go run cli/main.go -db.type=sqlite -db.dsn /tmp/vb.sqlite -p 4000 +go run ./cli/*.go -p 4000 ``` -可以通过 查看接口列表 +可以通过 `http://localhost:4000/_api.json` 查看接口列表。 ## UI 界面开发指南 - 界面采用 vhtml 框架,该框架可以将一个 html 文件自动加载为一个组件 -- 开始写界面前请阅读全局样式文件 /ui/assets/common.css,组件内必须使用全局中的变量去组合或者直接使用全局中的样式, 保证所有界面的样式一致, 比如只能使用颜色变量或者通过 color-mix 函数去包含至少一个颜色变量 -- 组件内部避免重复的样式定义 如 body 内无需重复定义字体 -- 本项目使用 vhtml-ui 组件库,该组件库可以通过 curl -sS 查看文档 其组件代码都已经映射到了/v/目录下 -- 前端路由文件 /ui/routes.js 该文件定义了所有的路由规则 +- 开始写界面前请阅读全局样式文件 `/ui/assets/common.css`,组件内必须使用全局中的变量去组合或者直接使用全局中的样式,保证所有界面的样式一致,比如只能使用颜色变量或者通过 color-mix 函数去包含至少一个颜色变量 +- 组件内部避免重复的样式定义,如 body 内无需重复定义字体 +- 本项目使用 vhtml-ui 组件库,该组件库可以通过 `curl -sS http://localhost:4000/v/README.md` 查看文档,其组件代码都已映射到了 `/v/` 目录下 +- 前端路由文件 `/ui/routes.js`,该文件定义了所有的路由规则 ## vhtml-ui 文档查看方法 -curl 指令可以不用沙盒运行 -获取文档目录 该操作可以查看所有组件的目录结构 -curl -sS -获取文档全文(较长,一般建议查询目录再查询章节内容) -curl -sS -获取章节内容(可以根据第一步获取的目录编号查询内容) 一般使用这个查询章节内容,查询内容时不能带 toc 参数 -curl -sS +获取文档目录(查看所有组件的目录结构): +```bash +curl -sS http://localhost:4000/v/docs/README.md?toc=1 +``` + +获取章节内容(根据目录编号查询内容,不能带 toc 参数): +```bash +curl -sS http://localhost:4000/v/docs/README.md?from=1.2&to=1.2 +``` diff --git a/auth/design.md b/auth/design.md index ed9bd37..43b744f 100644 --- a/auth/design.md +++ b/auth/design.md @@ -110,16 +110,56 @@ app:vbase:role:admin → 第4层 (偶数) - 实例 ## 五、Auth 接口设计 +VBase 的权限系统基于 vigo 框架的 `auth` SPI 模式,分为两层: + +- **Provider 接口**:由 VBase 实现 `vbaseProvider`,负责实际的权限存储和校验逻辑。 +- **Auth 结构体**:由 vigo 框架提供 (`auth.Auth`),包装 Provider,提供中间件和高层 API,用于业务模块直接调用。 + +### 5.1 Provider 接口(VBase 实现) + ```go -package auth +// Provider 是实现端需要实现的 SPI +type Provider interface { + UserID(x *vigo.X) string + Check(ctx context.Context, userID, permCode string, permLevel int) bool + Grant(ctx context.Context, userID, permCode string, permLevel int) error + Revoke(ctx context.Context, userID, permCode string) error + ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) + ListUsers(ctx context.Context, permCode string) (map[string]int, error) + GrantRole(ctx context.Context, userID, roleCode string) error + RevokeRole(ctx context.Context, userID, roleCode string) error + AddRole(roleCode, roleName string, permPolicies ...string) error +} +``` -import ( - "context" - "github.com/veypi/vigo" -) +VBase 通过 `auth.Factory.New(scope)` 创建作用域隔离的 Provider 实例,实现多应用间权限隔离。 -// ========== 权限等级 ========== +### 5.2 Auth 结构体(业务调用) +```go +// cfg.Auth 是全局权限管理实例,由 vigo 框架提供 +// 中间件方法: +cfg.Auth.Login() // 检查登录(不检查权限) +cfg.Auth.Require(permExpr, permLevel) // 通用权限检查 +cfg.Auth.RequireCreate(permExpr) // 创建权限 (level 1) +cfg.Auth.RequireRead(permExpr) // 读取权限 (level 2) +cfg.Auth.RequireWrite(permExpr) // 写入权限 (level 4) +cfg.Auth.RequireAdmin(permExpr) // 管理员权限 (level 7) + +// 业务调用方法: +cfg.Auth.UserID(x) // 获取当前用户 ID +cfg.Auth.Check(ctx, userID, permCode, level) // 静态权限检查 +cfg.Auth.Grant(ctx, userID, permCode, level) // 授予权限 +cfg.Auth.Revoke(ctx, userID, permCode) // 撤销权限 +cfg.Auth.GrantRole(ctx, userID, roleCode) // 授予角色 +cfg.Auth.RevokeRole(ctx, userID, roleCode) // 撤销角色 +cfg.Auth.ListResources(ctx, userID, resourceType) // 列出资源权限 +cfg.Auth.ListUsers(ctx, permCode) // 列出资源协作者 +``` + +### 5.3 权限等级常量 + +```go const ( LevelNone = 0 LevelCreate = 1 // 001 创建 (检查奇数层) @@ -128,92 +168,19 @@ const ( LevelReadWrite = 6 // 110 读写 (检查偶数层) LevelAdmin = 7 // 111 管理员 (完全控制) ) +``` -// PermFunc 权限检查函数类型 -type PermFunc func(x *vigo.X) error - -// Auth 权限管理接口 -type Auth interface { - // ========== 上下文 ========== - - // UserID 获取当前用户ID - UserID(x *vigo.X) string - - // ========== 登录检查 ========== - - // Login 检查用户是否登录 - Login() PermFunc - - // ========== 权限检查 ========== - - // Perm 检查权限 - // code: 权限码,支持动态解析 - // - 固定写法: "app:vbase" - // - 动态解析: "app:{appID}" 从 path 获取 - // "app:{appID@query}" 从 query 获取 - // "app:{appID@header}" 从 header 获取 - // "app:{appID@ctx}" 从 ctx 获取 - // level: 需要的权限等级 - Perm(code string, level int) PermFunc - - // ========== 快捷方法 ========== - - // PermCreate 检查创建权限 (level 1,检查奇数层) - PermCreate(code string) PermFunc - - // PermRead 检查读取权限 (level 2,检查偶数层) - PermRead(code string) PermFunc - - // PermWrite 检查更新权限 (level 4,检查偶数层) - PermWrite(code string) PermFunc - - // PermAdmin 检查管理员权限 (level 7,检查偶数层) - PermAdmin(code string) PermFunc - - // ========== 权限授予(业务调用) ========== - - // Grant 授予权限 - // 在创建资源、被授权等业务逻辑中调用 - // permissionID: 权限码,如 "app:vbase" - // level: 权限等级 - Grant(ctx context.Context, userID, permissionID string, level int) error - - // Revoke 撤销权限 - Revoke(ctx context.Context, userID, permissionID string) error - - // ========== 权限查询 ========== - - // Check 检查权限 不支持动态解析 - // permissionID: 完整的权限码,如 "app:vbase" - Check(ctx context.Context, userID, permissionID string, level int) bool - - // ========== 资源列表查询 ========== - - // ListResources 查询用户在特定资源类型下的详细权限信息 - // 用于解决 "查询我有权限的 app 列表" 等场景 - // userID: 用户ID - // resourceType: 资源类型 (奇数层),如 "app" 或 "app:{appID}:role" - // 返回: map[实例ID]权限等级 (如 {"vbase": 2, "other": 7}) - ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) - - // ListUsers 查询特定资源的所有协作者及其权限 - // 用于解决 "查看这个应用有哪些成员" 等场景 - // permissionID: 资源实例权限码,如 "app:vbase" - // 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7}) - ListUsers(ctx context.Context, permissionID string) (map[string]int, error) -} - -// ========== 数据结构 ========== +### 5.4 Permission 数据模型 -// Permission 用户权限 +```go type Permission struct { - ID string `json:"id"` - UserID string `json:"user_id"` - RoleID string `json:"role_id"` - PermissionID string `json:"permission_id"` - Level int `json:"level"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + ID string `json:"id"` + Scope string `json:"scope"` // 作用域 + UserID *string `json:"user_id"` // 用户ID(直接授权) + RoleID *string `json:"role_id"` // 角色ID(角色授权) + PermissionID string `json:"permission_id"` // 权限ID,层级结构 + Level int `json:"level"` // 权限等级 + ExpireAt *time.Time `json:"expire_at"` // 过期时间(可选) } ``` @@ -228,10 +195,10 @@ var Router = vigo.NewRouter() func init() { // 创建应用 - 需要系统级 app 权限 - Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) + Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp) // 超级管理员接口 - Router.Get("/admin/users", cfg.Auth.PermAdmin("*"), AdminListUsers) + Router.Get("/admin/users", cfg.Auth.RequireAdmin("*"), AdminListUsers) } ``` @@ -241,16 +208,16 @@ func init() { func init() { // 从路径参数获取 appID (默认) // GET /apps/{appID} - Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) + Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp) // 从 query 参数获取 // GET /apps?appID=xxx - Router.Get("/apps", cfg.Auth.PermRead("app:{appID@query}"), GetApp) + Router.Get("/apps", cfg.Auth.RequireRead("app:{appID@query}"), GetApp) // 多层嵌套 // GET /apps/{appID}/roles/{roleID} Router.Get("/apps/{appID}/roles/{roleID}", - cfg.Auth.PermRead("app:{appID}:role:{roleID}"), + cfg.Auth.RequireRead("app:{appID}:role:{roleID}"), GetRole, ) } @@ -263,21 +230,21 @@ var Router = vigo.NewRouter().Use(cfg.Auth.Login()) func init() { // 创建应用 - 系统级权限 - Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) + Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp) // 列出我的应用 - 只需登录 Router.Get("/apps", ListMyApps) // 应用操作 - 从路径获取 - Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) - Router.Put("/apps/{appID}", cfg.Auth.PermWrite("app:{appID}"), UpdateApp) - Router.Delete("/apps/{appID}", cfg.Auth.PermAdmin("app:{appID}"), DeleteApp) + Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp) + Router.Put("/apps/{appID}", cfg.Auth.RequireWrite("app:{appID}"), UpdateApp) + Router.Delete("/apps/{appID}", cfg.Auth.RequireAdmin("app:{appID}"), DeleteApp) // 角色操作 - 嵌套资源 - Router.Post("/apps/{appID}/roles", cfg.Auth.PermCreate("app:{appID}:role"), CreateRole) - Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.PermRead("app:{appID}:role:{roleID}"), GetRole) - Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.PermWrite("app:{appID}:role:{roleID}"), UpdateRole) - Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.PermAdmin("app:{appID}:role:{roleID}"), DeleteRole) + Router.Post("/apps/{appID}/roles", cfg.Auth.RequireCreate("app:{appID}:role"), CreateRole) + Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireRead("app:{appID}:role:{roleID}"), GetRole) + Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireWrite("app:{appID}:role:{roleID}"), UpdateRole) + Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireAdmin("app:{appID}:role:{roleID}"), DeleteRole) } ``` @@ -322,7 +289,7 @@ func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) { 对于资源列表(List)或搜索接口,推荐以下设计模式: 1. **全量管理接口**(如后台管理系统): - - 使用 `PermAdmin("*")` 或 `PermAdmin("app:*")`。 + - 使用 `RequireAdmin("*")` 或 `RequireAdmin("app:*")`。 - 这类接口返回所有数据,必须严格控制权限。 2. **用户侧列表/搜索**(如“我的应用”): diff --git a/docs/auth.md b/docs/auth.md index 84414d7..a4e48ac 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -110,110 +110,77 @@ app:vbase:role:admin → 第4层 (偶数) - 实例 ## 五、Auth 接口设计 -```go -package auth - -import ( - "context" - "github.com/veypi/vigo" -) - -// ========== 权限等级 ========== - -const ( - LevelNone = 0 - LevelCreate = 1 // 001 创建 (检查奇数层) - LevelRead = 2 // 010 读取 (检查偶数层) - LevelWrite = 4 // 100 写入 (检查偶数层) - LevelReadWrite = 6 // 110 读写 (检查偶数层) - LevelAdmin = 7 // 111 管理员 (完全控制) -) - -// PermFunc 权限检查函数类型 -type PermFunc func(x *vigo.X) error - -// Auth 权限管理接口 -type Auth interface { - // ========== 上下文 ========== - - // UserID 获取当前用户ID - UserID(x *vigo.X) string - - // ========== 登录检查 ========== - - // Login 检查用户是否登录 - Login() PermFunc - - // ========== 权限检查 ========== - - // Perm 检查权限 - // code: 权限码,支持动态解析 - // - 固定写法: "app:vbase" - // - 动态解析: "app:{appID}" 从 path 获取 - // "app:{appID@query}" 从 query 获取 - // "app:{appID@header}" 从 header 获取 - // "app:{appID@ctx}" 从 ctx 获取 - // level: 需要的权限等级 - Perm(code string, level int) PermFunc - - // ========== 快捷方法 ========== +VBase 的权限系统基于 vigo 框架的 SPI 模式,分为两层: - // PermCreate 检查创建权限 (level 1,检查奇数层) - PermCreate(code string) PermFunc +- **Provider 接口**:由 VBase 实现 (`vbaseProvider`),负责实际的权限存储和校验逻辑。 +- **Auth 结构体**:由 vigo 框架提供 (`auth.Auth`),包装 Provider,提供中间件和高层 API 供业务模块直接使用。 - // PermRead 检查读取权限 (level 2,检查偶数层) - PermRead(code string) PermFunc +### 5.1 Provider 接口(VBase 实现) - // PermWrite 检查更新权限 (level 4,检查偶数层) - PermWrite(code string) PermFunc - - // PermAdmin 检查管理员权限 (level 7,检查偶数层) - PermAdmin(code string) PermFunc - - // ========== 权限授予(业务调用) ========== - - // Grant 授予权限 - // 在创建资源、被授权等业务逻辑中调用 - // permissionID: 权限码,如 "app:vbase" - // level: 权限等级 - Grant(ctx context.Context, userID, permissionID string, level int) error - - // Revoke 撤销权限 - Revoke(ctx context.Context, userID, permissionID string) error +```go +// Provider 是实现端需要实现的 SPI +type Provider interface { + UserID(x *vigo.X) string + Check(ctx context.Context, userID, permCode string, permLevel int) bool + Grant(ctx context.Context, userID, permCode string, permLevel int) error + Revoke(ctx context.Context, userID, permCode string) error + ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) + ListUsers(ctx context.Context, permCode string) (map[string]int, error) + GrantRole(ctx context.Context, userID, roleCode string) error + RevokeRole(ctx context.Context, userID, roleCode string) error + AddRole(roleCode, roleName string, permPolicies ...string) error +} +``` - // ========== 权限查询 ========== +VBase 通过 `auth.Factory.New(scope)` 创建作用域隔离的 Provider 实例。 - // Check 检查权限 不支持动态解析 - // permissionID: 完整的权限码,如 "app:vbase" - Check(ctx context.Context, userID, permissionID string, level int) bool +### 5.2 Auth 结构体(业务调用) - // ========== 资源列表查询 ========== +```go +// cfg.Auth 是全局权限管理实例 +// 中间件方法: +cfg.Auth.Login() +cfg.Auth.Require(permExpr, permLevel) // 通用权限检查 +cfg.Auth.RequireCreate(permExpr) // 创建 (level 1) +cfg.Auth.RequireRead(permExpr) // 读取 (level 2) +cfg.Auth.RequireWrite(permExpr) // 写入 (level 4) +cfg.Auth.RequireAdmin(permExpr) // 管理员 (level 7) + +// 业务调用方法: +cfg.Auth.UserID(x) // 获取当前用户 ID +cfg.Auth.Check(ctx, userID, permCode, level) // 静态权限检查 +cfg.Auth.Grant(ctx, userID, permCode, level) // 授予权限 +cfg.Auth.Revoke(ctx, userID, permCode) // 撤销权限 +cfg.Auth.GrantRole(ctx, userID, roleCode) // 授予角色 +cfg.Auth.RevokeRole(ctx, userID, roleCode) // 撤销角色 +cfg.Auth.ListResources(ctx, userID, resourceType) // 列出资源权限 +cfg.Auth.ListUsers(ctx, permCode) // 列出资源协作者 +``` - // ListResources 查询用户在特定资源类型下的详细权限信息 - // 用于解决 "查询我有权限的 app 列表" 等场景 - // userID: 用户ID - // resourceType: 资源类型 (奇数层),如 "app" 或 "app:{appID}:role" - // 返回: map[实例ID]权限等级 (如 {"vbase": 2, "other": 7}) - ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) +### 5.3 权限等级常量 - // ListUsers 查询特定资源的所有协作者及其权限 - // 用于解决 "查看这个应用有哪些成员" 等场景 - // permissionID: 资源实例权限码,如 "app:vbase" - // 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7}) - ListUsers(ctx context.Context, permissionID string) (map[string]int, error) -} +```go +const ( + LevelNone = 0 + LevelCreate = 1 // 001 创建 (检查奇数层) + LevelRead = 2 // 010 读取 (检查偶数层) + LevelWrite = 4 // 100 写入 (检查偶数层) + LevelReadWrite = 6 // 110 读写 (检查偶数层) + LevelAdmin = 7 // 111 管理员 (完全控制) +) +``` -// ========== 数据结构 ========== +### 5.4 Permission 数据模型 -// Permission 用户权限 +```go type Permission struct { - ID string `json:"id"` - UserID string `json:"user_id"` - RoleID string `json:"role_id"` - PermissionID string `json:"permission_id"` - Level int `json:"level"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + ID string `json:"id"` + Scope string `json:"scope"` + UserID *string `json:"user_id"` + RoleID *string `json:"role_id"` + PermissionID string `json:"permission_id"` + Level int `json:"level"` + ExpireAt *time.Time `json:"expire_at"` } ``` @@ -228,10 +195,10 @@ var Router = vigo.NewRouter() func init() { // 创建应用 - 需要系统级 app 权限 - Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) + Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp) // 超级管理员接口 - Router.Get("/admin/users", cfg.Auth.PermAdmin("*"), AdminListUsers) + Router.Get("/admin/users", cfg.Auth.RequireAdmin("*"), AdminListUsers) } ``` @@ -241,16 +208,16 @@ func init() { func init() { // 从路径参数获取 appID (默认) // GET /apps/{appID} - Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) + Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp) // 从 query 参数获取 // GET /apps?appID=xxx - Router.Get("/apps", cfg.Auth.PermRead("app:{appID@query}"), GetApp) + Router.Get("/apps", cfg.Auth.RequireRead("app:{appID@query}"), GetApp) // 多层嵌套 // GET /apps/{appID}/roles/{roleID} Router.Get("/apps/{appID}/roles/{roleID}", - cfg.Auth.PermRead("app:{appID}:role:{roleID}"), + cfg.Auth.RequireRead("app:{appID}:role:{roleID}"), GetRole, ) } @@ -263,21 +230,21 @@ var Router = vigo.NewRouter().Use(cfg.Auth.Login()) func init() { // 创建应用 - 系统级权限 - Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) + Router.Post("/apps", cfg.Auth.RequireCreate("app"), CreateApp) // 列出我的应用 - 只需登录 Router.Get("/apps", ListMyApps) // 应用操作 - 从路径获取 - Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) - Router.Put("/apps/{appID}", cfg.Auth.PermWrite("app:{appID}"), UpdateApp) - Router.Delete("/apps/{appID}", cfg.Auth.PermAdmin("app:{appID}"), DeleteApp) + Router.Get("/apps/{appID}", cfg.Auth.RequireRead("app:{appID}"), GetApp) + Router.Put("/apps/{appID}", cfg.Auth.RequireWrite("app:{appID}"), UpdateApp) + Router.Delete("/apps/{appID}", cfg.Auth.RequireAdmin("app:{appID}"), DeleteApp) // 角色操作 - 嵌套资源 - Router.Post("/apps/{appID}/roles", cfg.Auth.PermCreate("app:{appID}:role"), CreateRole) - Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.PermRead("app:{appID}:role:{roleID}"), GetRole) - Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.PermWrite("app:{appID}:role:{roleID}"), UpdateRole) - Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.PermAdmin("app:{appID}:role:{roleID}"), DeleteRole) + Router.Post("/apps/{appID}/roles", cfg.Auth.RequireCreate("app:{appID}:role"), CreateRole) + Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireRead("app:{appID}:role:{roleID}"), GetRole) + Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireWrite("app:{appID}:role:{roleID}"), UpdateRole) + Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.RequireAdmin("app:{appID}:role:{roleID}"), DeleteRole) } ``` @@ -324,7 +291,7 @@ func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) { 1. **全量管理接口**(如后台管理系统): - - 使用 `PermAdmin("*")` 或 `PermAdmin("app:*")`。 + - 使用 `RequireAdmin("*")` 或 `RequireAdmin("app:*")`。 - 这类接口返回所有数据,必须严格控制权限。 2. **用户侧列表/搜索**(如“我的应用”): diff --git a/docs/configuration.md b/docs/configuration.md index dcf9868..c9a0e9f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,66 +13,76 @@ ## 一、本地配置(Local Config) -存储在 `cfg/cfg.go`,仅包含系统启动必需的配置项。 +存储在 `cfg/cfg.go`,仅包含系统启动必需的配置项。默认使用 SQLite 内存数据库,无需任何外部依赖即可运行。 -### 1.1 配置项列表 +### 1.1 YAML 配置示例 ```yaml # config.yaml 示例 -dsn: "root:123456@tcp(127.0.0.1:3306)/vbase?charset=utf8&parseTime=True&loc=Local" -db: "mysql" +db: + type: "sqlite" # sqlite / mysql / postgres + dsn: "/tmp/vbase.db" # 数据库连接字符串 + prefix: "vb_" # 表名前缀 redis: - addr: "localhost:6379" + addr: "memory" # memory(内存模式)或 redis://localhost:6379 password: "" db: 0 -# 系统密钥,用于加密敏感数据(JWT、数据库加密字段等) -# 生产环境务必修改,建议 32 位以上随机字符串 key: "your-secret-key-change-in-production-min-32-characters" -host: "0.0.0.0" -port: 8080 +jwt: + secret: "" # JWT 密钥(为空则使用 Key) + access_expiry: 900 # Access Token 有效期(秒),默认 15m + refresh_expiry: 2592000 # Refresh Token 有效期(秒),默认 30d + issuer: "vbase" # JWT 签发者 + cookie_path: "/" # Cookie Path,默认 / 全站 + cookie_prefix: "vb_" # Cookie Key 前缀 -storage_path: "./data" - -# 初始管理员配置(无人值守部署时使用) -# 当用户表为空且 username/password 都有值时,启动会自动创建该管理员 init_admin: - username: "admin" - password: "" # 生产环境务必设置强密码 + username: "admin" # 管理员用户名 + password: "" # 密码(为空则首注成为 admin) email: "admin@example.com" ``` ### 1.2 配置项说明 -| 配置项 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| `dsn` | string | 是 | 数据库连接字符串 | -| `db` | string | 是 | 数据库类型:mysql/postgres/sqlite | -| `redis` | object | 是 | Redis 配置,`addr: "memory"` 使用内存模式 | -| `key` | string | 是 | 系统密钥,用于加密敏感数据(建议 32 位以上) | -| `host` | string | 否 | 服务监听地址,默认 `0.0.0.0` | -| `port` | int | 否 | 服务监听端口,默认 `8080` | -| `storage_path` | string | 否 | 文件存储路径,默认 `./data` | -| `init_admin` | object | 否 | 初始管理员配置(无人值守部署时使用) | +| 配置项 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `db.type` | string | 否 | `sqlite` | 数据库类型:sqlite / mysql / postgres | +| `db.dsn` | string | 否 | `/tmp/vbase.db` | 数据库连接字符串 | +| `db.prefix` | string | 否 | `vb_` | 表名前缀 | +| `redis.addr` | string | 否 | `memory` | Redis 地址,`memory` 使用内存模式 | +| `redis.password` | string | 否 | — | Redis 密码 | +| `redis.db` | int | 否 | `0` | Redis 数据库编号 | +| `key` | string | 是 | — | 系统密钥,用于加密敏感数据(建议 32 位以上) | +| `jwt.secret` | string | 否 | — | JWT 密钥(为空则使用 `key`) | +| `jwt.access_expiry` | duration | 否 | `15m` | Access Token 有效期 | +| `jwt.refresh_expiry` | duration | 否 | `30d` | Refresh Token 有效期 | +| `jwt.issuer` | string | 否 | `vbase` | JWT 签发者名称 | +| `jwt.cookie_path` | string | 否 | `/` | Cookie Path 限定路径 | +| `jwt.cookie_prefix` | string | 否 | `vb_` | Cookie Key 前缀 | + +### 1.3 Token 传送方式 -**`init_admin` 详细说明:** +VBase 支持三种 Token 提取方式,优先级从高到低: + +1. **Cookie**(HttpOnly,浏览器自动携带):Key 为 `{cookie_prefix}access` +2. **Authorization Header**:`Bearer ` +3. **Query 参数**:`?access_token=` + +### 1.4 初始管理员配置 系统提供两种互斥的初始化管理员方式: | 方式 | 触发条件 | 适用场景 | |------|----------|----------| -| **自动创建**(本配置) | `username` 和 `password` 均已配置 | 无人值守部署、容器化环境 | -| **首注成为 admin** | `password` 为空,首个用户注册时 | 交互式部署、开发测试 | +| **自动创建** | `init_admin.username` 和 `init_admin.password` 均已配置 | 无人值守部署、容器化环境 | +| **首注成为 admin** | `init_admin.password` 为空 | 交互式部署、开发测试 | **方式一:配置 init_admin(推荐用于生产)** -当满足以下条件时,**系统启动时**自动创建管理员: -- 用户表为空(首次部署) -- `init_admin.username` 和 `init_admin.password` 均已配置 - ```yaml init_admin: username: "admin" @@ -80,21 +90,29 @@ init_admin: email: "admin@example.com" ``` -效果:启动时自动创建 admin,后续注册的用户均为普通 user。 +启动时自动创建管理员,后续注册的用户均为普通 user。 **方式二:首个注册用户成为 admin(默认行为)** -当 `init_admin.password` 为空时,保持原有逻辑: -- 第一个注册用户自动被授予 `admin` 角色 -- 从第二个用户开始均为普通 user +当 `init_admin.password` 为空时,第一个注册用户自动被授予 `admin` 角色。 + +### 1.5 运行时钩子 + +```go +// OnUserCreate 用户创建成功后的回调钩子 +// 可用于发送欢迎邮件、初始化用户资源等 +cfg.OnUserCreate = func(userID string) error { + // 自定义逻辑 + return nil +} +``` + +--- + +## 二、线上配置(Settings) -**字段说明:** +线上配置存储在数据库 `settings` 表中,通过管理后台或 API 实时修改,无需重启。 -| 字段 | 类型 | 必填 | 说明 | -|------|------|------|------| -| `username` | string | 是 | 管理员用户名 | -| `password` | string | 条件 | 密码(配置则启用方式一,为空则启用方式二) | -| `email` | string | 否 | 邮箱地址 | +包括:应用名称、登录方式、密码字段配置、验证码开关、邮件/SMS 开关、OAuth 提供商等。 -**其他注意事项:** -- 仅在没有用户时生效,已有用户则跳过 +详见 API 的 `/api/settings` 和 `/api/info` 接口。 diff --git a/docs/design.md b/docs/design.md index 562468d..764f067 100644 --- a/docs/design.md +++ b/docs/design.md @@ -2,16 +2,17 @@ ## 1. 概览 -VBase 是一个基于 Golang 的高性能后端基础框架,旨在提供一套标准化的用户管理、作用域权限控制(Scoped RBAC)和 OAuth2 认证服务。项目采用分层架构设计,基于 `vigo` 框架构建,强调代码的可维护性、扩展性和安全性。 +VBase 是一个基于 Golang 的高性能后端基础框架,旨在提供一套标准化的用户管理、作用域权限控制(Scoped RBAC)和 OAuth2 认证服务。项目采用分层架构设计,基于 `vigo` 框架构建,支持 Session-based 认证、Cookie 令牌传送以及短信/邮箱验证码注册,强调代码的可维护性、扩展性和安全性。 ## 2. 技术栈 -- **语言**: Golang 1.22+ +- **语言**: Golang 1.24+ - **Web 框架**: [vigo](https://github.com/veypi/vigo) (基于洋葱模型的高性能框架) -- **ORM**: GORM (支持 MySQL, PostgreSQL, SQLite 等) -- **配置管理**: `cfg` 包 (支持环境变量、配置文件) -- **认证**: JWT (JSON Web Token) + OAuth2 -- **数据库**: 关系型数据库 (MySQL/PostgreSQL) +- **ORM**: GORM (支持 MySQL, PostgreSQL, SQLite) +- **配置管理**: `cfg` 包 (YAML 配置文件 + 环境变量) +- **认证**: Session + JWT (Access/Refresh Token)、Cookie 令牌传送 +- **多因素认证**: 短信/邮箱验证码注册 +- **数据库**: 默认为 SQLite,可按需切换 MySQL/PostgreSQL ## 3. 系统架构 @@ -32,12 +33,15 @@ Response <- [Global After Middleware] <------------------------------------+ │ ├── oauth/ # OAuth2 Provider 接口 │ ├── role/ # 角色管理接口 │ ├── user/ # 用户管理接口 +│ ├── settings/ # 系统设置接口 +│ ├── verification/ # 短信/邮箱验证码接口 │ └── init.go # 路由聚合与全局中间件配置 -├── auth/ # 核心权限控制模块 (Scoped RBAC 实现) -├── cfg/ # 配置与基础设施 (DB, Redis, Log) +├── auth/ # 核心权限控制模块 (Scoped RBAC + Session 管理) +├── cfg/ # 配置与基础设施 (DB, Redis, JWT) ├── models/ # 数据模型定义 (GORM 结构体) -├── libs/ # 通用工具库 +├── libs/ # 通用工具库 (jwt, cache, crypto, sms, email) ├── cli/ # 命令行入口 +├── ui/ # 嵌入式管理界面 (vhtml) └── docs/ # 文档 ``` @@ -63,7 +67,7 @@ Auth 模块是系统的核心安全组件,实现了基于作用域(Scope) 系统提供 `auth.Auth` 接口和 `auth.Factory` 工厂,支持多应用集成: 1. **获取实例**: `myAuth := auth.Factory.New("my_scope")` -2. **基础权限**: `myAuth.PermRead("resource:id")` - 检查读取权限。 +2. **基础权限**: `myAuth.RequireRead("resource:id")` - 检查读取权限。 3. **层级检查**: - **Admin (Level 7)**: 拥有 `*` 或前缀匹配权限(向下继承)。 - **Standard**: 精确匹配且满足 Level 要求。 @@ -71,14 +75,14 @@ Auth 模块是系统的核心安全组件,实现了基于作用域(Scope) #### 4.1.3 中间件流程 -1. **LoginMiddleware**: 解析 JWT Token,提取 UserID。 -2. **PermMiddleware**: 在业务 Handler 执行前拦截请求,调用 `Check` 进行鉴权。 +1. **Login 中间件**: 从 Cookie / Authorization Header / Query 提取 Token,解析 JWT 获取 UserID 和 SessionID,验证 Session 有效性(Redis 缓存 + DB 回退)。 +2. **Perm 中间件**: 在业务 Handler 执行前拦截请求,调用 `Check` 进行鉴权,支持动态权限码解析(从 Path/Query/Header 获取参数)。 ### 4.2 用户体系 (User Module) -- **User**: 核心用户实体,包含基本信息 (昵称、头像、邮箱等)。 -- **Identity**: 认证信息表,支持多种登录方式 (密码、OAuth、验证码等) 关联到同一用户。 -- **Session**: 用户会话管理,用于记录登录状态和刷新 Token。 +- **User**: 核心用户实体,包含基本信息(用户名、密码、邮箱、手机号等),支持邮箱/手机验证状态追踪。 +- **Identity**: 认证信息表,支持多种登录方式(密码、OAuth 第三方登录)关联到同一用户。 +- **Session**: 登录会话管理,每次登录创建一个 Session,记录设备信息和 IP,支持版本号轮换(防止并发刷新互踢)和单独吊销。 ### 4.3 OAuth2 服务 (OAuth Module) @@ -91,12 +95,16 @@ Auth 模块是系统的核心安全组件,实现了基于作用域(Scope) ## 5. 接口规范 -### 5.1 请求处理 +### 5.1 API 文档 + +路由启用了 `EnableApiDoc()`,可通过 `/_api.json` 查看所有注册接口的完整文档(包括参数、响应、中间件信息)。 + +### 5.2 请求处理 - **参数解析**: 利用 `vigo` 的泛型 Handler 机制,自动解析 Query, Path, Header, JSON Body 参数到结构体。 - **验证**: 结构体 Tag (`src`, `json`, `default`) 定义参数源和默认值。 -### 5.2 响应格式 +### 5.3 响应格式 统一使用 JSON 格式响应,由全局后置中间件 `common.JsonResponse` 处理: @@ -118,19 +126,30 @@ Auth 模块是系统的核心安全组件,实现了基于作用域(Scope) ## 6. 数据库设计 -推荐使用 `vigo.Model` 作为基类,统一包含以下字段: +所有模型以 `vigo.Model` 作为基类,统一包含以下字段: - `ID`: UUID (varchar(36)) - `CreatedAt`: 创建时间 - `UpdatedAt`: 更新时间 -- `DeletedAt`: 软删除标记 +- `DeletedAt`: 软删除标记(GORM) + +### 6.1 核心表 + +| 表 | 说明 | +|----|------| +| `users` | 用户表(用户名、密码、邮箱、手机、验证状态) | +| `identities` | 第三方身份绑定表 | +| `sessions` | 登录会话表(设备信息、IP、版本号轮换) | +| `roles` | 角色表(系统/自定义) | +| `permissions` | 权限表(作用域隔离、支持按用户/角色授权) | +| `user_roles` | 用户-角色关联表 | -所有模型在 `models/init.go` 中注册,支持服务启动时自动迁移 (`AutoMigrate`)。 +所有模型在 `models/init.go` 中注册,启动时通过 `Init()` 自动迁移 (`AutoMigrate`)。 ## 7. 部署与运维 -- **配置**: 支持 `.env` 文件和环境变量覆盖。 -- **构建**: `go build -o vbase cli/main.go` -- **运行**: `./vbase -p 8080` +- **配置**: 支持 YAML 配置文件和环境变量覆盖,默认 SQLite 无需额外配置即可运行。 +- **运行**: `go run ./cli/*.go -p 4000` 或 `make run` +- **数据库迁移**: `go run ./cli/main.go db migrate` ## 8. 开发规范 diff --git a/docs/integration.md b/docs/integration.md index a843350..02ca178 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -10,121 +10,141 @@ go get github.com/veypi/vbase ## 2. 初始化 -在你的应用入口文件(如 `main.go` 或 `init.go`)中进行初始化。 +在你的应用入口文件(如 `main.go`)中进行初始化。 ```go package main import ( - "github.com/veypi/vbase" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/auth" - "github.com/veypi/vigo" + "github.com/veypi/vbase" + "github.com/veypi/vbase/cfg" + "github.com/veypi/vigo" ) -// 1. 创建你的应用 Router -var Router = vigo.NewRouter() +func main() { + // cfg.Global 已包含默认配置(SQLite 内存数据库) + // 可通过环境变量或 YAML 文件覆盖 + app := vigo.New("myapp", vbase.Router, cfg.Global, vigo.WithInit(vbase.Init)) + panic(app.Run()) +} +``` -// 2. 创建你的应用 Auth 实例 (指定 Scope) -var AppAuth = auth.Factory.New("my_app") +VBase 自带嵌入式管理界面,启动后访问 `/vb/` 即可使用。 -func init() { - // 3. 挂载 VBase 路由 (提供登录、用户管理等基础接口) - Router.Extend("/vb", vbase.Router) +### 2.1 挂载路由 - // 4. 配置数据库连接 (如果尚未配置) - // export DSN="root:password@tcp(127.0.0.1:3306)/myapp?charset=utf8&parseTime=True" -} +VBase Router 已内置所有 API 路由(`/api/auth`、`/api/users`、`/api/roles`、`/api/oauth`、`/api/settings` 等)和 UI 路由(`/vb/`、`/v/`、`/vhtml/`)。 -func main() { - // 5. 初始化数据库迁移 - if err := vbase.Init(); err != nil { - panic(err) - } - - // 6. 启动服务 - app := vigo.New("myapp", Router, cfg.Global, nil) - panic(app.Run()) +如果你的应用需要自定义路由,将 VBase Router 挂载到你的 Router 上: + +```go +var Router = vigo.NewRouter() + +func init() { + // 将 VBase 挂载到 /vb 前缀 + Router.Extend("/vb", vbase.Router) } ``` ## 3. 使用权限系统 -### 3.1 定义路由权限 +VBase 通过 `cfg.Auth` 提供全局权限管理实例。所有中间件方法前缀为 `Require*`。 -使用 `AppAuth` 实例的中间件方法来保护你的路由。 +### 3.1 定义路由权限 ```go func init() { - // 需要登录 - Router.Use(AppAuth.Login()) + // 所有路由需要登录 + Router.Use(cfg.Auth.Login()) - // 资源操作示例 - // 假设你的资源是 "article" - - // 创建文章 (需要 "article" 的创建权限 Level 1) - Router.Post("/articles", AppAuth.PermCreate("article"), CreateArticle) + // 资源操作示例 + // 创建文章 (需要 "article" 的创建权限 Level 1) + Router.Post("/articles", cfg.Auth.RequireCreate("article"), CreateArticle) - // 读取文章 (需要 "article:{id}" 的读取权限 Level 2) - Router.Get("/articles/{id}", AppAuth.PermRead("article:{id}"), GetArticle) + // 读取文章 (需要对特定实例的读取权限 Level 2) + Router.Get("/articles/{id}", cfg.Auth.RequireRead("article:{id}"), GetArticle) - // 更新文章 (需要 "article:{id}" 的写入权限 Level 4) - Router.Put("/articles/{id}", AppAuth.PermWrite("article:{id}"), UpdateArticle) + // 更新文章 (需要对特定实例的写入权限 Level 4) + Router.Put("/articles/{id}", cfg.Auth.RequireWrite("article:{id}"), UpdateArticle) - // 删除文章 (需要 "article:{id}" 的管理员权限 Level 7) - Router.Delete("/articles/{id}", AppAuth.PermAdmin("article:{id}"), DeleteArticle) + // 删除文章 (需要对特定实例的管理员权限 Level 7) + Router.Delete("/articles/{id}", cfg.Auth.RequireAdmin("article:{id}"), DeleteArticle) } ``` -### 3.2 业务逻辑授权 +### 3.2 权限码动态解析 + +权限码支持动态占位符,从不同来源获取值: + +| 语法 | 来源 | 示例 | +|------|------|------| +| `{key}` 或 `{key@ctx}` | Context | `{appID}` | +| `{key@path}` | 路径参数 | `{appID@path}` | +| `{key@query}` | Query 参数 | `{appID@query}` | +| `{key@header}` | Header | `{Authorization@header}` | -在创建资源时,通常需要授予创建者管理权限。 +### 3.3 业务逻辑授权 + +在创建资源时,授予创建者管理权限: ```go func CreateArticle(x *vigo.X, req *ArticleReq) (*Article, error) { - // 1. 获取当前用户 - userID := AppAuth.UserID(x) - - // 2. 创建资源逻辑... - article := &Article{Title: req.Title, AuthorID: userID} - db.Create(article) - - // 3. 授予权限 - // 授予用户对该文章的管理员权限 (Level 7) - // 这样用户后续可以读取、修改、删除该文章 - permissionID := fmt.Sprintf("article:%s", article.ID) - if err := AppAuth.Grant(x.Context(), userID, permissionID, auth.LevelAdmin); err != nil { - // 处理错误 (通常记录日志) - x.Log.Error("Failed to grant permission", "err", err) - } - - return article, nil + userID := cfg.Auth.UserID(x) + + article := &Article{Title: req.Title, AuthorID: userID} + cfg.DB().Create(article) + + // 授予用户对该文章的管理员权限 (Level 7) + permID := "article:" + article.ID + cfg.Auth.Grant(x.Context(), userID, permID, auth.LevelAdmin) + + return article, nil } ``` -### 3.3 复杂场景:项目成员管理 +### 3.4 多 Scope 权限隔离 -假设你有一个项目管理系统,用户可以被邀请加入项目。 +不同业务模块通过 Scope 实现权限隔离: ```go -// 邀请成员接口 -func InviteMember(x *vigo.X, req *InviteReq) error { - // 检查当前用户是否有权限邀请 (通常需要管理员权限) - // 这一步由路由中间件 PermAdmin("project:{id}") 保证 - - // 授予被邀请人读取权限 (Level 2) - permID := fmt.Sprintf("project:%s", req.ProjectID) - return AppAuth.Grant(x.Context(), req.TargetUserID, permID, auth.LevelRead) -} +// 为不同应用创建独立的 Provider +var AppA_Auth = auth.Factory.New("app_a") +var AppB_Auth = auth.Factory.New("app_b") + +// AppA 的用户权限不会影响 AppB ``` -## 4. 配置说明 +## 4. Session 管理 + +VBase 使用 Session + JWT 双 Token 机制: + +- **Access Token**:短期有效(默认 15 分钟),用于 API 认证 +- **Refresh Token**:长期有效(默认 30 天),用于刷新 Access Token +- **Session**:记录登录设备信息和 IP,支持版本号轮换防止并发刷新互踢 -VBase 使用 `cfg` 包管理配置,支持环境变量。 +Token 默认通过 HttpOnly Cookie 传送。Session 可通过 `auth.RevokeSession`、`auth.RevokeOtherSessions`、`auth.RevokeAllSessions` 管理。 -- `DB_DSN`: 数据库连接字符串 -- `DB_TYPE`: 数据库类型 (mysql, postgres, sqlite) -- `JWT_SECRET`: JWT 签名密钥 (生产环境务必修改) -- `JWT_ISSUER`: Token 签发者 +## 5. 配置说明 + +VBase 默认使用 SQLite 内存数据库(`/tmp/vbase.db`),无需任何外部依赖即可运行。 + +配置文件示例(`config.yaml`): + +```yaml +db: + type: "sqlite" + dsn: "/tmp/vbase.db" + +redis: + addr: "memory" + +key: "your-secret-key-min-32-characters" + +jwt: + access_expiry: 900 + refresh_expiry: 2592000 + issuer: "vbase" + cookie_prefix: "vb_" +``` 更多配置详情请参考 [配置文档](./configuration.md)。 diff --git a/init.go b/init.go index c1a9c97..678e826 100644 --- a/init.go +++ b/init.go @@ -18,7 +18,7 @@ import ( "github.com/veypi/vigo" ) -const Version = "v1.1.1" +const Version = "v1.2.0" var Router = vigo.NewRouter().EnableApiDoc() var ( diff --git a/tests/README.md b/tests/README.md index 468a04d..5d2223f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,9 +22,9 @@ go test -v ./tests/auth_test.go ### 1. 全局设置 (`main_test.go`) - **TestMain**: 测试套件的入口点。 - - 初始化 Vigo 应用程序。 + - 初始化 Vigo 应用程序(带 VBase Router + Auth)。 - 设置内存 SQLite 数据库 (`:memory:`)。 - - 模拟 Redis 客户端。 + - 模拟 Redis 客户端(支持 Session 缓存验证)。 - 运行所有测试。 - 执行后无需清理资源。 diff --git a/tests/SECURITY_TEST_REPORT.md b/tests/SECURITY_TEST_REPORT.md index 8431760..856d215 100644 --- a/tests/SECURITY_TEST_REPORT.md +++ b/tests/SECURITY_TEST_REPORT.md @@ -1,7 +1,17 @@ # VBase 安全测试报告 ## 测试执行时间 -2026-02-18 +2026-02-18(原始) / 2026-06-06(v1.2.0 更新) + +## v1.2.0 修复状态 + +| 问题 | 严重度 | 状态 | +|------|--------|------| +| OAuth 客户端访问控制缺失 | 高 | 已修复 | +| 输入验证缺失 | 高 | 已修复 | +| Admin 无法访问所有组织 | 高 | 已修复(Session 认证替代) | +| 并发操作问题 | 中 | 部分修复(Session 版本号轮换) | +| 速率限制缺失 | 中 | 待实现 | ## 测试概述 本次测试针对 VBase 身份认证和权限管理系统进行了全面的安全测试,包括权限系统、多租户隔离、OAuth2 安全、并发安全和边界情况测试。