imean-service-engine-htmx-plugin 1.0.1 → 1.2.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,63 @@ 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
+ /** 登录页面 URL(当用户未登录且需要权限时重定向到此页面) */
54
+ loginUrl?: string;
55
+ /** 退出登录页面 URL(用于退出登录功能) */
56
+ logoutUrl?: string;
57
+ /** 将 token 转换为用户信息 */
58
+ tokenToUser: (token: string, ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
59
+ /** 权限校验钩子(可选,如果不提供则使用默认实现) */
60
+ checkPermission?: (user: UserInfo | null, operation: string, context: any) => PermissionResult | Promise<PermissionResult>;
61
+ }
62
+ /**
63
+ * 操作类型
64
+ */
65
+ type OperationType = "read" | "create" | "edit" | "delete";
66
+ /**
67
+ * 操作信息
68
+ */
69
+ interface OperationInfo {
70
+ /** 操作ID(格式:moduleName.operation) */
71
+ operationId: string;
72
+ /** 模块名 */
73
+ moduleName: string;
74
+ /** 操作类型 */
75
+ operationType: OperationType;
76
+ /** 模块类型 */
77
+ moduleType: ModuleType;
78
+ /** 模块标题 */
79
+ moduleTitle?: string;
80
+ /** 模块描述 */
81
+ moduleDescription?: string;
82
+ /** HTTP 方法 */
83
+ httpMethod: string;
84
+ /** 路由路径 */
85
+ routePath: string;
86
+ }
32
87
  /**
33
88
  * 模块类型信息
34
89
  */
@@ -69,8 +124,8 @@ interface HtmxAdminPluginOptions {
69
124
  homePath?: string;
70
125
  /** 导航配置(集中式声明导航结构,支持嵌套) */
71
126
  navigation?: NavItemConfig[];
72
- /** 获取用户信息的函数(用于显示用户信息) */
73
- getUserInfo?: (ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
127
+ /** 认证提供者(用于鉴权和权限控制) */
128
+ authProvider?: AuthProvider;
74
129
  }
75
130
  /**
76
131
  * 模块类型
@@ -216,6 +271,8 @@ declare class HtmxAdminContext {
216
271
  readonly moduleMetadata: ModuleTypeInfo;
217
272
  /** 插件选项 */
218
273
  readonly pluginOptions: Required<HtmxAdminPluginOptions>;
274
+ /** 服务名(用于生成权限ID) */
275
+ readonly serviceName: string;
219
276
  /** Hono Context */
220
277
  readonly ctx: Context;
221
278
  /** 之前的模块名 */
@@ -240,7 +297,7 @@ declare class HtmxAdminContext {
240
297
  redirectUrl?: string;
241
298
  /** 是否需要刷新页面(用于 HX-Refresh) */
242
299
  refresh: boolean;
243
- constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>);
300
+ constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>, serviceName?: string);
244
301
  /**
245
302
  * 发送通知
246
303
  * 通知将在响应时通过 OOB 更新到错误容器
@@ -334,6 +391,20 @@ declare abstract class PageModule {
334
391
  * 默认实现:首页 => 自定义页面
335
392
  */
336
393
  getBreadcrumbs(): BreadcrumbItem[];
394
+ /**
395
+ * 获取所需权限
396
+ * 子类可以重写此方法来声明页面所需的权限
397
+ *
398
+ * 返回值说明:
399
+ * - 返回空字符串 "" 或 null 或 undefined:表示开放访问(无需权限)
400
+ * - 返回权限字符串:表示需要该权限,如 "users.read", "users.*"
401
+ *
402
+ * 默认实现:返回空字符串(开放访问)
403
+ *
404
+ * @param ctx Hono Context(可选,用于根据请求动态决定权限)
405
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
406
+ */
407
+ getRequiredPermission?(ctx?: any): string | null | undefined;
337
408
  /**
338
409
  * 处理请求的统一入口(由 RouteHandler 调用)
339
410
  * 默认实现:直接调用 render 方法
@@ -370,6 +441,15 @@ declare abstract class PageModule {
370
441
  declare abstract class DetailPageModule<T = any> extends PageModule {
371
442
  /** ID 字段名(默认 "id") */
