"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.login = login; exports.setupTotp = setupTotp; exports.verifyTotp = verifyTotp; exports.regenerateRecoveryCodes = regenerateRecoveryCodes; exports.forgotPassword = forgotPassword; exports.resetPassword = resetPassword; exports.listCompanies = listCompanies; exports.getCompany = getCompany; exports.updateCompany = updateCompany; exports.setCompanyStatus = setCompanyStatus; exports.deleteCompany = deleteCompany; exports.impersonateCompany = impersonateCompany; exports.listRenters = listRenters; exports.setRenterActive = setRenterActive; exports.getPlatformMetrics = getPlatformMetrics; exports.listNotifications = listNotifications; exports.getAuditLogs = getAuditLogs; exports.listAdmins = listAdmins; exports.createAdmin = createAdmin; exports.updateAdmin = updateAdmin; exports.updateAdminRole = updateAdminRole; exports.updateAdminPermissions = updateAdminPermissions; exports.getBilling = getBilling; exports.getCompanyInvoices = getCompanyInvoices; exports.getInvoicePdf = getInvoicePdf; exports.getBillingAccountDetail = getBillingAccountDetail; exports.updateBillingAccount = updateBillingAccount; exports.setDunningPaused = setDunningPaused; exports.createBillingInvoice = createBillingInvoice; exports.finalizeBillingInvoice = finalizeBillingInvoice; exports.payBillingInvoice = payBillingInvoice; exports.retryBillingInvoicePayment = retryBillingInvoicePayment; exports.voidBillingInvoice = voidBillingInvoice; exports.markBillingInvoiceUncollectible = markBillingInvoiceUncollectible; exports.issueBillingCreditNote = issueBillingCreditNote; exports.issueBillingRefund = issueBillingRefund; exports.getMarketplaceHomepage = getMarketplaceHomepage; exports.updateMarketplaceHomepage = updateMarketplaceHomepage; exports.getPricingConfigs = getPricingConfigs; exports.updatePricingConfigs = updatePricingConfigs; exports.listPlanFeatures = listPlanFeatures; exports.createPlanFeature = createPlanFeature; exports.updatePlanFeature = updatePlanFeature; exports.deletePlanFeature = deletePlanFeature; exports.listPromotions = listPromotions; exports.createPromotion = createPromotion; exports.updatePromotion = updatePromotion; exports.deletePromotion = deletePromotion; const bcryptjs_1 = __importDefault(require("bcryptjs")); const crypto_1 = __importDefault(require("crypto")); const otplib_1 = require("otplib"); const tokens_1 = require("../../security/tokens"); const qrcode_1 = __importDefault(require("qrcode")); const platformContentService_1 = require("../../services/platformContentService"); const notificationService_1 = require("../../services/notificationService"); const presenter = __importStar(require("./admin.presenter")); const repo = __importStar(require("./admin.repo")); const billingService = __importStar(require("./admin.billing.service")); const ADMIN_RESET_TTL_MINUTES = 60; const ADMIN_RECOVERY_CODE_COUNT = 10; function generateRecoveryCode() { const raw = crypto_1.default.randomBytes(9).toString('base64url').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 12); return `${raw.slice(0, 4)}-${raw.slice(4, 8)}-${raw.slice(8, 12)}`; } async function issueAdminRecoveryCodes(adminId) { const codes = Array.from({ length: ADMIN_RECOVERY_CODE_COUNT }, generateRecoveryCode); const hashes = await Promise.all(codes.map((code) => bcryptjs_1.default.hash(code, 12))); await repo.replaceAdminRecoveryCodes(adminId, hashes); await repo.createAuditLog({ adminUserId: adminId, action: 'ADMIN_2FA_RECOVERY_CODES_ISSUED', resource: 'AdminUser', resourceId: adminId, }); return codes; } async function consumeAdminRecoveryCode(adminId, code) { const normalized = code.trim().toUpperCase(); if (!normalized) return false; const codes = await repo.listUnusedAdminRecoveryCodes(adminId); for (const candidate of codes) { if (await bcryptjs_1.default.compare(normalized, candidate.codeHash)) { await repo.markAdminRecoveryCodeUsed(candidate.id); await repo.createAuditLog({ adminUserId: adminId, action: 'ADMIN_2FA_RECOVERY_CODE_USED', resource: 'AdminUser', resourceId: adminId, }); return true; } } return false; } function signAdminToken(adminId, last2faAt) { return (0, tokens_1.signActorToken)(adminId, 'admin', { expiresIn: '8h', last2faAt }); } function toAuditJson(value) { return JSON.parse(JSON.stringify(value)); } function ensureAdminBasePath(baseUrl) { try { const url = new URL(baseUrl); const pathname = url.pathname.replace(/\/$/, ''); if (!pathname.endsWith('/admin')) url.pathname = `${pathname}/admin`; return url.toString().replace(/\/$/, ''); } catch { const trimmed = baseUrl.replace(/\/$/, ''); return trimmed.endsWith('/admin') ? trimmed : `${trimmed}/admin`; } } async function login(email, password, totpCode, recoveryCode) { const admin = await repo.findAdminByEmail(email); if (!admin || !admin.isActive) return null; const valid = await bcryptjs_1.default.compare(password, admin.passwordHash); if (!valid) return null; if (admin.totpEnabled) { if (!totpCode && !recoveryCode) return { totpRequired: true }; const validTotp = totpCode ? otplib_1.authenticator.verify({ token: totpCode, secret: admin.totpSecret }) : false; const validRecoveryCode = !validTotp && recoveryCode ? await consumeAdminRecoveryCode(admin.id, recoveryCode) : false; if (!validTotp && !validRecoveryCode) { return { invalidTotp: true }; } } await repo.updateAdminLastLogin(admin.id); await repo.createAuditLog({ adminUserId: admin.id, action: 'ADMIN_LOGIN', resource: 'AdminUser', resourceId: admin.id, }); return presenter.presentAdminSession(admin, signAdminToken(admin.id, admin.totpEnabled ? Date.now() : undefined)); } async function setupTotp(adminId, email) { const secret = otplib_1.authenticator.generateSecret(); await repo.updateAdminTotpSecret(adminId, secret); const otpauth = otplib_1.authenticator.keyuri(email, 'RentalDriveGo Admin', secret); const qrCode = await qrcode_1.default.toDataURL(otpauth); return { secret, qrCode }; } async function verifyTotp(adminId, code) { const admin = await repo.findAdminByIdOrThrow(adminId); if (!admin.totpSecret) return false; const valid = otplib_1.authenticator.verify({ token: code, secret: admin.totpSecret }); if (!valid) return false; await repo.enableAdminTotp(adminId); await repo.createAuditLog({ adminUserId: adminId, action: 'ADMIN_2FA_VERIFIED', resource: 'AdminUser', resourceId: adminId, }); const recoveryCodes = await issueAdminRecoveryCodes(adminId); return { ...presenter.presentAdminSession({ ...admin, totpEnabled: true }, signAdminToken(adminId, Date.now())), recoveryCodes, }; } async function regenerateRecoveryCodes(adminId) { return { recoveryCodes: await issueAdminRecoveryCodes(adminId) }; } async function forgotPassword(email) { const admin = await repo.findAdminByEmail(email); if (!admin || !admin.isActive) return; const rawToken = crypto_1.default.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + ADMIN_RESET_TTL_MINUTES * 60 * 1000); await repo.setAdminPasswordReset(admin.id, rawToken, expiresAt); const adminUrl = ensureAdminBasePath(process.env.ADMIN_URL ?? process.env.NEXT_PUBLIC_ADMIN_URL ?? 'http://localhost:3000/admin'); const resetUrl = `${adminUrl}/reset-password?token=${rawToken}`; await (0, notificationService_1.sendTransactionalEmail)({ to: admin.email, subject: 'Reset your RentalDriveGo admin password', html: `

