@h-ai/iam 0.1.0-alpha.36 → 0.1.0-alpha.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -8
- package/dist/index.d.ts +99 -1
- package/dist/index.js +101 -29
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
7
|
-
| 功能 | 说明
|
|
8
|
-
| -------------- |
|
|
9
|
-
| **认证** | 密码、OTP(邮箱/短信验证码)、LDAP、API Key 多策略认证
|
|
10
|
-
| **会话** | 有状态会话(随机访问令牌 + 缓存,滑动续期可选)
|
|
11
|
-
| **授权** | RBAC 角色与权限管理(DB + 缓存,通配符权限匹配)
|
|
12
|
-
| **用户管理** | 注册、查询、更新、密码重置、管理员重置密码
|
|
13
|
-
| **API Key** | API Key 创建、吊销、验证,支持 scope 与过期时间
|
|
14
|
-
|
|
|
7
|
+
| 功能 | 说明 |
|
|
8
|
+
| -------------- | -------------------------------------------------------------------------------------- |
|
|
9
|
+
| **认证** | 密码、OTP(邮箱/短信验证码)、LDAP、API Key 多策略认证 |
|
|
10
|
+
| **会话** | 有状态会话(随机访问令牌 + 缓存,滑动续期可选) |
|
|
11
|
+
| **授权** | RBAC 角色与权限管理(DB + 缓存,通配符权限匹配) |
|
|
12
|
+
| **用户管理** | 注册、查询、更新、密码重置、管理员重置密码 |
|
|
13
|
+
| **API Key** | API Key 创建、吊销、验证,支持 scope 与过期时间 |
|
|
14
|
+
| **一次性票据** | 短期、一次性能力票据(签发 / 原子消费),用于 WebSocket 等无法携带 Bearer Token 的通道 |
|
|
15
|
+
| **前端客户端** | HTTP API 契约定义,支持独立前端使用 |
|
|
15
16
|
|
|
16
17
|
## 安装
|
|
17
18
|
|
|
@@ -488,6 +489,30 @@ const result = await iam.user.confirmPasswordReset(token, 'NewPassword456')
|
|
|
488
489
|
await iam.user.adminResetPassword(userId, 'TempPassword123')
|
|
489
490
|
```
|
|
490
491
|
|
|
492
|
+
## 一次性票据 — `iam.ticket`
|
|
493
|
+
|
|
494
|
+
短期、一次性能力票据。用于无法携带 Bearer Token 的通道(如 WebSocket URL):由已认证的 HTTP 请求签发一个绑定主体、用途与操作的票据,服务端在首次校验时**原子消费**(单次有效)。
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
// 签发(已登录 HTTP 请求内):绑定主体、用途与本次操作,30 秒过期
|
|
498
|
+
const issued = await iam.ticket.issue({
|
|
499
|
+
subjectId: session.userId,
|
|
500
|
+
purpose: 'ai-audio',
|
|
501
|
+
grant: { operation: 'transcribe', model: 'whisper-1' },
|
|
502
|
+
ttlMs: 30_000,
|
|
503
|
+
})
|
|
504
|
+
// issued.data.ticket 交给客户端,用于建立 WebSocket 连接
|
|
505
|
+
|
|
506
|
+
// 消费(服务端建连时):原子单次,返回主体 + 用途 + grant
|
|
507
|
+
const consumed = await iam.ticket.consume(ticket, { purpose: 'ai-audio' })
|
|
508
|
+
if (consumed.success) {
|
|
509
|
+
const { subjectId, grant } = consumed.data
|
|
510
|
+
// 用 grant.operation / grant.model 交叉校验客户端请求
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
特性:密码学安全随机值、TTL、原子单次消费(并发消费仅一个成功)、用途绑定(防跨用途重放)。
|
|
515
|
+
|
|
491
516
|
## 错误处理
|
|
492
517
|
|
|
493
518
|
所有操作返回 `HaiResult<T>`,通过 `HaiIamError` 做分支判断:
|
package/dist/index.d.ts
CHANGED
|
@@ -1240,6 +1240,99 @@ interface LdapSearchEntry {
|
|
|
1240
1240
|
*/
|
|
1241
1241
|
type LdapClientFactory = (config: LdapConfig) => Promise<HaiResult<LdapClient>>;
|
|
1242
1242
|
|
|
1243
|
+
/**
|
|
1244
|
+
* @h-ai/iam — 一次性票据类型
|
|
1245
|
+
*
|
|
1246
|
+
* 定义通用的短期、一次性能力票据(capability ticket):由已认证的上下文签发,
|
|
1247
|
+
* 绑定主体、用途与授权信息,在无法携带 Bearer Token 的通道(如 WebSocket URL)中
|
|
1248
|
+
* 用作一次性入场凭证。典型用途:AI 语音 WebSocket 接入。
|
|
1249
|
+
* @module ticket/iam-ticket-types
|
|
1250
|
+
*/
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* 票据授权绑定信息
|
|
1254
|
+
*
|
|
1255
|
+
* 消费时原样返回,供服务端确认本次操作、模型、会话等参数与签发时一致。
|
|
1256
|
+
* 除固定字段外允许应用自定义键值。
|
|
1257
|
+
*/
|
|
1258
|
+
interface TicketGrant {
|
|
1259
|
+
/** 操作类型(如 `'transcribe'` | `'synthesize'` 或应用自定义) */
|
|
1260
|
+
operation?: string;
|
|
1261
|
+
/** 模型 ID */
|
|
1262
|
+
model?: string;
|
|
1263
|
+
/** 会话 ID */
|
|
1264
|
+
sessionId?: string;
|
|
1265
|
+
/** 应用自定义绑定字段 */
|
|
1266
|
+
[key: string]: unknown;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* 签发一次性票据的选项
|
|
1270
|
+
*/
|
|
1271
|
+
interface IssueTicketOptions {
|
|
1272
|
+
/** 主体 ID(票据绑定的用户 / 主体,消费时原样返回) */
|
|
1273
|
+
subjectId: string;
|
|
1274
|
+
/** 票据用途(如 `'ai-audio'`),消费时校验一致 */
|
|
1275
|
+
purpose: string;
|
|
1276
|
+
/** 授权绑定信息(操作 / 模型 / 会话等,消费时原样返回) */
|
|
1277
|
+
grant?: TicketGrant;
|
|
1278
|
+
/** 有效期(毫秒,默认 30000) */
|
|
1279
|
+
ttlMs?: number;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* 签发结果
|
|
1283
|
+
*/
|
|
1284
|
+
interface IssuedTicket {
|
|
1285
|
+
/** 一次性票据值(密码学安全随机,base64url) */
|
|
1286
|
+
ticket: string;
|
|
1287
|
+
/** 过期时间(Unix 毫秒) */
|
|
1288
|
+
expiresAt: number;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* 消费票据的选项
|
|
1292
|
+
*/
|
|
1293
|
+
interface ConsumeTicketOptions {
|
|
1294
|
+
/** 期望用途;与签发用途不一致时返回 `TICKET_INVALID`(防止跨用途重放) */
|
|
1295
|
+
purpose?: string;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* 消费结果(校验通过并原子消费后返回)
|
|
1299
|
+
*/
|
|
1300
|
+
interface ConsumedTicket {
|
|
1301
|
+
/** 票据绑定的主体 ID */
|
|
1302
|
+
subjectId: string;
|
|
1303
|
+
/** 票据用途 */
|
|
1304
|
+
purpose: string;
|
|
1305
|
+
/** 授权绑定信息 */
|
|
1306
|
+
grant: TicketGrant;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* 一次性票据操作接口(通过 `iam.ticket` 访问)
|
|
1310
|
+
*
|
|
1311
|
+
* 提供密码学安全、带 TTL、原子单次消费的能力票据签发与消费。
|
|
1312
|
+
*/
|
|
1313
|
+
interface TicketOperations {
|
|
1314
|
+
/**
|
|
1315
|
+
* 签发一次性票据
|
|
1316
|
+
*
|
|
1317
|
+
* 生成密码学安全随机值,绑定主体 / 用途 / 授权信息并按 TTL 存入 cache。
|
|
1318
|
+
*
|
|
1319
|
+
* @param options - 签发选项
|
|
1320
|
+
* @returns 票据值与过期时间
|
|
1321
|
+
*/
|
|
1322
|
+
issue: (options: IssueTicketOptions) => Promise<HaiResult<IssuedTicket>>;
|
|
1323
|
+
/**
|
|
1324
|
+
* 原子消费一次性票据
|
|
1325
|
+
*
|
|
1326
|
+
* 单次有效:并发消费同一票据时只有一个成功,其余返回 `TICKET_INVALID`。
|
|
1327
|
+
* 票据不存在 / 已过期 / 已消费 / 用途不匹配时返回错误。
|
|
1328
|
+
*
|
|
1329
|
+
* @param ticket - 票据值
|
|
1330
|
+
* @param options - 消费选项(可校验用途)
|
|
1331
|
+
* @returns 主体 + 用途 + 授权信息
|
|
1332
|
+
*/
|
|
1333
|
+
consume: (ticket: string, options?: ConsumeTicketOptions) => Promise<HaiResult<ConsumedTicket>>;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1243
1336
|
declare const HaiIamError: {
|
|
1244
1337
|
readonly AUTH_FAILED: _h_ai_core.HaiErrorDef;
|
|
1245
1338
|
readonly INVALID_CREDENTIALS: _h_ai_core.HaiErrorDef;
|
|
@@ -1278,6 +1371,9 @@ declare const HaiIamError: {
|
|
|
1278
1371
|
readonly LDAP_CONNECTION_FAILED: _h_ai_core.HaiErrorDef;
|
|
1279
1372
|
readonly LDAP_BIND_FAILED: _h_ai_core.HaiErrorDef;
|
|
1280
1373
|
readonly LDAP_SEARCH_FAILED: _h_ai_core.HaiErrorDef;
|
|
1374
|
+
readonly TICKET_INVALID: _h_ai_core.HaiErrorDef;
|
|
1375
|
+
readonly TICKET_EXPIRED: _h_ai_core.HaiErrorDef;
|
|
1376
|
+
readonly TICKET_ISSUE_FAILED: _h_ai_core.HaiErrorDef;
|
|
1281
1377
|
readonly REPOSITORY_ERROR: _h_ai_core.HaiErrorDef;
|
|
1282
1378
|
readonly NOT_FOUND: _h_ai_core.HaiErrorDef;
|
|
1283
1379
|
readonly CONFLICT: _h_ai_core.HaiErrorDef;
|
|
@@ -1380,6 +1476,8 @@ interface IamFunctions {
|
|
|
1380
1476
|
readonly session: SessionOperations;
|
|
1381
1477
|
/** API Key 管理(创建、列表、吐销、验证),未启用 apikey 登录时返回未初始化代理 */
|
|
1382
1478
|
readonly apiKey: ApiKeyOperations;
|
|
1479
|
+
/** 一次性能力票据(签发 / 原子消费短期票据,用于 WebSocket 等无法携带 Bearer Token 的通道) */
|
|
1480
|
+
readonly ticket: TicketOperations;
|
|
1383
1481
|
}
|
|
1384
1482
|
|
|
1385
1483
|
/**
|
|
@@ -1391,4 +1489,4 @@ interface IamFunctions {
|
|
|
1391
1489
|
|
|
1392
1490
|
declare const iam: IamFunctions;
|
|
1393
1491
|
|
|
1394
|
-
export { type AgreementConfig, AgreementConfigSchema, type AgreementDisplay, type ApiKey, type ApiKeyConfig, ApiKeyConfigSchema, type ApiKeyCredentials, type ApiKeyOperations, type AuthResult, type AuthStrategy, type AuthStrategyType, AuthStrategyTypeSchema, type AuthnOperations, type AuthzOperations, type CreateApiKeyOptions, type CreateApiKeyResult, type CreateSessionOptions, type Credentials, HaiIamError, type IamConfig, type IamConfigInput, IamConfigSchema, type IamConfigSettingsInput, type IamFunctions, type LdapClientFactory, type LdapConfig, LdapConfigSchema, type LdapCredentials, type ListUsersOptions, type LoginConfig, LoginConfigSchema, type OtpConfig, OtpConfigSchema, type OtpCredentials, type PasswordConfig, PasswordConfigSchema, type PasswordCredentials, type PasswordResetConfig, PasswordResetConfigSchema, type Permission, type PermissionQueryOptions, type PermissionType, type RbacConfig, RbacConfigSchema, type RegisterConfig, RegisterConfigSchema, type RegisterOptions, type RegisterResult, type Role, type SecurityConfig, SecurityConfigSchema, type Session, type SessionConfig, SessionConfigSchema, type SessionData, type SessionFieldUpdates, type SessionOperations, type StoredUser, type TokenPair, type UpdateCurrentUserInput, type User, type UserOperations, iam };
|
|
1492
|
+
export { type AgreementConfig, AgreementConfigSchema, type AgreementDisplay, type ApiKey, type ApiKeyConfig, ApiKeyConfigSchema, type ApiKeyCredentials, type ApiKeyOperations, type AuthResult, type AuthStrategy, type AuthStrategyType, AuthStrategyTypeSchema, type AuthnOperations, type AuthzOperations, type ConsumeTicketOptions, type ConsumedTicket, type CreateApiKeyOptions, type CreateApiKeyResult, type CreateSessionOptions, type Credentials, HaiIamError, type IamConfig, type IamConfigInput, IamConfigSchema, type IamConfigSettingsInput, type IamFunctions, type IssueTicketOptions, type IssuedTicket, type LdapClientFactory, type LdapConfig, LdapConfigSchema, type LdapCredentials, type ListUsersOptions, type LoginConfig, LoginConfigSchema, type OtpConfig, OtpConfigSchema, type OtpCredentials, type PasswordConfig, PasswordConfigSchema, type PasswordCredentials, type PasswordResetConfig, PasswordResetConfigSchema, type Permission, type PermissionQueryOptions, type PermissionType, type RbacConfig, RbacConfigSchema, type RegisterConfig, RegisterConfigSchema, type RegisterOptions, type RegisterResult, type Role, type SecurityConfig, SecurityConfigSchema, type Session, type SessionConfig, SessionConfigSchema, type SessionData, type SessionFieldUpdates, type SessionOperations, type StoredUser, type TicketGrant, type TicketOperations, type TokenPair, type UpdateCurrentUserInput, type User, type UserOperations, iam };
|
package/dist/index.js
CHANGED
|
@@ -200,6 +200,9 @@ var en_US_default = {
|
|
|
200
200
|
iam_ldapStrategyRequired: "LDAP authentication requires LDAP strategy configuration",
|
|
201
201
|
iam_tokenExpired: "Token has expired",
|
|
202
202
|
iam_tokenInvalid: "Invalid token",
|
|
203
|
+
iam_ticketInvalid: "Invalid, already used, or unknown ticket",
|
|
204
|
+
iam_ticketExpired: "Ticket has expired",
|
|
205
|
+
iam_ticketIssueFailed: "Failed to issue or read ticket",
|
|
203
206
|
iam_createSessionFailed: "Failed to create session",
|
|
204
207
|
iam_sessionExpired: "Session has expired",
|
|
205
208
|
iam_invalidRefreshToken: "Invalid refresh token",
|
|
@@ -380,6 +383,9 @@ var zh_CN_default = {
|
|
|
380
383
|
iam_ldapStrategyRequired: "LDAP \u8BA4\u8BC1\u9700\u8981\u914D\u7F6E LDAP \u7B56\u7565",
|
|
381
384
|
iam_tokenExpired: "\u4EE4\u724C\u5DF2\u8FC7\u671F",
|
|
382
385
|
iam_tokenInvalid: "\u4EE4\u724C\u65E0\u6548",
|
|
386
|
+
iam_ticketInvalid: "\u7968\u636E\u65E0\u6548\u3001\u5DF2\u4F7F\u7528\u6216\u4E0D\u5B58\u5728",
|
|
387
|
+
iam_ticketExpired: "\u7968\u636E\u5DF2\u8FC7\u671F",
|
|
388
|
+
iam_ticketIssueFailed: "\u7968\u636E\u7B7E\u53D1\u6216\u8BFB\u53D6\u5931\u8D25",
|
|
383
389
|
iam_createSessionFailed: "\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25",
|
|
384
390
|
iam_sessionExpired: "\u4F1A\u8BDD\u5DF2\u5931\u6548",
|
|
385
391
|
iam_invalidRefreshToken: "\u65E0\u6548\u7684\u5237\u65B0\u4EE4\u724C",
|
|
@@ -568,6 +574,9 @@ var IamErrorInfo = {
|
|
|
568
574
|
LDAP_CONNECTION_FAILED: "301:500",
|
|
569
575
|
LDAP_BIND_FAILED: "302:401",
|
|
570
576
|
LDAP_SEARCH_FAILED: "303:500",
|
|
577
|
+
TICKET_INVALID: "110:401",
|
|
578
|
+
TICKET_EXPIRED: "111:401",
|
|
579
|
+
TICKET_ISSUE_FAILED: "112:500",
|
|
571
580
|
REPOSITORY_ERROR: "401:500",
|
|
572
581
|
NOT_FOUND: "402:404",
|
|
573
582
|
CONFLICT: "403:409",
|
|
@@ -685,7 +694,7 @@ var DbApiKeyRepository = class extends BaseReldbCrudRepository {
|
|
|
685
694
|
});
|
|
686
695
|
}
|
|
687
696
|
async insert(data, tx) {
|
|
688
|
-
const result = await this.create(data, tx);
|
|
697
|
+
const result = await this.create({ ...data }, tx);
|
|
689
698
|
if (!result.success) {
|
|
690
699
|
return err(
|
|
691
700
|
HaiIamError.REPOSITORY_ERROR,
|
|
@@ -724,7 +733,7 @@ var DbApiKeyRepository = class extends BaseReldbCrudRepository {
|
|
|
724
733
|
return ok(result.data);
|
|
725
734
|
}
|
|
726
735
|
async updateFields(id, data, tx) {
|
|
727
|
-
const result = await this.updateById(id, data, tx);
|
|
736
|
+
const result = await this.updateById(id, { ...data }, tx);
|
|
728
737
|
if (!result.success) {
|
|
729
738
|
return err(
|
|
730
739
|
HaiIamError.REPOSITORY_ERROR,
|
|
@@ -4000,6 +4009,62 @@ function buildSessionFunctions(config) {
|
|
|
4000
4009
|
}
|
|
4001
4010
|
};
|
|
4002
4011
|
}
|
|
4012
|
+
var logger9 = core.logger.child({ module: "iam", scope: "ticket" });
|
|
4013
|
+
var TICKET_KEY_PREFIX = "hai:iam:ticket:";
|
|
4014
|
+
var DEFAULT_TICKET_TTL_MS = 3e4;
|
|
4015
|
+
function buildTicketKey(ticket) {
|
|
4016
|
+
return `${TICKET_KEY_PREFIX}${ticket}`;
|
|
4017
|
+
}
|
|
4018
|
+
function createTicketOperations() {
|
|
4019
|
+
return {
|
|
4020
|
+
async issue(options) {
|
|
4021
|
+
const ttlMs = options.ttlMs !== void 0 && options.ttlMs > 0 ? options.ttlMs : DEFAULT_TICKET_TTL_MS;
|
|
4022
|
+
const ticket = generateToken();
|
|
4023
|
+
const expiresAt = Date.now() + ttlMs;
|
|
4024
|
+
const record = {
|
|
4025
|
+
subjectId: options.subjectId,
|
|
4026
|
+
purpose: options.purpose,
|
|
4027
|
+
grant: options.grant ?? {},
|
|
4028
|
+
expiresAt
|
|
4029
|
+
};
|
|
4030
|
+
const saved = await cache.kv.set(buildTicketKey(ticket), record, { px: ttlMs, nx: true });
|
|
4031
|
+
if (!saved.success) {
|
|
4032
|
+
logger9.warn("Failed to issue ticket", { purpose: options.purpose });
|
|
4033
|
+
return err(HaiIamError.TICKET_ISSUE_FAILED, iamM("iam_ticketIssueFailed"));
|
|
4034
|
+
}
|
|
4035
|
+
return ok({ ticket, expiresAt });
|
|
4036
|
+
},
|
|
4037
|
+
async consume(ticket, options) {
|
|
4038
|
+
if (!ticket) {
|
|
4039
|
+
return err(HaiIamError.TICKET_INVALID, iamM("iam_ticketInvalid"));
|
|
4040
|
+
}
|
|
4041
|
+
const key = buildTicketKey(ticket);
|
|
4042
|
+
const found = await cache.kv.get(key);
|
|
4043
|
+
if (!found.success) {
|
|
4044
|
+
return err(HaiIamError.TICKET_ISSUE_FAILED, iamM("iam_ticketIssueFailed"));
|
|
4045
|
+
}
|
|
4046
|
+
const record = found.data;
|
|
4047
|
+
if (!record) {
|
|
4048
|
+
return err(HaiIamError.TICKET_INVALID, iamM("iam_ticketInvalid"));
|
|
4049
|
+
}
|
|
4050
|
+
const removed = await cache.kv.del(key);
|
|
4051
|
+
if (!removed.success || removed.data !== 1) {
|
|
4052
|
+
return err(HaiIamError.TICKET_INVALID, iamM("iam_ticketInvalid"));
|
|
4053
|
+
}
|
|
4054
|
+
if (record.expiresAt <= Date.now()) {
|
|
4055
|
+
return err(HaiIamError.TICKET_EXPIRED, iamM("iam_ticketExpired"));
|
|
4056
|
+
}
|
|
4057
|
+
if (options?.purpose !== void 0 && options.purpose !== record.purpose) {
|
|
4058
|
+
return err(HaiIamError.TICKET_INVALID, iamM("iam_ticketInvalid"));
|
|
4059
|
+
}
|
|
4060
|
+
return ok({
|
|
4061
|
+
subjectId: record.subjectId,
|
|
4062
|
+
purpose: record.purpose,
|
|
4063
|
+
grant: record.grant
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4003
4068
|
function hashResetToken(token) {
|
|
4004
4069
|
const result = crypto$1.hash.hash(token);
|
|
4005
4070
|
if (!result.success) {
|
|
@@ -4181,7 +4246,7 @@ function createCacheResetTokenRepository() {
|
|
|
4181
4246
|
}
|
|
4182
4247
|
|
|
4183
4248
|
// src/user/iam-user-functions.ts
|
|
4184
|
-
var
|
|
4249
|
+
var logger10 = core.logger.child({ module: "iam", scope: "user" });
|
|
4185
4250
|
async function createUserOperations(deps) {
|
|
4186
4251
|
try {
|
|
4187
4252
|
const { config, passwordStrategyResult, sessionFunctions, authzFunctions, onPasswordResetRequest } = deps;
|
|
@@ -4196,10 +4261,10 @@ async function createUserOperations(deps) {
|
|
|
4196
4261
|
config,
|
|
4197
4262
|
onPasswordResetRequest
|
|
4198
4263
|
});
|
|
4199
|
-
|
|
4264
|
+
logger10.info("User sub-feature initialized");
|
|
4200
4265
|
return ok(functions);
|
|
4201
4266
|
} catch (error) {
|
|
4202
|
-
|
|
4267
|
+
logger10.error("User sub-feature initialization failed", { error });
|
|
4203
4268
|
return err(
|
|
4204
4269
|
HaiIamError.CONFIG_ERROR,
|
|
4205
4270
|
iamM("iam_initComponentFailed"),
|
|
@@ -4345,7 +4410,7 @@ function buildRegistrationOps(ctx) {
|
|
|
4345
4410
|
}
|
|
4346
4411
|
const createdUser = createdUserResult.data;
|
|
4347
4412
|
await assignDefaultRole(createdUser.id);
|
|
4348
|
-
|
|
4413
|
+
logger10.info("User registered", { userId: createdUser.id, username: options.username });
|
|
4349
4414
|
return ok({
|
|
4350
4415
|
user: toUser(createdUser),
|
|
4351
4416
|
agreements: buildAgreementDisplay(agreementConfig, agreementConfig.showOnRegister)
|
|
@@ -4540,7 +4605,7 @@ function buildUserMutationOps(ctx) {
|
|
|
4540
4605
|
return ok(toUser(updatedResult.data));
|
|
4541
4606
|
},
|
|
4542
4607
|
async deleteUser(userId) {
|
|
4543
|
-
|
|
4608
|
+
logger10.debug("Deleting user", { userId });
|
|
4544
4609
|
const userResult = await userRepository.findById(userId);
|
|
4545
4610
|
if (!userResult.success) {
|
|
4546
4611
|
return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
|
|
@@ -4580,7 +4645,7 @@ function buildUserMutationOps(ctx) {
|
|
|
4580
4645
|
);
|
|
4581
4646
|
}
|
|
4582
4647
|
await sessionFunctions.deleteByUserId(userId);
|
|
4583
|
-
|
|
4648
|
+
logger10.info("User deleted", { userId });
|
|
4584
4649
|
return ok(void 0);
|
|
4585
4650
|
}
|
|
4586
4651
|
};
|
|
@@ -4590,7 +4655,7 @@ function buildPasswordChangeOps(ctx) {
|
|
|
4590
4655
|
const { validatePassword, hashPassword } = ctx;
|
|
4591
4656
|
return {
|
|
4592
4657
|
async adminResetPassword(userId, newPassword) {
|
|
4593
|
-
|
|
4658
|
+
logger10.debug("Admin resetting user password", { userId });
|
|
4594
4659
|
const userResult = await userRepository.findById(userId);
|
|
4595
4660
|
if (!userResult.success) {
|
|
4596
4661
|
return mapRepositoryError("iam_queryUserFailed", userResult.error.message);
|
|
@@ -4615,7 +4680,7 @@ function buildPasswordChangeOps(ctx) {
|
|
|
4615
4680
|
return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
|
|
4616
4681
|
}
|
|
4617
4682
|
await sessionFunctions.deleteByUserId(userId);
|
|
4618
|
-
|
|
4683
|
+
logger10.info("Admin reset password", { userId });
|
|
4619
4684
|
return ok(void 0);
|
|
4620
4685
|
},
|
|
4621
4686
|
async changePassword(userId, oldPassword, newPassword) {
|
|
@@ -4668,7 +4733,7 @@ function buildPasswordChangeOps(ctx) {
|
|
|
4668
4733
|
return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
|
|
4669
4734
|
}
|
|
4670
4735
|
await sessionFunctions.deleteByUserId(userId);
|
|
4671
|
-
|
|
4736
|
+
logger10.info("Password changed", { userId });
|
|
4672
4737
|
return ok(void 0);
|
|
4673
4738
|
},
|
|
4674
4739
|
/**
|
|
@@ -4693,15 +4758,15 @@ function buildPasswordResetOps(ctx) {
|
|
|
4693
4758
|
const { validatePassword, hashPassword } = ctx;
|
|
4694
4759
|
return {
|
|
4695
4760
|
async requestPasswordReset(identifier) {
|
|
4696
|
-
|
|
4761
|
+
logger10.debug("Password reset requested", { identifier });
|
|
4697
4762
|
const resetConfig = ctx.resetConfig;
|
|
4698
4763
|
const userResult = await userRepository.findByIdentifier(identifier);
|
|
4699
4764
|
if (!userResult.success) {
|
|
4700
|
-
|
|
4765
|
+
logger10.warn("Failed to look up user for password reset", { identifier });
|
|
4701
4766
|
return ok(void 0);
|
|
4702
4767
|
}
|
|
4703
4768
|
if (!userResult.data) {
|
|
4704
|
-
|
|
4769
|
+
logger10.debug("User not found for password reset, returning ok to prevent enumeration", { identifier });
|
|
4705
4770
|
return ok(void 0);
|
|
4706
4771
|
}
|
|
4707
4772
|
const user = toUser(userResult.data);
|
|
@@ -4715,16 +4780,16 @@ function buildPasswordResetOps(ctx) {
|
|
|
4715
4780
|
try {
|
|
4716
4781
|
await onPasswordResetRequest(user, token, expiresAt);
|
|
4717
4782
|
} catch (callbackError) {
|
|
4718
|
-
|
|
4783
|
+
logger10.error("Password reset callback failed", { userId: user.id, error: callbackError });
|
|
4719
4784
|
}
|
|
4720
4785
|
} else {
|
|
4721
|
-
|
|
4786
|
+
logger10.warn("No password reset callback configured, token will not be delivered to user", { userId: user.id });
|
|
4722
4787
|
}
|
|
4723
|
-
|
|
4788
|
+
logger10.info("Password reset token generated", { userId: user.id });
|
|
4724
4789
|
return ok(void 0);
|
|
4725
4790
|
},
|
|
4726
4791
|
async confirmPasswordReset(token, newPassword) {
|
|
4727
|
-
|
|
4792
|
+
logger10.debug("Confirming password reset");
|
|
4728
4793
|
const resetConfig = ctx.resetConfig;
|
|
4729
4794
|
const validateResult = validatePassword(newPassword);
|
|
4730
4795
|
if (!validateResult.success)
|
|
@@ -4756,7 +4821,7 @@ function buildPasswordResetOps(ctx) {
|
|
|
4756
4821
|
}
|
|
4757
4822
|
await resetTokenRepository.removeToken(token);
|
|
4758
4823
|
await sessionFunctions.deleteByUserId(userId);
|
|
4759
|
-
|
|
4824
|
+
logger10.info("Password reset confirmed", { userId });
|
|
4760
4825
|
return ok(void 0);
|
|
4761
4826
|
}
|
|
4762
4827
|
};
|
|
@@ -4782,7 +4847,7 @@ function buildUserFunctions(deps) {
|
|
|
4782
4847
|
}
|
|
4783
4848
|
|
|
4784
4849
|
// src/iam-main.ts
|
|
4785
|
-
var
|
|
4850
|
+
var logger11 = core.logger.child({ module: "iam", scope: "main" });
|
|
4786
4851
|
var initInProgress = false;
|
|
4787
4852
|
var currentConfig = null;
|
|
4788
4853
|
var currentAuth = null;
|
|
@@ -4790,6 +4855,7 @@ var currentUser = null;
|
|
|
4790
4855
|
var currentAuthz = null;
|
|
4791
4856
|
var currentSession = null;
|
|
4792
4857
|
var currentApiKey = null;
|
|
4858
|
+
var currentTicket = null;
|
|
4793
4859
|
var notInitialized = core.module.createNotInitializedKit(
|
|
4794
4860
|
HaiIamError.NOT_INITIALIZED,
|
|
4795
4861
|
() => iamM("iam_notInitialized")
|
|
@@ -4798,6 +4864,7 @@ var notInitializedAuth = notInitialized.proxy();
|
|
|
4798
4864
|
var notInitializedAuthz = notInitialized.proxy();
|
|
4799
4865
|
var notInitializedSession = notInitialized.proxy();
|
|
4800
4866
|
var notInitializedApiKey = notInitialized.proxy();
|
|
4867
|
+
var notInitializedTicket = notInitialized.proxy();
|
|
4801
4868
|
var syncUserProxy = notInitialized.proxy("sync");
|
|
4802
4869
|
var asyncUserProxy = notInitialized.proxy();
|
|
4803
4870
|
var notInitializedUser = new Proxy({}, {
|
|
@@ -4808,7 +4875,7 @@ var notInitializedUser = new Proxy({}, {
|
|
|
4808
4875
|
var iam = {
|
|
4809
4876
|
async init(config) {
|
|
4810
4877
|
if (initInProgress) {
|
|
4811
|
-
|
|
4878
|
+
logger11.warn("IAM init already in progress, skipping concurrent call");
|
|
4812
4879
|
return err(
|
|
4813
4880
|
HaiIamError.CONFIG_ERROR,
|
|
4814
4881
|
iamM("iam_initInProgress")
|
|
@@ -4817,11 +4884,11 @@ var iam = {
|
|
|
4817
4884
|
initInProgress = true;
|
|
4818
4885
|
try {
|
|
4819
4886
|
if (currentConfig !== null) {
|
|
4820
|
-
|
|
4887
|
+
logger11.warn("IAM module is already initialized, reinitializing");
|
|
4821
4888
|
await iam.close();
|
|
4822
4889
|
}
|
|
4823
4890
|
const { ldapClientFactory, ldapSyncUser, onPasswordResetRequest, onOtpSendEmail, onOtpSendSms, ...settingsInput } = config;
|
|
4824
|
-
|
|
4891
|
+
logger11.info("Initializing IAM module");
|
|
4825
4892
|
if (!reldb.isInitialized) {
|
|
4826
4893
|
return err(
|
|
4827
4894
|
HaiIamError.CONFIG_ERROR,
|
|
@@ -4846,7 +4913,7 @@ var iam = {
|
|
|
4846
4913
|
}
|
|
4847
4914
|
const parseResult = IamConfigSchema.safeParse(settingsInput);
|
|
4848
4915
|
if (!parseResult.success) {
|
|
4849
|
-
|
|
4916
|
+
logger11.error("IAM config validation failed", { error: parseResult.error.message });
|
|
4850
4917
|
return err(
|
|
4851
4918
|
HaiIamError.CONFIG_ERROR,
|
|
4852
4919
|
iamM("iam_configError", { params: { error: parseResult.error.message } }),
|
|
@@ -4895,6 +4962,7 @@ var iam = {
|
|
|
4895
4962
|
currentUser = userResult.data;
|
|
4896
4963
|
currentConfig = parsed;
|
|
4897
4964
|
currentApiKey = authnResult.data.apiKeyFunctions;
|
|
4965
|
+
currentTicket = createTicketOperations();
|
|
4898
4966
|
const authn = authnResult.data.authn;
|
|
4899
4967
|
currentAuth = {
|
|
4900
4968
|
...authn,
|
|
@@ -4906,10 +4974,10 @@ var iam = {
|
|
|
4906
4974
|
return authn.login({ identifier: options.username, password: options.password });
|
|
4907
4975
|
}
|
|
4908
4976
|
};
|
|
4909
|
-
|
|
4977
|
+
logger11.info("IAM module initialized");
|
|
4910
4978
|
return ok(void 0);
|
|
4911
4979
|
} catch (error) {
|
|
4912
|
-
|
|
4980
|
+
logger11.error("IAM module initialization failed", { error });
|
|
4913
4981
|
return err(
|
|
4914
4982
|
HaiIamError.CONFIG_ERROR,
|
|
4915
4983
|
iamM("iam_initFailed"),
|
|
@@ -4934,6 +5002,9 @@ var iam = {
|
|
|
4934
5002
|
get apiKey() {
|
|
4935
5003
|
return currentApiKey ?? notInitializedApiKey;
|
|
4936
5004
|
},
|
|
5005
|
+
get ticket() {
|
|
5006
|
+
return currentTicket ?? notInitializedTicket;
|
|
5007
|
+
},
|
|
4937
5008
|
get config() {
|
|
4938
5009
|
return currentConfig ? core.sanitize.sanitizeSensitiveFields(currentConfig) : null;
|
|
4939
5010
|
},
|
|
@@ -4945,15 +5016,16 @@ var iam = {
|
|
|
4945
5016
|
},
|
|
4946
5017
|
async close() {
|
|
4947
5018
|
if (currentConfig === null && currentAuth === null && currentUser === null && currentAuthz === null && currentSession === null) {
|
|
4948
|
-
|
|
5019
|
+
logger11.info("IAM module already closed, skipping");
|
|
4949
5020
|
return;
|
|
4950
5021
|
}
|
|
4951
|
-
|
|
5022
|
+
logger11.info("Closing IAM module");
|
|
4952
5023
|
currentAuth = null;
|
|
4953
5024
|
currentUser = null;
|
|
4954
5025
|
currentAuthz = null;
|
|
4955
5026
|
currentSession = null;
|
|
4956
5027
|
currentApiKey = null;
|
|
5028
|
+
currentTicket = null;
|
|
4957
5029
|
currentConfig = null;
|
|
4958
5030
|
resetApiKeyRepoSingleton();
|
|
4959
5031
|
resetOtpRepoSingleton();
|
|
@@ -4961,7 +5033,7 @@ var iam = {
|
|
|
4961
5033
|
resetResetTokenRepoSingleton();
|
|
4962
5034
|
resetRoleRepoSingleton();
|
|
4963
5035
|
resetPermissionRepoSingleton();
|
|
4964
|
-
|
|
5036
|
+
logger11.info("IAM module closed");
|
|
4965
5037
|
}
|
|
4966
5038
|
};
|
|
4967
5039
|
|