@@ -7,10 +7,51 @@ import authOptions from '@/lib/auth-options'
77import { streamComplete } from '@chatbotkit/react/actions/complete'
88import { ChatBotKit } from '@chatbotkit/sdk'
99
10+ import crypto from 'node:crypto'
11+
1012const cbk = new ChatBotKit ( {
1113 secret : process . env . CHATBOTKIT_API_SECRET ,
1214} )
1315
16+ /**
17+ * @note Namespace for generating deterministic UUID v5 fingerprints from user
18+ * emails. This ensures the same email always produces the same fingerprint
19+ * without leaking PII.
20+ */
21+ const CONTACT_NAMESPACE = 'e676f123-b5eb-4c44-a80b-8aa0e723cfe6'
22+
23+ /**
24+ * Generates a deterministic UUID v5 from an email address and namespace.
25+ *
26+ * @param {string } email - The email to derive a fingerprint from
27+ * @returns {string } A deterministic UUID v5 string
28+ */
29+ function generateFingerprint ( email ) {
30+ const namespaceBytes = Buffer . from ( CONTACT_NAMESPACE . replace ( / - / g, '' ) , 'hex' )
31+
32+ const hash = crypto
33+ . createHash ( 'sha1' )
34+ . update ( namespaceBytes )
35+ . update ( email . toLowerCase ( ) )
36+ . digest ( )
37+
38+ // Set version to 5 (SHA-1 based)
39+ hash [ 6 ] = ( hash [ 6 ] & 0x0f ) | 0x50
40+
41+ // Set variant to RFC 4122
42+ hash [ 8 ] = ( hash [ 8 ] & 0x3f ) | 0x80
43+
44+ const hex = hash . toString ( 'hex' ) . slice ( 0 , 32 )
45+
46+ return [
47+ hex . slice ( 0 , 8 ) ,
48+ hex . slice ( 8 , 12 ) ,
49+ hex . slice ( 12 , 16 ) ,
50+ hex . slice ( 16 , 20 ) ,
51+ hex . slice ( 20 , 32 ) ,
52+ ] . join ( '-' )
53+ }
54+
1455/**
1556 * Parses the CHATBOTKIT_BOT_IDS environment variable into an array of bot IDs.
1657 *
@@ -45,14 +86,16 @@ async function requireSession() {
4586/**
4687 * Ensures a ChatBotKit contact exists for the authenticated user.
4788 *
48- * Uses the user's email as a stable identifier via `contact.ensure()`.
89+ * Generates a deterministic UUID v5 fingerprint from the user's email so the
90+ * fingerprint is stable and doesn't leak raw PII.
4991 *
5092 * @returns {Promise<string> } The contact ID
5193 */
5294export async function ensureContact ( ) {
5395 const session = await requireSession ( )
5496
5597 const { id } = await cbk . contact . ensure ( {
98+ fingerprint : generateFingerprint ( session . user . email ) ,
5699 email : session . user . email ,
57100 name : session . user . name || '' ,
58101 } )
0 commit comments