@h-ai/cli 0.1.0-alpha.59 → 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.
Files changed (84) hide show
  1. package/README.en.md +9 -1
  2. package/README.ja.md +9 -1
  3. package/README.md +9 -1
  4. package/dist/{chunk-WUSFJFW5.js → chunk-P6X63D3I.js} +64 -6
  5. package/dist/chunk-P6X63D3I.js.map +1 -0
  6. package/dist/cli-config-templates-EFABFSCY.js +5 -0
  7. package/dist/{cli-config-templates-6XA7V6CC.js.map → cli-config-templates-EFABFSCY.js.map} +1 -1
  8. package/dist/index.js +6 -4
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/templates/README.md +8 -0
  12. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +12 -0
  13. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +4 -7
  14. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +11 -1
  15. package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +2 -1
  16. package/templates/apps/api/e2e/api-service.spec.ts.hbs +72 -6
  17. package/templates/apps/api/package.json.hbs +4 -0
  18. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +10 -0
  19. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +1 -1
  20. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -0
  21. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +1 -1
  22. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  23. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +1 -1
  24. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +10 -0
  25. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +1 -1
  26. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +63 -1
  27. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +1 -1
  28. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +8 -1
  29. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  30. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +10 -0
  31. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +46 -0
  32. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +21 -0
  33. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
  34. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +1 -1
  35. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +2 -2
  36. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +7 -3
  37. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +11 -4
  38. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +12 -0
  39. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +2 -2
  40. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +2 -2
  41. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
  42. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +1 -1
  43. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +3 -0
  44. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +32 -23
  45. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +24 -24
  46. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/UserManagement.svelte.hbs +1 -1
  47. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +20 -20
  48. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +7 -8
  49. package/templates/apps/mobile-app/src/App.svelte.hbs +43 -0
  50. package/templates/base/e2e/app.spec.ts.hbs +105 -0
  51. package/templates/base/package.json.hbs +1 -1
  52. package/templates/base/src/hooks.server.ts.hbs +2 -2
  53. package/templates/base/src/lib/server/init.ts.hbs +1 -1
  54. package/templates/base/src/routes/+layout.server.ts.hbs +6 -0
  55. package/templates/base/tests/scaffold.test.ts.hbs +3 -0
  56. package/templates/base/vite.config.ts.hbs +1 -0
  57. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +2 -16
  58. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +10 -0
  59. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +6 -95
  60. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +6 -117
  61. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +10 -0
  62. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +10 -0
  63. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +6 -97
  64. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +6 -112
  65. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +10 -0
  66. package/templates/features/iam/routes-shared/api/auth/config/+server.ts +4 -0
  67. package/templates/features/iam/routes-shared/api/auth/login/+server.ts +1 -33
  68. package/templates/features/iam/routes-shared/api/auth/login/ldap/+server.ts +4 -0
  69. package/templates/features/iam/routes-shared/api/auth/login/otp/+server.ts +4 -0
  70. package/templates/features/iam/routes-shared/api/auth/otp/send/+server.ts +4 -0
  71. package/templates/features/iam/routes-shared/api/auth/password-reset/confirm/+server.ts +4 -0
  72. package/templates/features/iam/routes-shared/api/auth/password-reset/request/+server.ts +4 -0
  73. package/templates/features/iam/routes-shared/api/auth/register/+server.ts +1 -23
  74. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +10 -0
  75. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +11 -0
  76. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +11 -0
  77. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +10 -0
  78. package/templates/skills/hai-api-client/SKILL.md +5 -0
  79. package/templates/skills/hai-iam/SKILL.md +9 -2
  80. package/templates/skills/hai-iam/reference.md +76 -11
  81. package/templates/skills/hai-reach/SKILL.md +2 -0
  82. package/templates/skills/hai-ui/reference.md +45 -0
  83. package/dist/chunk-WUSFJFW5.js.map +0 -1
  84. package/dist/cli-config-templates-6XA7V6CC.js +0 -5
@@ -89,10 +89,10 @@ const haiHandle = kit.createHandle({
89
89
  data: session.data,
90
90
  }
91
91
  },
92
- operations: iam.auth,
93
92
  loginUrl: '/auth/login',
94
93
  protectedPaths: ['/admin/*', '/api/*'],
95
- publicPaths: ['/api/auth/*', '/api/public/*', '/api/health'],
94
+ publicPaths: [...kit.auth.publicPaths, '/api/auth/logout', '/api/public/*', '/api/health'],
95
+ operations: () => iam.auth,
96
96
  },
97
97
  {{/if}}
98
98
  rateLimit: { windowMs: 60000, maxRequests: 200 },
