import { Injectable, Inject, InternalServerErrorException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { Logger } from "nestjs-pino"; import { getErrorMessage } from "@bff/core/utils/error.util"; import type { FreebitConfig, FreebitAuthRequest, FreebitAuthResponse } from "../interfaces/freebit.types"; import { FreebitError } from "./freebit-error.service"; @Injectable() export class FreebitAuthService { private readonly config: FreebitConfig; private authKeyCache: { token: string; expiresAt: number } | null = null; constructor( private readonly configService: ConfigService, @Inject(Logger) private readonly logger: Logger ) { this.config = { baseUrl: this.configService.get("FREEBIT_BASE_URL") || "https://i1.mvno.net/emptool/api", oemId: this.configService.get("FREEBIT_OEM_ID") || "PASI", oemKey: this.configService.get("FREEBIT_OEM_KEY") || "", timeout: this.configService.get("FREEBIT_TIMEOUT") || 30000, retryAttempts: this.configService.get("FREEBIT_RETRY_ATTEMPTS") || 3, detailsEndpoint: this.configService.get("FREEBIT_DETAILS_ENDPOINT") || "/master/getAcnt/", }; if (!this.config.oemKey) { this.logger.warn("FREEBIT_OEM_KEY is not configured. SIM management features will not work."); } this.logger.debug("Freebit auth service initialized", { baseUrl: this.config.baseUrl, oemId: this.config.oemId, hasOemKey: !!this.config.oemKey, }); } /** * Get the current configuration */ getConfig(): FreebitConfig { return this.config; } /** * Get authentication key (cached or fetch new one) */ async getAuthKey(): Promise { if (this.authKeyCache && this.authKeyCache.expiresAt > Date.now()) { return this.authKeyCache.token; } try { if (!this.config.oemKey) { throw new Error("Freebit API not configured: FREEBIT_OEM_KEY is missing"); } const request: FreebitAuthRequest = { oemId: this.config.oemId, oemKey: this.config.oemKey, }; const response = await fetch(`${this.config.baseUrl}/authOem/`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: `json=${JSON.stringify(request)}`, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = (await response.json()) as FreebitAuthResponse; if (data.resultCode !== "100") { throw new FreebitError( `Authentication failed: ${data.status.message}`, data.resultCode, data.status.statusCode, data.status.message ); } this.authKeyCache = { token: data.authKey, expiresAt: Date.now() + 50 * 60 * 1000 }; this.logger.log("Successfully authenticated with Freebit API"); return data.authKey; } catch (error: unknown) { const message = getErrorMessage(error); this.logger.error("Failed to authenticate with Freebit API", { error: message }); throw new InternalServerErrorException("Failed to authenticate with Freebit API"); } } /** * Clear cached authentication key */ clearAuthCache(): void { this.authKeyCache = null; this.logger.debug("Cleared Freebit auth cache"); } /** * Check if we have a valid cached auth key */ hasValidAuthCache(): boolean { return !!(this.authKeyCache && this.authKeyCache.expiresAt > Date.now()); } }