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 DypnsClient = require('@alicloud/dypnsapi20170525').default;
|
||||||
const { SendSmsVerifyCodeRequest, CheckSmsVerifyCodeRequest } = require('@alicloud/dypnsapi20170525/dist/models/model.js');
|
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() {
|
function createClient() {
|
||||||
const openApiConfig = new $OpenApiUtil.Config({
|
const openApiConfig = new $OpenApiUtil.Config({
|
||||||
accessKeyId: config.ALIYUN_ACCESS_KEY_ID,
|
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) {
|
switch (code) {
|
||||||
case 'MOBILE_NUMBER_ILLEGAL':
|
case 'MOBILE_NUMBER_ILLEGAL':
|
||||||
return new ValidationError('Invalid phone number');
|
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 'BUSINESS_LIMIT_CONTROL':
|
||||||
case 'FREQUENCY_FAIL':
|
case 'FREQUENCY_FAIL':
|
||||||
return new AppError('Too many SMS requests, please try again later', 429, 'RATE_LIMITED');
|
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({
|
const RUNTIME_OPTIONS = new RuntimeOptions({
|
||||||
readTimeout: 10000,
|
readTimeout: 10000,
|
||||||
connectTimeout: 5000,
|
connectTimeout: 5000,
|
||||||
@ -70,12 +109,15 @@ export async function sendCode(phoneNumber: string): Promise<void> {
|
|||||||
templateParam: config.ALIYUN_SMS_TEMPLATE_PARAM,
|
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') {
|
if (!response.body || response.body.code !== 'OK') {
|
||||||
throw mapAliyunError(
|
throw mapAliyunError(
|
||||||
response.body?.code ?? 'UNKNOWN',
|
response.body?.code ?? 'UNKNOWN',
|
||||||
response.body?.message ?? 'Unknown SMS error',
|
response.body?.message ?? 'Unknown SMS error',
|
||||||
|
'send',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,16 +131,19 @@ export async function verifyCode(phoneNumber: string, code: string): Promise<voi
|
|||||||
caseAuthPolicy: 1,
|
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') {
|
if (!response.body || response.body.code !== 'OK') {
|
||||||
throw mapAliyunError(
|
throw mapAliyunError(
|
||||||
response.body?.code ?? 'UNKNOWN',
|
response.body?.code ?? 'UNKNOWN',
|
||||||
response.body?.message ?? 'Unknown SMS verification error',
|
response.body?.message ?? 'Unknown SMS verification error',
|
||||||
|
'verify',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.body.model?.verifyResult !== 'PASS') {
|
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