@h-ai/iam 0.1.0-alpha.73 → 0.1.0-alpha.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -3
- package/README.ja.md +9 -3
- package/README.md +9 -3
- package/dist/browser.d.ts +50 -1
- package/dist/browser.js +1 -1
- package/dist/{chunk-22SERPFD.js → chunk-KPG3M34Q.js} +37 -15
- package/dist/chunk-KPG3M34Q.js.map +1 -0
- package/dist/index.js +129 -18
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/dist/chunk-22SERPFD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HaiIamError, IamConfigSchema, SessionConfigSchema, SecurityConfigSchema, LoginConfigSchema, OtpConfigSchema, ApiKeyConfigSchema, AuthUiConfigSchema, PasswordConfigSchema, RbacConfigSchema, AgreementConfigSchema, PasswordResetConfigSchema, RegisterConfigSchema } from './chunk-
|
|
2
|
-
export { AccountConfigSchema, AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, AuthUiConfigSchema, CaptchaConfigSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginAccessConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema } from './chunk-
|
|
1
|
+
import { HaiIamError, IamConfigSchema, SessionConfigSchema, SecurityConfigSchema, LoginConfigSchema, OtpConfigSchema, ApiKeyConfigSchema, AuthUiConfigSchema, PasswordConfigSchema, RbacConfigSchema, AgreementConfigSchema, PasswordResetConfigSchema, RegisterConfigSchema } from './chunk-KPG3M34Q.js';
|
|
2
|
+
export { AccountConfigSchema, AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, AuthUiConfigSchema, CaptchaConfigSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginAccessConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema } from './chunk-KPG3M34Q.js';
|
|
3
3
|
import { cache } from '@h-ai/cache';
|
|
4
4
|
import { core, err, ok } from '@h-ai/core';
|
|
5
5
|
import { crypto as crypto$1 } from '@h-ai/crypto';
|
|
@@ -213,7 +213,9 @@ var en_US_default = {
|
|
|
213
213
|
iam_captchaRequired: "Complete the security check first",
|
|
214
214
|
iam_captchaInvalid: "Security check is invalid or expired; try again",
|
|
215
215
|
iam_captchaDisabled: "Security challenges are disabled",
|
|
216
|
-
iam_captchaUnavailable: "Security check is temporarily unavailable"
|
|
216
|
+
iam_captchaUnavailable: "Security check is temporarily unavailable",
|
|
217
|
+
iam_sensitiveOperationsInvalid: "Invalid sensitive operation IDs: use at most 1000 entries of 1\u2013128 characters each.",
|
|
218
|
+
iam_terminalTypeInvalid: "Invalid terminal type: use web, app, desktop or api."
|
|
217
219
|
};
|
|
218
220
|
|
|
219
221
|
// messages/ja-JP.json
|
|
@@ -419,7 +421,9 @@ var ja_JP_default = {
|
|
|
419
421
|
iam_captchaRequired: "\u5148\u306B\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u78BA\u8A8D\u3092\u5B8C\u4E86\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
420
422
|
iam_captchaInvalid: "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u78BA\u8A8D\u304C\u7121\u52B9\u307E\u305F\u306F\u671F\u9650\u5207\u308C\u3067\u3059\u3002\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
421
423
|
iam_captchaDisabled: "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u78BA\u8A8D\u306F\u7121\u52B9\u3067\u3059",
|
|
422
|
-
iam_captchaUnavailable: "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u78BA\u8A8D\u3092\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093"
|
|
424
|
+
iam_captchaUnavailable: "\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u78BA\u8A8D\u3092\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
425
|
+
iam_sensitiveOperationsInvalid: "\u91CD\u8981\u64CD\u4F5C ID \u306F\u6700\u59271000\u4EF6\u3001\u54041\uFF5E128\u6587\u5B57\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
426
|
+
iam_terminalTypeInvalid: "\u7AEF\u672B\u7A2E\u5225\u306F web\u3001app\u3001desktop\u3001api \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
423
427
|
};
|
|
424
428
|
|
|
425
429
|
// messages/zh-CN.json
|
|
@@ -625,7 +629,9 @@ var zh_CN_default = {
|
|
|
625
629
|
iam_captchaRequired: "\u8BF7\u5148\u5B8C\u6210\u5B89\u5168\u9A8C\u8BC1",
|
|
626
630
|
iam_captchaInvalid: "\u5B89\u5168\u9A8C\u8BC1\u65E0\u6548\u6216\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u8BD5",
|
|
627
631
|
iam_captchaDisabled: "\u5B89\u5168\u9A8C\u8BC1\u672A\u542F\u7528",
|
|
628
|
-
iam_captchaUnavailable: "\u5B89\u5168\u9A8C\u8BC1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
|
|
632
|
+
iam_captchaUnavailable: "\u5B89\u5168\u9A8C\u8BC1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
633
|
+
iam_sensitiveOperationsInvalid: "\u654F\u611F\u64CD\u4F5C ID \u5217\u8868\u65E0\u6548\uFF1A\u6700\u591A 1000 \u9879\uFF0C\u6BCF\u9879\u4E3A 1\u2013128 \u5B57\u7B26\u3002",
|
|
634
|
+
iam_terminalTypeInvalid: "\u7EC8\u7AEF\u7C7B\u578B\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 web\u3001app\u3001desktop \u6216 api\u3002"
|
|
629
635
|
};
|
|
630
636
|
|
|
631
637
|
// src/iam-i18n.ts
|
|
@@ -882,6 +888,7 @@ function buildSession(options, now, sessionTtl, accessToken, credentialVersion)
|
|
|
882
888
|
roles: options.roles ?? [],
|
|
883
889
|
permissions: options.permissions ?? [],
|
|
884
890
|
source: options.source,
|
|
891
|
+
terminalType: options.terminalType ?? "web",
|
|
885
892
|
accessToken,
|
|
886
893
|
createdAt: now,
|
|
887
894
|
startedAt: now,
|
|
@@ -1490,7 +1497,7 @@ function createApiKeyStrategy(config) {
|
|
|
1490
1497
|
if (!isUserActive(userResult.data)) {
|
|
1491
1498
|
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1492
1499
|
}
|
|
1493
|
-
logger2.
|
|
1500
|
+
logger2.debug("API Key authentication succeeded", { userId: userResult.data.id, keyId: apiKey.id });
|
|
1494
1501
|
return ok({ user: { ...toUser(userResult.data), credentialVersion: userResult.data.credentialVersion ?? "" }, apiKey });
|
|
1495
1502
|
}
|
|
1496
1503
|
};
|
|
@@ -1892,7 +1899,7 @@ function createLdapStrategy(config) {
|
|
|
1892
1899
|
const resetResult = await resetLoginFailures(userRepository, storedUser, failurePolicy);
|
|
1893
1900
|
if (!resetResult.success)
|
|
1894
1901
|
return resetResult;
|
|
1895
|
-
logger4.
|
|
1902
|
+
logger4.debug("LDAP authentication succeeded", { userId: storedUser.id });
|
|
1896
1903
|
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
1897
1904
|
} finally {
|
|
1898
1905
|
await client.unbind();
|
|
@@ -2080,7 +2087,7 @@ function createOtpStrategy(config) {
|
|
|
2080
2087
|
const resetResult = await resetLoginFailures(config.userRepository, storedUser, failurePolicy);
|
|
2081
2088
|
if (!resetResult.success)
|
|
2082
2089
|
return resetResult;
|
|
2083
|
-
logger5.
|
|
2090
|
+
logger5.debug("OTP authentication succeeded", { userId: storedUser.id });
|
|
2084
2091
|
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
2085
2092
|
}
|
|
2086
2093
|
};
|
|
@@ -2243,7 +2250,7 @@ function createPasswordStrategy(config) {
|
|
|
2243
2250
|
const resetResult = await resetLoginFailures(config.userRepository, storedUser, failurePolicy);
|
|
2244
2251
|
if (!resetResult.success)
|
|
2245
2252
|
return resetResult;
|
|
2246
|
-
logger6.
|
|
2253
|
+
logger6.debug("Password authentication succeeded", { userId: storedUser.id });
|
|
2247
2254
|
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
2248
2255
|
}
|
|
2249
2256
|
};
|
|
@@ -2430,7 +2437,8 @@ function buildAuthnOperations(deps) {
|
|
|
2430
2437
|
avatarUrl: user.avatarUrl,
|
|
2431
2438
|
...effective,
|
|
2432
2439
|
apiKeyId: apiKey?.id,
|
|
2433
|
-
apiKeyScopes: apiKey?.scopes
|
|
2440
|
+
apiKeyScopes: apiKey?.scopes,
|
|
2441
|
+
terminalType: context?.terminalType ?? (apiKey ? "api" : "web")
|
|
2434
2442
|
});
|
|
2435
2443
|
if (!sessionResult.success) {
|
|
2436
2444
|
return sessionResult;
|
|
@@ -2476,6 +2484,8 @@ function buildAuthnOperations(deps) {
|
|
|
2476
2484
|
logger7.warn("Login failed", { type, reason: authResult.error.code });
|
|
2477
2485
|
return authResult;
|
|
2478
2486
|
}
|
|
2487
|
+
if (credentials.expectedUserId && credentials.expectedUserId !== authResult.data.id)
|
|
2488
|
+
return err(HaiIamError.INVALID_CREDENTIALS, iamM("iam_passwordWrong"));
|
|
2479
2489
|
const result = await buildAuthResult(authResult.data, void 0, context);
|
|
2480
2490
|
if (result.success) {
|
|
2481
2491
|
logger7.info("Login succeeded", { type, userId: authResult.data.id });
|
|
@@ -2503,7 +2513,14 @@ function buildAuthnOperations(deps) {
|
|
|
2503
2513
|
}
|
|
2504
2514
|
const tickets = createTicketOperations();
|
|
2505
2515
|
const sensitive = config.reauthentication;
|
|
2506
|
-
|
|
2516
|
+
async function requiresProof(operation) {
|
|
2517
|
+
if (!sensitive?.enabled)
|
|
2518
|
+
return ok(false);
|
|
2519
|
+
const configured = await authzFunctions.getSensitiveOperations();
|
|
2520
|
+
if (!configured.success)
|
|
2521
|
+
return configured;
|
|
2522
|
+
return ok(sensitive.operations.includes(operation) || configured.data.includes(operation));
|
|
2523
|
+
}
|
|
2507
2524
|
return { loginRegisteredUser: async (user, context) => {
|
|
2508
2525
|
const stored = await userRepository.findById(user.id);
|
|
2509
2526
|
if (!stored.success)
|
|
@@ -2514,7 +2531,10 @@ function buildAuthnOperations(deps) {
|
|
|
2514
2531
|
}, authn: {
|
|
2515
2532
|
createCaptcha: (input) => createIamCaptcha(config.captcha, input),
|
|
2516
2533
|
async reauthenticate(accessToken, password, operation) {
|
|
2517
|
-
|
|
2534
|
+
const required = await requiresProof(operation);
|
|
2535
|
+
if (!required.success)
|
|
2536
|
+
return required;
|
|
2537
|
+
if (!required.data)
|
|
2518
2538
|
return err(HaiIamError.PERMISSION_DENIED, iamM("iam_reauthenticationRequired"));
|
|
2519
2539
|
const current = await verifyCurrentSession(accessToken);
|
|
2520
2540
|
if (!current.success)
|
|
@@ -2535,7 +2555,10 @@ function buildAuthnOperations(deps) {
|
|
|
2535
2555
|
const current = await verifyCurrentSession(accessToken);
|
|
2536
2556
|
if (!current.success)
|
|
2537
2557
|
return current;
|
|
2538
|
-
|
|
2558
|
+
const required = await requiresProof(operation);
|
|
2559
|
+
if (!required.success)
|
|
2560
|
+
return required;
|
|
2561
|
+
if (!required.data)
|
|
2539
2562
|
return ok(void 0);
|
|
2540
2563
|
if (!ticket)
|
|
2541
2564
|
return err(HaiIamError.REAUTHENTICATION_REQUIRED, iamM("iam_reauthenticationRequired"));
|
|
@@ -2590,6 +2613,7 @@ function buildAuthnOperations(deps) {
|
|
|
2590
2613
|
}
|
|
2591
2614
|
var TABLE_NAME3 = "hai_iam_permissions";
|
|
2592
2615
|
var PERMISSION_FIELDS = [
|
|
2616
|
+
{ fieldName: "sensitiveOperations", columnName: "sensitive_operations", def: { type: "JSON" }, select: true, create: true, update: true },
|
|
2593
2617
|
{
|
|
2594
2618
|
fieldName: "id",
|
|
2595
2619
|
columnName: "id",
|
|
@@ -2679,6 +2703,27 @@ async function createDbPermissionRepository() {
|
|
|
2679
2703
|
return repo;
|
|
2680
2704
|
}
|
|
2681
2705
|
var DbPermissionRepository = class extends BaseReldbCrudRepository {
|
|
2706
|
+
async getSensitiveOperations() {
|
|
2707
|
+
const result = await reldb.sql.query("SELECT sensitive_operations FROM hai_iam_permissions WHERE sensitive_operations IS NOT NULL");
|
|
2708
|
+
if (!result.success)
|
|
2709
|
+
return result;
|
|
2710
|
+
const operations = /* @__PURE__ */ new Set();
|
|
2711
|
+
try {
|
|
2712
|
+
for (const row of result.data) {
|
|
2713
|
+
const values = typeof row.sensitive_operations === "string" ? JSON.parse(row.sensitive_operations) : row.sensitive_operations;
|
|
2714
|
+
if (!Array.isArray(values) || values.length > 1e3)
|
|
2715
|
+
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_reauthenticationRequired"));
|
|
2716
|
+
for (const value of values) {
|
|
2717
|
+
if (typeof value !== "string" || !value.trim() || value.length > 128)
|
|
2718
|
+
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_reauthenticationRequired"));
|
|
2719
|
+
operations.add(value.trim());
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
return ok([...operations].sort());
|
|
2723
|
+
} catch {
|
|
2724
|
+
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_reauthenticationRequired"));
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2682
2727
|
constructor() {
|
|
2683
2728
|
super(reldb, {
|
|
2684
2729
|
table: TABLE_NAME3,
|
|
@@ -3428,6 +3473,22 @@ function createRbacManager(config) {
|
|
|
3428
3473
|
return ok(void 0);
|
|
3429
3474
|
}
|
|
3430
3475
|
return {
|
|
3476
|
+
getSensitiveOperations: () => rbacConfig.enabled ? permissionRepository.getSensitiveOperations() : Promise.resolve(ok([])),
|
|
3477
|
+
async setPermissionSensitiveOperations(permissionId, operations) {
|
|
3478
|
+
if (!Array.isArray(operations) || operations.length > 1e3 || operations.some((value) => typeof value !== "string" || !value.trim() || value.length > 128))
|
|
3479
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sensitiveOperationsInvalid"));
|
|
3480
|
+
const updated = await permissionRepository.updateById(permissionId, { sensitiveOperations: [...new Set(operations.map((value) => value.trim()))] });
|
|
3481
|
+
if (!updated.success)
|
|
3482
|
+
return updated;
|
|
3483
|
+
if (updated.data.changes === 0) {
|
|
3484
|
+
const existing = await permissionRepository.findById(permissionId);
|
|
3485
|
+
if (!existing.success)
|
|
3486
|
+
return existing;
|
|
3487
|
+
if (!existing.data)
|
|
3488
|
+
return err(HaiIamError.PERMISSION_NOT_FOUND, iamM("iam_permissionNotExist"));
|
|
3489
|
+
}
|
|
3490
|
+
return ok(void 0);
|
|
3491
|
+
},
|
|
3431
3492
|
async checkPermission(userId, permission) {
|
|
3432
3493
|
if (!rbacConfig.enabled) {
|
|
3433
3494
|
return ok(true);
|
|
@@ -3779,6 +3840,10 @@ function createRbacManager(config) {
|
|
|
3779
3840
|
},
|
|
3780
3841
|
// ─── 权限管理 ───
|
|
3781
3842
|
async createPermission(permission, tx) {
|
|
3843
|
+
const operations = permission.sensitiveOperations ?? [];
|
|
3844
|
+
if (!Array.isArray(operations) || operations.length > 1e3 || operations.some((value) => typeof value !== "string" || !value.trim() || value.length > 128))
|
|
3845
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sensitiveOperationsInvalid"));
|
|
3846
|
+
permission = { ...permission, sensitiveOperations: [...new Set(operations.map((value) => value.trim()))] };
|
|
3782
3847
|
const ownTx = !tx;
|
|
3783
3848
|
if (!tx) {
|
|
3784
3849
|
const txResult = await reldb.tx.begin();
|
|
@@ -4065,6 +4130,7 @@ function observeAuthorization(operations) {
|
|
|
4065
4130
|
updateRole: (id, data, tx) => observe(() => operations.updateRole(id, data, tx), { action: "role.update", resource: "iam.roles", resourceId: id, details: { fields: Object.keys(data), permissionIds: data.permissionIds } }, tx),
|
|
4066
4131
|
deleteRole: (id, tx) => observe(() => operations.deleteRole(id, tx), { action: "role.delete", resource: "iam.roles", resourceId: id }, tx),
|
|
4067
4132
|
createPermission: (input, tx) => observe(() => operations.createPermission(input, tx), (result) => ({ action: "permission.create", resource: "iam.permissions", resourceId: result.success ? result.data.id : void 0, details: { code: input.code } }), tx),
|
|
4133
|
+
setPermissionSensitiveOperations: (id, operationIds) => observe(() => operations.setPermissionSensitiveOperations(id, operationIds), { action: "permission.sensitive-operations", resource: "iam.permissions", resourceId: id, details: { operations: operationIds } }),
|
|
4068
4134
|
deletePermission: (id, tx) => observe(() => operations.deletePermission(id, tx), { action: "permission.delete", resource: "iam.permissions", resourceId: id }, tx),
|
|
4069
4135
|
assignPermissionToRole: (id, permissionId, tx) => observe(() => operations.assignPermissionToRole(id, permissionId, tx), { action: "permission.assign", resource: "iam.role-permissions", resourceId: id, details: { permissionId } }, tx),
|
|
4070
4136
|
removePermissionFromRole: (id, permissionId, tx) => observe(() => operations.removePermissionFromRole(id, permissionId, tx), { action: "permission.remove", resource: "iam.role-permissions", resourceId: id, details: { permissionId } }, tx)
|
|
@@ -4151,6 +4217,17 @@ var steps = [{
|
|
|
4151
4217
|
const added = await sql.execute("ALTER TABLE hai_iam_users ADD COLUMN first_login_failed_at TIMESTAMP");
|
|
4152
4218
|
return added.success ? ok(void 0) : added;
|
|
4153
4219
|
}
|
|
4220
|
+
}, {
|
|
4221
|
+
version: 5,
|
|
4222
|
+
name: "permission-sensitive-operations",
|
|
4223
|
+
async needed(sql) {
|
|
4224
|
+
const found = await schemaColumns(reldb, "hai_iam_permissions", sql);
|
|
4225
|
+
return found.success ? ok(found.data.length > 0 && !found.data.includes("sensitive_operations")) : found;
|
|
4226
|
+
},
|
|
4227
|
+
async apply(sql) {
|
|
4228
|
+
const added = await sql.execute("ALTER TABLE hai_iam_permissions ADD COLUMN sensitive_operations JSON");
|
|
4229
|
+
return added.success ? ok(void 0) : added;
|
|
4230
|
+
}
|
|
4154
4231
|
}];
|
|
4155
4232
|
function migrateIam(options = {}) {
|
|
4156
4233
|
return migrateSchema(reldb, "iam", steps, options);
|
|
@@ -4331,7 +4408,7 @@ function retainedUntil(entry) {
|
|
|
4331
4408
|
}
|
|
4332
4409
|
function restoreSession(session) {
|
|
4333
4410
|
const result = { ...session, createdAt: new Date(session.createdAt), startedAt: new Date(session.startedAt ?? session.createdAt), lastActiveAt: new Date(session.lastActiveAt), expiresAt: new Date(session.expiresAt) };
|
|
4334
|
-
if (typeof result.credentialVersion !== "string" || !result.version || !Number.isSafeInteger(result.maxAge) || result.maxAge <= 0 || !Array.isArray(result.roles) || !result.roles.every((role) => typeof role === "string") || !Array.isArray(result.permissions) || !result.permissions.every((permission) => typeof permission === "string") || ![result.createdAt, result.startedAt, result.lastActiveAt, result.expiresAt].every((date) => Number.isFinite(date.getTime()))) {
|
|
4411
|
+
if (result.terminalType !== void 0 && !["web", "app", "desktop", "api"].includes(result.terminalType) || typeof result.credentialVersion !== "string" || !result.version || !Number.isSafeInteger(result.maxAge) || result.maxAge <= 0 || !Array.isArray(result.roles) || !result.roles.every((role) => typeof role === "string") || !Array.isArray(result.permissions) || !result.permissions.every((permission) => typeof permission === "string") || ![result.createdAt, result.startedAt, result.lastActiveAt, result.expiresAt].every((date) => Number.isFinite(date.getTime()))) {
|
|
4335
4412
|
throw new TypeError("Invalid session state");
|
|
4336
4413
|
}
|
|
4337
4414
|
return result;
|
|
@@ -4410,12 +4487,29 @@ function createCacheSessionRepository(refreshTokenMaxAge, policy = {}) {
|
|
|
4410
4487
|
}
|
|
4411
4488
|
return {
|
|
4412
4489
|
save: (session, tokens, singleDevice = false) => mutate(getUserSessionScope(session.userId), (state) => {
|
|
4413
|
-
const
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4490
|
+
const active = Object.entries(state.sessions).filter(([, entry]) => entry.session.credentialVersion === session.credentialVersion);
|
|
4491
|
+
const terminal = session.terminalType ?? "web";
|
|
4492
|
+
const limits = policy.terminalLimits ?? {};
|
|
4493
|
+
const cap = limits[terminal];
|
|
4494
|
+
if (Object.keys(limits).length && cap === void 0)
|
|
4417
4495
|
return err(HaiIamError.SESSION_LIMIT_EXCEEDED, iamM("iam_sessionLimitExceeded"));
|
|
4496
|
+
const removed = /* @__PURE__ */ new Set();
|
|
4497
|
+
if (singleDevice) {
|
|
4498
|
+
if (policy.singleDeviceMode === "reject" && active.length)
|
|
4499
|
+
return err(HaiIamError.SESSION_LIMIT_EXCEEDED, iamM("iam_sessionLimitExceeded"));
|
|
4500
|
+
if (policy.singleDeviceMode !== "reject")
|
|
4501
|
+
Object.keys(state.sessions).forEach((token) => removed.add(token));
|
|
4502
|
+
} else if (cap) {
|
|
4503
|
+
const same = active.filter(([, entry]) => (entry.session.terminalType ?? "web") === terminal).sort(([firstToken, first], [secondToken, second]) => new Date(first.session.startedAt ?? first.session.createdAt).getTime() - new Date(second.session.startedAt ?? second.session.createdAt).getTime() || firstToken.localeCompare(secondToken));
|
|
4504
|
+
if (same.length >= cap) {
|
|
4505
|
+
if (policy.terminalLimitMode !== "replace")
|
|
4506
|
+
return err(HaiIamError.SESSION_LIMIT_EXCEEDED, iamM("iam_sessionLimitExceeded"));
|
|
4507
|
+
same.slice(0, same.length - cap + 1).forEach(([token]) => removed.add(token));
|
|
4508
|
+
}
|
|
4418
4509
|
}
|
|
4510
|
+
if (maxSessions > 0 && active.filter(([token]) => !removed.has(token)).length >= maxSessions)
|
|
4511
|
+
return err(HaiIamError.SESSION_LIMIT_EXCEEDED, iamM("iam_sessionLimitExceeded"));
|
|
4512
|
+
removed.forEach((token) => delete state.sessions[token]);
|
|
4419
4513
|
return storeSession(state, session, tokens);
|
|
4420
4514
|
}),
|
|
4421
4515
|
rotate: (previous, refreshToken, session, tokens) => mutate(getUserSessionScope(previous.userId), (state) => {
|
|
@@ -4466,6 +4560,8 @@ function createCacheSessionRepository(refreshTokenMaxAge, policy = {}) {
|
|
|
4466
4560
|
return err(HaiIamError.SESSION_NOT_FOUND, iamM("iam_sessionNotExist"));
|
|
4467
4561
|
if (!expectedVersion || entry.session.version !== expectedVersion)
|
|
4468
4562
|
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
4563
|
+
if (patch.terminalType !== void 0 && patch.terminalType !== (entry.session.terminalType ?? "web"))
|
|
4564
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
4469
4565
|
const updated = applySessionPatch(restoreSession(entry.session), patch);
|
|
4470
4566
|
if (![updated.expiresAt, updated.lastActiveAt].every((date) => Number.isFinite(date.getTime())))
|
|
4471
4567
|
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sessionMaxAgeInvalid"));
|
|
@@ -4580,6 +4676,8 @@ function buildSessionFunctions(config) {
|
|
|
4580
4676
|
const singleDevice = config.singleDevice ?? false;
|
|
4581
4677
|
const repo = config.sessionRepository;
|
|
4582
4678
|
function candidate(options, credentialVersion) {
|
|
4679
|
+
if (options.terminalType !== void 0 && !["web", "app", "desktop", "api"].includes(options.terminalType))
|
|
4680
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_terminalTypeInvalid"));
|
|
4583
4681
|
const now = /* @__PURE__ */ new Date();
|
|
4584
4682
|
const ttl = options.maxAge ?? maxAge;
|
|
4585
4683
|
if (!Number.isSafeInteger(ttl) || ttl <= 0 || !Number.isFinite(new Date(now.getTime() + ttl * 1e3).getTime()))
|
|
@@ -4738,6 +4836,7 @@ function buildSessionFunctions(config) {
|
|
|
4738
4836
|
apiKeyId: effectiveSession.apiKeyId,
|
|
4739
4837
|
apiKeyScopes: effectiveSession.apiKeyScopes,
|
|
4740
4838
|
source: oldSession.source,
|
|
4839
|
+
terminalType: oldSession.terminalType ?? "web",
|
|
4741
4840
|
maxAge: oldSession.maxAge,
|
|
4742
4841
|
data: oldSession.data ? { ...oldSession.data, _tokenPair: void 0 } : void 0
|
|
4743
4842
|
}, oldSession.credentialVersion);
|
|
@@ -5701,6 +5800,18 @@ var iam = {
|
|
|
5701
5800
|
get auth() {
|
|
5702
5801
|
return currentAuth ?? notInitializedAuth;
|
|
5703
5802
|
},
|
|
5803
|
+
async getPublicConfig() {
|
|
5804
|
+
const snapshot = currentPublicConfig ? structuredClone(currentPublicConfig) : null;
|
|
5805
|
+
const authz = currentAuthz;
|
|
5806
|
+
if (!snapshot || !authz)
|
|
5807
|
+
return notInitialized.result();
|
|
5808
|
+
if (!snapshot.reauthentication.enabled)
|
|
5809
|
+
return ok(snapshot);
|
|
5810
|
+
const configured = await authz.getSensitiveOperations();
|
|
5811
|
+
if (!configured.success)
|
|
5812
|
+
return configured;
|
|
5813
|
+
return ok({ ...snapshot, reauthentication: { ...snapshot.reauthentication, operations: [.../* @__PURE__ */ new Set([...snapshot.reauthentication.operations, ...configured.data])].sort() } });
|
|
5814
|
+
},
|
|
5704
5815
|
get user() {
|
|
5705
5816
|
return currentUser ?? notInitializedUser;
|
|
5706
5817
|
},
|