@@ -3,40 +3,108 @@ import { verifyTypedData } from "viem";
3
3
import { createClient } from "@supabase/supabase-js" ;
4
4
import { Database } from "../../src/types/supabase-notification" ;
5
5
import messages from "../../src/consts/eip712-messages" ;
6
+ import { EMAIL_REGEX , TELEGRAM_REGEX , ETH_ADDRESS_REGEX , ETH_SIGNATURE_REGEX } from "../../src/consts/index" ;
6
7
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
+ } ;
8
71
9
72
export const handler : Handler = async ( event ) => {
10
73
try {
11
74
if ( ! event . body ) {
12
75
throw new Error ( "No body provided" ) ;
13
76
}
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 ) ;
16
78
const lowerCaseAddress = address . toLowerCase ( ) as `0x${string } `;
17
79
// Note: this does NOT work for smart contract wallets, but viem's publicClient.verifyMessage() fails to verify atm.
18
80
// https://viem.sh/docs/utilities/verifyTypedData.html
81
+ const data = messages . contactDetails ( address , nonce , telegram , email ) ;
19
82
const isValid = await verifyTypedData ( {
20
- ...messages . contactDetails ( address , nonce , telegram , email ) ,
83
+ ...data ,
21
84
signature,
22
85
} ) ;
23
86
if ( ! isValid ) {
24
87
// If the recovered address does not match the provided address, return an error
25
88
throw new Error ( "Signature verification failed" ) ;
26
89
}
27
- // TODO: use typed supabase client
90
+
91
+ const supabase = createClient < Database > ( process . env . SUPABASE_URL ! , process . env . SUPABASE_CLIENT_API_KEY ! ) ;
92
+
28
93
// If the message is empty, delete the user record
29
94
if ( email === "" && telegram === "" ) {
30
95
const { error } = await supabase . from ( "users" ) . delete ( ) . match ( { address : lowerCaseAddress } ) ;
31
96
if ( error ) throw error ;
32
97
return { statusCode : 200 , body : JSON . stringify ( { message : "Record deleted successfully." } ) } ;
33
98
}
99
+
34
100
// For a user matching this address, upsert the user record
35
101
const { error } = await supabase
36
102
. from ( "user-settings" )
37
103
. upsert ( { address : lowerCaseAddress , email : email , telegram : telegram } )
38
104
. match ( { address : lowerCaseAddress } ) ;
39
- if ( error ) throw error ;
105
+ if ( error ) {
106
+ throw error ;
107
+ }
40
108
return { statusCode : 200 , body : JSON . stringify ( { message : "Record updated successfully." } ) } ;
41
109
} catch ( err ) {
42
110
return { statusCode : 500 , body : JSON . stringify ( { message : `Error: ${ err } ` } ) } ;
0 commit comments