372
443
  protected readonly idField: string;
444
+ /**
445
+ * 获取所需权限
446
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
447
+ * 子类可以重写此方法来自定义权限要求
448
+ *
449
+ * @param ctx Hono Context(可选)
450
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
451
+ */
452
+ getRequiredPermission(ctx?: any): string | null | undefined;
373
453
  /**
374
454
  * 获取数据源(抽象方法,必须实现)
375
455
  * 子类必须实现此方法来提供数据源
@@ -435,6 +515,17 @@ declare abstract class FormPageModule<T = any> extends PageModule {
435
515
  /** Zod Schema(可选,如果提供则自动生成表单字段和校验) */
436
516
  protected schema?: z.ZodObject<any>;
437
517
  constructor(schema?: z.ZodObject<any>);
518
+ /**
519
+ * 获取所需权限
520
+ * 根据是新建还是编辑返回不同的权限要求
521
+ * - 新建:{serviceName}.{moduleName}.create
522
+ * - 编辑:{serviceName}.{moduleName}.edit
523
+ * 子类可以重写此方法来自定义权限要求
524
+ *
525
+ * @param ctx Hono Context(可选)
526
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
527
+ */
528
+ getRequiredPermission(ctx?: any): string | null | undefined;
438
529
  /**
439
530
  * 获取数据源(抽象方法,必须实现)
440
531
  * 子类必须实现此方法来提供数据源
@@ -558,6 +649,15 @@ declare abstract class ListPageModule<T extends {
558
649
  }> extends PageModule {
559
650
  /** ID 字段名(默认 "id") */
560
651
  protected readonly idField: string;
652
+ /**
653
+ * 获取所需权限
654
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
655
+ * 子类可以重写此方法来自定义权限要求
656
+ *
657
+ * @param ctx Hono Context(可选)
658
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
659
+ */
660
+ getRequiredPermission(ctx?: any): string | null | undefined;
561
661
  /**
562
662
  * 获取数据源(抽象方法,必须实现)
563
663
  * 子类必须实现此方法来提供数据源
@@ -688,7 +788,9 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
688
788
  private engine;
689
789
  private hono;
690
790
  private options;
791
+ private serviceName;
691
792
  private moduleTypeMap;
793
+ private registeredOperations;
692
794
  constructor(options?: HtmxAdminPluginOptions);
693
795
  /**
694
796
  * 声明Module配置Schema
@@ -702,10 +804,38 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
702
804
  * 检查并注册模块
703
805
  */
704
806
  private checkAndRegisterModules;
807
+ /**
808
+ * 根据模块类型和路由信息生成操作ID
809
+ */
810
+ private generateOperationId;
811
+ /**
812
+ * 注册内置路由(权限提示页面)
813
+ */
814
+ private registerBuiltinRoutes;
705
815
  /**
706
816
  * 注册路由
707
817
  */
708
818
  private registerRoutes;
819
+ /**
820
+ * 获取所有注册的操作(权限)列表
821
+ * 可用于权限管理模块作为数据源,或用于确定管理员的权限
822
+ *
823
+ * @returns 所有注册的操作信息列表
824
+ */
825
+ getRegisteredOperations(): OperationInfo[];
826
+ /**
827
+ * 获取指定模块的所有操作
828
+ *
829
+ * @param moduleName 模块名
830
+ * @returns 该模块的所有操作信息列表
831
+ */
832
+ getModuleOperations(moduleName: string): OperationInfo[];
833
+ /**
834
+ * 获取所有唯一的操作ID列表(去重)
835
+ *
836
+ * @returns 所有唯一的操作ID列表
837
+ */
838
+ getOperationIds(): string[];
709
839
  /**
710
840
  * 模块加载钩子:检查模块类型约束并注册路由
711
841
  */
