|
| 1 | +import type { TelemetryEventRaw } from '@clerk/types'; |
| 2 | + |
| 3 | +import { createClerkClientWithOptions } from './createClerkClient'; |
| 4 | + |
| 5 | +const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED'; |
| 6 | +const EVENT_SAMPLING_RATE = 1; // 100% sampling rate |
| 7 | + |
| 8 | +type EventKeylessEnvDriftPayload = { |
| 9 | + publicKeyMatch: boolean; |
| 10 | + secretKeyMatch: boolean; |
| 11 | + envVarsMissing: boolean; |
| 12 | + keylessFileHasKeys: boolean; |
| 13 | + keylessPublishableKey: string; |
| 14 | + envPublishableKey: string; |
| 15 | +}; |
| 16 | + |
| 17 | +/** |
| 18 | + * Detects environment variable drift for keyless Next.js applications and fires telemetry events. |
| 19 | + * |
| 20 | + * This function compares the publishableKey and secretKey values from `.clerk/.tmp/keyless.json` |
| 21 | + * with the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables. |
| 22 | + * |
| 23 | + * If there's a mismatch, it fires a `KEYLESS_ENV_DRIFT_DETECTED` event. |
| 24 | + * For local testing purposes, it also fires a `KEYLESS_ENV_DRIFT_NOT_DETECTED` event when |
| 25 | + * keys exist and match the environment variables. |
| 26 | + */ |
| 27 | +export async function detectKeylessEnvDrift(): Promise<void> { |
| 28 | + // Only run on server side |
| 29 | + if (typeof window !== 'undefined') { |
| 30 | + return; |
| 31 | + } |
| 32 | + |
| 33 | + try { |
| 34 | + // Dynamically import server-side dependencies to avoid client-side issues |
| 35 | + const { safeParseClerkFile } = await import('./keyless-node.js'); |
| 36 | + |
| 37 | + // Read the keyless configuration file |
| 38 | + const keylessFile = safeParseClerkFile(); |
| 39 | + |
| 40 | + if (!keylessFile) { |
| 41 | + return; |
| 42 | + } |
| 43 | + |
| 44 | + // Get environment variables |
| 45 | + const envPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; |
| 46 | + const envSecretKey = process.env.CLERK_SECRET_KEY; |
| 47 | + |
| 48 | + // Check if environment variables are missing, and keys exist in keyless file |
| 49 | + const envVarsMissing = !envPublishableKey && !envSecretKey; |
| 50 | + const keylessFileHasKeys = Boolean(keylessFile?.publishableKey && keylessFile?.secretKey); |
| 51 | + |
| 52 | + if (envVarsMissing && keylessFileHasKeys) { |
| 53 | + // Environment variables are missing but keyless file has keys - this is normal for keyless mode |
| 54 | + return; |
| 55 | + } |
| 56 | + |
| 57 | + // Compare publishable keys |
| 58 | + const publicKeyMatch = Boolean(envPublishableKey === keylessFile?.publishableKey); |
| 59 | + |
| 60 | + // Compare secret keys |
| 61 | + const secretKeyMatch = Boolean(envSecretKey === keylessFile?.secretKey); |
| 62 | + |
| 63 | + // Check if there's a drift (mismatch between env vars and keyless file) |
| 64 | + const hasDrift = !publicKeyMatch || !secretKeyMatch; |
| 65 | + |
| 66 | + const payload: EventKeylessEnvDriftPayload = { |
| 67 | + publicKeyMatch, |
| 68 | + secretKeyMatch, |
| 69 | + envVarsMissing, |
| 70 | + keylessFileHasKeys, |
| 71 | + keylessPublishableKey: keylessFile.publishableKey, |
| 72 | + envPublishableKey: envPublishableKey as string, |
| 73 | + }; |
| 74 | + |
| 75 | + // Create a clerk client to access telemetry |
| 76 | + const clerkClient = createClerkClientWithOptions({ |
| 77 | + publishableKey: keylessFile.publishableKey, |
| 78 | + secretKey: keylessFile.secretKey, |
| 79 | + }); |
| 80 | + |
| 81 | + if (hasDrift) { |
| 82 | + // Fire drift detected event |
| 83 | + const driftDetectedEvent: TelemetryEventRaw<EventKeylessEnvDriftPayload> = { |
| 84 | + event: EVENT_KEYLESS_ENV_DRIFT_DETECTED, |
| 85 | + eventSamplingRate: EVENT_SAMPLING_RATE, |
| 86 | + payload, |
| 87 | + }; |
| 88 | + |
| 89 | + clerkClient.telemetry?.record(driftDetectedEvent); |
| 90 | + } |
| 91 | + } catch (error) { |
| 92 | + // Silently handle errors to avoid breaking the application |
| 93 | + console.warn('Failed to detect keyless environment drift:', error); |
| 94 | + } |
| 95 | +} |
0 commit comments