mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-03 12:03:40 +00:00
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
import { HttpService } from "@nestjs/axios";
|
|
import { Injectable, Logger } from "@nestjs/common";
|
|
import { ConfigService } from "@nestjs/config";
|
|
import { firstValueFrom } from "rxjs";
|
|
import { EimsConfig } from "../../config/eims.config";
|
|
import { EimsAuthService } from "./eims-auth.service";
|
|
import { EimsSignerService, toSignedBody } from "./eims-signer.service";
|
|
import { toEimsApiException } from "./eims.errors";
|
|
|
|
/**
|
|
* Foundation for EIMS's authenticated endpoints (`/v1/register`, `/v1/verify`, …).
|
|
*
|
|
* Login is not routed through here: `/auth/login` carries no bearer token and lives in
|
|
* `EimsAuthService`.
|
|
*/
|
|
@Injectable()
|
|
export class EimsClientService {
|
|
private readonly logger = new Logger(EimsClientService.name);
|
|
|
|
constructor(
|
|
private readonly http: HttpService,
|
|
private readonly config: ConfigService,
|
|
private readonly auth: EimsAuthService,
|
|
private readonly signer: EimsSignerService,
|
|
) {}
|
|
|
|
private get cfg(): EimsConfig {
|
|
return this.config.get<EimsConfig>("eims")!;
|
|
}
|
|
|
|
/**
|
|
* Sign `request`, POST it to `path` with the shared protected-endpoint headers, and return the
|
|
* parsed response.
|
|
* A 401 invalidates the cached token and retries exactly once.
|
|
*/
|
|
async postSigned<TRequest, TResponse>(path: string, request: TRequest): Promise<TResponse> {
|
|
return this.send<TRequest, TResponse>(path, request, false, true);
|
|
}
|
|
|
|
/**
|
|
* POST `request` verbatim with the shared protected-endpoint headers, but **not** wrapped in a
|
|
* signed envelope. This is the wire contract for verify, cancel and receipt calls.
|
|
*/
|
|
async postBearer<TRequest, TResponse>(path: string, request: TRequest): Promise<TResponse> {
|
|
return this.send<TRequest, TResponse>(path, request, false, false);
|
|
}
|
|
|
|
private async send<TRequest, TResponse>(
|
|
path: string,
|
|
request: TRequest,
|
|
isRetry: boolean,
|
|
signed: boolean,
|
|
): Promise<TResponse> {
|
|
const cfg = this.cfg;
|
|
const token = await this.auth.getValidAccessToken();
|
|
const body = signed ? toSignedBody(this.signer.signRequest(request)) : request;
|
|
|
|
try {
|
|
const res = await firstValueFrom(
|
|
this.http.post<TResponse>(`${cfg.baseUrl}${path}`, body, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
apikey: cfg.apiKey,
|
|
},
|
|
timeout: cfg.httpTimeoutMs,
|
|
}),
|
|
);
|
|
return res.data;
|
|
} catch (err) {
|
|
const mapped = toEimsApiException(err, `POST ${path}`);
|
|
if (mapped.kind === "AUTH" && !isRetry) {
|
|
this.logger.warn(`EIMS rejected the token on ${path}; re-authenticating once`);
|
|
this.auth.invalidate();
|
|
return this.send<TRequest, TResponse>(path, request, true, signed);
|
|
}
|
|
this.logger.error(mapped.message);
|
|
throw mapped;
|
|
}
|
|
}
|
|
}
|