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.js"; import type { FreebitConfig, FreebitAuthRequest, FreebitAuthResponse, } from "../interfaces/freebit.types.js"; import { FreebitError } from "./freebit-error.service.js"; @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, }; // Ensure proper URL construction - remove double slashes const baseUrl = this.config.baseUrl.replace(/\/$/, ""); const authUrl = `${baseUrl}/authOem/`; const response = await fetch(authUrl, { 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; const resultCode = data?.resultCode != null ? String(data.resultCode).trim() : undefined; const statusCode = data?.status?.statusCode != null ? String(data.status.statusCode).trim() : undefined; if (resultCode !== "100") { throw new FreebitError( `Authentication failed: ${data.status.message}`, resultCode, 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()); } }