Enhance WHMCS integration with mTLS support and product mapping features. Added mTLS configuration options, updated API endpoints for mTLS status and fingerprint registration, and implemented product validation API. Updated database schema and documentation accordingly.
This commit is contained in:
@@ -254,6 +254,10 @@ export {
|
||||
whmcsUsageResponseSchema,
|
||||
whmcsUsageAdjustmentRequestSchema,
|
||||
whmcsDashboardStatsResponseSchema,
|
||||
whmcsRegisterMtlsRequestSchema,
|
||||
whmcsMtlsStatusResponseSchema,
|
||||
whmcsValidatePlanRequestSchema,
|
||||
whmcsValidatePlanResponseSchema,
|
||||
type WhmcsReconcileRequest,
|
||||
type WhmcsReconcileResponse,
|
||||
type WhmcsListAccountsResponse,
|
||||
@@ -264,4 +268,7 @@ export {
|
||||
type WhmcsUsageResponse,
|
||||
type WhmcsUsageAdjustmentRequest,
|
||||
type WhmcsDashboardStatsResponse,
|
||||
type WhmcsRegisterMtlsRequest,
|
||||
type WhmcsMtlsStatusResponse,
|
||||
type WhmcsValidatePlanResponse,
|
||||
} from './whmcs';
|
||||
|
||||
@@ -284,6 +284,35 @@ export const whmcsDashboardStatsResponseSchema = z.object({
|
||||
eventsCursor: z.string().uuid().nullable(),
|
||||
lastEventSyncAt: z.string().datetime().nullable(),
|
||||
lastReconciliationAt: z.string().datetime().nullable(),
|
||||
mtlsEnabled: z.boolean(),
|
||||
mtlsFingerprintRegistered: z.boolean(),
|
||||
});
|
||||
|
||||
export type WhmcsDashboardStatsResponse = z.infer<typeof whmcsDashboardStatsResponseSchema>;
|
||||
|
||||
export const whmcsRegisterMtlsRequestSchema = z.object({
|
||||
fingerprint: z.string().min(32).max(128),
|
||||
subject: z.string().max(256).optional(),
|
||||
});
|
||||
|
||||
export const whmcsMtlsStatusResponseSchema = z.object({
|
||||
integrationId: z.string(),
|
||||
mtlsEnabled: z.boolean(),
|
||||
fingerprintRegistered: z.boolean(),
|
||||
fingerprintPreview: z.string().nullable(),
|
||||
subject: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const whmcsValidatePlanRequestSchema = z.object({
|
||||
planSlug: z.string().min(1).max(64),
|
||||
});
|
||||
|
||||
export const whmcsValidatePlanResponseSchema = z.object({
|
||||
valid: z.boolean(),
|
||||
planSlug: z.string(),
|
||||
plan: whmcsPlanCatalogItemSchema.nullable(),
|
||||
});
|
||||
|
||||
export type WhmcsRegisterMtlsRequest = z.infer<typeof whmcsRegisterMtlsRequestSchema>;
|
||||
export type WhmcsMtlsStatusResponse = z.infer<typeof whmcsMtlsStatusResponseSchema>;
|
||||
export type WhmcsValidatePlanResponse = z.infer<typeof whmcsValidatePlanResponseSchema>;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
||||
-- mTLS client certificate binding for WHMCS installations
|
||||
|
||||
ALTER TABLE "whmcs_installations"
|
||||
ADD COLUMN "mtlsClientFingerprint" TEXT,
|
||||
ADD COLUMN "mtlsClientSubject" TEXT;
|
||||
@@ -735,6 +735,8 @@ model WhmcsInstallation {
|
||||
integrationId String @unique
|
||||
name String
|
||||
apiSecret String
|
||||
mtlsClientFingerprint String?
|
||||
mtlsClientSubject String?
|
||||
isActive Boolean @default(true)
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,3 +7,11 @@ export {
|
||||
verifyRequestSignature,
|
||||
type SignedRequestParts,
|
||||
} from './hmac';
|
||||
|
||||
export {
|
||||
INTEGRATION_MTLS_HEADERS,
|
||||
normalizeCertificateFingerprint,
|
||||
fingerprintFromCertificateDer,
|
||||
verifyClientCertificateFingerprint,
|
||||
isIntegrationMtlsEnabled,
|
||||
} from './mtls';
|
||||
|
||||
36
packages/integration-auth/src/mtls.ts
Normal file
36
packages/integration-auth/src/mtls.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { createHash, timingSafeEqual } from 'node:crypto';
|
||||
|
||||
export const INTEGRATION_MTLS_HEADERS = {
|
||||
clientFingerprint: 'x-hgc-client-cert-fingerprint',
|
||||
clientSubject: 'x-hgc-client-cert-subject',
|
||||
} as const;
|
||||
|
||||
/** Normalizes nginx, Traefik, or OpenSSL fingerprint formats to lowercase hex without colons. */
|
||||
export function normalizeCertificateFingerprint(value: string): string {
|
||||
return value.replace(/:/g, '').replace(/\s/g, '').toLowerCase();
|
||||
}
|
||||
|
||||
export function fingerprintFromCertificateDer(der: Buffer): string {
|
||||
return createHash('sha256').update(der).digest('hex');
|
||||
}
|
||||
|
||||
export function verifyClientCertificateFingerprint(
|
||||
provided: string,
|
||||
expected: string,
|
||||
): boolean {
|
||||
const normalizedProvided = normalizeCertificateFingerprint(provided);
|
||||
const normalizedExpected = normalizeCertificateFingerprint(expected);
|
||||
|
||||
if (normalizedProvided.length !== normalizedExpected.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const providedBuffer = Buffer.from(normalizedProvided, 'utf8');
|
||||
const expectedBuffer = Buffer.from(normalizedExpected, 'utf8');
|
||||
|
||||
return timingSafeEqual(providedBuffer, expectedBuffer);
|
||||
}
|
||||
|
||||
export function isIntegrationMtlsEnabled(): boolean {
|
||||
return process.env['INTEGRATION_MTLS_ENABLED'] === 'true';
|
||||
}
|
||||
Reference in New Issue
Block a user