package/dist/index.d.ts CHANGED
@@ -27,8 +27,63 @@ 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
+ /** 登录页面 URL(当用户未登录且需要权限时重定向到此页面) */
54
+ loginUrl?: string;
55
+ /** 退出登录页面 URL(用于退出登录功能) */
56
+ logoutUrl?: string;
57
+ /** 将 token 转换为用户信息 */
58
+ tokenToUser: (token: string, ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
59
+ /** 权限校验钩子(可选,如果不提供则使用默认实现) */
60
+ checkPermission?: (user: UserInfo | null, operation: string, context: any) => PermissionResult | Promise<PermissionResult>;
61
+ }
62
+ /**
63
+ * 操作类型
64
+ */
65
+ type OperationType = "read" | "create" | "edit" | "delete";
66
+ /**
67
+ * 操作信息
68
+ */
69
+ interface OperationInfo {
70
+ /** 操作ID(格式:moduleName.operation) */
71
+ operationId: string;
72
+ /** 模块名 */
73
+ moduleName: string;
74
+ /** 操作类型 */
75
+ operationType: OperationType;
76
+ /** 模块类型 */
77
+ moduleType: ModuleType;
78
+ /** 模块标题 */
79
+ moduleTitle?: string;
80
+ /** 模块描述 */
81
+ moduleDescription?: string;
82
+ /** HTTP 方法 */
83
+ httpMethod: string;
84
+ /** 路由路径 */
85
+ routePath: string;
86
+ }
32
87
  /**
33
88
  * 模块类型信息
34
89
  */
@@ -69,8 +124,8 @@ interface HtmxAdminPluginOptions {
69
124
  homePath?: string;
70
125
  /** 导航配置(集中式声明导航结构,支持嵌套) */
71
126
  navigation?: NavItemConfig[];
72
- /** 获取用户信息的函数(用于显示用户信息) */
73
- getUserInfo?: (ctx: Context) => UserInfo | null | Promise<UserInfo | null>;
127
+ /** 认证提供者(用于鉴权和权限控制) */
128
+ authProvider?: AuthProvider;
74
129
  }
75
130
  /**
76
131
  * 模块类型
@@ -216,6 +271,8 @@ declare class HtmxAdminContext {
216
271
  readonly moduleMetadata: ModuleTypeInfo;
217
272
  /** 插件选项 */
218
273
  readonly pluginOptions: Required<HtmxAdminPluginOptions>;
274
+ /** 服务名(用于生成权限ID) */
275
+ readonly serviceName: string;
219
276
  /** Hono Context */
220
277
  readonly ctx: Context;
221
278
  /** 之前的模块名 */
@@ -240,7 +297,7 @@ declare class HtmxAdminContext {
240
297
  redirectUrl?: string;
241
298
  /** 是否需要刷新页面(用于 HX-Refresh) */
242
299
  refresh: boolean;
243
- constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>);
300
+ constructor(ctx: Context, userInfo: UserInfo | null, moduleMetadata: ModuleTypeInfo, pluginOptions: Required<HtmxAdminPluginOptions>, serviceName?: string);
244
301
  /**
245
302
  * 发送通知
246
303
  * 通知将在响应时通过 OOB 更新到错误容器
@@ -334,6 +391,20 @@ declare abstract class PageModule {
334
391
  * 默认实现:首页 => 自定义页面
335
392
  */
336
393
  getBreadcrumbs(): BreadcrumbItem[];
394
+ /**
395
+ * 获取所需权限
396
+ * 子类可以重写此方法来声明页面所需的权限
397
+ *
398
+ * 返回值说明:
399
+ * - 返回空字符串 "" 或 null 或 undefined:表示开放访问(无需权限)
400
+ * - 返回权限字符串:表示需要该权限,如 "users.read", "users.*"
401
+ *
402
+ * 默认实现:返回空字符串(开放访问)
403
+ *
404
+ * @param ctx Hono Context(可选,用于根据请求动态决定权限)
405
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
406
+ */
407
+ getRequiredPermission?(ctx?: any): string | null | undefined;
337
408
  /**
338
409
  * 处理请求的统一入口(由 RouteHandler 调用)
339
410
  * 默认实现:直接调用 render 方法
@@ -370,6 +441,15 @@ declare abstract class PageModule {
370
441
  declare abstract class DetailPageModule<T = any> extends PageModule {
371
442
  /** ID 字段名(默认 "id") */
