@h-ai/iam 0.1.0-alpha5

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/dist/index.js ADDED
@@ -0,0 +1,4762 @@
1
+ import { z } from 'zod';
2
+ import { cache } from '@h-ai/cache';
3
+ import { core, err, ok } from '@h-ai/core';
4
+ import { crypto as crypto$1 } from '@h-ai/crypto';
5
+ import { reldb, BaseReldbCrudRepository } from '@h-ai/reldb';
6
+ import { randomBytes } from 'crypto';
7
+ import { audit } from '@h-ai/audit';
8
+
9
+ // src/iam-config.ts
10
+ var AuthStrategyTypeSchema = z.enum(["password", "otp", "ldap", "apikey"]);
11
+ var PasswordConfigSchema = z.object({
12
+ /** 最小长度(默认 8) */
13
+ minLength: z.number().int().min(1).default(8),
14
+ /** 最大长度(默认 128) */
15
+ maxLength: z.number().int().max(256).default(128),
16
+ /** 需要大写字母 */
17
+ requireUppercase: z.boolean().default(true),
18
+ /** 需要小写字母 */
19
+ requireLowercase: z.boolean().default(true),
20
+ /** 需要数字 */
21
+ requireNumber: z.boolean().default(true),
22
+ /** 需要特殊字符 */
23
+ requireSpecialChar: z.boolean().default(false),
24
+ /** 密码过期天数(0 表示不过期) */
25
+ expirationDays: z.number().int().min(0).default(0)
26
+ });
27
+ var OtpConfigSchema = z.object({
28
+ /** 验证码长度(默认 6) */
29
+ length: z.number().int().min(4).max(8).default(6),
30
+ /** 验证码过期时间(秒,默认 300) */
31
+ expiresIn: z.number().int().min(60).default(300),
32
+ /** 最大重试次数(默认 3) */
33
+ maxAttempts: z.number().int().min(1).default(3),
34
+ /** 发送间隔(秒,默认 60) */
35
+ resendInterval: z.number().int().min(30).default(60)
36
+ });
37
+ var PasswordResetConfigSchema = z.object({
38
+ /** 重置令牌有效期(秒,默认 3600 = 1小时) */
39
+ tokenExpiresIn: z.number().int().min(300).default(3600),
40
+ /** 最大验证尝试次数(默认 3) */
41
+ maxAttempts: z.number().int().min(1).default(3)
42
+ });
43
+ var LdapConfigSchema = z.object({
44
+ /** LDAP 服务器 URL */
45
+ url: z.string().url(),
46
+ /** 绑定 DN */
47
+ bindDn: z.string(),
48
+ /** 绑定密码 */
49
+ bindPassword: z.string(),
50
+ /** 搜索基础 DN */
51
+ searchBase: z.string(),
52
+ /** 搜索过滤器(默认使用 uid) */
53
+ searchFilter: z.string().default("(uid={{username}})"),
54
+ /** 用户名属性 */
55
+ usernameAttribute: z.string().default("uid"),
56
+ /** 邮箱属性 */
57
+ emailAttribute: z.string().default("mail"),
58
+ /** 显示名称属性 */
59
+ displayNameAttribute: z.string().default("cn"),
60
+ /** 启用 TLS */
61
+ useTls: z.boolean().default(false),
62
+ /** 连接超时(毫秒) */
63
+ connectTimeout: z.number().int().min(1e3).default(5e3)
64
+ });
65
+ var ApiKeyConfigSchema = z.object({
66
+ /** 单用户最大 API Key 数量(默认 10) */
67
+ maxKeysPerUser: z.number().int().min(1).default(10),
68
+ /** API Key 默认有效期(天,0 表示永不过期,默认 0) */
69
+ defaultExpirationDays: z.number().int().min(0).default(0),
70
+ /** API Key 前缀(默认 'hai_') */
71
+ prefix: z.string().default("hai_")
72
+ });
73
+ var LoginConfigSchema = z.object({
74
+ /** 是否启用密码登录 */
75
+ password: z.boolean().default(true),
76
+ /** 是否启用 OTP 登录 */
77
+ otp: z.boolean().default(true),
78
+ /** 是否启用 LDAP 登录 */
79
+ ldap: z.boolean().default(true),
80
+ /** 是否启用 API Key 登录 */
81
+ apikey: z.boolean().default(false)
82
+ });
83
+ var RegisterConfigSchema = z.object({
84
+ /** 是否启用注册 */
85
+ enabled: z.boolean().default(true),
86
+ /** 新注册用户是否默认启用 */
87
+ defaultEnabled: z.boolean().default(true)
88
+ });
89
+ var SecurityConfigSchema = z.object({
90
+ /** 最大登录失败次数(默认 5) */
91
+ maxLoginAttempts: z.number().int().min(1).default(5),
92
+ /** 锁定时长(秒,默认 900) */
93
+ lockoutDuration: z.number().int().min(60).default(900)
94
+ });
95
+ var AgreementConfigSchema = z.object({
96
+ /** 用户协议 URL */
97
+ userAgreementUrl: z.url().optional(),
98
+ /** 隐私协议 URL */
99
+ privacyPolicyUrl: z.url().optional(),
100
+ /** 注册时展示协议 */
101
+ showOnRegister: z.boolean().default(true),
102
+ /** 登录时展示协议 */
103
+ showOnLogin: z.boolean().default(false)
104
+ });
105
+ var SessionConfigSchema = z.object({
106
+ /** 会话超时时间(秒,默认 86400 = 24小时) */
107
+ maxAge: z.number().int().min(60).default(86400),
108
+ /** 是否滑动窗口(每次访问刷新过期时间) */
109
+ sliding: z.boolean().default(true),
110
+ /** 单设备登录(踢掉其他设备) */
111
+ singleDevice: z.boolean().default(false),
112
+ /** refreshToken 过期时间(秒,默认 604800 = 7天) */
113
+ refreshTokenMaxAge: z.number().int().min(3600).default(604800)
114
+ });
115
+ var RbacConfigSchema = z.object({
116
+ /** 是否启用 RBAC */
117
+ enabled: z.boolean().default(true),
118
+ /** 超级管理员角色代码 */
119
+ superAdminRole: z.string().default("super_admin"),
120
+ /** 默认用户角色 */
121
+ defaultRole: z.string().default("user")
122
+ });
123
+ var IamConfigSchema = z.object({
124
+ // ─── 认证策略配置 ───
125
+ /** 密码配置 */
126
+ password: PasswordConfigSchema.optional(),
127
+ /** OTP 配置 */
128
+ otp: OtpConfigSchema.optional(),
129
+ /** LDAP 配置 */
130
+ ldap: LdapConfigSchema.optional(),
131
+ /** API Key 配置 */
132
+ apikey: ApiKeyConfigSchema.optional(),
133
+ /** 密码重置配置 */
134
+ passwordReset: PasswordResetConfigSchema.optional(),
135
+ /** 登录启用配置 */
136
+ login: LoginConfigSchema.default({
137
+ password: true,
138
+ otp: true,
139
+ ldap: true,
140
+ apikey: false
141
+ }),
142
+ /** 注册配置 */
143
+ register: RegisterConfigSchema.default({
144
+ enabled: true,
145
+ defaultEnabled: true
146
+ }),
147
+ /** 协议展示配置 */
148
+ agreements: AgreementConfigSchema.default({
149
+ showOnRegister: true,
150
+ showOnLogin: false
151
+ }),
152
+ /** 安全策略配置 */
153
+ security: SecurityConfigSchema.default({
154
+ maxLoginAttempts: 5,
155
+ lockoutDuration: 900
156
+ }),
157
+ // ─── 会话配置 ───
158
+ /** 会话配置 */
159
+ session: SessionConfigSchema.optional(),
160
+ // ─── 授权配置 ───
161
+ /** RBAC 配置 */
162
+ rbac: RbacConfigSchema.optional(),
163
+ // ─── 运行时选项 ───
164
+ /** 是否初始化默认角色和权限(默认 true) */
165
+ seedDefaultData: z.boolean().default(true)
166
+ });
167
+
168
+ // messages/en-US.json
169
+ var en_US_default = {
170
+ $schema: "https://inlang.com/schema/inlang-message-format",
171
+ iam_passwordMinLength: "Password must be at least {minLength} characters",
172
+ iam_passwordMaxLength: "Password cannot exceed {maxLength} characters",
173
+ iam_passwordNeedUppercase: "Password must contain uppercase letters",
174
+ iam_passwordNeedLowercase: "Password must contain lowercase letters",
175
+ iam_passwordNeedNumber: "Password must contain numbers",
176
+ iam_passwordNeedSpecialChar: "Password must contain special characters",
177
+ iam_credentialTypeMismatch: "Credential type mismatch",
178
+ iam_userNotExist: "User does not exist",
179
+ iam_accountDisabled: "Account is disabled",
180
+ iam_accountLocked: "Account is locked, please try again later",
181
+ iam_accountNoPassword: "Account has no password set",
182
+ iam_passwordWrong: "Incorrect password",
183
+ iam_passwordExpired: "Password has expired, please change your password",
184
+ iam_otpNotExistOrExpired: "Verification code does not exist or has expired",
185
+ iam_otpInvalid: "Verification code is invalid, please request a new one",
186
+ iam_otpWrong: "Incorrect verification code",
187
+ iam_otpResendTooFast: "Please wait {seconds} seconds before requesting another code",
188
+ iam_identifierTypeNotSupported: "Unsupported identifier type or delivery method not configured",
189
+ iam_loginDisabled: "Login via {type} is disabled",
190
+ iam_registerDisabled: "Registration is disabled",
191
+ iam_otpStrategyRequired: "OTP authentication requires OTP strategy configuration",
192
+ iam_otpStrategyRequiredForSend: "OTP requires OTP strategy configuration",
193
+ iam_userNotExistNoAutoRegister: "User does not exist and auto-registration is not enabled",
194
+ iam_refreshTokenFailed: "Failed to refresh token",
195
+ iam_refreshTokenExpired: "Refresh token has expired",
196
+ iam_refreshTokenRequestFailed: "Refresh token request failed",
197
+ iam_ldapConnectionFailed: "Unable to connect to LDAP server",
198
+ iam_ldapAdminBindFailed: "LDAP admin bind failed",
199
+ iam_ldapSearchFailed: "LDAP search failed",
200
+ iam_ldapStrategyRequired: "LDAP authentication requires LDAP strategy configuration",
201
+ iam_tokenExpired: "Token has expired",
202
+ iam_tokenInvalid: "Invalid token",
203
+ iam_createSessionFailed: "Failed to create session",
204
+ iam_sessionExpired: "Session has expired",
205
+ iam_invalidRefreshToken: "Invalid refresh token",
206
+ iam_sessionNotExist: "Session does not exist",
207
+ iam_usernameAlreadyExist: "Username already exists",
208
+ iam_emailAlreadyUsed: "Email is already in use",
209
+ iam_originalPasswordWrong: "Original password is incorrect",
210
+ iam_featureNotImplemented: "Feature not implemented",
211
+ iam_userAlreadyExist: "User already exists",
212
+ iam_parseSessionDataFailed: "Failed to parse session data",
213
+ iam_roleAlreadyExist: "Role already exists",
214
+ iam_roleNotExist: "Role does not exist",
215
+ iam_cannotDeleteSystemRole: "Cannot delete system role",
216
+ iam_permissionAlreadyExist: "Permission already exists",
217
+ iam_permissionNotExist: "Permission does not exist",
218
+ iam_notInitialized: "Not initialized, please call iam.init() first",
219
+ iam_initFailed: "Initialization failed",
220
+ iam_depsNotInitialized: "Dependency {dep} is not initialized, please call {dep}.init() before iam.init()",
221
+ iam_initInProgress: "IAM initialization is already in progress",
222
+ iam_cacheRequired: "Cache service is required for session management",
223
+ iam_initComponentFailed: "Failed to initialize component",
224
+ iam_initSeedDataFailed: "Failed to initialize seed data",
225
+ iam_createUserTableFailed: "Failed to create user table: {message}",
226
+ iam_queryUserFailed: "Failed to query user: {message}",
227
+ iam_createUserFailed: "Failed to create user: {message}",
228
+ iam_queryUserListFailed: "Failed to query user list: {message}",
229
+ iam_updateUserFailed: "Failed to update user: {message}",
230
+ iam_deleteUserFailed: "Failed to delete user: {message}",
231
+ iam_createSessionTableFailed: "Failed to create session table: {message}",
232
+ iam_createSessionIndexFailed: "Failed to create session index: {message}",
233
+ iam_createSessionRecordFailed: "Failed to create session record: {message}",
234
+ iam_querySessionFailed: "Failed to query session: {message}",
235
+ iam_updateSessionFailed: "Failed to update session: {message}",
236
+ iam_deleteSessionFailed: "Failed to delete session: {message}",
237
+ iam_cleanupSessionFailed: "Failed to cleanup session: {message}",
238
+ iam_createSessionStoreTableFailed: "Failed to create session store table: {message}",
239
+ iam_createTokenMappingTableFailed: "Failed to create token mapping table: {message}",
240
+ iam_createUserSessionTableFailed: "Failed to create user session table: {message}",
241
+ iam_createSessionStoreIndexFailed: "Failed to create session index: {message}",
242
+ iam_saveSessionFailed: "Failed to save session: {message}",
243
+ iam_queryTokenMappingFailed: "Failed to query token mapping: {message}",
244
+ iam_saveTokenMappingFailed: "Failed to save token mapping: {message}",
245
+ iam_deleteTokenMappingFailed: "Failed to delete token mapping: {message}",
246
+ iam_deleteUserSessionMappingFailed: "Failed to delete user session mapping: {message}",
247
+ iam_queryUserSessionFailed: "Failed to query user session: {message}",
248
+ iam_addUserSessionMappingFailed: "Failed to add user session mapping: {message}",
249
+ iam_createRoleTableFailed: "Failed to create role table: {message}",
250
+ iam_queryRoleFailed: "Failed to query role: {message}",
251
+ iam_queryRoleListFailed: "Failed to query role list: {message}",
252
+ iam_createRoleFailed: "Failed to create role: {message}",
253
+ iam_updateRoleFailed: "Failed to update role: {message}",
254
+ iam_deleteRoleFailed: "Failed to delete role: {message}",
255
+ iam_createRolePermissionTableFailed: "Failed to create role-permission table: {message}",
256
+ iam_createRolePermissionIndexFailed: "Failed to create role-permission index: {message}",
257
+ iam_assignPermissionFailed: "Failed to assign permission: {message}",
258
+ iam_removePermissionFailed: "Failed to remove permission: {message}",
259
+ iam_createUserRoleTableFailed: "Failed to create user-role table: {message}",
260
+ iam_createUserRoleIndexFailed: "Failed to create user-role index: {message}",
261
+ iam_assignRoleFailed: "Failed to assign role: {message}",
262
+ iam_removeRoleFailed: "Failed to remove role: {message}",
263
+ iam_syncRolesFailed: "Failed to sync roles: {message}",
264
+ iam_createPermissionTableFailed: "Failed to create permission table: {message}",
265
+ iam_createPermissionFailed: "Failed to create permission: {message}",
266
+ iam_queryPermissionFailed: "Failed to query permission: {message}",
267
+ iam_queryPermissionListFailed: "Failed to query permission list: {message}",
268
+ iam_deletePermissionFailed: "Failed to delete permission: {message}",
269
+ iam_queryPermissionCacheFailed: "Failed to query permission cache: {message}",
270
+ iam_setPermissionCacheFailed: "Failed to set permission cache: {message}",
271
+ iam_clearPermissionCacheFailed: "Failed to clear permission cache: {message}",
272
+ iam_queryOtpCacheFailed: "Failed to query OTP cache: {message}",
273
+ iam_saveOtpCacheFailed: "Failed to save OTP cache: {message}",
274
+ iam_updateOtpCacheFailed: "Failed to update OTP cache: {message}",
275
+ iam_deleteOtpCacheFailed: "Failed to delete OTP cache: {message}",
276
+ iam_querySessionMappingCacheFailed: "Failed to query session mapping cache: {message}",
277
+ iam_saveSessionMappingCacheFailed: "Failed to save session mapping cache: {message}",
278
+ iam_deleteSessionMappingCacheFailed: "Failed to delete session mapping cache: {message}",
279
+ iam_queryTokenMappingCacheFailed: "Failed to query token mapping cache: {message}",
280
+ iam_saveTokenMappingCacheFailed: "Failed to save token mapping cache: {message}",
281
+ iam_deleteTokenMappingCacheFailed: "Failed to delete token mapping cache: {message}",
282
+ iam_queryUserSessionCacheFailed: "Failed to query user session cache: {message}",
283
+ iam_saveUserSessionCacheFailed: "Failed to save user session cache: {message}",
284
+ iam_deleteUserSessionCacheFailed: "Failed to delete user session cache: {message}",
285
+ iam_createOtpTableFailed: "Failed to create OTP table: {message}",
286
+ iam_saveOtpFailed: "Failed to save OTP: {message}",
287
+ iam_queryOtpFailed: "Failed to query OTP: {message}",
288
+ iam_updateOtpAttemptsFailed: "Failed to update OTP attempts: {message}",
289
+ iam_deleteOtpFailed: "Failed to delete OTP: {message}",
290
+ iam_otpSendFailed: "Failed to send OTP: {message}",
291
+ iam_resetTokenInvalid: "Invalid or expired password reset token",
292
+ iam_resetTokenMaxAttempts: "Too many verification attempts, please request a new reset token",
293
+ iam_saveResetTokenFailed: "Failed to save password reset token: {message}",
294
+ iam_hashResetTokenFailed: "Failed to hash reset token: {message}",
295
+ iam_queryResetTokenFailed: "Failed to query password reset token: {message}",
296
+ iam_passwordResetNotConfigured: "Password reset is not configured",
297
+ iam_clientFetchNotAvailable: "Fetch is not available in the current environment",
298
+ iam_clientRequestFailed: "Request failed: {status}",
299
+ iam_clientNetworkError: "Network request failed",
300
+ iam_clientNetworkErrorWithDetail: "Network request failed: {message}",
301
+ iam_seedRoleAdminName: "Administrator",
302
+ iam_seedRoleAdminDesc: "System administrator with all permissions",
303
+ iam_seedRoleUserName: "User",
304
+ iam_seedRoleUserDesc: "Standard user",
305
+ iam_seedRoleGuestName: "Guest",
306
+ iam_seedRoleGuestDesc: "Guest with read-only access",
307
+ iam_seedPermUserRead: "View Users",
308
+ iam_seedPermUserCreate: "Create Users",
309
+ iam_seedPermUserUpdate: "Update Users",
310
+ iam_seedPermUserDelete: "Delete Users",
311
+ iam_seedPermUserList: "List Users API",
312
+ iam_seedPermUserApiCreate: "Create User API",
313
+ iam_seedPermUserApiUpdate: "Update User API",
314
+ iam_seedPermUserApiDelete: "Delete User API",
315
+ iam_seedPermRoleRead: "View Roles",
316
+ iam_seedPermRoleCreate: "Create Roles",
317
+ iam_seedPermRoleUpdate: "Update Roles",
318
+ iam_seedPermRoleDelete: "Delete Roles",
319
+ iam_seedPermRoleList: "List Roles API",
320
+ iam_seedPermRoleApiCreate: "Create Role API",
321
+ iam_seedPermRoleApiUpdate: "Update Role API",
322
+ iam_seedPermRoleApiDelete: "Delete Role API",
323
+ iam_seedPermPermRead: "View Permissions",
324
+ iam_seedPermPermManage: "Manage Permissions",
325
+ iam_seedPermPermCreate: "Create Permissions",
326
+ iam_seedPermPermDelete: "Delete Permissions",
327
+ iam_seedPermPermList: "List Permissions API",
328
+ iam_seedPermPermApiCreate: "Create Permission API",
329
+ iam_seedPermPermApiDelete: "Delete Permission API",
330
+ iam_seedPermSystemSettings: "System Settings",
331
+ iam_seedPermSystemLogs: "View Logs",
332
+ iam_seedPermSystemModules: "Module Management",
333
+ iam_seedPermDashboardView: "Dashboard",
334
+ iam_seedPermProfileRead: "Profile",
335
+ iam_seedPermAuditRead: "Audit Logs",
336
+ iam_configError: "IAM config validation failed: {error}",
337
+ iam_apikeyInvalid: "Invalid API Key",
338
+ iam_apikeyExpired: "API Key has expired",
339
+ iam_apikeyDisabled: "API Key is disabled",
340
+ iam_apikeyMaxKeysReached: "Maximum number of API Keys ({max}) reached",
341
+ iam_apikeyCreateFailed: "Failed to create API Key: {message}",
342
+ iam_apikeyUpdateFailed: "Failed to update API Key: {message}",
343
+ iam_apikeyDeleteFailed: "Failed to delete API Key: {message}",
344
+ iam_apikeyQueryFailed: "Failed to query API Key: {message}",
345
+ iam_apikeyStrategyRequired: "API Key authentication requires API Key strategy configuration"
346
+ };
347
+
348
+ // messages/zh-CN.json
349
+ var zh_CN_default = {
350
+ $schema: "https://inlang.com/schema/inlang-message-format",
351
+ iam_passwordMinLength: "\u5BC6\u7801\u957F\u5EA6\u81F3\u5C11\u4E3A {minLength} \u4E2A\u5B57\u7B26",
352
+ iam_passwordMaxLength: "\u5BC6\u7801\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 {maxLength} \u4E2A\u5B57\u7B26",
353
+ iam_passwordNeedUppercase: "\u5BC6\u7801\u5FC5\u987B\u5305\u542B\u5927\u5199\u5B57\u6BCD",
354
+ iam_passwordNeedLowercase: "\u5BC6\u7801\u5FC5\u987B\u5305\u542B\u5C0F\u5199\u5B57\u6BCD",
355
+ iam_passwordNeedNumber: "\u5BC6\u7801\u5FC5\u987B\u5305\u542B\u6570\u5B57",
356
+ iam_passwordNeedSpecialChar: "\u5BC6\u7801\u5FC5\u987B\u5305\u542B\u7279\u6B8A\u5B57\u7B26",
357
+ iam_credentialTypeMismatch: "\u51ED\u8BC1\u7C7B\u578B\u4E0D\u5339\u914D",
358
+ iam_userNotExist: "\u7528\u6237\u4E0D\u5B58\u5728",
359
+ iam_accountDisabled: "\u8D26\u6237\u5DF2\u7981\u7528",
360
+ iam_accountLocked: "\u8D26\u6237\u5DF2\u9501\u5B9A\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
361
+ iam_accountNoPassword: "\u8D26\u6237\u672A\u8BBE\u7F6E\u5BC6\u7801",
362
+ iam_passwordWrong: "\u5BC6\u7801\u9519\u8BEF",
363
+ iam_passwordExpired: "\u5BC6\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u4FEE\u6539\u5BC6\u7801",
364
+ iam_otpNotExistOrExpired: "\u9A8C\u8BC1\u7801\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F",
365
+ iam_otpInvalid: "\u9A8C\u8BC1\u7801\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6",
366
+ iam_otpWrong: "\u9A8C\u8BC1\u7801\u9519\u8BEF",
367
+ iam_otpResendTooFast: "\u8BF7\u7B49\u5F85 {seconds} \u79D2\u540E\u518D\u83B7\u53D6\u9A8C\u8BC1\u7801",
368
+ iam_identifierTypeNotSupported: "\u4E0D\u652F\u6301\u7684\u6807\u8BC6\u7B26\u7C7B\u578B\u6216\u53D1\u9001\u65B9\u5F0F\u672A\u914D\u7F6E",
369
+ iam_loginDisabled: "{type} \u767B\u5F55\u5DF2\u7981\u7528",
370
+ iam_registerDisabled: "\u6CE8\u518C\u529F\u80FD\u5DF2\u7981\u7528",
371
+ iam_otpStrategyRequired: "OTP \u8BA4\u8BC1\u9700\u8981\u914D\u7F6E OTP \u7B56\u7565",
372
+ iam_otpStrategyRequiredForSend: "OTP \u9700\u8981\u914D\u7F6E OTP \u7B56\u7565",
373
+ iam_userNotExistNoAutoRegister: "\u7528\u6237\u4E0D\u5B58\u5728\u4E14\u672A\u542F\u7528\u81EA\u52A8\u6CE8\u518C",
374
+ iam_refreshTokenFailed: "\u5237\u65B0\u4EE4\u724C\u5931\u8D25",
375
+ iam_refreshTokenExpired: "\u5237\u65B0\u4EE4\u724C\u5DF2\u8FC7\u671F",
376
+ iam_refreshTokenRequestFailed: "\u5237\u65B0\u4EE4\u724C\u8BF7\u6C42\u5931\u8D25",
377
+ iam_ldapConnectionFailed: "\u65E0\u6CD5\u8FDE\u63A5\u5230 LDAP \u670D\u52A1\u5668",
378
+ iam_ldapAdminBindFailed: "LDAP \u7BA1\u7406\u5458\u7ED1\u5B9A\u5931\u8D25",
379
+ iam_ldapSearchFailed: "LDAP \u641C\u7D22\u5931\u8D25",
380
+ iam_ldapStrategyRequired: "LDAP \u8BA4\u8BC1\u9700\u8981\u914D\u7F6E LDAP \u7B56\u7565",
381
+ iam_tokenExpired: "\u4EE4\u724C\u5DF2\u8FC7\u671F",
382
+ iam_tokenInvalid: "\u4EE4\u724C\u65E0\u6548",
383
+ iam_createSessionFailed: "\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25",
384
+ iam_sessionExpired: "\u4F1A\u8BDD\u5DF2\u5931\u6548",
385
+ iam_invalidRefreshToken: "\u65E0\u6548\u7684\u5237\u65B0\u4EE4\u724C",
386
+ iam_sessionNotExist: "\u4F1A\u8BDD\u4E0D\u5B58\u5728",
387
+ iam_usernameAlreadyExist: "\u7528\u6237\u540D\u5DF2\u5B58\u5728",
388
+ iam_emailAlreadyUsed: "\u90AE\u7BB1\u5DF2\u88AB\u4F7F\u7528",
389
+ iam_originalPasswordWrong: "\u539F\u5BC6\u7801\u9519\u8BEF",
390
+ iam_featureNotImplemented: "\u529F\u80FD\u6682\u672A\u5B9E\u73B0",
391
+ iam_userAlreadyExist: "\u7528\u6237\u5DF2\u5B58\u5728",
392
+ iam_parseSessionDataFailed: "\u89E3\u6790\u4F1A\u8BDD\u6570\u636E\u5931\u8D25",
393
+ iam_roleAlreadyExist: "\u89D2\u8272\u5DF2\u5B58\u5728",
394
+ iam_roleNotExist: "\u89D2\u8272\u4E0D\u5B58\u5728",
395
+ iam_cannotDeleteSystemRole: "\u4E0D\u80FD\u5220\u9664\u7CFB\u7EDF\u89D2\u8272",
396
+ iam_permissionAlreadyExist: "\u6743\u9650\u5DF2\u5B58\u5728",
397
+ iam_permissionNotExist: "\u6743\u9650\u4E0D\u5B58\u5728",
398
+ iam_notInitialized: "\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 iam.init()",
399
+ iam_initFailed: "\u521D\u59CB\u5316\u5931\u8D25",
400
+ iam_depsNotInitialized: "\u4F9D\u8D56 {dep} \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5728 iam.init() \u4E4B\u524D\u8C03\u7528 {dep}.init()",
401
+ iam_initInProgress: "IAM \u521D\u59CB\u5316\u6B63\u5728\u8FDB\u884C\u4E2D",
402
+ iam_cacheRequired: "\u4F1A\u8BDD\u7BA1\u7406\u9700\u8981\u63D0\u4F9B\u7F13\u5B58\u670D\u52A1",
403
+ iam_initComponentFailed: "\u521D\u59CB\u5316\u7EC4\u4EF6\u5931\u8D25",
404
+ iam_initSeedDataFailed: "\u521D\u59CB\u5316\u79CD\u5B50\u6570\u636E\u5931\u8D25",
405
+ iam_createUserTableFailed: "\u521B\u5EFA\u7528\u6237\u8868\u5931\u8D25: {message}",
406
+ iam_queryUserFailed: "\u67E5\u8BE2\u7528\u6237\u5931\u8D25: {message}",
407
+ iam_createUserFailed: "\u521B\u5EFA\u7528\u6237\u5931\u8D25: {message}",
408
+ iam_queryUserListFailed: "\u67E5\u8BE2\u7528\u6237\u5217\u8868\u5931\u8D25: {message}",
409
+ iam_updateUserFailed: "\u66F4\u65B0\u7528\u6237\u5931\u8D25: {message}",
410
+ iam_deleteUserFailed: "\u5220\u9664\u7528\u6237\u5931\u8D25: {message}",
411
+ iam_createSessionTableFailed: "\u521B\u5EFA\u4F1A\u8BDD\u8868\u5931\u8D25: {message}",
412
+ iam_createSessionIndexFailed: "\u521B\u5EFA\u4F1A\u8BDD\u7D22\u5F15\u5931\u8D25: {message}",
413
+ iam_createSessionRecordFailed: "\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: {message}",
414
+ iam_querySessionFailed: "\u67E5\u8BE2\u4F1A\u8BDD\u5931\u8D25: {message}",
415
+ iam_updateSessionFailed: "\u66F4\u65B0\u4F1A\u8BDD\u5931\u8D25: {message}",
416
+ iam_deleteSessionFailed: "\u5220\u9664\u4F1A\u8BDD\u5931\u8D25: {message}",
417
+ iam_cleanupSessionFailed: "\u6E05\u7406\u4F1A\u8BDD\u5931\u8D25: {message}",
418
+ iam_createSessionStoreTableFailed: "\u521B\u5EFA\u4F1A\u8BDD\u5B58\u50A8\u8868\u5931\u8D25: {message}",
419
+ iam_createTokenMappingTableFailed: "\u521B\u5EFA\u4EE4\u724C\u6620\u5C04\u8868\u5931\u8D25: {message}",
420
+ iam_createUserSessionTableFailed: "\u521B\u5EFA\u7528\u6237\u4F1A\u8BDD\u8868\u5931\u8D25: {message}",
421
+ iam_createSessionStoreIndexFailed: "\u521B\u5EFA\u4F1A\u8BDD\u7D22\u5F15\u5931\u8D25: {message}",
422
+ iam_saveSessionFailed: "\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25: {message}",
423
+ iam_queryTokenMappingFailed: "\u67E5\u8BE2\u4EE4\u724C\u6620\u5C04\u5931\u8D25: {message}",
424
+ iam_saveTokenMappingFailed: "\u4FDD\u5B58\u4EE4\u724C\u6620\u5C04\u5931\u8D25: {message}",
425
+ iam_deleteTokenMappingFailed: "\u5220\u9664\u4EE4\u724C\u6620\u5C04\u5931\u8D25: {message}",
426
+ iam_deleteUserSessionMappingFailed: "\u5220\u9664\u7528\u6237\u4F1A\u8BDD\u6620\u5C04\u5931\u8D25: {message}",
427
+ iam_queryUserSessionFailed: "\u67E5\u8BE2\u7528\u6237\u4F1A\u8BDD\u5931\u8D25: {message}",
428
+ iam_addUserSessionMappingFailed: "\u6DFB\u52A0\u7528\u6237\u4F1A\u8BDD\u6620\u5C04\u5931\u8D25: {message}",
429
+ iam_createRoleTableFailed: "\u521B\u5EFA\u89D2\u8272\u8868\u5931\u8D25: {message}",
430
+ iam_queryRoleFailed: "\u67E5\u8BE2\u89D2\u8272\u5931\u8D25: {message}",
431
+ iam_queryRoleListFailed: "\u67E5\u8BE2\u89D2\u8272\u5217\u8868\u5931\u8D25: {message}",
432
+ iam_createRoleFailed: "\u521B\u5EFA\u89D2\u8272\u5931\u8D25: {message}",
433
+ iam_updateRoleFailed: "\u66F4\u65B0\u89D2\u8272\u5931\u8D25: {message}",
434
+ iam_deleteRoleFailed: "\u5220\u9664\u89D2\u8272\u5931\u8D25: {message}",
435
+ iam_createRolePermissionTableFailed: "\u521B\u5EFA\u89D2\u8272-\u6743\u9650\u5173\u8054\u8868\u5931\u8D25: {message}",
436
+ iam_createRolePermissionIndexFailed: "\u521B\u5EFA\u89D2\u8272-\u6743\u9650\u7D22\u5F15\u5931\u8D25: {message}",
437
+ iam_assignPermissionFailed: "\u5206\u914D\u6743\u9650\u5931\u8D25: {message}",
438
+ iam_removePermissionFailed: "\u79FB\u9664\u6743\u9650\u5931\u8D25: {message}",
439
+ iam_createUserRoleTableFailed: "\u521B\u5EFA\u7528\u6237-\u89D2\u8272\u5173\u8054\u8868\u5931\u8D25: {message}",
440
+ iam_createUserRoleIndexFailed: "\u521B\u5EFA\u7528\u6237-\u89D2\u8272\u7D22\u5F15\u5931\u8D25: {message}",
441
+ iam_assignRoleFailed: "\u5206\u914D\u89D2\u8272\u5931\u8D25: {message}",
442
+ iam_removeRoleFailed: "\u79FB\u9664\u89D2\u8272\u5931\u8D25: {message}",
443
+ iam_syncRolesFailed: "\u540C\u6B65\u89D2\u8272\u5931\u8D25: {message}",
444
+ iam_createPermissionTableFailed: "\u521B\u5EFA\u6743\u9650\u8868\u5931\u8D25: {message}",
445
+ iam_createPermissionFailed: "\u521B\u5EFA\u6743\u9650\u5931\u8D25: {message}",
446
+ iam_queryPermissionFailed: "\u67E5\u8BE2\u6743\u9650\u5931\u8D25: {message}",
447
+ iam_queryPermissionListFailed: "\u67E5\u8BE2\u6743\u9650\u5217\u8868\u5931\u8D25: {message}",
448
+ iam_deletePermissionFailed: "\u5220\u9664\u6743\u9650\u5931\u8D25: {message}",
449
+ iam_queryPermissionCacheFailed: "\u67E5\u8BE2\u6743\u9650\u7F13\u5B58\u5931\u8D25: {message}",
450
+ iam_setPermissionCacheFailed: "\u8BBE\u7F6E\u6743\u9650\u7F13\u5B58\u5931\u8D25: {message}",
451
+ iam_clearPermissionCacheFailed: "\u6E05\u9664\u6743\u9650\u7F13\u5B58\u5931\u8D25: {message}",
452
+ iam_queryOtpCacheFailed: "\u67E5\u8BE2 OTP \u7F13\u5B58\u5931\u8D25: {message}",
453
+ iam_saveOtpCacheFailed: "\u4FDD\u5B58 OTP \u7F13\u5B58\u5931\u8D25: {message}",
454
+ iam_updateOtpCacheFailed: "\u66F4\u65B0 OTP \u7F13\u5B58\u5931\u8D25: {message}",
455
+ iam_deleteOtpCacheFailed: "\u5220\u9664 OTP \u7F13\u5B58\u5931\u8D25: {message}",
456
+ iam_querySessionMappingCacheFailed: "\u67E5\u8BE2\u4F1A\u8BDD\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
457
+ iam_saveSessionMappingCacheFailed: "\u4FDD\u5B58\u4F1A\u8BDD\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
458
+ iam_deleteSessionMappingCacheFailed: "\u5220\u9664\u4F1A\u8BDD\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
459
+ iam_queryTokenMappingCacheFailed: "\u67E5\u8BE2\u4EE4\u724C\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
460
+ iam_saveTokenMappingCacheFailed: "\u4FDD\u5B58\u4EE4\u724C\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
461
+ iam_deleteTokenMappingCacheFailed: "\u5220\u9664\u4EE4\u724C\u6620\u5C04\u7F13\u5B58\u5931\u8D25: {message}",
462
+ iam_queryUserSessionCacheFailed: "\u67E5\u8BE2\u7528\u6237\u4F1A\u8BDD\u7F13\u5B58\u5931\u8D25: {message}",
463
+ iam_saveUserSessionCacheFailed: "\u4FDD\u5B58\u7528\u6237\u4F1A\u8BDD\u7F13\u5B58\u5931\u8D25: {message}",
464
+ iam_deleteUserSessionCacheFailed: "\u5220\u9664\u7528\u6237\u4F1A\u8BDD\u7F13\u5B58\u5931\u8D25: {message}",
465
+ iam_createOtpTableFailed: "\u521B\u5EFA OTP \u8868\u5931\u8D25: {message}",
466
+ iam_saveOtpFailed: "\u4FDD\u5B58 OTP \u5931\u8D25: {message}",
467
+ iam_queryOtpFailed: "\u67E5\u8BE2 OTP \u5931\u8D25: {message}",
468
+ iam_updateOtpAttemptsFailed: "\u66F4\u65B0 OTP \u5C1D\u8BD5\u6B21\u6570\u5931\u8D25: {message}",
469
+ iam_deleteOtpFailed: "\u5220\u9664 OTP \u5931\u8D25: {message}",
470
+ iam_otpSendFailed: "\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25: {message}",
471
+ iam_resetTokenInvalid: "\u5BC6\u7801\u91CD\u7F6E\u4EE4\u724C\u65E0\u6548\u6216\u5DF2\u8FC7\u671F",
472
+ iam_resetTokenMaxAttempts: "\u9A8C\u8BC1\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u91CD\u65B0\u7533\u8BF7\u91CD\u7F6E\u4EE4\u724C",
473
+ iam_saveResetTokenFailed: "\u4FDD\u5B58\u5BC6\u7801\u91CD\u7F6E\u4EE4\u724C\u5931\u8D25: {message}",
474
+ iam_hashResetTokenFailed: "\u5BC6\u7801\u91CD\u7F6E\u4EE4\u724C\u54C8\u5E0C\u5931\u8D25: {message}",
475
+ iam_queryResetTokenFailed: "\u67E5\u8BE2\u5BC6\u7801\u91CD\u7F6E\u4EE4\u724C\u5931\u8D25: {message}",
476
+ iam_passwordResetNotConfigured: "\u5BC6\u7801\u91CD\u7F6E\u529F\u80FD\u672A\u914D\u7F6E",
477
+ iam_clientFetchNotAvailable: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Fetch",
478
+ iam_clientRequestFailed: "\u8BF7\u6C42\u5931\u8D25: {status}",
479
+ iam_clientNetworkError: "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25",
480
+ iam_clientNetworkErrorWithDetail: "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25: {message}",
481
+ iam_seedRoleAdminName: "\u7BA1\u7406\u5458",
482
+ iam_seedRoleAdminDesc: "\u7CFB\u7EDF\u7BA1\u7406\u5458\uFF0C\u62E5\u6709\u6240\u6709\u6743\u9650",
483
+ iam_seedRoleUserName: "\u666E\u901A\u7528\u6237",
484
+ iam_seedRoleUserDesc: "\u666E\u901A\u7528\u6237",
485
+ iam_seedRoleGuestName: "\u8BBF\u5BA2",
486
+ iam_seedRoleGuestDesc: "\u8BBF\u5BA2\uFF0C\u53EA\u8BFB\u6743\u9650",
487
+ iam_seedPermUserRead: "\u67E5\u770B\u7528\u6237",
488
+ iam_seedPermUserCreate: "\u521B\u5EFA\u7528\u6237",
489
+ iam_seedPermUserUpdate: "\u66F4\u65B0\u7528\u6237",
490
+ iam_seedPermUserDelete: "\u5220\u9664\u7528\u6237",
491
+ iam_seedPermUserList: "\u7528\u6237\u5217\u8868 API",
492
+ iam_seedPermUserApiCreate: "\u521B\u5EFA\u7528\u6237 API",
493
+ iam_seedPermUserApiUpdate: "\u66F4\u65B0\u7528\u6237 API",
494
+ iam_seedPermUserApiDelete: "\u5220\u9664\u7528\u6237 API",
495
+ iam_seedPermRoleRead: "\u67E5\u770B\u89D2\u8272",
496
+ iam_seedPermRoleCreate: "\u521B\u5EFA\u89D2\u8272",
497
+ iam_seedPermRoleUpdate: "\u66F4\u65B0\u89D2\u8272",
498
+ iam_seedPermRoleDelete: "\u5220\u9664\u89D2\u8272",
499
+ iam_seedPermRoleList: "\u89D2\u8272\u5217\u8868 API",
500
+ iam_seedPermRoleApiCreate: "\u521B\u5EFA\u89D2\u8272 API",
501
+ iam_seedPermRoleApiUpdate: "\u66F4\u65B0\u89D2\u8272 API",
502
+ iam_seedPermRoleApiDelete: "\u5220\u9664\u89D2\u8272 API",
503
+ iam_seedPermPermRead: "\u67E5\u770B\u6743\u9650",
504
+ iam_seedPermPermManage: "\u7BA1\u7406\u6743\u9650",
505
+ iam_seedPermPermCreate: "\u521B\u5EFA\u6743\u9650",
506
+ iam_seedPermPermDelete: "\u5220\u9664\u6743\u9650",
507
+ iam_seedPermPermList: "\u6743\u9650\u5217\u8868 API",
508
+ iam_seedPermPermApiCreate: "\u521B\u5EFA\u6743\u9650 API",
509
+ iam_seedPermPermApiDelete: "\u5220\u9664\u6743\u9650 API",
510
+ iam_seedPermSystemSettings: "\u7CFB\u7EDF\u8BBE\u7F6E",
511
+ iam_seedPermSystemLogs: "\u67E5\u770B\u65E5\u5FD7",
512
+ iam_seedPermSystemModules: "\u6A21\u5757\u7BA1\u7406",
513
+ iam_seedPermDashboardView: "\u4EEA\u8868\u76D8",
514
+ iam_seedPermProfileRead: "\u4E2A\u4EBA\u4E2D\u5FC3",
515
+ iam_seedPermAuditRead: "\u5BA1\u8BA1\u65E5\u5FD7",
516
+ iam_configError: "IAM \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
517
+ iam_apikeyInvalid: "API Key \u65E0\u6548",
518
+ iam_apikeyExpired: "API Key \u5DF2\u8FC7\u671F",
519
+ iam_apikeyDisabled: "API Key \u5DF2\u7981\u7528",
520
+ iam_apikeyMaxKeysReached: "API Key \u6570\u91CF\u5DF2\u8FBE\u4E0A\u9650\uFF08{max}\uFF09",
521
+ iam_apikeyCreateFailed: "\u521B\u5EFA API Key \u5931\u8D25\uFF1A{message}",
522
+ iam_apikeyUpdateFailed: "\u66F4\u65B0 API Key \u5931\u8D25\uFF1A{message}",
523
+ iam_apikeyDeleteFailed: "\u5220\u9664 API Key \u5931\u8D25\uFF1A{message}",
524
+ iam_apikeyQueryFailed: "\u67E5\u8BE2 API Key \u5931\u8D25\uFF1A{message}",
525
+ iam_apikeyStrategyRequired: "API Key \u8BA4\u8BC1\u9700\u8981 API Key \u7B56\u7565\u914D\u7F6E"
526
+ };
527
+
528
+ // src/iam-i18n.ts
529
+ var iamM = core.i18n.createMessageGetter({
530
+ "zh-CN": zh_CN_default,
531
+ "en-US": en_US_default
532
+ });
533
+ var IamErrorInfo = {
534
+ AUTH_FAILED: "001:401",
535
+ INVALID_CREDENTIALS: "002:401",
536
+ USER_NOT_FOUND: "003:404",
537
+ USER_DISABLED: "004:403",
538
+ USER_LOCKED: "005:403",
539
+ USER_ALREADY_EXISTS: "006:409",
540
+ PASSWORD_EXPIRED: "007:401",
541
+ PASSWORD_POLICY_VIOLATION: "008:400",
542
+ OTP_INVALID: "009:400",
543
+ OTP_EXPIRED: "010:400",
544
+ OTP_RESEND_TOO_FAST: "011:429",
545
+ LOGIN_DISABLED: "012:400",
546
+ REGISTER_DISABLED: "013:403",
547
+ STRATEGY_NOT_SUPPORTED: "014:400",
548
+ APIKEY_INVALID: "015:401",
549
+ APIKEY_EXPIRED: "016:401",
550
+ APIKEY_DISABLED: "017:403",
551
+ APIKEY_NOT_FOUND: "018:404",
552
+ RESET_TOKEN_INVALID: "019:400",
553
+ RESET_TOKEN_EXPIRED: "020:400",
554
+ RESET_TOKEN_MAX_ATTEMPTS: "021:429",
555
+ SESSION_NOT_FOUND: "101:401",
556
+ SESSION_EXPIRED: "102:401",
557
+ SESSION_INVALID: "103:401",
558
+ SESSION_CREATE_FAILED: "104:500",
559
+ TOKEN_EXPIRED: "105:401",
560
+ TOKEN_INVALID: "106:401",
561
+ TOKEN_REFRESH_FAILED: "107:401",
562
+ PERMISSION_DENIED: "201:403",
563
+ ROLE_NOT_FOUND: "202:404",
564
+ PERMISSION_NOT_FOUND: "203:404",
565
+ ROLE_ALREADY_EXISTS: "204:409",
566
+ PERMISSION_ALREADY_EXISTS: "205:409",
567
+ LDAP_CONNECTION_FAILED: "301:500",
568
+ LDAP_BIND_FAILED: "302:401",
569
+ LDAP_SEARCH_FAILED: "303:500",
570
+ REPOSITORY_ERROR: "401:500",
571
+ NOT_FOUND: "402:404",
572
+ CONFLICT: "403:409",
573
+ FORBIDDEN: "501:403",
574
+ INVALID_ARGUMENT: "502:400",
575
+ CONFIG_ERROR: "901:500",
576
+ NOT_INITIALIZED: "910:500",
577
+ INTERNAL_ERROR: "999:500"
578
+ };
579
+ var HaiIamError = core.error.buildHaiErrorsDef("iam", IamErrorInfo);
580
+
581
+ // src/authn/apikey/iam-authn-apikey-repository.ts
582
+ var TABLE_NAME = "hai_iam_api_keys";
583
+ var API_KEY_FIELDS = [
584
+ {
585
+ fieldName: "id",
586
+ columnName: "id",
587
+ def: { type: "TEXT", primaryKey: true },
588
+ select: true,
589
+ create: true,
590
+ update: false
591
+ },
592
+ {
593
+ fieldName: "userId",
594
+ columnName: "user_id",
595
+ def: { type: "TEXT", notNull: true },
596
+ select: true,
597
+ create: true,
598
+ update: false
599
+ },
600
+ {
601
+ fieldName: "name",
602
+ columnName: "name",
603
+ def: { type: "TEXT", notNull: true },
604
+ select: true,
605
+ create: true,
606
+ update: true
607
+ },
608
+ {
609
+ fieldName: "keyHash",
610
+ columnName: "key_hash",
611
+ def: { type: "TEXT", notNull: true },
612
+ select: true,
613
+ create: true,
614
+ update: false
615
+ },
616
+ {
617
+ fieldName: "keyPrefix",
618
+ columnName: "key_prefix",
619
+ def: { type: "TEXT", notNull: true },
620
+ select: true,
621
+ create: true,
622
+ update: false
623
+ },
624
+ {
625
+ fieldName: "enabled",
626
+ columnName: "enabled",
627
+ def: { type: "BOOLEAN", notNull: true, defaultValue: 1 },
628
+ select: true,
629
+ create: true,
630
+ update: true
631
+ },
632
+ {
633
+ fieldName: "expiresAt",
634
+ columnName: "expires_at",
635
+ def: { type: "TIMESTAMP" },
636
+ select: true,
637
+ create: true,
638
+ update: false
639
+ },
640
+ {
641
+ fieldName: "createdAt",
642
+ columnName: "created_at",
643
+ def: { type: "TIMESTAMP", notNull: true },
644
+ select: true,
645
+ create: true,
646
+ update: false
647
+ },
648
+ {
649
+ fieldName: "lastUsedAt",
650
+ columnName: "last_used_at",
651
+ def: { type: "TIMESTAMP" },
652
+ select: true,
653
+ create: true,
654
+ update: true
655
+ },
656
+ {
657
+ fieldName: "scopes",
658
+ columnName: "scopes",
659
+ def: { type: "JSON" },
660
+ select: true,
661
+ create: true,
662
+ update: true
663
+ }
664
+ ];
665
+ var apiKeyRepoInstance = null;
666
+ var apiKeyRepoDbConfig = null;
667
+ function resetApiKeyRepoSingleton() {
668
+ apiKeyRepoInstance = null;
669
+ apiKeyRepoDbConfig = null;
670
+ }
671
+ async function createDbApiKeyRepository() {
672
+ if (apiKeyRepoInstance && apiKeyRepoDbConfig === reldb.config)
673
+ return apiKeyRepoInstance;
674
+ const repo = new DbApiKeyRepository();
675
+ await repo.count();
676
+ apiKeyRepoInstance = repo;
677
+ apiKeyRepoDbConfig = reldb.config;
678
+ return repo;
679
+ }
680
+ var DbApiKeyRepository = class extends BaseReldbCrudRepository {
681
+ constructor() {
682
+ super(reldb, {
683
+ table: TABLE_NAME,
684
+ fields: API_KEY_FIELDS
685
+ });
686
+ }
687
+ async insert(data, tx) {
688
+ const result = await this.create(data, tx);
689
+ if (!result.success) {
690
+ return err(
691
+ HaiIamError.REPOSITORY_ERROR,
692
+ iamM("iam_apikeyCreateFailed", { params: { message: result.error.message } }),
693
+ result.error
694
+ );
695
+ }
696
+ return ok(void 0);
697
+ }
698
+ async findOneById(id, tx) {
699
+ const result = await this.findById(id, tx);
700
+ if (!result.success) {
701
+ return this.buildQueryError(result.error);
702
+ }
703
+ return ok(result.data);
704
+ }
705
+ async findByKeyPrefix(prefix, tx) {
706
+ const result = await this.findAll({ where: "key_prefix = ?", params: [prefix] }, tx);
707
+ if (!result.success) {
708
+ return this.buildQueryError(result.error);
709
+ }
710
+ return ok(result.data);
711
+ }
712
+ async findByUserId(userId, tx) {
713
+ const result = await this.findAll({ where: "user_id = ?", params: [userId] }, tx);
714
+ if (!result.success) {
715
+ return this.buildQueryError(result.error);
716
+ }
717
+ return ok(result.data);
718
+ }
719
+ async countByUserId(userId, tx) {
720
+ const result = await this.count({ where: "user_id = ?", params: [userId] }, tx);
721
+ if (!result.success) {
722
+ return this.buildQueryError(result.error);
723
+ }
724
+ return ok(result.data);
725
+ }
726
+ async updateFields(id, data, tx) {
727
+ const result = await this.updateById(id, data, tx);
728
+ if (!result.success) {
729
+ return err(
730
+ HaiIamError.REPOSITORY_ERROR,
731
+ iamM("iam_apikeyUpdateFailed", { params: { message: result.error.message } }),
732
+ result.error
733
+ );
734
+ }
735
+ return ok(void 0);
736
+ }
737
+ async removeById(id, tx) {
738
+ const result = await this.deleteById(id, tx);
739
+ if (!result.success) {
740
+ return err(
741
+ HaiIamError.REPOSITORY_ERROR,
742
+ iamM("iam_apikeyDeleteFailed", { params: { message: result.error.message } }),
743
+ result.error
744
+ );
745
+ }
746
+ return ok(void 0);
747
+ }
748
+ buildQueryError(error) {
749
+ return err(
750
+ HaiIamError.REPOSITORY_ERROR,
751
+ iamM("iam_apikeyQueryFailed", { params: { message: error.message } }),
752
+ error
753
+ );
754
+ }
755
+ };
756
+ var TABLE_NAME2 = "hai_iam_users";
757
+ var USER_FIELDS = [
758
+ {
759
+ fieldName: "id",
760
+ columnName: "id",
761
+ def: { type: "TEXT", primaryKey: true },
762
+ select: true,
763
+ create: true,
764
+ update: false
765
+ },
766
+ {
767
+ fieldName: "username",
768
+ columnName: "username",
769
+ def: { type: "TEXT", notNull: true, unique: true },
770
+ select: true,
771
+ create: true,
772
+ update: true
773
+ },
774
+ {
775
+ fieldName: "email",
776
+ columnName: "email",
777
+ def: { type: "TEXT" },
778
+ select: true,
779
+ create: true,
780
+ update: true
781
+ },
782
+ {
783
+ fieldName: "phone",
784
+ columnName: "phone",
785
+ def: { type: "TEXT" },
786
+ select: true,
787
+ create: true,
788
+ update: true
789
+ },
790
+ {
791
+ fieldName: "displayName",
792
+ columnName: "display_name",
793
+ def: { type: "TEXT" },
794
+ select: true,
795
+ create: true,
796
+ update: true
797
+ },
798
+ {
799
+ fieldName: "avatarUrl",
800
+ columnName: "avatar_url",
801
+ def: { type: "TEXT" },
802
+ select: true,
803
+ create: true,
804
+ update: true
805
+ },
806
+ {
807
+ fieldName: "enabled",
808
+ columnName: "enabled",
809
+ def: { type: "BOOLEAN", notNull: true, defaultValue: 1 },
810
+ select: true,
811
+ create: true,
812
+ update: true
813
+ },
814
+ {
815
+ fieldName: "emailVerified",
816
+ columnName: "email_verified",
817
+ def: { type: "BOOLEAN", defaultValue: 0 },
818
+ select: true,
819
+ create: true,
820
+ update: true
821
+ },
822
+ {
823
+ fieldName: "phoneVerified",
824
+ columnName: "phone_verified",
825
+ def: { type: "BOOLEAN", defaultValue: 0 },
826
+ select: true,
827
+ create: true,
828
+ update: true
829
+ },
830
+ {
831
+ fieldName: "passwordHash",
832
+ columnName: "password_hash",
833
+ def: { type: "TEXT" },
834
+ select: true,
835
+ create: true,
836
+ update: true
837
+ },
838
+ {
839
+ fieldName: "passwordUpdatedAt",
840
+ columnName: "password_updated_at",
841
+ def: { type: "TIMESTAMP" },
842
+ select: true,
843
+ create: true,
844
+ update: true
845
+ },
846
+ {
847
+ fieldName: "loginFailedCount",
848
+ columnName: "login_failed_count",
849
+ def: { type: "INTEGER", defaultValue: 0 },
850
+ select: true,
851
+ create: true,
852
+ update: true
853
+ },
854
+ {
855
+ fieldName: "lastLoginFailedAt",
856
+ columnName: "last_login_failed_at",
857
+ def: { type: "TIMESTAMP" },
858
+ select: true,
859
+ create: true,
860
+ update: true
861
+ },
862
+ {
863
+ fieldName: "lockedUntil",
864
+ columnName: "locked_until",
865
+ def: { type: "TIMESTAMP" },
866
+ select: true,
867
+ create: true,
868
+ update: true
869
+ },
870
+ {
871
+ fieldName: "metadata",
872
+ columnName: "metadata",
873
+ def: { type: "JSON" },
874
+ select: true,
875
+ create: true,
876
+ update: true
877
+ },
878
+ {
879
+ fieldName: "createdAt",
880
+ columnName: "created_at",
881
+ def: { type: "TIMESTAMP", notNull: true },
882
+ select: true,
883
+ create: true,
884
+ update: false
885
+ },
886
+ {
887
+ fieldName: "updatedAt",
888
+ columnName: "updated_at",
889
+ def: { type: "TIMESTAMP", notNull: true },
890
+ select: true,
891
+ create: true,
892
+ update: false
893
+ }
894
+ ];
895
+ var userRepoInstance = null;
896
+ var userRepoDbConfig = null;
897
+ function resetUserRepoSingleton() {
898
+ userRepoInstance = null;
899
+ userRepoDbConfig = null;
900
+ }
901
+ async function createDbUserRepository() {
902
+ if (userRepoInstance && userRepoDbConfig === reldb.config)
903
+ return userRepoInstance;
904
+ const repo = new DbUserRepository();
905
+ await repo.count();
906
+ userRepoInstance = repo;
907
+ userRepoDbConfig = reldb.config;
908
+ return repo;
909
+ }
910
+ var DbUserRepository = class extends BaseReldbCrudRepository {
911
+ constructor() {
912
+ super(reldb, {
913
+ table: TABLE_NAME2,
914
+ fields: USER_FIELDS
915
+ });
916
+ }
917
+ /** 根据用户名查找用户 */
918
+ async findByUsername(username, tx) {
919
+ return this.findOneBy("username = ?", [username], tx);
920
+ }
921
+ /** 根据邮箱查找用户 */
922
+ async findByEmail(email, tx) {
923
+ return this.findOneBy("email = ?", [email], tx);
924
+ }
925
+ /** 根据手机号查找用户 */
926
+ async findByPhone(phone, tx) {
927
+ return this.findOneBy("phone = ?", [phone], tx);
928
+ }
929
+ /** 根据标识符查找用户(同时匹配用户名、邮箱、手机号) */
930
+ async findByIdentifier(identifier, tx) {
931
+ return this.findOneBy("username = ? OR email = ? OR phone = ?", [identifier, identifier, identifier], tx);
932
+ }
933
+ /** 检查用户名是否已存在 */
934
+ async existsByUsername(username, tx) {
935
+ return this.existsBy("username = ?", [username], tx);
936
+ }
937
+ /** 检查邮箱是否已存在 */
938
+ async existsByEmail(email, tx) {
939
+ return this.existsBy("email = ?", [email], tx);
940
+ }
941
+ /**
942
+ * 构建查询错误响应
943
+ *
944
+ * @param error - 原始错误对象
945
+ * @param error.message - 错误消息
946
+ * @param cause - 错误原因
947
+ */
948
+ buildQueryError(error, cause) {
949
+ return err(
950
+ HaiIamError.REPOSITORY_ERROR,
951
+ iamM("iam_queryUserFailed", { params: { message: error.message } }),
952
+ cause
953
+ );
954
+ }
955
+ /**
956
+ * 按条件检查是否存在
957
+ *
958
+ * @param where - SQL WHERE 条件
959
+ * @param params - 绑定参数
960
+ * @param tx - 可选事务句柄
961
+ */
962
+ async existsBy(where, params, tx) {
963
+ const result = await this.exists({ where, params }, tx);
964
+ if (!result.success) {
965
+ return this.buildQueryError(result.error, result.error);
966
+ }
967
+ return ok(result.data);
968
+ }
969
+ /**
970
+ * 按条件查找单条记录
971
+ *
972
+ * @param where - SQL WHERE 条件
973
+ * @param params - 绑定参数
974
+ * @param tx - 可选事务句柄
975
+ * @returns 单条用户记录,或 null
976
+ */
977
+ async findOneBy(where, params, tx) {
978
+ const result = await this.findAll({ where, params, limit: 1 }, tx);
979
+ if (!result.success) {
980
+ return this.buildQueryError(result.error, result.error);
981
+ }
982
+ return ok(result.data[0] ?? null);
983
+ }
984
+ };
985
+
986
+ // src/user/iam-user-utils.ts
987
+ function toUser(storedUser) {
988
+ return {
989
+ id: storedUser.id,
990
+ username: storedUser.username,
991
+ email: storedUser.email,
992
+ phone: storedUser.phone,
993
+ displayName: storedUser.displayName,
994
+ avatarUrl: storedUser.avatarUrl,
995
+ enabled: storedUser.enabled,
996
+ emailVerified: storedUser.emailVerified,
997
+ phoneVerified: storedUser.phoneVerified,
998
+ createdAt: storedUser.createdAt,
999
+ updatedAt: storedUser.updatedAt,
1000
+ metadata: storedUser.metadata
1001
+ };
1002
+ }
1003
+ function ensureCredentialType(credentials, type) {
1004
+ if (credentials.type !== type) {
1005
+ return err(
1006
+ HaiIamError.INVALID_CREDENTIALS,
1007
+ iamM("iam_credentialTypeMismatch")
1008
+ );
1009
+ }
1010
+ return ok(credentials);
1011
+ }
1012
+ function isAccountLocked(user) {
1013
+ if (!user.lockedUntil) {
1014
+ return false;
1015
+ }
1016
+ return /* @__PURE__ */ new Date() < user.lockedUntil;
1017
+ }
1018
+ async function recordLoginFailure(userRepository, user, policy) {
1019
+ const failedCount = (user.loginFailedCount || 0) + 1;
1020
+ const updateData = {
1021
+ loginFailedCount: failedCount,
1022
+ lastLoginFailedAt: /* @__PURE__ */ new Date()
1023
+ };
1024
+ if (failedCount >= policy.maxLoginAttempts) {
1025
+ updateData.lockedUntil = new Date(Date.now() + policy.lockoutDuration * 1e3);
1026
+ }
1027
+ await userRepository.updateById(user.id, updateData);
1028
+ }
1029
+ async function resetLoginFailures(userRepository, user) {
1030
+ if (user.loginFailedCount && user.loginFailedCount > 0) {
1031
+ await userRepository.updateById(user.id, {
1032
+ loginFailedCount: 0,
1033
+ lastLoginFailedAt: void 0,
1034
+ lockedUntil: void 0
1035
+ });
1036
+ }
1037
+ }
1038
+
1039
+ // src/authn/apikey/iam-authn-apikey-strategy.ts
1040
+ var logger = core.logger.child({ module: "iam", scope: "apikey-strategy" });
1041
+ var KEY_PREFIX_LENGTH = 12;
1042
+ function generateRawKey(prefix) {
1043
+ const hex = randomBytes(32).toString("hex");
1044
+ return `${prefix}${hex}`;
1045
+ }
1046
+ function extractKeyPrefix(rawKey) {
1047
+ return rawKey.slice(0, KEY_PREFIX_LENGTH);
1048
+ }
1049
+ function toApiKey(stored) {
1050
+ return {
1051
+ id: stored.id,
1052
+ userId: stored.userId,
1053
+ name: stored.name,
1054
+ keyPrefix: stored.keyPrefix,
1055
+ enabled: stored.enabled,
1056
+ expiresAt: stored.expiresAt,
1057
+ createdAt: stored.createdAt,
1058
+ lastUsedAt: stored.lastUsedAt,
1059
+ scopes: stored.scopes
1060
+ };
1061
+ }
1062
+ function createApiKeyStrategy(config) {
1063
+ const apikeyConfig = config.apikeyConfig ? ApiKeyConfigSchema.parse(config.apikeyConfig) : ApiKeyConfigSchema.parse({});
1064
+ const { userRepository, apiKeyRepository } = config;
1065
+ const passwordOps = crypto$1.password;
1066
+ async function verifyRawKey(rawKey) {
1067
+ const prefix = extractKeyPrefix(rawKey);
1068
+ const candidatesResult = await apiKeyRepository.findByKeyPrefix(prefix);
1069
+ if (!candidatesResult.success) {
1070
+ return candidatesResult;
1071
+ }
1072
+ for (const candidate of candidatesResult.data) {
1073
+ const verifyResult = passwordOps.verify(rawKey, candidate.keyHash);
1074
+ if (!verifyResult.success)
1075
+ continue;
1076
+ if (!verifyResult.data)
1077
+ continue;
1078
+ if (!candidate.enabled) {
1079
+ return err(HaiIamError.APIKEY_DISABLED, iamM("iam_apikeyDisabled"));
1080
+ }
1081
+ if (candidate.expiresAt && /* @__PURE__ */ new Date() > candidate.expiresAt) {
1082
+ return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
1083
+ }
1084
+ apiKeyRepository.updateFields(candidate.id, { lastUsedAt: /* @__PURE__ */ new Date() }).catch(() => {
1085
+ logger.warn("Failed to update API Key lastUsedAt", { keyId: candidate.id });
1086
+ });
1087
+ return ok(toApiKey(candidate));
1088
+ }
1089
+ return err(HaiIamError.APIKEY_INVALID, iamM("iam_apikeyInvalid"));
1090
+ }
1091
+ const strategy = {
1092
+ type: "apikey",
1093
+ name: "apikey-strategy",
1094
+ async authenticate(credentials) {
1095
+ const credentialResult = ensureCredentialType(credentials, "apikey");
1096
+ if (!credentialResult.success) {
1097
+ return credentialResult;
1098
+ }
1099
+ const { key } = credentialResult.data;
1100
+ const verifyResult = await verifyRawKey(key);
1101
+ if (!verifyResult.success) {
1102
+ return verifyResult;
1103
+ }
1104
+ const apiKey = verifyResult.data;
1105
+ const userResult = await userRepository.findById(apiKey.userId);
1106
+ if (!userResult.success) {
1107
+ return err(HaiIamError.REPOSITORY_ERROR, userResult.error.message, userResult.error);
1108
+ }
1109
+ if (!userResult.data) {
1110
+ return err(HaiIamError.USER_NOT_FOUND, iamM("iam_userNotExist"));
1111
+ }
1112
+ if (!userResult.data.enabled) {
1113
+ return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
1114
+ }
1115
+ logger.info("API Key authentication succeeded", { userId: userResult.data.id, keyId: apiKey.id });
1116
+ return ok(toUser(userResult.data));
1117
+ }
1118
+ };
1119
+ const apiKeyFunctions = {
1120
+ async createApiKey(userId, options) {
1121
+ try {
1122
+ const countResult = await apiKeyRepository.countByUserId(userId);
1123
+ if (!countResult.success)
1124
+ return countResult;
1125
+ if (countResult.data >= apikeyConfig.maxKeysPerUser) {
1126
+ return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_apikeyMaxKeysReached", { params: { max: apikeyConfig.maxKeysPerUser } }));
1127
+ }
1128
+ const rawKey = generateRawKey(apikeyConfig.prefix);
1129
+ const hashResult = passwordOps.hash(rawKey);
1130
+ if (!hashResult.success) {
1131
+ return err(HaiIamError.INTERNAL_ERROR, hashResult.error.message);
1132
+ }
1133
+ const expirationDays = options.expirationDays ?? apikeyConfig.defaultExpirationDays;
1134
+ const expiresAt = expirationDays > 0 ? new Date(Date.now() + expirationDays * 864e5) : null;
1135
+ const id = core.id.generate();
1136
+ const now = /* @__PURE__ */ new Date();
1137
+ const storedApiKey = {
1138
+ id,
1139
+ userId,
1140
+ name: options.name,
1141
+ keyHash: hashResult.data,
1142
+ keyPrefix: extractKeyPrefix(rawKey),
1143
+ enabled: true,
1144
+ expiresAt,
1145
+ createdAt: now,
1146
+ lastUsedAt: null,
1147
+ scopes: options.scopes ?? []
1148
+ };
1149
+ const insertResult = await apiKeyRepository.insert(storedApiKey);
1150
+ if (!insertResult.success)
1151
+ return insertResult;
1152
+ logger.info("API Key created", { userId, keyId: id, name: options.name });
1153
+ return ok({ apiKey: toApiKey(storedApiKey), rawKey });
1154
+ } catch (error) {
1155
+ return err(HaiIamError.INTERNAL_ERROR, iamM("iam_apikeyCreateFailed", { params: { message: String(error) } }), error);
1156
+ }
1157
+ },
1158
+ async listApiKeys(userId) {
1159
+ const result = await apiKeyRepository.findByUserId(userId);
1160
+ if (!result.success)
1161
+ return result;
1162
+ return ok(result.data.map(toApiKey));
1163
+ },
1164
+ async getApiKey(keyId) {
1165
+ const result = await apiKeyRepository.findOneById(keyId);
1166
+ if (!result.success)
1167
+ return result;
1168
+ return ok(result.data ? toApiKey(result.data) : null);
1169
+ },
1170
+ async revokeApiKey(keyId) {
1171
+ const result = await apiKeyRepository.removeById(keyId);
1172
+ if (!result.success)
1173
+ return result;
1174
+ logger.info("API Key revoked", { keyId });
1175
+ return ok(void 0);
1176
+ },
1177
+ async verifyApiKey(rawKey) {
1178
+ return verifyRawKey(rawKey);
1179
+ }
1180
+ };
1181
+ return { strategy, apiKeyFunctions };
1182
+ }
1183
+ var logger2 = core.logger.child({ module: "iam", scope: "ldap-strategy" });
1184
+ function escapeLdapFilterValue(value) {
1185
+ return value.replace(/[\\*()/\0]/g, (ch) => {
1186
+ const hex = ch.charCodeAt(0).toString(16).padStart(2, "0");
1187
+ return `\\${hex}`;
1188
+ });
1189
+ }
1190
+ function createLdapStrategy(config) {
1191
+ const {
1192
+ ldapConfig,
1193
+ userRepository,
1194
+ ldapClientFactory,
1195
+ syncUser = true,
1196
+ maxLoginAttempts = 5,
1197
+ lockoutDuration = 900
1198
+ } = config;
1199
+ function getAttributeValue(entry, attr) {
1200
+ const value = entry.attributes[attr];
1201
+ if (Array.isArray(value)) {
1202
+ return value[0];
1203
+ }
1204
+ return value;
1205
+ }
1206
+ function buildLdapUser(entry, ldapUsername, ldapEmail, ldapDisplayName) {
1207
+ const now = /* @__PURE__ */ new Date();
1208
+ return {
1209
+ id: entry.dn,
1210
+ username: ldapUsername,
1211
+ email: ldapEmail,
1212
+ displayName: ldapDisplayName,
1213
+ enabled: true,
1214
+ createdAt: now,
1215
+ updatedAt: now,
1216
+ metadata: {
1217
+ ldapDn: entry.dn,
1218
+ authSource: "ldap"
1219
+ }
1220
+ };
1221
+ }
1222
+ return {
1223
+ type: "ldap",
1224
+ name: "ldap-strategy",
1225
+ async authenticate(credentials) {
1226
+ const credentialResult = ensureCredentialType(credentials, "ldap");
1227
+ if (!credentialResult.success) {
1228
+ return credentialResult;
1229
+ }
1230
+ const { username, password } = credentialResult.data;
1231
+ const clientResult = await ldapClientFactory(ldapConfig);
1232
+ if (!clientResult.success) {
1233
+ return err(
1234
+ HaiIamError.LDAP_CONNECTION_FAILED,
1235
+ iamM("iam_ldapConnectionFailed"),
1236
+ clientResult.error
1237
+ );
1238
+ }
1239
+ const client = clientResult.data;
1240
+ try {
1241
+ const adminBindResult = await client.bind(ldapConfig.bindDn, ldapConfig.bindPassword);
1242
+ if (!adminBindResult.success) {
1243
+ return err(
1244
+ HaiIamError.LDAP_BIND_FAILED,
1245
+ iamM("iam_ldapAdminBindFailed"),
1246
+ adminBindResult.error
1247
+ );
1248
+ }
1249
+ const searchFilter = ldapConfig.searchFilter.replace("{{username}}", escapeLdapFilterValue(username));
1250
+ const searchResult = await client.search(
1251
+ ldapConfig.searchBase,
1252
+ searchFilter,
1253
+ [ldapConfig.usernameAttribute, ldapConfig.emailAttribute, ldapConfig.displayNameAttribute]
1254
+ );
1255
+ if (!searchResult.success) {
1256
+ return err(
1257
+ HaiIamError.LDAP_SEARCH_FAILED,
1258
+ iamM("iam_ldapSearchFailed"),
1259
+ searchResult.error
1260
+ );
1261
+ }
1262
+ const entries = searchResult.data;
1263
+ if (entries.length === 0) {
1264
+ return err(
1265
+ HaiIamError.USER_NOT_FOUND,
1266
+ iamM("iam_userNotExist")
1267
+ );
1268
+ }
1269
+ const entry = entries[0];
1270
+ const ldapUsername = getAttributeValue(entry, ldapConfig.usernameAttribute) || username;
1271
+ const ldapEmail = getAttributeValue(entry, ldapConfig.emailAttribute);
1272
+ const ldapDisplayName = getAttributeValue(entry, ldapConfig.displayNameAttribute);
1273
+ const localUserResult = await userRepository.findByUsername(ldapUsername);
1274
+ if (!localUserResult.success) {
1275
+ return localUserResult;
1276
+ }
1277
+ let storedUser = localUserResult.data;
1278
+ if (storedUser) {
1279
+ if (!storedUser.enabled) {
1280
+ return err(
1281
+ HaiIamError.USER_DISABLED,
1282
+ iamM("iam_accountDisabled")
1283
+ );
1284
+ }
1285
+ if (isAccountLocked(storedUser)) {
1286
+ return err(
1287
+ HaiIamError.USER_LOCKED,
1288
+ iamM("iam_accountLocked")
1289
+ );
1290
+ }
1291
+ }
1292
+ const userBindResult = await client.bind(entry.dn, password);
1293
+ if (!userBindResult.success) {
1294
+ if (storedUser) {
1295
+ await recordLoginFailure(userRepository, storedUser, { maxLoginAttempts, lockoutDuration });
1296
+ }
1297
+ logger2.warn("LDAP authentication failed", { username });
1298
+ return err(
1299
+ HaiIamError.INVALID_CREDENTIALS,
1300
+ iamM("iam_passwordWrong")
1301
+ );
1302
+ }
1303
+ if (!storedUser && syncUser) {
1304
+ const createResult = await userRepository.create({
1305
+ username: ldapUsername,
1306
+ email: ldapEmail,
1307
+ displayName: ldapDisplayName,
1308
+ enabled: true,
1309
+ emailVerified: !!ldapEmail,
1310
+ metadata: {
1311
+ ldapDn: entry.dn,
1312
+ authSource: "ldap"
1313
+ }
1314
+ });
1315
+ if (!createResult.success) {
1316
+ return err(
1317
+ HaiIamError.REPOSITORY_ERROR,
1318
+ iamM("iam_createUserFailed", { params: { message: createResult.error.message } }),
1319
+ createResult.error
1320
+ );
1321
+ }
1322
+ const createdResult = await userRepository.findByUsername(ldapUsername);
1323
+ if (!createdResult.success) {
1324
+ return createdResult;
1325
+ }
1326
+ storedUser = createdResult.data;
1327
+ if (storedUser && config.onUserAutoRegistered) {
1328
+ try {
1329
+ await config.onUserAutoRegistered(storedUser.id);
1330
+ } catch (callbackError) {
1331
+ logger2.warn("onUserAutoRegistered callback failed", { userId: storedUser?.id, error: callbackError });
1332
+ }
1333
+ }
1334
+ } else if (storedUser && syncUser) {
1335
+ const updateResult = await userRepository.updateById(storedUser.id, {
1336
+ email: ldapEmail || storedUser.email,
1337
+ displayName: ldapDisplayName || storedUser.displayName,
1338
+ metadata: {
1339
+ ...storedUser.metadata,
1340
+ ldapDn: entry.dn,
1341
+ authSource: "ldap"
1342
+ }
1343
+ });
1344
+ if (updateResult.success) {
1345
+ const refreshed = await userRepository.findByUsername(ldapUsername);
1346
+ if (refreshed.success) {
1347
+ storedUser = refreshed.data ?? storedUser;
1348
+ }
1349
+ }
1350
+ }
1351
+ if (!storedUser) {
1352
+ const ldapUser = buildLdapUser(entry, ldapUsername, ldapEmail, ldapDisplayName);
1353
+ return ok(ldapUser);
1354
+ }
1355
+ await resetLoginFailures(userRepository, storedUser);
1356
+ logger2.info("LDAP authentication succeeded", { userId: storedUser.id });
1357
+ return ok(toUser(storedUser));
1358
+ } finally {
1359
+ await client.unbind();
1360
+ }
1361
+ }
1362
+ };
1363
+ }
1364
+ var OTP_KEY_PREFIX = "hai:iam:otp:";
1365
+ function buildOtpKey(identifier) {
1366
+ return `${OTP_KEY_PREFIX}${identifier}`;
1367
+ }
1368
+ function restoreOtpDates(record) {
1369
+ return {
1370
+ ...record,
1371
+ expiresAt: record.expiresAt instanceof Date ? record.expiresAt : new Date(record.expiresAt),
1372
+ createdAt: record.createdAt instanceof Date ? record.createdAt : new Date(record.createdAt)
1373
+ };
1374
+ }
1375
+ var otpRepoInstance = null;
1376
+ function resetOtpRepoSingleton() {
1377
+ otpRepoInstance = null;
1378
+ }
1379
+ function createCacheOtpRepository() {
1380
+ if (otpRepoInstance)
1381
+ return otpRepoInstance;
1382
+ const repo = {
1383
+ async saveOtp(identifier, code, expiresIn) {
1384
+ const now = Date.now();
1385
+ const record = {
1386
+ identifier,
1387
+ code,
1388
+ expiresAt: new Date(now + expiresIn * 1e3),
1389
+ attempts: 0,
1390
+ createdAt: new Date(now)
1391
+ };
1392
+ const result = await cache.kv.set(buildOtpKey(identifier), record, { ex: expiresIn });
1393
+ if (!result.success) {
1394
+ return err(
1395
+ HaiIamError.REPOSITORY_ERROR,
1396
+ iamM("iam_saveOtpFailed", { params: { message: result.error.message } }),
1397
+ result.error
1398
+ );
1399
+ }
1400
+ return ok(void 0);
1401
+ },
1402
+ async fetchOtp(identifier) {
1403
+ const result = await cache.kv.get(buildOtpKey(identifier));
1404
+ if (!result.success) {
1405
+ return err(
1406
+ HaiIamError.REPOSITORY_ERROR,
1407
+ iamM("iam_queryOtpFailed", { params: { message: result.error.message } }),
1408
+ result.error
1409
+ );
1410
+ }
1411
+ if (!result.data) {
1412
+ return ok(null);
1413
+ }
1414
+ return ok(restoreOtpDates(result.data));
1415
+ },
1416
+ async incrementOtpAttempts(identifier) {
1417
+ const otpKey = buildOtpKey(identifier);
1418
+ const current = await cache.kv.get(otpKey);
1419
+ if (!current.success) {
1420
+ return err(
1421
+ HaiIamError.REPOSITORY_ERROR,
1422
+ iamM("iam_queryOtpFailed", { params: { message: current.error.message } }),
1423
+ current.error
1424
+ );
1425
+ }
1426
+ if (!current.data) {
1427
+ return ok(0);
1428
+ }
1429
+ const record = restoreOtpDates(current.data);
1430
+ const nextAttempts = record.attempts + 1;
1431
+ const ttlResult = await cache.kv.ttl(otpKey);
1432
+ const ttl = ttlResult.success && ttlResult.data > 0 ? ttlResult.data : 1;
1433
+ const updateResult = await cache.kv.set(otpKey, { ...record, attempts: nextAttempts }, { ex: ttl });
1434
+ if (!updateResult.success) {
1435
+ return err(
1436
+ HaiIamError.REPOSITORY_ERROR,
1437
+ iamM("iam_updateOtpAttemptsFailed", { params: { message: updateResult.error.message } }),
1438
+ updateResult.error
1439
+ );
1440
+ }
1441
+ return ok(nextAttempts);
1442
+ },
1443
+ async removeOtp(identifier) {
1444
+ const result = await cache.kv.del(buildOtpKey(identifier));
1445
+ if (!result.success) {
1446
+ return err(
1447
+ HaiIamError.REPOSITORY_ERROR,
1448
+ iamM("iam_deleteOtpFailed", { params: { message: result.error.message } }),
1449
+ result.error
1450
+ );
1451
+ }
1452
+ return ok(void 0);
1453
+ }
1454
+ };
1455
+ otpRepoInstance = repo;
1456
+ return repo;
1457
+ }
1458
+ var logger3 = core.logger.child({ module: "iam", scope: "otp-strategy" });
1459
+ function generateOtpCode(length) {
1460
+ let code = "";
1461
+ const threshold = 250;
1462
+ while (code.length < length) {
1463
+ const randomValues = new Uint8Array(length - code.length);
1464
+ crypto.getRandomValues(randomValues);
1465
+ for (const byte of randomValues) {
1466
+ if (byte < threshold && code.length < length) {
1467
+ code += String(byte % 10);
1468
+ }
1469
+ }
1470
+ }
1471
+ return code;
1472
+ }
1473
+ function identifierType(identifier) {
1474
+ if (identifier.includes("@")) {
1475
+ return "email";
1476
+ }
1477
+ if (/^\+?\d{8,}$/.test(identifier.replace(/[\s-]/g, ""))) {
1478
+ return "phone";
1479
+ }
1480
+ return "unknown";
1481
+ }
1482
+ function createOtpStrategy(config) {
1483
+ const otpConfig = config.otpConfig ? OtpConfigSchema.parse(config.otpConfig) : OtpConfigSchema.parse({});
1484
+ const maxLoginAttempts = config.maxLoginAttempts ?? 5;
1485
+ const lockoutDuration = config.lockoutDuration ?? 900;
1486
+ const registerConfig = config.registerConfig;
1487
+ const allowAutoRegister = registerConfig?.enabled ?? config.autoRegister ?? false;
1488
+ const defaultEnabled = registerConfig?.defaultEnabled ?? true;
1489
+ function buildChallengeResult(expiresAt) {
1490
+ return { expiresAt };
1491
+ }
1492
+ const strategy = {
1493
+ type: "otp",
1494
+ name: "otp-strategy",
1495
+ async authenticate(credentials) {
1496
+ const credentialResult = ensureCredentialType(credentials, "otp");
1497
+ if (!credentialResult.success) {
1498
+ return credentialResult;
1499
+ }
1500
+ const { identifier, code } = credentialResult.data;
1501
+ const userResult = await config.userRepository.findByIdentifier(identifier);
1502
+ if (!userResult.success) {
1503
+ return userResult;
1504
+ }
1505
+ let storedUser = userResult.data;
1506
+ if (storedUser) {
1507
+ if (!storedUser.enabled) {
1508
+ return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
1509
+ }
1510
+ if (isAccountLocked(storedUser)) {
1511
+ return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
1512
+ }
1513
+ }
1514
+ const storedOtpResult = await config.otpRepository.fetchOtp(identifier);
1515
+ if (!storedOtpResult.success) {
1516
+ return storedOtpResult;
1517
+ }
1518
+ const storedOtp = storedOtpResult.data;
1519
+ if (!storedOtp) {
1520
+ return err(HaiIamError.OTP_INVALID, iamM("iam_otpNotExistOrExpired"));
1521
+ }
1522
+ if (storedOtp.attempts >= otpConfig.maxAttempts) {
1523
+ await config.otpRepository.removeOtp(identifier);
1524
+ if (storedUser) {
1525
+ await recordLoginFailure(config.userRepository, storedUser, { maxLoginAttempts, lockoutDuration });
1526
+ }
1527
+ return err(HaiIamError.OTP_INVALID, iamM("iam_otpInvalid"));
1528
+ }
1529
+ if (!core.string.constantTimeEqual(storedOtp.code, code)) {
1530
+ await config.otpRepository.incrementOtpAttempts(identifier);
1531
+ if (storedUser) {
1532
+ await recordLoginFailure(config.userRepository, storedUser, { maxLoginAttempts, lockoutDuration });
1533
+ }
1534
+ return err(HaiIamError.OTP_INVALID, iamM("iam_otpWrong"));
1535
+ }
1536
+ await config.otpRepository.removeOtp(identifier);
1537
+ if (!storedUser && allowAutoRegister) {
1538
+ const type = identifierType(identifier);
1539
+ const createResult = await config.userRepository.create({
1540
+ username: identifier,
1541
+ email: type === "email" ? identifier : void 0,
1542
+ phone: type === "phone" ? identifier : void 0,
1543
+ enabled: defaultEnabled,
1544
+ emailVerified: type === "email",
1545
+ phoneVerified: type === "phone"
1546
+ });
1547
+ if (!createResult.success) {
1548
+ return err(
1549
+ HaiIamError.REPOSITORY_ERROR,
1550
+ iamM("iam_createUserFailed", { params: { message: createResult.error.message } }),
1551
+ createResult.error
1552
+ );
1553
+ }
1554
+ const createdResult = await config.userRepository.findByIdentifier(identifier);
1555
+ if (!createdResult.success) {
1556
+ return createdResult;
1557
+ }
1558
+ storedUser = createdResult.data;
1559
+ if (storedUser && config.onUserAutoRegistered) {
1560
+ try {
1561
+ await config.onUserAutoRegistered(storedUser.id);
1562
+ } catch (callbackError) {
1563
+ logger3.warn("onUserAutoRegistered callback failed", { userId: storedUser.id, error: callbackError });
1564
+ }
1565
+ }
1566
+ }
1567
+ if (!storedUser) {
1568
+ return err(HaiIamError.USER_NOT_FOUND, iamM("iam_userNotExist"));
1569
+ }
1570
+ await resetLoginFailures(config.userRepository, storedUser);
1571
+ logger3.info("OTP authentication succeeded", { userId: storedUser.id });
1572
+ return ok(toUser(storedUser));
1573
+ }
1574
+ };
1575
+ async function challenge(identifier) {
1576
+ const existingResult = await config.otpRepository.fetchOtp(identifier);
1577
+ if (existingResult.success && existingResult.data) {
1578
+ const elapsedSeconds = Math.floor((Date.now() - existingResult.data.createdAt.getTime()) / 1e3);
1579
+ if (elapsedSeconds < otpConfig.resendInterval) {
1580
+ return err(
1581
+ HaiIamError.OTP_RESEND_TOO_FAST,
1582
+ iamM("iam_otpResendTooFast", { params: { seconds: otpConfig.resendInterval - elapsedSeconds } })
1583
+ );
1584
+ }
1585
+ }
1586
+ const code = generateOtpCode(otpConfig.length);
1587
+ const expiresAt = new Date(Date.now() + otpConfig.expiresIn * 1e3);
1588
+ const storeResult = await config.otpRepository.saveOtp(identifier, code, otpConfig.expiresIn);
1589
+ if (!storeResult.success) {
1590
+ return storeResult;
1591
+ }
1592
+ const type = identifierType(identifier);
1593
+ if (type === "email" && config.onOtpSendEmail) {
1594
+ try {
1595
+ await config.onOtpSendEmail(identifier, code);
1596
+ } catch (error) {
1597
+ return err(
1598
+ HaiIamError.INTERNAL_ERROR,
1599
+ iamM("iam_otpSendFailed", { params: { message: String(error) } }),
1600
+ error
1601
+ );
1602
+ }
1603
+ } else if (type === "phone" && config.onOtpSendSms) {
1604
+ try {
1605
+ await config.onOtpSendSms(identifier, code);
1606
+ } catch (error) {
1607
+ return err(
1608
+ HaiIamError.INTERNAL_ERROR,
1609
+ iamM("iam_otpSendFailed", { params: { message: String(error) } }),
1610
+ error
1611
+ );
1612
+ }
1613
+ } else {
1614
+ return err(
1615
+ HaiIamError.INTERNAL_ERROR,
1616
+ iamM("iam_identifierTypeNotSupported")
1617
+ );
1618
+ }
1619
+ const result = buildChallengeResult(expiresAt);
1620
+ logger3.debug("OTP challenge sent", { identifier, expiresAt });
1621
+ return ok(result);
1622
+ }
1623
+ return { strategy, challenge };
1624
+ }
1625
+ var logger4 = core.logger.child({ module: "iam", scope: "password-strategy" });
1626
+ function createPasswordStrategy(config) {
1627
+ const passwordConfig = config.passwordConfig ? PasswordConfigSchema.parse(config.passwordConfig) : PasswordConfigSchema.parse({});
1628
+ const maxLoginAttempts = config.maxLoginAttempts ?? 5;
1629
+ const lockoutDuration = config.lockoutDuration ?? 900;
1630
+ const passwordOps = crypto$1.password;
1631
+ function mapPasswordError(message) {
1632
+ return err(
1633
+ HaiIamError.INTERNAL_ERROR,
1634
+ message
1635
+ );
1636
+ }
1637
+ function validatePasswordStrength(password) {
1638
+ if (password.length < passwordConfig.minLength) {
1639
+ return err(
1640
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1641
+ iamM("iam_passwordMinLength", { params: { minLength: passwordConfig.minLength } })
1642
+ );
1643
+ }
1644
+ if (password.length > passwordConfig.maxLength) {
1645
+ return err(
1646
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1647
+ iamM("iam_passwordMaxLength", { params: { maxLength: passwordConfig.maxLength } })
1648
+ );
1649
+ }
1650
+ if (passwordConfig.requireUppercase && !/[A-Z]/.test(password)) {
1651
+ return err(
1652
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1653
+ iamM("iam_passwordNeedUppercase")
1654
+ );
1655
+ }
1656
+ if (passwordConfig.requireLowercase && !/[a-z]/.test(password)) {
1657
+ return err(
1658
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1659
+ iamM("iam_passwordNeedLowercase")
1660
+ );
1661
+ }
1662
+ if (passwordConfig.requireNumber && !/\d/.test(password)) {
1663
+ return err(
1664
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1665
+ iamM("iam_passwordNeedNumber")
1666
+ );
1667
+ }
1668
+ if (passwordConfig.requireSpecialChar && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
1669
+ return err(
1670
+ HaiIamError.PASSWORD_POLICY_VIOLATION,
1671
+ iamM("iam_passwordNeedSpecialChar")
1672
+ );
1673
+ }
1674
+ return ok(void 0);
1675
+ }
1676
+ function isPasswordExpired(user) {
1677
+ if (passwordConfig.expirationDays <= 0 || !user.passwordUpdatedAt) {
1678
+ return false;
1679
+ }
1680
+ const expirationDate = new Date(user.passwordUpdatedAt);
1681
+ expirationDate.setDate(expirationDate.getDate() + passwordConfig.expirationDays);
1682
+ return /* @__PURE__ */ new Date() > expirationDate;
1683
+ }
1684
+ function checkAccountStatus(user) {
1685
+ if (!user.enabled) {
1686
+ return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
1687
+ }
1688
+ if (isAccountLocked(user)) {
1689
+ return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
1690
+ }
1691
+ return ok(void 0);
1692
+ }
1693
+ async function verifyUserPassword(user, password) {
1694
+ if (!user.passwordHash) {
1695
+ return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_accountNoPassword"));
1696
+ }
1697
+ const verifyResult = passwordOps.verify(password, user.passwordHash);
1698
+ if (!verifyResult.success) {
1699
+ return mapPasswordError(verifyResult.error.message);
1700
+ }
1701
+ if (!verifyResult.data) {
1702
+ await recordLoginFailure(config.userRepository, user, { maxLoginAttempts, lockoutDuration });
1703
+ logger4.warn("Password verification failed", { userId: user.id });
1704
+ return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_passwordWrong"));
1705
+ }
1706
+ return ok(void 0);
1707
+ }
1708
+ const strategy = {
1709
+ type: "password",
1710
+ name: "password-strategy",
1711
+ async authenticate(credentials) {
1712
+ const credentialResult = ensureCredentialType(credentials, "password");
1713
+ if (!credentialResult.success) {
1714
+ return credentialResult;
1715
+ }
1716
+ const { identifier, password } = credentialResult.data;
1717
+ const userResult = await config.userRepository.findByIdentifier(identifier);
1718
+ if (!userResult.success)
1719
+ return userResult;
1720
+ const storedUser = userResult.data;
1721
+ if (!storedUser) {
1722
+ await passwordOps.hash("dummy-password-to-prevent-timing-leak");
1723
+ return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_passwordWrong"));
1724
+ }
1725
+ const statusResult = checkAccountStatus(storedUser);
1726
+ if (!statusResult.success)
1727
+ return statusResult;
1728
+ const pwResult = await verifyUserPassword(storedUser, password);
1729
+ if (!pwResult.success)
1730
+ return pwResult;
1731
+ if (isPasswordExpired(storedUser)) {
1732
+ return err(HaiIamError.PASSWORD_EXPIRED, iamM("iam_passwordExpired"));
1733
+ }
1734
+ await resetLoginFailures(config.userRepository, storedUser);
1735
+ logger4.info("Password authentication succeeded", { userId: storedUser.id });
1736
+ return ok(toUser(storedUser));
1737
+ }
1738
+ };
1739
+ function hashPassword(password) {
1740
+ const hashResult = passwordOps.hash(password);
1741
+ if (!hashResult.success) {
1742
+ return mapPasswordError(hashResult.error.message);
1743
+ }
1744
+ return ok(hashResult.data);
1745
+ }
1746
+ return {
1747
+ strategy,
1748
+ validatePassword: validatePasswordStrength,
1749
+ hashPassword,
1750
+ passwordConfig
1751
+ };
1752
+ }
1753
+
1754
+ // src/authn/iam-authn-functions.ts
1755
+ var logger5 = core.logger.child({ module: "iam", scope: "authn" });
1756
+ async function createAuthnOperations(deps) {
1757
+ try {
1758
+ const { config, sessionFunctions, authzFunctions, ldapClientFactory, ldapSyncUser, onOtpSendEmail, onOtpSendSms } = deps;
1759
+ const userRepository = await createDbUserRepository();
1760
+ const securityConfig = SecurityConfigSchema.parse(config.security ?? {});
1761
+ const loginConfig = LoginConfigSchema.parse(config.login ?? {});
1762
+ async function onUserAutoRegistered(userId) {
1763
+ if (!config.rbac?.defaultRole)
1764
+ return;
1765
+ const roleResult = await authzFunctions.getRoleByCode(config.rbac.defaultRole);
1766
+ if (roleResult.success && roleResult.data) {
1767
+ await authzFunctions.assignRole(userId, roleResult.data.id);
1768
+ }
1769
+ }
1770
+ const passwordResult = createPasswordStrategy({
1771
+ passwordConfig: config.password,
1772
+ userRepository,
1773
+ maxLoginAttempts: securityConfig.maxLoginAttempts,
1774
+ lockoutDuration: securityConfig.lockoutDuration
1775
+ });
1776
+ let otpResult;
1777
+ const otpConfig = config.otp ? OtpConfigSchema.parse(config.otp) : void 0;
1778
+ if (loginConfig.otp && otpConfig) {
1779
+ const otpRepository = createCacheOtpRepository();
1780
+ otpResult = createOtpStrategy({
1781
+ otpConfig,
1782
+ userRepository,
1783
+ otpRepository,
1784
+ autoRegister: true,
1785
+ registerConfig: config.register,
1786
+ maxLoginAttempts: securityConfig.maxLoginAttempts,
1787
+ lockoutDuration: securityConfig.lockoutDuration,
1788
+ onUserAutoRegistered,
1789
+ onOtpSendEmail,
1790
+ onOtpSendSms
1791
+ });
1792
+ }
1793
+ let ldapStrategy;
1794
+ if (loginConfig.ldap && config.ldap && ldapClientFactory) {
1795
+ ldapStrategy = createLdapStrategy({
1796
+ ldapConfig: config.ldap,
1797
+ userRepository,
1798
+ ldapClientFactory,
1799
+ syncUser: ldapSyncUser ?? true,
1800
+ maxLoginAttempts: securityConfig.maxLoginAttempts,
1801
+ lockoutDuration: securityConfig.lockoutDuration,
1802
+ onUserAutoRegistered
1803
+ });
1804
+ }
1805
+ let apiKeyStrategy;
1806
+ let apiKeyFunctions = null;
1807
+ if (loginConfig.apikey) {
1808
+ const apiKeyConfig = config.apikey ? ApiKeyConfigSchema.parse(config.apikey) : void 0;
1809
+ const apiKeyRepository = await createDbApiKeyRepository();
1810
+ const apiKeyResult = createApiKeyStrategy({
1811
+ apikeyConfig: apiKeyConfig,
1812
+ userRepository,
1813
+ apiKeyRepository
1814
+ });
1815
+ apiKeyStrategy = apiKeyResult.strategy;
1816
+ apiKeyFunctions = apiKeyResult.apiKeyFunctions;
1817
+ }
1818
+ const operations = buildAuthnOperations({
1819
+ passwordStrategy: passwordResult.strategy,
1820
+ otpStrategy: otpResult?.strategy,
1821
+ otpChallenge: otpResult?.challenge,
1822
+ ldapStrategy,
1823
+ apiKeyStrategy,
1824
+ sessionFunctions,
1825
+ authzFunctions,
1826
+ config
1827
+ });
1828
+ logger5.info("Authn sub-feature initialized");
1829
+ return ok({ authn: operations, passwordStrategyResult: passwordResult, apiKeyFunctions });
1830
+ } catch (error) {
1831
+ logger5.error("Authn sub-feature initialization failed", { error });
1832
+ return err(
1833
+ HaiIamError.CONFIG_ERROR,
1834
+ iamM("iam_initComponentFailed"),
1835
+ error
1836
+ );
1837
+ }
1838
+ }
1839
+ function buildAuthnOperations(deps) {
1840
+ const {
1841
+ passwordStrategy,
1842
+ otpStrategy,
1843
+ otpChallenge,
1844
+ ldapStrategy,
1845
+ apiKeyStrategy,
1846
+ sessionFunctions,
1847
+ authzFunctions,
1848
+ config
1849
+ } = deps;
1850
+ const loginConfig = LoginConfigSchema.parse(config.login ?? {});
1851
+ const agreementConfig = AgreementConfigSchema.parse(config.agreements ?? {});
1852
+ function buildAgreementDisplay() {
1853
+ if (!agreementConfig.showOnLogin) {
1854
+ return void 0;
1855
+ }
1856
+ if (!agreementConfig.userAgreementUrl && !agreementConfig.privacyPolicyUrl) {
1857
+ return void 0;
1858
+ }
1859
+ return {
1860
+ userAgreementUrl: agreementConfig.userAgreementUrl,
1861
+ privacyPolicyUrl: agreementConfig.privacyPolicyUrl,
1862
+ showOnRegister: agreementConfig.showOnRegister,
1863
+ showOnLogin: agreementConfig.showOnLogin
1864
+ };
1865
+ }
1866
+ function loginDisabled(type) {
1867
+ if (!loginConfig[type]) {
1868
+ return err(
1869
+ HaiIamError.LOGIN_DISABLED,
1870
+ iamM("iam_loginDisabled", { params: { type } })
1871
+ );
1872
+ }
1873
+ return null;
1874
+ }
1875
+ async function resolveUserRoleCodes(userId) {
1876
+ const rolesResult = await authzFunctions.getUserRoles(userId);
1877
+ if (!rolesResult.success) {
1878
+ return rolesResult;
1879
+ }
1880
+ return ok(rolesResult.data.map((role) => role.code));
1881
+ }
1882
+ async function resolveUserPermissionCodes(userId) {
1883
+ const permissionsResult = await authzFunctions.getUserPermissions(userId);
1884
+ if (!permissionsResult.success) {
1885
+ return permissionsResult;
1886
+ }
1887
+ return ok(permissionsResult.data.map((p) => p.code));
1888
+ }
1889
+ function resolveStrategy(type, strategy) {
1890
+ if (strategy) {
1891
+ return ok(strategy);
1892
+ }
1893
+ if (type === "otp") {
1894
+ return err(
1895
+ HaiIamError.STRATEGY_NOT_SUPPORTED,
1896
+ iamM("iam_otpStrategyRequired")
1897
+ );
1898
+ }
1899
+ if (type === "ldap") {
1900
+ return err(
1901
+ HaiIamError.STRATEGY_NOT_SUPPORTED,
1902
+ iamM("iam_ldapStrategyRequired")
1903
+ );
1904
+ }
1905
+ return err(
1906
+ HaiIamError.STRATEGY_NOT_SUPPORTED,
1907
+ iamM("iam_featureNotImplemented")
1908
+ );
1909
+ }
1910
+ async function buildAuthResult(user) {
1911
+ const [roleCodesResult, permCodesResult] = await Promise.all([
1912
+ resolveUserRoleCodes(user.id),
1913
+ resolveUserPermissionCodes(user.id)
1914
+ ]);
1915
+ if (!roleCodesResult.success) {
1916
+ return roleCodesResult;
1917
+ }
1918
+ if (!permCodesResult.success) {
1919
+ return permCodesResult;
1920
+ }
1921
+ const sessionResult = await sessionFunctions.create({
1922
+ userId: user.id,
1923
+ username: user.username,
1924
+ displayName: user.displayName,
1925
+ avatarUrl: user.avatarUrl,
1926
+ roles: roleCodesResult.data,
1927
+ permissions: permCodesResult.data
1928
+ });
1929
+ if (!sessionResult.success) {
1930
+ return sessionResult;
1931
+ }
1932
+ const session = sessionResult.data;
1933
+ const tokenPair = session.data?._tokenPair;
1934
+ if (!tokenPair) {
1935
+ return err(HaiIamError.SESSION_CREATE_FAILED, iamM("iam_createSessionFailed"));
1936
+ }
1937
+ return ok({
1938
+ user,
1939
+ tokens: tokenPair,
1940
+ roles: roleCodesResult.data,
1941
+ permissions: permCodesResult.data,
1942
+ agreements: buildAgreementDisplay()
1943
+ });
1944
+ }
1945
+ async function loginWithStrategy(type, credentials, strategy) {
1946
+ const disabled = loginDisabled(type);
1947
+ if (disabled)
1948
+ return disabled;
1949
+ const strategyResult = resolveStrategy(type, strategy);
1950
+ if (!strategyResult.success)
1951
+ return strategyResult;
1952
+ const authResult = await strategyResult.data.authenticate({
1953
+ type,
1954
+ ...credentials
1955
+ });
1956
+ if (!authResult.success) {
1957
+ logger5.warn("Login failed", { type, reason: authResult.error.code });
1958
+ return authResult;
1959
+ }
1960
+ const result = await buildAuthResult(authResult.data);
1961
+ if (result.success) {
1962
+ logger5.info("Login succeeded", { type, userId: authResult.data.id });
1963
+ }
1964
+ return result;
1965
+ }
1966
+ return {
1967
+ async login(credentials) {
1968
+ return loginWithStrategy("password", credentials, passwordStrategy);
1969
+ },
1970
+ async loginWithOtp(credentials) {
1971
+ return loginWithStrategy("otp", credentials, otpStrategy);
1972
+ },
1973
+ async loginWithLdap(credentials) {
1974
+ return loginWithStrategy("ldap", credentials, ldapStrategy);
1975
+ },
1976
+ async loginWithApiKey(credentials) {
1977
+ return loginWithStrategy("apikey", credentials, apiKeyStrategy);
1978
+ },
1979
+ async logout(accessToken) {
1980
+ const sessionResult = await sessionFunctions.get(accessToken);
1981
+ if (sessionResult.success && sessionResult.data) {
1982
+ const tokenPair = sessionResult.data.data?._tokenPair;
1983
+ if (tokenPair?.refreshToken) {
1984
+ await sessionFunctions.revokeRefresh(tokenPair.refreshToken);
1985
+ }
1986
+ await sessionFunctions.delete(sessionResult.data.accessToken);
1987
+ logger5.info("User logged out", { userId: sessionResult.data.userId });
1988
+ }
1989
+ return ok(void 0);
1990
+ },
1991
+ async verifyToken(accessToken) {
1992
+ return sessionFunctions.verifyToken(accessToken);
1993
+ },
1994
+ async sendOtp(identifier) {
1995
+ const disabled = loginDisabled("otp");
1996
+ if (disabled) {
1997
+ return disabled;
1998
+ }
1999
+ if (!otpChallenge) {
2000
+ return err(
2001
+ HaiIamError.STRATEGY_NOT_SUPPORTED,
2002
+ iamM("iam_otpStrategyRequiredForSend")
2003
+ );
2004
+ }
2005
+ return otpChallenge(identifier);
2006
+ }
2007
+ };
2008
+ }
2009
+ var TABLE_NAME3 = "hai_iam_permissions";
2010
+ var PERMISSION_FIELDS = [
2011
+ {
2012
+ fieldName: "id",
2013
+ columnName: "id",
2014
+ def: { type: "TEXT", primaryKey: true },
2015
+ select: true,
2016
+ create: true,
2017
+ update: false
2018
+ },
2019
+ {
2020
+ fieldName: "code",
2021
+ columnName: "code",
2022
+ def: { type: "TEXT", notNull: true, unique: true },
2023
+ select: true,
2024
+ create: true,
2025
+ update: true
2026
+ },
2027
+ {
2028
+ fieldName: "name",
2029
+ columnName: "name",
2030
+ def: { type: "TEXT", notNull: true },
2031
+ select: true,
2032
+ create: true,
2033
+ update: true
2034
+ },
2035
+ {
2036
+ fieldName: "description",
2037
+ columnName: "description",
2038
+ def: { type: "TEXT" },
2039
+ select: true,
2040
+ create: true,
2041
+ update: true
2042
+ },
2043
+ {
2044
+ fieldName: "resource",
2045
+ columnName: "resource",
2046
+ def: { type: "TEXT" },
2047
+ select: true,
2048
+ create: true,
2049
+ update: true
2050
+ },
2051
+ {
2052
+ fieldName: "action",
2053
+ columnName: "action",
2054
+ def: { type: "TEXT" },
2055
+ select: true,
2056
+ create: true,
2057
+ update: true
2058
+ },
2059
+ {
2060
+ fieldName: "type",
2061
+ columnName: "type",
2062
+ def: { type: "TEXT" },
2063
+ select: true,
2064
+ create: true,
2065
+ update: true
2066
+ },
2067
+ {
2068
+ fieldName: "createdAt",
2069
+ columnName: "created_at",
2070
+ def: { type: "TIMESTAMP", notNull: true },
2071
+ select: true,
2072
+ create: true,
2073
+ update: false
2074
+ },
2075
+ {
2076
+ fieldName: "updatedAt",
2077
+ columnName: "updated_at",
2078
+ def: { type: "TIMESTAMP", notNull: true },
2079
+ select: true,
2080
+ create: true,
2081
+ update: false
2082
+ }
2083
+ ];
2084
+ var permRepoInstance = null;
2085
+ var permRepoDbConfig = null;
2086
+ function resetPermissionRepoSingleton() {
2087
+ permRepoInstance = null;
2088
+ permRepoDbConfig = null;
2089
+ }
2090
+ async function createDbPermissionRepository() {
2091
+ if (permRepoInstance && permRepoDbConfig === reldb.config)
2092
+ return permRepoInstance;
2093
+ const repo = new DbPermissionRepository();
2094
+ await repo.count();
2095
+ permRepoInstance = repo;
2096
+ permRepoDbConfig = reldb.config;
2097
+ return repo;
2098
+ }
2099
+ var DbPermissionRepository = class extends BaseReldbCrudRepository {
2100
+ constructor() {
2101
+ super(reldb, {
2102
+ table: TABLE_NAME3,
2103
+ fields: PERMISSION_FIELDS
2104
+ });
2105
+ }
2106
+ /** 根据权限代码查找权限 */
2107
+ async findByCode(code, tx) {
2108
+ return this.findOneBy("code = ?", [code], tx);
2109
+ }
2110
+ buildQueryError(error, cause) {
2111
+ return err(
2112
+ HaiIamError.REPOSITORY_ERROR,
2113
+ iamM("iam_queryPermissionFailed", { params: { message: error.message } }),
2114
+ cause
2115
+ );
2116
+ }
2117
+ async findOneBy(where, params, tx) {
2118
+ const result = await this.findAll({ where, params, limit: 1 }, tx);
2119
+ if (!result.success) {
2120
+ return this.buildQueryError(result.error, result.error);
2121
+ }
2122
+ return ok(result.data[0] ?? null);
2123
+ }
2124
+ };
2125
+ var ROLE_PERMISSION_TABLE = "hai_iam_role_permissions";
2126
+ var PERMISSION_TABLE = "hai_iam_permissions";
2127
+ var ROLE_PERMISSION_SCHEMA = {
2128
+ role_id: { type: "TEXT", notNull: true },
2129
+ permission_id: { type: "TEXT", notNull: true }
2130
+ };
2131
+ async function createDbRolePermissionRepository() {
2132
+ async function ensureTable() {
2133
+ const result = await reldb.ddl.createTable(ROLE_PERMISSION_TABLE, ROLE_PERMISSION_SCHEMA, true);
2134
+ if (!result.success) {
2135
+ return err(
2136
+ HaiIamError.REPOSITORY_ERROR,
2137
+ iamM("iam_createRolePermissionTableFailed", { params: { message: result.error.message } }),
2138
+ result.error
2139
+ );
2140
+ }
2141
+ const indexResults = await Promise.all([
2142
+ reldb.ddl.createIndex(ROLE_PERMISSION_TABLE, "idx_role_perm_role_perm", { columns: ["role_id", "permission_id"], unique: true }),
2143
+ reldb.ddl.createIndex(ROLE_PERMISSION_TABLE, "idx_role_perm_role", { columns: ["role_id"] })
2144
+ ]);
2145
+ for (const indexResult of indexResults) {
2146
+ if (!indexResult.success) {
2147
+ return err(
2148
+ HaiIamError.REPOSITORY_ERROR,
2149
+ iamM("iam_createRolePermissionIndexFailed", { params: { message: indexResult.error.message } }),
2150
+ indexResult.error
2151
+ );
2152
+ }
2153
+ }
2154
+ return ok(void 0);
2155
+ }
2156
+ const initResult = await ensureTable();
2157
+ if (!initResult.success) {
2158
+ return err(
2159
+ HaiIamError.REPOSITORY_ERROR,
2160
+ initResult.error.message,
2161
+ initResult.error
2162
+ );
2163
+ }
2164
+ async function getPermissionIdsInternal(roleId, tx) {
2165
+ const runner = tx ?? reldb.sql;
2166
+ const result = await runner.query(
2167
+ `SELECT permission_id FROM ${ROLE_PERMISSION_TABLE} WHERE role_id = ?`,
2168
+ [roleId]
2169
+ );
2170
+ if (!result.success) {
2171
+ return err(
2172
+ HaiIamError.REPOSITORY_ERROR,
2173
+ iamM("iam_queryPermissionFailed", { params: { message: result.error.message } }),
2174
+ result.error
2175
+ );
2176
+ }
2177
+ return ok(result.data.map((r) => r.permission_id));
2178
+ }
2179
+ return ok({
2180
+ async assign(roleId, permissionId, tx) {
2181
+ const runner = tx ?? reldb.sql;
2182
+ const result = await runner.execute(
2183
+ `INSERT INTO ${ROLE_PERMISSION_TABLE} (role_id, permission_id) VALUES (?, ?) ON CONFLICT DO NOTHING`,
2184
+ [roleId, permissionId]
2185
+ );
2186
+ if (!result.success) {
2187
+ return err(
2188
+ HaiIamError.REPOSITORY_ERROR,
2189
+ iamM("iam_assignPermissionFailed", { params: { message: result.error.message } }),
2190
+ result.error
2191
+ );
2192
+ }
2193
+ return ok(void 0);
2194
+ },
2195
+ async remove(roleId, permissionId, tx) {
2196
+ const runner = tx ?? reldb.sql;
2197
+ const result = await runner.execute(
2198
+ `DELETE FROM ${ROLE_PERMISSION_TABLE} WHERE role_id = ? AND permission_id = ?`,
2199
+ [roleId, permissionId]
2200
+ );
2201
+ if (!result.success) {
2202
+ return err(
2203
+ HaiIamError.REPOSITORY_ERROR,
2204
+ iamM("iam_removePermissionFailed", { params: { message: result.error.message } }),
2205
+ result.error
2206
+ );
2207
+ }
2208
+ return ok(void 0);
2209
+ },
2210
+ async getPermissions(roleId, tx) {
2211
+ const idsResult = await getPermissionIdsInternal(roleId, tx);
2212
+ if (!idsResult.success)
2213
+ return idsResult;
2214
+ if (idsResult.data.length === 0) {
2215
+ return ok([]);
2216
+ }
2217
+ const placeholders = idsResult.data.map(() => "?").join(", ");
2218
+ const result = await reldb.sql.query(
2219
+ `SELECT * FROM ${PERMISSION_TABLE} WHERE id IN (${placeholders})`,
2220
+ idsResult.data
2221
+ );
2222
+ if (!result.success) {
2223
+ return err(
2224
+ HaiIamError.REPOSITORY_ERROR,
2225
+ iamM("iam_queryPermissionFailed", { params: { message: result.error.message } }),
2226
+ result.error
2227
+ );
2228
+ }
2229
+ return ok(result.data);
2230
+ },
2231
+ async getPermissionCodesForRoles(roleIds) {
2232
+ if (roleIds.length === 0) {
2233
+ return ok([]);
2234
+ }
2235
+ const placeholders = roleIds.map(() => "?").join(", ");
2236
+ const result = await reldb.sql.query(
2237
+ `SELECT DISTINCT p.code FROM ${ROLE_PERMISSION_TABLE} rp JOIN ${PERMISSION_TABLE} p ON rp.permission_id = p.id WHERE rp.role_id IN (${placeholders})`,
2238
+ roleIds
2239
+ );
2240
+ if (!result.success) {
2241
+ return err(
2242
+ HaiIamError.REPOSITORY_ERROR,
2243
+ iamM("iam_queryPermissionFailed", { params: { message: result.error.message } }),
2244
+ result.error
2245
+ );
2246
+ }
2247
+ return ok(result.data.map((r) => r.code));
2248
+ },
2249
+ async removeByRoleId(roleId, tx) {
2250
+ const runner = tx ?? reldb.sql;
2251
+ const result = await runner.execute(
2252
+ `DELETE FROM ${ROLE_PERMISSION_TABLE} WHERE role_id = ?`,
2253
+ [roleId]
2254
+ );
2255
+ if (!result.success) {
2256
+ return err(
2257
+ HaiIamError.REPOSITORY_ERROR,
2258
+ iamM("iam_removePermissionFailed", { params: { message: result.error.message } }),
2259
+ result.error
2260
+ );
2261
+ }
2262
+ return ok(void 0);
2263
+ },
2264
+ async removeByPermissionId(permissionId, tx) {
2265
+ const runner = tx ?? reldb.sql;
2266
+ const result = await runner.execute(
2267
+ `DELETE FROM ${ROLE_PERMISSION_TABLE} WHERE permission_id = ?`,
2268
+ [permissionId]
2269
+ );
2270
+ if (!result.success) {
2271
+ return err(
2272
+ HaiIamError.REPOSITORY_ERROR,
2273
+ iamM("iam_removePermissionFailed", { params: { message: result.error.message } }),
2274
+ result.error
2275
+ );
2276
+ }
2277
+ return ok(void 0);
2278
+ },
2279
+ async getRoleIdsByPermissionId(permissionId) {
2280
+ const result = await reldb.sql.query(
2281
+ `SELECT role_id FROM ${ROLE_PERMISSION_TABLE} WHERE permission_id = ?`,
2282
+ [permissionId]
2283
+ );
2284
+ if (!result.success) {
2285
+ return err(
2286
+ HaiIamError.REPOSITORY_ERROR,
2287
+ iamM("iam_queryPermissionFailed", { params: { message: result.error.message } }),
2288
+ result.error
2289
+ );
2290
+ }
2291
+ return ok(result.data.map((r) => r.role_id));
2292
+ },
2293
+ async getPermissionsForRoles(roleIds) {
2294
+ const result = /* @__PURE__ */ new Map();
2295
+ if (roleIds.length === 0) {
2296
+ return ok(result);
2297
+ }
2298
+ const placeholders = roleIds.map(() => "?").join(", ");
2299
+ const relationsResult = await reldb.sql.query(
2300
+ `SELECT role_id, permission_id FROM ${ROLE_PERMISSION_TABLE} WHERE role_id IN (${placeholders})`,
2301
+ roleIds
2302
+ );
2303
+ if (!relationsResult.success) {
2304
+ return err(
2305
+ HaiIamError.REPOSITORY_ERROR,
2306
+ iamM("iam_queryPermissionFailed", { params: { message: relationsResult.error.message } }),
2307
+ relationsResult.error
2308
+ );
2309
+ }
2310
+ for (const rid of roleIds) {
2311
+ result.set(rid, []);
2312
+ }
2313
+ if (relationsResult.data.length === 0) {
2314
+ return ok(result);
2315
+ }
2316
+ const uniquePermIds = [...new Set(relationsResult.data.map((r) => r.permission_id))];
2317
+ const permPlaceholders = uniquePermIds.map(() => "?").join(", ");
2318
+ const permsResult = await reldb.sql.query(
2319
+ `SELECT * FROM ${PERMISSION_TABLE} WHERE id IN (${permPlaceholders})`,
2320
+ uniquePermIds
2321
+ );
2322
+ if (!permsResult.success) {
2323
+ return err(
2324
+ HaiIamError.REPOSITORY_ERROR,
2325
+ iamM("iam_queryPermissionFailed", { params: { message: permsResult.error.message } }),
2326
+ permsResult.error
2327
+ );
2328
+ }
2329
+ const permMap = /* @__PURE__ */ new Map();
2330
+ for (const perm of permsResult.data) {
2331
+ permMap.set(perm.id, perm);
2332
+ }
2333
+ for (const rel of relationsResult.data) {
2334
+ const perm = permMap.get(rel.permission_id);
2335
+ if (perm) {
2336
+ result.get(rel.role_id).push(perm);
2337
+ }
2338
+ }
2339
+ return ok(result);
2340
+ }
2341
+ });
2342
+ }
2343
+ var USER_ROLE_TABLE = "hai_iam_user_roles";
2344
+ var USER_ROLE_SCHEMA = {
2345
+ user_id: { type: "TEXT", notNull: true },
2346
+ role_id: { type: "TEXT", notNull: true }
2347
+ };
2348
+ async function createDbUserRoleRepository(roleRepository) {
2349
+ async function ensureTable() {
2350
+ const result = await reldb.ddl.createTable(USER_ROLE_TABLE, USER_ROLE_SCHEMA, true);
2351
+ if (!result.success) {
2352
+ return err(
2353
+ HaiIamError.REPOSITORY_ERROR,
2354
+ iamM("iam_createUserRoleTableFailed", { params: { message: result.error.message } }),
2355
+ result.error
2356
+ );
2357
+ }
2358
+ const indexResults = await Promise.all([
2359
+ reldb.ddl.createIndex(USER_ROLE_TABLE, "idx_user_role_user_role", { columns: ["user_id", "role_id"], unique: true }),
2360
+ reldb.ddl.createIndex(USER_ROLE_TABLE, "idx_user_role_user", { columns: ["user_id"] })
2361
+ ]);
2362
+ for (const indexResult of indexResults) {
2363
+ if (!indexResult.success) {
2364
+ return err(
2365
+ HaiIamError.REPOSITORY_ERROR,
2366
+ iamM("iam_createUserRoleIndexFailed", { params: { message: indexResult.error.message } }),
2367
+ indexResult.error
2368
+ );
2369
+ }
2370
+ }
2371
+ return ok(void 0);
2372
+ }
2373
+ const initResult = await ensureTable();
2374
+ if (!initResult.success) {
2375
+ return err(
2376
+ HaiIamError.REPOSITORY_ERROR,
2377
+ initResult.error.message,
2378
+ initResult.error
2379
+ );
2380
+ }
2381
+ async function getRoleIdsInternal(userId, tx) {
2382
+ const runner = tx ?? reldb.sql;
2383
+ const result = await runner.query(
2384
+ `SELECT role_id FROM ${USER_ROLE_TABLE} WHERE user_id = ?`,
2385
+ [userId]
2386
+ );
2387
+ if (!result.success) {
2388
+ return err(
2389
+ HaiIamError.REPOSITORY_ERROR,
2390
+ iamM("iam_queryRoleFailed", { params: { message: result.error.message } }),
2391
+ result.error
2392
+ );
2393
+ }
2394
+ return ok(result.data.map((r) => r.role_id));
2395
+ }
2396
+ return ok({
2397
+ async assign(userId, roleId, tx) {
2398
+ const runner = tx ?? reldb.sql;
2399
+ const result = await runner.execute(
2400
+ `INSERT INTO ${USER_ROLE_TABLE} (user_id, role_id) VALUES (?, ?) ON CONFLICT DO NOTHING`,
2401
+ [userId, roleId]
2402
+ );
2403
+ if (!result.success) {
2404
+ return err(
2405
+ HaiIamError.REPOSITORY_ERROR,
2406
+ iamM("iam_assignRoleFailed", { params: { message: result.error.message } }),
2407
+ result.error
2408
+ );
2409
+ }
2410
+ return ok(void 0);
2411
+ },
2412
+ async remove(userId, roleId, tx) {
2413
+ const runner = tx ?? reldb.sql;
2414
+ const result = await runner.execute(
2415
+ `DELETE FROM ${USER_ROLE_TABLE} WHERE user_id = ? AND role_id = ?`,
2416
+ [userId, roleId]
2417
+ );
2418
+ if (!result.success) {
2419
+ return err(
2420
+ HaiIamError.REPOSITORY_ERROR,
2421
+ iamM("iam_removeRoleFailed", { params: { message: result.error.message } }),
2422
+ result.error
2423
+ );
2424
+ }
2425
+ return ok(void 0);
2426
+ },
2427
+ async getRoleIds(userId, tx) {
2428
+ return getRoleIdsInternal(userId, tx);
2429
+ },
2430
+ async getRoles(userId, tx) {
2431
+ const idsResult = await getRoleIdsInternal(userId, tx);
2432
+ if (!idsResult.success)
2433
+ return idsResult;
2434
+ if (idsResult.data.length === 0) {
2435
+ return ok([]);
2436
+ }
2437
+ const placeholders = idsResult.data.map(() => "?").join(", ");
2438
+ const roleResult = await roleRepository.findAll({
2439
+ where: `id IN (${placeholders})`,
2440
+ params: idsResult.data
2441
+ }, tx);
2442
+ if (!roleResult.success) {
2443
+ return err(
2444
+ HaiIamError.REPOSITORY_ERROR,
2445
+ iamM("iam_queryRoleFailed", { params: { message: roleResult.error.message } }),
2446
+ roleResult.error
2447
+ );
2448
+ }
2449
+ return ok(roleResult.data);
2450
+ },
2451
+ async removeByRoleId(roleId, tx) {
2452
+ const runner = tx ?? reldb.sql;
2453
+ const usersResult = await runner.query(
2454
+ `SELECT user_id FROM ${USER_ROLE_TABLE} WHERE role_id = ?`,
2455
+ [roleId]
2456
+ );
2457
+ if (!usersResult.success) {
2458
+ return err(
2459
+ HaiIamError.REPOSITORY_ERROR,
2460
+ iamM("iam_queryRoleFailed", { params: { message: usersResult.error.message } }),
2461
+ usersResult.error
2462
+ );
2463
+ }
2464
+ const deleteResult = await runner.execute(
2465
+ `DELETE FROM ${USER_ROLE_TABLE} WHERE role_id = ?`,
2466
+ [roleId]
2467
+ );
2468
+ if (!deleteResult.success) {
2469
+ return err(
2470
+ HaiIamError.REPOSITORY_ERROR,
2471
+ iamM("iam_removeRoleFailed", { params: { message: deleteResult.error.message } }),
2472
+ deleteResult.error
2473
+ );
2474
+ }
2475
+ return ok(usersResult.data.map((r) => r.user_id));
2476
+ },
2477
+ async getUserIdsByRoleId(roleId) {
2478
+ const result = await reldb.sql.query(
2479
+ `SELECT user_id FROM ${USER_ROLE_TABLE} WHERE role_id = ?`,
2480
+ [roleId]
2481
+ );
2482
+ if (!result.success) {
2483
+ return err(
2484
+ HaiIamError.REPOSITORY_ERROR,
2485
+ iamM("iam_queryRoleFailed", { params: { message: result.error.message } }),
2486
+ result.error
2487
+ );
2488
+ }
2489
+ return ok(result.data.map((r) => r.user_id));
2490
+ },
2491
+ async getRolesForUsers(userIds) {
2492
+ const result = /* @__PURE__ */ new Map();
2493
+ if (userIds.length === 0) {
2494
+ return ok(result);
2495
+ }
2496
+ const placeholders = userIds.map(() => "?").join(", ");
2497
+ const relationsResult = await reldb.sql.query(
2498
+ `SELECT user_id, role_id FROM ${USER_ROLE_TABLE} WHERE user_id IN (${placeholders})`,
2499
+ userIds
2500
+ );
2501
+ if (!relationsResult.success) {
2502
+ return err(
2503
+ HaiIamError.REPOSITORY_ERROR,
2504
+ iamM("iam_queryRoleFailed", { params: { message: relationsResult.error.message } }),
2505
+ relationsResult.error
2506
+ );
2507
+ }
2508
+ for (const uid of userIds) {
2509
+ result.set(uid, []);
2510
+ }
2511
+ if (relationsResult.data.length === 0) {
2512
+ return ok(result);
2513
+ }
2514
+ const uniqueRoleIds = [...new Set(relationsResult.data.map((r) => r.role_id))];
2515
+ const rolePlaceholders = uniqueRoleIds.map(() => "?").join(", ");
2516
+ const rolesResult = await roleRepository.findAll({
2517
+ where: `id IN (${rolePlaceholders})`,
2518
+ params: uniqueRoleIds
2519
+ });
2520
+ if (!rolesResult.success) {
2521
+ return err(
2522
+ HaiIamError.REPOSITORY_ERROR,
2523
+ iamM("iam_queryRoleFailed", { params: { message: rolesResult.error.message } }),
2524
+ rolesResult.error
2525
+ );
2526
+ }
2527
+ const roleMap = /* @__PURE__ */ new Map();
2528
+ for (const role of rolesResult.data) {
2529
+ roleMap.set(role.id, role);
2530
+ }
2531
+ for (const rel of relationsResult.data) {
2532
+ const role = roleMap.get(rel.role_id);
2533
+ if (role) {
2534
+ result.get(rel.user_id).push(role);
2535
+ }
2536
+ }
2537
+ return ok(result);
2538
+ }
2539
+ });
2540
+ }
2541
+ var TABLE_NAME4 = "hai_iam_roles";
2542
+ var ROLE_FIELDS = [
2543
+ {
2544
+ fieldName: "id",
2545
+ columnName: "id",
2546
+ def: { type: "TEXT", primaryKey: true },
2547
+ select: true,
2548
+ create: true,
2549
+ update: false
2550
+ },
2551
+ {
2552
+ fieldName: "code",
2553
+ columnName: "code",
2554
+ def: { type: "TEXT", notNull: true, unique: true },
2555
+ select: true,
2556
+ create: true,
2557
+ update: true
2558
+ },
2559
+ {
2560
+ fieldName: "name",
2561
+ columnName: "name",
2562
+ def: { type: "TEXT", notNull: true },
2563
+ select: true,
2564
+ create: true,
2565
+ update: true
2566
+ },
2567
+ {
2568
+ fieldName: "description",
2569
+ columnName: "description",
2570
+ def: { type: "TEXT" },
2571
+ select: true,
2572
+ create: true,
2573
+ update: true
2574
+ },
2575
+ {
2576
+ fieldName: "isSystem",
2577
+ columnName: "is_system",
2578
+ def: { type: "BOOLEAN", defaultValue: 0 },
2579
+ select: true,
2580
+ create: true,
2581
+ update: true
2582
+ },
2583
+ {
2584
+ fieldName: "createdAt",
2585
+ columnName: "created_at",
2586
+ def: { type: "TIMESTAMP", notNull: true },
2587
+ select: true,
2588
+ create: true,
2589
+ update: false
2590
+ },
2591
+ {
2592
+ fieldName: "updatedAt",
2593
+ columnName: "updated_at",
2594
+ def: { type: "TIMESTAMP", notNull: true },
2595
+ select: true,
2596
+ create: true,
2597
+ update: false
2598
+ }
2599
+ ];
2600
+ var roleRepoInstance = null;
2601
+ var roleRepoDbConfig = null;
2602
+ function resetRoleRepoSingleton() {
2603
+ roleRepoInstance = null;
2604
+ roleRepoDbConfig = null;
2605
+ }
2606
+ async function createDbRoleRepository() {
2607
+ if (roleRepoInstance && roleRepoDbConfig === reldb.config)
2608
+ return roleRepoInstance;
2609
+ const repo = new DbRoleRepository();
2610
+ await repo.count();
2611
+ roleRepoInstance = repo;
2612
+ roleRepoDbConfig = reldb.config;
2613
+ return repo;
2614
+ }
2615
+ var DbRoleRepository = class extends BaseReldbCrudRepository {
2616
+ constructor() {
2617
+ super(reldb, {
2618
+ table: TABLE_NAME4,
2619
+ fields: ROLE_FIELDS
2620
+ });
2621
+ }
2622
+ /** 根据角色代码查找角色 */
2623
+ async findByCode(code, tx) {
2624
+ return this.findOneBy("code = ?", [code], tx);
2625
+ }
2626
+ buildQueryError(error, cause) {
2627
+ return err(
2628
+ HaiIamError.REPOSITORY_ERROR,
2629
+ iamM("iam_queryRoleFailed", { params: { message: error.message } }),
2630
+ cause
2631
+ );
2632
+ }
2633
+ async findOneBy(where, params, tx) {
2634
+ const result = await this.findAll({ where, params, limit: 1 }, tx);
2635
+ if (!result.success) {
2636
+ return this.buildQueryError(result.error, result.error);
2637
+ }
2638
+ return ok(result.data[0] ?? null);
2639
+ }
2640
+ };
2641
+
2642
+ // src/authz/iam-authz-functions.ts
2643
+ var logger6 = core.logger.child({ module: "iam", scope: "authz" });
2644
+ async function createAuthzOperations(deps) {
2645
+ try {
2646
+ const { config, session } = deps;
2647
+ const roleRepository = await createDbRoleRepository();
2648
+ const permissionRepository = await createDbPermissionRepository();
2649
+ const rolePermResult = await createDbRolePermissionRepository();
2650
+ if (!rolePermResult.success) {
2651
+ return rolePermResult;
2652
+ }
2653
+ const userRoleResult = await createDbUserRoleRepository(roleRepository);
2654
+ if (!userRoleResult.success) {
2655
+ return userRoleResult;
2656
+ }
2657
+ const manager = createRbacManager({
2658
+ rbacConfig: config.rbac,
2659
+ roleRepository,
2660
+ permissionRepository,
2661
+ rolePermissionRepository: rolePermResult.data,
2662
+ userRoleRepository: userRoleResult.data,
2663
+ session
2664
+ });
2665
+ logger6.info("Authz sub-feature initialized");
2666
+ return ok(manager);
2667
+ } catch (error) {
2668
+ logger6.error("Authz sub-feature initialization failed", { error });
2669
+ return err(
2670
+ HaiIamError.CONFIG_ERROR,
2671
+ iamM("iam_initComponentFailed"),
2672
+ error
2673
+ );
2674
+ }
2675
+ }
2676
+ function createRbacManager(config) {
2677
+ const rbacConfig = config.rbacConfig ? RbacConfigSchema.parse(config.rbacConfig) : RbacConfigSchema.parse({});
2678
+ const {
2679
+ roleRepository,
2680
+ permissionRepository,
2681
+ rolePermissionRepository,
2682
+ userRoleRepository,
2683
+ session
2684
+ } = config;
2685
+ let superAdminRoleId;
2686
+ async function resolveSuperAdminRoleId() {
2687
+ if (superAdminRoleId !== void 0) {
2688
+ return ok(superAdminRoleId);
2689
+ }
2690
+ const roleResult = await roleRepository.findByCode(rbacConfig.superAdminRole);
2691
+ if (!roleResult.success) {
2692
+ return mapRepositoryError2("iam_queryRoleFailed", roleResult.error.message);
2693
+ }
2694
+ superAdminRoleId = roleResult.data?.id ?? null;
2695
+ return ok(superAdminRoleId);
2696
+ }
2697
+ function mapRepositoryError2(messageKey, message) {
2698
+ return err(
2699
+ HaiIamError.REPOSITORY_ERROR,
2700
+ iamM(messageKey, { params: { message } })
2701
+ );
2702
+ }
2703
+ function matchesPermission(permission, code) {
2704
+ if (code === permission) {
2705
+ return true;
2706
+ }
2707
+ if (code.endsWith(":*")) {
2708
+ const prefix = code.slice(0, -1);
2709
+ return permission.startsWith(prefix);
2710
+ }
2711
+ return false;
2712
+ }
2713
+ async function hasPermissionInRoles(roleIds, permission) {
2714
+ const codesResult = await rolePermissionRepository.getPermissionCodesForRoles(roleIds);
2715
+ if (!codesResult.success)
2716
+ return codesResult;
2717
+ for (const code of codesResult.data) {
2718
+ if (matchesPermission(permission, code))
2719
+ return ok(true);
2720
+ }
2721
+ return ok(false);
2722
+ }
2723
+ async function getUserPermissionsInternal(userId) {
2724
+ const roleIdsResult = await userRoleRepository.getRoleIds(userId);
2725
+ if (!roleIdsResult.success)
2726
+ return roleIdsResult;
2727
+ if (roleIdsResult.data.length === 0)
2728
+ return ok([]);
2729
+ const permMapResult = await rolePermissionRepository.getPermissionsForRoles(roleIdsResult.data);
2730
+ if (!permMapResult.success)
2731
+ return permMapResult;
2732
+ const permissions = [];
2733
+ const seen = /* @__PURE__ */ new Set();
2734
+ for (const perms of permMapResult.data.values()) {
2735
+ for (const perm of perms) {
2736
+ if (!seen.has(perm.id)) {
2737
+ permissions.push(perm);
2738
+ seen.add(perm.id);
2739
+ }
2740
+ }
2741
+ }
2742
+ return ok(permissions);
2743
+ }
2744
+ async function resolveUserPermissionCodes(userId) {
2745
+ const roleIdsResult = await userRoleRepository.getRoleIds(userId);
2746
+ if (!roleIdsResult.success) {
2747
+ return roleIdsResult;
2748
+ }
2749
+ if (roleIdsResult.data.length === 0) {
2750
+ return ok([]);
2751
+ }
2752
+ return rolePermissionRepository.getPermissionCodesForRoles(roleIdsResult.data);
2753
+ }
2754
+ async function resolveUserRoleCodes(userId) {
2755
+ const rolesResult = await userRoleRepository.getRoles(userId);
2756
+ if (!rolesResult.success) {
2757
+ return rolesResult;
2758
+ }
2759
+ return ok(rolesResult.data.map((r) => r.code));
2760
+ }
2761
+ async function syncSessionPermissionsForRole(roleId) {
2762
+ const userIdsResult = await userRoleRepository.getUserIdsByRoleId(roleId);
2763
+ if (!userIdsResult.success) {
2764
+ logger6.error("Failed to query users for permission sync", { roleId, error: userIdsResult.error.message });
2765
+ return;
2766
+ }
2767
+ await Promise.allSettled(
2768
+ userIdsResult.data.map(async (userId) => {
2769
+ const permResult = await resolveUserPermissionCodes(userId);
2770
+ if (!permResult.success) {
2771
+ logger6.error("Failed to resolve permissions for session sync", { userId, roleId, error: permResult.error.message });
2772
+ return;
2773
+ }
2774
+ const syncResult = await session.patchUserSessions(userId, { permissions: permResult.data });
2775
+ if (!syncResult.success) {
2776
+ logger6.error("Failed to sync session permissions", { userId, roleId, error: syncResult.error.message });
2777
+ }
2778
+ })
2779
+ );
2780
+ }
2781
+ async function syncUserSessionAfterRoleChange(userId) {
2782
+ const [roleCodesResult, permCodesResult] = await Promise.all([
2783
+ resolveUserRoleCodes(userId),
2784
+ resolveUserPermissionCodes(userId)
2785
+ ]);
2786
+ const updates = {};
2787
+ if (roleCodesResult.success) {
2788
+ updates.roles = roleCodesResult.data;
2789
+ } else {
2790
+ logger6.error("Failed to resolve roles for session sync", { userId, error: roleCodesResult.error.message });
2791
+ }
2792
+ if (permCodesResult.success) {
2793
+ updates.permissions = permCodesResult.data;
2794
+ } else {
2795
+ logger6.error("Failed to resolve permissions for session sync", { userId, error: permCodesResult.error.message });
2796
+ }
2797
+ if (updates.roles !== void 0 || updates.permissions !== void 0) {
2798
+ const syncResult = await session.patchUserSessions(userId, updates);
2799
+ if (!syncResult.success) {
2800
+ logger6.error("Failed to patch user sessions", { userId, error: syncResult.error.message });
2801
+ }
2802
+ }
2803
+ }
2804
+ return {
2805
+ async checkPermission(userId, permission) {
2806
+ if (!rbacConfig.enabled) {
2807
+ return ok(true);
2808
+ }
2809
+ const roleIdsResult = await userRoleRepository.getRoleIds(userId);
2810
+ if (!roleIdsResult.success)
2811
+ return roleIdsResult;
2812
+ const roleIds = roleIdsResult.data;
2813
+ if (roleIds.length === 0) {
2814
+ return ok(false);
2815
+ }
2816
+ const superAdminResult = await resolveSuperAdminRoleId();
2817
+ if (!superAdminResult.success)
2818
+ return superAdminResult;
2819
+ if (superAdminResult.data && roleIds.includes(superAdminResult.data)) {
2820
+ return ok(true);
2821
+ }
2822
+ return hasPermissionInRoles(roleIds, permission);
2823
+ },
2824
+ async getUserPermissions(userId) {
2825
+ return getUserPermissionsInternal(userId);
2826
+ },
2827
+ async getUserRoles(userId) {
2828
+ return userRoleRepository.getRoles(userId);
2829
+ },
2830
+ async assignRole(userId, roleId, tx) {
2831
+ const roleExistsResult = await roleRepository.existsById(roleId, tx);
2832
+ if (!roleExistsResult.success) {
2833
+ return mapRepositoryError2("iam_queryRoleFailed", roleExistsResult.error.message);
2834
+ }
2835
+ if (!roleExistsResult.data) {
2836
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
2837
+ }
2838
+ const result = await userRoleRepository.assign(userId, roleId, tx);
2839
+ if (result.success) {
2840
+ logger6.info("Role assigned to user", { userId, roleId });
2841
+ void audit.log({ action: "role.assign", resource: "iam_user_role", resourceId: userId, details: { roleId } });
2842
+ if (!tx) {
2843
+ await syncUserSessionAfterRoleChange(userId);
2844
+ }
2845
+ }
2846
+ return result;
2847
+ },
2848
+ async removeRole(userId, roleId, tx) {
2849
+ const result = await userRoleRepository.remove(userId, roleId, tx);
2850
+ if (result.success) {
2851
+ logger6.info("Role removed from user", { userId, roleId });
2852
+ void audit.log({ action: "role.remove", resource: "iam_user_role", resourceId: userId, details: { roleId } });
2853
+ if (!tx) {
2854
+ await syncUserSessionAfterRoleChange(userId);
2855
+ }
2856
+ }
2857
+ return result;
2858
+ },
2859
+ async syncRoles(userId, roleIds, tx) {
2860
+ const currentResult = await userRoleRepository.getRoles(userId, tx);
2861
+ if (!currentResult.success) {
2862
+ return currentResult;
2863
+ }
2864
+ const currentIds = new Set(currentResult.data.map((r) => r.id));
2865
+ const targetIds = new Set(roleIds);
2866
+ const toRemove = [...currentIds].filter((id) => !targetIds.has(id));
2867
+ const toAdd = [...targetIds].filter((id) => !currentIds.has(id));
2868
+ if (toRemove.length === 0 && toAdd.length === 0) {
2869
+ return ok(void 0);
2870
+ }
2871
+ for (const roleId of toAdd) {
2872
+ const existsResult = await roleRepository.existsById(roleId, tx);
2873
+ if (!existsResult.success) {
2874
+ return mapRepositoryError2("iam_queryRoleFailed", existsResult.error.message);
2875
+ }
2876
+ if (!existsResult.data) {
2877
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
2878
+ }
2879
+ }
2880
+ const ownTx = !tx;
2881
+ if (!tx) {
2882
+ const txResult = await reldb.tx.begin();
2883
+ if (!txResult.success) {
2884
+ return mapRepositoryError2("iam_syncRolesFailed", txResult.error.message);
2885
+ }
2886
+ tx = txResult.data;
2887
+ }
2888
+ try {
2889
+ for (const roleId of toRemove) {
2890
+ const result = await userRoleRepository.remove(userId, roleId, tx);
2891
+ if (!result.success) {
2892
+ if (ownTx)
2893
+ await tx.rollback();
2894
+ return result;
2895
+ }
2896
+ }
2897
+ for (const roleId of toAdd) {
2898
+ const result = await userRoleRepository.assign(userId, roleId, tx);
2899
+ if (!result.success) {
2900
+ if (ownTx)
2901
+ await tx.rollback();
2902
+ return result;
2903
+ }
2904
+ }
2905
+ if (ownTx) {
2906
+ const commitResult = await tx.commit();
2907
+ if (!commitResult.success) {
2908
+ return mapRepositoryError2("iam_syncRolesFailed", commitResult.error.message);
2909
+ }
2910
+ }
2911
+ } catch (error) {
2912
+ if (ownTx)
2913
+ await tx.rollback();
2914
+ return err(
2915
+ HaiIamError.REPOSITORY_ERROR,
2916
+ iamM("iam_syncRolesFailed", { params: { message: String(error) } }),
2917
+ error
2918
+ );
2919
+ }
2920
+ logger6.info("Roles synced for user", { userId, added: toAdd.length, removed: toRemove.length });
2921
+ void audit.log({ action: "roles.sync", resource: "iam_user_role", resourceId: userId, details: { added: toAdd, removed: toRemove } });
2922
+ if (ownTx) {
2923
+ await syncUserSessionAfterRoleChange(userId);
2924
+ }
2925
+ return ok(void 0);
2926
+ },
2927
+ // ─── 角色管理 ───
2928
+ async createRole(role, tx) {
2929
+ const ownTx = !tx;
2930
+ if (!tx) {
2931
+ const txResult = await reldb.tx.begin();
2932
+ if (!txResult.success) {
2933
+ return mapRepositoryError2("iam_createRoleFailed", txResult.error.message);
2934
+ }
2935
+ tx = txResult.data;
2936
+ }
2937
+ try {
2938
+ const createResult = await roleRepository.create(role, tx);
2939
+ if (!createResult.success) {
2940
+ if (ownTx)
2941
+ await tx.rollback();
2942
+ const msg = createResult.error.message.toLowerCase();
2943
+ if (msg.includes("unique") || msg.includes("duplicate")) {
2944
+ return err(HaiIamError.ROLE_ALREADY_EXISTS, iamM("iam_roleAlreadyExist"));
2945
+ }
2946
+ return mapRepositoryError2("iam_createRoleFailed", createResult.error.message);
2947
+ }
2948
+ const createdResult = await roleRepository.findByCode(role.code, tx);
2949
+ if (!createdResult.success) {
2950
+ if (ownTx)
2951
+ await tx.rollback();
2952
+ return mapRepositoryError2("iam_queryRoleFailed", createdResult.error.message);
2953
+ }
2954
+ if (!createdResult.data) {
2955
+ if (ownTx)
2956
+ await tx.rollback();
2957
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
2958
+ }
2959
+ if (ownTx) {
2960
+ const commitResult = await tx.commit();
2961
+ if (!commitResult.success) {
2962
+ return mapRepositoryError2("iam_createRoleFailed", commitResult.error.message);
2963
+ }
2964
+ }
2965
+ logger6.info("Role created", { roleId: createdResult.data.id, code: role.code });
2966
+ void audit.helper.crud({ action: "create", resource: "iam_role", resourceId: createdResult.data.id, details: { code: role.code } });
2967
+ if (role.code === rbacConfig.superAdminRole) {
2968
+ superAdminRoleId = void 0;
2969
+ }
2970
+ return ok(createdResult.data);
2971
+ } catch (error) {
2972
+ if (ownTx)
2973
+ await tx.rollback();
2974
+ const msg = String(error).toLowerCase();
2975
+ if (msg.includes("unique") || msg.includes("duplicate")) {
2976
+ return err(HaiIamError.ROLE_ALREADY_EXISTS, iamM("iam_roleAlreadyExist"));
2977
+ }
2978
+ return err(
2979
+ HaiIamError.REPOSITORY_ERROR,
2980
+ iamM("iam_createRoleFailed", { params: { message: String(error) } }),
2981
+ error
2982
+ );
2983
+ }
2984
+ },
2985
+ async getRole(roleId) {
2986
+ const result = await roleRepository.findById(roleId);
2987
+ if (!result.success) {
2988
+ return mapRepositoryError2("iam_queryRoleFailed", result.error.message);
2989
+ }
2990
+ return ok(result.data);
2991
+ },
2992
+ async getRoleByCode(code) {
2993
+ const result = await roleRepository.findByCode(code);
2994
+ if (!result.success) {
2995
+ return mapRepositoryError2("iam_queryRoleFailed", result.error.message);
2996
+ }
2997
+ return ok(result.data);
2998
+ },
2999
+ async getAllRoles(options) {
3000
+ const result = await roleRepository.findPage({
3001
+ orderBy: "created_at DESC",
3002
+ pagination: options
3003
+ });
3004
+ if (!result.success) {
3005
+ return mapRepositoryError2("iam_queryRoleListFailed", result.error.message);
3006
+ }
3007
+ return ok(result.data);
3008
+ },
3009
+ async updateRole(roleId, data, tx) {
3010
+ const ownTx = !tx;
3011
+ if (!tx) {
3012
+ const txResult = await reldb.tx.begin();
3013
+ if (!txResult.success) {
3014
+ return mapRepositoryError2("iam_updateRoleFailed", txResult.error.message);
3015
+ }
3016
+ tx = txResult.data;
3017
+ }
3018
+ try {
3019
+ const updateResult = await roleRepository.updateById(roleId, data, tx);
3020
+ if (!updateResult.success) {
3021
+ if (ownTx)
3022
+ await tx.rollback();
3023
+ return mapRepositoryError2("iam_updateRoleFailed", updateResult.error.message);
3024
+ }
3025
+ if (updateResult.data.changes === 0) {
3026
+ if (ownTx)
3027
+ await tx.rollback();
3028
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3029
+ }
3030
+ const updatedResult = await roleRepository.findById(roleId, tx);
3031
+ if (!updatedResult.success) {
3032
+ if (ownTx)
3033
+ await tx.rollback();
3034
+ return mapRepositoryError2("iam_queryRoleFailed", updatedResult.error.message);
3035
+ }
3036
+ if (!updatedResult.data) {
3037
+ if (ownTx)
3038
+ await tx.rollback();
3039
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3040
+ }
3041
+ if (ownTx) {
3042
+ const commitResult = await tx.commit();
3043
+ if (!commitResult.success) {
3044
+ return mapRepositoryError2("iam_updateRoleFailed", commitResult.error.message);
3045
+ }
3046
+ }
3047
+ superAdminRoleId = void 0;
3048
+ if (ownTx) {
3049
+ const affectedUsersResult = await userRoleRepository.getUserIdsByRoleId(roleId);
3050
+ if (affectedUsersResult.success) {
3051
+ for (const userId of affectedUsersResult.data) {
3052
+ await syncUserSessionAfterRoleChange(userId);
3053
+ }
3054
+ } else {
3055
+ logger6.error("Failed to query affected users after updateRole", { roleId, error: affectedUsersResult.error.message });
3056
+ }
3057
+ }
3058
+ void audit.helper.crud({ action: "update", resource: "iam_role", resourceId: roleId, details: data });
3059
+ return ok(updatedResult.data);
3060
+ } catch (error) {
3061
+ if (ownTx)
3062
+ await tx.rollback();
3063
+ return err(
3064
+ HaiIamError.REPOSITORY_ERROR,
3065
+ iamM("iam_updateRoleFailed", { params: { message: String(error) } }),
3066
+ error
3067
+ );
3068
+ }
3069
+ },
3070
+ async deleteRole(roleId, tx) {
3071
+ const roleResult = await roleRepository.findById(roleId, tx);
3072
+ if (!roleResult.success) {
3073
+ return mapRepositoryError2("iam_queryRoleFailed", roleResult.error.message);
3074
+ }
3075
+ if (!roleResult.data) {
3076
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3077
+ }
3078
+ if (roleResult.data.isSystem) {
3079
+ return err(HaiIamError.PERMISSION_DENIED, iamM("iam_cannotDeleteSystemRole"));
3080
+ }
3081
+ const ownTx = !tx;
3082
+ if (!tx) {
3083
+ const txResult = await reldb.tx.begin();
3084
+ if (!txResult.success) {
3085
+ return mapRepositoryError2("iam_deleteRoleFailed", txResult.error.message);
3086
+ }
3087
+ tx = txResult.data;
3088
+ }
3089
+ let affectedUserIds = [];
3090
+ try {
3091
+ const userIdsResult = await userRoleRepository.removeByRoleId(roleId, tx);
3092
+ if (!userIdsResult.success) {
3093
+ if (ownTx)
3094
+ await tx.rollback();
3095
+ return userIdsResult;
3096
+ }
3097
+ affectedUserIds = userIdsResult.data;
3098
+ const rpResult = await rolePermissionRepository.removeByRoleId(roleId, tx);
3099
+ if (!rpResult.success) {
3100
+ if (ownTx)
3101
+ await tx.rollback();
3102
+ return rpResult;
3103
+ }
3104
+ const delResult = await roleRepository.deleteById(roleId, tx);
3105
+ if (!delResult.success) {
3106
+ if (ownTx)
3107
+ await tx.rollback();
3108
+ return mapRepositoryError2("iam_deleteRoleFailed", delResult.error.message);
3109
+ }
3110
+ if (ownTx) {
3111
+ const commitResult = await tx.commit();
3112
+ if (!commitResult.success) {
3113
+ return mapRepositoryError2("iam_deleteRoleFailed", commitResult.error.message);
3114
+ }
3115
+ }
3116
+ } catch (error) {
3117
+ if (ownTx)
3118
+ await tx.rollback();
3119
+ return err(
3120
+ HaiIamError.REPOSITORY_ERROR,
3121
+ iamM("iam_deleteRoleFailed", { params: { message: String(error) } }),
3122
+ error
3123
+ );
3124
+ }
3125
+ if (ownTx) {
3126
+ for (const userId of affectedUserIds) {
3127
+ await syncUserSessionAfterRoleChange(userId);
3128
+ }
3129
+ }
3130
+ superAdminRoleId = void 0;
3131
+ logger6.info("Role deleted", { roleId });
3132
+ void audit.helper.crud({ action: "delete", resource: "iam_role", resourceId: roleId });
3133
+ return ok(void 0);
3134
+ },
3135
+ // ─── 权限管理 ───
3136
+ async createPermission(permission, tx) {
3137
+ const ownTx = !tx;
3138
+ if (!tx) {
3139
+ const txResult = await reldb.tx.begin();
3140
+ if (!txResult.success) {
3141
+ return mapRepositoryError2("iam_createPermissionFailed", txResult.error.message);
3142
+ }
3143
+ tx = txResult.data;
3144
+ }
3145
+ try {
3146
+ const createResult = await permissionRepository.create(permission, tx);
3147
+ if (!createResult.success) {
3148
+ if (ownTx)
3149
+ await tx.rollback();
3150
+ const msg = createResult.error.message.toLowerCase();
3151
+ if (msg.includes("unique") || msg.includes("duplicate")) {
3152
+ return err(HaiIamError.PERMISSION_ALREADY_EXISTS, iamM("iam_permissionAlreadyExist"));
3153
+ }
3154
+ return mapRepositoryError2("iam_createPermissionFailed", createResult.error.message);
3155
+ }
3156
+ const createdResult = await permissionRepository.findByCode(permission.code, tx);
3157
+ if (!createdResult.success) {
3158
+ if (ownTx)
3159
+ await tx.rollback();
3160
+ return mapRepositoryError2("iam_queryPermissionFailed", createdResult.error.message);
3161
+ }
3162
+ if (!createdResult.data) {
3163
+ if (ownTx)
3164
+ await tx.rollback();
3165
+ return err(HaiIamError.PERMISSION_NOT_FOUND, iamM("iam_permissionNotExist"));
3166
+ }
3167
+ if (ownTx) {
3168
+ const commitResult = await tx.commit();
3169
+ if (!commitResult.success) {
3170
+ return mapRepositoryError2("iam_createPermissionFailed", commitResult.error.message);
3171
+ }
3172
+ }
3173
+ logger6.info("Permission created", { permissionId: createdResult.data.id, code: permission.code });
3174
+ void audit.helper.crud({ action: "create", resource: "iam_permission", resourceId: createdResult.data.id, details: { code: permission.code } });
3175
+ return ok(createdResult.data);
3176
+ } catch (error) {
3177
+ if (ownTx)
3178
+ await tx.rollback();
3179
+ const msg = String(error).toLowerCase();
3180
+ if (msg.includes("unique") || msg.includes("duplicate")) {
3181
+ return err(HaiIamError.PERMISSION_ALREADY_EXISTS, iamM("iam_permissionAlreadyExist"));
3182
+ }
3183
+ return err(
3184
+ HaiIamError.REPOSITORY_ERROR,
3185
+ iamM("iam_createPermissionFailed", { params: { message: String(error) } }),
3186
+ error
3187
+ );
3188
+ }
3189
+ },
3190
+ async getPermission(permissionId) {
3191
+ const result = await permissionRepository.findById(permissionId);
3192
+ if (!result.success) {
3193
+ return mapRepositoryError2("iam_queryPermissionFailed", result.error.message);
3194
+ }
3195
+ return ok(result.data);
3196
+ },
3197
+ async getPermissionByCode(code) {
3198
+ const result = await permissionRepository.findByCode(code);
3199
+ if (!result.success) {
3200
+ return mapRepositoryError2("iam_queryPermissionFailed", result.error.message);
3201
+ }
3202
+ return ok(result.data);
3203
+ },
3204
+ async getAllPermissions(options) {
3205
+ const whereClauses = [];
3206
+ const whereParams = [];
3207
+ if (options?.type) {
3208
+ whereClauses.push("type = ?");
3209
+ whereParams.push(options.type);
3210
+ }
3211
+ if (options?.search) {
3212
+ whereClauses.push("(code LIKE ? OR name LIKE ?)");
3213
+ const escaped = options.search.replace(/[%_\\]/g, "\\$&");
3214
+ const pattern = `%${escaped}%`;
3215
+ whereParams.push(pattern, pattern);
3216
+ }
3217
+ const result = await permissionRepository.findPage({
3218
+ where: whereClauses.length > 0 ? whereClauses.join(" AND ") : void 0,
3219
+ params: whereParams.length > 0 ? whereParams : void 0,
3220
+ orderBy: "created_at DESC",
3221
+ pagination: options
3222
+ });
3223
+ if (!result.success) {
3224
+ return mapRepositoryError2("iam_queryPermissionListFailed", result.error.message);
3225
+ }
3226
+ return ok(result.data);
3227
+ },
3228
+ async deletePermission(permissionId, tx) {
3229
+ const permissionResult = await permissionRepository.findById(permissionId, tx);
3230
+ if (!permissionResult.success) {
3231
+ return mapRepositoryError2("iam_queryPermissionFailed", permissionResult.error.message);
3232
+ }
3233
+ if (!permissionResult.data) {
3234
+ return err(
3235
+ HaiIamError.PERMISSION_NOT_FOUND,
3236
+ iamM("iam_permissionNotExist")
3237
+ );
3238
+ }
3239
+ const affectedRoleIdsResult = await rolePermissionRepository.getRoleIdsByPermissionId(permissionId);
3240
+ const affectedRoleIds = affectedRoleIdsResult.success ? affectedRoleIdsResult.data : [];
3241
+ const ownTx = !tx;
3242
+ if (!tx) {
3243
+ const txResult = await reldb.tx.begin();
3244
+ if (!txResult.success) {
3245
+ return mapRepositoryError2("iam_deletePermissionFailed", txResult.error.message);
3246
+ }
3247
+ tx = txResult.data;
3248
+ }
3249
+ try {
3250
+ const cascadeResult = await rolePermissionRepository.removeByPermissionId(permissionId, tx);
3251
+ if (!cascadeResult.success) {
3252
+ if (ownTx)
3253
+ await tx.rollback();
3254
+ return cascadeResult;
3255
+ }
3256
+ const delResult = await permissionRepository.deleteById(permissionId, tx);
3257
+ if (!delResult.success) {
3258
+ if (ownTx)
3259
+ await tx.rollback();
3260
+ return mapRepositoryError2("iam_deletePermissionFailed", delResult.error.message);
3261
+ }
3262
+ if (ownTx) {
3263
+ const commitResult = await tx.commit();
3264
+ if (!commitResult.success) {
3265
+ return mapRepositoryError2("iam_deletePermissionFailed", commitResult.error.message);
3266
+ }
3267
+ }
3268
+ } catch (error) {
3269
+ if (ownTx)
3270
+ await tx.rollback();
3271
+ return err(
3272
+ HaiIamError.REPOSITORY_ERROR,
3273
+ iamM("iam_deletePermissionFailed", { params: { message: String(error) } }),
3274
+ error
3275
+ );
3276
+ }
3277
+ if (ownTx) {
3278
+ for (const roleId of affectedRoleIds) {
3279
+ await syncSessionPermissionsForRole(roleId);
3280
+ }
3281
+ }
3282
+ logger6.info("Permission deleted", { permissionId });
3283
+ void audit.helper.crud({ action: "delete", resource: "iam_permission", resourceId: permissionId });
3284
+ return ok(void 0);
3285
+ },
3286
+ async assignPermissionToRole(roleId, permissionId, tx) {
3287
+ const [roleResult, permResult] = await Promise.all([
3288
+ roleRepository.existsById(roleId, tx),
3289
+ permissionRepository.findById(permissionId, tx)
3290
+ ]);
3291
+ if (!roleResult.success) {
3292
+ return mapRepositoryError2("iam_queryRoleFailed", roleResult.error.message);
3293
+ }
3294
+ if (!roleResult.data) {
3295
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3296
+ }
3297
+ if (!permResult.success) {
3298
+ return mapRepositoryError2("iam_queryPermissionFailed", permResult.error.message);
3299
+ }
3300
+ if (!permResult.data) {
3301
+ return err(HaiIamError.PERMISSION_NOT_FOUND, iamM("iam_permissionNotExist"));
3302
+ }
3303
+ const assignResult = await rolePermissionRepository.assign(roleId, permissionId, tx);
3304
+ if (assignResult.success) {
3305
+ logger6.info("Permission assigned to role", { roleId, permissionId });
3306
+ void audit.log({ action: "permission.assign", resource: "iam_role_permission", resourceId: roleId, details: { permissionId } });
3307
+ if (!tx) {
3308
+ await syncSessionPermissionsForRole(roleId);
3309
+ }
3310
+ }
3311
+ return assignResult;
3312
+ },
3313
+ async removePermissionFromRole(roleId, permissionId, tx) {
3314
+ const [roleResult, permResult] = await Promise.all([
3315
+ roleRepository.existsById(roleId, tx),
3316
+ permissionRepository.findById(permissionId, tx)
3317
+ ]);
3318
+ if (!roleResult.success) {
3319
+ return mapRepositoryError2("iam_queryRoleFailed", roleResult.error.message);
3320
+ }
3321
+ if (!roleResult.data) {
3322
+ return err(HaiIamError.ROLE_NOT_FOUND, iamM("iam_roleNotExist"));
3323
+ }
3324
+ if (!permResult.success) {
3325
+ return mapRepositoryError2("iam_queryPermissionFailed", permResult.error.message);
3326
+ }
3327
+ if (!permResult.data) {
3328
+ return err(
3329
+ HaiIamError.PERMISSION_NOT_FOUND,
3330
+ iamM("iam_permissionNotExist")
3331
+ );
3332
+ }
3333
+ const removeResult = await rolePermissionRepository.remove(roleId, permissionId, tx);
3334
+ if (removeResult.success) {
3335
+ logger6.info("Permission removed from role", { roleId, permissionId });
3336
+ void audit.log({ action: "permission.remove", resource: "iam_role_permission", resourceId: roleId, details: { permissionId } });
3337
+ if (!tx) {
3338
+ await syncSessionPermissionsForRole(roleId);
3339
+ }
3340
+ }
3341
+ return removeResult;
3342
+ },
3343
+ async getRolePermissions(roleId) {
3344
+ return rolePermissionRepository.getPermissions(roleId);
3345
+ },
3346
+ async getUserRolesForMany(userIds) {
3347
+ return userRoleRepository.getRolesForUsers(userIds);
3348
+ },
3349
+ async getRolePermissionsForMany(roleIds) {
3350
+ return rolePermissionRepository.getPermissionsForRoles(roleIds);
3351
+ }
3352
+ };
3353
+ }
3354
+ var logger7 = core.logger.child({ module: "iam", scope: "seed" });
3355
+ var DEFAULT_ROLES = [
3356
+ { code: "admin", name: () => iamM("iam_seedRoleAdminName"), description: () => iamM("iam_seedRoleAdminDesc"), isSystem: true },
3357
+ { code: "user", name: () => iamM("iam_seedRoleUserName"), description: () => iamM("iam_seedRoleUserDesc"), isSystem: true },
3358
+ { code: "guest", name: () => iamM("iam_seedRoleGuestName"), description: () => iamM("iam_seedRoleGuestDesc"), isSystem: true }
3359
+ ];
3360
+ var DEFAULT_PERMISSIONS = [
3361
+ // ─── 菜单权限 ───
3362
+ { code: "dashboard:view", name: () => iamM("iam_seedPermDashboardView"), type: "menu", resource: "dashboard", action: "view" },
3363
+ { code: "user:read", name: () => iamM("iam_seedPermUserRead"), type: "menu", resource: "user", action: "read" },
3364
+ { code: "role:read", name: () => iamM("iam_seedPermRoleRead"), type: "menu", resource: "role", action: "read" },
3365
+ { code: "permission:read", name: () => iamM("iam_seedPermPermRead"), type: "menu", resource: "permission", action: "read" },
3366
+ { code: "system:logs", name: () => iamM("iam_seedPermSystemLogs"), type: "menu", resource: "system", action: "logs" },
3367
+ { code: "system:settings", name: () => iamM("iam_seedPermSystemSettings"), type: "menu", resource: "system", action: "settings" },
3368
+ { code: "system:modules", name: () => iamM("iam_seedPermSystemModules"), type: "menu", resource: "system", action: "modules" },
3369
+ { code: "profile:read", name: () => iamM("iam_seedPermProfileRead"), type: "menu", resource: "profile", action: "read" },
3370
+ // ─── API 权限 ───
3371
+ { code: "user:list", name: () => iamM("iam_seedPermUserList"), type: "api", resource: "user", action: "list" },
3372
+ { code: "user:api:create", name: () => iamM("iam_seedPermUserApiCreate"), type: "api", resource: "user", action: "api:create" },
3373
+ { code: "user:api:update", name: () => iamM("iam_seedPermUserApiUpdate"), type: "api", resource: "user", action: "api:update" },
3374
+ { code: "user:api:delete", name: () => iamM("iam_seedPermUserApiDelete"), type: "api", resource: "user", action: "api:delete" },
3375
+ { code: "role:list", name: () => iamM("iam_seedPermRoleList"), type: "api", resource: "role", action: "list" },
3376
+ { code: "role:api:create", name: () => iamM("iam_seedPermRoleApiCreate"), type: "api", resource: "role", action: "api:create" },
3377
+ { code: "role:api:update", name: () => iamM("iam_seedPermRoleApiUpdate"), type: "api", resource: "role", action: "api:update" },
3378
+ { code: "role:api:delete", name: () => iamM("iam_seedPermRoleApiDelete"), type: "api", resource: "role", action: "api:delete" },
3379
+ { code: "permission:list", name: () => iamM("iam_seedPermPermList"), type: "api", resource: "permission", action: "list" },
3380
+ { code: "permission:manage", name: () => iamM("iam_seedPermPermManage"), type: "api", resource: "permission", action: "manage" },
3381
+ { code: "permission:api:create", name: () => iamM("iam_seedPermPermApiCreate"), type: "api", resource: "permission", action: "api:create" },
3382
+ { code: "permission:api:delete", name: () => iamM("iam_seedPermPermApiDelete"), type: "api", resource: "permission", action: "api:delete" },
3383
+ { code: "audit:read", name: () => iamM("iam_seedPermAuditRead"), type: "api", resource: "audit", action: "read" },
3384
+ // ─── 按钮权限 ───
3385
+ { code: "user:create", name: () => iamM("iam_seedPermUserCreate"), type: "button", resource: "user", action: "create" },
3386
+ { code: "user:update", name: () => iamM("iam_seedPermUserUpdate"), type: "button", resource: "user", action: "update" },
3387
+ { code: "user:delete", name: () => iamM("iam_seedPermUserDelete"), type: "button", resource: "user", action: "delete" },
3388
+ { code: "role:create", name: () => iamM("iam_seedPermRoleCreate"), type: "button", resource: "role", action: "create" },
3389
+ { code: "role:update", name: () => iamM("iam_seedPermRoleUpdate"), type: "button", resource: "role", action: "update" },
3390
+ { code: "role:delete", name: () => iamM("iam_seedPermRoleDelete"), type: "button", resource: "role", action: "delete" },
3391
+ { code: "permission:create", name: () => iamM("iam_seedPermPermCreate"), type: "button", resource: "permission", action: "create" },
3392
+ { code: "permission:delete", name: () => iamM("iam_seedPermPermDelete"), type: "button", resource: "permission", action: "delete" }
3393
+ ];
3394
+ var USER_ROLE_PERMISSIONS = ["dashboard:view", "profile:read"];
3395
+ async function seedIamData(authz) {
3396
+ try {
3397
+ const existingRoles = await authz.getAllRoles({ page: 1, pageSize: 1e3 });
3398
+ const existingRoleMap = /* @__PURE__ */ new Map();
3399
+ if (existingRoles.success) {
3400
+ for (const role of existingRoles.data.items) {
3401
+ existingRoleMap.set(role.code, role.id);
3402
+ }
3403
+ }
3404
+ const roleMap = /* @__PURE__ */ new Map();
3405
+ for (const role of DEFAULT_ROLES) {
3406
+ const existingId = existingRoleMap.get(role.code);
3407
+ if (existingId) {
3408
+ roleMap.set(role.code, existingId);
3409
+ continue;
3410
+ }
3411
+ const result = await authz.createRole({ code: role.code, name: role.name(), description: role.description(), isSystem: role.isSystem });
3412
+ if (result.success) {
3413
+ roleMap.set(role.code, result.data.id);
3414
+ } else {
3415
+ return result;
3416
+ }
3417
+ }
3418
+ const existingPerms = await authz.getAllPermissions({ page: 1, pageSize: 1e3 });
3419
+ const existingPermMap = /* @__PURE__ */ new Map();
3420
+ if (existingPerms.success) {
3421
+ for (const perm of existingPerms.data.items) {
3422
+ existingPermMap.set(perm.code, perm.id);
3423
+ }
3424
+ }
3425
+ const permMap = /* @__PURE__ */ new Map();
3426
+ for (const perm of DEFAULT_PERMISSIONS) {
3427
+ const existingId = existingPermMap.get(perm.code);
3428
+ if (existingId) {
3429
+ permMap.set(perm.code, existingId);
3430
+ continue;
3431
+ }
3432
+ const result = await authz.createPermission({ code: perm.code, name: perm.name(), type: perm.type, resource: perm.resource, action: perm.action });
3433
+ if (result.success) {
3434
+ permMap.set(perm.code, result.data.id);
3435
+ } else {
3436
+ return result;
3437
+ }
3438
+ }
3439
+ const adminRoleId = roleMap.get("admin");
3440
+ if (adminRoleId) {
3441
+ for (const [, permId] of permMap) {
3442
+ await authz.assignPermissionToRole(adminRoleId, permId);
3443
+ }
3444
+ }
3445
+ const userRoleId = roleMap.get("user");
3446
+ if (userRoleId) {
3447
+ for (const permCode of USER_ROLE_PERMISSIONS) {
3448
+ const permId = permMap.get(permCode);
3449
+ if (permId) {
3450
+ await authz.assignPermissionToRole(userRoleId, permId);
3451
+ }
3452
+ }
3453
+ }
3454
+ logger7.info("IAM seed data initialized");
3455
+ return ok(void 0);
3456
+ } catch (error) {
3457
+ logger7.error("Failed to seed IAM data", { error });
3458
+ return err(
3459
+ HaiIamError.REPOSITORY_ERROR,
3460
+ iamM("iam_initSeedDataFailed"),
3461
+ error
3462
+ );
3463
+ }
3464
+ }
3465
+
3466
+ // src/session/iam-session-utils.ts
3467
+ function generateToken() {
3468
+ const bytes = new Uint8Array(32);
3469
+ crypto.getRandomValues(bytes);
3470
+ const base64 = btoa(String.fromCharCode(...bytes));
3471
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3472
+ }
3473
+ function buildSession(options, now, sessionTtl, accessToken) {
3474
+ return {
3475
+ userId: options.userId,
3476
+ username: options.username,
3477
+ displayName: options.displayName,
3478
+ avatarUrl: options.avatarUrl,
3479
+ roles: options.roles ?? [],
3480
+ permissions: options.permissions ?? [],
3481
+ source: options.source,
3482
+ accessToken,
3483
+ createdAt: now,
3484
+ lastActiveAt: now,
3485
+ expiresAt: new Date(now.getTime() + sessionTtl * 1e3),
3486
+ data: options.data
3487
+ };
3488
+ }
3489
+ function getSessionTtl(session, now = Date.now()) {
3490
+ return Math.max(0, Math.floor((session.expiresAt.getTime() - now) / 1e3));
3491
+ }
3492
+ function applySessionPatch(session, patch) {
3493
+ const nextSession = { ...session };
3494
+ if (patch.data !== void 0) {
3495
+ nextSession.data = { ...nextSession.data, ...patch.data };
3496
+ }
3497
+ if (patch.roles !== void 0) {
3498
+ nextSession.roles = patch.roles;
3499
+ }
3500
+ if (patch.permissions !== void 0) {
3501
+ nextSession.permissions = patch.permissions;
3502
+ }
3503
+ if (patch.username !== void 0) {
3504
+ nextSession.username = patch.username;
3505
+ }
3506
+ if (patch.displayName !== void 0) {
3507
+ nextSession.displayName = patch.displayName;
3508
+ }
3509
+ if (patch.avatarUrl !== void 0) {
3510
+ nextSession.avatarUrl = patch.avatarUrl;
3511
+ }
3512
+ if (patch.source !== void 0) {
3513
+ nextSession.source = patch.source;
3514
+ }
3515
+ nextSession.lastActiveAt = /* @__PURE__ */ new Date();
3516
+ return nextSession;
3517
+ }
3518
+
3519
+ // src/session/iam-session-repository-cache.ts
3520
+ var TOKEN_KEY_PREFIX = "hai:iam:token:";
3521
+ var USER_TOKENS_KEY_PREFIX = "hai:iam:user:";
3522
+ var REFRESH_TOKEN_PREFIX = "hai:iam:refresh:";
3523
+ function buildTokenKey(token) {
3524
+ return `${TOKEN_KEY_PREFIX}${token}`;
3525
+ }
3526
+ function buildUserTokensKey(userId) {
3527
+ return `${USER_TOKENS_KEY_PREFIX}${userId}:tokens`;
3528
+ }
3529
+ function buildRefreshKey(refreshToken) {
3530
+ return `${REFRESH_TOKEN_PREFIX}${refreshToken}`;
3531
+ }
3532
+ function restoreSessionDates(session) {
3533
+ return {
3534
+ ...session,
3535
+ createdAt: session.createdAt instanceof Date ? session.createdAt : new Date(session.createdAt),
3536
+ lastActiveAt: session.lastActiveAt instanceof Date ? session.lastActiveAt : new Date(session.lastActiveAt),
3537
+ expiresAt: session.expiresAt instanceof Date ? session.expiresAt : new Date(session.expiresAt)
3538
+ };
3539
+ }
3540
+ function createCacheSessionRepository(sessionMaxAge, refreshTokenMaxAge) {
3541
+ const repo = {
3542
+ async save(session, tokenPair) {
3543
+ const accessToken = session.accessToken;
3544
+ const userId = session.userId;
3545
+ const ttl = getSessionTtl(session);
3546
+ const setResult = await cache.kv.set(buildTokenKey(accessToken), session, { ex: ttl });
3547
+ if (!setResult.success) {
3548
+ return err(
3549
+ HaiIamError.REPOSITORY_ERROR,
3550
+ iamM("iam_saveSessionMappingCacheFailed", { params: { message: setResult.error.message } }),
3551
+ setResult.error
3552
+ );
3553
+ }
3554
+ const saddResult = await cache.set_.sadd(buildUserTokensKey(userId), accessToken);
3555
+ if (!saddResult.success) {
3556
+ return err(
3557
+ HaiIamError.REPOSITORY_ERROR,
3558
+ iamM("iam_saveUserSessionCacheFailed", { params: { message: saddResult.error.message } }),
3559
+ saddResult.error
3560
+ );
3561
+ }
3562
+ await cache.kv.expire(buildUserTokensKey(userId), sessionMaxAge * 2);
3563
+ const refreshResult = await cache.kv.set(
3564
+ buildRefreshKey(tokenPair.refreshToken),
3565
+ { userId, accessToken },
3566
+ { ex: refreshTokenMaxAge }
3567
+ );
3568
+ if (!refreshResult.success) {
3569
+ return err(
3570
+ HaiIamError.REPOSITORY_ERROR,
3571
+ iamM("iam_saveSessionMappingCacheFailed", { params: { message: refreshResult.error.message } }),
3572
+ refreshResult.error
3573
+ );
3574
+ }
3575
+ return ok(void 0);
3576
+ },
3577
+ async getByAccessToken(accessToken) {
3578
+ const result = await cache.kv.get(buildTokenKey(accessToken));
3579
+ if (!result.success) {
3580
+ return err(
3581
+ HaiIamError.REPOSITORY_ERROR,
3582
+ iamM("iam_querySessionMappingCacheFailed", { params: { message: result.error.message } }),
3583
+ result.error
3584
+ );
3585
+ }
3586
+ if (!result.data) {
3587
+ return ok(null);
3588
+ }
3589
+ return ok(restoreSessionDates(result.data));
3590
+ },
3591
+ async updateByAccessToken(accessToken, data) {
3592
+ const sessionResult = await repo.getByAccessToken(accessToken);
3593
+ if (!sessionResult.success) {
3594
+ return sessionResult;
3595
+ }
3596
+ if (!sessionResult.data) {
3597
+ return err(
3598
+ HaiIamError.SESSION_NOT_FOUND,
3599
+ iamM("iam_sessionNotExist")
3600
+ );
3601
+ }
3602
+ const nextSession = applySessionPatch(sessionResult.data, data);
3603
+ const ttl = getSessionTtl(nextSession);
3604
+ const setResult = await cache.kv.set(buildTokenKey(accessToken), nextSession, { ex: ttl });
3605
+ if (!setResult.success) {
3606
+ return err(
3607
+ HaiIamError.REPOSITORY_ERROR,
3608
+ iamM("iam_saveSessionMappingCacheFailed", { params: { message: setResult.error.message } }),
3609
+ setResult.error
3610
+ );
3611
+ }
3612
+ return ok(void 0);
3613
+ },
3614
+ async removeByAccessToken(accessToken) {
3615
+ const sessionResult = await cache.kv.get(buildTokenKey(accessToken));
3616
+ if (sessionResult.success && sessionResult.data) {
3617
+ const session = sessionResult.data;
3618
+ await cache.set_.srem(buildUserTokensKey(session.userId), accessToken);
3619
+ const tokenPair = session.data?._tokenPair;
3620
+ if (tokenPair?.refreshToken) {
3621
+ await cache.kv.del(buildRefreshKey(tokenPair.refreshToken));
3622
+ }
3623
+ }
3624
+ await cache.kv.del(buildTokenKey(accessToken));
3625
+ return ok(void 0);
3626
+ },
3627
+ async removeByUserId(userId) {
3628
+ const tokensResult = await cache.set_.smembers(buildUserTokensKey(userId));
3629
+ if (tokensResult.success) {
3630
+ for (const token of tokensResult.data) {
3631
+ await repo.removeByAccessToken(token);
3632
+ }
3633
+ }
3634
+ await cache.kv.del(buildUserTokensKey(userId));
3635
+ return ok(void 0);
3636
+ },
3637
+ async getByRefreshToken(refreshToken) {
3638
+ const mappingResult = await cache.kv.get(buildRefreshKey(refreshToken));
3639
+ if (!mappingResult.success) {
3640
+ return err(
3641
+ HaiIamError.REPOSITORY_ERROR,
3642
+ iamM("iam_querySessionMappingCacheFailed", { params: { message: mappingResult.error.message } }),
3643
+ mappingResult.error
3644
+ );
3645
+ }
3646
+ if (!mappingResult.data) {
3647
+ return ok(null);
3648
+ }
3649
+ return repo.getByAccessToken(mappingResult.data.accessToken);
3650
+ },
3651
+ async removeRefreshToken(refreshToken) {
3652
+ const result = await cache.kv.del(buildRefreshKey(refreshToken));
3653
+ if (!result.success) {
3654
+ return err(
3655
+ HaiIamError.REPOSITORY_ERROR,
3656
+ iamM("iam_deleteSessionMappingCacheFailed", { params: { message: result.error.message } }),
3657
+ result.error
3658
+ );
3659
+ }
3660
+ return ok(void 0);
3661
+ },
3662
+ async patchUserSessions(userId, updates) {
3663
+ const tokensResult = await cache.set_.smembers(buildUserTokensKey(userId));
3664
+ if (!tokensResult.success) {
3665
+ return err(
3666
+ HaiIamError.REPOSITORY_ERROR,
3667
+ iamM("iam_queryUserSessionCacheFailed", { params: { message: tokensResult.error.message } }),
3668
+ tokensResult.error
3669
+ );
3670
+ }
3671
+ const staleTokens = [];
3672
+ for (const token of tokensResult.data) {
3673
+ const sessionKey = buildTokenKey(token);
3674
+ const sessionResult = await cache.kv.get(sessionKey);
3675
+ if (!sessionResult.success || !sessionResult.data) {
3676
+ staleTokens.push(token);
3677
+ continue;
3678
+ }
3679
+ const ttlResult = await cache.kv.ttl(sessionKey);
3680
+ if (!ttlResult.success || ttlResult.data <= 0) {
3681
+ staleTokens.push(token);
3682
+ continue;
3683
+ }
3684
+ const updated = { ...sessionResult.data, ...updates };
3685
+ const setResult = await cache.kv.set(sessionKey, updated, { ex: ttlResult.data });
3686
+ if (!setResult.success) {
3687
+ return err(
3688
+ HaiIamError.REPOSITORY_ERROR,
3689
+ iamM("iam_saveUserSessionCacheFailed", { params: { message: setResult.error.message } }),
3690
+ setResult.error
3691
+ );
3692
+ }
3693
+ }
3694
+ if (staleTokens.length > 0) {
3695
+ await cache.set_.srem(buildUserTokensKey(userId), ...staleTokens);
3696
+ }
3697
+ return ok(void 0);
3698
+ }
3699
+ };
3700
+ return repo;
3701
+ }
3702
+
3703
+ // src/session/iam-session-functions.ts
3704
+ var logger8 = core.logger.child({ module: "iam", scope: "session" });
3705
+ async function createSessionOperations(deps) {
3706
+ try {
3707
+ const { config } = deps;
3708
+ const sessionConfig = SessionConfigSchema.parse(config.session ?? {});
3709
+ const sessionRepository = createCacheSessionRepository(
3710
+ sessionConfig.maxAge ?? 86400,
3711
+ sessionConfig.refreshTokenMaxAge ?? 604800
3712
+ );
3713
+ const functions = buildSessionFunctions({
3714
+ maxAge: sessionConfig.maxAge,
3715
+ sliding: sessionConfig.sliding,
3716
+ singleDevice: sessionConfig.singleDevice,
3717
+ sessionRepository
3718
+ });
3719
+ logger8.info("Session sub-feature initialized");
3720
+ return ok(functions);
3721
+ } catch (error) {
3722
+ logger8.error("Session sub-feature initialization failed", { error });
3723
+ return err(
3724
+ HaiIamError.CONFIG_ERROR,
3725
+ iamM("iam_initComponentFailed"),
3726
+ error
3727
+ );
3728
+ }
3729
+ }
3730
+ function buildSessionFunctions(config) {
3731
+ const maxAge = config.maxAge ?? 86400;
3732
+ const sliding = config.sliding ?? true;
3733
+ const singleDevice = config.singleDevice ?? false;
3734
+ const repo = config.sessionRepository;
3735
+ return {
3736
+ async create(options) {
3737
+ try {
3738
+ if (singleDevice) {
3739
+ const clearResult = await repo.removeByUserId(options.userId);
3740
+ if (!clearResult.success) {
3741
+ return clearResult;
3742
+ }
3743
+ }
3744
+ const accessToken = generateToken();
3745
+ const tokenPair = {
3746
+ accessToken,
3747
+ refreshToken: generateToken(),
3748
+ expiresIn: maxAge,
3749
+ tokenType: "Bearer"
3750
+ };
3751
+ const now = /* @__PURE__ */ new Date();
3752
+ const sessionTtl = options.maxAge ?? maxAge;
3753
+ const session = buildSession(options, now, sessionTtl, accessToken);
3754
+ session.data = { ...session.data, _tokenPair: tokenPair };
3755
+ const saveResult = await repo.save(session, tokenPair);
3756
+ if (!saveResult.success) {
3757
+ return saveResult;
3758
+ }
3759
+ logger8.debug("Session created", { userId: options.userId });
3760
+ return ok(session);
3761
+ } catch (error) {
3762
+ return err(
3763
+ HaiIamError.SESSION_CREATE_FAILED,
3764
+ iamM("iam_createSessionFailed"),
3765
+ error
3766
+ );
3767
+ }
3768
+ },
3769
+ async get(accessToken) {
3770
+ const sessionResult = await repo.getByAccessToken(accessToken);
3771
+ if (!sessionResult.success) {
3772
+ return sessionResult;
3773
+ }
3774
+ const session = sessionResult.data;
3775
+ if (!session) {
3776
+ return ok(null);
3777
+ }
3778
+ if (/* @__PURE__ */ new Date() > session.expiresAt) {
3779
+ await repo.removeByAccessToken(accessToken);
3780
+ return ok(null);
3781
+ }
3782
+ if (sliding) {
3783
+ const now = /* @__PURE__ */ new Date();
3784
+ await repo.updateByAccessToken(accessToken, {
3785
+ lastActiveAt: now,
3786
+ expiresAt: new Date(now.getTime() + maxAge * 1e3)
3787
+ });
3788
+ }
3789
+ return ok(session);
3790
+ },
3791
+ async verifyToken(accessToken) {
3792
+ const sessionResult = await this.get(accessToken);
3793
+ if (!sessionResult.success) {
3794
+ return sessionResult;
3795
+ }
3796
+ if (!sessionResult.data) {
3797
+ return err(
3798
+ HaiIamError.SESSION_INVALID,
3799
+ iamM("iam_sessionExpired")
3800
+ );
3801
+ }
3802
+ return ok(sessionResult.data);
3803
+ },
3804
+ async update(accessToken, data) {
3805
+ return repo.updateByAccessToken(accessToken, data);
3806
+ },
3807
+ async delete(accessToken) {
3808
+ logger8.debug("Session deleted", { accessToken });
3809
+ return repo.removeByAccessToken(accessToken);
3810
+ },
3811
+ async deleteByUserId(userId) {
3812
+ const result = await repo.removeByUserId(userId);
3813
+ if (!result.success) {
3814
+ return result;
3815
+ }
3816
+ return ok(0);
3817
+ },
3818
+ async refresh(refreshToken) {
3819
+ try {
3820
+ const oldSessionResult = await repo.getByRefreshToken(refreshToken);
3821
+ if (!oldSessionResult.success) {
3822
+ return oldSessionResult;
3823
+ }
3824
+ const oldSession = oldSessionResult.data;
3825
+ if (!oldSession) {
3826
+ return err(
3827
+ HaiIamError.TOKEN_EXPIRED,
3828
+ iamM("iam_refreshTokenExpired")
3829
+ );
3830
+ }
3831
+ await repo.removeByAccessToken(oldSession.accessToken);
3832
+ const newSessionResult = await this.create({
3833
+ userId: oldSession.userId,
3834
+ username: oldSession.username,
3835
+ displayName: oldSession.displayName,
3836
+ avatarUrl: oldSession.avatarUrl,
3837
+ roles: oldSession.roles,
3838
+ permissions: oldSession.permissions,
3839
+ source: oldSession.source,
3840
+ data: oldSession.data ? { ...oldSession.data, _tokenPair: void 0 } : void 0
3841
+ });
3842
+ if (!newSessionResult.success) {
3843
+ return newSessionResult;
3844
+ }
3845
+ const tokenPair = newSessionResult.data.data?._tokenPair;
3846
+ if (!tokenPair) {
3847
+ return err(
3848
+ HaiIamError.TOKEN_REFRESH_FAILED,
3849
+ iamM("iam_refreshTokenFailed")
3850
+ );
3851
+ }
3852
+ logger8.debug("Token refreshed", { userId: oldSession.userId });
3853
+ return ok(tokenPair);
3854
+ } catch (error) {
3855
+ return err(
3856
+ HaiIamError.TOKEN_REFRESH_FAILED,
3857
+ iamM("iam_refreshTokenFailed"),
3858
+ error
3859
+ );
3860
+ }
3861
+ },
3862
+ async revokeRefresh(refreshToken) {
3863
+ return repo.removeRefreshToken(refreshToken);
3864
+ },
3865
+ async patchUserSessions(userId, updates) {
3866
+ return repo.patchUserSessions(userId, { ...updates });
3867
+ }
3868
+ };
3869
+ }
3870
+ function hashResetToken(token) {
3871
+ const result = crypto$1.hash.hash(token);
3872
+ if (!result.success) {
3873
+ return err(
3874
+ HaiIamError.REPOSITORY_ERROR,
3875
+ iamM("iam_hashResetTokenFailed", { params: { message: result.error.message } }),
3876
+ result.error
3877
+ );
3878
+ }
3879
+ return ok(result.data);
3880
+ }
3881
+ var RESET_TOKEN_KEY_PREFIX = "hai:iam:reset:";
3882
+ var RESET_ATTEMPTS_KEY_PREFIX = "hai:iam:reset:attempts:";
3883
+ function buildResetTokenKey(hashedToken) {
3884
+ return `${RESET_TOKEN_KEY_PREFIX}${hashedToken}`;
3885
+ }
3886
+ function buildAttemptsKey(userId) {
3887
+ return `${RESET_ATTEMPTS_KEY_PREFIX}${userId}`;
3888
+ }
3889
+ var resetTokenRepoInstance = null;
3890
+ function resetResetTokenRepoSingleton() {
3891
+ resetTokenRepoInstance = null;
3892
+ }
3893
+ function createCacheResetTokenRepository() {
3894
+ if (resetTokenRepoInstance)
3895
+ return resetTokenRepoInstance;
3896
+ const repo = {
3897
+ async saveToken(token, userId, expiresAt) {
3898
+ const hashResult = hashResetToken(token);
3899
+ if (!hashResult.success) {
3900
+ return hashResult;
3901
+ }
3902
+ const hashedToken = hashResult.data;
3903
+ const ttlSeconds = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
3904
+ const setResult = await cache.kv.set(buildResetTokenKey(hashedToken), userId, { ex: ttlSeconds });
3905
+ if (!setResult.success) {
3906
+ return err(
3907
+ HaiIamError.REPOSITORY_ERROR,
3908
+ iamM("iam_saveResetTokenFailed", { params: { message: setResult.error.message } }),
3909
+ setResult.error
3910
+ );
3911
+ }
3912
+ const attemptsResult = await cache.kv.set(buildAttemptsKey(userId), 0, { ex: ttlSeconds });
3913
+ if (!attemptsResult.success) {
3914
+ return err(
3915
+ HaiIamError.REPOSITORY_ERROR,
3916
+ iamM("iam_saveResetTokenFailed", { params: { message: attemptsResult.error.message } }),
3917
+ attemptsResult.error
3918
+ );
3919
+ }
3920
+ return ok(void 0);
3921
+ },
3922
+ async tryGetUserByToken(token, maxAttempts) {
3923
+ const hashResult = hashResetToken(token);
3924
+ if (!hashResult.success) {
3925
+ return hashResult;
3926
+ }
3927
+ const hashedToken = hashResult.data;
3928
+ const tokenKey = buildResetTokenKey(hashedToken);
3929
+ const result = await cache.kv.get(tokenKey);
3930
+ if (!result.success) {
3931
+ return err(
3932
+ HaiIamError.REPOSITORY_ERROR,
3933
+ iamM("iam_queryResetTokenFailed", { params: { message: result.error.message } }),
3934
+ result.error
3935
+ );
3936
+ }
3937
+ if (!result.data) {
3938
+ return err(
3939
+ HaiIamError.RESET_TOKEN_INVALID,
3940
+ iamM("iam_resetTokenInvalid")
3941
+ );
3942
+ }
3943
+ const userId = result.data;
3944
+ const attemptsKey = buildAttemptsKey(userId);
3945
+ const incrResult = await cache.kv.incr(attemptsKey);
3946
+ const nextAttempts = incrResult.success ? incrResult.data : 1;
3947
+ if (nextAttempts > maxAttempts) {
3948
+ await cache.kv.del(tokenKey);
3949
+ await cache.kv.del(attemptsKey);
3950
+ return err(
3951
+ HaiIamError.RESET_TOKEN_MAX_ATTEMPTS,
3952
+ iamM("iam_resetTokenMaxAttempts")
3953
+ );
3954
+ }
3955
+ return ok(userId);
3956
+ },
3957
+ async removeToken(token) {
3958
+ const hashResult = hashResetToken(token);
3959
+ if (!hashResult.success) {
3960
+ return hashResult;
3961
+ }
3962
+ const hashedToken = hashResult.data;
3963
+ const tokenKey = buildResetTokenKey(hashedToken);
3964
+ const result = await cache.kv.get(tokenKey);
3965
+ if (result.success && result.data) {
3966
+ await cache.kv.del(buildAttemptsKey(result.data));
3967
+ }
3968
+ await cache.kv.del(tokenKey);
3969
+ return ok(void 0);
3970
+ }
3971
+ };
3972
+ resetTokenRepoInstance = repo;
3973
+ return repo;
3974
+ }
3975
+
3976
+ // src/user/iam-user-functions.ts
3977
+ var logger9 = core.logger.child({ module: "iam", scope: "user" });
3978
+ async function createUserOperations(deps) {
3979
+ try {
3980
+ const { config, passwordStrategyResult, sessionFunctions, authzFunctions, onPasswordResetRequest } = deps;
3981
+ const userRepository = await createDbUserRepository();
3982
+ const resetTokenRepository = createCacheResetTokenRepository();
3983
+ const functions = buildUserFunctions({
3984
+ userRepository,
3985
+ resetTokenRepository,
3986
+ passwordStrategyResult,
3987
+ sessionFunctions,
3988
+ authzFunctions,
3989
+ config,
3990
+ onPasswordResetRequest
3991
+ });
3992
+ logger9.info("User sub-feature initialized");
3993
+ return ok(functions);
3994
+ } catch (error) {
3995
+ logger9.error("User sub-feature initialization failed", { error });
3996
+ return err(
3997
+ HaiIamError.CONFIG_ERROR,
3998
+ iamM("iam_initComponentFailed"),
3999
+ error
4000
+ );
4001
+ }
4002
+ }
4003
+ function hasUpdateFields(data) {
4004
+ return Object.values(data).some((value) => value !== void 0);
4005
+ }
4006
+ function mapRepositoryError(messageKey, message) {
4007
+ return err(
4008
+ HaiIamError.REPOSITORY_ERROR,
4009
+ iamM(messageKey, { params: { message } })
4010
+ );
4011
+ }
4012
+ function mapUpdateErrorAsDomainError(message) {
4013
+ const loweredMessage = message.toLowerCase();
4014
+ if (loweredMessage.includes("unique") || loweredMessage.includes("duplicate")) {
4015
+ return err(
4016
+ HaiIamError.USER_ALREADY_EXISTS,
4017
+ iamM("iam_userAlreadyExist")
4018
+ );
4019
+ }
4020
+ return mapRepositoryError("iam_updateUserFailed", message);
4021
+ }
4022
+ async function validateUniqueFieldsForUpdate(userRepository, userId, data) {
4023
+ const currentResult = await userRepository.findById(userId);
4024
+ if (!currentResult.success) {
4025
+ return mapRepositoryError("iam_queryUserFailed", currentResult.error.message);
4026
+ }
4027
+ if (!currentResult.data) {
4028
+ return err(
4029
+ HaiIamError.USER_NOT_FOUND,
4030
+ iamM("iam_userNotExist")
4031
+ );
4032
+ }
4033
+ if (data.username && data.username !== currentResult.data.username) {
4034
+ const usernameExistsResult = await userRepository.existsByUsername(data.username);
4035
+ if (!usernameExistsResult.success) {
4036
+ return mapRepositoryError("iam_queryUserFailed", usernameExistsResult.error.message);
4037
+ }
4038
+ if (usernameExistsResult.data) {
4039
+ return err(
4040
+ HaiIamError.USER_ALREADY_EXISTS,
4041
+ iamM("iam_usernameAlreadyExist")
4042
+ );
4043
+ }
4044
+ }
4045
+ if (data.email && data.email !== currentResult.data.email) {
4046
+ const emailExistsResult = await userRepository.existsByEmail(data.email);
4047
+ if (!emailExistsResult.success) {
4048
+ return mapRepositoryError("iam_queryUserFailed", emailExistsResult.error.message);
4049
+ }
4050
+ if (emailExistsResult.data) {
4051
+ return err(
4052
+ HaiIamError.USER_ALREADY_EXISTS,
4053
+ iamM("iam_emailAlreadyUsed")
4054
+ );
4055
+ }
4056
+ }
4057
+ return ok(void 0);
4058
+ }
4059
+ function buildRegistrationOps(ctx) {
4060
+ const { userRepository, authzFunctions, config, registerConfig, agreementConfig } = ctx;
4061
+ const { validatePassword, hashPassword } = ctx;
4062
+ async function validateRegisterPreconditions(options) {
4063
+ if (!registerConfig.enabled) {
4064
+ return err(
4065
+ HaiIamError.REGISTER_DISABLED,
4066
+ iamM("iam_registerDisabled")
4067
+ );
4068
+ }
4069
+ const validateResult = validatePassword(options.password);
4070
+ if (!validateResult.success)
4071
+ return validateResult;
4072
+ const existsResult = await userRepository.existsByUsername(options.username);
4073
+ if (existsResult.success && existsResult.data) {
4074
+ return err(
4075
+ HaiIamError.USER_ALREADY_EXISTS,
4076
+ iamM("iam_usernameAlreadyExist")
4077
+ );
4078
+ }
4079
+ if (options.email) {
4080
+ const emailExistsResult = await userRepository.existsByEmail(options.email);
4081
+ if (emailExistsResult.success && emailExistsResult.data) {
4082
+ return err(
4083
+ HaiIamError.USER_ALREADY_EXISTS,
4084
+ iamM("iam_emailAlreadyUsed")
4085
+ );
4086
+ }
4087
+ }
4088
+ return ok(void 0);
4089
+ }
4090
+ function buildAgreementDisplay() {
4091
+ if (!agreementConfig.showOnRegister)
4092
+ return void 0;
4093
+ if (!agreementConfig.userAgreementUrl && !agreementConfig.privacyPolicyUrl)
4094
+ return void 0;
4095
+ return {
4096
+ userAgreementUrl: agreementConfig.userAgreementUrl,
4097
+ privacyPolicyUrl: agreementConfig.privacyPolicyUrl,
4098
+ showOnRegister: agreementConfig.showOnRegister,
4099
+ showOnLogin: agreementConfig.showOnLogin
4100
+ };
4101
+ }
4102
+ async function assignDefaultRole(userId) {
4103
+ if (!config.rbac?.defaultRole)
4104
+ return;
4105
+ const roleResult = await authzFunctions.getRoleByCode(config.rbac.defaultRole);
4106
+ if (roleResult.success && roleResult.data) {
4107
+ await authzFunctions.assignRole(userId, roleResult.data.id);
4108
+ }
4109
+ }
4110
+ return {
4111
+ async register(options) {
4112
+ const preResult = await validateRegisterPreconditions(options);
4113
+ if (!preResult.success)
4114
+ return preResult;
4115
+ const hashResult = hashPassword(options.password);
4116
+ if (!hashResult.success)
4117
+ return hashResult;
4118
+ const txResult = await reldb.tx.begin();
4119
+ if (!txResult.success) {
4120
+ return mapRepositoryError("iam_createUserFailed", txResult.error.message);
4121
+ }
4122
+ const tx = txResult.data;
4123
+ const createResult = await userRepository.create({
4124
+ username: options.username,
4125
+ email: options.email,
4126
+ phone: options.phone,
4127
+ displayName: options.displayName,
4128
+ enabled: registerConfig.defaultEnabled,
4129
+ emailVerified: false,
4130
+ phoneVerified: false,
4131
+ passwordHash: hashResult.data,
4132
+ passwordUpdatedAt: /* @__PURE__ */ new Date(),
4133
+ metadata: options.metadata
4134
+ }, tx);
4135
+ if (!createResult.success) {
4136
+ await tx.rollback();
4137
+ return mapUpdateErrorAsDomainError(createResult.error.message);
4138
+ }
4139
+ const createdUserResult = await userRepository.findByUsername(options.username, tx);
4140
+ if (!createdUserResult.success || !createdUserResult.data) {
4141
+ await tx.rollback();
4142
+ return err(
4143
+ HaiIamError.USER_NOT_FOUND,
4144
+ iamM("iam_userNotExist")
4145
+ );
4146
+ }
4147
+ const commitResult = await tx.commit();
4148
+ if (!commitResult.success) {
4149
+ return mapRepositoryError("iam_createUserFailed", commitResult.error.message);
4150
+ }
4151
+ const createdUser = createdUserResult.data;
4152
+ await assignDefaultRole(createdUser.id);
4153
+ logger9.info("User registered", { userId: createdUser.id, username: options.username });
4154
+ return ok({
4155
+ user: toUser(createdUser),
4156
+ agreements: buildAgreementDisplay()
4157
+ });
4158
+ },
4159
+ validatePassword(password) {
4160
+ return validatePassword(password);
4161
+ }
4162
+ };
4163
+ }
4164
+ function buildUserQueryOps(ctx) {
4165
+ const { userRepository, sessionFunctions, authzFunctions } = ctx;
4166
+ return {
4167
+ async getCurrentUser(accessToken) {
4168
+ const verifyResult = await sessionFunctions.verifyToken(accessToken);
4169
+ if (!verifyResult.success) {
4170
+ return verifyResult;
4171
+ }
4172
+ const userResult = await userRepository.findById(verifyResult.data.userId);
4173
+ if (!userResult.success) {
4174
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4175
+ }
4176
+ if (!userResult.data) {
4177
+ return err(
4178
+ HaiIamError.USER_NOT_FOUND,
4179
+ iamM("iam_userNotExist")
4180
+ );
4181
+ }
4182
+ return ok(toUser(userResult.data));
4183
+ },
4184
+ async getUser(userId, options) {
4185
+ const userResult = await userRepository.findById(userId);
4186
+ if (!userResult.success) {
4187
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4188
+ }
4189
+ if (!userResult.data) {
4190
+ return ok(null);
4191
+ }
4192
+ const user = toUser(userResult.data);
4193
+ if (options?.include?.includes("roles")) {
4194
+ const rolesResult = await authzFunctions.getUserRoles(userId);
4195
+ if (rolesResult.success) {
4196
+ user.roles = rolesResult.data;
4197
+ }
4198
+ }
4199
+ return ok(user);
4200
+ },
4201
+ async listUsers(options) {
4202
+ const conditions = [];
4203
+ const params = [];
4204
+ if (options?.search) {
4205
+ const escaped = options.search.replace(/[%_\\]/g, "\\$&");
4206
+ const keyword = `%${escaped}%`;
4207
+ conditions.push("(username LIKE ? OR email LIKE ? OR phone LIKE ? OR display_name LIKE ?)");
4208
+ params.push(keyword, keyword, keyword, keyword);
4209
+ }
4210
+ if (options?.enabled !== void 0) {
4211
+ conditions.push("enabled = ?");
4212
+ params.push(options.enabled ? 1 : 0);
4213
+ }
4214
+ const where = conditions.length > 0 ? conditions.join(" AND ") : void 0;
4215
+ const usersResult = await userRepository.findPage({
4216
+ where,
4217
+ params: params.length > 0 ? params : void 0,
4218
+ orderBy: "created_at DESC",
4219
+ pagination: options ? { page: options.page, pageSize: options.pageSize } : void 0
4220
+ });
4221
+ if (!usersResult.success) {
4222
+ return mapRepositoryError("iam_queryUserListFailed", usersResult.error.message);
4223
+ }
4224
+ const items = usersResult.data.items.map(toUser);
4225
+ if (options?.include?.includes("roles") && items.length > 0) {
4226
+ const userIds = items.map((u) => u.id);
4227
+ const rolesMapResult = await authzFunctions.getUserRolesForMany(userIds);
4228
+ if (rolesMapResult.success) {
4229
+ for (const user of items) {
4230
+ user.roles = rolesMapResult.data.get(user.id) ?? [];
4231
+ }
4232
+ }
4233
+ }
4234
+ return ok({
4235
+ items,
4236
+ total: usersResult.data.total,
4237
+ page: usersResult.data.page,
4238
+ pageSize: usersResult.data.pageSize
4239
+ });
4240
+ }
4241
+ };
4242
+ }
4243
+ function buildUserMutationOps(ctx) {
4244
+ const { userRepository, sessionFunctions, authzFunctions } = ctx;
4245
+ return {
4246
+ async updateCurrentUser(accessToken, data) {
4247
+ const verifyResult = await sessionFunctions.verifyToken(accessToken);
4248
+ if (!verifyResult.success) {
4249
+ return verifyResult;
4250
+ }
4251
+ const userId = verifyResult.data.userId;
4252
+ const safeData = {};
4253
+ if (data.username !== void 0)
4254
+ safeData.username = data.username;
4255
+ if (data.email !== void 0)
4256
+ safeData.email = data.email;
4257
+ if (data.displayName !== void 0)
4258
+ safeData.displayName = data.displayName;
4259
+ if (data.avatarUrl !== void 0)
4260
+ safeData.avatarUrl = data.avatarUrl;
4261
+ if (data.phone !== void 0)
4262
+ safeData.phone = data.phone;
4263
+ if (data.metadata !== void 0)
4264
+ safeData.metadata = data.metadata;
4265
+ if (!hasUpdateFields(safeData)) {
4266
+ const currentResult = await userRepository.findById(userId);
4267
+ if (!currentResult.success) {
4268
+ return mapRepositoryError("iam_queryUserFailed", currentResult.error.message);
4269
+ }
4270
+ if (!currentResult.data) {
4271
+ return err(
4272
+ HaiIamError.USER_NOT_FOUND,
4273
+ iamM("iam_userNotExist")
4274
+ );
4275
+ }
4276
+ return ok(toUser(currentResult.data));
4277
+ }
4278
+ const uniqueResult = await validateUniqueFieldsForUpdate(userRepository, userId, safeData);
4279
+ if (!uniqueResult.success) {
4280
+ return uniqueResult;
4281
+ }
4282
+ const updateResult = await userRepository.updateById(userId, safeData);
4283
+ if (!updateResult.success) {
4284
+ return mapUpdateErrorAsDomainError(updateResult.error.message);
4285
+ }
4286
+ if (updateResult.data.changes === 0) {
4287
+ return err(
4288
+ HaiIamError.USER_NOT_FOUND,
4289
+ iamM("iam_userNotExist")
4290
+ );
4291
+ }
4292
+ const updatedResult = await userRepository.findById(userId);
4293
+ if (!updatedResult.success) {
4294
+ return mapRepositoryError("iam_queryUserFailed", updatedResult.error.message);
4295
+ }
4296
+ if (!updatedResult.data) {
4297
+ return err(
4298
+ HaiIamError.USER_NOT_FOUND,
4299
+ iamM("iam_userNotExist")
4300
+ );
4301
+ }
4302
+ return ok(toUser(updatedResult.data));
4303
+ },
4304
+ async updateUser(userId, data) {
4305
+ if (!hasUpdateFields(data)) {
4306
+ const currentResult = await userRepository.findById(userId);
4307
+ if (!currentResult.success) {
4308
+ return mapRepositoryError("iam_queryUserFailed", currentResult.error.message);
4309
+ }
4310
+ if (!currentResult.data) {
4311
+ return err(
4312
+ HaiIamError.USER_NOT_FOUND,
4313
+ iamM("iam_userNotExist")
4314
+ );
4315
+ }
4316
+ return ok(toUser(currentResult.data));
4317
+ }
4318
+ const uniqueResult = await validateUniqueFieldsForUpdate(userRepository, userId, data);
4319
+ if (!uniqueResult.success) {
4320
+ return uniqueResult;
4321
+ }
4322
+ const updateResult = await userRepository.updateById(userId, data);
4323
+ if (!updateResult.success) {
4324
+ return mapUpdateErrorAsDomainError(updateResult.error.message);
4325
+ }
4326
+ if (updateResult.data.changes === 0) {
4327
+ return err(
4328
+ HaiIamError.USER_NOT_FOUND,
4329
+ iamM("iam_userNotExist")
4330
+ );
4331
+ }
4332
+ if (data.enabled === false) {
4333
+ await sessionFunctions.deleteByUserId(userId);
4334
+ }
4335
+ const updatedResult = await userRepository.findById(userId);
4336
+ if (!updatedResult.success) {
4337
+ return mapRepositoryError("iam_queryUserFailed", updatedResult.error.message);
4338
+ }
4339
+ if (!updatedResult.data) {
4340
+ return err(
4341
+ HaiIamError.USER_NOT_FOUND,
4342
+ iamM("iam_userNotExist")
4343
+ );
4344
+ }
4345
+ return ok(toUser(updatedResult.data));
4346
+ },
4347
+ async deleteUser(userId) {
4348
+ logger9.debug("Deleting user", { userId });
4349
+ const userResult = await userRepository.findById(userId);
4350
+ if (!userResult.success) {
4351
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4352
+ }
4353
+ if (!userResult.data) {
4354
+ return err(
4355
+ HaiIamError.USER_NOT_FOUND,
4356
+ iamM("iam_userNotExist")
4357
+ );
4358
+ }
4359
+ const txResult = await reldb.tx.begin();
4360
+ if (!txResult.success) {
4361
+ return mapRepositoryError("iam_deleteUserFailed", txResult.error.message);
4362
+ }
4363
+ const tx = txResult.data;
4364
+ try {
4365
+ const syncResult = await authzFunctions.syncRoles(userId, [], tx);
4366
+ if (!syncResult.success) {
4367
+ await tx.rollback();
4368
+ return mapRepositoryError("iam_deleteUserFailed", syncResult.error.message);
4369
+ }
4370
+ const deleteResult = await userRepository.deleteById(userId, tx);
4371
+ if (!deleteResult.success) {
4372
+ await tx.rollback();
4373
+ return mapRepositoryError("iam_deleteUserFailed", deleteResult.error.message);
4374
+ }
4375
+ const commitResult = await tx.commit();
4376
+ if (!commitResult.success) {
4377
+ return mapRepositoryError("iam_deleteUserFailed", commitResult.error.message);
4378
+ }
4379
+ } catch (error) {
4380
+ await tx.rollback();
4381
+ return err(
4382
+ HaiIamError.REPOSITORY_ERROR,
4383
+ iamM("iam_deleteUserFailed", { params: { message: String(error) } }),
4384
+ error
4385
+ );
4386
+ }
4387
+ await sessionFunctions.deleteByUserId(userId);
4388
+ logger9.info("User deleted", { userId });
4389
+ return ok(void 0);
4390
+ }
4391
+ };
4392
+ }
4393
+ function buildPasswordChangeOps(ctx) {
4394
+ const { userRepository, sessionFunctions } = ctx;
4395
+ const { validatePassword, hashPassword } = ctx;
4396
+ return {
4397
+ async adminResetPassword(userId, newPassword) {
4398
+ logger9.debug("Admin resetting user password", { userId });
4399
+ const userResult = await userRepository.findById(userId);
4400
+ if (!userResult.success) {
4401
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4402
+ }
4403
+ if (!userResult.data) {
4404
+ return err(
4405
+ HaiIamError.USER_NOT_FOUND,
4406
+ iamM("iam_userNotExist")
4407
+ );
4408
+ }
4409
+ const validateResult = validatePassword(newPassword);
4410
+ if (!validateResult.success)
4411
+ return validateResult;
4412
+ const hashResult = hashPassword(newPassword);
4413
+ if (!hashResult.success)
4414
+ return hashResult;
4415
+ const updateResult = await userRepository.updateById(userId, {
4416
+ passwordHash: hashResult.data,
4417
+ passwordUpdatedAt: /* @__PURE__ */ new Date()
4418
+ });
4419
+ if (!updateResult.success) {
4420
+ return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
4421
+ }
4422
+ await sessionFunctions.deleteByUserId(userId);
4423
+ logger9.info("Admin reset password", { userId });
4424
+ return ok(void 0);
4425
+ },
4426
+ async changePassword(userId, oldPassword, newPassword) {
4427
+ const userResult = await userRepository.findById(userId);
4428
+ if (!userResult.success) {
4429
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4430
+ }
4431
+ if (!userResult.data) {
4432
+ return err(
4433
+ HaiIamError.USER_NOT_FOUND,
4434
+ iamM("iam_userNotExist")
4435
+ );
4436
+ }
4437
+ const user = userResult.data;
4438
+ if (!user.passwordHash) {
4439
+ return err(
4440
+ HaiIamError.INVALID_CREDENTIALS,
4441
+ iamM("iam_accountNoPassword")
4442
+ );
4443
+ }
4444
+ const verifyResult = crypto$1.password.verify(oldPassword, user.passwordHash);
4445
+ if (!verifyResult.success || !verifyResult.data) {
4446
+ return err(
4447
+ HaiIamError.INVALID_CREDENTIALS,
4448
+ iamM("iam_originalPasswordWrong")
4449
+ );
4450
+ }
4451
+ const validateResult = validatePassword(newPassword);
4452
+ if (!validateResult.success)
4453
+ return validateResult;
4454
+ const hashResult = hashPassword(newPassword);
4455
+ if (!hashResult.success)
4456
+ return hashResult;
4457
+ const updateResult = await userRepository.updateById(userId, {
4458
+ passwordHash: hashResult.data,
4459
+ passwordUpdatedAt: /* @__PURE__ */ new Date()
4460
+ });
4461
+ if (!updateResult.success) {
4462
+ return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
4463
+ }
4464
+ await sessionFunctions.deleteByUserId(userId);
4465
+ logger9.info("Password changed", { userId });
4466
+ return ok(void 0);
4467
+ },
4468
+ /**
4469
+ * 通过访问令牌定位当前用户并执行改密。
4470
+ *
4471
+ * @param accessToken 访问令牌
4472
+ * @param oldPassword 原密码
4473
+ * @param newPassword 新密码
4474
+ * @returns 改密执行结果
4475
+ */
4476
+ async changeCurrentUserPassword(accessToken, oldPassword, newPassword) {
4477
+ const verifyResult = await sessionFunctions.verifyToken(accessToken);
4478
+ if (!verifyResult.success) {
4479
+ return verifyResult;
4480
+ }
4481
+ return this.changePassword(verifyResult.data.userId, oldPassword, newPassword);
4482
+ }
4483
+ };
4484
+ }
4485
+ function buildPasswordResetOps(ctx) {
4486
+ const { userRepository, resetTokenRepository, sessionFunctions, config, onPasswordResetRequest } = ctx;
4487
+ const { validatePassword, hashPassword } = ctx;
4488
+ return {
4489
+ async requestPasswordReset(identifier) {
4490
+ logger9.debug("Password reset requested", { identifier });
4491
+ const resetConfig = PasswordResetConfigSchema.parse(config.passwordReset ?? {});
4492
+ const userResult = await userRepository.findByIdentifier(identifier);
4493
+ if (!userResult.success) {
4494
+ logger9.warn("Failed to look up user for password reset", { identifier });
4495
+ return ok(void 0);
4496
+ }
4497
+ if (!userResult.data) {
4498
+ logger9.debug("User not found for password reset, returning ok to prevent enumeration", { identifier });
4499
+ return ok(void 0);
4500
+ }
4501
+ const user = toUser(userResult.data);
4502
+ const token = globalThis.crypto.randomUUID();
4503
+ const expiresAt = new Date(Date.now() + resetConfig.tokenExpiresIn * 1e3);
4504
+ const saveResult = await resetTokenRepository.saveToken(token, user.id, expiresAt);
4505
+ if (!saveResult.success) {
4506
+ return saveResult;
4507
+ }
4508
+ if (onPasswordResetRequest) {
4509
+ try {
4510
+ await onPasswordResetRequest(user, token, expiresAt);
4511
+ } catch (callbackError) {
4512
+ logger9.error("Password reset callback failed", { userId: user.id, error: callbackError });
4513
+ }
4514
+ } else {
4515
+ logger9.warn("No password reset callback configured, token will not be delivered to user", { userId: user.id });
4516
+ }
4517
+ logger9.info("Password reset token generated", { userId: user.id });
4518
+ return ok(void 0);
4519
+ },
4520
+ async confirmPasswordReset(token, newPassword) {
4521
+ logger9.debug("Confirming password reset");
4522
+ const resetConfig = PasswordResetConfigSchema.parse(config.passwordReset ?? {});
4523
+ const validateResult = validatePassword(newPassword);
4524
+ if (!validateResult.success)
4525
+ return validateResult;
4526
+ const tokenResult = await resetTokenRepository.tryGetUserByToken(token, resetConfig.maxAttempts);
4527
+ if (!tokenResult.success) {
4528
+ return tokenResult;
4529
+ }
4530
+ const userId = tokenResult.data;
4531
+ const userResult = await userRepository.findById(userId);
4532
+ if (!userResult.success) {
4533
+ return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
4534
+ }
4535
+ if (!userResult.data) {
4536
+ return err(
4537
+ HaiIamError.USER_NOT_FOUND,
4538
+ iamM("iam_userNotExist")
4539
+ );
4540
+ }
4541
+ const hashResult = hashPassword(newPassword);
4542
+ if (!hashResult.success)
4543
+ return hashResult;
4544
+ const updateResult = await userRepository.updateById(userId, {
4545
+ passwordHash: hashResult.data,
4546
+ passwordUpdatedAt: /* @__PURE__ */ new Date()
4547
+ });
4548
+ if (!updateResult.success) {
4549
+ return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
4550
+ }
4551
+ await resetTokenRepository.removeToken(token);
4552
+ await sessionFunctions.deleteByUserId(userId);
4553
+ logger9.info("Password reset confirmed", { userId });
4554
+ return ok(void 0);
4555
+ }
4556
+ };
4557
+ }
4558
+ function buildUserFunctions(deps) {
4559
+ const { validatePassword, hashPassword } = deps.passwordStrategyResult;
4560
+ const ctx = {
4561
+ ...deps,
4562
+ validatePassword,
4563
+ hashPassword,
4564
+ registerConfig: RegisterConfigSchema.parse(deps.config.register ?? {}),
4565
+ agreementConfig: AgreementConfigSchema.parse(deps.config.agreements ?? {})
4566
+ };
4567
+ return {
4568
+ ...buildRegistrationOps(ctx),
4569
+ ...buildUserQueryOps(ctx),
4570
+ ...buildUserMutationOps(ctx),
4571
+ ...buildPasswordChangeOps(ctx),
4572
+ ...buildPasswordResetOps(ctx)
4573
+ };
4574
+ }
4575
+
4576
+ // src/iam-main.ts
4577
+ var logger10 = core.logger.child({ module: "iam", scope: "main" });
4578
+ var initInProgress = false;
4579
+ var currentConfig = null;
4580
+ var currentAuth = null;
4581
+ var currentUser = null;
4582
+ var currentAuthz = null;
4583
+ var currentSession = null;
4584
+ var currentApiKey = null;
4585
+ var notInitialized = core.module.createNotInitializedKit(
4586
+ HaiIamError.NOT_INITIALIZED,
4587
+ () => iamM("iam_notInitialized")
4588
+ );
4589
+ var notInitializedAuth = notInitialized.proxy();
4590
+ var notInitializedAuthz = notInitialized.proxy();
4591
+ var notInitializedSession = notInitialized.proxy();
4592
+ var notInitializedApiKey = notInitialized.proxy();
4593
+ var syncUserProxy = notInitialized.proxy("sync");
4594
+ var asyncUserProxy = notInitialized.proxy();
4595
+ var notInitializedUser = new Proxy({}, {
4596
+ get(_, prop, receiver) {
4597
+ return prop === "validatePassword" ? Reflect.get(syncUserProxy, prop, receiver) : Reflect.get(asyncUserProxy, prop, receiver);
4598
+ }
4599
+ });
4600
+ var iam = {
4601
+ async init(config) {
4602
+ if (initInProgress) {
4603
+ logger10.warn("IAM init already in progress, skipping concurrent call");
4604
+ return err(
4605
+ HaiIamError.CONFIG_ERROR,
4606
+ iamM("iam_initInProgress")
4607
+ );
4608
+ }
4609
+ initInProgress = true;
4610
+ try {
4611
+ if (currentConfig !== null) {
4612
+ logger10.warn("IAM module is already initialized, reinitializing");
4613
+ await iam.close();
4614
+ }
4615
+ const { ldapClientFactory, ldapSyncUser, onPasswordResetRequest, onOtpSendEmail, onOtpSendSms, ...settingsInput } = config;
4616
+ logger10.info("Initializing IAM module");
4617
+ if (!reldb.isInitialized) {
4618
+ return err(
4619
+ HaiIamError.CONFIG_ERROR,
4620
+ iamM("iam_depsNotInitialized", { params: { dep: "reldb" } })
4621
+ );
4622
+ }
4623
+ if (!cache.isInitialized) {
4624
+ return err(
4625
+ HaiIamError.CONFIG_ERROR,
4626
+ iamM("iam_depsNotInitialized", { params: { dep: "cache" } })
4627
+ );
4628
+ }
4629
+ if (!crypto$1.isInitialized) {
4630
+ const cryptoResult = await crypto$1.init();
4631
+ if (!cryptoResult.success) {
4632
+ return err(
4633
+ HaiIamError.CONFIG_ERROR,
4634
+ iamM("iam_initFailed"),
4635
+ cryptoResult.error
4636
+ );
4637
+ }
4638
+ }
4639
+ const parseResult = IamConfigSchema.safeParse(settingsInput);
4640
+ if (!parseResult.success) {
4641
+ logger10.error("IAM config validation failed", { error: parseResult.error.message });
4642
+ return err(
4643
+ HaiIamError.CONFIG_ERROR,
4644
+ iamM("iam_configError", { params: { error: parseResult.error.message } }),
4645
+ parseResult.error
4646
+ );
4647
+ }
4648
+ const parsed = parseResult.data;
4649
+ const sessionResult = await createSessionOperations({ config: parsed });
4650
+ if (!sessionResult.success) {
4651
+ return sessionResult;
4652
+ }
4653
+ const authzResult = await createAuthzOperations({ config: parsed, session: sessionResult.data });
4654
+ if (!authzResult.success) {
4655
+ return authzResult;
4656
+ }
4657
+ const authnResult = await createAuthnOperations({
4658
+ config: parsed,
4659
+ sessionFunctions: sessionResult.data,
4660
+ authzFunctions: authzResult.data,
4661
+ ldapClientFactory,
4662
+ ldapSyncUser,
4663
+ onOtpSendEmail,
4664
+ onOtpSendSms
4665
+ });
4666
+ if (!authnResult.success) {
4667
+ return authnResult;
4668
+ }
4669
+ const userResult = await createUserOperations({
4670
+ config: parsed,
4671
+ passwordStrategyResult: authnResult.data.passwordStrategyResult,
4672
+ sessionFunctions: sessionResult.data,
4673
+ authzFunctions: authzResult.data,
4674
+ onPasswordResetRequest
4675
+ });
4676
+ if (!userResult.success) {
4677
+ return userResult;
4678
+ }
4679
+ if (parsed.seedDefaultData) {
4680
+ const seedResult = await seedIamData(authzResult.data);
4681
+ if (!seedResult.success) {
4682
+ return seedResult;
4683
+ }
4684
+ }
4685
+ currentSession = sessionResult.data;
4686
+ currentAuthz = authzResult.data;
4687
+ currentUser = userResult.data;
4688
+ currentConfig = parsed;
4689
+ currentApiKey = authnResult.data.apiKeyFunctions;
4690
+ const authn = authnResult.data.authn;
4691
+ currentAuth = {
4692
+ ...authn,
4693
+ async registerAndLogin(options) {
4694
+ const regResult = await currentUser.register(options);
4695
+ if (!regResult.success) {
4696
+ return regResult;
4697
+ }
4698
+ return authn.login({ identifier: options.username, password: options.password });
4699
+ }
4700
+ };
4701
+ logger10.info("IAM module initialized");
4702
+ return ok(void 0);
4703
+ } catch (error) {
4704
+ logger10.error("IAM module initialization failed", { error });
4705
+ return err(
4706
+ HaiIamError.CONFIG_ERROR,
4707
+ iamM("iam_initFailed"),
4708
+ error
4709
+ );
4710
+ } finally {
4711
+ initInProgress = false;
4712
+ }
4713
+ },
4714
+ get auth() {
4715
+ return currentAuth ?? notInitializedAuth;
4716
+ },
4717
+ get user() {
4718
+ return currentUser ?? notInitializedUser;
4719
+ },
4720
+ get authz() {
4721
+ return currentAuthz ?? notInitializedAuthz;
4722
+ },
4723
+ get session() {
4724
+ return currentSession ?? notInitializedSession;
4725
+ },
4726
+ get apiKey() {
4727
+ return currentApiKey ?? notInitializedApiKey;
4728
+ },
4729
+ get config() {
4730
+ return currentConfig;
4731
+ },
4732
+ get isInitialized() {
4733
+ return currentConfig !== null;
4734
+ },
4735
+ get isRegisterEnabled() {
4736
+ return currentConfig?.register?.enabled !== false;
4737
+ },
4738
+ async close() {
4739
+ if (currentConfig === null && currentAuth === null && currentUser === null && currentAuthz === null && currentSession === null) {
4740
+ logger10.info("IAM module already closed, skipping");
4741
+ return;
4742
+ }
4743
+ logger10.info("Closing IAM module");
4744
+ currentAuth = null;
4745
+ currentUser = null;
4746
+ currentAuthz = null;
4747
+ currentSession = null;
4748
+ currentApiKey = null;
4749
+ currentConfig = null;
4750
+ resetApiKeyRepoSingleton();
4751
+ resetOtpRepoSingleton();
4752
+ resetUserRepoSingleton();
4753
+ resetResetTokenRepoSingleton();
4754
+ resetRoleRepoSingleton();
4755
+ resetPermissionRepoSingleton();
4756
+ logger10.info("IAM module closed");
4757
+ }
4758
+ };
4759
+
4760
+ export { AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema, iam };
4761
+ //# sourceMappingURL=index.js.map
4762
+ //# sourceMappingURL=index.js.map