@@ -3,40 +3,108 @@ import { verifyTypedData } from "viem";
33import { createClient } from "@supabase/supabase-js" ;
44import { Database } from "../../src/types/supabase-notification" ;
55import messages from "../../src/consts/eip712-messages" ;
6+ import { EMAIL_REGEX , TELEGRAM_REGEX , ETH_ADDRESS_REGEX , ETH_SIGNATURE_REGEX } from "../../src/consts/index" ;
67
7- const supabase = createClient < Database > ( process . env . SUPABASE_URL ! , process . env . SUPABASE_CLIENT_API_KEY ! ) ;
8+ type NotificationSettings = {
9+ email ?: string ;
10+ telegram ?: string ;
11+ nonce : `${number } `;
12+ address : `0x${string } `;
13+ signature : string ;
14+ } ;
15+
16+ const parse = ( inputString : string ) : NotificationSettings => {
17+ let input ;
18+ try {
19+ input = JSON . parse ( inputString ) ;
20+ } catch ( err ) {
21+ throw new Error ( "Invalid JSON format" ) ;
22+ }
23+
24+ const requiredKeys : ( keyof NotificationSettings ) [ ] = [ "nonce" , "address" , "signature" ] ;
25+ const optionalKeys : ( keyof NotificationSettings ) [ ] = [ "email" , "telegram" ] ;
26+ const receivedKeys = Object . keys ( input ) ;
27+
28+ for ( const key of requiredKeys ) {
29+ if ( ! receivedKeys . includes ( key ) ) {
30+ throw new Error ( `Missing key: ${ key } ` ) ;
31+ }
32+ }
33+
34+ const allExpectedKeys = [ ...requiredKeys , ...optionalKeys ] ;
35+ for ( const key of receivedKeys ) {
36+ if ( ! allExpectedKeys . includes ( key as keyof NotificationSettings ) ) {
37+ throw new Error ( `Unexpected key: ${ key } ` ) ;
38+ }
39+ }
40+
41+ const email = input . email ? input . email . trim ( ) : "" ;
42+ if ( email && ! EMAIL_REGEX . test ( email ) ) {
43+ throw new Error ( "Invalid email format" ) ;
44+ }
45+
46+ const telegram = input . telegram ? input . telegram . trim ( ) : "" ;
47+ if ( telegram && ! TELEGRAM_REGEX . test ( telegram ) ) {
48+ throw new Error ( "Invalid Telegram username format" ) ;
49+ }
50+
51+ if ( ! / ^ \d + $ / . test ( input . nonce ) ) {
52+ throw new Error ( "Invalid nonce format. Expected an integer as a string." ) ;
53+ }
54+
55+ if ( ! ETH_ADDRESS_REGEX . test ( input . address ) ) {
56+ throw new Error ( "Invalid Ethereum address format" ) ;
57+ }
58+
59+ if ( ! ETH_SIGNATURE_REGEX . test ( input . signature ) ) {
60+ throw new Error ( "Invalid signature format" ) ;
61+ }
62+
63+ return {
64+ email : input . email . trim ( ) ,
65+ telegram : input . telegram . trim ( ) ,
66+ nonce : input . nonce ,
67+ address : input . address . trim ( ) . toLowerCase ( ) ,
68+ signature : input . signature . trim ( ) ,
69+ } ;
70+ } ;
871
972export const handler : Handler = async ( event ) => {
1073 try {
1174 if ( ! event . body ) {
1275 throw new Error ( "No body provided" ) ;
1376 }
14- // TODO: sanitize event.body
15- const { email, telegram, nonce, address, signature } = JSON . parse ( event . body ) ;
77+ const { email, telegram, nonce, address, signature } = parse ( event . body ) ;
1678 const lowerCaseAddress = address . toLowerCase ( ) as `0x${string } `;
1779 // Note: this does NOT work for smart contract wallets, but viem's publicClient.verifyMessage() fails to verify atm.
1880 // https://viem.sh/docs/utilities/verifyTypedData.html
81+ const data = messages . contactDetails ( address , nonce , telegram , email ) ;
1982 const isValid = await verifyTypedData ( {
20- ...messages . contactDetails ( address , nonce , telegram , email ) ,
83+ ...data ,
2184 signature,
2285 } ) ;
2386 if ( ! isValid ) {
2487 // If the recovered address does not match the provided address, return an error
2588 throw new Error ( "Signature verification failed" ) ;
2689 }
27- // TODO: use typed supabase client
90+
91+ const supabase = createClient < Database > ( process . env . SUPABASE_URL ! , process . env . SUPABASE_CLIENT_API_KEY ! ) ;
92+
2893 // If the message is empty, delete the user record
2994 if ( email === "" && telegram === "" ) {
3095 const { error } = await supabase . from ( "users" ) . delete ( ) . match ( { address : lowerCaseAddress } ) ;
3196 if ( error ) throw error ;
3297 return { statusCode : 200 , body : JSON . stringify ( { message : "Record deleted successfully." } ) } ;
3398 }
99+
34100 // For a user matching this address, upsert the user record
35101 const { error } = await supabase
36102 . from ( "user-settings" )
37103 . upsert ( { address : lowerCaseAddress , email : email , telegram : telegram } )
38104 . match ( { address : lowerCaseAddress } ) ;
39- if ( error ) throw error ;
105+ if ( error ) {
106+ throw error ;
107+ }
40108 return { statusCode : 200 , body : JSON . stringify ( { message : "Record updated successfully." } ) } ;
41109 } catch ( err ) {
42110 return { statusCode : 500 , body : JSON . stringify ( { message : `Error: ${ err } ` } ) } ;
0 commit comments