forked from Shared/esg
feat: add car inquiries with resilience, ownership privacy, and retry visibility
Introduce plate, chassis, and third-party car endpoints, a wall-clock inquiry deadline with transport-only retries, and ownership-safe no-match errors so another person's identity never leaks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,12 @@ import {
|
||||
AppErrorCode,
|
||||
buildNormalizedError,
|
||||
} from '../../common/constants/error-messages';
|
||||
import {
|
||||
AUTH_MAX_ATTEMPTS,
|
||||
AUTH_RETRY_DELAY_MS,
|
||||
isAuthTransportRetryable,
|
||||
} from '../../common/helpers/provider-resilience.helper';
|
||||
import { withRetry } from '../../common/helpers/retry.helper';
|
||||
import { AmitisAuthServiceConfig } from '../../config/configuration';
|
||||
import { GeneralTokenDocument } from '../schemas/general-token.schema';
|
||||
import { GeneralTokenService } from '../services/general-token.service';
|
||||
@@ -124,14 +130,16 @@ export class AmitisProvider {
|
||||
`AMITIS login → POST ${loginUrl} | provider=${providerName} | inquiry=${inquiryType} | username=${username.toLowerCase()}`,
|
||||
);
|
||||
|
||||
const response = await this.httpClient.post<CentInsurTokenResponse>(
|
||||
this.config.loginPath,
|
||||
loginBody.toString(),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
const response = await this.withGentleAuthRetry(() =>
|
||||
this.httpClient.post<CentInsurTokenResponse>(
|
||||
this.config.loginPath,
|
||||
loginBody.toString(),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (isOutboundHttpDebugEnabled()) {
|
||||
@@ -142,7 +150,7 @@ export class AmitisProvider {
|
||||
|
||||
const token = this.extractToken(response.data);
|
||||
|
||||
await this.saveToken(token, providerName, inquiryType, username, password, 'login');
|
||||
await this.saveToken(token, providerName, inquiryType, username, 'login');
|
||||
return token.accessToken;
|
||||
} catch (error) {
|
||||
throw this.toAuthError(`AMITIS login failed for ${providerName}/${inquiryType}`, error);
|
||||
@@ -155,10 +163,12 @@ export class AmitisProvider {
|
||||
inquiryType: InquiryType,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = await this.httpClient.post<CentInsurTokenResponse>(this.config.refreshPath, {
|
||||
Token: latestToken.accessToken,
|
||||
RefreshToken: latestToken.refreshToken,
|
||||
});
|
||||
const response = await this.withGentleAuthRetry(() =>
|
||||
this.httpClient.post<CentInsurTokenResponse>(this.config.refreshPath, {
|
||||
Token: latestToken.accessToken,
|
||||
RefreshToken: latestToken.refreshToken,
|
||||
}),
|
||||
);
|
||||
const token = this.extractToken(response.data, latestToken.refreshToken);
|
||||
|
||||
await this.saveToken(
|
||||
@@ -166,7 +176,6 @@ export class AmitisProvider {
|
||||
providerName,
|
||||
inquiryType,
|
||||
latestToken.username,
|
||||
latestToken.clientSecret,
|
||||
'refresh',
|
||||
);
|
||||
return token.accessToken;
|
||||
@@ -178,20 +187,27 @@ export class AmitisProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/** One gentle retry on timeout/network only — never on 401/403/429/auth rejects. */
|
||||
private async withGentleAuthRetry<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return withRetry(fn, {
|
||||
maxAttempts: AUTH_MAX_ATTEMPTS,
|
||||
delayMs: AUTH_RETRY_DELAY_MS,
|
||||
shouldRetry: isAuthTransportRetryable,
|
||||
});
|
||||
}
|
||||
|
||||
private async saveToken(
|
||||
token: AmitisAuthToken,
|
||||
providerName: ProviderName,
|
||||
inquiryType: InquiryType,
|
||||
username: string,
|
||||
password: string,
|
||||
scope: string,
|
||||
): Promise<void> {
|
||||
await this.generalTokenService.create({
|
||||
await this.generalTokenService.persistToken({
|
||||
serviceProvider: this.getTokenKey(providerName, inquiryType),
|
||||
tokenType: token.tokenType,
|
||||
url: this.config.baseUrl,
|
||||
clientId: username.toLowerCase(),
|
||||
clientSecret: password,
|
||||
username: username.toLowerCase(),
|
||||
scope,
|
||||
accessToken: token.accessToken,
|
||||
|
||||
Reference in New Issue
Block a user