@h-ai/cli 0.1.0-alpha.59 → 0.1.0-alpha.61

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.
Files changed (100) hide show
  1. package/README.en.md +19 -1
  2. package/README.ja.md +19 -1
  3. package/README.md +19 -1
  4. package/dist/{chunk-WUSFJFW5.js → chunk-WW2MIO6T.js} +437 -126
  5. package/dist/chunk-WW2MIO6T.js.map +1 -0
  6. package/dist/cli-config-templates-QRF6B5T4.js +5 -0
  7. package/dist/{cli-config-templates-6XA7V6CC.js.map → cli-config-templates-QRF6B5T4.js.map} +1 -1
  8. package/dist/index.js +28 -7
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/templates/README.md +14 -0
  12. package/templates/apps/api/.gitignore.hbs +1 -0
  13. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +12 -0
  14. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +4 -7
  15. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +11 -1
  16. package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +2 -1
  17. package/templates/apps/api/e2e/api-service.spec.ts.hbs +72 -6
  18. package/templates/apps/api/package.json.hbs +4 -0
  19. package/templates/apps/fullstack/.gitignore.hbs +1 -0
  20. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +10 -0
  21. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +1 -1
  22. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -0
  23. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +1 -1
  24. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  25. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +1 -1
  26. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +10 -0
  27. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +1 -1
  28. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +63 -1
  29. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +1 -1
  30. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +8 -1
  31. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  32. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +18 -3
  33. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_ai.yml.hbs +1 -0
  34. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_audit.yml.hbs +1 -3
  35. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_cache.yml.hbs +1 -2
  36. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +7 -0
  37. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_db.yml.hbs +1 -3
  38. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_deploy.yml.hbs +1 -0
  39. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +1 -21
  40. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_payment.yml.hbs +1 -0
  41. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +52 -0
  42. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_scheduler.yml.hbs +1 -0
  43. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +8 -0
  44. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_storage.yml.hbs +1 -3
  45. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_vecdb.yml.hbs +1 -0
  46. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
  47. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +2 -2
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +7 -3
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +11 -4
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +12 -0
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +2 -2
  53. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +2 -2
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +1 -1
  56. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +3 -0
  57. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +32 -23
  58. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +24 -24
  59. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/UserManagement.svelte.hbs +1 -1
  60. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +20 -20
  61. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +7 -8
  62. package/templates/apps/h5/src/routes/+layout.svelte.hbs +35 -23
  63. package/templates/apps/mobile-app/src/App.svelte.hbs +43 -0
  64. package/templates/base/.gitignore.hbs +1 -2
  65. package/templates/base/e2e/app.spec.ts.hbs +147 -0
  66. package/templates/base/package.json.hbs +1 -1
  67. package/templates/base/src/hooks.server.ts.hbs +2 -2
  68. package/templates/base/src/lib/server/init.ts.hbs +1 -1
  69. package/templates/base/src/routes/+layout.server.ts.hbs +6 -0
  70. package/templates/base/tests/scaffold.test.ts.hbs +3 -0
  71. package/templates/base/vite.config.ts.hbs +1 -0
  72. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +2 -16
  73. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +10 -0
  74. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +6 -95
  75. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +6 -117
  76. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +10 -0
  77. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +10 -0
  78. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +6 -97
  79. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +6 -112
  80. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +10 -0
  81. package/templates/features/iam/routes-shared/api/auth/config/+server.ts +4 -0
  82. package/templates/features/iam/routes-shared/api/auth/login/+server.ts +1 -33
  83. package/templates/features/iam/routes-shared/api/auth/login/ldap/+server.ts +4 -0
  84. package/templates/features/iam/routes-shared/api/auth/login/otp/+server.ts +4 -0
  85. package/templates/features/iam/routes-shared/api/auth/otp/send/+server.ts +4 -0
  86. package/templates/features/iam/routes-shared/api/auth/password-reset/confirm/+server.ts +4 -0
  87. package/templates/features/iam/routes-shared/api/auth/password-reset/request/+server.ts +4 -0
  88. package/templates/features/iam/routes-shared/api/auth/register/+server.ts +1 -23
  89. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +10 -0
  90. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +11 -0
  91. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +11 -0
  92. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +10 -0
  93. package/templates/skills/hai-api-client/SKILL.md +5 -0
  94. package/templates/skills/hai-app-create/reference.md +10 -0
  95. package/templates/skills/hai-iam/SKILL.md +9 -2
  96. package/templates/skills/hai-iam/reference.md +76 -11
  97. package/templates/skills/hai-reach/SKILL.md +2 -0
  98. package/templates/skills/hai-ui/reference.md +47 -0
  99. package/dist/chunk-WUSFJFW5.js.map +0 -1
  100. package/dist/cli-config-templates-6XA7V6CC.js +0 -5
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/cli",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.59",
4
+ "version": "0.1.0-alpha.61",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -30,7 +30,7 @@
30
30
  "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "@h-ai/core": "0.1.0-alpha.59",