@@ -190,7 +190,7 @@ export async function initApp(): Promise<void> {
190
190
 
191
191
  {{#if features.iam}}
192
192
  // ─── 初始化 IAM ───
193
- const iamResult = await iam.init(iamConfig)
193
+ const iamResult = await iam.init({ ...iamConfig{{#if features.reach}}, reach{{/if}} })
194
194
  if (!iamResult.success) {
195
195
  throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
196
196
  }
@@ -2,11 +2,17 @@
2
2
  {{#unless isSvelteOnlyApp}}
3
3
  import type { CoreConfig } from '@h-ai/core'
4
4
  import { core } from '@h-ai/core'
5
+ {{#if features.iam}}
6
+ import { iam } from '@h-ai/iam'
7
+ {{/if}}
5
8
 
6
9
  /** 只向浏览器提供公开配置,避免暴露日志上下文、凭证或其它模块配置。 */
7
10
  export function load() {
8
11
  const config = core.config.getOrThrow<CoreConfig>('core')
9
12
  return {
13
+ {{#if features.iam}}
14
+ iamPublicConfig: iam.publicConfig,
15
+ {{/if}}
10
16
  appConfig: {
11
17
  name: config.name,
12
18
  version: config.version,
@@ -55,6 +55,9 @@ describe('generated scaffold', () => {
55
55
  const config = core.config.getOrThrow<CoreConfig>('core')
56
56
  expect(core.logger.getLevel()).toBe(config.logging?.level ?? 'info')
57
57
  expect(load()).toEqual({
58
+ {{#if features.iam}}
59
+ iamPublicConfig: null,
60
+ {{/if}}
58
61
  appConfig: { name: config.name, version: config.version, env: config.env, defaultLocale: config.defaultLocale },
59
62
  })
60
63
  })
@@ -42,6 +42,7 @@ export default defineConfig(({ mode }) => {
42
42
  {{/if}}
43
43
 
44
44
  return {
45
+ envPrefix: ['VITE_', 'PUBLIC_'],
45
46
  {{#if isSvelteOnlyApp}}
46
47
  define: { __APP_CORE_CONFIG__: JSON.stringify(appConfig) },
47
48
  {{/if}}
@@ -1,21 +1,7 @@
1
- {{!-- 认证布局(登录/注册共用) --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 认证布局(登录/注册共用)
5
- */
6
2
  import type { Snippet } from 'svelte'
7
3
 
8
- interface Props {
9
- children: Snippet
10
- }
11
-
12
- const { children }: Props = $props()
4
+ const { children }: { children: Snippet } = $props()
13
5
  </script>
14
6
 
15
- <div class='min-h-screen flex items-center justify-center bg-base-200'>
16
- <div class='card w-full max-w-md bg-base-100 shadow-xl'>
17
- <div class='card-body'>
18
- {@render children()}
19
- </div>
20
- </div>
21
- </div>
7
+ {@render children()}
@@ -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', {})} />
@@ -1,100 +1,11 @@
1
- {{!-- Admin 登录页面 --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 登录页面
5
- */
2
+ import { goto } from '$app/navigation'
6
3
  import { resolve } from '$app/paths'
7
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
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
- let username = $state('')
13
- let password = $state('')
14
- let error = $state('')
15
- let loading = $state(false)
16
-
17
- async function handleLogin(event: SubmitEvent) {
18
- event.preventDefault()
19
- error = ''
20
- loading = true
21
- try {
22
- const res = await fetch('/api/auth/login', {
23
- method: 'POST',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({ identifier: username, password }),
26
- })
27
- const data = await res.json()
28
- if (data.success) {
29
- window.location.href = '/admin'
30
- }
31
- else {
32
- {{#if hasI18n}}
33
- error = data.error?.message || m.auth_login_failed()
34
- {{else}}
35
- error = data.error?.message || '登录失败'
36
- {{/if}}
37
- }
38
- }
39
- catch {
40
- {{#if hasI18n}}
41
- error = m.auth_error_network()
42
- {{else}}
43
- error = '网络错误'
44
- {{/if}}
45
- }
46
- finally {
47
- loading = false
48
- }
49
- }
8
+ const actions = kit.client.createAuthActions()
50
9
  </script>
51
10
 
52
- <svelte:head>
53
- {{#if hasI18n}}
54
- <title>{m.auth_login_title()}</title>
55
- {{else}}
56
- <title>登录</title>
57
- {{/if}}
58
- </svelte:head>
59
-
60
- {{#if hasI18n}}
61
- <h2 class='text-2xl font-bold text-center mb-6'>{m.auth_login_title()}</h2>
62
- {{else}}
63
- <h2 class='text-2xl font-bold text-center mb-6'>登录</h2>
64
- {{/if}}
65
-
66
- {#if error}
67
- <Alert variant='error' class='mb-4'>{error}</Alert>
68
- {/if}
69
-
70
- <form onsubmit={handleLogin} class='space-y-4'>
71
- {{#if hasI18n}}
72
- <FormField label={m.auth_label_username()} required>
73
- <Input id='username' type='text' bind:value={username} required />
74
- </FormField>
75
- <FormField label={m.auth_label_password()} required>
76
- <Input id='password' type='password' bind:value={password} required />
77
- </FormField>
78
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
79
- {loading ? m.auth_login_loading() : m.auth_login_submit()}
80
- </Button>
81
- {{else}}
82
- <FormField label='用户名' required>
83
- <Input id='username' type='text' bind:value={username} required />
84
- </FormField>
85
- <FormField label='密码' required>
86
- <Input id='password' type='password' bind:value={password} required />
87
- </FormField>
88
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
89
- {loading ? '登录中...' : '登录'}
90
- </Button>
91
- {{/if}}
92
- </form>
93
-
94
- <div class='text-center mt-4'>
95
- {{#if hasI18n}}
96
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>{m.auth_login_to_register()}</a>
97
- {{else}}
98
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>没有账号?立即注册</a>
99
- {{/if}}
100
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='login' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/admin', {}))} />
@@ -1,122 +1,11 @@
1
- {{!-- Admin 注册页面 --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 注册页面
5
- */
2
+ import { goto } from '$app/navigation'
6
3
  import { resolve } from '$app/paths'
7
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
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
- let username = $state('')
13
- let email = $state('')
14
- let password = $state('')
15
- let confirmPassword = $state('')
16
- let error = $state('')
17
- let loading = $state(false)
18
-
19
- async function handleRegister(event: SubmitEvent) {
20
- event.preventDefault()
21
- error = ''
22
- if (password !== confirmPassword) {
23
- {{#if hasI18n}}
24
- error = m.auth_error_password_mismatch()
25
- {{else}}
26
- error = '密码不一致'
27
- {{/if}}
28
- return
29
- }
30
- loading = true
31
- try {
32
- const res = await fetch('/api/auth/register', {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ username, email, password, confirmPassword }),
36
- })
37
- const data = await res.json()
38
- if (data.success) {
39
- window.location.href = '/auth/login'
40
- }
41
- else {
42
- {{#if hasI18n}}
43
- error = data.error?.message || m.auth_register_failed()
44
- {{else}}
45
- error = data.error?.message || '注册失败'
46
- {{/if}}
47
- }
48
- }
49
- catch {
50
- {{#if hasI18n}}
51
- error = m.auth_error_network()
52
- {{else}}
53
- error = '网络错误'
54
- {{/if}}
55
- }
56
- finally {
57
- loading = false
58
- }
59
- }
8
+ const actions = kit.client.createAuthActions()
60
9
  </script>
61
10
 
62
- <svelte:head>
63
- {{#if hasI18n}}
64
- <title>{m.auth_register_title()}</title>
65
- {{else}}
66
- <title>注册</title>
67
- {{/if}}
68
- </svelte:head>
69
-
70
- {{#if hasI18n}}
71
- <h2 class='text-2xl font-bold text-center mb-6'>{m.auth_register_title()}</h2>
72
- {{else}}
73
- <h2 class='text-2xl font-bold text-center mb-6'>注册</h2>
74
- {{/if}}
75
-
76
- {#if error}
77
- <Alert variant='error' class='mb-4'>{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_email()} required>
86
- <Input id='email' type='email' bind:value={email} required />
87
- </FormField>
88
- <FormField label={m.auth_label_password()} required>
89
- <Input id='password' type='password' bind:value={password} required />
90
- </FormField>
91
- <FormField label={m.auth_label_confirm_password()} required>
92
- <Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
93
- </FormField>
94
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
95
- {loading ? m.auth_register_loading() : m.auth_register_submit()}
96
- </Button>
97
- {{else}}
98
- <FormField label='用户名' required>
99
- <Input id='username' type='text' bind:value={username} required />
100
- </FormField>
101
- <FormField label='邮箱' required>
102
- <Input id='email' type='email' bind:value={email} required />
103
- </FormField>
104
- <FormField label='密码' required>
105
- <Input id='password' type='password' bind:value={password} required />
106
- </FormField>
107
- <FormField label='确认密码' required>
108
- <Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
109
- </FormField>
110
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
111
- {loading ? '注册中...' : '注册'}
112
- </Button>
113
- {{/if}}
114
- </form>
115
-
116
- <div class='text-center mt-4'>
117
- {{#if hasI18n}}
118
- <a href={resolve('/auth/login', {})} class='link link-primary text-sm'>{m.auth_register_to_login()}</a>
119
- {{else}}
120
- <a href={resolve('/auth/login', {})} class='link link-primary text-sm'>已有账号?立即登录</a>
121
- {{/if}}
122
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='register' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/admin', {}))} />
@@ -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', {})} />
@@ -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', {})} />
@@ -1,102 +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
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
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
- let username = $state('')
13
- let password = $state('')
14
- let error = $state('')
15
- let loading = $state(false)
16
-
17
- async function handleLogin(event: SubmitEvent) {
18
- event.preventDefault()
19
- error = ''
20
- loading = true
21
- try {
22
- const res = await fetch('/api/auth/login', {
23
- method: 'POST',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({ identifier: username, password }),
26
- })
27
- const data = await res.json()
28
- if (data.success) {
29
- window.location.href = '/'
30
- }
31
- else {
32
- {{#if hasI18n}}
33
- error = data.error?.message || m.auth_login_failed()
34
- {{else}}
35
- error = data.error?.message || '登录失败'
36
- {{/if}}
37
- }
38
- }
39
- catch {
40
- {{#if hasI18n}}
41
- error = m.auth_error_network()
42
- {{else}}
43
- error = '网络错误'
44
- {{/if}}
45
- }
46
- finally {
47
- loading = false
48
- }
49
- }
8
+ const authActions = kit.client.createAuthActions()
50
9
  </script>
51
10
 
52
- <svelte:head>
53
- {{#if hasI18n}}
54
- <title>{m.auth_login_title()}</title>
55
- {{else}}
56
- <title>登录</title>
57
- {{/if}}
58
- </svelte:head>
59
-
60
- <div class='p-4 pt-12 space-y-4'>
61
- {{#if hasI18n}}
62
- <h1 class='text-2xl font-black tracking-tight text-center'>{m.auth_login_title()}</h1>
63
- {{else}}
64
- <h1 class='text-2xl font-black tracking-tight text-center'>登录</h1>
65
- {{/if}}
66
-
67
- {#if error}
68
- <Alert variant='error'>{error}</Alert>
69
- {/if}
70
-
71
- <form onsubmit={handleLogin} class='space-y-4'>
72
- {{#if hasI18n}}
73
- <FormField label={m.auth_label_username()} required>
74
- <Input id='username' type='text' bind:value={username} required />
75
- </FormField>
76
- <FormField label={m.auth_label_password()} required>
77
- <Input id='password' type='password' bind:value={password} required />
78
- </FormField>
79
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
80
- {loading ? m.auth_login_loading() : m.auth_login_submit()}
81
- </Button>
82
- {{else}}
83
- <FormField label='用户名' required>
84
- <Input id='username' type='text' bind:value={username} required />
85
- </FormField>
86
- <FormField label='密码' required>
87
- <Input id='password' type='password' bind:value={password} required />
88
- </FormField>
89
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
90
- {loading ? '登录中...' : '登录'}
91
- </Button>
92
- {{/if}}
93
- </form>
94
-
95
- <div class='text-center'>
96
- {{#if hasI18n}}
97
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>{m.auth_login_to_register()}</a>
98
- {{else}}
99
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>没有账号?立即注册</a>
100
- {{/if}}
101
- </div>
102
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} actions={authActions} initialMode='login' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/', {}))} />
@@ -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
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
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
- let username = $state('')
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
- <svelte:head>
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', {})} />
@@ -0,0 +1,4 @@
1
+ import { iam } from '@h-ai/iam'
2
+ import { kit } from '@h-ai/kit'
3
+
4
+ export const GET = kit.auth.createApi(iam).publicConfig
@@ -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 LoginInputSchema = z.object({
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
@@ -0,0 +1,4 @@
1
+ import { iam } from '@h-ai/iam'
2
+ import { kit } from '@h-ai/kit'
3
+
4
+ export const POST = kit.auth.createApi(iam).loginWithLdap
@@ -0,0 +1,4 @@
1
+ import { iam } from '@h-ai/iam'
2
+ import { kit } from '@h-ai/kit'
3
+
4
+ export const POST = kit.auth.createApi(iam).loginWithOtp
@@ -0,0 +1,4 @@
1
+ import { iam } from '@h-ai/iam'
2
+ import { kit } from '@h-ai/kit'
3
+
4
+ export const POST = kit.auth.createApi(iam).sendOtp
@@ -0,0 +1,4 @@
1
+ import { iam } from '@h-ai/iam'
2
+ import { kit } from '@h-ai/kit'
3
+
4
+ export const POST = kit.auth.createApi(iam).confirmPasswordReset