Hi ${admin.firstName},

Reset password

Expires in ${ADMIN_RESET_TTL_MINUTES} minutes.

`, text: `Hi ${admin.firstName},\n\nReset here: ${resetUrl}\n\nExpires in ${ADMIN_RESET_TTL_MINUTES} minutes.`, }).catch((err) => console.error('[AdminForgotPassword]', err?.message)); } async function resetPassword(token, password) { const admin = await repo.findAdminByResetToken(token); if (!admin) return false; await repo.updateAdminPassword(admin.id, await bcryptjs_1.default.hash(password, 12)); return true; } async function listCompanies(query) { const { data, total } = await repo.listCompaniesPage(query); return presenter.presentPaginated(data, total, query.page, query.pageSize); } function getCompany(id) { return repo.getCompanyDetail(id); } async function updateCompany(id, body, adminId, ip) { const before = await repo.getCompanyUpdateSnapshot(id); const updated = await repo.applyCompanyUpdate(id, body, before); await repo.createAuditLog({ adminUserId: adminId, action: 'UPDATE_COMPANY', resource: 'Company', resourceId: id, companyId: id, before: toAuditJson(before), after: toAuditJson(body), ipAddress: ip, }); return updated; } async function setCompanyStatus(id, status, reason, adminId, ip) { const before = await repo.getCompanyUpdateSnapshot(id); const updated = await repo.updateCompanyStatus(id, status); await repo.createAuditLog({ adminUserId: adminId, action: `SET_COMPANY_STATUS_${status}`, resource: 'Company', resourceId: id, companyId: id, before: { status: before.status }, after: { status }, note: reason, ipAddress: ip, }); return updated; } async function deleteCompany(id, adminId, ip) { await repo.deleteCompany(id); await repo.createAuditLog({ adminUserId: adminId, action: 'DELETE_COMPANY', resource: 'Company', resourceId: id, ipAddress: ip, }); } async function impersonateCompany(id, adminId, ip, reason, durationMinutes = 15) { const company = await repo.getCompanyForImpersonation(id); const ttlMinutes = Math.min(Math.max(durationMinutes, 1), 30); const employeeId = company.employees[0]?.id; if (!employeeId) throw new Error('Company has no employee account to impersonate'); const token = (0, tokens_1.signActorToken)(employeeId, 'employee', { expiresIn: `${ttlMinutes}m` }); await repo.createAuditLog({ adminUserId: adminId, action: 'IMPERSONATE_COMPANY', resource: 'Company', resourceId: id, companyId: id, note: reason, before: { originalAdminId: adminId }, after: { targetCompanyId: id, durationMinutes: ttlMinutes }, ipAddress: ip, }); return { token, expiresIn: ttlMinutes * 60, impersonation: { companyId: id, reason, durationMinutes: ttlMinutes } }; } async function listRenters(query) { const { data, total } = await repo.listRentersPage(query); return presenter.presentPaginated(data, total, query.page, query.pageSize); } function setRenterActive(id, isActive) { return repo.updateRenterActive(id, isActive); } function getPlatformMetrics() { return repo.getPlatformMetricCounts(); } async function listNotifications(query) { const { data, total } = await repo.listNotificationsPage(query); return presenter.presentPaginated(data, total, query.page, query.pageSize); } async function getAuditLogs(query) { const { data, total } = await repo.listAuditLogsPage(query); return presenter.presentPaginated(data, total, query.page, query.pageSize); } async function listAdmins() { const admins = await repo.listAdmins(); return admins.map((admin) => presenter.presentAdminUser(admin)); } async function createAdmin(body) { const admin = await repo.createAdmin({ ...body, passwordHash: await bcryptjs_1.default.hash(body.password, 12), }); return presenter.presentAdminUser(admin); } async function updateAdmin(id, body) { const admin = await repo.updateAdmin(id, { ...(body.email !== undefined ? { email: body.email } : {}), ...(body.firstName !== undefined ? { firstName: body.firstName } : {}), ...(body.lastName !== undefined ? { lastName: body.lastName } : {}), ...(body.role !== undefined ? { role: body.role } : {}), ...(body.isActive !== undefined ? { isActive: body.isActive } : {}), ...(body.password ? { passwordHash: await bcryptjs_1.default.hash(body.password, 12) } : {}), }); return presenter.presentAdminUser(admin); } function updateAdminRole(id, role) { return repo.updateAdminRole(id, role); } async function updateAdminPermissions(id, permissions) { const admin = await repo.replaceAdminPermissions(id, permissions); return presenter.presentAdminUser(admin); } async function getBilling(query) { const { data, total, stats } = await billingService.listBillingAccounts(query); return presenter.presentPaginated(data, total, query.page, query.pageSize, { stats }); } async function getCompanyInvoices(companyId, query) { const detail = await billingService.getBillingAccountDetail(companyId); const start = (query.page - 1) * query.pageSize; const end = start + query.pageSize; const data = detail.invoices.slice(start, end); const total = detail.invoices.length; return presenter.presentPaginated(data, total, query.page, query.pageSize); } async function getInvoicePdf(invoiceId) { return billingService.getInvoicePdf(invoiceId); } function getBillingAccountDetail(companyId) { return billingService.getBillingAccountDetail(companyId); } function updateBillingAccount(billingAccountId, data, adminId, ip) { return billingService.updateBillingAccount(billingAccountId, data, adminId, ip); } function setDunningPaused(billingAccountId, paused, adminId, ip) { return billingService.setDunningPaused(billingAccountId, paused, adminId, ip); } function createBillingInvoice(billingAccountId, data, adminId, ip) { return billingService.createDraftInvoice(billingAccountId, data, adminId, ip); } function finalizeBillingInvoice(invoiceId, adminId, ip) { return billingService.finalizeInvoice(invoiceId, adminId, ip); } function payBillingInvoice(invoiceId, data, adminId, ip) { return billingService.payInvoice(invoiceId, data, adminId, ip); } function retryBillingInvoicePayment(invoiceId, data, adminId, ip) { return billingService.retryInvoicePayment(invoiceId, data, adminId, ip); } function voidBillingInvoice(invoiceId, reason, adminId, ip) { return billingService.voidInvoice(invoiceId, reason, adminId, ip); } function markBillingInvoiceUncollectible(invoiceId, reason, adminId, ip) { return billingService.markInvoiceUncollectible(invoiceId, reason, adminId, ip); } function issueBillingCreditNote(invoiceId, data, adminId, ip) { return billingService.issueCreditNote(invoiceId, data, adminId, ip); } function issueBillingRefund(invoiceId, data, adminId, ip) { return billingService.issueRefund(invoiceId, data, adminId, ip); } function getMarketplaceHomepage() { return (0, platformContentService_1.getMarketplaceHomepageContent)(); } async function updateMarketplaceHomepage(homepage, adminId, ip) { const saved = await (0, platformContentService_1.saveMarketplaceHomepageContent)(homepage); await repo.createAuditLog({ adminUserId: adminId, action: 'UPDATE', resource: 'MarketplaceHomepage', after: toAuditJson(saved), ipAddress: ip, userAgent: undefined, }); return saved; } function getPricingConfigs() { return repo.listPricingConfigs(); } async function updatePricingConfigs(entries, adminId, ip) { const results = await Promise.all(entries.map((e) => repo.upsertPricingConfig(e.plan, e.billingPeriod, e.amount, adminId))); await repo.createAuditLog({ adminUserId: adminId, action: 'UPDATE', resource: 'PricingConfig', after: toAuditJson(results), ipAddress: ip, userAgent: undefined, }); return results; } function listPlanFeatures() { return repo.listPlanFeatures(); } async function createPlanFeature(data, adminId, ip) { const feature = await repo.createPlanFeature(data); await repo.createAuditLog({ adminUserId: adminId, action: 'CREATE', resource: 'PlanFeature', after: toAuditJson(feature), ipAddress: ip, userAgent: undefined, }); return feature; } async function updatePlanFeature(id, data, adminId, ip) { const feature = await repo.updatePlanFeature(id, data); await repo.createAuditLog({ adminUserId: adminId, action: 'UPDATE', resource: 'PlanFeature', resourceId: id, after: toAuditJson(feature), ipAddress: ip, userAgent: undefined, }); return feature; } async function deletePlanFeature(id, adminId, ip) { await repo.deletePlanFeature(id); await repo.createAuditLog({ adminUserId: adminId, action: 'DELETE', resource: 'PlanFeature', resourceId: id, ipAddress: ip, userAgent: undefined, }); } // ─── Promotions ──────────────────────────────────────────────────────────── function listPromotions() { return repo.listPromotions(); } async function createPromotion(data, adminId, ip) { const promo = await repo.createPromotion({ ...data, createdBy: adminId }); await repo.createAuditLog({ adminUserId: adminId, action: 'CREATE', resource: 'PricingPromotion', after: toAuditJson(promo), ipAddress: ip, userAgent: undefined, }); return promo; } async function updatePromotion(id, data, adminId, ip) { const promo = await repo.updatePromotion(id, data); await repo.createAuditLog({ adminUserId: adminId, action: 'UPDATE', resource: 'PricingPromotion', after: toAuditJson(promo), ipAddress: ip, userAgent: undefined, }); return promo; } async function deletePromotion(id, adminId, ip) { await repo.deletePromotion(id); await repo.createAuditLog({ adminUserId: adminId, action: 'DELETE', resource: 'PricingPromotion', entityId: id, ipAddress: ip, userAgent: undefined, }); } //# sourceMappingURL=admin.service.js.map