11import { APIGatewayProxyEvent , APIGatewayProxyResult } from 'aws-lambda' ;
22import { PrivyClient , User } from '@privy-io/server-auth' ;
3+ import axios from 'axios' ;
34
45// Define types for all identifiers
56type UserIdentifiers = {
7+ [ key : string ] : string | boolean | undefined ;
68 email ?: string ;
79 google ?: string ;
810 apple ?: string ;
@@ -12,6 +14,7 @@ type UserIdentifiers = {
1214 telegram ?: string ;
1315 instagram ?: string ;
1416 linkedin ?: string ;
17+ isDisposable ?: boolean ;
1518} ;
1619
1720// Define social account types for type safety
@@ -26,12 +29,122 @@ type SocialAccountType =
2629 | 'instagram_oauth'
2730 | 'linkedin_oauth' ;
2831
32+ interface LinkedAccount {
33+ type : string ;
34+ email ?: string | null ;
35+ address ?: string | null ;
36+ username ?: string | null ;
37+ }
38+
39+ const PRIVY_APP_ID = process . env . PRIVY_APP_ID as string ;
40+ const PRIVY_APP_SECRET = process . env . PRIVY_APP_SECRET as string ;
41+
42+ export async function addToDenylist ( email : string ) : Promise < boolean > {
43+ const emailDomain = email . split ( '@' ) [ 1 ] ;
44+ try {
45+ const response = await axios . post ( `https://auth.privy.io/api/v1/apps/${ PRIVY_APP_ID } /denylist` , {
46+ type : 'emailDomain' ,
47+ value : emailDomain
48+ } , {
49+ auth : {
50+ username : PRIVY_APP_ID ,
51+ password : PRIVY_APP_SECRET
52+ } ,
53+ headers : {
54+ 'privy-app-id' : PRIVY_APP_ID
55+ }
56+ } ) ;
57+ if ( response . status === 200 ) {
58+ return true ;
59+ } else {
60+ console . error ( 'Failed to add domain to denylist. API returned error:' , response . data ) ;
61+ return false ;
62+ }
63+ } catch ( error ) {
64+ console . error ( 'Unexpected error while adding domain to denylist:' , error ) ;
65+ return false ;
66+ }
67+ }
68+
69+ export async function checkPrivyDenylist ( email : string ) : Promise < boolean > {
70+ const emailDomain = email . split ( '@' ) [ 1 ] ;
71+ try {
72+ const response = await axios . get ( `https://auth.privy.io/api/v1/apps/${ PRIVY_APP_ID } /denylist` , { // Initial request to get the denylist, no cursor query parameter
73+ auth : {
74+ username : PRIVY_APP_ID ,
75+ password : PRIVY_APP_SECRET
76+ } ,
77+ headers : {
78+ 'privy-app-id' : PRIVY_APP_ID
79+ }
80+ } ) ;
81+
82+ if ( ! response . data . data ) {
83+ return false ;
84+ }
85+
86+ const exists = response . data . data . some ( ( dataObject : { value : string } ) => dataObject . value === emailDomain ) ;
87+ if ( exists ) {
88+ return true ;
89+ }
90+
91+ let cursor = response . data ?. next_cursor ;
92+ while ( cursor ) {
93+ const nextResponse = await axios . get ( `https://auth.privy.io/api/v1/apps/${ PRIVY_APP_ID } /denylist?cursor=${ cursor } ` , {
94+ auth : {
95+ username : PRIVY_APP_ID ,
96+ password : PRIVY_APP_SECRET
97+ } ,
98+ headers : {
99+ 'privy-app-id' : PRIVY_APP_ID
100+ }
101+ } ) ;
102+
103+ const exists = nextResponse . data . data . some ( ( dataObject : { value : string } ) => dataObject . value === emailDomain ) ;
104+ if ( exists ) {
105+ return true ;
106+ }
107+ cursor = nextResponse . data . next_cursor ;
108+ }
109+ return false ;
110+ } catch ( error ) {
111+ console . error ( 'Error checking email disposability:' , error ) ;
112+ return false ;
113+ }
114+ }
115+
116+ export async function tryUserCheck ( email : string ) : Promise < boolean > {
117+ const USERCHECK_API_KEY = process . env . USERCHECK_API_KEY as string ;
118+ if ( ! USERCHECK_API_KEY ) {
119+ return false ;
120+ }
121+ try {
122+ const response = await axios . get ( `https://api.usercheck.com/domain/${ email . split ( '@' ) [ 1 ] } ` , {
123+ headers : {
124+ 'Authorization' : `Bearer ${ USERCHECK_API_KEY } `
125+ }
126+ } ) ;
127+ if ( response . status === 200 ) {
128+ return response . data . disposable ;
129+ } else if ( response . status === 400 ) {
130+ console . error ( 'Error domain is invalid:' , response . data ) ;
131+ return false ;
132+ } else { // Should be only status 429
133+ console . error ( 'Error too many requests:' , response . data ) ;
134+ return false ;
135+ }
136+ } catch ( error ) {
137+ console . error ( 'Error checking email disposability:' , error ) ;
138+ return false ;
139+ }
140+ }
141+
29142/**
30- * Extracts user identifiers (email and social media usernames) from a Privy user
31- * @param user - The Privy user object
32- * @returns UserIdentifiers object containing all identifiers
33- */
34- function getUserIdentifiers ( user : User ) : UserIdentifiers {
143+ * Get the identifiers for the user
144+ * @param user - The user object from Privy
145+ * @returns The identifiers for the user
146+ */
147+ export function getUserIdentifiers ( user : User ) : UserIdentifiers {
35148 const identifiers : UserIdentifiers = { } ;
36149
37150 // Get email if available
@@ -40,24 +153,22 @@ function getUserIdentifiers(user: User): UserIdentifiers {
40153 }
41154
42155 // Get usernames from linked accounts
43- user . linkedAccounts . forEach ( account => {
156+ user . linkedAccounts . forEach ( ( account : LinkedAccount ) => {
44157 const accountType = account . type as SocialAccountType ;
45158 // Remove '_oauth' suffix to get the field name
46159 const field = accountType . replace ( '_oauth' , '' ) as keyof UserIdentifiers ;
47160
48161 // Handle email-based accounts (email, google, apple, linkedin)
49162 if ( accountType === 'email' || accountType === 'google_oauth' || accountType === 'apple_oauth' || accountType === 'linkedin_oauth' ) {
50- const emailAccount = account as { email ?: string } | { address ?: string } ;
51- if ( 'email' in emailAccount ) {
52- identifiers [ field ] = emailAccount . email ;
53- } else if ( 'address' in emailAccount ) {
54- identifiers [ field ] = emailAccount . address ;
163+ if ( account . email ) {
164+ identifiers [ field ] = account . email || undefined ;
165+ } else if ( account . address ) {
166+ identifiers [ field ] = account . address || undefined ;
55167 }
56168 }
57169 // Handle username-based accounts
58- else {
59- const usernameAccount = account as { username ?: string } ;
60- identifiers [ field ] = usernameAccount . username || undefined ;
170+ else if ( account . username ) {
171+ identifiers [ field ] = account . username || undefined ;
61172 }
62173 } ) ;
63174 return identifiers ;
@@ -67,7 +178,6 @@ export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGat
67178 try {
68179 // Get wallet address from query parameters
69180 const walletAddress = event . pathParameters ?. walletAddress ;
70-
71181 if ( ! walletAddress ) {
72182 return {
73183 statusCode : 422 ,
@@ -77,13 +187,17 @@ export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGat
77187 } ;
78188 }
79189
190+ if ( ! PRIVY_APP_ID || ! PRIVY_APP_SECRET ) {
191+ throw new Error ( 'Missing required environment variables' ) ;
192+ }
193+
80194 const privy = new PrivyClient (
81- process . env . PRIVY_APP_ID ! ,
82- process . env . PRIVY_APP_SECRET !
195+ PRIVY_APP_ID ,
196+ PRIVY_APP_SECRET
83197 ) ;
84198
85199 const user = await privy . getUserByWalletAddress ( walletAddress ) ;
86-
200+
87201 if ( ! user ) {
88202 return {
89203 statusCode : 404 ,
@@ -92,17 +206,35 @@ export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGat
92206 } ) ,
93207 } ;
94208 }
209+
95210 const identifiers = getUserIdentifiers ( user ) ;
211+
212+ if ( identifiers . email ) {
213+ try {
214+ const isInDenylist = await checkPrivyDenylist ( identifiers . email ) ;
215+ identifiers . isDisposable = isInDenylist ;
216+ if ( ! isInDenylist ) {
217+ const isDisposable = await tryUserCheck ( identifiers . email ) ;
218+ identifiers . isDisposable = isDisposable ;
219+ if ( isDisposable ) {
220+ await addToDenylist ( identifiers . email ) ;
221+ }
222+ }
223+ } catch ( error ) {
224+ console . error ( 'Error checking email disposability:' , error ) ;
225+ }
226+ }
227+
96228 return {
97229 statusCode : 200 ,
98230 body : JSON . stringify ( identifiers ) ,
99231 } ;
100232 } catch ( err ) {
101- console . log ( err ) ;
233+ console . error ( 'Error processing request:' , err ) ;
102234 return {
103235 statusCode : 500 ,
104236 body : JSON . stringify ( {
105- message : 'Some error happened ' ,
237+ message : 'Internal server error ' ,
106238 } ) ,
107239 } ;
108240 }
0 commit comments