imean-service-engine-htmx-plugin 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -138,12 +138,24 @@ const adminPlugin = new HtmxAdminPlugin({
138
138
  icon: "👥"
139
139
  }
140
140
  ],
141
- getUserInfo: async (ctx) => { // 可选,获取用户信息
142
- return {
143
- name: "管理员",
144
- email: "admin@example.com"
145
- };
146
- }
141
+ authProvider: { // 可选,认证和权限提供者
142
+ cookieKey: "auth_token", // Cookie 键名(默认 "auth_token")
143
+ async tokenToUser(token: string, ctx: Context) {
144
+ // 从 token 获取用户信息
145
+ const user = await getUserByToken(token);
146
+ return {
147
+ name: user.name,
148
+ email: user.email,
149
+ avatar: user.avatar,
150
+ permissions: user.permissions, // 用户权限列表
151
+ };
152
+ },
153
+ // checkPermission 可选,不提供则使用默认实现
154
+ async checkPermission(user, operation, context) {
155
+ // 自定义权限检查逻辑
156
+ return { allowed: true };
157
+ },
158
+ },
147
159
  });
148
160
  ```
149
161
 
@@ -1758,27 +1770,6 @@ class UserDetailModule extends DetailPageModule<User> {
1758
1770
  }
1759
1771
  ```
1760
1772
 
1761
- ### 用户信息
1762
-
1763
- #### 获取用户信息
1764
-
1765
- ```typescript
1766
- const adminPlugin = new HtmxAdminPlugin({
1767
- getUserInfo: async (ctx: Context) => {
1768
- // 从 session 或 token 中获取用户信息
1769
- const userId = ctx.req.header("X-User-Id");
1770
- const user = await getUserById(userId);
1771
-
1772
- return {
1773
- name: user.name,
1774
- email: user.email,
1775
- avatar: user.avatar,
1776
- };
1777
- },
1778
- });
1779
- ```
1780
-
1781
- 用户信息会显示在页面右上角。
1782
1773
 
1783
1774
  ### 侧边栏折叠
1784
1775
 