33
+ "@h-ai/core": "0.1.0-alpha.61",
34
34
  "cac": "^7.0.1",
35
35
  "chalk": "^6.0.1",
36
36
  "fast-wrap-ansi": "^0.2.2",
@@ -41,11 +41,11 @@
41
41
  "yaml": "^2.9.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@h-ai/cache": "0.1.0-alpha.59",
45
- "@h-ai/deploy": "0.1.0-alpha.59",
46
- "@h-ai/reach": "0.1.0-alpha.59",
47
- "@h-ai/reldb": "0.1.0-alpha.59",
48
- "@h-ai/storage": "0.1.0-alpha.59",
44
+ "@h-ai/cache": "0.1.0-alpha.61",
45
+ "@h-ai/deploy": "0.1.0-alpha.61",
46
+ "@h-ai/reach": "0.1.0-alpha.61",
47
+ "@h-ai/reldb": "0.1.0-alpha.61",
48
+ "@h-ai/storage": "0.1.0-alpha.61",
49
49
  "@types/fs-extra": "^11.0.4",
50
50
  "@types/node": "^26.6.5",
51
51
  "@types/prompts": "^2.4.9",
@@ -104,8 +104,14 @@ templates/
104
104
 
105
105
  `appType` 支持 `admin` / `website` / `h5` / `api` / `mobile-app` / `fullstack`。
106
106
 
107
+ H5 业务页面复用 `@h-ai/ui` 的 `BottomNav` 和移动端壳样式,桌面预览保持居中限宽,认证路由独立显示。
108
+
107
109
  `fullstack.*` 包含包名、前端选择、依赖版本、`contractExportName` 和原生壳 `nativeAppIdSegment`,用于渲染 contract / serv / 多端 UI / Capacitor / Tauri 模板。
108
110
 
