@h-ai/iam 0.1.0-alpha.67 → 0.1.0-alpha.69
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 +55 -30
- package/README.ja.md +56 -31
- package/README.md +57 -32
- package/dist/browser.d.ts +2320 -0
- package/dist/browser.js +3 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-VCE74BFW.js +243 -0
- package/dist/chunk-VCE74BFW.js.map +1 -0
- package/dist/index.d.ts +19 -1623
- package/dist/index.js +789 -1173
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
package/dist/index.js
CHANGED
|
@@ -1,196 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HaiIamError, IamConfigSchema, SessionConfigSchema, SecurityConfigSchema, LoginConfigSchema, OtpConfigSchema, ApiKeyConfigSchema, AuthUiConfigSchema, PasswordConfigSchema, RbacConfigSchema, AgreementConfigSchema, PasswordResetConfigSchema, RegisterConfigSchema } from './chunk-VCE74BFW.js';
|
|
2
|
+
export { AgreementConfigSchema, ApiKeyConfigSchema, AuthStrategyTypeSchema, AuthUiConfigSchema, HaiIamError, IamConfigSchema, LdapConfigSchema, LoginConfigSchema, OtpConfigSchema, PasswordConfigSchema, PasswordResetConfigSchema, RbacConfigSchema, RegisterConfigSchema, SecurityConfigSchema, SessionConfigSchema } from './chunk-VCE74BFW.js';
|
|
2
3
|
import { cache } from '@h-ai/cache';
|
|
3
4
|
import { core, err, ok } from '@h-ai/core';
|
|
4
5
|
import { crypto as crypto$1 } from '@h-ai/crypto';
|
|
5
6
|
import { reldb, BaseReldbCrudRepository } from '@h-ai/reldb';
|
|
6
|
-
import { randomBytes } from 'crypto';
|
|
7
|
+
import { createHash, randomBytes } from 'crypto';
|
|
7
8
|
import { audit } from '@h-ai/audit';
|
|
8
9
|
|
|
9
|
-
// src/iam-config.ts
|
|
10
|
-
var WebUrlSchema = z.url().refine((value) => ["http:", "https:"].includes(new URL(value).protocol));
|
|
11
|
-
var AuthStrategyTypeSchema = z.enum(["password", "otp", "ldap", "apikey"]);
|
|
12
|
-
var PasswordConfigSchema = z.object({
|
|
13
|
-
/** 最小长度(默认 8) */
|
|
14
|
-
minLength: z.number().int().min(1).default(8),
|
|
15
|
-
/** 最大长度(默认 128) */
|
|
16
|
-
maxLength: z.number().int().min(1).max(256).default(128),
|
|
17
|
-
/** 需要大写字母 */
|
|
18
|
-
requireUppercase: z.boolean().default(true),
|
|
19
|
-
/** 需要小写字母 */
|
|
20
|
-
requireLowercase: z.boolean().default(true),
|
|
21
|
-
/** 需要数字 */
|
|
22
|
-
requireNumber: z.boolean().default(true),
|
|
23
|
-
/** 需要特殊字符 */
|
|
24
|
-
requireSpecialChar: z.boolean().default(false),
|
|
25
|
-
/** 密码过期天数(0 表示不过期) */
|
|
26
|
-
expirationDays: z.number().int().min(0).default(0)
|
|
27
|
-
}).refine((config) => config.maxLength >= config.minLength, { message: "maxLength must be >= minLength", path: ["maxLength"] });
|
|
28
|
-
var OtpConfigSchema = z.object({
|
|
29
|
-
/** 分别启用邮件、短信验证码 */
|
|
30
|
-
email: z.boolean().default(true),
|
|
31
|
-
sms: z.boolean().default(true),
|
|
32
|
-
/** 验证码长度(默认 6) */
|
|
33
|
-
length: z.number().int().min(4).max(8).default(6),
|
|
34
|
-
/** 验证码过期时间(秒,默认 300) */
|
|
35
|
-
expiresIn: z.number().int().min(60).default(300),
|
|
36
|
-
/** 最大重试次数(默认 3) */
|
|
37
|
-
maxAttempts: z.number().int().min(1).default(3),
|
|
38
|
-
/** 发送间隔(秒,默认 60) */
|
|
39
|
-
resendInterval: z.number().int().min(30).default(60)
|
|
40
|
-
});
|
|
41
|
-
var PasswordResetConfigSchema = z.object({
|
|
42
|
-
/** 重置通知渠道,phone 使用 password_reset_sms 模板。 */
|
|
43
|
-
mode: z.enum(["email", "phone"]).default("email"),
|
|
44
|
-
/** 重置页面的绝对地址,通知模板可使用 resetUrl。 */
|
|
45
|
-
resetUrl: WebUrlSchema.optional(),
|
|
46
|
-
/** 是否启用密码重置 */
|
|
47
|
-
enabled: z.boolean().default(true),
|
|
48
|
-
/** 重置令牌有效期(秒,默认 3600 = 1小时) */
|
|
49
|
-
tokenExpiresIn: z.number().int().min(300).default(3600),
|
|
50
|
-
/** 最大验证尝试次数(默认 3) */
|
|
51
|
-
maxAttempts: z.number().int().min(1).default(3)
|
|
52
|
-
});
|
|
53
|
-
var LdapConfigSchema = z.object({
|
|
54
|
-
/** LDAP 服务器 URL */
|
|
55
|
-
url: z.url().refine((value) => ["ldap:", "ldaps:"].includes(new URL(value).protocol)),
|
|
56
|
-
/** 绑定 DN */
|
|
57
|
-
bindDn: z.string(),
|
|
58
|
-
/** 绑定密码 */
|
|
59
|
-
bindPassword: z.string(),
|
|
60
|
-
/** 搜索基础 DN */
|
|
61
|
-
searchBase: z.string(),
|
|
62
|
-
/** 搜索过滤器(默认使用 uid) */
|
|
63
|
-
searchFilter: z.string().default("(uid={{username}})"),
|
|
64
|
-
/** 用户名属性 */
|
|
65
|
-
usernameAttribute: z.string().default("uid"),
|
|
66
|
-
/** 邮箱属性 */
|
|
67
|
-
emailAttribute: z.string().default("mail"),
|
|
68
|
-
/** 显示名称属性 */
|
|
69
|
-
displayNameAttribute: z.string().default("cn"),
|
|
70
|
-
/** 启用 TLS */
|
|
71
|
-
useTls: z.boolean().default(false),
|
|
72
|
-
/** 连接超时(毫秒) */
|
|
73
|
-
connectTimeout: z.number().int().min(1e3).default(5e3)
|
|
74
|
-
});
|
|
75
|
-
var ApiKeyConfigSchema = z.object({
|
|
76
|
-
/** 单用户最大 API Key 数量(默认 10) */
|
|
77
|
-
maxKeysPerUser: z.number().int().min(1).default(10),
|
|
78
|
-
/** API Key 默认有效期(天,0 表示永不过期,默认 0) */
|
|
79
|
-
defaultExpirationDays: z.number().int().min(0).default(0),
|
|
80
|
-
/** API Key 前缀(默认 'hai_') */
|
|
81
|
-
prefix: z.string().default("hai_")
|
|
82
|
-
});
|
|
83
|
-
var LoginConfigSchema = z.object({
|
|
84
|
-
/** 是否启用密码登录 */
|
|
85
|
-
password: z.boolean().default(true),
|
|
86
|
-
/** 是否启用 OTP 登录 */
|
|
87
|
-
otp: z.boolean().default(true),
|
|
88
|
-
/** 是否启用 LDAP 登录;需要显式开启并提供 ldap 配置。 */
|
|
89
|
-
ldap: z.boolean().default(false),
|
|
90
|
-
/** 是否启用 API Key 登录 */
|
|
91
|
-
apikey: z.boolean().default(false)
|
|
92
|
-
});
|
|
93
|
-
var RegisterConfigSchema = z.object({
|
|
94
|
-
/** 是否启用注册 */
|
|
95
|
-
enabled: z.boolean().default(true),
|
|
96
|
-
/** 新注册用户是否默认启用 */
|
|
97
|
-
defaultEnabled: z.boolean().default(true)
|
|
98
|
-
});
|
|
99
|
-
var SecurityConfigSchema = z.object({
|
|
100
|
-
/** 最大登录失败次数(默认 5) */
|
|
101
|
-
maxLoginAttempts: z.number().int().min(1).default(5),
|
|
102
|
-
/** 锁定时长(秒,默认 900) */
|
|
103
|
-
lockoutDuration: z.number().int().min(60).default(900)
|
|
104
|
-
});
|
|
105
|
-
var AgreementConfigSchema = z.object({
|
|
106
|
-
/** 用户协议 Markdown 正文;同时配置 URL 时优先显示正文。 */
|
|
107
|
-
userAgreementMarkdown: z.string().min(1).optional(),
|
|
108
|
-
/** 隐私协议 Markdown 正文;YAML 使用 | 保留段落和换行。 */
|
|
109
|
-
privacyPolicyMarkdown: z.string().min(1).optional(),
|
|
110
|
-
/** 用户协议 URL */
|
|
111
|
-
userAgreementUrl: WebUrlSchema.optional(),
|
|
112
|
-
/** 隐私协议 URL */
|
|
113
|
-
privacyPolicyUrl: WebUrlSchema.optional(),
|
|
114
|
-
/** 注册时展示协议 */
|
|
115
|
-
showOnRegister: z.boolean().default(true),
|
|
116
|
-
/** 登录时展示协议 */
|
|
117
|
-
showOnLogin: z.boolean().default(false)
|
|
118
|
-
});
|
|
119
|
-
var AuthImageSchema = z.string().refine((value) => /^https?:\/\//.test(value) || value.startsWith("/") && !value.startsWith("//"));
|
|
120
|
-
var AuthUiConfigSchema = z.object({
|
|
121
|
-
variant: z.enum(["card", "split"]).default("card"),
|
|
122
|
-
title: z.string().optional(),
|
|
123
|
-
subtitle: z.string().optional(),
|
|
124
|
-
brandTitle: z.string().optional(),
|
|
125
|
-
brandText: z.string().optional(),
|
|
126
|
-
logoUrl: AuthImageSchema.optional(),
|
|
127
|
-
illustrationUrl: AuthImageSchema.optional()
|
|
128
|
-
});
|
|
129
|
-
var SessionConfigSchema = z.object({
|
|
130
|
-
/** 会话超时时间(秒,默认 86400 = 24小时) */
|
|
131
|
-
maxAge: z.number().int().min(60).default(86400),
|
|
132
|
-
/** 是否滑动窗口(每次访问刷新过期时间) */
|
|
133
|
-
sliding: z.boolean().default(true),
|
|
134
|
-
/** 单设备登录(踢掉其他设备) */
|
|
135
|
-
singleDevice: z.boolean().default(false),
|
|
136
|
-
/** refreshToken 过期时间(秒,默认 604800 = 7天) */
|
|
137
|
-
refreshTokenMaxAge: z.number().int().min(3600).default(604800)
|
|
138
|
-
});
|
|
139
|
-
var RbacConfigSchema = z.object({
|
|
140
|
-
/** 是否启用 RBAC */
|
|
141
|
-
enabled: z.boolean().default(true),
|
|
142
|
-
/** 超级管理员角色代码 */
|
|
143
|
-
superAdminRole: z.string().default("super_admin"),
|
|
144
|
-
/** 默认用户角色 */
|
|
145
|
-
defaultRole: z.string().default("user")
|
|
146
|
-
});
|
|
147
|
-
var IamConfigSchema = z.object({
|
|
148
|
-
/** 认证页外观 */
|
|
149
|
-
ui: AuthUiConfigSchema.optional(),
|
|
150
|
-
// ─── 认证策略配置 ───
|
|
151
|
-
/** 密码配置 */
|
|
152
|
-
password: PasswordConfigSchema.optional(),
|
|
153
|
-
/** OTP 配置 */
|
|
154
|
-
otp: OtpConfigSchema.optional(),
|
|
155
|
-
/** LDAP 配置 */
|
|
156
|
-
ldap: LdapConfigSchema.optional(),
|
|
157
|
-
/** API Key 配置 */
|
|
158
|
-
apikey: ApiKeyConfigSchema.optional(),
|
|
159
|
-
/** 密码重置配置 */
|
|
160
|
-
passwordReset: PasswordResetConfigSchema.optional(),
|
|
161
|
-
/** 登录启用配置 */
|
|
162
|
-
login: LoginConfigSchema.default({
|
|
163
|
-
password: true,
|
|
164
|
-
otp: true,
|
|
165
|
-
ldap: false,
|
|
166
|
-
apikey: false
|
|
167
|
-
}),
|
|
168
|
-
/** 注册配置 */
|
|
169
|
-
register: RegisterConfigSchema.default({
|
|
170
|
-
enabled: true,
|
|
171
|
-
defaultEnabled: true
|
|
172
|
-
}),
|
|
173
|
-
/** 协议展示配置 */
|
|
174
|
-
agreements: AgreementConfigSchema.default({
|
|
175
|
-
showOnRegister: true,
|
|
176
|
-
showOnLogin: false
|
|
177
|
-
}),
|
|
178
|
-
/** 安全策略配置 */
|
|
179
|
-
security: SecurityConfigSchema.default({
|
|
180
|
-
maxLoginAttempts: 5,
|
|
181
|
-
lockoutDuration: 900
|
|
182
|
-
}),
|
|
183
|
-
// ─── 会话配置 ───
|
|
184
|
-
/** 会话配置 */
|
|
185
|
-
session: SessionConfigSchema.optional(),
|
|
186
|
-
// ─── 授权配置 ───
|
|
187
|
-
/** RBAC 配置 */
|
|
188
|
-
rbac: RbacConfigSchema.default(() => RbacConfigSchema.parse({})),
|
|
189
|
-
// ─── 运行时选项 ───
|
|
190
|
-
/** 是否初始化默认角色和权限(默认 true) */
|
|
191
|
-
seedDefaultData: z.boolean().default(true)
|
|
192
|
-
});
|
|
193
|
-
|
|
194
10
|
// messages/en-US.json
|
|
195
11
|
var en_US_default = {
|
|
196
12
|
iam_batchUserIdsInvalid: "Provide at most 1000 non-empty user IDs",
|
|
@@ -376,7 +192,203 @@ var en_US_default = {
|
|
|
376
192
|
iam_apikeyUpdateFailed: "Failed to update API Key: {message}",
|
|
377
193
|
iam_apikeyDeleteFailed: "Failed to delete API Key: {message}",
|
|
378
194
|
iam_apikeyQueryFailed: "Failed to query API Key: {message}",
|
|
379
|
-
iam_apikeyStrategyRequired: "API Key authentication requires API Key strategy configuration"
|
|
195
|
+
iam_apikeyStrategyRequired: "API Key authentication requires API Key strategy configuration",
|
|
196
|
+
iam_sessionStateFailed: "Shared session state operation failed",
|
|
197
|
+
iam_sessionStateConflict: "Session state changed; retry the operation",
|
|
198
|
+
iam_sessionMaxAgeInvalid: "Invalid session lifetime or timestamp",
|
|
199
|
+
iam_notificationDeliveryUnknown: "Notification delivery is unknown and may have succeeded; do not resend automatically."
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// messages/ja-JP.json
|
|
203
|
+
var ja_JP_default = {
|
|
204
|
+
iam_batchUserIdsInvalid: "\u7A7A\u3067\u306A\u3044\u30E6\u30FC\u30B6\u30FCID\u30921000\u4EF6\u4EE5\u5185\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
205
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
206
|
+
iam_securityStateFailed: "\u8A8D\u8A3C\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u72B6\u614B\u3092\u66F4\u65B0\u3067\u304D\u307E\u305B\u3093\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
207
|
+
iam_securityBusy: "\u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
208
|
+
iam_passwordResetDisabled: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u30EA\u30BB\u30C3\u30C8\u304C\u7121\u52B9\u304B\u3001\u914D\u4FE1\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
209
|
+
iam_passwordResetDeliveryFailed: "\u30EA\u30BB\u30C3\u30C8\u901A\u77E5\u3092\u914D\u4FE1\u3067\u304D\u307E\u305B\u3093\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
210
|
+
iam_passwordMinLength: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F{minLength}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
211
|
+
iam_passwordMaxLength: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F{maxLength}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
212
|
+
iam_passwordNeedUppercase: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u306F\u82F1\u5927\u6587\u5B57\u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044",
|
|
213
|
+
iam_passwordNeedLowercase: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u306F\u82F1\u5C0F\u6587\u5B57\u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044",
|
|
214
|
+
iam_passwordNeedNumber: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u306F\u6570\u5B57\u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044",
|
|
215
|
+
iam_passwordNeedSpecialChar: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u306F\u7279\u6B8A\u6587\u5B57\u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044",
|
|
216
|
+
iam_credentialTypeMismatch: "\u8A8D\u8A3C\u60C5\u5831\u306E\u7A2E\u985E\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093",
|
|
217
|
+
iam_userNotExist: "\u30E6\u30FC\u30B6\u30FC\u304C\u5B58\u5728\u3057\u307E\u305B\u3093",
|
|
218
|
+
iam_accountDisabled: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u7121\u52B9\u3067\u3059",
|
|
219
|
+
iam_accountLocked: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u30ED\u30C3\u30AF\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
220
|
+
iam_accountNoPassword: "\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
221
|
+
iam_passwordWrong: "\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
222
|
+
iam_passwordExpired: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
223
|
+
iam_otpNotExistOrExpired: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
224
|
+
iam_otpInvalid: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u304C\u7121\u52B9\u3067\u3059\u3002\u65B0\u3057\u3044\u30B3\u30FC\u30C9\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
225
|
+
iam_otpWrong: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
226
|
+
iam_otpResendTooFast: "\u6B21\u306E\u30B3\u30FC\u30C9\u3092\u53D6\u5F97\u3059\u308B\u307E\u3067{seconds}\u79D2\u304A\u5F85\u3061\u304F\u3060\u3055\u3044",
|
|
227
|
+
iam_identifierTypeNotSupported: "\u672A\u5BFE\u5FDC\u306E\u8B58\u5225\u5B50\u306E\u7A2E\u985E\u304B\u3001\u914D\u4FE1\u65B9\u6CD5\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
228
|
+
iam_loginDisabled: "{type}\u306B\u3088\u308B\u30ED\u30B0\u30A4\u30F3\u306F\u7121\u52B9\u3067\u3059",
|
|
229
|
+
iam_registerDisabled: "\u30E6\u30FC\u30B6\u30FC\u767B\u9332\u306F\u7121\u52B9\u3067\u3059",
|
|
230
|
+
iam_otpStrategyRequired: "OTP\u8A8D\u8A3C\u306B\u306FOTP\u6226\u7565\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059",
|
|
231
|
+
iam_otpStrategyRequiredForSend: "OTP\u306B\u306FOTP\u6226\u7565\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059",
|
|
232
|
+
iam_userNotExistNoAutoRegister: "\u30E6\u30FC\u30B6\u30FC\u304C\u5B58\u5728\u305B\u305A\u3001\u81EA\u52D5\u767B\u9332\u3082\u6709\u52B9\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
|
|
233
|
+
iam_refreshTokenFailed: "\u30C8\u30FC\u30AF\u30F3\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
234
|
+
iam_refreshTokenExpired: "\u66F4\u65B0\u30C8\u30FC\u30AF\u30F3\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
235
|
+
iam_refreshTokenRequestFailed: "\u30C8\u30FC\u30AF\u30F3\u66F4\u65B0\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
236
|
+
iam_ldapConnectionFailed: "LDAP\u30B5\u30FC\u30D0\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093",
|
|
237
|
+
iam_ldapAdminBindFailed: "LDAP\u7BA1\u7406\u8005\u306E\u30D0\u30A4\u30F3\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
238
|
+
iam_ldapSearchFailed: "LDAP\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
239
|
+
iam_ldapStrategyRequired: "LDAP\u8A8D\u8A3C\u306B\u306FLDAP\u6226\u7565\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059",
|
|
240
|
+
iam_tokenExpired: "\u30C8\u30FC\u30AF\u30F3\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
241
|
+
iam_tokenInvalid: "\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u52B9\u3067\u3059",
|
|
242
|
+
iam_ticketInvalid: "\u30C1\u30B1\u30C3\u30C8\u304C\u7121\u52B9\u3001\u4F7F\u7528\u6E08\u307F\u3001\u307E\u305F\u306F\u4E0D\u660E\u3067\u3059",
|
|
243
|
+
iam_ticketExpired: "\u30C1\u30B1\u30C3\u30C8\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
244
|
+
iam_ticketIssueFailed: "\u30C1\u30B1\u30C3\u30C8\u306E\u767A\u884C\u307E\u305F\u306F\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
245
|
+
iam_createSessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
246
|
+
iam_sessionExpired: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
247
|
+
iam_invalidRefreshToken: "\u66F4\u65B0\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u52B9\u3067\u3059",
|
|
248
|
+
iam_sessionNotExist: "\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u5B58\u5728\u3057\u307E\u305B\u3093",
|
|
249
|
+
iam_usernameAlreadyExist: "\u30E6\u30FC\u30B6\u30FC\u540D\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
|
|
250
|
+
iam_emailAlreadyUsed: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u65E2\u306B\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059",
|
|
251
|
+
iam_originalPasswordWrong: "\u73FE\u5728\u306E\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
|
|
252
|
+
iam_featureNotImplemented: "\u3053\u306E\u6A5F\u80FD\u306F\u5B9F\u88C5\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
253
|
+
iam_userAlreadyExist: "\u30E6\u30FC\u30B6\u30FC\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
|
|
254
|
+
iam_parseSessionDataFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30C7\u30FC\u30BF\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
255
|
+
iam_roleAlreadyExist: "\u30ED\u30FC\u30EB\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
|
|
256
|
+
iam_roleNotExist: "\u30ED\u30FC\u30EB\u304C\u5B58\u5728\u3057\u307E\u305B\u3093",
|
|
257
|
+
iam_cannotDeleteSystemRole: "\u30B7\u30B9\u30C6\u30E0\u30ED\u30FC\u30EB\u306F\u524A\u9664\u3067\u304D\u307E\u305B\u3093",
|
|
258
|
+
iam_permissionAlreadyExist: "\u6A29\u9650\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
|
|
259
|
+
iam_permissionNotExist: "\u6A29\u9650\u304C\u5B58\u5728\u3057\u307E\u305B\u3093",
|
|
260
|
+
iam_notInitialized: "\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Biam.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
261
|
+
iam_initFailed: "\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
262
|
+
iam_depsNotInitialized: "\u4F9D\u5B58\u30E2\u30B8\u30E5\u30FC\u30EB{dep}\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002iam.init()\u306E\u524D\u306B{dep}.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
263
|
+
iam_initInProgress: "IAM\u306E\u521D\u671F\u5316\u306F\u65E2\u306B\u5B9F\u884C\u4E2D\u3067\u3059",
|
|
264
|
+
iam_cacheRequired: "\u30BB\u30C3\u30B7\u30E7\u30F3\u7BA1\u7406\u306B\u306F\u30AD\u30E3\u30C3\u30B7\u30E5\u30B5\u30FC\u30D3\u30B9\u304C\u5FC5\u8981\u3067\u3059",
|
|
265
|
+
iam_initComponentFailed: "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
266
|
+
iam_initSeedDataFailed: "\u521D\u671F\u30C7\u30FC\u30BF\u306E\u8A2D\u5B9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
267
|
+
iam_createUserTableFailed: "\u30E6\u30FC\u30B6\u30FC\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
268
|
+
iam_queryUserFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
269
|
+
iam_createUserFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
270
|
+
iam_queryUserListFailed: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
271
|
+
iam_updateUserFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
272
|
+
iam_deleteUserFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
273
|
+
iam_createSessionTableFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
274
|
+
iam_createSessionIndexFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
275
|
+
iam_createSessionRecordFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30EC\u30B3\u30FC\u30C9\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
276
|
+
iam_querySessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
277
|
+
iam_updateSessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
278
|
+
iam_deleteSessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
279
|
+
iam_cleanupSessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
280
|
+
iam_createSessionStoreTableFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30B9\u30C8\u30A2\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
281
|
+
iam_createTokenMappingTableFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
282
|
+
iam_createUserSessionTableFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
283
|
+
iam_createSessionStoreIndexFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
284
|
+
iam_saveSessionFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
285
|
+
iam_queryTokenMappingFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u60C5\u5831\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
286
|
+
iam_saveTokenMappingFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u60C5\u5831\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
287
|
+
iam_deleteTokenMappingFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u60C5\u5831\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
288
|
+
iam_deleteUserSessionMappingFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u5BFE\u5FDC\u60C5\u5831\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
289
|
+
iam_queryUserSessionFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
290
|
+
iam_addUserSessionMappingFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u5BFE\u5FDC\u60C5\u5831\u306E\u8FFD\u52A0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
291
|
+
iam_createRoleTableFailed: "\u30ED\u30FC\u30EB\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
292
|
+
iam_queryRoleFailed: "\u30ED\u30FC\u30EB\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
293
|
+
iam_queryRoleListFailed: "\u30ED\u30FC\u30EB\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
294
|
+
iam_createRoleFailed: "\u30ED\u30FC\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
295
|
+
iam_updateRoleFailed: "\u30ED\u30FC\u30EB\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
296
|
+
iam_deleteRoleFailed: "\u30ED\u30FC\u30EB\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
297
|
+
iam_createRolePermissionTableFailed: "\u30ED\u30FC\u30EB\u6A29\u9650\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
298
|
+
iam_createRolePermissionIndexFailed: "\u30ED\u30FC\u30EB\u6A29\u9650\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
299
|
+
iam_assignPermissionFailed: "\u6A29\u9650\u306E\u5272\u308A\u5F53\u3066\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
300
|
+
iam_removePermissionFailed: "\u6A29\u9650\u306E\u89E3\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
301
|
+
iam_createUserRoleTableFailed: "\u30E6\u30FC\u30B6\u30FC\u30ED\u30FC\u30EB\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
302
|
+
iam_createUserRoleIndexFailed: "\u30E6\u30FC\u30B6\u30FC\u30ED\u30FC\u30EB\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
303
|
+
iam_assignRoleFailed: "\u30ED\u30FC\u30EB\u306E\u5272\u308A\u5F53\u3066\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
304
|
+
iam_removeRoleFailed: "\u30ED\u30FC\u30EB\u306E\u89E3\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
305
|
+
iam_syncRolesFailed: "\u30ED\u30FC\u30EB\u306E\u540C\u671F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
306
|
+
iam_createPermissionTableFailed: "\u6A29\u9650\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
307
|
+
iam_createPermissionFailed: "\u6A29\u9650\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
308
|
+
iam_queryPermissionFailed: "\u6A29\u9650\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
309
|
+
iam_queryPermissionListFailed: "\u6A29\u9650\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
310
|
+
iam_deletePermissionFailed: "\u6A29\u9650\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
311
|
+
iam_queryPermissionCacheFailed: "\u6A29\u9650\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
312
|
+
iam_setPermissionCacheFailed: "\u6A29\u9650\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
313
|
+
iam_clearPermissionCacheFailed: "\u6A29\u9650\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u30AF\u30EA\u30A2\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
314
|
+
iam_queryOtpCacheFailed: "OTP\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
315
|
+
iam_saveOtpCacheFailed: "OTP\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
316
|
+
iam_updateOtpCacheFailed: "OTP\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
317
|
+
iam_deleteOtpCacheFailed: "OTP\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
318
|
+
iam_querySessionMappingCacheFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
319
|
+
iam_saveSessionMappingCacheFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
320
|
+
iam_deleteSessionMappingCacheFailed: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
321
|
+
iam_queryTokenMappingCacheFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
322
|
+
iam_saveTokenMappingCacheFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
323
|
+
iam_deleteTokenMappingCacheFailed: "\u30C8\u30FC\u30AF\u30F3\u5BFE\u5FDC\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
324
|
+
iam_queryUserSessionCacheFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
325
|
+
iam_saveUserSessionCacheFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
326
|
+
iam_deleteUserSessionCacheFailed: "\u30E6\u30FC\u30B6\u30FC\u30BB\u30C3\u30B7\u30E7\u30F3\u30AD\u30E3\u30C3\u30B7\u30E5\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
327
|
+
iam_createOtpTableFailed: "OTP\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
328
|
+
iam_saveOtpFailed: "OTP\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
329
|
+
iam_queryOtpFailed: "OTP\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
330
|
+
iam_updateOtpAttemptsFailed: "OTP\u8A66\u884C\u56DE\u6570\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
331
|
+
iam_deleteOtpFailed: "OTP\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
332
|
+
iam_otpSendFailed: "\u8A8D\u8A3C\u30B3\u30FC\u30C9\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
333
|
+
iam_resetTokenInvalid: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u7528\u30C8\u30FC\u30AF\u30F3\u304C\u7121\u52B9\u304B\u3001\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
334
|
+
iam_resetTokenMaxAttempts: "\u8A8D\u8A3C\u306E\u8A66\u884C\u56DE\u6570\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u65B0\u3057\u3044\u30EA\u30BB\u30C3\u30C8\u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
335
|
+
iam_saveResetTokenFailed: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u7528\u30C8\u30FC\u30AF\u30F3\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
336
|
+
iam_hashResetTokenFailed: "\u30EA\u30BB\u30C3\u30C8\u30C8\u30FC\u30AF\u30F3\u306E\u30CF\u30C3\u30B7\u30E5\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
337
|
+
iam_queryResetTokenFailed: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u7528\u30C8\u30FC\u30AF\u30F3\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
338
|
+
iam_passwordResetNotConfigured: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u30EA\u30BB\u30C3\u30C8\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
339
|
+
iam_clientFetchNotAvailable: "\u73FE\u5728\u306E\u74B0\u5883\u3067\u306FFetch\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
340
|
+
iam_clientRequestFailed: "\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {status}",
|
|
341
|
+
iam_clientNetworkError: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
342
|
+
iam_clientNetworkErrorWithDetail: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
343
|
+
iam_seedRoleAdminName: "\u7BA1\u7406\u8005",
|
|
344
|
+
iam_seedRoleAdminDesc: "\u3059\u3079\u3066\u306E\u6A29\u9650\u3092\u6301\u3064\u30B7\u30B9\u30C6\u30E0\u7BA1\u7406\u8005",
|
|
345
|
+
iam_seedRoleUserName: "\u30E6\u30FC\u30B6\u30FC",
|
|
346
|
+
iam_seedRoleUserDesc: "\u4E00\u822C\u30E6\u30FC\u30B6\u30FC",
|
|
347
|
+
iam_seedRoleGuestName: "\u30B2\u30B9\u30C8",
|
|
348
|
+
iam_seedRoleGuestDesc: "\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u306E\u30B2\u30B9\u30C8",
|
|
349
|
+
iam_seedPermUserRead: "\u30E6\u30FC\u30B6\u30FC\u306E\u95B2\u89A7",
|
|
350
|
+
iam_seedPermUserCreate: "\u30E6\u30FC\u30B6\u30FC\u306E\u4F5C\u6210",
|
|
351
|
+
iam_seedPermUserUpdate: "\u30E6\u30FC\u30B6\u30FC\u306E\u66F4\u65B0",
|
|
352
|
+
iam_seedPermUserDelete: "\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664",
|
|
353
|
+
iam_seedPermUserList: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7API",
|
|
354
|
+
iam_seedPermUserApiCreate: "\u30E6\u30FC\u30B6\u30FC\u4F5C\u6210API",
|
|
355
|
+
iam_seedPermUserApiUpdate: "\u30E6\u30FC\u30B6\u30FC\u66F4\u65B0API",
|
|
356
|
+
iam_seedPermUserApiDelete: "\u30E6\u30FC\u30B6\u30FC\u524A\u9664API",
|
|
357
|
+
iam_seedPermRoleRead: "\u30ED\u30FC\u30EB\u306E\u95B2\u89A7",
|
|
358
|
+
iam_seedPermRoleCreate: "\u30ED\u30FC\u30EB\u306E\u4F5C\u6210",
|
|
359
|
+
iam_seedPermRoleUpdate: "\u30ED\u30FC\u30EB\u306E\u66F4\u65B0",
|
|
360
|
+
iam_seedPermRoleDelete: "\u30ED\u30FC\u30EB\u306E\u524A\u9664",
|
|
361
|
+
iam_seedPermRoleList: "\u30ED\u30FC\u30EB\u4E00\u89A7API",
|
|
362
|
+
iam_seedPermRoleApiCreate: "\u30ED\u30FC\u30EB\u4F5C\u6210API",
|
|
363
|
+
iam_seedPermRoleApiUpdate: "\u30ED\u30FC\u30EB\u66F4\u65B0API",
|
|
364
|
+
iam_seedPermRoleApiDelete: "\u30ED\u30FC\u30EB\u524A\u9664API",
|
|
365
|
+
iam_seedPermPermRead: "\u6A29\u9650\u306E\u95B2\u89A7",
|
|
366
|
+
iam_seedPermPermManage: "\u6A29\u9650\u306E\u7BA1\u7406",
|
|
367
|
+
iam_seedPermPermCreate: "\u6A29\u9650\u306E\u4F5C\u6210",
|
|
368
|
+
iam_seedPermPermDelete: "\u6A29\u9650\u306E\u524A\u9664",
|
|
369
|
+
iam_seedPermPermList: "\u6A29\u9650\u4E00\u89A7API",
|
|
370
|
+
iam_seedPermPermApiCreate: "\u6A29\u9650\u4F5C\u6210API",
|
|
371
|
+
iam_seedPermPermApiDelete: "\u6A29\u9650\u524A\u9664API",
|
|
372
|
+
iam_seedPermSystemSettings: "\u30B7\u30B9\u30C6\u30E0\u8A2D\u5B9A",
|
|
373
|
+
iam_seedPermSystemLogs: "\u30ED\u30B0\u306E\u95B2\u89A7",
|
|
374
|
+
iam_seedPermSystemModules: "\u30E2\u30B8\u30E5\u30FC\u30EB\u7BA1\u7406",
|
|
375
|
+
iam_seedPermDashboardView: "\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9",
|
|
376
|
+
iam_seedPermProfileRead: "\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB",
|
|
377
|
+
iam_seedPermAuditRead: "\u76E3\u67FB\u30ED\u30B0",
|
|
378
|
+
iam_configError: "IAM\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
379
|
+
iam_apikeyInvalid: "API\u30AD\u30FC\u304C\u7121\u52B9\u3067\u3059",
|
|
380
|
+
iam_apikeyExpired: "API\u30AD\u30FC\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
|
|
381
|
+
iam_apikeyDisabled: "API\u30AD\u30FC\u304C\u7121\u52B9\u5316\u3055\u308C\u3066\u3044\u307E\u3059",
|
|
382
|
+
iam_apikeyMaxKeysReached: "API\u30AD\u30FC\u306E\u4E0A\u9650\uFF08{max}\u4EF6\uFF09\u306B\u9054\u3057\u307E\u3057\u305F",
|
|
383
|
+
iam_apikeyCreateFailed: "API\u30AD\u30FC\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
384
|
+
iam_apikeyUpdateFailed: "API\u30AD\u30FC\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
385
|
+
iam_apikeyDeleteFailed: "API\u30AD\u30FC\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
386
|
+
iam_apikeyQueryFailed: "API\u30AD\u30FC\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
387
|
+
iam_apikeyStrategyRequired: "API\u30AD\u30FC\u8A8D\u8A3C\u306B\u306FAPI\u30AD\u30FC\u6226\u7565\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059",
|
|
388
|
+
iam_sessionStateFailed: "\u5171\u6709\u30BB\u30C3\u30B7\u30E7\u30F3\u72B6\u614B\u306E\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
389
|
+
iam_sessionStateConflict: "\u30BB\u30C3\u30B7\u30E7\u30F3\u72B6\u614B\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F\u3002\u64CD\u4F5C\u3092\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
390
|
+
iam_sessionMaxAgeInvalid: "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6709\u52B9\u671F\u9593\u307E\u305F\u306F\u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7\u304C\u7121\u52B9\u3067\u3059",
|
|
391
|
+
iam_notificationDeliveryUnknown: "\u901A\u77E5\u306E\u914D\u4FE1\u7D50\u679C\u306F\u4E0D\u660E\u3067\u3001\u6210\u529F\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u81EA\u52D5\u3067\u518D\u9001\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002"
|
|
380
392
|
};
|
|
381
393
|
|
|
382
394
|
// messages/zh-CN.json
|
|
@@ -564,66 +576,19 @@ var zh_CN_default = {
|
|
|
564
576
|
iam_apikeyUpdateFailed: "\u66F4\u65B0 API Key \u5931\u8D25\uFF1A{message}",
|
|
565
577
|
iam_apikeyDeleteFailed: "\u5220\u9664 API Key \u5931\u8D25\uFF1A{message}",
|
|
566
578
|
iam_apikeyQueryFailed: "\u67E5\u8BE2 API Key \u5931\u8D25\uFF1A{message}",
|
|
567
|
-
iam_apikeyStrategyRequired: "API Key \u8BA4\u8BC1\u9700\u8981 API Key \u7B56\u7565\u914D\u7F6E"
|
|
579
|
+
iam_apikeyStrategyRequired: "API Key \u8BA4\u8BC1\u9700\u8981 API Key \u7B56\u7565\u914D\u7F6E",
|
|
580
|
+
iam_sessionStateFailed: "\u4F1A\u8BDD\u5171\u4EAB\u72B6\u6001\u64CD\u4F5C\u5931\u8D25",
|
|
581
|
+
iam_sessionStateConflict: "\u4F1A\u8BDD\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u8BD5",
|
|
582
|
+
iam_sessionMaxAgeInvalid: "\u4F1A\u8BDD\u6709\u6548\u671F\u6216\u65F6\u95F4\u5B57\u6BB5\u65E0\u6548",
|
|
583
|
+
iam_notificationDeliveryUnknown: "\u901A\u77E5\u53EF\u80FD\u5DF2\u9001\u8FBE\uFF0C\u7ED3\u679C\u672A\u77E5\uFF1B\u8BF7\u52FF\u81EA\u52A8\u91CD\u53D1\u3002"
|
|
568
584
|
};
|
|
569
585
|
|
|
570
586
|
// src/iam-i18n.ts
|
|
571
587
|
var iamM = core.i18n.createMessageGetter({
|
|
572
588
|
"zh-CN": zh_CN_default,
|
|
573
|
-
"en-US": en_US_default
|
|
589
|
+
"en-US": en_US_default,
|
|
590
|
+
"ja-JP": ja_JP_default
|
|
574
591
|
});
|
|
575
|
-
var IamErrorInfo = {
|
|
576
|
-
AUTH_FAILED: "001:401",
|
|
577
|
-
INVALID_CREDENTIALS: "002:401",
|
|
578
|
-
USER_NOT_FOUND: "003:404",
|
|
579
|
-
USER_DISABLED: "004:403",
|
|
580
|
-
USER_LOCKED: "005:403",
|
|
581
|
-
USER_ALREADY_EXISTS: "006:409",
|
|
582
|
-
PASSWORD_EXPIRED: "007:401",
|
|
583
|
-
PASSWORD_POLICY_VIOLATION: "008:400",
|
|
584
|
-
OTP_INVALID: "009:400",
|
|
585
|
-
NOT_INITIALIZED: "010:500",
|
|
586
|
-
OTP_RESEND_TOO_FAST: "011:429",
|
|
587
|
-
LOGIN_DISABLED: "012:400",
|
|
588
|
-
REGISTER_DISABLED: "013:403",
|
|
589
|
-
STRATEGY_NOT_SUPPORTED: "014:400",
|
|
590
|
-
APIKEY_INVALID: "015:401",
|
|
591
|
-
APIKEY_EXPIRED: "016:401",
|
|
592
|
-
APIKEY_DISABLED: "017:403",
|
|
593
|
-
APIKEY_NOT_FOUND: "018:404",
|
|
594
|
-
RESET_TOKEN_INVALID: "019:400",
|
|
595
|
-
RESET_TOKEN_EXPIRED: "020:400",
|
|
596
|
-
RESET_TOKEN_MAX_ATTEMPTS: "021:429",
|
|
597
|
-
OTP_EXPIRED: "022:400",
|
|
598
|
-
SECURITY_BUSY: "023:429",
|
|
599
|
-
PASSWORD_RESET_DISABLED: "024:403",
|
|
600
|
-
SESSION_NOT_FOUND: "101:401",
|
|
601
|
-
SESSION_EXPIRED: "102:401",
|
|
602
|
-
SESSION_INVALID: "103:401",
|
|
603
|
-
SESSION_CREATE_FAILED: "104:500",
|
|
604
|
-
TOKEN_EXPIRED: "105:401",
|
|
605
|
-
TOKEN_INVALID: "106:401",
|
|
606
|
-
TOKEN_REFRESH_FAILED: "107:401",
|
|
607
|
-
PERMISSION_DENIED: "201:403",
|
|
608
|
-
ROLE_NOT_FOUND: "202:404",
|
|
609
|
-
PERMISSION_NOT_FOUND: "203:404",
|
|
610
|
-
ROLE_ALREADY_EXISTS: "204:409",
|
|
611
|
-
PERMISSION_ALREADY_EXISTS: "205:409",
|
|
612
|
-
LDAP_CONNECTION_FAILED: "301:500",
|
|
613
|
-
LDAP_BIND_FAILED: "302:401",
|
|
614
|
-
LDAP_SEARCH_FAILED: "303:500",
|
|
615
|
-
TICKET_INVALID: "110:401",
|
|
616
|
-
TICKET_EXPIRED: "111:401",
|
|
617
|
-
TICKET_ISSUE_FAILED: "112:500",
|
|
618
|
-
REPOSITORY_ERROR: "401:500",
|
|
619
|
-
NOT_FOUND: "402:404",
|
|
620
|
-
CONFLICT: "403:409",
|
|
621
|
-
FORBIDDEN: "501:403",
|
|
622
|
-
INVALID_ARGUMENT: "502:400",
|
|
623
|
-
CONFIG_ERROR: "901:500",
|
|
624
|
-
INTERNAL_ERROR: "999:500"
|
|
625
|
-
};
|
|
626
|
-
var HaiIamError = core.error.buildHaiErrorsDef("iam", IamErrorInfo);
|
|
627
592
|
|
|
628
593
|
// src/authn/apikey/iam-authn-apikey-repository.ts
|
|
629
594
|
var TABLE_NAME = "hai_iam_api_keys";
|
|
@@ -843,6 +808,11 @@ function buildAgreementDisplay(config, enabled) {
|
|
|
843
808
|
}
|
|
844
809
|
var TABLE_NAME2 = "hai_iam_users";
|
|
845
810
|
var USER_FIELDS = [
|
|
811
|
+
{ fieldName: "credentialVersion", columnName: "credential_version", def: { type: "TEXT", notNull: true, defaultValue: "" }, select: true, create: false, update: false },
|
|
812
|
+
{ fieldName: "resetVersion", columnName: "reset_version", def: { type: "TEXT", notNull: true, defaultValue: "" }, select: true, create: false, update: false },
|
|
813
|
+
{ fieldName: "resetHash", columnName: "reset_hash", def: { type: "TEXT" }, select: true, create: false, update: false },
|
|
814
|
+
{ fieldName: "resetExpiresAt", columnName: "reset_expires_at", def: { type: "BIGINT" }, select: true, create: false, update: false },
|
|
815
|
+
{ fieldName: "resetAttempts", columnName: "reset_attempts", def: { type: "INTEGER", notNull: true, defaultValue: 0 }, select: true, create: false, update: false },
|
|
846
816
|
{
|
|
847
817
|
fieldName: "id",
|
|
848
818
|
columnName: "id",
|
|
@@ -1002,6 +972,64 @@ var DbUserRepository = class extends BaseReldbCrudRepository {
|
|
|
1002
972
|
fields: USER_FIELDS
|
|
1003
973
|
});
|
|
1004
974
|
}
|
|
975
|
+
/** 数据库时钟在执行最终 SQL 时核验有效期,暂停进程不能复用旧的 Date.now()。 */
|
|
976
|
+
databaseNow() {
|
|
977
|
+
if (reldb.config?.type === "postgresql")
|
|
978
|
+
return "(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)";
|
|
979
|
+
if (reldb.config?.type === "mysql")
|
|
980
|
+
return "(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000)";
|
|
981
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
982
|
+
}
|
|
983
|
+
/** 改密推进凭据代际;会话存储清理失败也不能让旧凭据恢复授权。 */
|
|
984
|
+
async setPassword(input) {
|
|
985
|
+
const result = await reldb.sql.execute(
|
|
986
|
+
`UPDATE ${TABLE_NAME2} SET password_hash = ?, password_updated_at = ?, credential_version = ?, reset_version = ?, reset_hash = NULL, reset_expires_at = NULL, reset_attempts = 0 WHERE id = ? AND credential_version = ? AND enabled = ?`,
|
|
987
|
+
[input.passwordHash, reldb.config?.type === "sqlite" ? Date.now() : /* @__PURE__ */ new Date(), crypto.randomUUID(), crypto.randomUUID(), input.userId, input.expectedVersion, reldb.config?.type === "postgresql" ? true : 1]
|
|
988
|
+
);
|
|
989
|
+
return result.success ? ok(result.data.changes === 1) : this.buildQueryError(result.error, result.error);
|
|
990
|
+
}
|
|
991
|
+
/** 发布只替换读取到的重置代际;旧发送者暂停后不能覆盖接管节点的新令牌。 */
|
|
992
|
+
async publishPasswordReset(input) {
|
|
993
|
+
const expiry = input.expiresAt.getTime();
|
|
994
|
+
if (!Number.isSafeInteger(expiry))
|
|
995
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_resetTokenInvalid"));
|
|
996
|
+
const result = await reldb.sql.execute(
|
|
997
|
+
`UPDATE ${TABLE_NAME2} SET reset_hash = ?, reset_expires_at = ?, reset_attempts = 0, reset_version = ? WHERE id = ? AND credential_version = ? AND reset_version = ? AND enabled = ? AND ? > ${this.databaseNow()}`,
|
|
998
|
+
[input.tokenHash, expiry, crypto.randomUUID(), input.user.id, input.user.credentialVersion ?? "", input.user.resetVersion ?? "", reldb.config?.type === "postgresql" ? true : 1, expiry]
|
|
999
|
+
);
|
|
1000
|
+
return result.success ? ok(result.data.changes === 1) : this.buildQueryError(result.error, result.error);
|
|
1001
|
+
}
|
|
1002
|
+
/** 条件计次返回新代际;业务策略失败也消耗尝试次数。 */
|
|
1003
|
+
async claimPasswordReset(tokenHash, maxAttempts) {
|
|
1004
|
+
const found = await this.findOneBy("reset_hash = ?", [tokenHash]);
|
|
1005
|
+
if (!found.success)
|
|
1006
|
+
return found;
|
|
1007
|
+
const user = found.data;
|
|
1008
|
+
if (!user)
|
|
1009
|
+
return err(HaiIamError.RESET_TOKEN_INVALID, iamM("iam_resetTokenInvalid"));
|
|
1010
|
+
if (!user.enabled)
|
|
1011
|
+
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1012
|
+
if ((user.resetAttempts ?? 0) >= maxAttempts)
|
|
1013
|
+
return err(HaiIamError.RESET_TOKEN_MAX_ATTEMPTS, iamM("iam_resetTokenMaxAttempts"));
|
|
1014
|
+
const version = crypto.randomUUID();
|
|
1015
|
+
const result = await reldb.sql.execute(
|
|
1016
|
+
`UPDATE ${TABLE_NAME2} SET reset_attempts = reset_attempts + 1, reset_version = ? WHERE id = ? AND reset_hash = ? AND reset_version = ? AND credential_version = ? AND reset_attempts < ? AND reset_expires_at > ${this.databaseNow()} AND enabled = ?`,
|
|
1017
|
+
[version, user.id, tokenHash, user.resetVersion ?? "", user.credentialVersion ?? "", maxAttempts, reldb.config?.type === "postgresql" ? true : 1]
|
|
1018
|
+
);
|
|
1019
|
+
if (!result.success)
|
|
1020
|
+
return this.buildQueryError(result.error, result.error);
|
|
1021
|
+
if (result.data.changes !== 1)
|
|
1022
|
+
return err(HaiIamError.RESET_TOKEN_INVALID, iamM("iam_resetTokenInvalid"));
|
|
1023
|
+
return ok({ ...user, resetVersion: version, resetAttempts: (user.resetAttempts ?? 0) + 1 });
|
|
1024
|
+
}
|
|
1025
|
+
/** 密码与令牌消费同一行原子提交,禁止先消费 Redis 凭据再无条件写数据库。 */
|
|
1026
|
+
async commitPasswordReset(input) {
|
|
1027
|
+
const result = await reldb.sql.execute(
|
|
1028
|
+
`UPDATE ${TABLE_NAME2} SET password_hash = ?, password_updated_at = ?, credential_version = ?, reset_version = ?, reset_hash = NULL, reset_expires_at = NULL, reset_attempts = 0, login_failed_count = 0, locked_until = NULL, last_login_failed_at = NULL WHERE id = ? AND reset_hash = ? AND reset_version = ? AND credential_version = ? AND reset_expires_at > ${this.databaseNow()} AND enabled = ?`,
|
|
1029
|
+
[input.passwordHash, reldb.config?.type === "sqlite" ? Date.now() : /* @__PURE__ */ new Date(), crypto.randomUUID(), crypto.randomUUID(), input.user.id, input.tokenHash, input.user.resetVersion ?? "", input.user.credentialVersion ?? "", reldb.config?.type === "postgresql" ? true : 1]
|
|
1030
|
+
);
|
|
1031
|
+
return result.success ? ok(result.data.changes === 1) : this.buildQueryError(result.error, result.error);
|
|
1032
|
+
}
|
|
1005
1033
|
async recordLoginFailure(userId, policy) {
|
|
1006
1034
|
const sqlite = reldb.config?.type === "sqlite";
|
|
1007
1035
|
const now = sqlite ? Date.now() : /* @__PURE__ */ new Date();
|
|
@@ -1018,10 +1046,10 @@ var DbUserRepository = class extends BaseReldbCrudRepository {
|
|
|
1018
1046
|
return this.buildQueryError(result.error, result.error);
|
|
1019
1047
|
return ok(void 0);
|
|
1020
1048
|
}
|
|
1021
|
-
async resetLoginFailures(userId) {
|
|
1049
|
+
async resetLoginFailures(userId, expectedCredentialVersion) {
|
|
1022
1050
|
const result = await reldb.sql.execute(
|
|
1023
|
-
`UPDATE ${TABLE_NAME2} SET login_failed_count = 0, last_login_failed_at = NULL, locked_until = NULL WHERE id = ? AND enabled = ? AND (locked_until IS NULL OR locked_until <= ?)`,
|
|
1024
|
-
[userId, reldb.config?.type === "postgresql" ? true : 1, reldb.config?.type === "sqlite" ? Date.now() : /* @__PURE__ */ new Date()]
|
|
1051
|
+
`UPDATE ${TABLE_NAME2} SET login_failed_count = 0, last_login_failed_at = NULL, locked_until = NULL WHERE id = ? AND credential_version = ? AND enabled = ? AND (locked_until IS NULL OR locked_until <= ?)`,
|
|
1052
|
+
[userId, expectedCredentialVersion, reldb.config?.type === "postgresql" ? true : 1, reldb.config?.type === "sqlite" ? Date.now() : /* @__PURE__ */ new Date()]
|
|
1025
1053
|
);
|
|
1026
1054
|
if (!result.success)
|
|
1027
1055
|
return this.buildQueryError(result.error, result.error);
|
|
@@ -1031,6 +1059,8 @@ var DbUserRepository = class extends BaseReldbCrudRepository {
|
|
|
1031
1059
|
return user;
|
|
1032
1060
|
if (!user.data?.enabled)
|
|
1033
1061
|
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1062
|
+
if ((user.data.credentialVersion ?? "") !== expectedCredentialVersion)
|
|
1063
|
+
return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
|
|
1034
1064
|
if (user.data.lockedUntil && user.data.lockedUntil > /* @__PURE__ */ new Date())
|
|
1035
1065
|
return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
|
|
1036
1066
|
}
|
|
@@ -1158,7 +1188,7 @@ async function recordLoginFailure(userRepository, user, policy) {
|
|
|
1158
1188
|
return userRepository.recordLoginFailure(user.id, policy);
|
|
1159
1189
|
}
|
|
1160
1190
|
async function resetLoginFailures(userRepository, user) {
|
|
1161
|
-
return userRepository.resetLoginFailures(user.id);
|
|
1191
|
+
return userRepository.resetLoginFailures(user.id, user.credentialVersion ?? "");
|
|
1162
1192
|
}
|
|
1163
1193
|
|
|
1164
1194
|
// src/authn/apikey/iam-authn-apikey-strategy.ts
|
|
@@ -1203,7 +1233,7 @@ function createApiKeyStrategy(config) {
|
|
|
1203
1233
|
if (!candidate.enabled) {
|
|
1204
1234
|
return err(HaiIamError.APIKEY_DISABLED, iamM("iam_apikeyDisabled"));
|
|
1205
1235
|
}
|
|
1206
|
-
if (candidate.expiresAt && /* @__PURE__ */ new Date()
|
|
1236
|
+
if (candidate.expiresAt && /* @__PURE__ */ new Date() >= candidate.expiresAt) {
|
|
1207
1237
|
return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
|
|
1208
1238
|
}
|
|
1209
1239
|
apiKeyRepository.updateFields(candidate.id, { lastUsedAt: /* @__PURE__ */ new Date() }).catch(() => {
|
|
@@ -1238,7 +1268,7 @@ function createApiKeyStrategy(config) {
|
|
|
1238
1268
|
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1239
1269
|
}
|
|
1240
1270
|
logger.info("API Key authentication succeeded", { userId: userResult.data.id, keyId: apiKey.id });
|
|
1241
|
-
return ok({ user: toUser(userResult.data), apiKey });
|
|
1271
|
+
return ok({ user: { ...toUser(userResult.data), credentialVersion: userResult.data.credentialVersion ?? "" }, apiKey });
|
|
1242
1272
|
}
|
|
1243
1273
|
};
|
|
1244
1274
|
const apiKeyFunctions = {
|
|
@@ -1382,22 +1412,6 @@ function createLdapStrategy(config) {
|
|
|
1382
1412
|
}
|
|
1383
1413
|
return value;
|
|
1384
1414
|
}
|
|
1385
|
-
function buildLdapUser(entry, ldapUsername, ldapEmail, ldapDisplayName) {
|
|
1386
|
-
const now = /* @__PURE__ */ new Date();
|
|
1387
|
-
return {
|
|
1388
|
-
id: entry.dn,
|
|
1389
|
-
username: ldapUsername,
|
|
1390
|
-
email: ldapEmail,
|
|
1391
|
-
displayName: ldapDisplayName,
|
|
1392
|
-
enabled: true,
|
|
1393
|
-
createdAt: now,
|
|
1394
|
-
updatedAt: now,
|
|
1395
|
-
metadata: {
|
|
1396
|
-
ldapDn: entry.dn,
|
|
1397
|
-
authSource: "ldap"
|
|
1398
|
-
}
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
1415
|
return {
|
|
1402
1416
|
type: "ldap",
|
|
1403
1417
|
name: "ldap-strategy",
|
|
@@ -1535,41 +1549,22 @@ function createLdapStrategy(config) {
|
|
|
1535
1549
|
}
|
|
1536
1550
|
}
|
|
1537
1551
|
}
|
|
1538
|
-
if (!storedUser)
|
|
1539
|
-
|
|
1540
|
-
return ok(ldapUser);
|
|
1541
|
-
}
|
|
1552
|
+
if (!storedUser)
|
|
1553
|
+
return err(HaiIamError.USER_NOT_FOUND, iamM("iam_userNotExist"));
|
|
1542
1554
|
const resetResult = await resetLoginFailures(userRepository, storedUser);
|
|
1543
1555
|
if (!resetResult.success)
|
|
1544
1556
|
return resetResult;
|
|
1545
1557
|
logger2.info("LDAP authentication succeeded", { userId: storedUser.id });
|
|
1546
|
-
return ok(toUser(storedUser));
|
|
1558
|
+
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
1547
1559
|
} finally {
|
|
1548
1560
|
await client.unbind();
|
|
1549
1561
|
}
|
|
1550
1562
|
}
|
|
1551
1563
|
};
|
|
1552
1564
|
}
|
|
1553
|
-
var OTP_KEY_PREFIX = "hai:iam:otp:";
|
|
1554
|
-
var OTP_ATTEMPTS_KEY_PREFIX = "hai:iam:otp:attempts:";
|
|
1555
1565
|
function normalizeOtpIdentifier(identifier) {
|
|
1556
1566
|
const trimmed = identifier.trim();
|
|
1557
|
-
|
|
1558
|
-
return trimmed.toLowerCase();
|
|
1559
|
-
return trimmed.replace(/[\s-]/g, "");
|
|
1560
|
-
}
|
|
1561
|
-
function buildOtpKey(identifier) {
|
|
1562
|
-
return `${OTP_KEY_PREFIX}${normalizeOtpIdentifier(identifier)}`;
|
|
1563
|
-
}
|
|
1564
|
-
function buildOtpAttemptsKey(identifier) {
|
|
1565
|
-
return `${OTP_ATTEMPTS_KEY_PREFIX}${normalizeOtpIdentifier(identifier)}`;
|
|
1566
|
-
}
|
|
1567
|
-
function restoreOtpDates(record) {
|
|
1568
|
-
return {
|
|
1569
|
-
...record,
|
|
1570
|
-
expiresAt: record.expiresAt instanceof Date ? record.expiresAt : new Date(record.expiresAt),
|
|
1571
|
-
createdAt: record.createdAt instanceof Date ? record.createdAt : new Date(record.createdAt)
|
|
1572
|
-
};
|
|
1567
|
+
return trimmed.includes("@") ? trimmed.toLowerCase() : trimmed.replace(/[\s-]/g, "");
|
|
1573
1568
|
}
|
|
1574
1569
|
var otpRepoInstance = null;
|
|
1575
1570
|
function resetOtpRepoSingleton() {
|
|
@@ -1578,192 +1573,65 @@ function resetOtpRepoSingleton() {
|
|
|
1578
1573
|
function createCacheOtpRepository() {
|
|
1579
1574
|
if (otpRepoInstance)
|
|
1580
1575
|
return otpRepoInstance;
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1576
|
+
const keyFor = (identifier) => `hai:iam:otp:${normalizeOtpIdentifier(identifier)}`;
|
|
1577
|
+
const failure = () => err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"));
|
|
1578
|
+
const invalid = () => err(HaiIamError.OTP_INVALID, iamM("iam_otpNotExistOrExpired"));
|
|
1579
|
+
otpRepoInstance = {
|
|
1580
|
+
async issueOtp(input) {
|
|
1583
1581
|
const now = Date.now();
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1586
|
-
code,
|
|
1587
|
-
expiresAt: new Date(now + expiresIn * 1e3),
|
|
1582
|
+
const state = {
|
|
1583
|
+
version: crypto.randomUUID(),
|
|
1584
|
+
code: input.code,
|
|
1588
1585
|
attempts: 0,
|
|
1589
|
-
|
|
1586
|
+
expiresAt: now + input.expiresIn * 1e3,
|
|
1587
|
+
cooldownUntil: now + input.resendInterval * 1e3
|
|
1590
1588
|
};
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
1609
|
-
iamM("iam_queryOtpFailed", { params: { message: result.error.message } }),
|
|
1610
|
-
result.error
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
if (!result.data) {
|
|
1614
|
-
return ok(null);
|
|
1615
|
-
}
|
|
1616
|
-
const attemptsResult = await cache.kv.get(buildOtpAttemptsKey(identifier));
|
|
1617
|
-
if (!attemptsResult.success)
|
|
1618
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), attemptsResult.error);
|
|
1619
|
-
const attempts = attemptsResult.success && typeof attemptsResult.data === "number" ? attemptsResult.data : 0;
|
|
1620
|
-
const restored = restoreOtpDates(result.data);
|
|
1621
|
-
return ok({ ...restored, attempts });
|
|
1622
|
-
},
|
|
1623
|
-
async incrementOtpAttempts(identifier) {
|
|
1624
|
-
const otpKey = buildOtpKey(identifier);
|
|
1625
|
-
const attemptsKey = buildOtpAttemptsKey(identifier);
|
|
1626
|
-
const current = await cache.kv.get(otpKey);
|
|
1627
|
-
if (!current.success) {
|
|
1628
|
-
return err(
|
|
1629
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
1630
|
-
iamM("iam_queryOtpFailed", { params: { message: current.error.message } }),
|
|
1631
|
-
current.error
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
if (!current.data) {
|
|
1635
|
-
return ok(0);
|
|
1636
|
-
}
|
|
1637
|
-
const incrResult = await cache.kv.incr(attemptsKey);
|
|
1638
|
-
if (!incrResult.success) {
|
|
1639
|
-
return err(
|
|
1640
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
1641
|
-
iamM("iam_updateOtpAttemptsFailed", { params: { message: incrResult.error.message } }),
|
|
1642
|
-
incrResult.error
|
|
1643
|
-
);
|
|
1644
|
-
}
|
|
1645
|
-
const ttlResult = await cache.kv.ttl(otpKey);
|
|
1646
|
-
if (!ttlResult.success)
|
|
1647
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), ttlResult.error);
|
|
1648
|
-
const ttl = ttlResult.success && ttlResult.data > 0 ? ttlResult.data : 1;
|
|
1649
|
-
const expire = await cache.kv.expire(attemptsKey, ttl);
|
|
1650
|
-
if (!expire.success)
|
|
1651
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), expire.error);
|
|
1652
|
-
return ok(incrResult.data);
|
|
1589
|
+
const key = keyFor(input.identifier);
|
|
1590
|
+
const current = await cache.kv.get(key);
|
|
1591
|
+
if (!current.success)
|
|
1592
|
+
return failure();
|
|
1593
|
+
if (current.data && current.data.cooldownUntil > Date.now()) {
|
|
1594
|
+
return err(HaiIamError.OTP_RESEND_TOO_FAST, iamM("iam_otpResendTooFast", { params: { seconds: Math.ceil((current.data.cooldownUntil - Date.now()) / 1e3) } }));
|
|
1595
|
+
}
|
|
1596
|
+
if (Date.now() >= state.expiresAt)
|
|
1597
|
+
return invalid();
|
|
1598
|
+
const published = await cache.kv.compareAndSwap(key, current.data ?? void 0, state, { px: Math.max(state.expiresAt, state.cooldownUntil) - Date.now() });
|
|
1599
|
+
if (!published.success)
|
|
1600
|
+
return failure();
|
|
1601
|
+
if (!published.data)
|
|
1602
|
+
return err(HaiIamError.SECURITY_BUSY, iamM("iam_securityBusy"));
|
|
1603
|
+
if (Date.now() >= state.expiresAt)
|
|
1604
|
+
return invalid();
|
|
1605
|
+
return ok({ idempotencyKey: `iam-otp:${state.version}`, expiresAt: new Date(state.expiresAt) });
|
|
1653
1606
|
},
|
|
1654
|
-
async
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1607
|
+
async consumeOtp(identifier, code, maxAttempts) {
|
|
1608
|
+
const key = keyFor(identifier);
|
|
1609
|
+
let version;
|
|
1610
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
1611
|
+
const current = await cache.kv.get(key);
|
|
1612
|
+
if (!current.success)
|
|
1613
|
+
return failure();
|
|
1614
|
+
const state = current.data;
|
|
1615
|
+
if (!state || state.code === null || state.expiresAt <= Date.now() || state.attempts >= maxAttempts || version && state.version !== version)
|
|
1616
|
+
return invalid();
|
|
1617
|
+
version = state.version;
|
|
1618
|
+
const correct = core.string.constantTimeEqual(state.code, code);
|
|
1619
|
+
const attempts = state.attempts + 1;
|
|
1620
|
+
const next = { ...state, attempts, code: correct || attempts >= maxAttempts ? null : state.code };
|
|
1621
|
+
const committed = await cache.kv.compareAndSwap(key, state, next, { keepTtl: true });
|
|
1622
|
+
if (!committed.success)
|
|
1623
|
+
return failure();
|
|
1624
|
+
if (!committed.data)
|
|
1625
|
+
continue;
|
|
1626
|
+
if (!correct || state.expiresAt <= Date.now())
|
|
1627
|
+
return err(HaiIamError.OTP_INVALID, iamM("iam_otpWrong"));
|
|
1628
|
+
return ok(void 0);
|
|
1662
1629
|
}
|
|
1663
|
-
|
|
1664
|
-
if (!removed.success)
|
|
1665
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), removed.error);
|
|
1666
|
-
return ok(void 0);
|
|
1630
|
+
return err(HaiIamError.SECURITY_BUSY, iamM("iam_securityBusy"));
|
|
1667
1631
|
}
|
|
1668
1632
|
};
|
|
1669
|
-
otpRepoInstance
|
|
1670
|
-
return repo;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
// src/session/iam-session-utils.ts
|
|
1674
|
-
var BASE64_PLUS_REGEX = /\+/g;
|
|
1675
|
-
var BASE64_SLASH_REGEX = /\//g;
|
|
1676
|
-
var BASE64_TRAILING_PADDING_REGEX = /=+$/;
|
|
1677
|
-
function generateToken() {
|
|
1678
|
-
const bytes = new Uint8Array(32);
|
|
1679
|
-
crypto.getRandomValues(bytes);
|
|
1680
|
-
const base64 = btoa(String.fromCharCode(...bytes));
|
|
1681
|
-
return base64.replace(BASE64_PLUS_REGEX, "-").replace(BASE64_SLASH_REGEX, "_").replace(BASE64_TRAILING_PADDING_REGEX, "");
|
|
1682
|
-
}
|
|
1683
|
-
function buildSession(options, now, sessionTtl, accessToken) {
|
|
1684
|
-
return {
|
|
1685
|
-
apiKeyId: options.apiKeyId,
|
|
1686
|
-
apiKeyScopes: options.apiKeyScopes,
|
|
1687
|
-
userId: options.userId,
|
|
1688
|
-
username: options.username,
|
|
1689
|
-
displayName: options.displayName,
|
|
1690
|
-
avatarUrl: options.avatarUrl,
|
|
1691
|
-
roles: options.roles ?? [],
|
|
1692
|
-
permissions: options.permissions ?? [],
|
|
1693
|
-
source: options.source,
|
|
1694
|
-
accessToken,
|
|
1695
|
-
createdAt: now,
|
|
1696
|
-
lastActiveAt: now,
|
|
1697
|
-
expiresAt: new Date(now.getTime() + sessionTtl * 1e3),
|
|
1698
|
-
data: options.data
|
|
1699
|
-
};
|
|
1700
|
-
}
|
|
1701
|
-
function getSessionTtl(session, now = Date.now()) {
|
|
1702
|
-
return Math.max(0, Math.floor((session.expiresAt.getTime() - now) / 1e3));
|
|
1703
|
-
}
|
|
1704
|
-
function applySessionPatch(session, patch) {
|
|
1705
|
-
const nextSession = { ...session };
|
|
1706
|
-
if (patch.data !== void 0) {
|
|
1707
|
-
nextSession.data = { ...nextSession.data, ...patch.data };
|
|
1708
|
-
}
|
|
1709
|
-
if (patch.roles !== void 0) {
|
|
1710
|
-
nextSession.roles = patch.roles;
|
|
1711
|
-
}
|
|
1712
|
-
if (patch.permissions !== void 0) {
|
|
1713
|
-
nextSession.permissions = patch.permissions;
|
|
1714
|
-
}
|
|
1715
|
-
if (patch.username !== void 0) {
|
|
1716
|
-
nextSession.username = patch.username;
|
|
1717
|
-
}
|
|
1718
|
-
if (patch.displayName !== void 0) {
|
|
1719
|
-
nextSession.displayName = patch.displayName;
|
|
1720
|
-
}
|
|
1721
|
-
if (patch.avatarUrl !== void 0) {
|
|
1722
|
-
nextSession.avatarUrl = patch.avatarUrl;
|
|
1723
|
-
}
|
|
1724
|
-
if (patch.source !== void 0) {
|
|
1725
|
-
nextSession.source = patch.source;
|
|
1726
|
-
}
|
|
1727
|
-
nextSession.lastActiveAt = /* @__PURE__ */ new Date();
|
|
1728
|
-
return nextSession;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
// src/iam-security.ts
|
|
1732
|
-
async function withIamLock(key, operation) {
|
|
1733
|
-
const owner = generateToken();
|
|
1734
|
-
const lockKey = `hai:iam:security:${key}`;
|
|
1735
|
-
for (let attempt = 0; attempt < 500; attempt++) {
|
|
1736
|
-
const acquired = await cache.lock.acquire(lockKey, { owner, ttl: 30 });
|
|
1737
|
-
if (!acquired.success)
|
|
1738
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), acquired.error);
|
|
1739
|
-
if (acquired.data) {
|
|
1740
|
-
let leaseValid = true;
|
|
1741
|
-
const renewal = setInterval(() => {
|
|
1742
|
-
void cache.lock.extend(lockKey, 30, owner).then((result) => {
|
|
1743
|
-
if (!result.success || !result.data)
|
|
1744
|
-
leaseValid = false;
|
|
1745
|
-
}).catch(() => {
|
|
1746
|
-
leaseValid = false;
|
|
1747
|
-
});
|
|
1748
|
-
}, 1e4);
|
|
1749
|
-
try {
|
|
1750
|
-
const result = await operation();
|
|
1751
|
-
if (!leaseValid)
|
|
1752
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"));
|
|
1753
|
-
return result;
|
|
1754
|
-
} catch (error) {
|
|
1755
|
-
return err(HaiIamError.INTERNAL_ERROR, iamM("iam_securityStateFailed"), error);
|
|
1756
|
-
} finally {
|
|
1757
|
-
clearInterval(renewal);
|
|
1758
|
-
await cache.lock.release(lockKey, owner);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1762
|
-
}
|
|
1763
|
-
return err(HaiIamError.SECURITY_BUSY, iamM("iam_securityBusy"));
|
|
1633
|
+
return otpRepoInstance;
|
|
1764
1634
|
}
|
|
1765
|
-
|
|
1766
|
-
// src/authn/otp/iam-authn-otp-strategy.ts
|
|
1767
1635
|
var logger3 = core.logger.child({ module: "iam", scope: "otp-strategy" });
|
|
1768
1636
|
var PHONE_IDENTIFIER_REGEX = /^\+?\d{8,}$/;
|
|
1769
1637
|
var PHONE_SEPARATOR_REGEX = /[\s-]/g;
|
|
@@ -1809,103 +1677,72 @@ function createOtpStrategy(config) {
|
|
|
1809
1677
|
const { code } = credentialResult.data;
|
|
1810
1678
|
if (!isChannelEnabled(identifier))
|
|
1811
1679
|
return err(HaiIamError.LOGIN_DISABLED, iamM("iam_identifierTypeNotSupported"));
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
if (storedUser) {
|
|
1819
|
-
|
|
1820
|
-
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1821
|
-
}
|
|
1822
|
-
if (isAccountLocked(storedUser)) {
|
|
1823
|
-
return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
|
|
1824
|
-
}
|
|
1680
|
+
const userResult = await config.userRepository.findByIdentifier(identifier);
|
|
1681
|
+
if (!userResult.success) {
|
|
1682
|
+
return userResult;
|
|
1683
|
+
}
|
|
1684
|
+
let storedUser = userResult.data;
|
|
1685
|
+
if (storedUser) {
|
|
1686
|
+
if (!storedUser.enabled) {
|
|
1687
|
+
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1825
1688
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
return storedOtpResult;
|
|
1689
|
+
if (isAccountLocked(storedUser)) {
|
|
1690
|
+
return err(HaiIamError.USER_LOCKED, iamM("iam_accountLocked"));
|
|
1829
1691
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1692
|
+
}
|
|
1693
|
+
const consumed = await config.otpRepository.consumeOtp(identifier, code, otpConfig.maxAttempts);
|
|
1694
|
+
if (!consumed.success) {
|
|
1695
|
+
if (storedUser && consumed.error.code === HaiIamError.OTP_INVALID.code) {
|
|
1696
|
+
const failed = await recordLoginFailure(config.userRepository, storedUser, { maxLoginAttempts, lockoutDuration });
|
|
1697
|
+
if (!failed.success)
|
|
1698
|
+
return failed;
|
|
1833
1699
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1700
|
+
return consumed;
|
|
1701
|
+
}
|
|
1702
|
+
if (!storedUser && allowAutoRegister) {
|
|
1703
|
+
const type = identifierType(identifier);
|
|
1704
|
+
const createResult = await config.userRepository.create({
|
|
1705
|
+
username: identifier,
|
|
1706
|
+
email: type === "email" ? identifier : void 0,
|
|
1707
|
+
phone: type === "phone" ? identifier : void 0,
|
|
1708
|
+
enabled: defaultEnabled,
|
|
1709
|
+
emailVerified: type === "email",
|
|
1710
|
+
phoneVerified: type === "phone"
|
|
1711
|
+
});
|
|
1712
|
+
if (!createResult.success) {
|
|
1713
|
+
return err(
|
|
1714
|
+
HaiIamError.REPOSITORY_ERROR,
|
|
1715
|
+
iamM("iam_createUserFailed", { params: { message: createResult.error.message } }),
|
|
1716
|
+
createResult.error
|
|
1717
|
+
);
|
|
1844
1718
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
return attemptResult;
|
|
1849
|
-
if (attemptResult.data >= otpConfig.maxAttempts) {
|
|
1850
|
-
const removed2 = await config.otpRepository.removeOtp(identifier);
|
|
1851
|
-
if (!removed2.success)
|
|
1852
|
-
return removed2;
|
|
1853
|
-
}
|
|
1854
|
-
if (storedUser) {
|
|
1855
|
-
const failureResult = await recordLoginFailure(config.userRepository, storedUser, { maxLoginAttempts, lockoutDuration });
|
|
1856
|
-
if (!failureResult.success)
|
|
1857
|
-
return failureResult;
|
|
1858
|
-
}
|
|
1859
|
-
return err(HaiIamError.OTP_INVALID, iamM("iam_otpWrong"));
|
|
1719
|
+
const createdResult = await config.userRepository.findByIdentifier(identifier);
|
|
1720
|
+
if (!createdResult.success) {
|
|
1721
|
+
return createdResult;
|
|
1860
1722
|
}
|
|
1861
|
-
|
|
1862
|
-
if (
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
email: type === "email" ? identifier : void 0,
|
|
1869
|
-
phone: type === "phone" ? identifier : void 0,
|
|
1870
|
-
enabled: defaultEnabled,
|
|
1871
|
-
emailVerified: type === "email",
|
|
1872
|
-
phoneVerified: type === "phone"
|
|
1873
|
-
});
|
|
1874
|
-
if (!createResult.success) {
|
|
1875
|
-
return err(
|
|
1876
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
1877
|
-
iamM("iam_createUserFailed", { params: { message: createResult.error.message } }),
|
|
1878
|
-
createResult.error
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1881
|
-
const createdResult = await config.userRepository.findByIdentifier(identifier);
|
|
1882
|
-
if (!createdResult.success) {
|
|
1883
|
-
return createdResult;
|
|
1884
|
-
}
|
|
1885
|
-
storedUser = createdResult.data;
|
|
1886
|
-
if (storedUser && config.onUserAutoRegistered) {
|
|
1887
|
-
try {
|
|
1888
|
-
const assigned = await config.onUserAutoRegistered(storedUser.id);
|
|
1889
|
-
if (!assigned.success) {
|
|
1890
|
-
await config.userRepository.deleteById(storedUser.id);
|
|
1891
|
-
return assigned;
|
|
1892
|
-
}
|
|
1893
|
-
} catch (callbackError) {
|
|
1894
|
-
logger3.warn("onUserAutoRegistered callback failed", { userId: storedUser.id, error: callbackError });
|
|
1723
|
+
storedUser = createdResult.data;
|
|
1724
|
+
if (storedUser && config.onUserAutoRegistered) {
|
|
1725
|
+
try {
|
|
1726
|
+
const assigned = await config.onUserAutoRegistered(storedUser.id);
|
|
1727
|
+
if (!assigned.success) {
|
|
1728
|
+
await config.userRepository.deleteById(storedUser.id);
|
|
1729
|
+
return assigned;
|
|
1895
1730
|
}
|
|
1731
|
+
} catch (callbackError) {
|
|
1732
|
+
logger3.warn("onUserAutoRegistered callback failed", { userId: storedUser.id, error: callbackError });
|
|
1896
1733
|
}
|
|
1897
1734
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
});
|
|
1735
|
+
}
|
|
1736
|
+
if (!storedUser) {
|
|
1737
|
+
return err(HaiIamError.USER_NOT_FOUND, iamM("iam_userNotExist"));
|
|
1738
|
+
}
|
|
1739
|
+
if (!storedUser.enabled)
|
|
1740
|
+
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
1741
|
+
const resetResult = await resetLoginFailures(config.userRepository, storedUser);
|
|
1742
|
+
if (!resetResult.success)
|
|
1743
|
+
return resetResult;
|
|
1744
|
+
logger3.info("OTP authentication succeeded", { userId: storedUser.id });
|
|
1745
|
+
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
1909
1746
|
}
|
|
1910
1747
|
};
|
|
1911
1748
|
function isChannelEnabled(identifier) {
|
|
@@ -1917,43 +1754,28 @@ function createOtpStrategy(config) {
|
|
|
1917
1754
|
if (!isChannelEnabled(identifier))
|
|
1918
1755
|
return err(HaiIamError.LOGIN_DISABLED, iamM("iam_identifierTypeNotSupported"));
|
|
1919
1756
|
const code = generateOtpCode(otpConfig.length);
|
|
1920
|
-
const
|
|
1921
|
-
const saved = await withIamLock(`otp:${identifier}`, async () => {
|
|
1922
|
-
const cooldownKey = `hai:iam:otp:cooldown:${identifier}`;
|
|
1923
|
-
const cooldown = await cache.kv.get(cooldownKey);
|
|
1924
|
-
if (!cooldown.success)
|
|
1925
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), cooldown.error);
|
|
1926
|
-
if (cooldown.data && cooldown.data > Date.now())
|
|
1927
|
-
return err(HaiIamError.OTP_RESEND_TOO_FAST, iamM("iam_otpResendTooFast", { params: { seconds: Math.ceil((cooldown.data - Date.now()) / 1e3) } }));
|
|
1928
|
-
const storeResult = await config.otpRepository.saveOtp(identifier, code, otpConfig.expiresIn);
|
|
1929
|
-
if (!storeResult.success)
|
|
1930
|
-
return storeResult;
|
|
1931
|
-
const limited = await cache.kv.set(cooldownKey, Date.now() + otpConfig.resendInterval * 1e3, { ex: otpConfig.resendInterval });
|
|
1932
|
-
if (!limited.success)
|
|
1933
|
-
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_securityStateFailed"), limited.error);
|
|
1934
|
-
return ok(void 0);
|
|
1935
|
-
});
|
|
1757
|
+
const saved = await config.otpRepository.issueOtp({ identifier, code, expiresIn: otpConfig.expiresIn, resendInterval: otpConfig.resendInterval });
|
|
1936
1758
|
if (!saved.success)
|
|
1937
1759
|
return saved;
|
|
1760
|
+
const context = saved.data;
|
|
1761
|
+
const expiresAt = context.expiresAt;
|
|
1938
1762
|
const type = identifierType(identifier);
|
|
1939
1763
|
if (type === "email" && config.onOtpSendEmail) {
|
|
1940
1764
|
try {
|
|
1941
|
-
await config.onOtpSendEmail(identifier, code);
|
|
1942
|
-
} catch
|
|
1765
|
+
await config.onOtpSendEmail(identifier, code, context);
|
|
1766
|
+
} catch {
|
|
1943
1767
|
return err(
|
|
1944
1768
|
HaiIamError.INTERNAL_ERROR,
|
|
1945
|
-
iamM("
|
|
1946
|
-
error
|
|
1769
|
+
iamM("iam_notificationDeliveryUnknown")
|
|
1947
1770
|
);
|
|
1948
1771
|
}
|
|
1949
1772
|
} else if (type === "phone" && config.onOtpSendSms) {
|
|
1950
1773
|
try {
|
|
1951
|
-
await config.onOtpSendSms(identifier, code);
|
|
1952
|
-
} catch
|
|
1774
|
+
await config.onOtpSendSms(identifier, code, context);
|
|
1775
|
+
} catch {
|
|
1953
1776
|
return err(
|
|
1954
1777
|
HaiIamError.INTERNAL_ERROR,
|
|
1955
|
-
iamM("
|
|
1956
|
-
error
|
|
1778
|
+
iamM("iam_notificationDeliveryUnknown")
|
|
1957
1779
|
);
|
|
1958
1780
|
}
|
|
1959
1781
|
} else {
|
|
@@ -2087,7 +1909,7 @@ function createPasswordStrategy(config) {
|
|
|
2087
1909
|
if (!resetResult.success)
|
|
2088
1910
|
return resetResult;
|
|
2089
1911
|
logger4.info("Password authentication succeeded", { userId: storedUser.id });
|
|
2090
|
-
return ok(toUser(storedUser));
|
|
1912
|
+
return ok({ ...toUser(storedUser), credentialVersion: storedUser.credentialVersion ?? "" });
|
|
2091
1913
|
}
|
|
2092
1914
|
};
|
|
2093
1915
|
function hashPassword(password) {
|
|
@@ -2249,6 +2071,7 @@ function buildAuthnOperations(deps) {
|
|
|
2249
2071
|
const effective = apiKey ? applyApiKeyScopes(access, apiKey.scopes) : access;
|
|
2250
2072
|
const sessionResult = await sessionFunctions.create({
|
|
2251
2073
|
userId: user.id,
|
|
2074
|
+
expectedCredentialVersion: user.credentialVersion,
|
|
2252
2075
|
username: user.username,
|
|
2253
2076
|
displayName: user.displayName,
|
|
2254
2077
|
avatarUrl: user.avatarUrl,
|
|
@@ -2264,8 +2087,9 @@ function buildAuthnOperations(deps) {
|
|
|
2264
2087
|
if (!tokenPair) {
|
|
2265
2088
|
return err(HaiIamError.SESSION_CREATE_FAILED, iamM("iam_createSessionFailed"));
|
|
2266
2089
|
}
|
|
2090
|
+
const { credentialVersion, ...publicUser } = user;
|
|
2267
2091
|
return ok({
|
|
2268
|
-
user,
|
|
2092
|
+
user: publicUser,
|
|
2269
2093
|
tokens: tokenPair,
|
|
2270
2094
|
...effective,
|
|
2271
2095
|
agreements: buildAgreementDisplay(agreementConfig, agreementConfig.showOnLogin)
|
|
@@ -2292,7 +2116,14 @@ function buildAuthnOperations(deps) {
|
|
|
2292
2116
|
}
|
|
2293
2117
|
return result;
|
|
2294
2118
|
}
|
|
2295
|
-
return { loginRegisteredUser:
|
|
2119
|
+
return { loginRegisteredUser: async (user) => {
|
|
2120
|
+
const stored = await userRepository.findById(user.id);
|
|
2121
|
+
if (!stored.success)
|
|
2122
|
+
return stored;
|
|
2123
|
+
if (!stored.data?.enabled)
|
|
2124
|
+
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
2125
|
+
return buildAuthResult({ ...user, credentialVersion: stored.data.credentialVersion ?? "" });
|
|
2126
|
+
}, authn: {
|
|
2296
2127
|
async login(credentials) {
|
|
2297
2128
|
return loginWithStrategy("password", credentials, passwordStrategy);
|
|
2298
2129
|
},
|
|
@@ -2314,16 +2145,7 @@ function buildAuthnOperations(deps) {
|
|
|
2314
2145
|
return buildAuthResult(authenticated.data.user, authenticated.data.apiKey);
|
|
2315
2146
|
},
|
|
2316
2147
|
async logout(accessToken) {
|
|
2317
|
-
|
|
2318
|
-
if (sessionResult.success && sessionResult.data) {
|
|
2319
|
-
const tokenPair = sessionResult.data.data?._tokenPair;
|
|
2320
|
-
if (tokenPair?.refreshToken) {
|
|
2321
|
-
await sessionFunctions.revokeRefresh(tokenPair.refreshToken);
|
|
2322
|
-
}
|
|
2323
|
-
await sessionFunctions.delete(sessionResult.data.accessToken);
|
|
2324
|
-
logger5.info("User logged out", { userId: sessionResult.data.userId });
|
|
2325
|
-
}
|
|
2326
|
-
return ok(void 0);
|
|
2148
|
+
return sessionFunctions.delete(accessToken);
|
|
2327
2149
|
},
|
|
2328
2150
|
async verifyToken(accessToken) {
|
|
2329
2151
|
const session = await sessionFunctions.verifyToken(accessToken);
|
|
@@ -2337,6 +2159,8 @@ function buildAuthnOperations(deps) {
|
|
|
2337
2159
|
return user;
|
|
2338
2160
|
if (!user.data?.enabled)
|
|
2339
2161
|
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
2162
|
+
if (session.data.credentialVersion !== (user.data.credentialVersion ?? ""))
|
|
2163
|
+
return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
|
|
2340
2164
|
if (!authorization.success)
|
|
2341
2165
|
return authorization;
|
|
2342
2166
|
const current = { ...session.data, username: user.data.username, displayName: user.data.displayName, avatarUrl: user.data.avatarUrl, ...authorization.data };
|
|
@@ -3792,25 +3616,25 @@ async function resolveIamNotifications(input) {
|
|
|
3792
3616
|
if (!templates.success)
|
|
3793
3617
|
throw new Error(iamM("iam_securityStateFailed"));
|
|
3794
3618
|
const configured = new Map(templates.data.map((template) => [template.name, template.provider]));
|
|
3795
|
-
async function send(template, to, vars) {
|
|
3619
|
+
async function send(template, to, vars, context) {
|
|
3796
3620
|
const provider = configured.get(template);
|
|
3797
3621
|
if (!reach?.isInitialized || !provider || !to)
|
|
3798
3622
|
throw new Error(iamM("iam_passwordResetDeliveryFailed"));
|
|
3799
|
-
const result = await reach.send({ provider, to, template, vars });
|
|
3623
|
+
const result = await reach.send({ provider, to, template, vars, idempotencyKey: context.idempotencyKey });
|
|
3800
3624
|
if (!result.success || !result.data.success)
|
|
3801
3625
|
throw new Error(iamM("iam_passwordResetDeliveryFailed"));
|
|
3802
3626
|
}
|
|
3803
3627
|
if (needsEmail && configured.has("otp_email"))
|
|
3804
|
-
callbacks.onOtpSendEmail = (email, code) => send("otp_email", email, { code });
|
|
3628
|
+
callbacks.onOtpSendEmail = (email, code, context) => send("otp_email", email, { code }, context);
|
|
3805
3629
|
if (needsSms && configured.has("otp_sms"))
|
|
3806
|
-
callbacks.onOtpSendSms = (phone, code) => send("otp_sms", phone, { code });
|
|
3630
|
+
callbacks.onOtpSendSms = (phone, code, context) => send("otp_sms", phone, { code }, context);
|
|
3807
3631
|
const resetTemplate = input.passwordReset?.mode === "phone" ? "password_reset_sms" : "password_reset";
|
|
3808
3632
|
if (needsReset && configured.has(resetTemplate)) {
|
|
3809
|
-
callbacks.onPasswordResetRequest = (user, token,
|
|
3633
|
+
callbacks.onPasswordResetRequest = (user, token, context) => {
|
|
3810
3634
|
const url = input.passwordReset?.resetUrl ? new URL(input.passwordReset.resetUrl) : null;
|
|
3811
3635
|
url?.searchParams.set("token", token);
|
|
3812
|
-
const vars = { token, expiresAt: expiresAt.toISOString(), resetUrl: url?.toString() ?? token };
|
|
3813
|
-
return send(resetTemplate, input.passwordReset?.mode === "phone" ? user.phone ?? "" : user.email ?? "", vars);
|
|
3636
|
+
const vars = { token, expiresAt: context.expiresAt.toISOString(), resetUrl: url?.toString() ?? token };
|
|
3637
|
+
return send(resetTemplate, input.passwordReset?.mode === "phone" ? user.phone ?? "" : user.email ?? "", vars, context);
|
|
3814
3638
|
};
|
|
3815
3639
|
}
|
|
3816
3640
|
return callbacks;
|
|
@@ -3943,283 +3767,267 @@ async function seedIamData(authz) {
|
|
|
3943
3767
|
);
|
|
3944
3768
|
}
|
|
3945
3769
|
}
|
|
3946
|
-
var
|
|
3947
|
-
var
|
|
3948
|
-
var
|
|
3949
|
-
function
|
|
3950
|
-
|
|
3770
|
+
var BASE64_PLUS_REGEX = /\+/g;
|
|
3771
|
+
var BASE64_SLASH_REGEX = /\//g;
|
|
3772
|
+
var BASE64_TRAILING_PADDING_REGEX = /=+$/;
|
|
3773
|
+
function generateToken() {
|
|
3774
|
+
const bytes = new Uint8Array(32);
|
|
3775
|
+
crypto.getRandomValues(bytes);
|
|
3776
|
+
const base64 = btoa(String.fromCharCode(...bytes));
|
|
3777
|
+
return base64.replace(BASE64_PLUS_REGEX, "-").replace(BASE64_SLASH_REGEX, "_").replace(BASE64_TRAILING_PADDING_REGEX, "");
|
|
3778
|
+
}
|
|
3779
|
+
function getUserSessionScope(userId) {
|
|
3780
|
+
return createHash("sha256").update(userId).digest("hex");
|
|
3951
3781
|
}
|
|
3952
|
-
function
|
|
3953
|
-
return
|
|
3782
|
+
function generateSessionToken(userId) {
|
|
3783
|
+
return `s1.${getUserSessionScope(userId)}.${generateToken()}`;
|
|
3954
3784
|
}
|
|
3955
|
-
function
|
|
3956
|
-
return
|
|
3785
|
+
function getTokenSessionScope(token) {
|
|
3786
|
+
return /^s1\.([a-f0-9]{64})\.[\w-]{43}$/.exec(token)?.[1];
|
|
3957
3787
|
}
|
|
3958
|
-
function
|
|
3788
|
+
function buildSession(options, now, sessionTtl, accessToken, credentialVersion) {
|
|
3959
3789
|
return {
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3790
|
+
version: crypto.randomUUID(),
|
|
3791
|
+
credentialVersion,
|
|
3792
|
+
apiKeyId: options.apiKeyId,
|
|
3793
|
+
apiKeyScopes: options.apiKeyScopes,
|
|
3794
|
+
userId: options.userId,
|
|
3795
|
+
username: options.username,
|
|
3796
|
+
displayName: options.displayName,
|
|
3797
|
+
avatarUrl: options.avatarUrl,
|
|
3798
|
+
roles: options.roles ?? [],
|
|
3799
|
+
permissions: options.permissions ?? [],
|
|
3800
|
+
source: options.source,
|
|
3801
|
+
accessToken,
|
|
3802
|
+
createdAt: now,
|
|
3803
|
+
lastActiveAt: now,
|
|
3804
|
+
expiresAt: new Date(now.getTime() + sessionTtl * 1e3),
|
|
3805
|
+
maxAge: sessionTtl,
|
|
3806
|
+
data: options.data
|
|
3964
3807
|
};
|
|
3965
3808
|
}
|
|
3966
|
-
function
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
)
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
async updateByAccessToken(accessToken, data) {
|
|
4028
|
-
const sessionResult = await repo.getByAccessToken(accessToken);
|
|
4029
|
-
if (!sessionResult.success) {
|
|
4030
|
-
return sessionResult;
|
|
4031
|
-
}
|
|
4032
|
-
if (!sessionResult.data) {
|
|
4033
|
-
return err(
|
|
4034
|
-
HaiIamError.SESSION_NOT_FOUND,
|
|
4035
|
-
iamM("iam_sessionNotExist")
|
|
4036
|
-
);
|
|
4037
|
-
}
|
|
4038
|
-
const nextSession = applySessionPatch(sessionResult.data, data);
|
|
4039
|
-
const ttl = getSessionTtl(nextSession);
|
|
4040
|
-
const setResult = await cache.kv.set(buildTokenKey(accessToken), nextSession, { ex: ttl });
|
|
4041
|
-
if (!setResult.success) {
|
|
4042
|
-
return err(
|
|
4043
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4044
|
-
iamM("iam_saveSessionMappingCacheFailed", { params: { message: setResult.error.message } }),
|
|
4045
|
-
setResult.error
|
|
4046
|
-
);
|
|
4047
|
-
}
|
|
4048
|
-
return ok(void 0);
|
|
4049
|
-
},
|
|
4050
|
-
async removeByAccessToken(accessToken) {
|
|
4051
|
-
const tokenKey = buildTokenKey(accessToken);
|
|
4052
|
-
const sessionResult = await cache.kv.get(tokenKey);
|
|
4053
|
-
if (!sessionResult.success) {
|
|
4054
|
-
return buildRepositoryError(
|
|
4055
|
-
"iam_querySessionMappingCacheFailed",
|
|
4056
|
-
sessionResult.error.message,
|
|
4057
|
-
sessionResult.error
|
|
4058
|
-
);
|
|
4059
|
-
}
|
|
4060
|
-
if (sessionResult.data) {
|
|
4061
|
-
const session = sessionResult.data;
|
|
4062
|
-
const removeUserTokenResult = await cache.set_.srem(buildUserTokensKey(session.userId), accessToken);
|
|
4063
|
-
if (!removeUserTokenResult.success) {
|
|
4064
|
-
return buildRepositoryError(
|
|
4065
|
-
"iam_deleteUserSessionCacheFailed",
|
|
4066
|
-
removeUserTokenResult.error.message,
|
|
4067
|
-
removeUserTokenResult.error
|
|
4068
|
-
);
|
|
4069
|
-
}
|
|
4070
|
-
const tokenPair = session.data?._tokenPair;
|
|
4071
|
-
if (tokenPair?.refreshToken) {
|
|
4072
|
-
const removeRefreshTokenResult = await cache.kv.del(buildRefreshKey(tokenPair.refreshToken));
|
|
4073
|
-
if (!removeRefreshTokenResult.success) {
|
|
4074
|
-
return buildRepositoryError(
|
|
4075
|
-
"iam_deleteSessionMappingCacheFailed",
|
|
4076
|
-
removeRefreshTokenResult.error.message,
|
|
4077
|
-
removeRefreshTokenResult.error
|
|
4078
|
-
);
|
|
4079
|
-
}
|
|
3809
|
+
function applySessionPatch(session, patch) {
|
|
3810
|
+
const nextSession = { ...session };
|
|
3811
|
+
if (patch.data !== void 0) {
|
|
3812
|
+
nextSession.data = { ...nextSession.data, ...patch.data };
|
|
3813
|
+
}
|
|
3814
|
+
if (patch.roles !== void 0) {
|
|
3815
|
+
nextSession.roles = patch.roles;
|
|
3816
|
+
}
|
|
3817
|
+
if (patch.permissions !== void 0) {
|
|
3818
|
+
nextSession.permissions = patch.permissions;
|
|
3819
|
+
}
|
|
3820
|
+
if (patch.username !== void 0) {
|
|
3821
|
+
nextSession.username = patch.username;
|
|
3822
|
+
}
|
|
3823
|
+
if (patch.displayName !== void 0) {
|
|
3824
|
+
nextSession.displayName = patch.displayName;
|
|
3825
|
+
}
|
|
3826
|
+
if (patch.avatarUrl !== void 0) {
|
|
3827
|
+
nextSession.avatarUrl = patch.avatarUrl;
|
|
3828
|
+
}
|
|
3829
|
+
if (patch.source !== void 0) {
|
|
3830
|
+
nextSession.source = patch.source;
|
|
3831
|
+
}
|
|
3832
|
+
if (patch.expiresAt !== void 0)
|
|
3833
|
+
nextSession.expiresAt = new Date(patch.expiresAt);
|
|
3834
|
+
nextSession.lastActiveAt = patch.lastActiveAt !== void 0 ? new Date(patch.lastActiveAt) : /* @__PURE__ */ new Date();
|
|
3835
|
+
return nextSession;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
// src/session/iam-session-repository-cache.ts
|
|
3839
|
+
function retainedUntil(entry) {
|
|
3840
|
+
return Math.max(new Date(entry.session.expiresAt).getTime(), entry.refreshToken ? entry.refreshExpiresAt : 0);
|
|
3841
|
+
}
|
|
3842
|
+
function restoreSession(session) {
|
|
3843
|
+
const result = { ...session, createdAt: new Date(session.createdAt), lastActiveAt: new Date(session.lastActiveAt), expiresAt: new Date(session.expiresAt) };
|
|
3844
|
+
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.lastActiveAt, result.expiresAt].every((date) => Number.isFinite(date.getTime()))) {
|
|
3845
|
+
throw new TypeError("Invalid session state");
|
|
3846
|
+
}
|
|
3847
|
+
return result;
|
|
3848
|
+
}
|
|
3849
|
+
function stateKey(scope) {
|
|
3850
|
+
return `hai:iam:sessions:{${scope}}`;
|
|
3851
|
+
}
|
|
3852
|
+
function repositoryFailure(cause) {
|
|
3853
|
+
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_sessionStateFailed"), cause);
|
|
3854
|
+
}
|
|
3855
|
+
function createCacheSessionRepository(refreshTokenMaxAge) {
|
|
3856
|
+
async function read(scope) {
|
|
3857
|
+
try {
|
|
3858
|
+
const result = await cache.kv.get(stateKey(scope));
|
|
3859
|
+
if (!result.success)
|
|
3860
|
+
return repositoryFailure(result.error);
|
|
3861
|
+
if (result.data === null)
|
|
3862
|
+
return ok(void 0);
|
|
3863
|
+
const state = result.data;
|
|
3864
|
+
if (typeof state.revision !== "string" || !state.revision || !state.sessions || typeof state.sessions !== "object" || Array.isArray(state.sessions))
|
|
3865
|
+
throw new TypeError("Invalid session state");
|
|
3866
|
+
for (const [token, entry] of Object.entries(state.sessions)) {
|
|
3867
|
+
const session = restoreSession(entry.session);
|
|
3868
|
+
if (getTokenSessionScope(token) !== scope || token !== session.accessToken || getUserSessionScope(session.userId) !== scope || !Number.isSafeInteger(entry.refreshExpiresAt) || entry.refreshToken !== void 0 && getTokenSessionScope(entry.refreshToken) !== scope) {
|
|
3869
|
+
throw new TypeError("Invalid session state");
|
|
4080
3870
|
}
|
|
4081
3871
|
}
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
tokensResult.error.message,
|
|
4098
|
-
tokensResult.error
|
|
4099
|
-
);
|
|
4100
|
-
}
|
|
4101
|
-
let removed = 0;
|
|
4102
|
-
if (tokensResult.data.length > 0) {
|
|
4103
|
-
const results = await Promise.allSettled(
|
|
4104
|
-
tokensResult.data.map((token) => repo.removeByAccessToken(token))
|
|
4105
|
-
);
|
|
4106
|
-
for (const r of results) {
|
|
4107
|
-
if (r.status === "rejected") {
|
|
4108
|
-
return buildRepositoryError(
|
|
4109
|
-
"iam_deleteSessionFailed",
|
|
4110
|
-
String(r.reason),
|
|
4111
|
-
r.reason
|
|
4112
|
-
);
|
|
4113
|
-
}
|
|
4114
|
-
if (!r.value.success)
|
|
4115
|
-
return r.value;
|
|
4116
|
-
removed += 1;
|
|
3872
|
+
return ok(state);
|
|
3873
|
+
} catch (cause) {
|
|
3874
|
+
return repositoryFailure(cause);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
async function mutate(scope, operation) {
|
|
3878
|
+
try {
|
|
3879
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
3880
|
+
const current = await read(scope);
|
|
3881
|
+
if (!current.success)
|
|
3882
|
+
return current;
|
|
3883
|
+
const next = current.data ? JSON.parse(JSON.stringify(current.data)) : { revision: "", sessions: {} };
|
|
3884
|
+
for (const [token, entry] of Object.entries(next.sessions)) {
|
|
3885
|
+
if (retainedUntil(entry) <= Date.now())
|
|
3886
|
+
delete next.sessions[token];
|
|
4117
3887
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
)
|
|
4126
|
-
|
|
4127
|
-
|
|
3888
|
+
const outcome = operation(next);
|
|
3889
|
+
if (!outcome.success)
|
|
3890
|
+
return outcome;
|
|
3891
|
+
next.revision = crypto.randomUUID();
|
|
3892
|
+
const entries = Object.values(next.sessions);
|
|
3893
|
+
const px = entries.reduce((ttl, entry) => Math.max(ttl, retainedUntil(entry) - Date.now()), 1);
|
|
3894
|
+
const written = await cache.kv.compareAndSwap(stateKey(scope), current.data, entries.length ? next : void 0, { px });
|
|
3895
|
+
if (!written.success)
|
|
3896
|
+
return repositoryFailure(written.error);
|
|
3897
|
+
if (written.data)
|
|
3898
|
+
return outcome;
|
|
3899
|
+
}
|
|
3900
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
3901
|
+
} catch (cause) {
|
|
3902
|
+
return repositoryFailure(cause);
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
function storeSession(state, session, tokens) {
|
|
3906
|
+
const scope = getUserSessionScope(session.userId);
|
|
3907
|
+
if (getTokenSessionScope(session.accessToken) !== scope || tokens.accessToken !== session.accessToken || getTokenSessionScope(tokens.refreshToken) !== scope)
|
|
3908
|
+
return err(HaiIamError.TOKEN_INVALID, iamM("iam_sessionStateFailed"));
|
|
3909
|
+
state.sessions[session.accessToken] = { session, refreshToken: tokens.refreshToken, refreshExpiresAt: session.createdAt.getTime() + refreshTokenMaxAge * 1e3 };
|
|
3910
|
+
return ok(void 0);
|
|
3911
|
+
}
|
|
3912
|
+
return {
|
|
3913
|
+
save: (session, tokens, singleDevice = false) => mutate(getUserSessionScope(session.userId), (state) => {
|
|
3914
|
+
if (singleDevice)
|
|
3915
|
+
state.sessions = {};
|
|
3916
|
+
return storeSession(state, session, tokens);
|
|
3917
|
+
}),
|
|
3918
|
+
rotate: (previous, refreshToken, session, tokens) => mutate(getUserSessionScope(previous.userId), (state) => {
|
|
3919
|
+
const old = state.sessions[previous.accessToken];
|
|
3920
|
+
if (!old || old.refreshToken !== refreshToken || old.refreshExpiresAt <= Date.now())
|
|
3921
|
+
return err(HaiIamError.TOKEN_EXPIRED, iamM("iam_refreshTokenExpired"));
|
|
3922
|
+
if (old.session.version !== previous.version || session.userId !== previous.userId)
|
|
3923
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
3924
|
+
delete state.sessions[previous.accessToken];
|
|
3925
|
+
return storeSession(state, session, tokens);
|
|
3926
|
+
}),
|
|
3927
|
+
async getByAccessToken(token) {
|
|
3928
|
+
const scope = getTokenSessionScope(token);
|
|
3929
|
+
if (!scope)
|
|
3930
|
+
return ok(null);
|
|
3931
|
+
const result = await read(scope);
|
|
3932
|
+
if (!result.success)
|
|
3933
|
+
return result;
|
|
3934
|
+
const record = result.data?.sessions[token];
|
|
3935
|
+
if (!record)
|
|
3936
|
+
return ok(null);
|
|
3937
|
+
const session = restoreSession(record.session);
|
|
3938
|
+
return ok(session.expiresAt.getTime() <= Date.now() ? null : session);
|
|
4128
3939
|
},
|
|
4129
|
-
async getByRefreshToken(
|
|
4130
|
-
const
|
|
4131
|
-
if (!
|
|
4132
|
-
return err(
|
|
4133
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4134
|
-
iamM("iam_querySessionMappingCacheFailed", { params: { message: mappingResult.error.message } }),
|
|
4135
|
-
mappingResult.error
|
|
4136
|
-
);
|
|
4137
|
-
}
|
|
4138
|
-
if (!mappingResult.data) {
|
|
3940
|
+
async getByRefreshToken(token) {
|
|
3941
|
+
const scope = getTokenSessionScope(token);
|
|
3942
|
+
if (!scope)
|
|
4139
3943
|
return ok(null);
|
|
4140
|
-
|
|
4141
|
-
|
|
3944
|
+
const result = await read(scope);
|
|
3945
|
+
if (!result.success)
|
|
3946
|
+
return result;
|
|
3947
|
+
const record = Object.values(result.data?.sessions ?? {}).find((entry) => entry.refreshToken === token && entry.refreshExpiresAt > Date.now());
|
|
3948
|
+
if (!record)
|
|
3949
|
+
return ok(null);
|
|
3950
|
+
return ok(restoreSession(record.session));
|
|
4142
3951
|
},
|
|
4143
|
-
async
|
|
4144
|
-
const
|
|
4145
|
-
if (!
|
|
4146
|
-
return err(
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
3952
|
+
async updateByAccessToken(token, patch, expectedVersion) {
|
|
3953
|
+
const scope = getTokenSessionScope(token);
|
|
3954
|
+
if (!scope)
|
|
3955
|
+
return err(HaiIamError.SESSION_NOT_FOUND, iamM("iam_sessionNotExist"));
|
|
3956
|
+
return mutate(scope, (state) => {
|
|
3957
|
+
const entry = state.sessions[token];
|
|
3958
|
+
if (!entry || new Date(entry.session.expiresAt).getTime() <= Date.now())
|
|
3959
|
+
return err(HaiIamError.SESSION_NOT_FOUND, iamM("iam_sessionNotExist"));
|
|
3960
|
+
if (!expectedVersion || entry.session.version !== expectedVersion)
|
|
3961
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
3962
|
+
const updated = applySessionPatch(restoreSession(entry.session), patch);
|
|
3963
|
+
if (![updated.expiresAt, updated.lastActiveAt].every((date) => Number.isFinite(date.getTime())))
|
|
3964
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sessionMaxAgeInvalid"));
|
|
3965
|
+
entry.session = { ...updated, version: crypto.randomUUID() };
|
|
3966
|
+
return ok(void 0);
|
|
3967
|
+
});
|
|
4153
3968
|
},
|
|
4154
|
-
async
|
|
4155
|
-
const
|
|
4156
|
-
if (!
|
|
4157
|
-
return err(
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
const
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
"iam_saveUserSessionCacheFailed",
|
|
4197
|
-
String(writeResult.reason),
|
|
4198
|
-
writeResult.reason
|
|
4199
|
-
);
|
|
4200
|
-
}
|
|
4201
|
-
if (!writeResult.value.success) {
|
|
4202
|
-
return buildRepositoryError(
|
|
4203
|
-
"iam_saveUserSessionCacheFailed",
|
|
4204
|
-
writeResult.value.error.message,
|
|
4205
|
-
writeResult.value.error
|
|
4206
|
-
);
|
|
3969
|
+
async touchByAccessToken(token, expectedVersion, now) {
|
|
3970
|
+
const scope = getTokenSessionScope(token);
|
|
3971
|
+
if (!scope)
|
|
3972
|
+
return err(HaiIamError.SESSION_NOT_FOUND, iamM("iam_sessionNotExist"));
|
|
3973
|
+
return mutate(scope, (state) => {
|
|
3974
|
+
const entry = state.sessions[token];
|
|
3975
|
+
if (!entry || new Date(entry.session.expiresAt).getTime() <= Date.now())
|
|
3976
|
+
return err(HaiIamError.SESSION_NOT_FOUND, iamM("iam_sessionNotExist"));
|
|
3977
|
+
if (entry.session.version !== expectedVersion)
|
|
3978
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
3979
|
+
const current = restoreSession(entry.session);
|
|
3980
|
+
const next = {
|
|
3981
|
+
...current,
|
|
3982
|
+
lastActiveAt: new Date(Math.max(current.lastActiveAt.getTime(), now.getTime())),
|
|
3983
|
+
expiresAt: new Date(Math.max(current.expiresAt.getTime(), now.getTime() + current.maxAge * 1e3))
|
|
3984
|
+
};
|
|
3985
|
+
entry.session = next;
|
|
3986
|
+
return ok(next);
|
|
3987
|
+
});
|
|
3988
|
+
},
|
|
3989
|
+
async removeByAccessToken(token) {
|
|
3990
|
+
const scope = getTokenSessionScope(token);
|
|
3991
|
+
if (!scope)
|
|
3992
|
+
return ok(void 0);
|
|
3993
|
+
return mutate(scope, (state) => {
|
|
3994
|
+
delete state.sessions[token];
|
|
3995
|
+
return ok(void 0);
|
|
3996
|
+
});
|
|
3997
|
+
},
|
|
3998
|
+
removeByUserId: (userId) => mutate(getUserSessionScope(userId), (state) => {
|
|
3999
|
+
const count = Object.keys(state.sessions).length;
|
|
4000
|
+
state.sessions = {};
|
|
4001
|
+
return ok(count);
|
|
4002
|
+
}),
|
|
4003
|
+
async removeRefreshToken(token) {
|
|
4004
|
+
const scope = getTokenSessionScope(token);
|
|
4005
|
+
if (!scope)
|
|
4006
|
+
return ok(void 0);
|
|
4007
|
+
return mutate(scope, (state) => {
|
|
4008
|
+
for (const entry of Object.values(state.sessions)) {
|
|
4009
|
+
if (entry.refreshToken === token)
|
|
4010
|
+
delete entry.refreshToken;
|
|
4207
4011
|
}
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4012
|
+
return ok(void 0);
|
|
4013
|
+
});
|
|
4014
|
+
},
|
|
4015
|
+
patchUserSessions(userId, patch) {
|
|
4016
|
+
let targets;
|
|
4017
|
+
return mutate(getUserSessionScope(userId), (state) => {
|
|
4018
|
+
targets ??= new Map(Object.entries(state.sessions).map(([token, entry]) => [token, entry.session.version]));
|
|
4019
|
+
for (const [token, version] of targets) {
|
|
4020
|
+
const entry = state.sessions[token];
|
|
4021
|
+
if (!entry)
|
|
4022
|
+
continue;
|
|
4023
|
+
if (entry.session.version !== version)
|
|
4024
|
+
return err(HaiIamError.CONFLICT, iamM("iam_sessionStateConflict"));
|
|
4025
|
+
entry.session = { ...applySessionPatch(restoreSession(entry.session), patch), version: crypto.randomUUID() };
|
|
4217
4026
|
}
|
|
4218
|
-
|
|
4219
|
-
|
|
4027
|
+
return ok(void 0);
|
|
4028
|
+
});
|
|
4220
4029
|
}
|
|
4221
4030
|
};
|
|
4222
|
-
return repo;
|
|
4223
4031
|
}
|
|
4224
4032
|
|
|
4225
4033
|
// src/session/iam-session-functions.ts
|
|
@@ -4236,10 +4044,10 @@ async function createSessionOperations(deps) {
|
|
|
4236
4044
|
const { config } = deps;
|
|
4237
4045
|
const sessionConfig = SessionConfigSchema.parse(config.session ?? {});
|
|
4238
4046
|
const sessionRepository = createCacheSessionRepository(
|
|
4239
|
-
sessionConfig.maxAge ?? 86400,
|
|
4240
4047
|
sessionConfig.refreshTokenMaxAge ?? 604800
|
|
4241
4048
|
);
|
|
4242
4049
|
const functions = buildSessionFunctions({
|
|
4050
|
+
readCredential: deps.readCredential,
|
|
4243
4051
|
maxAge: sessionConfig.maxAge,
|
|
4244
4052
|
sliding: sessionConfig.sliding,
|
|
4245
4053
|
singleDevice: sessionConfig.singleDevice,
|
|
@@ -4262,6 +4070,31 @@ function buildSessionFunctions(config) {
|
|
|
4262
4070
|
const sliding = config.sliding ?? true;
|
|
4263
4071
|
const singleDevice = config.singleDevice ?? false;
|
|
4264
4072
|
const repo = config.sessionRepository;
|
|
4073
|
+
function candidate(options, credentialVersion) {
|
|
4074
|
+
const now = /* @__PURE__ */ new Date();
|
|
4075
|
+
const ttl = options.maxAge ?? maxAge;
|
|
4076
|
+
if (!Number.isSafeInteger(ttl) || ttl <= 0 || !Number.isFinite(new Date(now.getTime() + ttl * 1e3).getTime()))
|
|
4077
|
+
return err(HaiIamError.INVALID_ARGUMENT, iamM("iam_sessionMaxAgeInvalid"));
|
|
4078
|
+
const accessToken = generateSessionToken(options.userId);
|
|
4079
|
+
const tokens = { accessToken, refreshToken: generateSessionToken(options.userId), expiresIn: ttl, tokenType: "Bearer" };
|
|
4080
|
+
const session = buildSession(options, now, ttl, accessToken, credentialVersion);
|
|
4081
|
+
session.data = { ...session.data, _tokenPair: tokens };
|
|
4082
|
+
return ok({ session, tokens });
|
|
4083
|
+
}
|
|
4084
|
+
async function credential(userId, expected) {
|
|
4085
|
+
try {
|
|
4086
|
+
const current = await config.readCredential(userId);
|
|
4087
|
+
if (!current.success)
|
|
4088
|
+
return current;
|
|
4089
|
+
if (!current.data || current.data.enabled !== true)
|
|
4090
|
+
return err(HaiIamError.USER_DISABLED, iamM("iam_accountDisabled"));
|
|
4091
|
+
if (typeof current.data.version !== "string" || expected !== void 0 && current.data.version !== expected)
|
|
4092
|
+
return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
|
|
4093
|
+
return ok(current.data.version);
|
|
4094
|
+
} catch {
|
|
4095
|
+
return err(HaiIamError.REPOSITORY_ERROR, iamM("iam_sessionExpired"));
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4265
4098
|
async function validateApiKey(session) {
|
|
4266
4099
|
if (!session.apiKeyId)
|
|
4267
4100
|
return ok(session);
|
|
@@ -4275,31 +4108,21 @@ function buildSessionFunctions(config) {
|
|
|
4275
4108
|
return err(HaiIamError.APIKEY_INVALID, iamM("iam_apikeyInvalid"));
|
|
4276
4109
|
if (!key.enabled)
|
|
4277
4110
|
return err(HaiIamError.APIKEY_DISABLED, iamM("iam_apikeyDisabled"));
|
|
4278
|
-
if (key.expiresAt && /* @__PURE__ */ new Date()
|
|
4111
|
+
if (key.expiresAt && /* @__PURE__ */ new Date() >= key.expiresAt)
|
|
4279
4112
|
return err(HaiIamError.APIKEY_EXPIRED, iamM("iam_apikeyExpired"));
|
|
4280
4113
|
return ok(applyApiKeyScopes({ ...session, apiKeyScopes: key.scopes }, key.scopes));
|
|
4281
4114
|
}
|
|
4282
4115
|
return {
|
|
4283
4116
|
async create(options) {
|
|
4284
4117
|
try {
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
const
|
|
4292
|
-
const
|
|
4293
|
-
accessToken,
|
|
4294
|
-
refreshToken: generateToken(),
|
|
4295
|
-
expiresIn: maxAge,
|
|
4296
|
-
tokenType: "Bearer"
|
|
4297
|
-
};
|
|
4298
|
-
const now = /* @__PURE__ */ new Date();
|
|
4299
|
-
const sessionTtl = options.maxAge ?? maxAge;
|
|
4300
|
-
const session = buildSession(options, now, sessionTtl, accessToken);
|
|
4301
|
-
session.data = { ...session.data, _tokenPair: tokenPair };
|
|
4302
|
-
const saveResult = await repo.save(session, tokenPair);
|
|
4118
|
+
const bound = await credential(options.userId, options.expectedCredentialVersion);
|
|
4119
|
+
if (!bound.success)
|
|
4120
|
+
return bound;
|
|
4121
|
+
const built = candidate(options, bound.data);
|
|
4122
|
+
if (!built.success)
|
|
4123
|
+
return built;
|
|
4124
|
+
const { session, tokens } = built.data;
|
|
4125
|
+
const saveResult = await repo.save(session, tokens, singleDevice);
|
|
4303
4126
|
if (!saveResult.success) {
|
|
4304
4127
|
return saveResult;
|
|
4305
4128
|
}
|
|
@@ -4329,20 +4152,23 @@ function buildSessionFunctions(config) {
|
|
|
4329
4152
|
}
|
|
4330
4153
|
return ok(null);
|
|
4331
4154
|
}
|
|
4155
|
+
if (typeof session.credentialVersion !== "string")
|
|
4156
|
+
return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
|
|
4157
|
+
const bound = await credential(session.userId, session.credentialVersion);
|
|
4158
|
+
if (!bound.success)
|
|
4159
|
+
return bound;
|
|
4332
4160
|
const validated = await validateApiKey(session);
|
|
4333
4161
|
if (!validated.success)
|
|
4334
4162
|
return validated;
|
|
4163
|
+
let output = validated.data;
|
|
4335
4164
|
if (sliding) {
|
|
4336
|
-
const
|
|
4337
|
-
const updateResult = await repo.updateByAccessToken(accessToken, {
|
|
4338
|
-
lastActiveAt: now,
|
|
4339
|
-
expiresAt: new Date(now.getTime() + maxAge * 1e3)
|
|
4340
|
-
});
|
|
4165
|
+
const updateResult = await repo.touchByAccessToken(accessToken, session.version, /* @__PURE__ */ new Date());
|
|
4341
4166
|
if (!updateResult.success) {
|
|
4342
4167
|
return updateResult;
|
|
4343
4168
|
}
|
|
4169
|
+
output = { ...output, lastActiveAt: updateResult.data.lastActiveAt, expiresAt: updateResult.data.expiresAt };
|
|
4344
4170
|
}
|
|
4345
|
-
return ok(stripInternalSessionFields(
|
|
4171
|
+
return ok(stripInternalSessionFields(output));
|
|
4346
4172
|
},
|
|
4347
4173
|
async verifyToken(accessToken) {
|
|
4348
4174
|
const sessionResult = await this.get(accessToken);
|
|
@@ -4357,8 +4183,8 @@ function buildSessionFunctions(config) {
|
|
|
4357
4183
|
}
|
|
4358
4184
|
return ok(sessionResult.data);
|
|
4359
4185
|
},
|
|
4360
|
-
async update(accessToken, data) {
|
|
4361
|
-
return repo.updateByAccessToken(accessToken, data);
|
|
4186
|
+
async update(accessToken, data, expectedVersion) {
|
|
4187
|
+
return repo.updateByAccessToken(accessToken, data, expectedVersion);
|
|
4362
4188
|
},
|
|
4363
4189
|
async delete(accessToken) {
|
|
4364
4190
|
logger8.debug("Session delete requested");
|
|
@@ -4384,15 +4210,16 @@ function buildSessionFunctions(config) {
|
|
|
4384
4210
|
iamM("iam_refreshTokenExpired")
|
|
4385
4211
|
);
|
|
4386
4212
|
}
|
|
4213
|
+
if (typeof oldSession.credentialVersion !== "string")
|
|
4214
|
+
return err(HaiIamError.SESSION_INVALID, iamM("iam_sessionExpired"));
|
|
4215
|
+
const bound = await credential(oldSession.userId, oldSession.credentialVersion);
|
|
4216
|
+
if (!bound.success)
|
|
4217
|
+
return bound;
|
|
4387
4218
|
const validated = await validateApiKey(oldSession);
|
|
4388
4219
|
if (!validated.success)
|
|
4389
4220
|
return validated;
|
|
4390
4221
|
const effectiveSession = validated.data;
|
|
4391
|
-
const
|
|
4392
|
-
if (!removeOldSessionResult.success) {
|
|
4393
|
-
return removeOldSessionResult;
|
|
4394
|
-
}
|
|
4395
|
-
const newSessionResult = await this.create({
|
|
4222
|
+
const next = candidate({
|
|
4396
4223
|
userId: oldSession.userId,
|
|
4397
4224
|
username: oldSession.username,
|
|
4398
4225
|
displayName: oldSession.displayName,
|
|
@@ -4402,20 +4229,19 @@ function buildSessionFunctions(config) {
|
|
|
4402
4229
|
apiKeyId: effectiveSession.apiKeyId,
|
|
4403
4230
|
apiKeyScopes: effectiveSession.apiKeyScopes,
|
|
4404
4231
|
source: oldSession.source,
|
|
4232
|
+
maxAge: oldSession.maxAge,
|
|
4405
4233
|
data: oldSession.data ? { ...oldSession.data, _tokenPair: void 0 } : void 0
|
|
4406
|
-
});
|
|
4407
|
-
if (!
|
|
4408
|
-
return
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
);
|
|
4416
|
-
}
|
|
4234
|
+
}, oldSession.credentialVersion);
|
|
4235
|
+
if (!next.success)
|
|
4236
|
+
return next;
|
|
4237
|
+
const rotated = await repo.rotate(oldSession, refreshToken, next.data.session, next.data.tokens);
|
|
4238
|
+
if (!rotated.success)
|
|
4239
|
+
return rotated;
|
|
4240
|
+
const current = await credential(oldSession.userId, oldSession.credentialVersion);
|
|
4241
|
+
if (!current.success)
|
|
4242
|
+
return current;
|
|
4417
4243
|
logger8.debug("Token refreshed", { userId: oldSession.userId });
|
|
4418
|
-
return ok(
|
|
4244
|
+
return ok(next.data.tokens);
|
|
4419
4245
|
} catch (error) {
|
|
4420
4246
|
return err(
|
|
4421
4247
|
HaiIamError.TOKEN_REFRESH_FAILED,
|
|
@@ -4451,7 +4277,7 @@ function createTicketOperations() {
|
|
|
4451
4277
|
expiresAt
|
|
4452
4278
|
};
|
|
4453
4279
|
const saved = await cache.kv.set(buildTicketKey(ticket), record, { px: ttlMs, nx: true });
|
|
4454
|
-
if (!saved.success) {
|
|
4280
|
+
if (!saved.success || !saved.data) {
|
|
4455
4281
|
logger9.warn("Failed to issue ticket", { purpose: options.purpose });
|
|
4456
4282
|
return err(HaiIamError.TICKET_ISSUE_FAILED, iamM("iam_ticketIssueFailed"));
|
|
4457
4283
|
}
|
|
@@ -4488,213 +4314,6 @@ function createTicketOperations() {
|
|
|
4488
4314
|
}
|
|
4489
4315
|
};
|
|
4490
4316
|
}
|
|
4491
|
-
function hashResetToken(token) {
|
|
4492
|
-
const result = crypto$1.hash.hash(token);
|
|
4493
|
-
if (!result.success) {
|
|
4494
|
-
return err(
|
|
4495
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4496
|
-
iamM("iam_hashResetTokenFailed", { params: { message: result.error.message } }),
|
|
4497
|
-
result.error
|
|
4498
|
-
);
|
|
4499
|
-
}
|
|
4500
|
-
return ok(result.data);
|
|
4501
|
-
}
|
|
4502
|
-
var RESET_TOKEN_KEY_PREFIX = "hai:iam:reset:";
|
|
4503
|
-
var RESET_ATTEMPTS_KEY_PREFIX = "hai:iam:reset:attempts:";
|
|
4504
|
-
var RESET_USER_TOKEN_KEY_PREFIX = "hai:iam:reset:user:";
|
|
4505
|
-
function buildResetTokenKey(hashedToken) {
|
|
4506
|
-
return `${RESET_TOKEN_KEY_PREFIX}${hashedToken}`;
|
|
4507
|
-
}
|
|
4508
|
-
function buildAttemptsKey(userId) {
|
|
4509
|
-
return `${RESET_ATTEMPTS_KEY_PREFIX}${userId}`;
|
|
4510
|
-
}
|
|
4511
|
-
function buildUserTokenKey(userId) {
|
|
4512
|
-
return `${RESET_USER_TOKEN_KEY_PREFIX}${userId}`;
|
|
4513
|
-
}
|
|
4514
|
-
function buildResetTokenRepositoryError(messageKey, message, cause) {
|
|
4515
|
-
return err(
|
|
4516
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4517
|
-
iamM(messageKey, { params: { message } }),
|
|
4518
|
-
cause
|
|
4519
|
-
);
|
|
4520
|
-
}
|
|
4521
|
-
var resetTokenRepoInstance = null;
|
|
4522
|
-
function resetResetTokenRepoSingleton() {
|
|
4523
|
-
resetTokenRepoInstance = null;
|
|
4524
|
-
}
|
|
4525
|
-
function createCacheResetTokenRepository() {
|
|
4526
|
-
if (resetTokenRepoInstance)
|
|
4527
|
-
return resetTokenRepoInstance;
|
|
4528
|
-
async function clearTokenState(hashedToken, userId) {
|
|
4529
|
-
const cleanupResults = await Promise.all([
|
|
4530
|
-
cache.kv.del(buildResetTokenKey(hashedToken)),
|
|
4531
|
-
cache.kv.del(buildAttemptsKey(userId)),
|
|
4532
|
-
cache.kv.del(buildUserTokenKey(userId))
|
|
4533
|
-
]);
|
|
4534
|
-
const failedCleanup = cleanupResults.find((result) => !result.success);
|
|
4535
|
-
if (failedCleanup && !failedCleanup.success) {
|
|
4536
|
-
return buildResetTokenRepositoryError(
|
|
4537
|
-
"iam_saveResetTokenFailed",
|
|
4538
|
-
failedCleanup.error.message,
|
|
4539
|
-
failedCleanup.error
|
|
4540
|
-
);
|
|
4541
|
-
}
|
|
4542
|
-
return ok(void 0);
|
|
4543
|
-
}
|
|
4544
|
-
const repo = {
|
|
4545
|
-
async saveToken(token, userId, expiresAt) {
|
|
4546
|
-
return withIamLock(`reset:${userId}`, async () => {
|
|
4547
|
-
const hashResult = hashResetToken(token);
|
|
4548
|
-
if (!hashResult.success) {
|
|
4549
|
-
return hashResult;
|
|
4550
|
-
}
|
|
4551
|
-
const hashedToken = hashResult.data;
|
|
4552
|
-
const ttlSeconds = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
|
|
4553
|
-
const userTokenKey = buildUserTokenKey(userId);
|
|
4554
|
-
const existingTokenResult = await cache.kv.get(userTokenKey);
|
|
4555
|
-
if (!existingTokenResult.success) {
|
|
4556
|
-
return buildResetTokenRepositoryError(
|
|
4557
|
-
"iam_queryResetTokenFailed",
|
|
4558
|
-
existingTokenResult.error.message,
|
|
4559
|
-
existingTokenResult.error
|
|
4560
|
-
);
|
|
4561
|
-
}
|
|
4562
|
-
if (existingTokenResult.data && existingTokenResult.data !== hashedToken) {
|
|
4563
|
-
const removeExistingTokenResult = await cache.kv.del(buildResetTokenKey(existingTokenResult.data));
|
|
4564
|
-
if (!removeExistingTokenResult.success) {
|
|
4565
|
-
return buildResetTokenRepositoryError(
|
|
4566
|
-
"iam_saveResetTokenFailed",
|
|
4567
|
-
removeExistingTokenResult.error.message,
|
|
4568
|
-
removeExistingTokenResult.error
|
|
4569
|
-
);
|
|
4570
|
-
}
|
|
4571
|
-
}
|
|
4572
|
-
const setResult = await cache.kv.set(buildResetTokenKey(hashedToken), userId, { ex: ttlSeconds });
|
|
4573
|
-
if (!setResult.success) {
|
|
4574
|
-
return err(
|
|
4575
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4576
|
-
iamM("iam_saveResetTokenFailed", { params: { message: setResult.error.message } }),
|
|
4577
|
-
setResult.error
|
|
4578
|
-
);
|
|
4579
|
-
}
|
|
4580
|
-
const saveUserTokenResult = await cache.kv.set(userTokenKey, hashedToken, { ex: ttlSeconds });
|
|
4581
|
-
if (!saveUserTokenResult.success) {
|
|
4582
|
-
await cache.kv.del(buildResetTokenKey(hashedToken));
|
|
4583
|
-
return buildResetTokenRepositoryError(
|
|
4584
|
-
"iam_saveResetTokenFailed",
|
|
4585
|
-
saveUserTokenResult.error.message,
|
|
4586
|
-
saveUserTokenResult.error
|
|
4587
|
-
);
|
|
4588
|
-
}
|
|
4589
|
-
const attemptsResult = await cache.kv.set(buildAttemptsKey(userId), 0, { ex: ttlSeconds });
|
|
4590
|
-
if (!attemptsResult.success) {
|
|
4591
|
-
await Promise.all([
|
|
4592
|
-
cache.kv.del(buildResetTokenKey(hashedToken)),
|
|
4593
|
-
cache.kv.del(userTokenKey)
|
|
4594
|
-
]);
|
|
4595
|
-
return buildResetTokenRepositoryError(
|
|
4596
|
-
"iam_saveResetTokenFailed",
|
|
4597
|
-
attemptsResult.error.message,
|
|
4598
|
-
attemptsResult.error
|
|
4599
|
-
);
|
|
4600
|
-
}
|
|
4601
|
-
return ok(void 0);
|
|
4602
|
-
});
|
|
4603
|
-
},
|
|
4604
|
-
async useToken(token, maxAttempts, operation) {
|
|
4605
|
-
const hash = hashResetToken(token);
|
|
4606
|
-
if (!hash.success)
|
|
4607
|
-
return hash;
|
|
4608
|
-
const user = await cache.kv.get(buildResetTokenKey(hash.data));
|
|
4609
|
-
if (!user.success)
|
|
4610
|
-
return buildResetTokenRepositoryError("iam_queryResetTokenFailed", user.error.message, user.error);
|
|
4611
|
-
if (!user.data)
|
|
4612
|
-
return err(HaiIamError.RESET_TOKEN_INVALID, iamM("iam_resetTokenInvalid"));
|
|
4613
|
-
return withIamLock(`reset:${user.data}`, async () => {
|
|
4614
|
-
const verified = await repo.tryGetUserByToken(token, maxAttempts);
|
|
4615
|
-
if (!verified.success)
|
|
4616
|
-
return verified;
|
|
4617
|
-
const result = await operation(verified.data);
|
|
4618
|
-
if (!result.success)
|
|
4619
|
-
return result;
|
|
4620
|
-
return repo.removeToken(token);
|
|
4621
|
-
});
|
|
4622
|
-
},
|
|
4623
|
-
async tryGetUserByToken(token, maxAttempts) {
|
|
4624
|
-
const hashResult = hashResetToken(token);
|
|
4625
|
-
if (!hashResult.success) {
|
|
4626
|
-
return hashResult;
|
|
4627
|
-
}
|
|
4628
|
-
const hashedToken = hashResult.data;
|
|
4629
|
-
const tokenKey = buildResetTokenKey(hashedToken);
|
|
4630
|
-
const result = await cache.kv.get(tokenKey);
|
|
4631
|
-
if (!result.success) {
|
|
4632
|
-
return err(
|
|
4633
|
-
HaiIamError.REPOSITORY_ERROR,
|
|
4634
|
-
iamM("iam_queryResetTokenFailed", { params: { message: result.error.message } }),
|
|
4635
|
-
result.error
|
|
4636
|
-
);
|
|
4637
|
-
}
|
|
4638
|
-
if (!result.data) {
|
|
4639
|
-
return err(
|
|
4640
|
-
HaiIamError.RESET_TOKEN_INVALID,
|
|
4641
|
-
iamM("iam_resetTokenInvalid")
|
|
4642
|
-
);
|
|
4643
|
-
}
|
|
4644
|
-
const userId = result.data;
|
|
4645
|
-
const current = await cache.kv.get(buildUserTokenKey(userId));
|
|
4646
|
-
if (!current.success)
|
|
4647
|
-
return buildResetTokenRepositoryError("iam_queryResetTokenFailed", current.error.message, current.error);
|
|
4648
|
-
if (current.data !== hashedToken)
|
|
4649
|
-
return err(HaiIamError.RESET_TOKEN_INVALID, iamM("iam_resetTokenInvalid"));
|
|
4650
|
-
const attemptsKey = buildAttemptsKey(userId);
|
|
4651
|
-
const incrResult = await cache.kv.incr(attemptsKey);
|
|
4652
|
-
if (!incrResult.success) {
|
|
4653
|
-
return buildResetTokenRepositoryError(
|
|
4654
|
-
"iam_saveResetTokenFailed",
|
|
4655
|
-
incrResult.error.message,
|
|
4656
|
-
incrResult.error
|
|
4657
|
-
);
|
|
4658
|
-
}
|
|
4659
|
-
const nextAttempts = incrResult.data;
|
|
4660
|
-
if (nextAttempts > maxAttempts) {
|
|
4661
|
-
const clearResult = await clearTokenState(hashedToken, userId);
|
|
4662
|
-
if (!clearResult.success) {
|
|
4663
|
-
return clearResult;
|
|
4664
|
-
}
|
|
4665
|
-
return err(
|
|
4666
|
-
HaiIamError.RESET_TOKEN_MAX_ATTEMPTS,
|
|
4667
|
-
iamM("iam_resetTokenMaxAttempts")
|
|
4668
|
-
);
|
|
4669
|
-
}
|
|
4670
|
-
return ok(userId);
|
|
4671
|
-
},
|
|
4672
|
-
async removeToken(token) {
|
|
4673
|
-
const hashResult = hashResetToken(token);
|
|
4674
|
-
if (!hashResult.success) {
|
|
4675
|
-
return hashResult;
|
|
4676
|
-
}
|
|
4677
|
-
const hashedToken = hashResult.data;
|
|
4678
|
-
const tokenKey = buildResetTokenKey(hashedToken);
|
|
4679
|
-
const result = await cache.kv.get(tokenKey);
|
|
4680
|
-
if (!result.success) {
|
|
4681
|
-
return buildResetTokenRepositoryError(
|
|
4682
|
-
"iam_queryResetTokenFailed",
|
|
4683
|
-
result.error.message,
|
|
4684
|
-
result.error
|
|
4685
|
-
);
|
|
4686
|
-
}
|
|
4687
|
-
if (!result.data) {
|
|
4688
|
-
return ok(void 0);
|
|
4689
|
-
}
|
|
4690
|
-
return clearTokenState(hashedToken, result.data);
|
|
4691
|
-
}
|
|
4692
|
-
};
|
|
4693
|
-
resetTokenRepoInstance = repo;
|
|
4694
|
-
return repo;
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
|
-
// src/user/iam-user-functions.ts
|
|
4698
4317
|
var logger10 = core.logger.child({ module: "iam", scope: "user" });
|
|
4699
4318
|
var USER_SORT_COLUMNS = {
|
|
4700
4319
|
/** 用户名列。 */
|
|
@@ -4712,10 +4331,8 @@ async function createUserOperations(deps) {
|
|
|
4712
4331
|
try {
|
|
4713
4332
|
const { config, passwordStrategyResult, sessionFunctions, authzFunctions, onPasswordResetRequest } = deps;
|
|
4714
4333
|
const userRepository = await createDbUserRepository();
|
|
4715
|
-
const resetTokenRepository = createCacheResetTokenRepository();
|
|
4716
4334
|
const functions = buildUserFunctions({
|
|
4717
4335
|
userRepository,
|
|
4718
|
-
resetTokenRepository,
|
|
4719
4336
|
passwordStrategyResult,
|
|
4720
4337
|
sessionFunctions,
|
|
4721
4338
|
authzFunctions,
|
|
@@ -5172,14 +4789,14 @@ function buildPasswordChangeOps(ctx) {
|
|
|
5172
4789
|
const hashResult = hashPassword(newPassword);
|
|
5173
4790
|
if (!hashResult.success)
|
|
5174
4791
|
return hashResult;
|
|
5175
|
-
const updateResult = await userRepository.
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
4792
|
+
const updateResult = await userRepository.setPassword({ userId, expectedVersion: userResult.data.credentialVersion ?? "", passwordHash: hashResult.data });
|
|
4793
|
+
if (!updateResult.success)
|
|
4794
|
+
return updateResult;
|
|
4795
|
+
if (!updateResult.data)
|
|
4796
|
+
return err(HaiIamError.CONFLICT, iamM("iam_securityStateFailed"));
|
|
4797
|
+
const revoked = await sessionFunctions.deleteByUserId(userId);
|
|
4798
|
+
if (!revoked.success)
|
|
4799
|
+
return revoked;
|
|
5183
4800
|
logger10.info("Admin reset password", { userId });
|
|
5184
4801
|
return ok(void 0);
|
|
5185
4802
|
},
|
|
@@ -5229,14 +4846,14 @@ function buildPasswordChangeOps(ctx) {
|
|
|
5229
4846
|
const hashResult = hashPassword(newPassword);
|
|
5230
4847
|
if (!hashResult.success)
|
|
5231
4848
|
return hashResult;
|
|
5232
|
-
const updateResult = await userRepository.
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
4849
|
+
const updateResult = await userRepository.setPassword({ userId, expectedVersion: user.credentialVersion ?? "", passwordHash: hashResult.data });
|
|
4850
|
+
if (!updateResult.success)
|
|
4851
|
+
return updateResult;
|
|
4852
|
+
if (!updateResult.data)
|
|
4853
|
+
return err(HaiIamError.CONFLICT, iamM("iam_securityStateFailed"));
|
|
4854
|
+
const revoked = await sessionFunctions.deleteByUserId(userId);
|
|
4855
|
+
if (!revoked.success)
|
|
4856
|
+
return revoked;
|
|
5240
4857
|
logger10.info("Password changed", { userId });
|
|
5241
4858
|
return ok(void 0);
|
|
5242
4859
|
},
|
|
@@ -5258,7 +4875,7 @@ function buildPasswordChangeOps(ctx) {
|
|
|
5258
4875
|
};
|
|
5259
4876
|
}
|
|
5260
4877
|
function buildPasswordResetOps(ctx) {
|
|
5261
|
-
const { userRepository,
|
|
4878
|
+
const { userRepository, sessionFunctions, onPasswordResetRequest } = ctx;
|
|
5262
4879
|
const { validatePassword, hashPassword } = ctx;
|
|
5263
4880
|
return {
|
|
5264
4881
|
async requestPasswordReset(identifier) {
|
|
@@ -5280,15 +4897,19 @@ function buildPasswordResetOps(ctx) {
|
|
|
5280
4897
|
return ok(void 0);
|
|
5281
4898
|
const token = generateToken();
|
|
5282
4899
|
const expiresAt = new Date(Date.now() + resetConfig.tokenExpiresIn * 1e3);
|
|
5283
|
-
const
|
|
5284
|
-
if (!
|
|
4900
|
+
const tokenHash = crypto$1.hash.hash(token);
|
|
4901
|
+
if (!tokenHash.success)
|
|
4902
|
+
return tokenHash;
|
|
4903
|
+
const saveResult = await userRepository.publishPasswordReset({ user: userResult.data, tokenHash: tokenHash.data, expiresAt });
|
|
4904
|
+
if (!saveResult.success)
|
|
5285
4905
|
return saveResult;
|
|
5286
|
-
|
|
4906
|
+
if (!saveResult.data)
|
|
4907
|
+
return err(HaiIamError.CONFLICT, iamM("iam_securityStateFailed"));
|
|
5287
4908
|
try {
|
|
5288
|
-
await onPasswordResetRequest(user, token, expiresAt);
|
|
4909
|
+
await onPasswordResetRequest(user, token, { expiresAt, idempotencyKey: `iam-reset:${tokenHash.data}` });
|
|
5289
4910
|
} catch {
|
|
5290
4911
|
logger10.error("Password reset callback failed", { userId: user.id });
|
|
5291
|
-
return err(HaiIamError.INTERNAL_ERROR, iamM("
|
|
4912
|
+
return err(HaiIamError.INTERNAL_ERROR, iamM("iam_notificationDeliveryUnknown"));
|
|
5292
4913
|
}
|
|
5293
4914
|
logger10.info("Password reset token generated", { userId: user.id });
|
|
5294
4915
|
return ok(void 0);
|
|
@@ -5298,41 +4919,28 @@ function buildPasswordResetOps(ctx) {
|
|
|
5298
4919
|
const resetConfig = ctx.resetConfig;
|
|
5299
4920
|
if (!resetConfig.enabled)
|
|
5300
4921
|
return err(HaiIamError.PASSWORD_RESET_DISABLED, iamM("iam_passwordResetDisabled"));
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
loginFailedCount: 0,
|
|
5324
|
-
lockedUntil: null,
|
|
5325
|
-
lastLoginFailedAt: null
|
|
5326
|
-
});
|
|
5327
|
-
if (!updateResult.success) {
|
|
5328
|
-
return mapRepositoryError("iam_updateUserFailed", updateResult.error.message);
|
|
5329
|
-
}
|
|
5330
|
-
const revoked = await sessionFunctions.deleteByUserId(userId);
|
|
5331
|
-
if (!revoked.success)
|
|
5332
|
-
return revoked;
|
|
5333
|
-
logger10.info("Password reset confirmed", { userId });
|
|
5334
|
-
return ok(void 0);
|
|
5335
|
-
});
|
|
4922
|
+
const tokenHash = crypto$1.hash.hash(token);
|
|
4923
|
+
if (!tokenHash.success)
|
|
4924
|
+
return tokenHash;
|
|
4925
|
+
const claimed = await userRepository.claimPasswordReset(tokenHash.data, resetConfig.maxAttempts);
|
|
4926
|
+
if (!claimed.success)
|
|
4927
|
+
return claimed;
|
|
4928
|
+
const validateResult = validatePassword(newPassword);
|
|
4929
|
+
if (!validateResult.success)
|
|
4930
|
+
return validateResult;
|
|
4931
|
+
const hashResult = hashPassword(newPassword);
|
|
4932
|
+
if (!hashResult.success)
|
|
4933
|
+
return hashResult;
|
|
4934
|
+
const committed = await userRepository.commitPasswordReset({ user: claimed.data, tokenHash: tokenHash.data, passwordHash: hashResult.data });
|
|
4935
|
+
if (!committed.success)
|
|
4936
|
+
return committed;
|
|
4937
|
+
if (!committed.data)
|
|
4938
|
+
return err(HaiIamError.RESET_TOKEN_INVALID, iamM("iam_resetTokenInvalid"));
|
|
4939
|
+
const revoked = await sessionFunctions.deleteByUserId(claimed.data.id);
|
|
4940
|
+
if (!revoked.success)
|
|
4941
|
+
return revoked;
|
|
4942
|
+
logger10.info("Password reset confirmed", { userId: claimed.data.id });
|
|
4943
|
+
return ok(void 0);
|
|
5336
4944
|
}
|
|
5337
4945
|
};
|
|
5338
4946
|
}
|
|
@@ -5434,7 +5042,16 @@ var iam = {
|
|
|
5434
5042
|
const parsed = parseResult.data;
|
|
5435
5043
|
const { onPasswordResetRequest, onOtpSendEmail, onOtpSendSms } = await resolveIamNotifications(config);
|
|
5436
5044
|
logger11.info("IAM notification channels resolved", { email: Boolean(onOtpSendEmail), sms: Boolean(onOtpSendSms), passwordReset: Boolean(onPasswordResetRequest) });
|
|
5437
|
-
const
|
|
5045
|
+
const credentialUsers = await createDbUserRepository();
|
|
5046
|
+
const sessionResult = await createSessionOperations({
|
|
5047
|
+
config: parsed,
|
|
5048
|
+
readCredential: async (userId) => {
|
|
5049
|
+
const found = await credentialUsers.findById(userId);
|
|
5050
|
+
if (!found.success)
|
|
5051
|
+
return found;
|
|
5052
|
+
return ok(found.data ? { version: found.data.credentialVersion ?? "", enabled: found.data.enabled } : null);
|
|
5053
|
+
}
|
|
5054
|
+
});
|
|
5438
5055
|
if (!sessionResult.success) {
|
|
5439
5056
|
return sessionResult;
|
|
5440
5057
|
}
|
|
@@ -5548,13 +5165,12 @@ var iam = {
|
|
|
5548
5165
|
resetApiKeyRepoSingleton();
|
|
5549
5166
|
resetOtpRepoSingleton();
|
|
5550
5167
|
resetUserRepoSingleton();
|
|
5551
|
-
resetResetTokenRepoSingleton();
|
|
5552
5168
|
resetRoleRepoSingleton();
|
|
5553
5169
|
resetPermissionRepoSingleton();
|
|
5554
5170
|
logger11.info("IAM module closed");
|
|
5555
5171
|
}
|
|
5556
5172
|
};
|
|
5557
5173
|
|
|
5558
|
-
export {
|
|
5174
|
+
export { iam };
|
|
5559
5175
|
//# sourceMappingURL=index.js.map
|
|
5560
5176
|
//# sourceMappingURL=index.js.map
|