@cloudbase/oauth 3.1.12 → 3.2.1

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.
@@ -1,3 +1,334 @@
1
+ /**
2
+ * 认证错误分类枚举
3
+ * 用于区分不同类型的认证失败场景,方便调用方针对性处理
4
+ */
5
+ import { ErrorType } from './consts'
6
+
7
+ export enum AuthErrorCategory {
8
+ /** 身份源/登录方式未启用 - 需要在控制台开启对应认证方式 */
9
+ PROVIDER_NOT_ENABLED = 'PROVIDER_NOT_ENABLED',
10
+ /** 凭据错误 - 用户名、密码、验证码等不正确 */
11
+ INVALID_CREDENTIALS = 'INVALID_CREDENTIALS',
12
+ /** 用户不存在 */
13
+ USER_NOT_FOUND = 'USER_NOT_FOUND',
14
+ /** 用户状态异常(被封禁、待审核等) */
15
+ USER_STATUS_ABNORMAL = 'USER_STATUS_ABNORMAL',
16
+ /** 网络或服务端错误 */
17
+ SERVICE_ERROR = 'SERVICE_ERROR',
18
+ /** 参数校验失败 */
19
+ INVALID_PARAMS = 'INVALID_PARAMS',
20
+ /** 认证方式不匹配 - 使用了错误的登录方式(如匿名登录失败后用密码登录,但凭据不存在) */
21
+ AUTH_METHOD_MISMATCH = 'AUTH_METHOD_MISMATCH',
22
+ /** 其他未分类错误 */
23
+ UNKNOWN = 'UNKNOWN',
24
+ }
25
+
26
+ /**
27
+ * 云开发控制台认证配置页面路径
28
+ */
29
+ const CONSOLE_AUTH_URL = 'https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage'
30
+
31
+ const DOCS_URL = 'https://docs.cloudbase.net/api-reference/webv3-next/initialization'
32
+
33
+ /**
34
+ * 根据错误信息内容推断错误分类
35
+ *
36
+ * @param message - 错误描述信息(error_description 或 message)
37
+ * @param status - 错误状态码,对应 ErrorType 枚举值(如 'failed_precondition'、'invalid_password' 等)
38
+ * 来源于服务端响应的 error 字段,参考 {@link ErrorType}
39
+ */
40
+ function inferErrorCategory(message?: string, status?: string): AuthErrorCategory {
41
+ if (!message && !status) return AuthErrorCategory.UNKNOWN
42
+
43
+ try {
44
+ const msg = (message || '').toLowerCase()
45
+
46
+ // 身份源/登录方式未启用
47
+ if (
48
+ status === ErrorType.FAILED_PRECONDITION
49
+ || status === ErrorType.PROVIDER_NOT_ENABLED
50
+ || status === ErrorType.LOGIN_METHOD_DISABLED
51
+ || msg.includes('身份源')
52
+ || msg.includes('开启')
53
+ || msg.includes('identity provider')
54
+ || msg.includes('provider not enabled')
55
+ || msg.includes('login method')
56
+ || msg.includes('登录方式')
57
+ || msg.includes('not enabled')
58
+ || msg.includes('未开启')
59
+ || msg.includes('未启用')
60
+ || msg.includes('请联系开发者')
61
+ || msg.includes('anonymous login is not enabled')
62
+ || msg.includes('匿名登录未开启')
63
+ ) {
64
+ return AuthErrorCategory.PROVIDER_NOT_ENABLED
65
+ }
66
+
67
+ // 凭据错误
68
+ if (
69
+ status === ErrorType.INVALID_PASSWORD
70
+ || status === ErrorType.INVALID_GRANT
71
+ || status === ErrorType.INVALID_VERIFICATION_CODE
72
+ || status === ErrorType.INVALID_USERNAME_OR_PASSWORD
73
+ || status === ErrorType.INVALID_CREDENTIALS
74
+ || status === ErrorType.WRONG_PASSWORD
75
+ || status === ErrorType.UNAUTHENTICATED
76
+ || status === ErrorType.INVALID_TWO_FACTOR
77
+ || status === ErrorType.INVALID_TWO_FACTOR_RECOVERY
78
+ || status === ErrorType.PASSWORD_NOT_SET
79
+ || msg.includes('密码不正确')
80
+ || msg.includes('password')
81
+ || msg.includes('invalid_password')
82
+ || msg.includes('凭据')
83
+ || msg.includes('credentials')
84
+ || msg.includes('验证码')
85
+ || msg.includes('verification')
86
+ || msg.includes('invalid_verification_code')
87
+ || msg.includes('invalid_username_or_password')
88
+ || msg.includes('username or password')
89
+ || msg.includes('用户名或密码')
90
+ || msg.includes('wrong password')
91
+ || msg.includes('incorrect password')
92
+ ) {
93
+ return AuthErrorCategory.INVALID_CREDENTIALS
94
+ }
95
+
96
+ // 用户不存在
97
+ if (
98
+ status === ErrorType.NOT_FOUND
99
+ || status === ErrorType.USER_NOT_FOUND
100
+ || msg.includes('用户不存在')
101
+ || msg.includes('not found')
102
+ || msg.includes('user not found')
103
+ || msg.includes('不存在')
104
+ || msg.includes('no such user')
105
+ || msg.includes('user does not exist')
106
+ ) {
107
+ return AuthErrorCategory.USER_NOT_FOUND
108
+ }
109
+
110
+ // 用户状态异常
111
+ if (
112
+ status === ErrorType.USER_BLOCKED
113
+ || status === ErrorType.USER_PENDING
114
+ || status === ErrorType.UNDER_REVIEW
115
+ || status === ErrorType.INVALID_STATUS
116
+ || msg.includes('被封禁')
117
+ || msg.includes('blocked')
118
+ || msg.includes('pending')
119
+ || msg.includes('审核')
120
+ || msg.includes('review')
121
+ || msg.includes('locked')
122
+ || msg.includes('已锁定')
123
+ ) {
124
+ return AuthErrorCategory.USER_STATUS_ABNORMAL
125
+ }
126
+
127
+ // 服务端/网络错误
128
+ if (
129
+ status === ErrorType.SERVER_ERROR
130
+ || status === ErrorType.UNAVAILABLE
131
+ || status === ErrorType.TEMPORARILY_UNAVAILABLE
132
+ || status === ErrorType.INTERNAL
133
+ || status === ErrorType.UNREACHABLE
134
+ || status === ErrorType.DEADLINE_EXCEEDED
135
+ || status === ErrorType.DATA_LOSS
136
+ || msg.includes('network')
137
+ || msg.includes('timeout')
138
+ || msg.includes('服务')
139
+ || msg.includes('timed out')
140
+ || msg.includes('超时')
141
+ ) {
142
+ return AuthErrorCategory.SERVICE_ERROR
143
+ }
144
+
145
+ // 参数错误
146
+ if (
147
+ status === ErrorType.INVALID_ARGUMENT
148
+ || status === ErrorType.INVALID_REQUEST
149
+ || status === ErrorType.INVALID_SCOPE
150
+ || status === ErrorType.INVALID_REQUEST_URI
151
+ || status === ErrorType.INVALID_REQUEST_OBJECT
152
+ ) {
153
+ return AuthErrorCategory.INVALID_PARAMS
154
+ }
155
+
156
+ // 权限/授权错误 → 归入 PROVIDER_NOT_ENABLED(需要配置)
157
+ if (
158
+ status === ErrorType.PERMISSION_DENIED
159
+ || status === ErrorType.ACCESS_DENIED
160
+ || status === ErrorType.UNAUTHORIZED_CLIENT
161
+ ) {
162
+ return AuthErrorCategory.PROVIDER_NOT_ENABLED
163
+ }
164
+
165
+ return AuthErrorCategory.UNKNOWN
166
+ } catch (error) {
167
+ return status as any
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 根据错误分类生成可操作性指引信息
173
+ */
174
+ function generateHelpMessage(category: AuthErrorCategory, context?: { method?: string }): string {
175
+ const methodName = context?.method || 'signIn'
176
+
177
+ // 根据方法名推荐对应需要开启的登录方式
178
+ const methodToProvider: Record<string, string> = {
179
+ signInWithPassword: '「用户名密码登录」',
180
+ signInAnonymously: '「匿名登录」',
181
+ signInWithOtp: '「短信验证码登录」或「邮箱验证码登录」',
182
+ signUp: '「短信验证码登录」或「邮箱验证码登录」',
183
+ signInWithOAuth: '对应的第三方身份源',
184
+ signInWithIdToken: '对应的第三方身份源',
185
+ signInWithOpenId: '「微信小程序 OpenID 登录」',
186
+ signInWithPhoneAuth: '「微信小程序手机号授权登录」',
187
+ signInWithCustomTicket: '「自定义登录」',
188
+ }
189
+ const requiredProvider = methodToProvider[methodName] || '对应的登录方式'
190
+
191
+ switch (category) {
192
+ case AuthErrorCategory.PROVIDER_NOT_ENABLED:
193
+ return (
194
+ '[CloudBase Auth] 登录方式未开启\n'
195
+ + '\n'
196
+ + ` 当前调用的 ${methodName}() 所需的登录方式尚未在云开发控制台启用。\n`
197
+ + ` 需要开启的登录方式:${requiredProvider}\n`
198
+ + '\n'
199
+ + ' 请按以下步骤开启:\n'
200
+ + ` 1. 打开云开发控制台:${CONSOLE_AUTH_URL}\n`
201
+ + ' 2. 选择对应的云开发环境\n'
202
+ + ' 3. 进入「登录授权」页面,在「登录方式」或「身份源列表」中开启所需的登录方式\n'
203
+ + '\n'
204
+ + ' 各登录方法与所需登录方式的对应关系:\n'
205
+ + ' - signInWithPassword → 「用户名密码登录」\n'
206
+ + ' - signInAnonymously → 「匿名登录」\n'
207
+ + ' - signInWithOtp/signUp → 「短信验证码登录」或「邮箱验证码登录」\n'
208
+ + ' - signInWithOAuth/signInWithIdToken → 对应的第三方身份源\n'
209
+ + '\n'
210
+ + ' 如使用 CLI/MCP 工具,可通过 auth 工具查询和配置身份源状态。\n'
211
+ + ` 更多帮助请参考文档:${DOCS_URL}`
212
+ )
213
+
214
+ case AuthErrorCategory.INVALID_CREDENTIALS:
215
+ return (
216
+ '[CloudBase Auth] 凭据验证失败\n'
217
+ + '\n'
218
+ + ` 调用 ${methodName}() 时凭据校验未通过。\n`
219
+ + '\n'
220
+ + ' 请按优先级逐项排查:\n'
221
+ + ' 1. 确认用户名/邮箱/手机号是否拼写正确、格式完整\n'
222
+ + ' 2. 确认密码是否正确(注意大小写、特殊字符、前后空格)\n'
223
+ + ' 3. 确认用户账号确实存在(可通过 auth.isUsernameRegistered() 检查)\n'
224
+ + ' 4. 确认凭据参数来源可靠(环境变量、配置文件中的值是否已正确设置)\n'
225
+ + ' 5. 如使用加密登录(is_encrypt: true),请确认公钥版本正确\n'
226
+ + '\n'
227
+ + ' 如忘记密码,可通过 auth.resetPasswordForEmail() 重置密码。\n'
228
+ + '\n'
229
+ + ' 提示:如果是 AI agent / 自动化测试场景,请确认 ENV_ID、USERNAME、PASSWORD\n'
230
+ + ' 等环境变量已正确传入,且对应用户已在目标环境中注册。'
231
+ )
232
+
233
+ case AuthErrorCategory.USER_NOT_FOUND:
234
+ return (
235
+ '[CloudBase Auth] 用户不存在\n'
236
+ + '\n'
237
+ + ' 请检查以下项目:\n'
238
+ + ' 1. 确认用户名/邮箱/手机号是否正确\n'
239
+ + ' 2. 确认是在正确的云开发环境(envId)下操作\n'
240
+ + ' 3. 如果是新用户,请先通过 auth.signUp() 注册\n'
241
+ + ` 4. 也可在云开发控制台手动创建用户:${CONSOLE_AUTH_URL}\n`
242
+ + '\n'
243
+ + ' 提示:不同环境(开发/测试/生产)的用户库是隔离的,请确认 envId 正确。'
244
+ )
245
+
246
+ case AuthErrorCategory.USER_STATUS_ABNORMAL:
247
+ return (
248
+ '[CloudBase Auth] 用户状态异常\n'
249
+ + '\n'
250
+ + ' 当前用户账号可能处于以下状态之一:\n'
251
+ + ' - 账号已被封禁\n'
252
+ + ' - 账号正在审核中\n'
253
+ + ' - 账号已被锁定(多次密码错误)\n'
254
+ + '\n'
255
+ + ` 请联系管理员在云开发控制台检查用户状态:${CONSOLE_AUTH_URL}`
256
+ )
257
+
258
+ case AuthErrorCategory.SERVICE_ERROR:
259
+ return (
260
+ '[CloudBase Auth] 服务异常\n'
261
+ + '\n'
262
+ + ' 请检查以下项目:\n'
263
+ + ' 1. 网络连接是否正常\n'
264
+ + ' 2. 云开发环境是否正常运行\n'
265
+ + ' 3. 稍后重试,如持续失败请联系技术支持\n'
266
+ + '\n'
267
+ + ' 提示:如果是超时错误,请检查网络环境和请求频率。'
268
+ )
269
+
270
+ case AuthErrorCategory.INVALID_PARAMS:
271
+ return (
272
+ '[CloudBase Auth] 参数错误\n'
273
+ + '\n'
274
+ + ' 请检查传入的参数是否符合要求,参考 API 文档确认各参数的格式和约束。\n'
275
+ + ` 文档地址:${DOCS_URL}`
276
+ )
277
+
278
+ case AuthErrorCategory.AUTH_METHOD_MISMATCH:
279
+ return (
280
+ '[CloudBase Auth] 登录方式不匹配\n'
281
+ + '\n'
282
+ + ` 调用 ${methodName}() 时遇到错误,可能原因:\n`
283
+ + ' - 匿名登录失败后尝试了用户名密码登录,但凭据不正确或用户不存在\n'
284
+ + ' - 应使用匿名登录但传入了密码参数\n'
285
+ + ' - 应使用密码登录但未传入正确的凭据\n'
286
+ + '\n'
287
+ + ' 建议:\n'
288
+ + ' 1. 明确当前场景需要的登录方式:\n'
289
+ + ' - 无需用户注册 → auth.signInAnonymously()\n'
290
+ + ' - 有用户名和密码 → auth.signInWithPassword({ username, password })\n'
291
+ + ' - 第三方平台 → auth.signInWithOAuth({ provider })\n'
292
+ + ' 2. 不要在同一流程中混合使用不同的登录方式\n'
293
+ + ' 3. 每种登录方式需要在控制台开启对应的身份源\n'
294
+ + ` 4. 控制台地址:${CONSOLE_AUTH_URL}`
295
+ )
296
+
297
+ default:
298
+ return (
299
+ '[CloudBase Auth] 操作失败\n'
300
+ + '\n'
301
+ + ' 如果问题持续存在,建议:\n'
302
+ + ` 1. 检查云开发控制台的认证配置:${CONSOLE_AUTH_URL}\n`
303
+ + ' 2. 到官方问答社区提问:https://cnb.cool/tencent/cloud/cloudbase/community/-/issues\n'
304
+ + ` 3. 查阅文档:${DOCS_URL}`
305
+ )
306
+ }
307
+ }
308
+
309
+ /**
310
+ * 根据调用方法推荐最合适的登录方式
311
+ */
312
+ function generateLoginMethodHint(method?: string): string {
313
+ if (!method) return ''
314
+
315
+ const hints: Record<string, string> = {
316
+ signInWithPassword:
317
+ '当前使用「用户名密码登录」,需确保:\n'
318
+ + ' ✓ 控制台已开启「用户名密码登录」\n'
319
+ + ' ✓ 用户已注册且凭据正确\n'
320
+ + ' 如果不需要用户系统,建议改用 auth.signInAnonymously()',
321
+ signInAnonymously:
322
+ '当前使用「匿名登录」,需确保:\n'
323
+ + ' ✓ 控制台已开启「匿名登录」\n'
324
+ + ' 注意:匿名用户无需用户名密码,适用于快速体验或临时访问场景',
325
+ signInWithOtp: '当前使用「OTP 验证码登录」,需确保:\n' + ' ✓ 控制台已开启「短信验证码登录」或「邮箱验证码登录」',
326
+ signInWithOAuth: '当前使用「第三方 OAuth 登录」,需确保:\n' + ' ✓ 控制台已配置并开启对应的第三方身份源',
327
+ }
328
+
329
+ return hints[method] || ''
330
+ }
331
+
1
332
  export class AuthError extends Error {
2
333
  /**
3
334
  * Error code associated with the error. Most errors coming from
@@ -7,19 +338,40 @@ export class AuthError extends Error {
7
338
  */
8
339
  code: (string & {}) | undefined
9
340
 
10
- /** HTTP status code that caused the error. */
11
- status: number | undefined
341
+ /** status code that caused the error. */
342
+ status: string | undefined
12
343
 
13
344
  /** Request ID associated with the error. */
14
345
  requestId: string | undefined
15
346
 
347
+ /**
348
+ * 错误分类,用于区分不同类型的认证错误场景。
349
+ * 例如:PROVIDER_NOT_ENABLED(身份源未启用)、INVALID_CREDENTIALS(凭据错误)等。
350
+ */
351
+ category: AuthErrorCategory
352
+
353
+ /**
354
+ * 可操作性指引信息,包含具体的排查步骤和解决方案。
355
+ * 开发者可以直接使用此信息提示用户或用于调试。
356
+ */
357
+ helpMessage: string
358
+
359
+ /**
360
+ * 登录方式选择建议。当使用了不恰当的登录方式时,此字段会提供正确的选择指引。
361
+ */
362
+ loginMethodHint: string
363
+
16
364
  protected __isAuthError = true
17
365
 
18
- constructor(error) {
19
- super(error.error_description || error.message)
366
+ constructor(error, context?: { method?: string }) {
367
+ const message = error.error_description || error.message
368
+ super(message)
20
369
  this.name = 'AuthError'
21
370
  this.status = error.error
22
371
  this.code = error.error_code
23
372
  this.requestId = error.requestId
373
+ this.category = inferErrorCategory(message, this.status)
374
+ this.helpMessage = generateHelpMessage(this.category, context)
375
+ this.loginMethodHint = generateLoginMethodHint(context?.method)
24
376
  }
25
377
  }
@@ -118,6 +118,14 @@ export enum ErrorType {
118
118
  REGISTRATION_NOT_SUPPORTED = 'registration_not_supported',
119
119
  CAPTCHA_REQUIRED = 'captcha_required',
120
120
  CAPTCHA_INVALID = 'captcha_invalid',
121
+ // 身份源/登录方式相关错误
122
+ PROVIDER_NOT_ENABLED = 'provider_not_enabled',
123
+ LOGIN_METHOD_DISABLED = 'login_method_disabled',
124
+ // 扩展的凭据相关错误码(服务端实际返回格式)
125
+ INVALID_USERNAME_OR_PASSWORD = 'invalid_username_or_password',
126
+ INVALID_CREDENTIALS = 'invalid_credentials',
127
+ WRONG_PASSWORD = 'wrong_password',
128
+ USER_NOT_FOUND = 'user_not_found',
121
129
  }
122
130
 
123
131
  export const LOGIN_STATE_CHANGED_TYPE = {
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import { AuthOptions, Auth } from './auth/apis'
3
3
  import { AUTH_API_PREFIX } from './auth/consts'
4
4
  import { Credentials } from './oauth2client/models'
5
5
  export { Auth } from './auth/apis'
6
- export { AuthError } from './auth/auth-error'
6
+ export { AuthError, AuthErrorCategory } from './auth/auth-error'
7
7
  export * as authModels from './auth/models'
8
8
  export type { ProviderProfile, UserInfo, ModifyUserBasicInfoRequest } from './auth/models'
9
9
  export type { Credentials, OAuth2ClientOptions, ResponseError, AuthClientRequestOptions } from './oauth2client/models'