111
+ `moduleConfigs` 保留创建时指定的模块值。Fullstack 的模块 YAML 用 `{{{moduleConfig 'db' true}}}` 等 helper 复用 `cli-config-templates.ts`;可选模块传 `features.ai` 等开关,未选择时整份注释。创建流程保留已渲染的配置,不再用精简内容覆盖;core、serv、Reach 保留应用专用默认值及完整的可选字段。新增 Schema 字段同步此生成器和相应模板。
112
+
113
+ 所有根 `.gitignore`(base / api / fullstack)保留 `**/data/`,包括 `.gitkeep` 在内的运行数据不跟踪。Fullstack 服务端 config 始终含 core、serv、db、cache、iam、storage、audit、reach、ai、vecdb、payment、scheduler、deploy 共 13 份 YAML;不为无配置入口的 crypto/datapipe 生成 YAML。未启用块的后续接入步骤见 [CLI 配置说明](../README.md#应用配置与显示)。
114
+
109
115
  ### 常用条件写法
110
116
 
111
117
  ```handlebars
@@ -142,3 +148,11 @@ import { iam } from '@h-ai/iam'
142
148
  ### 新增 feature 路由
143
149
 
144
150
  在 `features/{featureId}/routes-shared/`(API 路由)或 `features/{featureId}/routes-{appType}/`(UI 页面)下创建文件,然后在 `cli-template-engine.ts` 的 `FEATURE_ROUTE_DIRS` / `FEATURE_APP_ROUTE_DIRS` 中注册该目录。
151
+
152
+ ## 配置驱动认证页面
153
+
154
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
155
+
156
+ 选用 IAM 的 admin/h5/website 模板提供 AuthPanel 和标准认证路由;mobile-app 与 fullstack Web/App/Desktop 使用 API 客户端共享动作。修改服务端 config/_iam.yml 即可控制登录方式、注册、重置、协议和品牌,无需改表单。配置 Reach 模板和真实发送 Provider 后启用 OTP/重置。分布式部署使用共享数据库与 Redis。
157
+
158
+ LDAP/Reach 是可选 peer;IAM 不强制启用 Reach。默认 LDAP 客户端由应用选择安装 ldapts。自定义通知回调、协议弹窗、表单 snippet 用法随 hai-iam/hai-ui skills 完整复制到生成项目,不能要求应用读取本仓库私有路径。
@@ -1,4 +1,5 @@
1
1
  node_modules
2
+ **/data/
2
3
  .turbo
3
4
  dist
4
5
  build
@@ -21,3 +21,15 @@ pnpm --filter {{api.servicePackageName}} test
21
21
  - `src/app.ts`:HTTP App 装配
22
22
  - `src/lib/server/init.ts`:模块初始化与关闭
23
23
  - `src/server/procedures/`:应用级 procedures
24
+
25
+ {{#if features.iam}}
26
+ ## IAM 认证配置
27
+
28
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。客户端可通过安全公开配置和框架认证动作接入 `AuthPanel`。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
29
+
30
+ LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
31
+
32
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
33
+
34
+ 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
35
+ {{/if}}
@@ -59,6 +59,10 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
59
59
  },
60
60
  {{#if features.iam}}
61
61
  iam,
62
+ refreshCookie,
63
+ {{/if}}
64
+ {{#if features.crypto}}
65
+ transport,
62
66
  {{/if}}
63
67
  middlewares: [
64
68
  {
@@ -71,13 +75,6 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
71
75
  },
72
76
  ],
73
77
  }
74
- {{#if features.iam}}
75
-
76
- appOptions.refreshCookie = refreshCookie
77
- {{/if}}{{#if features.crypto}}
78
- if (transport)
79
- appOptions.transport = transport
80
- {{/if}}
81
78
 
82
79
  return serv.createApp(appOptions)
83
80
  }
@@ -8,6 +8,7 @@
8
8
  {{/if}}import { core } from '@h-ai/core'
9
9
  {{#if features.crypto}}import { crypto } from '@h-ai/crypto'
10
10
  {{/if}}{{#if features.iam}}import { iam, IamConfigSchema } from '@h-ai/iam'
11
+ {{/if}}{{#if features.reach}}import { reach, ReachConfigSchema } from '@h-ai/reach'
11
12
  {{/if}}{{#if features.db}}import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
12
13
  {{/if}}import { ServConfigSchema } from '@h-ai/serv'
13
14
  {{#if features.storage}}import { storage, StorageConfigSchema } from '@h-ai/storage'
@@ -122,6 +123,14 @@ async function doInit(): Promise<void> {
122
123
  if (!cacheResult.success) {
123
124
  throw new Error(`Cache initialization failed: ${cacheResult.error.message}`)
124
125
  }
126
+ {{/if}}{{#if features.reach}}
127
+
128
+ const reachConfig = core.config.get('reach')
129
+ if (reachConfig) {
130
+ const result = await reach.init(ReachConfigSchema.parse(reachConfig))
131
+ if (!result.success)
132
+ throw new Error('Reach initialization failed')
133
+ }
125
134
  {{/if}}{{#if features.vecdb}}
126
135
 
127
136
  const vecdbResult = await vecdb.init(vecdbConfig)
@@ -130,7 +139,7 @@ async function doInit(): Promise<void> {
130
139
  }
131
140
  {{/if}}{{#if features.iam}}
132
141
 
133
- const iamResult = await iam.init(iamConfig)
142
+ const iamResult = await iam.init({ ...iamConfig{{#if features.reach}}, reach{{/if}} })
134
143
  if (!iamResult.success) {
135
144
  throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
136
145
  }
@@ -159,6 +168,7 @@ export async function closeApp(): Promise<void> {
159
168
  {{#if features.ai}} ['ai', () => ai.close()],
160
169
  {{/if}}{{#if features.storage}} ['storage', () => storage.close()],
161
170
  {{/if}}{{#if features.iam}} ['iam', () => iam.close()],
171
+ {{/if}}{{#if features.reach}} ['reach', () => reach.close()],
162
172
  {{/if}}{{#if features.vecdb}} ['vecdb', () => vecdb.close()],
163
173
  {{/if}}{{#if features.cache}} ['cache', () => cache.close()],
164
174
  {{/if}}{{#if features.db}} ['reldb', () => reldb.close()],
@@ -146,7 +146,8 @@ describe('{{api.servicePackageName}}', () => {
146
146
  {{#if features.iam}}
147
147
  it('registers a user and returns authenticated echo context', async () => {
148
148
  const user = uniqueUser('svc')
149
- const register = await client.iam.auth.register({
149
+ const authActions = apiClient.createAuthActions(client)
150
+ const register = await authActions.register({
150
151
  username: user.username,
151
152
  password: user.password,
152
153
  email: user.email,
@@ -1,8 +1,36 @@
1
- import { apiContract } from '@h-ai/api-contract'
1
+ import process from 'node:process'
2
+ import { apiClient } from '@h-ai/api-client'
3
+ {{#if features.crypto}}
4
+ import { crypto } from '@h-ai/crypto'
5
+ {{/if}}
2
6
  import { expect, test } from '@playwright/test'
3
7
  import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
4
8
 
5
9
  const API_PREFIX = '/api/v1'
10
+ const client = apiClient.create({{api.contractExportName}})
11
+
12
+ test.beforeAll(async () => {
13
+ {{#if features.crypto}}
14
+ expect((await crypto.init()).success).toBe(true)
15
+ {{/if}}
16
+ const result = await client.init({
17
+ baseUrl: `${process.env.BASE_URL || 'http://127.0.0.1:3000'}${API_PREFIX}`,
18
+ {{#if features.iam}}
19
+ auth: { storage: apiClient.tokenStorage.memory() },
20
+ {{/if}}
21
+ {{#if features.crypto}}
22
+ transport: process.env.HAI_SERV_TRANSPORT === 'false' ? undefined : { crypto },
23
+ {{/if}}
24
+ })
25
+ expect(result.success).toBe(true)
26
+ })
27
+
28
+ test.afterAll(async () => {
29
+ await client.close()
30
+ {{#if features.crypto}}
31
+ await crypto.close()
32
+ {{/if}}
33
+ })
6
34
 
7
35
  test('service health endpoint returns ok', async ({ request }) => {
8
36
  const response = await request.get('/health')
@@ -11,14 +39,52 @@ test('service health endpoint returns ok', async ({ request }) => {
11
39
  await expect(response.json()).resolves.toEqual({ status: 'ok' })
12
40
  })
13
41
 
14
- test('service app.info endpoint returns HaiResult', async ({ request }) => {
15
- const response = await request.post(`${API_PREFIX}${apiContract.pathOf({{api.contractExportName}}.app.info)}`)
16
- const body = await response.json()
17
-
18
- expect(response.status()).toBe(200)
42
+ test('service app.info endpoint returns HaiResult', async () => {
43
+ const body = await client.app.info()
19
44
  expect(body.success).toBe(true)
45
+ if (!body.success)
46
+ return
20
47
  expect(body.data.name).toBe(process.env.HAI_CORE_NAME ?? '{{projectName}} API Service')
21
48
  expect(body.data.version).toBe(process.env.HAI_CORE_VERSION ?? '0.1.0')
22
49
  expect(body.data.env).toBe(process.env.HAI_CORE_ENV ?? 'development')
23
50
  expect(typeof body.data.transportEnabled).toBe('boolean')
24
51
  })
52
+ {{#if features.iam}}
53
+
54
+ /** 通过真实 API 验证 IAM 开关、登录、通知和重置,不需要页面适配。 */
55
+ test('configured IAM supports registration, OTP and reset', async ({ request }) => {
56
+ test.setTimeout(60_000)
57
+ const actions = apiClient.createAuthActions(client)
58
+ const publicConfig = await actions.publicConfig()
59
+ expect(publicConfig.success).toBe(true)
60
+ if (!publicConfig.success)
61
+ return
62
+ expect(publicConfig.data?.register.enabled).toBe(true)
63
+ const username = `api${Date.now()}`
64
+ const email = `${username}@example.com`
65
+ const password = 'ApiPassword123'
66
+ expect((await actions.register({ username, email, password })).success).toBe(true)
67
+ expect((await actions.login({ identifier: username, password })).success).toBe(true)
68
+ if (!publicConfig.data?.login.email) {
69
+ // 无 Reach/发送回调时,基础认证仍可用,未就绪的通知入口保持关闭。
70
+ expect((await actions.sendOtp(email)).success).toBe(false)
71
+ return
72
+ }
73
+ if (!process.env.IAM_NOTIFICATION_URL)
74
+ return
75
+ async function received(template: string) {
76
+ const response = await request.get(`${process.env.IAM_NOTIFICATION_URL}/messages?to=${encodeURIComponent(email)}`)
77
+ const messages = await response.json() as { template: string, vars: Record<string, string> }[]
78
+ return messages.findLast(item => item.template === template)?.vars
79
+ }
80
+ expect((await actions.sendOtp(email)).success).toBe(true)
81
+ const otp = await received('otp_email')
82
+ expect(Boolean(otp?.code)).toBe(true)
83
+ expect((await actions.login({ identifier: email, method: 'email', code: otp?.code })).success).toBe(true)
84
+ expect((await actions.requestPasswordReset(email)).success).toBe(true)
85
+ const reset = await received('password_reset')
86
+ expect(Boolean(reset?.token)).toBe(true)
87
+ expect((await actions.confirmPasswordReset(reset?.token ?? '', 'ApiResetPassword456')).success).toBe(true)
88
+ expect((await actions.login({ identifier: username, password: 'ApiResetPassword456' })).success).toBe(true)
89
+ })
90
+ {{/if}}
@@ -25,6 +25,10 @@
25
25
  "{{api.contractPackageName}}": "workspace:*"
26
26
  },
27
27
  "devDependencies": {
28
+ "@h-ai/api-client": "{{haiPackageSpecifier}}",
29
+ {{#if features.crypto}}
30
+ "@h-ai/crypto": "{{haiPackageSpecifier}}",
31
+ {{/if}}
28
32
  "@antfu/eslint-config": "{{api.packageSpecifiers.antfuEslintConfig}}",
29
33
  "@playwright/test": "{{api.packageSpecifiers.playwright}}",
30
34
  "@types/node": "{{api.packageSpecifiers.nodeTypes}}",
@@ -1,4 +1,5 @@
1
1
  node_modules
2
+ **/data/
2
3
  .turbo
3
4
  dist
4
5
  build
@@ -35,4 +35,14 @@ pnpm --filter {{projectName}}-app deploy
35
35
  ```
36
36
 
37
37
  部署前确认 `PUBLIC_API_BASE` 在 `.env` 中指向可访问的后端服务。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000`。
38
+
39
+ ## IAM 认证配置
40
+
41
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
42
+
43
+ LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
44
+
45
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
46
+
47
+ 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
38
48
  {{/if}}
@@ -8,7 +8,7 @@
8
8
  {{/each}}
9
9
  ],
10
10
  "modules": [
11
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
11
+ "../../node_modules/@inlang/plugin-message-format/dist/index.js"
12
12
  ],
13
13
  "plugin.inlang.messageFormat": {
14
14
  "pathPattern": "./messages/{locale}.json"
@@ -34,4 +34,14 @@ pnpm --filter {{projectName}}-desktop deploy
34
34
  ```
35
35
 
36
36
  打包前需要安装 Rust 工具链和 Tauri 平台依赖。生产环境部署时同步调整 `.env` 中的 `PUBLIC_API_BASE` 与 `src-tauri/tauri.conf.json` 的 `connect-src` 白名单。
37
+
38
+ ## IAM 认证配置
39
+
40
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
41
+
42
+ LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
43
+
44
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
45
+
46
+ 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
37
47
  {{/if}}
@@ -8,7 +8,7 @@
8
8
  {{/each}}
9
9
  ],