372
443
  protected readonly idField: string;
444
+ /**
445
+ * 获取所需权限
446
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
447
+ * 子类可以重写此方法来自定义权限要求
448
+ *
449
+ * @param ctx Hono Context(可选)
450
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
451
+ */
452
+ getRequiredPermission(ctx?: any): string | null | undefined;
373
453
  /**
374
454
  * 获取数据源(抽象方法,必须实现)
375
455
  * 子类必须实现此方法来提供数据源
@@ -435,6 +515,17 @@ declare abstract class FormPageModule<T = any> extends PageModule {
435
515
  /** Zod Schema(可选,如果提供则自动生成表单字段和校验) */
436
516
  protected schema?: z.ZodObject<any>;
437
517
  constructor(schema?: z.ZodObject<any>);
518
+ /**
519
+ * 获取所需权限
520
+ * 根据是新建还是编辑返回不同的权限要求
521
+ * - 新建:{serviceName}.{moduleName}.create
522
+ * - 编辑:{serviceName}.{moduleName}.edit
523
+ * 子类可以重写此方法来自定义权限要求
524
+ *
525
+ * @param ctx Hono Context(可选)
526
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
527
+ */
528
+ getRequiredPermission(ctx?: any): string | null | undefined;
438
529
  /**
439
530
  * 获取数据源(抽象方法,必须实现)
440
531
  * 子类必须实现此方法来提供数据源
@@ -558,6 +649,15 @@ declare abstract class ListPageModule<T extends {
558
649
  }> extends PageModule {
559
650
  /** ID 字段名(默认 "id") */
560
651
  protected readonly idField: string;
652
+ /**
653
+ * 获取所需权限
654
+ * 默认实现:返回 "{serviceName}.{moduleName}.read"
655
+ * 子类可以重写此方法来自定义权限要求
656
+ *
657
+ * @param ctx Hono Context(可选)
658
+ * @returns 所需权限字符串,或空字符串/null/undefined 表示开放
659
+ */
660
+ getRequiredPermission(ctx?: any): string | null | undefined;
561
661
  /**
562
662
  * 获取数据源(抽象方法,必须实现)
563
663
  * 子类必须实现此方法来提供数据源
@@ -688,7 +788,9 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
688
788
  private engine;
689
789
  private hono;
690
790
  private options;
791
+ private serviceName;
691
792
  private moduleTypeMap;
793
+ private registeredOperations;
692
794
  constructor(options?: HtmxAdminPluginOptions);
693
795
  /**
694
796
  * 声明Module配置Schema
@@ -702,10 +804,38 @@ declare class HtmxAdminPlugin implements Plugin<HtmxAdminModuleOptions> {
702
804
  * 检查并注册模块
703
805
  */
704
806
  private checkAndRegisterModules;
807
+ /**
808
+ * 根据模块类型和路由信息生成操作ID
809
+ */
810
+ private generateOperationId;
811
+ /**
812
+ * 注册内置路由(权限提示页面)
813
+ */
814
+ private registerBuiltinRoutes;
705
815
  /**
706
816
  * 注册路由
707
817
  */
708
818
  private registerRoutes;
819
+ /**
820
+ * 获取所有注册的操作(权限)列表
821
+ * 可用于权限管理模块作为数据源,或用于确定管理员的权限
822
+ *
823
+ * @returns 所有注册的操作信息列表
824
+ */
825
+ getRegisteredOperations(): OperationInfo[];
826
+ /**
827
+ * 获取指定模块的所有操作
828
+ *
829
+ * @param moduleName 模块名
830
+ * @returns 该模块的所有操作信息列表
831
+ */
832
+ getModuleOperations(moduleName: string): OperationInfo[];
833
+ /**
834
+ * 获取所有唯一的操作ID列表(去重)
835
+ *
836
+ * @returns 所有唯一的操作ID列表
837
+ */
838
+ getOperationIds(): string[];
709
839
  /**
710
840
  * 模块加载钩子:检查模块类型约束并注册路由
711
841
  */