@h-ai/cli 0.1.0-alpha.58 → 0.1.0-alpha.60
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.en.md +43 -20
- package/README.ja.md +43 -20
- package/README.md +43 -20
- package/dist/{chunk-WUSFJFW5.js → chunk-P6X63D3I.js} +64 -6
- package/dist/chunk-P6X63D3I.js.map +1 -0
- package/dist/{chunk-F4IBPVZ5.js → chunk-WWG3U5ZD.js} +20 -2
- package/dist/chunk-WWG3U5ZD.js.map +1 -0
- package/dist/cli-config-templates-EFABFSCY.js +5 -0
- package/dist/{cli-config-templates-6XA7V6CC.js.map → cli-config-templates-EFABFSCY.js.map} +1 -1
- package/dist/{cli-skill-templates-45CK4RQ2.js → cli-skill-templates-2KDUACT6.js} +3 -3
- package/dist/{cli-skill-templates-45CK4RQ2.js.map → cli-skill-templates-2KDUACT6.js.map} +1 -1
- package/dist/index.js +96 -73
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
- package/templates/README.md +8 -0
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +12 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +4 -7
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +11 -1
- package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +2 -1
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +72 -6
- package/templates/apps/api/package.json.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +1 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +63 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +8 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +10 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +46 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +21 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +11 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +12 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +32 -23
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +24 -24
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/UserManagement.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +20 -20
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +7 -8
- package/templates/apps/mobile-app/src/App.svelte.hbs +43 -0
- package/templates/base/e2e/app.spec.ts.hbs +105 -0
- package/templates/base/package.json.hbs +1 -1
- package/templates/base/src/hooks.server.ts.hbs +2 -2
- package/templates/base/src/lib/server/init.ts.hbs +1 -1
- package/templates/base/src/routes/+layout.server.ts.hbs +6 -0
- package/templates/base/tests/scaffold.test.ts.hbs +3 -0
- package/templates/base/vite.config.ts.hbs +1 -0
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +2 -16
- package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +10 -0
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +6 -95
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +6 -117
- package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +10 -0
- package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +10 -0
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +6 -97
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +6 -112
- package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +10 -0
- package/templates/features/iam/routes-shared/api/auth/config/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +1 -33
- package/templates/features/iam/routes-shared/api/auth/login/ldap/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/login/otp/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/otp/send/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/password-reset/confirm/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/password-reset/request/+server.ts +4 -0
- package/templates/features/iam/routes-shared/api/auth/register/+server.ts +1 -23
- package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +10 -0
- package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +11 -0
- package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +11 -0
- package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +10 -0
- package/templates/skills/hai-api-client/SKILL.md +5 -0
- package/templates/skills/hai-app-create/SKILL.md +2 -0
- package/templates/skills/hai-app-create/development.md +15 -0
- package/templates/skills/hai-deploy/SKILL.md +2 -0
- package/templates/skills/hai-framework-sync/SKILL.md +4 -0
- package/templates/skills/hai-iam/SKILL.md +9 -2
- package/templates/skills/hai-iam/reference.md +76 -11
- package/templates/skills/hai-reach/SKILL.md +2 -0
- package/templates/skills/hai-ui/reference.md +45 -0
- package/dist/chunk-F4IBPVZ5.js.map +0 -1
- package/dist/chunk-WUSFJFW5.js.map +0 -1
- package/dist/cli-config-templates-6XA7V6CC.js +0 -5
|
@@ -1,117 +1,11 @@
|
|
|
1
|
-
{{!-- H5 注册页面 --}}
|
|
2
1
|
<script lang='ts'>
|
|
3
|
-
|
|
4
|
-
* H5 注册页面
|
|
5
|
-
*/
|
|
2
|
+
import { goto } from '$app/navigation'
|
|
6
3
|
import { resolve } from '$app/paths'
|
|
7
|
-
{
|
|
8
|
-
import
|
|
9
|
-
{
|
|
10
|
-
import { Alert, Button, FormField, Input } from '@h-ai/ui'
|
|
4
|
+
import { page } from '$app/state'
|
|
5
|
+
import { kit } from '@h-ai/kit'
|
|
6
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
let password = $state('')
|
|
14
|
-
let confirmPassword = $state('')
|
|
15
|
-
let error = $state('')
|
|
16
|
-
let loading = $state(false)
|
|
17
|
-
|
|
18
|
-
async function handleRegister(event: SubmitEvent) {
|
|
19
|
-
event.preventDefault()
|
|
20
|
-
error = ''
|
|
21
|
-
if (password !== confirmPassword) {
|
|
22
|
-
{{#if hasI18n}}
|
|
23
|
-
error = m.auth_error_password_mismatch()
|
|
24
|
-
{{else}}
|
|
25
|
-
error = '密码不一致'
|
|
26
|
-
{{/if}}
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
loading = true
|
|
30
|
-
try {
|
|
31
|
-
const res = await fetch('/api/auth/register', {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify({ username, password, confirmPassword }),
|
|
35
|
-
})
|
|
36
|
-
const data = await res.json()
|
|
37
|
-
if (data.success) {
|
|
38
|
-
window.location.href = '/auth/login'
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
{{#if hasI18n}}
|
|
42
|
-
error = data.error?.message || m.auth_register_failed()
|
|
43
|
-
{{else}}
|
|
44
|
-
error = data.error?.message || '注册失败'
|
|
45
|
-
{{/if}}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
{{#if hasI18n}}
|
|
50
|
-
error = m.auth_error_network()
|
|
51
|
-
{{else}}
|
|
52
|
-
error = '网络错误'
|
|
53
|
-
{{/if}}
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
loading = false
|
|
57
|
-
}
|
|
58
|
-
}
|
|
8
|
+
const authActions = kit.client.createAuthActions()
|
|
59
9
|
</script>
|
|
60
10
|
|
|
61
|
-
<
|
|
62
|
-
{{#if hasI18n}}
|
|
63
|
-
<title>{m.auth_register_title()}</title>
|
|
64
|
-
{{else}}
|
|
65
|
-
<title>注册</title>
|
|
66
|
-
{{/if}}
|
|
67
|
-
</svelte:head>
|
|
68
|
-
|
|
69
|
-
<div class='p-4 pt-12 space-y-4'>
|
|
70
|
-
{{#if hasI18n}}
|
|
71
|
-
<h1 class='text-2xl font-black tracking-tight text-center'>{m.auth_register_title()}</h1>
|
|
72
|
-
{{else}}
|
|
73
|
-
<h1 class='text-2xl font-black tracking-tight text-center'>注册</h1>
|
|
74
|
-
{{/if}}
|
|
75
|
-
|
|
76
|
-
{#if error}
|
|
77
|
-
<Alert variant='error'>{error}</Alert>
|
|
78
|
-
{/if}
|
|
79
|
-
|
|
80
|
-
<form onsubmit={handleRegister} class='space-y-4'>
|
|
81
|
-
{{#if hasI18n}}
|
|
82
|
-
<FormField label={m.auth_label_username()} required>
|
|
83
|
-
<Input id='username' type='text' bind:value={username} required />
|
|
84
|
-
</FormField>
|
|
85
|
-
<FormField label={m.auth_label_password()} required>
|
|
86
|
-
<Input id='password' type='password' bind:value={password} required />
|
|
87
|
-
</FormField>
|
|
88
|
-
<FormField label={m.auth_label_confirm_password()} required>
|
|
89
|
-
<Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
|
|
90
|
-
</FormField>
|
|
91
|
-
<Button type='submit' variant='primary' loading={loading} class='w-full'>
|
|
92
|
-
{loading ? m.auth_register_loading() : m.auth_register_submit()}
|
|
93
|
-
</Button>
|
|
94
|
-
{{else}}
|
|
95
|
-
<FormField label='用户名' required>
|
|
96
|
-
<Input id='username' type='text' bind:value={username} required />
|
|
97
|
-
</FormField>
|
|
98
|
-
<FormField label='密码' required>
|
|
99
|
-
<Input id='password' type='password' bind:value={password} required />
|
|
100
|
-
</FormField>
|
|
101
|
-
<FormField label='确认密码' required>
|
|
102
|
-
<Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
|
|
103
|
-
</FormField>
|
|
104
|
-
<Button type='submit' variant='primary' loading={loading} class='w-full'>
|
|
105
|
-
{loading ? '注册中...' : '注册'}
|
|
106
|
-
</Button>
|
|
107
|
-
{{/if}}
|
|
108
|
-
</form>
|
|
109
|
-
|
|
110
|
-
<div class='text-center'>
|
|
111
|
-
{{#if hasI18n}}
|
|
112
|
-
<a href={resolve('/auth/login', {})} class='link link-primary text-sm'>{m.auth_register_to_login()}</a>
|
|
113
|
-
{{else}}
|
|
114
|
-
<a href={resolve('/auth/login', {})} class='link link-primary text-sm'>已有账号?立即登录</a>
|
|
115
|
-
{{/if}}
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
11
|
+
<AuthPanel config={page.data.iamPublicConfig} actions={authActions} initialMode='register' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/', {}))} />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { resolve } from '$app/paths'
|
|
3
|
+
import { page } from '$app/state'
|
|
4
|
+
import { kit } from '@h-ai/kit'
|
|
5
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
6
|
+
|
|
7
|
+
const actions = kit.client.createAuthActions()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='reset' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
|
|
@@ -1,36 +1,4 @@
|
|
|
1
|
-
import type { CoreConfig } from '@h-ai/core'
|
|
2
|
-
/**
|
|
3
|
-
* 登录 API
|
|
4
|
-
*/
|
|
5
|
-
import type { RequestHandler } from './$types'
|
|
6
|
-
import { core } from '@h-ai/core'
|
|
7
1
|
import { iam } from '@h-ai/iam'
|
|
8
2
|
import { kit } from '@h-ai/kit'
|
|
9
|
-
import { z } from 'zod'
|
|
10
3
|
|
|
11
|
-
const
|
|
12
|
-
identifier: z.string().trim().min(1).max(254),
|
|
13
|
-
password: z.string().min(1).max(128),
|
|
14
|
-
}).strict()
|
|
15
|
-
|
|
16
|
-
export const POST: RequestHandler = kit.handler(async ({ request, cookies, locals }) => {
|
|
17
|
-
const { identifier, password } = await kit.validate.body(request, LoginInputSchema)
|
|
18
|
-
|
|
19
|
-
const result = await iam.auth.login({ identifier, password })
|
|
20
|
-
if (!result.success) {
|
|
21
|
-
return kit.response.fromError(result.error, locals.requestId)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const { tokens } = result.data
|
|
25
|
-
const config = core.config.getOrThrow<CoreConfig>('core')
|
|
26
|
-
|
|
27
|
-
cookies.set('session_token', tokens.accessToken, {
|
|
28
|
-
path: '/',
|
|
29
|
-
httpOnly: true,
|
|
30
|
-
sameSite: 'lax',
|
|
31
|
-
secure: config.env === 'production' || config.env === 'staging',
|
|
32
|
-
maxAge: tokens.expiresIn,
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
return kit.response.ok({ authenticated: true }, locals.requestId)
|
|
36
|
-
})
|
|
4
|
+
export const POST = kit.auth.createApi(iam).login
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 注册 API
|
|
3
|
-
*/
|
|
4
|
-
import type { RequestHandler } from './$types'
|
|
5
1
|
import { iam } from '@h-ai/iam'
|
|
6
2
|
import { kit } from '@h-ai/kit'
|
|
7
3
|
|
|
8
|
-
export const POST
|
|
9
|
-
const body = await request.json()
|
|
10
|
-
const { username, email, password, confirmPassword } = body
|
|
11
|
-
|
|
12
|
-
if (!username || !password) {
|
|
13
|
-
return kit.response.badRequest('Username and password are required')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (password !== confirmPassword) {
|
|
17
|
-
return kit.response.badRequest('Passwords do not match')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const result = await iam.user.register({ username, email, password })
|
|
21
|
-
if (!result.success) {
|
|
22
|
-
return kit.response.badRequest(result.error.message)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return kit.response.ok({ message: 'Registration successful' }, locals.requestId)
|
|
26
|
-
}
|
|
4
|
+
export const POST = kit.auth.createApi(iam).register
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { resolve } from '$app/paths'
|
|
3
|
+
import { page } from '$app/state'
|
|
4
|
+
import { kit } from '@h-ai/kit'
|
|
5
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
6
|
+
|
|
7
|
+
const actions = kit.client.createAuthActions()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='forgot' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { goto } from '$app/navigation'
|
|
3
|
+
import { resolve } from '$app/paths'
|
|
4
|
+
import { page } from '$app/state'
|
|
5
|
+
import { kit } from '@h-ai/kit'
|
|
6
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
7
|
+
|
|
8
|
+
const authActions = kit.client.createAuthActions()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<AuthPanel config={page.data.iamPublicConfig} actions={authActions} initialMode='login' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/', {}))} />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { goto } from '$app/navigation'
|
|
3
|
+
import { resolve } from '$app/paths'
|
|
4
|
+
import { page } from '$app/state'
|
|
5
|
+
import { kit } from '@h-ai/kit'
|
|
6
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
7
|
+
|
|
8
|
+
const authActions = kit.client.createAuthActions()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<AuthPanel config={page.data.iamPublicConfig} actions={authActions} initialMode='register' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/', {}))} />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { resolve } from '$app/paths'
|
|
3
|
+
import { page } from '$app/state'
|
|
4
|
+
import { kit } from '@h-ai/kit'
|
|
5
|
+
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
6
|
+
|
|
7
|
+
const actions = kit.client.createAuthActions()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='reset' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
|
|
@@ -174,3 +174,8 @@ if (login.success) {
|
|
|
174
174
|
- 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
|
|
175
175
|
- 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
|
|
176
176
|
- httpOnly cookie 模式需服务端与客户端同步配置,不支持跨域刷新(`SameSite=Strict`)。
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
## 配置驱动认证页面
|
|
180
|
+
|
|
181
|
+
`apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
|
|
@@ -17,6 +17,8 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
|
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
+
需要新建项目、添加模块、补齐配置或生成代码时,先按 [CLI 安装与命令](development.md#cli-安装与命令) 全局安装 `@h-ai/cli`,再执行 `hai` 命令。
|
|
21
|
+
|
|
20
22
|
1. 读取 [development.md](development.md):先按真实需求匹配框架能力、已安装版本与应用架构,再确定改动。适用能力包括同一模块已有 feature、配置和扩展点,不以导入包作为复用完成的证据。
|
|
21
23
|
2. 读取所选模块的 skill 与实际公开类型,复用现有服务和组件。新增依赖或直接使用第三方 SDK 前说明已有能力的具体缺口;无需安装无关模块。
|
|
22
24
|
3. 新增或改变行为时用有意义的目标用例验证,已有回归保持通过;按下方参考实现,使用 [hai-app-review](../hai-app-review/SKILL.md) 检查改动涉及的安全与质量边界。
|
|
@@ -9,6 +9,21 @@
|
|
|
9
9
|
- 从所属 workspace 的依赖与 lockfile 确认 `@h-ai/*` 实际解析版本和 npm/local 模式。模板 skill 提供导航,**当前安装包的 `package.json.exports`、公开 `.d.ts` 与随包文档决定可用 API**;本地框架源码只在版本匹配或显式联调时作实现证据。
|
|
10
10
|
- 若 skill 与安装版本不符,以公开类型和实际行为核验。先使用已支持能力;任务确需新版本时列明版本变更、调用方与验证范围。不能直接复制未发布源码的 API,也不能为绕过差异添加虚构兼容层。
|
|
11
11
|
|
|
12
|
+
## CLI 安装与命令
|
|
13
|
+
|
|
14
|
+
CLI 的 npm 包名是 `@h-ai/cli`,安装后提供 `hai` 命令。使用脚手架前先全局安装并确认版本;PowerShell 中使用 `pnpm.cmd` 执行 pnpm 命令。
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add -g @h-ai/cli
|
|
18
|
+
hai --version
|
|
19
|
+
hai --help
|
|
20
|
+
|
|
21
|
+
# 新建项目
|
|
22
|
+
hai create my-app
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
已有项目先进入项目根目录,再按需要执行 `hai add <module>`、`hai init` 或 `hai generate <type> <name>`。已安装 CLI 时,相同安装命令可更新到当前发布版本;需要指定版本时使用 `@h-ai/cli@<version>`。命令和模板以实际安装的 CLI 为准,不能把未发布的框架源码能力当作已安装包能力。
|
|
26
|
+
|
|
12
27
|
## 将需求映射到现有能力
|
|
13
28
|
|
|
14
29
|
应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite mobile-app 在 Node 构建阶段经 core 加载 YAML/环境覆盖并注入公开字段,配置变化需重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
|
|
@@ -218,6 +218,8 @@ if (result.success) {
|
|
|
218
218
|
|
|
219
219
|
## CLI 命令
|
|
220
220
|
|
|
221
|
+
通过 CLI 部署前,先按 [CLI 安装与命令](../hai-app-create/development.md#cli-安装与命令) 全局安装 `@h-ai/cli`,确认 `hai --version` 可运行后再执行下列命令。在 Node.js 脚本中直接使用 `@h-ai/deploy` API 时无需安装 CLI。
|
|
222
|
+
|
|
221
223
|
```bash
|
|
222
224
|
# 部署当前目录
|
|
223
225
|
hai deploy
|
|
@@ -40,6 +40,8 @@ description: "同步 hai-framework 通用 skills、AI 入口与应用依赖,
|
|
|
40
40
|
|
|
41
41
|
先读取当前应用入口与 package.json/lockfile,记录实际 npm/local 模式及解析版本。同步 skill 不能证明新 API 已安装或可用:以目标依赖的公开 exports/types 验证示例与配置,未发布框架能力仅用于已授权本地联调。
|
|
42
42
|
|
|
43
|
+
CLI 生成的 `@h-ai/*` 依赖固定为 CLI 对应版本,pnpm catalog 也使用精确版本。统一升级所有框架包后检查 lockfile 与已安装 exports。
|
|
44
|
+
|
|
43
45
|
| 变更类型 | 首选位置 |
|
|
44
46
|
| --- | --- |
|
|
45
47
|
| 通用 hai 模块用法 | `hai-framework/packages/cli/templates/skills/hai-*/SKILL.md` |
|
|
@@ -53,6 +55,8 @@ description: "同步 hai-framework 通用 skills、AI 入口与应用依赖,
|
|
|
53
55
|
|
|
54
56
|
### 3. 同步到应用仓库
|
|
55
57
|
|
|
58
|
+
通过已发布 CLI 补齐缺失文件时,先按 [CLI 安装与命令](../hai-app-create/development.md#cli-安装与命令) 安装或更新 `@h-ai/cli`,再从应用根目录运行 `hai init`。它只补齐缺失内容,不覆盖已有 AI 规则和 skills;已有文件仍需对照模板合并。未发布的模板修正需从框架源目录同步,不能假定已安装 CLI 包含这些修正。
|
|
59
|
+
|
|
56
60
|
把源头完整 skill 目录(含 reference.md 等被引用资源)复制到应用仓库 `.agents/skills/<skill>/`,并同步入口引用:
|
|
57
61
|
|
|
58
62
|
- `AGENTS.md`
|
|
@@ -13,15 +13,21 @@ description: "使用 @h-ai/iam 实现登录、注册、会话、OTP/LDAP/API Key
|
|
|
13
13
|
| 适用场景 | 应用身份认证、会话失效或角色权限管理 |
|
|
14
14
|
| 输入 | IamConfigInput、已初始化依赖、凭据、用户/角色/权限 ID |
|
|
15
15
|
| 输出 | 认证、会话与授权操作的 HaiResult |
|
|
16
|
-
| 限制 | 仅服务端,先初始化 reldb/cache(crypto 未初始化时由 IAM 初始化);公开认证入口是 iam.auth。客户端经 contract/API
|
|
16
|
+
| 限制 | 仅服务端,先初始化 reldb/cache(crypto 未初始化时由 IAM 初始化);公开认证入口是 iam.auth。客户端经 contract/API 调用,凭证只用于认证和安全存储,不进入日志或公开配置。 |
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
-
- `_iam.yml`
|
|
20
|
+
- `_iam.yml` 只存 IamConfigSettingsInput,运行时回调单独传给 iam.init;不把 reldb/cache 句柄混入配置。
|
|
21
21
|
- 角色资料与 permissionIds 一次保存:未传保持、空数组清空、未知 ID 失败;人数查询失败不能显示为 0。
|
|
22
22
|
- IAM 服务端是 iam.auth;typed HTTP client 的路径是 apiClient.iam.auth。
|
|
23
23
|
- OTP/API Key 等先核对 login 开关;不把未启用能力的 NOT_INITIALIZED 当认证失败。
|
|
24
24
|
|
|
25
|
+
- 认证页面使用 iam.publicConfig + AuthPanel,协议用弹窗,表单通过 header/extraFields/footer 或 AuthPanel 的 login*/register* snippet 扩展;Kit 使用 kit.auth.createApi/publicPaths + kit.client.createAuthActions,HTTP 客户端使用 apiClient.createAuthActions。
|
|
26
|
+
- LDAP/Reach 均可选:基础认证无需安装;默认 LDAP 关闭,选用默认客户端才安装 ldapts 并设置 login.ldap + ldap。OTP 邮件/短信由 otp.email/sms 控制,选择 Reach 标准模板或自定义发送回调;具体签名与失败处理见参考中的“可选 LDAP 与通知”。
|
|
27
|
+
- 多节点共享 DB + Redis,Memory 只用于单进程。重置的策略失败计入 maxAttempts;OTP 消费不解除 resendInterval。管理员 createUser 不受自助注册开关限制。
|
|
28
|
+
|
|
29
|
+
- 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
|
|
30
|
+
|
|
25
31
|
## 按需参考
|
|
26
32
|
|
|
27
33
|
详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
|
|
@@ -30,6 +36,7 @@ description: "使用 @h-ai/iam 实现登录、注册、会话、OTP/LDAP/API Key
|
|
|
30
36
|
- 依赖
|
|
31
37
|
- 适用场景
|
|
32
38
|
- 使用步骤
|
|
39
|
+
- 可选 LDAP 与通知 — 安装、TLS/AD 配置、标准模板、自定义发送
|
|
33
40
|
- 核心 API
|
|
34
41
|
- HTTP API 契约
|
|
35
42
|
- 错误码 — `HaiIamError`
|
|
@@ -68,27 +68,40 @@ rbac:
|
|
|
68
68
|
### 2. 初始化
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
|
+
import type { IamConfigSettingsInput } from '@h-ai/iam'
|
|
71
72
|
import { cache } from '@h-ai/cache'
|
|
72
|
-
import {
|
|
73
|
+
import { core } from '@h-ai/core'
|
|
73
74
|
import { iam } from '@h-ai/iam'
|
|
75
|
+
import { reldb } from '@h-ai/reldb'
|
|
74
76
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
await
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
// 位于应用启动函数内;依赖初始化失败时立即返回,不能继续启动 IAM。
|
|
78
|
+
core.init({ configDir: './config' })
|
|
79
|
+
const db = await reldb.init({ type: 'sqlite', database: './data.db' })
|
|
80
|
+
if (!db.success) return db
|
|
81
|
+
const cached = await cache.init({ type: 'memory' })
|
|
82
|
+
if (!cached.success) return cached
|
|
83
|
+
const initialized = await iam.init(core.config.getOrThrow<IamConfigSettingsInput>('iam'))
|
|
84
|
+
if (!initialized.success) return initialized
|
|
85
|
+
// 关闭时按 iam → cache → reldb 的反向依赖顺序执行。
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
`iam.config` 返回的是脱敏后的配置快照;LDAP `url` / `bindPassword` 等敏感值会自动隐藏。
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
基础初始化创建用户/RBAC 五张表;启用 API Key 登录时另创建 `hai_iam_api_keys`。
|
|
89
91
|
|
|
90
92
|
Token 和 OTP 存储在 cache 中(不落库)。refreshToken 使用独立的 cache key 存储。
|
|
91
93
|
|
|
94
|
+
### 可选 LDAP 与通知
|
|
95
|
+
|
|
96
|
+
LDAP 默认关闭,Reach 不属于 IAM 的启动前置依赖。`ldapts` 和 `@h-ai/reach` 是可选 peer dependency,基础密码/RBAC 无需安装这两个包。
|
|
97
|
+
|
|
98
|
+
- 默认 LDAP 客户端:在服务端 workspace 执行 `pnpm add ldapts`,配置 `login.ldap: true` 与 `ldap.url/bindDn/bindPassword/searchBase`。`searchFilter` 默认 `(uid={{username}})`;AD 可使用 `(sAMAccountName={{username}})` 与对应 `usernameAttribute`。`ldaps://` 使用 TLS,`ldap://` + `useTls: true` 使用 StartTLS。`ldapClientFactory` 可注入返回 `bind/search/unbind` 和 HaiResult 的客户端,此时无需安装 ldapts。
|
|
99
|
+
- Reach 自动发送:应用安装、初始化 `@h-ai/reach`,在 `iam.init({ ...settings, reach })` 中注入一次。`otp_email/otp_sms` 模板使用 `{code}`;`password_reset/password_reset_sms` 使用 `{token}/{expiresAt}/{resetUrl}`。模板需在 IAM 初始化前配置,Provider 绑定实际邮件/短信通道。`passwordReset.mode` 选择 `email/phone`。
|
|
100
|
+
- 自定义发送:直接传 `onOtpSendEmail(email, code)`、`onOtpSendSms(phone, code)`、`onPasswordResetRequest(user, token, expiresAt)`,均返回 `Promise<void>`。可以只实现选用通道或与 Reach 混用,显式回调优先。检查发送服务结果,投递失败在回调内抛错;IAM 捕获并返回业务错误,不要吞掉发送失败。
|
|
101
|
+
- `otp` 必须有配置并开启所需通道;缺少回调或标准模板时 `iam.publicConfig` 不公布该入口。Reach 由应用管理生命周期,IAM 不负责初始化或关闭。Console 仅记录元数据。
|
|
102
|
+
|
|
103
|
+
协议以 Modal 显示,关闭后保留输入。URL 方式必须为 HTTP(S) 且页面允许 iframe 嵌入;脚本被隔离禁用。`LoginForm/RegisterForm` 提供 `header/extraFields/footer`;`AuthPanel` 提供 `loginHeader/loginFields/loginFooter`、`registerHeader/registerFields/registerFooter`。额外字段由应用绑定状态,在自己的动作中处理,IAM 不接收未经定义的字段。
|
|
104
|
+
|
|
92
105
|
## 核心 API
|
|
93
106
|
|
|
94
107
|
### 认证 — `iam.auth`
|
|
@@ -332,6 +345,58 @@ await apiClient.auth.clear()
|
|
|
332
345
|
- `deleteUser` / `updateUser({ enabled: false })` / `changePassword` / `confirmPasswordReset`
|
|
333
346
|
- `assignRole` / `removeRole` / `deleteRole` 会同步更新活跃会话中的角色列表
|
|
334
347
|
|
|
348
|
+
## 配置驱动认证页面
|
|
349
|
+
|
|
350
|
+
### 配置 Markdown 协议
|
|
351
|
+
|
|
352
|
+
协议支持 `agreements.userAgreementMarkdown` 与 `privacyPolicyMarkdown` 直接从配置加载;YAML 的 `|` 保留 Markdown 段落和换行。弹窗复用安全 MarkdownRenderer,关闭原始 HTML、脚本和代码复制工具。同主题同时提供正文与 URL 时优先正文,URL 可作为额外查看入口;只有 URL 时使用隔离 iframe。
|
|
353
|
+
|
|
354
|
+
```yaml
|
|
355
|
+
agreements:
|
|
356
|
+
showOnRegister: true
|
|
357
|
+
showOnLogin: false
|
|
358
|
+
userAgreementMarkdown: |
|
|
359
|
+
# 用户协议
|
|
360
|
+
|
|
361
|
+
**账户使用条款**
|
|
362
|
+
|
|
363
|
+
- 请妥善保管账户凭据。
|
|
364
|
+
privacyPolicyMarkdown: |
|
|
365
|
+
# 隐私政策
|
|
366
|
+
|
|
367
|
+
注册资料用于账户管理,会话信息用于身份认证。
|
|
368
|
+
# 可选 URL;同主题同时配置 Markdown 和 URL 时,弹窗优先显示 Markdown。
|
|
369
|
+
# userAgreementUrl: https://example.com/terms
|
|
370
|
+
# privacyPolicyUrl: https://example.com/privacy
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
服务端通过 `iam.publicConfig` 提供白名单配置,或通过客户端的 `client.iam.auth.publicConfig()` 标准 HTTP contract 获取。只展示已开启且具备依赖的登录方式;LDAP 为可选集成,见上方配置与安装说明。
|
|
376
|
+
|
|
377
|
+
| 配置 | 用途 |
|
|
378
|
+
| --- | --- |
|
|
379
|
+
| `login.password / otp / ldap` | 独立启用密码、验证码、企业账号 |
|
|
380
|
+
| `otp.email / sms` | 独立启用邮件、短信 |
|
|
381
|
+
| `register.enabled / defaultEnabled` | 自助注册及新账户状态 |
|
|
382
|
+
| `passwordReset.enabled / mode / resetUrl` | 启用重置、选择 email/phone 渠道、配置重置页面绝对地址 |
|
|
383
|
+
| `agreements.showOnRegister / showOnLogin` | 注册/登录协议弹窗显示;链接只接受 HTTP(S) |
|
|
384
|
+
| `ui.variant / title / subtitle / brandTitle / brandText / logoUrl / illustrationUrl` | 卡片/分栏布局、标题、品牌、Logo 和配图 |
|
|
385
|
+
|
|
386
|
+
选择 Reach 时,在 reldb/cache/Reach 初始化成功后使用 `iam.init({ ...settings, reach })` 自动接入 Reach 的 `otp_email`、`otp_sms`、`password_reset`、`password_reset_sms` 模板。模板绑定对应邮件/短信 Provider;显式发送回调可覆盖默认行为。Console Provider 只记录元数据,实际收信需配置 SMTP、短信或 HTTP 通知服务。发送失败返回错误,不会伪报已发送。
|
|
387
|
+
|
|
388
|
+
SvelteKit 使用 `kit.auth.createApi(iam)` 导出标准 handler,匿名端点白名单使用 `kit.auth.publicPaths`。客户端通过 `kit.client.createAuthActions()` 连接 Cookie 端点;独立 HTTP 客户端通过 `apiClient.createAuthActions(client)` 连接 IAM contract 并自动保存 Token 到已配置安全存储。将配置及动作直接传入 `AuthPanel`,完成登录、注册、找回及重置;单独使用 `LoginForm` 可传 `methods`、`onsendotp`、`resendInterval`,密码表单可传完整 `passwordPolicy`。
|
|
389
|
+
|
|
390
|
+
管理员使用 `iam.user.createUser()` 建用户,调用端必须校验管理员权限;关闭自助注册不影响管理端。注册与密码登录开关独立,`registerAndLogin` 在注册完成后创建会话;默认禁用账户不会创建会话。
|
|
391
|
+
|
|
392
|
+
### 分布式安全与边界
|
|
393
|
+
|
|
394
|
+
所有节点必须共享同一数据库和 Redis;Memory 仅用于单进程。登录 `maxLoginAttempts / lockoutDuration` 使用数据库原子更新,到期后的失败重新计数。OTP `maxAttempts / resendInterval` 使用共享锁和独立发送间隔状态,耗尽或消费验证码不能绕过重发限制,并发只消费一次。重置 `tokenExpiresIn / maxAttempts` 使用共享缓存 TTL 和每用户共享锁;每次有效令牌请求(包括密码策略失败)计数,最新令牌有效且只成功消费一次。重置后撤销旧会话、解除登录锁定;禁用用户不能通过密码、LDAP、OTP 登录。密码策略以服务端为准。
|
|
395
|
+
|
|
396
|
+
可选字段(如 `ldap`)若尚未写入 YAML,请先声明该字段,或通过 `HAI_IAM` 的 JSON 值整体覆盖模块配置;已有字段支持 `HAI_IAM_LOGIN_OTP` 等环境变量覆盖。
|
|
397
|
+
|
|
398
|
+
标准 HTTP 入口:`GET /auth/config`、`POST /auth/login/ldap`、`POST /auth/password-reset/request`、`POST /auth/password-reset/confirm`;验证码继续使用 `/auth/otp/send` 与 `/auth/login/otp`。安全错误:`SECURITY_BUSY (hai:iam:023)`、`PASSWORD_RESET_DISABLED (hai:iam:024)`。
|
|
399
|
+
|
|
335
400
|
## 相关 Skills
|
|
336
401
|
|
|
337
402
|
- `hai-build`:模块初始化顺序(db → cache → iam)
|
|
@@ -568,6 +568,51 @@ typescript, javascript, python, java, go, rust, c, cpp, csharp, ruby, php, swift
|
|
|
568
568
|
}
|
|
569
569
|
```
|
|
570
570
|
|
|
571
|
+
## 配置驱动认证页面
|
|
572
|
+
|
|
573
|
+
协议支持 `agreements.userAgreementMarkdown` 与 `privacyPolicyMarkdown` 直接从配置加载;YAML 的 `|` 保留 Markdown 段落和换行。弹窗复用安全 MarkdownRenderer,关闭原始 HTML、脚本和代码复制工具。同主题同时提供正文与 URL 时优先正文,URL 可作为额外查看入口;只有 URL 时使用隔离 iframe。
|
|
574
|
+
|
|
575
|
+
```yaml
|
|
576
|
+
agreements:
|
|
577
|
+
showOnRegister: true
|
|
578
|
+
showOnLogin: false
|
|
579
|
+
userAgreementMarkdown: |
|
|
580
|
+
# 用户协议
|
|
581
|
+
|
|
582
|
+
**账户使用条款**
|
|
583
|
+
|
|
584
|
+
- 请妥善保管账户凭据。
|
|
585
|
+
privacyPolicyMarkdown: |
|
|
586
|
+
# 隐私政策
|
|
587
|
+
|
|
588
|
+
注册资料用于账户管理,会话信息用于身份认证。
|
|
589
|
+
# 可选 URL;同主题同时配置 Markdown 和 URL 时,弹窗优先显示 Markdown。
|
|
590
|
+
# userAgreementUrl: https://example.com/terms
|
|
591
|
+
# privacyPolicyUrl: https://example.com/privacy
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
AuthPanel 直接接收 IAM 白名单配置和 actions,内置登录(密码/LDAP/邮件/短信)、注册、找回、重置及协议展示。使用 `config.ui` 配置卡片/分栏、标题、品牌、Logo 和配图。`LoginForm` 提供 methods、onsendotp、otpLength、resendInterval;密码表单接受 passwordPolicy。组件只渲染与交互,网络和 Token 存储通过 actions 注入。认证场景按公开 IAM 错误码显示当前 UI 语言的提示,应用自定义错误保留原文。
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
协议在 Modal 内显示并保留表单输入;使用 URL 时,协议页面须允许 iframe 嵌入,组件禁用其脚本,另提供新窗口查看入口。登录/注册表单使用 `header/extraFields/footer` snippet,AuthPanel 透传 `loginHeader/loginFields/loginFooter` 和 `registerHeader/registerFields/registerFooter`,底部保留内置导航。自定义字段由应用绑定状态和提交,不会隐式写入 IAM 用户模型。
|
|
600
|
+
|
|
601
|
+
```svelte
|
|
602
|
+
<!-- config 来自 IAM 公开配置,actions 来自 Kit/API client;m 为应用翻译。 -->
|
|
603
|
+
<AuthPanel {config} {actions}>
|
|
604
|
+
{#snippet loginHeader()}
|
|
605
|
+
<p>{m.loginHelp()}</p>
|
|
606
|
+
{/snippet}
|
|
607
|
+
{#snippet registerFields()}
|
|
608
|
+
<label><input type='checkbox' bind:checked={subscribe} />{m.subscribe()}</label>
|
|
609
|
+
{/snippet}
|
|
610
|
+
</AuthPanel>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
上例的 subscribe 为应用自身的 $state,应用包装 register 动作校验/提交扩展业务数据;如需单独组合表单,使用 `<RegisterForm> {#snippet extraFields()}...{/snippet} </RegisterForm>`。扩展里的按钮须为 `type="button"`,避免误提交。
|
|
614
|
+
|
|
615
|
+
|
|
571
616
|
## 重要约定
|
|
572
617
|
|
|
573
618
|
1. **Svelte 5 Runes**:使用 `$state`、`$derived`、`$effect`
|