forked from wangziqi/gongxue-base
fix: align SMS expiry with PNVS valid time
This commit is contained in:
@@ -29,6 +29,7 @@ export interface SmsSendResult {
|
|||||||
provider: SmsProviderName;
|
provider: SmsProviderName;
|
||||||
status: 'sent' | 'mocked';
|
status: 'sent' | 'mocked';
|
||||||
verification?: 'local' | 'provider';
|
verification?: 'local' | 'provider';
|
||||||
|
ttlSeconds?: number;
|
||||||
providerMessageId?: string;
|
providerMessageId?: string;
|
||||||
raw?: Record<string, unknown>;
|
raw?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
@@ -242,6 +243,11 @@ function optionalIntegerString(config: TenantAuthProviderConfig, keys: string[],
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function positiveInteger(value: string, fallback: number) {
|
||||||
|
const parsed = Number(value);
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
class AliyunPnvsSmsProvider implements SmsProvider {
|
class AliyunPnvsSmsProvider implements SmsProvider {
|
||||||
readonly name = 'aliyun-pnvs' as const;
|
readonly name = 'aliyun-pnvs' as const;
|
||||||
|
|
||||||
@@ -250,6 +256,7 @@ class AliyunPnvsSmsProvider implements SmsProvider {
|
|||||||
async send(input: SmsSendInput): Promise<SmsSendResult> {
|
async send(input: SmsSendInput): Promise<SmsSendResult> {
|
||||||
const signName = requirePublicString(this.providerConfig, ['signName'], 'SMS_PUBLIC_CONFIG_REQUIRED');
|
const signName = requirePublicString(this.providerConfig, ['signName'], 'SMS_PUBLIC_CONFIG_REQUIRED');
|
||||||
const templateCode = requirePublicString(this.providerConfig, ['templateCode'], 'SMS_PUBLIC_CONFIG_REQUIRED');
|
const templateCode = requirePublicString(this.providerConfig, ['templateCode'], 'SMS_PUBLIC_CONFIG_REQUIRED');
|
||||||
|
const validTime = optionalIntegerString(this.providerConfig, ['validTime'], String(input.ttlSeconds)) || String(input.ttlSeconds);
|
||||||
const params: Record<string, string> = {
|
const params: Record<string, string> = {
|
||||||
CountryCode: optionalPublicString(this.providerConfig, ['countryCode']) || '86',
|
CountryCode: optionalPublicString(this.providerConfig, ['countryCode']) || '86',
|
||||||
PhoneNumber: input.phone,
|
PhoneNumber: input.phone,
|
||||||
@@ -259,7 +266,7 @@ class AliyunPnvsSmsProvider implements SmsProvider {
|
|||||||
OutId: input.outId,
|
OutId: input.outId,
|
||||||
CodeType: optionalIntegerString(this.providerConfig, ['codeType'], '1') || '1',
|
CodeType: optionalIntegerString(this.providerConfig, ['codeType'], '1') || '1',
|
||||||
CodeLength: optionalIntegerString(this.providerConfig, ['codeLength'], '6') || '6',
|
CodeLength: optionalIntegerString(this.providerConfig, ['codeLength'], '6') || '6',
|
||||||
ValidTime: optionalIntegerString(this.providerConfig, ['validTime'], String(input.ttlSeconds)) || String(input.ttlSeconds),
|
ValidTime: validTime,
|
||||||
DuplicatePolicy: optionalIntegerString(this.providerConfig, ['duplicatePolicy'], '1') || '1',
|
DuplicatePolicy: optionalIntegerString(this.providerConfig, ['duplicatePolicy'], '1') || '1',
|
||||||
Interval: optionalIntegerString(this.providerConfig, ['interval'], String(input.cooldownSeconds)) || String(input.cooldownSeconds),
|
Interval: optionalIntegerString(this.providerConfig, ['interval'], String(input.cooldownSeconds)) || String(input.cooldownSeconds),
|
||||||
};
|
};
|
||||||
@@ -286,6 +293,7 @@ class AliyunPnvsSmsProvider implements SmsProvider {
|
|||||||
provider: this.name,
|
provider: this.name,
|
||||||
status: 'sent',
|
status: 'sent',
|
||||||
verification: 'provider',
|
verification: 'provider',
|
||||||
|
ttlSeconds: positiveInteger(validTime, input.ttlSeconds),
|
||||||
providerMessageId: typeof model.BizId === 'string' ? model.BizId : undefined,
|
providerMessageId: typeof model.BizId === 'string' ? model.BizId : undefined,
|
||||||
raw: {
|
raw: {
|
||||||
requestId: raw.RequestId,
|
requestId: raw.RequestId,
|
||||||
|
|||||||
@@ -307,8 +307,11 @@ export async function sendSmsCodeRoute(ctx: RequestContext) {
|
|||||||
cooldownSeconds: config.authSmsCooldownSeconds,
|
cooldownSeconds: config.authSmsCooldownSeconds,
|
||||||
metadata,
|
metadata,
|
||||||
});
|
});
|
||||||
|
const ttlSeconds = Number.isFinite(providerResult.ttlSeconds) && Number(providerResult.ttlSeconds) > 0
|
||||||
|
? Math.trunc(Number(providerResult.ttlSeconds))
|
||||||
|
: config.authCodeTtlSeconds;
|
||||||
const codeHash = hashSmsCode(tenantId, phone, purpose, code);
|
const codeHash = hashSmsCode(tenantId, phone, purpose, code);
|
||||||
const expiresAt = new Date(Date.now() + config.authCodeTtlSeconds * 1000).toISOString();
|
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
||||||
|
|
||||||
const item = await transaction(async client => {
|
const item = await transaction(async client => {
|
||||||
const insertResult = await client.query(
|
const insertResult = await client.query(
|
||||||
@@ -354,7 +357,7 @@ export async function sendSmsCodeRoute(ctx: RequestContext) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
expireIn: config.authCodeTtlSeconds,
|
expireIn: ttlSeconds,
|
||||||
cooldown: config.authSmsCooldownSeconds,
|
cooldown: config.authSmsCooldownSeconds,
|
||||||
debugCode: provider.name === 'mock' && !config.isProduction ? code : undefined,
|
debugCode: provider.name === 'mock' && !config.isProduction ? code : undefined,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ assert.match(providerSource, /SendSmsVerifyCode/, 'PNVS provider should call Sen
|
|||||||
assert.match(providerSource, /CheckSmsVerifyCode/, 'PNVS provider should call CheckSmsVerifyCode');
|
assert.match(providerSource, /CheckSmsVerifyCode/, 'PNVS provider should call CheckSmsVerifyCode');
|
||||||
assert.match(providerSource, /VerifyResult.*PASS/s, 'PNVS verify should require provider PASS result');
|
assert.match(providerSource, /VerifyResult.*PASS/s, 'PNVS verify should require provider PASS result');
|
||||||
assert.match(providerSource, /##code##/, 'PNVS provider should preserve Aliyun-generated code placeholder');
|
assert.match(providerSource, /##code##/, 'PNVS provider should preserve Aliyun-generated code placeholder');
|
||||||
|
assert.match(providerSource, /ttlSeconds:\s*positiveInteger\(validTime,\s*input\.ttlSeconds\)/, 'PNVS provider should expose provider ValidTime as send result ttlSeconds');
|
||||||
assert.match(routeSource, /aliyun-pnvs/, 'auth routes should recognize aliyun-pnvs aliases');
|
assert.match(routeSource, /aliyun-pnvs/, 'auth routes should recognize aliyun-pnvs aliases');
|
||||||
|
assert.match(routeSource, /const ttlSeconds =[\s\S]*providerResult\.ttlSeconds[\s\S]*expireIn: ttlSeconds/, 'auth routes should persist and return provider-specific SMS TTL');
|
||||||
assert.match(routeSource, /replace\(\s*\/\[_\\s\]\/g,\s*'-'\s*\)/, 'auth routes should normalize underscore PNVS provider aliases before runtime selection');
|
assert.match(routeSource, /replace\(\s*\/\[_\\s\]\/g,\s*'-'\s*\)/, 'auth routes should normalize underscore PNVS provider aliases before runtime selection');
|
||||||
assert.match(routeSource, /verification.*provider/s, 'auth routes should store provider verification mode');
|
assert.match(routeSource, /verification.*provider/s, 'auth routes should store provider verification mode');
|
||||||
assert.match(routeSource, /provider\.verify/, 'auth routes should delegate PNVS verification to provider');
|
assert.match(routeSource, /provider\.verify/, 'auth routes should delegate PNVS verification to provider');
|
||||||
|
|||||||
Reference in New Issue
Block a user