10
10
  "modules": [
11
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
11
+ "../../node_modules/@inlang/plugin-message-format/dist/index.js"
12
12
  ],
13
13
  "plugin.inlang.messageFormat": {
14
14
  "pathPattern": "./messages/{locale}.json"
@@ -7,6 +7,8 @@
7
7
  - `project.inlang/settings.json`:小程序端 locale 配置
8
8
  - `messages/zh-CN.json`、`messages/en-US.json`:小程序端自有文案
9
9
 
10
+ 消息格式插件复用 workspace 已安装的依赖,从根目录 `node_modules` 加载;生成后先在 workspace 根目录安装依赖,无需从 CDN 加载插件。
11
+
10
12
  后续实现时建议复用 `packages/{{fullstack.contractPackageName}}` 中的 Schema 与路径常量,保持接口真相源一致。
11
13
 
12
14
  协同边界:跨端通用文案/组件放 `packages/{{fullstack.sharedPackageName}}`,小程序独有页面文案放本目录 `messages/`。
@@ -8,7 +8,7 @@
8
8
  {{/each}}
9
9
  ],
10
10
  "modules": [
11
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
11
+ "../../node_modules/@inlang/plugin-message-format/dist/index.js"
12
12
  ],
13
13
  "plugin.inlang.messageFormat": {
14
14
  "pathPattern": "./messages/{locale}.json"
@@ -27,4 +27,14 @@ pnpm --filter {{projectName}}-web package
27
27
  # 当前模板的 deploy 入口默认执行 package,可替换为 CDN / 容器发布脚本
28
28
  pnpm --filter {{projectName}}-web deploy
29
29
  ```
30
+
31
+ ## IAM 认证配置
32
+
33
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
34
+
35
+ LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
36
+
37
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
38
+
39
+ 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
30
40
  {{/if}}
@@ -8,7 +8,7 @@
8
8
  {{/each}}
9
9
  ],
10
10
  "modules": [
11
- "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
11
+ "../../node_modules/@inlang/plugin-message-format/dist/index.js"
12
12
  ],
13
13
  "plugin.inlang.messageFormat": {
14
14
  "pathPattern": "./messages/{locale}.json"
@@ -1,6 +1,8 @@
1
1
  import type { Page } from '@playwright/test'
2
2
  import { Buffer } from 'node:buffer'
3
3
  import { randomUUID } from 'node:crypto'
4
+ import { mkdir } from 'node:fs/promises'
5
+ import path from 'node:path'
4
6
  import process from 'node:process'
5
7
  import { apiContract } from '@h-ai/api-contract'
6
8
  import { expect, test } from '@playwright/test'
@@ -43,6 +45,66 @@ test('service rejects plaintext business requests', async ({ request }) => {
43
45
  })
44
46
 
45
47
  {{#if fullstack.e2eFrontend}}
48
+ test('shared authentication supports public registration with configuration', async ({ page }) => {
49
+ test.setTimeout(90_000)
50
+ const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
51
+ await mkdir(evidence, { recursive: true })
52
+ await page.goto('/')
53
+ await expect(page.getByTestId('login-form')).toBeVisible()
54
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-login.png'), fullPage: true })
55
+ await page.getByRole('button', { name: /立即注册|Register now/i }).click()
56
+ await expect(page.locator('input[name="username"]')).toBeVisible()
57
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-register.png'), fullPage: true })
58
+ const username = `signup${Date.now()}`
59
+ await page.locator('input[name="username"]').fill(username)
60
+ await page.locator('input[name="email"]').fill(`${username}@example.com`)
61
+ await page.locator('input[type="password"]').nth(0).fill('SignupPassword123')
62
+ await page.locator('input[type="password"]').nth(1).fill('SignupPassword123')
63
+ await page.locator('button[type="submit"]').click()
64
+ await expect(page.getByTestId('settings-page')).toBeVisible()
65
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
66
+ if (!process.env.IAM_NOTIFICATION_URL)
67
+ return
68
+ const email = `${username}@example.com`
69
+ await page.getByTestId('logout').click()
70
+ await page.getByRole('button', { name: /邮件验证码|Email code/i, exact: true }).click()
71
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-email.png'), fullPage: true })
72
+ await page.locator('#login-username').fill(email)
73
+ await page.getByRole('button', { name: /发送验证码|Send code/i, exact: true }).click()
74
+ await expect(page.getByRole('button', { name: /秒后重发|Resend in/ })).toBeDisabled()
75
+ async function received(template: string) {
76
+ const response = await page.request.get(`${process.env.IAM_NOTIFICATION_URL}/messages?to=${encodeURIComponent(email)}`)
77
+ const messages = await response.json() as { template: string, vars: Record<string, string> }[]
78
+ return messages.findLast(item => item.template === template)?.vars
79
+ }
80
+ const otp = await received('otp_email')
81
+ expect(Boolean(otp?.code)).toBe(true)
82
+ await page.locator('#login-code').fill(otp?.code ?? '')
83
+ await page.locator('button[type="submit"]').click()
84
+ await expect(page.getByTestId('settings-page')).toBeVisible()
85
+ await page.getByTestId('logout').click()
86
+ await page.getByRole('button', { name: /忘记密码|Forgot password/i }).click()
87
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-forgot.png'), fullPage: true })
88
+ await page.locator('input[type="email"]').fill(email)
89
+ await page.locator('button[type="submit"]').click()
90
+ await expect(page.locator('.alert-success')).toBeVisible()
91
+ const reset = await received('password_reset')
92
+ expect(Boolean(reset?.token)).toBe(true)
93
+ await page.getByRole('button', { name: /已有重置令牌|I have a reset token/i }).click()
94
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-reset.png'), fullPage: true })
95
+ await page.goto(`/?token=${encodeURIComponent(reset?.token ?? '')}`)
96
+ await expect(page.locator('input[type="password"]').nth(0)).toBeVisible()
97
+ await page.locator('input[type="password"]').nth(0).fill('FullstackReset456')
98
+ await page.locator('input[type="password"]').nth(1).fill('FullstackReset456')
99
+ await page.locator('button[type="submit"]').click()
100
+ await expect(page.locator('.alert-success')).toBeVisible()
101
+ await page.getByRole('button', { name: /^(登录|Sign in|Login)$/i }).click()
102
+ await login(page, username, 'FullstackReset456')
103
+ await expect(page.getByTestId('settings-page')).toBeVisible()
104
+ expect(new URL(page.url()).searchParams.has('token')).toBe(false)
105
+ await page.reload()
106
+ await expect(page.getByTestId('settings-page')).toBeVisible()
107
+ })
46
108
 
47
109
  test('frontend displays configured core metadata and restores the administrator session', async ({ page }) => {
48
110
  await page.goto('/')
@@ -191,7 +253,7 @@ test('administrator and user complete account, avatar, preference, password and
191
253
  await userPage.getByTestId('password-form').getByRole('button', { name: /Change password/i }).click()
192
254
  await expect(userPage.getByTestId('login-form')).toBeVisible()
193
255
  await login(userPage, username, password)
194
- await expect(userPage.getByTestId('login-form')).toContainText(/不正确|incorrect/)
256
+ await expect(userPage.getByTestId('login-form')).toContainText(/不正确|incorrect/i)
195
257
  await login(userPage, username, newPassword)
196
258
  await expect(userPage.getByTestId('settings-page')).toBeVisible()
197
259
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `src/app-schemas.ts`:跨接口、跨层复用的输入/数据 Schema 与类型。
8
8
  - `src/app-contract.ts`:app 领域 contract;每个业务 path 直接写在对应接口定义中。
9
- - `src/admin-schemas.ts` / `src/admin-contract.ts`:管理员用户、固定角色、分页审计、个人设置、加密 JSON 头像上传 Schema/类型/路由;认证复用框架的 IAM auth contract,未开放自助注册和其它认证方式。
9
+ - `src/admin-schemas.ts` / `src/admin-contract.ts`:管理员用户、固定角色、分页审计、个人设置、加密 JSON 头像上传 Schema/类型/路由;认证复用框架的 IAM auth contract,通过服务端配置启用注册、密码/LDAP/验证码登录及密码重置。
10
10
  - `src/{{projectName}}-contract.ts`:工程级 contract,供 `@h-ai/serv` 与 `@h-ai/api-client` 共用。
11
11
 
12
12
  只使用一次的输出 Schema 直接内联到 `.output(...)`;同一 contract 文件内重复使用时才保留私有常量。
@@ -56,8 +56,15 @@ export const profileContract = {
56
56
  .output(apiContract.haiResultSchema(AvatarOutputSchema)),
57
57
  }
58
58
 
59
- /** 复用框架认证契约,避免额外开放自助注册和未配置的认证方式。 */
59
+ /** 复用框架认证契约;注册、通知和登录方式由 IAM 配置控制。 */
60
60
  export const authContract = {
61
+ publicConfig: apiContract.iam.auth.publicConfig,
62
+ register: apiContract.iam.auth.register,
63
+ loginWithLdap: apiContract.iam.auth.loginWithLdap,
64
+ loginWithOtp: apiContract.iam.auth.loginWithOtp,
65
+ sendOtp: apiContract.iam.auth.sendOtp,
66
+ requestPasswordReset: apiContract.iam.auth.requestPasswordReset,
67
+ confirmPasswordReset: apiContract.iam.auth.confirmPasswordReset,
61
68
  login: apiContract.iam.auth.login,
62
69
  logout: apiContract.iam.auth.logout,
63
70
  currentUser: apiContract.iam.auth.currentUser,
@@ -28,7 +28,7 @@ export const AdminUsersSchema = z.object({
28
28
  })
29
29
  export const AdminCreateUserSchema = z.object({
30
30
  username: z.string().min(3).max(64).regex(/^[\w-]+$/),
31
- password: z.string().min(8).max(128),
31
+ password: z.string().min(1),
32
32
  email: z.email().optional(),
33
33
  displayName: z.string().max(100).optional(),
34
34
  role: AdminRoleCodeSchema.default('user'),
@@ -6,12 +6,13 @@
6
6
 
7
7
  - `config/_core.yml`:应用元信息(名称 / 版本 / 环境 / 语言),启动时由 `core.config` 加载。
8
8
  - `config/_serv.yml`:服务端运行时设置(CORS 来源等)。
9
- - `config/_db.yml` / `_cache.yml` / `_iam.yml` / `_storage.yml` / `_audit.yml`:mini 管理所需模块配置,初始化前使用模块公开 Schema 验证。
10
- - `src/index.ts`:进程入口,先 `core.init({ configDir: './config' })`,再初始化 reldb → cache → crypto → storage → iam → audit;启动失败及退出反序释放。
9
+ - `config/_db.yml` / `_cache.yml` / `_iam.yml` / `_storage.yml` / `_audit.yml` / `_reach.yml`:管理及开发通知配置,初始化前使用模块公开 Schema 验证。Reach 默认控制台 Provider,生产按注释替换为 SMTP/短信/API。
10
+ - `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `_scheduler.yml` / `_deploy.yml`:保留未启用模块的完整注释示例;配置文件存在不代表模块已接入。先在本 workspace 安装对应依赖,再在 `src/server/runtime.ts` 按公开 Schema 校验、初始化并反序关闭,接入实际业务后按需取消注释。整份注释先去掉一层外层注释,可选字段继续按需开启。
11
+ - `src/index.ts`:进程入口,先 `core.init({ configDir: './config' })`,再初始化 reldb → cache → crypto → storage → reach(有配置时)→ iam → audit;启动失败及退出反序释放。
11
12
  - `src/app-server.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
12
13
  - `src/server/procedures/app-procedures.ts`:app 领域 procedures,对应 contract 中的 `app.info` / `app.echo` 路由。
13
14
  - `src/server/procedures/admin-procedures.ts`:认证、管理员用户/角色/审计管理和个人设置,使用 `.auth()` / `.role('admin')` 守卫。每个请求读取用户最新启用状态、角色和权限。
14
- - 当前用户与刷新 procedures 直接选用 `@h-ai/serv/features/iam` 的默认实现;仅覆写登录、退出、修改密码以追加业务审计。未选的注册/OTP/通用管理接口不挂载,HTTP App 类型直接复用公开 `ServHttpApp`。
15
+ - 标准认证 procedures 复用 `@h-ai/serv/features/iam`,注册、登录方式、重置与协议由 `_iam.yml` 控制;登录、退出、修改密码追加业务审计。HTTP App 类型复用公开 `ServHttpApp`。
15
16
  - `src/server/admin-service.ts`:角色分配、头像上传和审计编排,全部底层操作复用 hai 模块。固定 admin/user 两种角色;锁内重新鉴权,禁止管理员自删、自禁用、自降级,并补偿用户更新失败后的角色变更。
16
17
  - `tests/app-server.test.ts`:真实加密 HTTP 测试,覆盖 Cookie/受信原生刷新、RBAC、旧会话权限、并发管理操作、补偿、头像安全、偏好、审计及会话失效。
17
18
 
@@ -23,6 +24,10 @@ CLI 已生成根目录 `.env`,`HAI_ADMIN_DEFAULT_PASSWORD` 为每个项目独
23
24
 
24
25
  ## 开发
25
26
 
27
+ 所有配置字段以模块 Schema 为准;未使用字段和替代 Provider 用注释保留。叶子环境变量只覆盖已有 YAML 路径,先取消相应键的注释,或用 `HAI_DB` / `HAI_CACHE` 等整体对象覆盖;`connectTimeout` 对应 `HAI_CACHE_CONNECTTIMEOUT`。凭据示例在根 `.env.example`,实际密钥保存在忽略的 `.env` 或部署环境。`.gitignore` 的 `**/data/` 忽略所有运行数据及 `.gitkeep`;运行时会创建目录。`crypto.init()` 无配置参数,datapipe 为纯函数,不提供虚构配置。
28
+
29
+ 自动生成的 `.env` 只启用初始管理员密码和 `PUBLIC_API_BASE`;模块环境覆盖保留注释,以 YAML 为默认值来源。有明确环境覆盖时编辑 YAML 要同步核对。
30
+
26
31
  - `pnpm --filter {{fullstack.servPackageName}} dev`
27
32
  - `pnpm --filter {{fullstack.servPackageName}} test`
28
33
 
@@ -33,3 +38,13 @@ CLI 已生成根目录 `.env`,`HAI_ADMIN_DEFAULT_PASSWORD` 为每个项目独
33
38
  - Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
34
39
  - YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
35
40
  - 将 SQLite/本地存储放入本目录 `data/` 后运行 `pnpm docker:build:data` / `pnpm podman:build:data`,可生成带初始数据的镜像,持续写入仍应使用 volume。
41
+
42
+ ## IAM 认证配置
43
+
44
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
45
+
46
+ LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
47
+
48
+ 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
49
+
50
+ 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
@@ -0,0 +1 @@
1
+ {{{moduleConfig 'ai' features.ai}}}
@@ -1,3 +1 @@
1
- userTable: hai_iam_users
2
- userIdColumn: id
3
- userNameColumn: username
1
+ {{{moduleConfig 'audit' true}}}
@@ -1,2 +1 @@
1
- # 多实例部署请使用共享 Redis,保障会话、管理员变更锁一致。
2
- type: memory
1
+ {{{moduleConfig 'cache' true}}}
@@ -15,7 +15,14 @@ defaultLocale: {{fullstack.i18n.baseLocale}}
15
15
  logging:
16
16
  level: info
17
17
  format: json
18
+ # context:
19
+ # service: api
18
20
  redact:
19
21
  - password
20
22
  - token
21
23
  - secret
24
+
25
+ # 短 ID 生成配置;未设置时采用框架默认值。
26
+ # id:
27
+ # prefix: app_
28
+ # length: 21
@@ -1,3 +1 @@
1
- # 默认单实例 SQLite;HAI_DB 可整体替换为 PostgreSQL/MySQL 配置。
2
- type: sqlite
3
- database: ./data/app.db
1
+ {{{moduleConfig 'db' true}}}
@@ -0,0 +1 @@
1
+ {{{moduleConfig 'deploy' features.deploy}}}
@@ -1,21 +1 @@
1
- login:
2
- password: true
3
- otp: false
4
- ldap: false
5
- apikey: false
6
- password:
7
- minLength: 8
8
- maxLength: 128
9
- requireUppercase: false
10
- requireLowercase: false
11
- requireNumber: false
12
- requireSpecialChar: false
13
- session:
14
- maxAge: 86400
15
- sliding: true
16
- singleDevice: false
17
- refreshTokenMaxAge: 604800
18
- rbac:
19
- defaultRole: user
20
- # 仅由应用创建 admin/user,避免生成无业务用途的默认角色与权限。
21
- seedDefaultData: false
1
+ {{{moduleConfig 'iam' true}}}
@@ -0,0 +1 @@
1
+ {{{moduleConfig 'payment' features.payment}}}