/** * 认证错误分类枚举 * 用于区分不同类型的认证失败场景,方便调用方针对性处理 */ import { ErrorType } from './consts' export enum AuthErrorCategory { /** 身份源/登录方式未启用 - 需要在控制台开启对应认证方式 */ PROVIDER_NOT_ENABLED = 'PROVIDER_NOT_ENABLED', /** 凭据错误 - 用户名、密码、验证码等不正确 */ INVALID_CREDENTIALS = 'INVALID_CREDENTIALS', /** 用户不存在 */ USER_NOT_FOUND = 'USER_NOT_FOUND', /** 用户状态异常(被封禁、待审核等) */ USER_STATUS_ABNORMAL = 'USER_STATUS_ABNORMAL', /** 网络或服务端错误 */ SERVICE_ERROR = 'SERVICE_ERROR', /** 参数校验失败 */ INVALID_PARAMS = 'INVALID_PARAMS', /** 认证方式不匹配 - 使用了错误的登录方式(如匿名登录失败后用密码登录,但凭据不存在) */ AUTH_METHOD_MISMATCH = 'AUTH_METHOD_MISMATCH', /** 其他未分类错误 */ UNKNOWN = 'UNKNOWN', } /** * 云开发控制台认证配置页面路径 */ const CONSOLE_AUTH_URL = 'https://tcb.cloud.tencent.com/dev?envId=#/identity/login-manage' const DOCS_URL = 'https://docs.cloudbase.net/api-reference/webv3-next/initialization' /** * 根据错误信息内容推断错误分类 * * @param message - 错误描述信息(error_description 或 message) * @param status - 错误状态码,对应 ErrorType 枚举值(如 'failed_precondition'、'invalid_password' 等) * 来源于服务端响应的 error 字段,参考 {@link ErrorType} */ function inferErrorCategory(message?: string, status?: string): AuthErrorCategory { if (!message && !status) return AuthErrorCategory.UNKNOWN try { const msg = (message || '').toLowerCase() // 身份源/登录方式未启用 if ( status === ErrorType.FAILED_PRECONDITION || status === ErrorType.PROVIDER_NOT_ENABLED || status === ErrorType.LOGIN_METHOD_DISABLED || msg.includes('身份源') || msg.includes('开启') || msg.includes('identity provider') || msg.includes('provider not enabled') || msg.includes('login method') || msg.includes('登录方式') || msg.includes('not enabled') || msg.includes('未开启') || msg.includes('未启用') || msg.includes('请联系开发者') || msg.includes('anonymous login is not enabled') || msg.includes('匿名登录未开启') ) { return AuthErrorCategory.PROVIDER_NOT_ENABLED } // 凭据错误 if ( status === ErrorType.INVALID_PASSWORD || status === ErrorType.INVALID_GRANT || status === ErrorType.INVALID_VERIFICATION_CODE || status === ErrorType.INVALID_USERNAME_OR_PASSWORD || status === ErrorType.INVALID_CREDENTIALS || status === ErrorType.WRONG_PASSWORD || status === ErrorType.UNAUTHENTICATED || status === ErrorType.INVALID_TWO_FACTOR || status === ErrorType.INVALID_TWO_FACTOR_RECOVERY || status === ErrorType.PASSWORD_NOT_SET || msg.includes('密码不正确') || msg.includes('password') || msg.includes('invalid_password') || msg.includes('凭据') || msg.includes('credentials') || msg.includes('验证码') || msg.includes('verification') || msg.includes('invalid_verification_code') || msg.includes('invalid_username_or_password') || msg.includes('username or password') || msg.includes('用户名或密码') || msg.includes('wrong password') || msg.includes('incorrect password') ) { return AuthErrorCategory.INVALID_CREDENTIALS } // 用户不存在 if ( status === ErrorType.NOT_FOUND || status === ErrorType.USER_NOT_FOUND || msg.includes('用户不存在') || msg.includes('not found') || msg.includes('user not found') || msg.includes('不存在') || msg.includes('no such user') || msg.includes('user does not exist') ) { return AuthErrorCategory.USER_NOT_FOUND } // 用户状态异常 if ( status === ErrorType.USER_BLOCKED || status === ErrorType.USER_PENDING || status === ErrorType.UNDER_REVIEW || status === ErrorType.INVALID_STATUS || msg.includes('被封禁') || msg.includes('blocked') || msg.includes('pending') || msg.includes('审核') || msg.includes('review') || msg.includes('locked') || msg.includes('已锁定') ) { return AuthErrorCategory.USER_STATUS_ABNORMAL } // 服务端/网络错误 if ( status === ErrorType.SERVER_ERROR || status === ErrorType.UNAVAILABLE || status === ErrorType.TEMPORARILY_UNAVAILABLE || status === ErrorType.INTERNAL || status === ErrorType.UNREACHABLE || status === ErrorType.DEADLINE_EXCEEDED || status === ErrorType.DATA_LOSS || msg.includes('network') || msg.includes('timeout') || msg.includes('服务') || msg.includes('timed out') || msg.includes('超时') ) { return AuthErrorCategory.SERVICE_ERROR } // 参数错误 if ( status === ErrorType.INVALID_ARGUMENT || status === ErrorType.INVALID_REQUEST || status === ErrorType.INVALID_SCOPE || status === ErrorType.INVALID_REQUEST_URI || status === ErrorType.INVALID_REQUEST_OBJECT ) { return AuthErrorCategory.INVALID_PARAMS } // 权限/授权错误 → 归入 PROVIDER_NOT_ENABLED(需要配置) if ( status === ErrorType.PERMISSION_DENIED || status === ErrorType.ACCESS_DENIED || status === ErrorType.UNAUTHORIZED_CLIENT ) { return AuthErrorCategory.PROVIDER_NOT_ENABLED } return AuthErrorCategory.UNKNOWN } catch (error) { return status as any } } /** * 根据错误分类生成可操作性指引信息 */ function generateHelpMessage(category: AuthErrorCategory, context?: { method?: string }): string { const methodName = context?.method || 'signIn' // 根据方法名推荐对应需要开启的登录方式 const methodToProvider: Record = { signInWithPassword: '「用户名密码登录」', signInAnonymously: '「匿名登录」', signInWithOtp: '「短信验证码登录」或「邮箱验证码登录」', signUp: '「短信验证码登录」或「邮箱验证码登录」', signInWithOAuth: '对应的第三方身份源', signInWithIdToken: '对应的第三方身份源', signInWithOpenId: '「微信小程序 OpenID 登录」', signInWithPhoneAuth: '「微信小程序手机号授权登录」', signInWithCustomTicket: '「自定义登录」', } const requiredProvider = methodToProvider[methodName] || '对应的登录方式' switch (category) { case AuthErrorCategory.PROVIDER_NOT_ENABLED: return ( '[CloudBase Auth] 登录方式未开启\n' + '\n' + ` 当前调用的 ${methodName}() 所需的登录方式尚未在云开发控制台启用。\n` + ` 需要开启的登录方式:${requiredProvider}\n` + '\n' + ' 请按以下步骤开启:\n' + ` 1. 打开云开发控制台:${CONSOLE_AUTH_URL}\n` + ' 2. 选择对应的云开发环境\n' + ' 3. 进入「登录授权」页面,在「登录方式」或「身份源列表」中开启所需的登录方式\n' + '\n' + ' 各登录方法与所需登录方式的对应关系:\n' + ' - signInWithPassword → 「用户名密码登录」\n' + ' - signInAnonymously → 「匿名登录」\n' + ' - signInWithOtp/signUp → 「短信验证码登录」或「邮箱验证码登录」\n' + ' - signInWithOAuth/signInWithIdToken → 对应的第三方身份源\n' + '\n' + ' 如使用 CLI/MCP 工具,可通过 auth 工具查询和配置身份源状态。\n' + ` 更多帮助请参考文档:${DOCS_URL}` ) case AuthErrorCategory.INVALID_CREDENTIALS: return ( '[CloudBase Auth] 凭据验证失败\n' + '\n' + ` 调用 ${methodName}() 时凭据校验未通过。\n` + '\n' + ' 请按优先级逐项排查:\n' + ' 1. 确认用户名/邮箱/手机号是否拼写正确、格式完整\n' + ' 2. 确认密码是否正确(注意大小写、特殊字符、前后空格)\n' + ' 3. 确认用户账号确实存在(可通过 auth.isUsernameRegistered() 检查)\n' + ' 4. 确认凭据参数来源可靠(环境变量、配置文件中的值是否已正确设置)\n' + ' 5. 如使用加密登录(is_encrypt: true),请确认公钥版本正确\n' + '\n' + ' 如忘记密码,可通过 auth.resetPasswordForEmail() 重置密码。\n' + '\n' + ' 提示:如果是 AI agent / 自动化测试场景,请确认 ENV_ID、USERNAME、PASSWORD\n' + ' 等环境变量已正确传入,且对应用户已在目标环境中注册。' ) case AuthErrorCategory.USER_NOT_FOUND: return ( '[CloudBase Auth] 用户不存在\n' + '\n' + ' 请检查以下项目:\n' + ' 1. 确认用户名/邮箱/手机号是否正确\n' + ' 2. 确认是在正确的云开发环境(envId)下操作\n' + ' 3. 如果是新用户,请先通过 auth.signUp() 注册\n' + ` 4. 也可在云开发控制台手动创建用户:${CONSOLE_AUTH_URL}\n` + '\n' + ' 提示:不同环境(开发/测试/生产)的用户库是隔离的,请确认 envId 正确。' ) case AuthErrorCategory.USER_STATUS_ABNORMAL: return ( '[CloudBase Auth] 用户状态异常\n' + '\n' + ' 当前用户账号可能处于以下状态之一:\n' + ' - 账号已被封禁\n' + ' - 账号正在审核中\n' + ' - 账号已被锁定(多次密码错误)\n' + '\n' + ` 请联系管理员在云开发控制台检查用户状态:${CONSOLE_AUTH_URL}` ) case AuthErrorCategory.SERVICE_ERROR: return ( '[CloudBase Auth] 服务异常\n' + '\n' + ' 请检查以下项目:\n' + ' 1. 网络连接是否正常\n' + ' 2. 云开发环境是否正常运行\n' + ' 3. 稍后重试,如持续失败请联系技术支持\n' + '\n' + ' 提示:如果是超时错误,请检查网络环境和请求频率。' ) case AuthErrorCategory.INVALID_PARAMS: return ( '[CloudBase Auth] 参数错误\n' + '\n' + ' 请检查传入的参数是否符合要求,参考 API 文档确认各参数的格式和约束。\n' + ` 文档地址:${DOCS_URL}` ) case AuthErrorCategory.AUTH_METHOD_MISMATCH: return ( '[CloudBase Auth] 登录方式不匹配\n' + '\n' + ` 调用 ${methodName}() 时遇到错误,可能原因:\n` + ' - 匿名登录失败后尝试了用户名密码登录,但凭据不正确或用户不存在\n' + ' - 应使用匿名登录但传入了密码参数\n' + ' - 应使用密码登录但未传入正确的凭据\n' + '\n' + ' 建议:\n' + ' 1. 明确当前场景需要的登录方式:\n' + ' - 无需用户注册 → auth.signInAnonymously()\n' + ' - 有用户名和密码 → auth.signInWithPassword({ username, password })\n' + ' - 第三方平台 → auth.signInWithOAuth({ provider })\n' + ' 2. 不要在同一流程中混合使用不同的登录方式\n' + ' 3. 每种登录方式需要在控制台开启对应的身份源\n' + ` 4. 控制台地址:${CONSOLE_AUTH_URL}` ) default: return ( '[CloudBase Auth] 操作失败\n' + '\n' + ' 如果问题持续存在,建议:\n' + ` 1. 检查云开发控制台的认证配置:${CONSOLE_AUTH_URL}\n` + ' 2. 到官方问答社区提问:https://cnb.cool/tencent/cloud/cloudbase/community/-/issues\n' + ` 3. 查阅文档:${DOCS_URL}` ) } } /** * 根据调用方法推荐最合适的登录方式 */ function generateLoginMethodHint(method?: string): string { if (!method) return '' const hints: Record = { signInWithPassword: '当前使用「用户名密码登录」,需确保:\n' + ' ✓ 控制台已开启「用户名密码登录」\n' + ' ✓ 用户已注册且凭据正确\n' + ' 如果不需要用户系统,建议改用 auth.signInAnonymously()', signInAnonymously: '当前使用「匿名登录」,需确保:\n' + ' ✓ 控制台已开启「匿名登录」\n' + ' 注意:匿名用户无需用户名密码,适用于快速体验或临时访问场景', signInWithOtp: '当前使用「OTP 验证码登录」,需确保:\n' + ' ✓ 控制台已开启「短信验证码登录」或「邮箱验证码登录」', signInWithOAuth: '当前使用「第三方 OAuth 登录」,需确保:\n' + ' ✓ 控制台已配置并开启对应的第三方身份源', } return hints[method] || '' } export class AuthError extends Error { /** * Error code associated with the error. Most errors coming from * HTTP responses will have a code, though some errors that occur * before a response is received will not have one present. In that * case {@link #status} will also be undefined. */ code: (string & {}) | undefined /** status code that caused the error. */ status: string | undefined /** Request ID associated with the error. */ requestId: string | undefined /** * 错误分类,用于区分不同类型的认证错误场景。 * 例如:PROVIDER_NOT_ENABLED(身份源未启用)、INVALID_CREDENTIALS(凭据错误)等。 */ category: AuthErrorCategory /** * 可操作性指引信息,包含具体的排查步骤和解决方案。 * 开发者可以直接使用此信息提示用户或用于调试。 */ helpMessage: string /** * 登录方式选择建议。当使用了不恰当的登录方式时,此字段会提供正确的选择指引。 */ loginMethodHint: string protected __isAuthError = true constructor(error, context?: { method?: string }) { const message = error.error_description || error.message super(message) this.name = 'AuthError' this.status = error.error this.code = error.error_code this.requestId = error.requestId this.category = inferErrorCategory(message, this.status) this.helpMessage = generateHelpMessage(this.category, context) this.loginMethodHint = generateLoginMethodHint(context?.method) } }