@@ -1804,8 +1795,49 @@ interface HtmxAdminPluginOptions {
1804
1795
  homePath?: string;
1805
1796
  /** 导航配置(集中式声明导航结构,支持嵌套) */
1806
1797
  navigation?: NavItemConfig[];
1807
- /** 获取用户信息的函数(用于显示用户信息) */
1808
- getUserInfo?: (ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
1798
+ /** 认证提供者(用于鉴权和权限控制) */
1799
+ authProvider?: AuthProvider;
1800
+ }
1801
+ ```
1802
+
1803
+ ### AuthProvider
1804
+
1805
+ ```typescript
1806
+ interface AuthProvider {
1807
+ /** Cookie 键名(默认 "auth_token") */
1808
+ cookieKey?: string;
1809
+ /** 从 token 获取用户信息(必需) */
1810
+ tokenToUser: (token: string, ctx: Context) => Promise<UserInfo | null>;
1811
+ /** 自定义权限检查(可选,不提供则使用默认实现) */
1812
+ checkPermission?: (
1813
+ user: UserInfo | null,
1814
+ operation: string,
1815
+ context: HtmxAdminContext
1816
+ ) => PermissionResult | Promise<PermissionResult>;
1817
+ }
1818
+ ```
1819
+
1820
+ ### UserInfo
1821
+
1822
+ ```typescript
1823
+ interface UserInfo {
1824
+ name?: string;
1825
+ avatar?: string;
1826
+ email?: string;
1827
+ /** 用户权限列表(支持通配符和禁止权限) */
1828
+ permissions?: string[];
1829
+ [key: string]: any;
1830
+ }
1831
+ ```
1832
+
1833
+ ### PermissionResult
1834
+
1835
+ ```typescript
1836
+ interface PermissionResult {
1837
+ allowed: boolean;
1838
+ message?: string;
1839
+ operationId?: string;
1840
+ redirectUrl?: string;
1809
1841
  }
1810
1842
  ```
1811
1843
 
@@ -2257,9 +2289,11 @@ class UserListModule extends ListPageModule<User> {
2257
2289
 
2258
2290
  ### 7. 安全性
2259
2291
 
2260
- - 在数据源层面实现权限检查
2261
- - 验证用户输入
2262
- - 使用参数化查询防止 SQL 注入
2292
+ - **认证和权限**: 通过 `AuthProvider` 实现认证和权限控制
2293
+ - **权限声明**: 模块通过 `getRequiredPermission()` 声明所需权限
2294
+ - **权限匹配**: 支持通配符和禁止权限,提供灵活的权限管理
2295
+ - **输入验证**: 在数据源层面验证输入
2296
+ - **SQL 注入防护**: 使用参数化查询防止 SQL 注入
2263
2297
 
2264
2298
  ## 示例项目
2265
2299
 
package/dist/index.d.mts CHANGED
@@ -27,8 +27,59 @@ interface UserInfo {
27
27
  name?: string;
28
28
  avatar?: string;
29
29
  email?: string;
30
+ /** 用户权限列表(支持通配符,如 "users.*", "users.read", "deny:users.delete") */
31
+ permissions?: string[];
30
32
  [key: string]: any;
31
33
  }
34
+ /**
35
+ * 权限校验结果
36
+ */
37
+ interface PermissionResult {
38
+ /** 是否允许访问 */
39
+ allowed: boolean;
40
+ /** 如果拒绝访问,可选的错误消息 */
41
+ message?: string;
42
+ /** 如果拒绝访问,可选的重定向URL */
43
+ redirectUrl?: string;
44
+ /** 被拒绝的操作ID(用于显示详细信息) */
45
+ operationId?: string;
46
+ }
47
+ /**
48
+ * 认证提供者接口
49
+ */
50
+ interface AuthProvider {
51
+ /** Cookie 中存储 token 的 key(默认 "auth_token") */
52
+ cookieKey?: string;
53
+ /** 将 token 转换为用户信息 */
54
+ tokenToUser: (token: string, ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
55
+ /** 权限校验钩子(可选,如果不提供则使用默认实现) */
56
+ checkPermission?: (user: UserInfo | null, operation: string, context: any) => PermissionResult | Promise<PermissionResult>;
57
+ }
58
+ /**
59
+ * 操作类型
60
+ */
61
+ type OperationType = "read" | "create" | "edit" | "delete";
62
+ /**
63
+ * 操作信息
64
+ */
65
+ interface OperationInfo {
66
+ /** 操作ID(格式:moduleName.operation) */
67
+ operationId: string;
68
+ /** 模块名 */
69
+ moduleName: string;
70
+ /** 操作类型 */
71
+ operationType: OperationType;
72
+ /** 模块类型 */
73
+ moduleType: ModuleType;
74
+ /** 模块标题 */
75
+ moduleTitle?: string;
76
+ /** 模块描述 */
77
+ moduleDescription?: string;
78
+ /** HTTP 方法 */
79
+ httpMethod: string;
80
+ /** 路由路径 */
81
+ routePath: string;
82
+ }
32
83
  /**
33
84
  * 模块类型信息
34
85
  */
@@ -69,8 +120,8 @@ interface HtmxAdminPluginOptions {
69
120
  homePath?: string;
70
121
  /** 导航配置(集中式声明导航结构,支持嵌套) */
71
122
  navigation?: NavItemConfig[];
72
- /** 获取用户信息的函数(用于显示用户信息) */
73
- getUserInfo?: (ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
123
+ /** 认证提供者(用于鉴权和权限控制) */
124
+ authProvider?: AuthProvider;
74
125
  }
75
126
  /**
76
127
  * 模块类型
@@ -216,6 +267,8 @@ declare class HtmxAdminContext {
216
267
  readonly moduleMetadata: ModuleTypeInfo;
217
268
  /** 插件选项 */
218
269
  readonly pluginOptions: Required<HtmxAdminPluginOptions>;
270
+ /** 服务名(用于生成权限ID) */
271
+ readonly serviceName: string;
219
272
  /** Hono Context */
220
273
  readonly ctx: Context;
221
274
  /** 之前的模块名 */
@@ -240,7 +293,7 @@ declare class HtmxAdminContext {
240
293
  redirectUrl?: string;
241
294
  /** 是否需要刷新页面(用于 HX-Refresh) */
242
295
  refresh: boolean;
243
- constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>);
296
+ constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>, serviceName?: string);
244
297
  /**
245
298
  * 发送通知
246
299
  * 通知将在响应时通过 OOB 更新到错误容器
@@ -334,6 +387,20 @@ declare abstract class PageModule {
334
387
  * 默认实现:首页 => 自定义页面
335
388
  */
336
389
  getBreadcrumbs(): BreadcrumbItem[];
390
+ /**
391
+ * 获取所需权限
392
+ * 子类可以重写此方法来声明页面所需的权限
393
+ *
394
+ * 返回值说明:
395
+ * - 返回空字符串 "" 或 null 或 undefined:表示开放访问(无需权限)
396
+ * - 返回权限字符串:表示需要该权限,如 "users.read", "users.*"
397
+ *
398
+ * 默认实现:返回空字符串(开放访问)
399
+ *
400
+ * @param ctx Hono Context(可选,用于根据请求动态决定权限)
401
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
402
+ */
403
+ getRequiredPermission?(ctx?: any): string | null | undefined;
337
404
  /**
338
405
  * 处理请求的统一入口(由 RouteHandler 调用)
339
406
  * 默认实现:直接调用 render 方法
@@ -370,6 +437,15 @@ declare abstract class PageModule {
370
437
  declare abstract class DetailPageModule<T = any> extends PageModule {
371
438
  /** ID 字段名(默认 "id") */
372
439
  protected readonly idField: string;
440
+ /**
441
+ * 获取所需权限
442
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
443
+ * 子类可以重写此方法来自定义权限要求
444
+ *
445
+ * @param ctx Hono Context(可选)
446
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
447
+ */
448
+ getRequiredPermission(ctx?: any): string | null | undefined;
373
449
  /**
374
450
  * 获取数据源(抽象方法,必须实现)
375
451
  * 子类必须实现此方法来提供数据源
@@ -435,6 +511,17 @@ declare abstract class FormPageModule<T = any> extends PageModule {
435
511
  /** Zod Schema(可选,如果提供则自动生成表单字段和校验) */
436
512
  protected schema?: z.ZodObject<any>;
437
513
  constructor(schema?: z.ZodObject<any>);
514
+ /**
515
+ * 获取所需权限
516
+ * 根据是新建还是编辑返回不同的权限要求
517
+ * - 新建:{serviceName}.{moduleName}.create
518
+ * - 编辑:{serviceName}.{moduleName}.edit
519
+ * 子类可以重写此方法来自定义权限要求
520
+ *
521
+ * @param ctx Hono Context(可选)
522
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
523
+ */
524
+ getRequiredPermission(ctx?: any): string | null | undefined;
438
525
  /**
439
526
  * 获取数据源(抽象方法,必须实现)
440
527
  * 子类必须实现此方法来提供数据源
@@ -558,6 +645,15 @@ declare abstract class ListPageModule<T extends {
558
645
  }> extends PageModule {
559
646
  /** ID 字段名(默认 "id") */
560
647
  protected readonly idField: string;
648
+ /**
649
+ * 获取所需权限
650
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
651
+ * 子类可以重写此方法来自定义权限要求
652
+ *
653
+ * @param ctx Hono Context(可选)
654
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
655
+ */
656
+ getRequiredPermission(ctx?: any): string | null | undefined;
561
657
  /**
562
658
  * 获取数据源(抽象方法,必须实现)
563
659
  * 子类必须实现此方法来提供数据源
@@ -688,7 +784,9 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
688
784
  private engine;
689
785
  private hono;
690
786
  private options;
787
+ private serviceName;
691
788
  private moduleTypeMap;
789
+ private registeredOperations;
692
790
  constructor(options?: HtmxAdminPluginOptions);
693
791
  /**
694
792
  * 声明Module配置Schema
@@ -702,10 +800,38 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
702
800
  * 检查并注册模块
703
801
  */
704
802
  private checkAndRegisterModules;
803
+ /**
804
+ * 根据模块类型和路由信息生成操作ID
805
+ */
806
+ private generateOperationId;
807
+ /**
808
+ * 注册内置路由(权限提示页面)
809
+ */
810
+ private registerBuiltinRoutes;
705
811
  /**
706
812
  * 注册路由
707
813
  */
708
814
  private registerRoutes;
815
+ /**
816
+ * 获取所有注册的操作(权限)列表
817
+ * 可用于权限管理模块作为数据源,或用于确定管理员的权限
818
+ *
819
+ * @returns 所有注册的操作信息列表
820
+ */
821
+ getRegisteredOperations(): OperationInfo[];
822
+ /**
823
+ * 获取指定模块的所有操作
824
+ *
825
+ * @param moduleName 模块名
826
+ * @returns 该模块的所有操作信息列表
827
+ */
828
+ getModuleOperations(moduleName: string): OperationInfo[];
829
+ /**
830
+ * 获取所有唯一的操作ID列表(去重)
831
+ *
832
+ * @returns 所有唯一的操作ID列表
833
+ */
834
+ getOperationIds(): string[];
709
835
  /**
710
836
  * 模块加载钩子:检查模块类型约束并注册路由
711
837
  */
package/dist/index.d.ts CHANGED
@@ -27,8 +27,59 @@ interface UserInfo {
27
27
  name?: string;
28
28
  avatar?: string;
29
29
  email?: string;
30
+ /** 用户权限列表(支持通配符,如 "users.*", "users.read", "deny:users.delete") */
31
+ permissions?: string[];
30
32
  [key: string]: any;
31
33
  }
34
+ /**
35
+ * 权限校验结果
36
+ */
37
+ interface PermissionResult {
38
+ /** 是否允许访问 */
39
+ allowed: boolean;
40
+ /** 如果拒绝访问,可选的错误消息 */
41
+ message?: string;
42
+ /** 如果拒绝访问,可选的重定向URL */
43
+ redirectUrl?: string;
44
+ /** 被拒绝的操作ID(用于显示详细信息) */
45
+ operationId?: string;
46
+ }
47
+ /**
48
+ * 认证提供者接口
49
+ */
50
+ interface AuthProvider {
51
+ /** Cookie 中存储 token 的 key(默认 "auth_token") */
52
+ cookieKey?: string;
53
+ /** 将 token 转换为用户信息 */
54
+ tokenToUser: (token: string, ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
55
+ /** 权限校验钩子(可选,如果不提供则使用默认实现) */
56
+ checkPermission?: (user: UserInfo | null, operation: string, context: any) => PermissionResult | Promise<PermissionResult>;
57
+ }
58
+ /**
59
+ * 操作类型
60
+ */
61
+ type OperationType = "read" | "create" | "edit" | "delete";
62
+ /**
63
+ * 操作信息
64
+ */
65
+ interface OperationInfo {
66
+ /** 操作ID(格式:moduleName.operation) */
67
+ operationId: string;
68
+ /** 模块名 */
69
+ moduleName: string;
70
+ /** 操作类型 */
71
+ operationType: OperationType;
72
+ /** 模块类型 */
73
+ moduleType: ModuleType;
74
+ /** 模块标题 */
75
+ moduleTitle?: string;
76
+ /** 模块描述 */
77
+ moduleDescription?: string;
78
+ /** HTTP 方法 */
79
+ httpMethod: string;
80
+ /** 路由路径 */
81
+ routePath: string;
82
+ }
32
83
  /**
33
84
  * 模块类型信息
34
85
  */
@@ -69,8 +120,8 @@ interface HtmxAdminPluginOptions {
69
120
  homePath?: string;
70
121
  /** 导航配置(集中式声明导航结构,支持嵌套) */
71
122
  navigation?: NavItemConfig[];
72
- /** 获取用户信息的函数(用于显示用户信息) */
73
- getUserInfo?: (ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
123
+ /** 认证提供者(用于鉴权和权限控制) */
124
+ authProvider?: AuthProvider;
74
125
  }
75
126
  /**
76
127
  * 模块类型
@@ -216,6 +267,8 @@ declare class HtmxAdminContext {
216
267
  readonly moduleMetadata: ModuleTypeInfo;
217
268
  /** 插件选项 */
218
269
  readonly pluginOptions: Required<HtmxAdminPluginOptions>;
270
+ /** 服务名(用于生成权限ID) */
271
+ readonly serviceName: string;
219
272
  /** Hono Context */
220
273
  readonly ctx: Context;
221
274
  /** 之前的模块名 */
@@ -240,7 +293,7 @@ declare class HtmxAdminContext {
240
293
  redirectUrl?: string;
241
294
  /** 是否需要刷新页面(用于 HX-Refresh) */
242
295
  refresh: boolean;
243
- constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>);
296
+ constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>, serviceName?: string);
244
297
  /**
245
298
  * 发送通知
246
299
  * 通知将在响应时通过 OOB 更新到错误容器
@@ -334,6 +387,20 @@ declare abstract class PageModule {
334
387
  * 默认实现:首页 => 自定义页面
335
388
  */
336
389
  getBreadcrumbs(): BreadcrumbItem[];
390
+ /**
391
+ * 获取所需权限
392
+ * 子类可以重写此方法来声明页面所需的权限
393
+ *
394
+ * 返回值说明:
395
+ * - 返回空字符串 "" 或 null 或 undefined:表示开放访问(无需权限)
396
+ * - 返回权限字符串:表示需要该权限,如 "users.read", "users.*"
397
+ *
398
+ * 默认实现:返回空字符串(开放访问)
399
+ *
400
+ * @param ctx Hono Context(可选,用于根据请求动态决定权限)
401
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
402
+ */
403
+ getRequiredPermission?(ctx?: any): string | null | undefined;
337
404
  /**
338
405
  * 处理请求的统一入口(由 RouteHandler 调用)
339
406
  * 默认实现:直接调用 render 方法
@@ -370,6 +437,15 @@ declare abstract class PageModule {
370
437
  declare abstract class DetailPageModule<T = any> extends PageModule {
371
438
  /** ID 字段名(默认 "id") */
372
439
  protected readonly idField: string;
440
+ /**
441
+ * 获取所需权限
442
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
443
+ * 子类可以重写此方法来自定义权限要求
444
+ *
445
+ * @param ctx Hono Context(可选)
446
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
447
+ */
448
+ getRequiredPermission(ctx?: any): string | null | undefined;
373
449
  /**
374
450
  * 获取数据源(抽象方法,必须实现)
375
451
  * 子类必须实现此方法来提供数据源
@@ -435,6 +511,17 @@ declare abstract class FormPageModule<T = any> extends PageModule {
435
511
  /** Zod Schema(可选,如果提供则自动生成表单字段和校验) */
436
512
  protected schema?: z.ZodObject<any>;
437
513
  constructor(schema?: z.ZodObject<any>);
514
+ /**
515
+ * 获取所需权限
516
+ * 根据是新建还是编辑返回不同的权限要求
517
+ * - 新建:{serviceName}.{moduleName}.create
518
+ * - 编辑:{serviceName}.{moduleName}.edit
519
+ * 子类可以重写此方法来自定义权限要求
520
+ *
521
+ * @param ctx Hono Context(可选)
522
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
523
+ */
524
+ getRequiredPermission(ctx?: any): string | null | undefined;
438
525
  /**
439
526
  * 获取数据源(抽象方法,必须实现)
440
527
  * 子类必须实现此方法来提供数据源
@@ -558,6 +645,15 @@ declare abstract class ListPageModule<T extends {
558
645
  }> extends PageModule {
559
646
  /** ID 字段名(默认 "id") */
560
647
  protected readonly idField: string;
648
+ /**
649
+ * 获取所需权限
650
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
651
+ * 子类可以重写此方法来自定义权限要求
652
+ *
653
+ * @param ctx Hono Context(可选)
654
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
655
+ */
656
+ getRequiredPermission(ctx?: any): string | null | undefined;
561
657
  /**
562
658
  * 获取数据源(抽象方法,必须实现)
563
659
  * 子类必须实现此方法来提供数据源
@@ -688,7 +784,9 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
688
784
  private engine;
689
785
  private hono;
690
786
  private options;
787
+ private serviceName;
691
788
  private moduleTypeMap;
789
+ private registeredOperations;
692
790
  constructor(options?: HtmxAdminPluginOptions);
693
791
  /**
694
792
  * 声明Module配置Schema
@@ -702,10 +800,38 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
702
800
  * 检查并注册模块
703
801
  */
704
802
  private checkAndRegisterModules;
803
+ /**
804
+ * 根据模块类型和路由信息生成操作ID
805
+ */
806
+ private generateOperationId;
807
+ /**
808
+ * 注册内置路由(权限提示页面)
809
+ */
810
+ private registerBuiltinRoutes;
705
811
  /**
706
812
  * 注册路由
707
813
  */
708
814
  private registerRoutes;
815
+ /**
816
+ * 获取所有注册的操作(权限)列表
817
+ * 可用于权限管理模块作为数据源,或用于确定管理员的权限
818
+ *
819
+ * @returns 所有注册的操作信息列表
820
+ */
821
+ getRegisteredOperations(): OperationInfo[];
822
+ /**
823
+ * 获取指定模块的所有操作
824
+ *
825
+ * @param moduleName 模块名
826
+ * @returns 该模块的所有操作信息列表
827
+ */
828
+ getModuleOperations(moduleName: string): OperationInfo[];
829
+ /**
830
+ * 获取所有唯一的操作ID列表(去重)
831
+ *
832
+ * @returns 所有唯一的操作ID列表
833
+ */
834
+ getOperationIds(): string[];
709
835
  /**
710
836
  * 模块加载钩子:检查模块类型约束并注册路由
711
837
  */