121 lines
3.9 KiB
TypeScript
Raw Normal View History

import { Injectable, Inject, InternalServerErrorException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } 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<string>("FREEBIT_BASE_URL") || "https://i1.mvno.net/emptool/api",
oemId: this.configService.get<string>("FREEBIT_OEM_ID") || "PASI",
oemKey: this.configService.get<string>("FREEBIT_OEM_KEY") || "",
timeout: this.configService.get<number>("FREEBIT_TIMEOUT") || 30000,
retryAttempts: this.configService.get<number>("FREEBIT_RETRY_ATTEMPTS") || 3,
detailsEndpoint:
this.configService.get<string>("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<string> {
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 ? undefined : String(data.resultCode).trim();
const statusCode =
data?.status?.statusCode == null ? undefined : String(data.status.statusCode).trim();
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 = extractErrorMessage(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());
}
}