fix: handle Aliyun SMS validation errors
This commit is contained in:
parent
48cbc200d1
commit
0317c34099
23
src/__tests__/services/auth/aliyun-sms.test.ts
Normal file
23
src/__tests__/services/auth/aliyun-sms.test.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mapAliyunException } from '../../../services/auth/aliyun-sms.js';
|
||||
|
||||
describe('aliyun-sms', () => {
|
||||
it('maps Aliyun validate failure during verification to invalid code error', () => {
|
||||
const error = {
|
||||
name: 'ClientError',
|
||||
code: 'isv.ValidateFail',
|
||||
message: 'isv.ValidateFail: code: 400, 验证失败 request id: request-id',
|
||||
data: {
|
||||
Code: 'isv.ValidateFail',
|
||||
Message: '验证失败',
|
||||
},
|
||||
statusCode: 400,
|
||||
};
|
||||
|
||||
const mapped = mapAliyunException(error, 'verify');
|
||||
|
||||
expect(mapped.statusCode).toBe(401);
|
||||
expect(mapped.code).toBe('INVALID_VERIFY_CODE');
|
||||
expect(mapped.message).toBe('验证码无效或已过期');
|
||||
});
|
||||
});
|
||||
@ -11,6 +11,17 @@ const require = createRequire(import.meta.url);
|
||||
const DypnsClient = require('@alicloud/dypnsapi20170525').default;
|
||||
const { SendSmsVerifyCodeRequest, CheckSmsVerifyCodeRequest } = require('@alicloud/dypnsapi20170525/dist/models/model.js');
|
||||
|
||||
type SmsOperation = 'send' | 'verify';
|
||||
|
||||
type AliyunSdkError = {
|
||||
code?: unknown;
|
||||
message?: unknown;
|
||||
data?: {
|
||||
Code?: unknown;
|
||||
Message?: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
function createClient() {
|
||||
const openApiConfig = new $OpenApiUtil.Config({
|
||||
accessKeyId: config.ALIYUN_ACCESS_KEY_ID,
|
||||
@ -31,10 +42,22 @@ function assertSmsConfigured(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function mapAliyunError(code: string, message: string): AppError {
|
||||
function getString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function invalidVerifyCodeError(): AppError {
|
||||
return new AppError('验证码无效或已过期', 401, 'INVALID_VERIFY_CODE');
|
||||
}
|
||||
|
||||
function mapAliyunError(code: string, message: string, operation: SmsOperation): AppError {
|
||||
switch (code) {
|
||||
case 'MOBILE_NUMBER_ILLEGAL':
|
||||
return new ValidationError('Invalid phone number');
|
||||
case 'isv.ValidateFail':
|
||||
return operation === 'verify'
|
||||
? invalidVerifyCodeError()
|
||||
: new AppError('SMS service error: verification failed', 502, 'SMS_SERVICE_ERROR');
|
||||
case 'BUSINESS_LIMIT_CONTROL':
|
||||
case 'FREQUENCY_FAIL':
|
||||
return new AppError('Too many SMS requests, please try again later', 429, 'RATE_LIMITED');
|
||||
@ -55,6 +78,22 @@ function mapAliyunError(code: string, message: string): AppError {
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAliyunException(error: unknown, operation: SmsOperation): AppError {
|
||||
if (error instanceof AppError) return error;
|
||||
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
const sdkError = error as AliyunSdkError;
|
||||
const code = getString(sdkError.data?.Code) ?? getString(sdkError.code);
|
||||
const message = getString(sdkError.data?.Message) ?? getString(sdkError.message);
|
||||
|
||||
if (code) {
|
||||
return mapAliyunError(code, message ?? code, operation);
|
||||
}
|
||||
}
|
||||
|
||||
return new AppError('SMS service error: request failed', 502, 'SMS_SERVICE_ERROR');
|
||||
}
|
||||
|
||||
const RUNTIME_OPTIONS = new RuntimeOptions({
|
||||
readTimeout: 10000,
|
||||
connectTimeout: 5000,
|
||||
@ -70,12 +109,15 @@ export async function sendCode(phoneNumber: string): Promise<void> {
|
||||
templateParam: config.ALIYUN_SMS_TEMPLATE_PARAM,
|
||||
});
|
||||
|
||||
const response = await createClient().sendSmsVerifyCodeWithOptions(request, RUNTIME_OPTIONS);
|
||||
const response = await createClient().sendSmsVerifyCodeWithOptions(request, RUNTIME_OPTIONS).catch((error: unknown) => {
|
||||
throw mapAliyunException(error, 'send');
|
||||
});
|
||||
|
||||
if (!response.body || response.body.code !== 'OK') {
|
||||
throw mapAliyunError(
|
||||
response.body?.code ?? 'UNKNOWN',
|
||||
response.body?.message ?? 'Unknown SMS error',
|
||||
'send',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -89,16 +131,19 @@ export async function verifyCode(phoneNumber: string, code: string): Promise<voi
|
||||
caseAuthPolicy: 1,
|
||||
});
|
||||
|
||||
const response = await createClient().checkSmsVerifyCodeWithOptions(request, RUNTIME_OPTIONS);
|
||||
const response = await createClient().checkSmsVerifyCodeWithOptions(request, RUNTIME_OPTIONS).catch((error: unknown) => {
|
||||
throw mapAliyunException(error, 'verify');
|
||||
});
|
||||
|
||||
if (!response.body || response.body.code !== 'OK') {
|
||||
throw mapAliyunError(
|
||||
response.body?.code ?? 'UNKNOWN',
|
||||
response.body?.message ?? 'Unknown SMS verification error',
|
||||
'verify',
|
||||
);
|
||||
}
|
||||
|
||||
if (response.body.model?.verifyResult !== 'PASS') {
|
||||
throw new AppError('Invalid or expired verification code', 401, 'INVALID_VERIFY_CODE');
|
||||
throw invalidVerifyCodeError();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user