tema 675f7d5cfd Remove cached profile fields migration and update CSRF middleware for new public auth endpoints
- Deleted migration file that removed cached profile fields from the users table, centralizing profile data retrieval from WHMCS.
- Updated CsrfMiddleware to include new public authentication endpoints for password reset, setting password, and WHMCS account linking.
- Enhanced error handling in password and WHMCS linking workflows to provide clearer feedback on missing mappings and improve user experience.
- Adjusted user creation and update methods in UsersFacade to handle cases where WHMCS mappings are not yet available, ensuring smoother account setup.
2025-11-21 17:12:34 +09:00

120 lines
3.7 KiB
TypeScript

import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { ConfigService } from "@nestjs/config";
import { EmailService } from "@bff/infra/email/email.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import type { SimNotificationContext } from "../interfaces/sim-base.interface";
@Injectable()
export class SimNotificationService {
constructor(
@Inject(Logger) private readonly logger: Logger,
private readonly email: EmailService,
private readonly configService: ConfigService
) {}
/**
* Send notification for SIM actions
*/
async notifySimAction(
action: string,
status: "SUCCESS" | "ERROR",
context: SimNotificationContext
): Promise<void> {
const subject = `[SIM ACTION] ${action} - ${status}`;
const toAddress = this.configService.get<string>("SIM_ALERT_EMAIL_TO");
const fromAddress = this.configService.get<string>("SIM_ALERT_EMAIL_FROM");
if (!toAddress || !fromAddress) {
this.logger.debug("SIM action notification skipped: email config missing", {
action,
status,
});
return;
}
const publicContext = this.redactSensitiveFields(context);
try {
const lines: string[] = [
`Action: ${action}`,
`Result: ${status}`,
`Timestamp: ${new Date().toISOString()}`,
"",
"Context:",
JSON.stringify(publicContext, null, 2),
];
await this.email.sendEmail({
to: toAddress,
from: fromAddress,
subject,
text: lines.join("\n"),
});
} catch (err) {
this.logger.warn("Failed to send SIM action notification email", {
action,
status,
error: getErrorMessage(err),
});
}
}
/**
* Redact sensitive information from notification context
*/
private redactSensitiveFields(context: Record<string, unknown>): Record<string, unknown> {
const sanitized: Record<string, unknown> = {};
for (const [key, value] of Object.entries(context)) {
if (typeof key === "string" && key.toLowerCase().includes("password")) {
sanitized[key] = "[REDACTED]";
continue;
}
if (typeof value === "string" && value.length > 200) {
sanitized[key] = `${value.substring(0, 200)}`;
continue;
}
sanitized[key] = value;
}
return sanitized;
}
/**
* Convert technical errors to user-friendly messages for SIM operations
*/
getUserFriendlySimError(technicalError: string): string {
if (!technicalError) {
return "SIM operation failed. Please try again or contact support.";
}
const errorLower = technicalError.toLowerCase();
// Freebit API errors
if (errorLower.includes("api error: ng") || errorLower.includes("account not found")) {
return "SIM account not found. Please contact support to verify your SIM configuration.";
}
if (errorLower.includes("authentication failed") || errorLower.includes("auth")) {
return "SIM service is temporarily unavailable. Please try again later.";
}
if (errorLower.includes("timeout") || errorLower.includes("network")) {
return "SIM service request timed out. Please try again.";
}
// WHMCS errors
if (errorLower.includes("invalid permissions") || errorLower.includes("not allowed")) {
return "SIM service is temporarily unavailable. Please contact support for assistance.";
}
// Generic errors
if (errorLower.includes("failed") || errorLower.includes("error")) {
return "SIM operation failed. Please try again or contact support.";
}
// Default fallback
return "SIM operation failed. Please try again or contact support.";
}
}