113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
|
|
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<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,
|
||
|
|
};
|
||
|
|
|
||
|
|
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());
|
||
|
|
}
|
||
|
|
}
|