1+ import { vitest } from 'vitest' ;
2+
13import { User } from 'server/models' ;
24import { login , modelize , setup , teardown } from 'stubstub' ;
35
46const tonyEmail = `${ crypto . randomUUID ( ) } @gmail.com` ;
7+ const autofillEmail = `${ crypto . randomUUID ( ) } @gmail.com` ;
8+ const restrictedEmail = `${ crypto . randomUUID ( ) } @gmail.com` ;
9+ const delayedHoneypotEmail = `${ crypto . randomUUID ( ) } @gmail.com` ;
10+ const spamEmail = `${ crypto . randomUUID ( ) } @gmail.com` ;
511
612const models = modelize `
713 User user {}
@@ -11,10 +17,58 @@ const models = modelize`
1117 completed: false
1218 count: 1
1319 }
20+ Signup autofillSignup {
21+ email: ${ autofillEmail }
22+ hash: "hash-autofill"
23+ completed: false
24+ count: 1
25+ }
26+ Signup restrictedSignup {
27+ email: ${ restrictedEmail }
28+ hash: "hash-restricted"
29+ completed: false
30+ count: 1
31+ }
32+ Signup delayedHoneypotSignup {
33+ email: ${ delayedHoneypotEmail }
34+ hash: "hash-delayed-honeypot"
35+ completed: false
36+ count: 1
37+ }
38+ Signup spamSignup {
39+ email: ${ spamEmail }
40+ hash: "hash-spam"
41+ completed: false
42+ count: 1
43+ }
1444 User suggestionUser {}
1545` ;
1646
17- setup ( beforeAll , models . resolve ) ;
47+ const { deleteSessionsForUser } = vitest . hoisted ( ( ) => {
48+ return {
49+ deleteSessionsForUser : vitest . fn ( ) . mockResolvedValue ( undefined ) ,
50+ } ;
51+ } ) ;
52+
53+ vitest . mock ( import ( 'server/utils/session' ) , async ( importOriginal ) => {
54+ const og = await importOriginal ( ) ;
55+ return {
56+ ...og ,
57+ deleteSessionsForUser : deleteSessionsForUser ,
58+ } ;
59+ } ) ;
60+ vitest . mock ( import ( 'server/spamTag/notifications' ) , async ( importOriginal ) => {
61+ const og = await importOriginal ( ) ;
62+ return {
63+ ...og ,
64+ notify : vitest . fn ( ) . mockResolvedValue ( undefined ) ,
65+ } ;
66+ } ) ;
67+
68+ setup ( beforeAll , async ( ) => {
69+ await models . resolve ( ) ;
70+ } ) ;
71+
1872teardown ( afterAll ) ;
1973
2074describe ( '/api/users' , ( ) => {
@@ -44,6 +98,84 @@ describe('/api/users', () => {
4498 expect ( userNow ?. isSuperAdmin ) . toEqual ( false ) ;
4599 } ) ;
46100
101+ it ( 'immediately restricts users when website honeypot is filled' , async ( ) => {
102+ const { spamSignup } = models ;
103+ const agent = await login ( ) ;
104+ await agent
105+ . post ( '/api/users' )
106+ . send ( {
107+ email : spamSignup . email ,
108+ hash : spamSignup . hash ,
109+ firstName : 'Slow' ,
110+ lastName : 'Fill' ,
111+ password : 'oh!' ,
112+ _honeypot : 'oh!' ,
113+ _formStartedAtMs : Date . now ( ) - 6000 ,
114+ } )
115+ . expect ( 403 ) ;
116+ const createdUser = await User . findOne ( { where : { email : spamSignup . email } } ) ;
117+ expect ( createdUser ) . toBeDefined ( ) ;
118+ const { getSpamTagForUser } = await import ( 'server/spamTag/userQueries' ) ;
119+ const spamTag = await getSpamTagForUser ( createdUser ! . id ) ;
120+ expect ( spamTag ?. status ) . toBe ( 'confirmed-spam' ) ;
121+ await agent
122+ . post ( '/api/login' )
123+ . send ( { email : createdUser ! . email , password : 'oh!' } )
124+ . expect ( 403 ) ;
125+ } ) ;
126+
127+ it ( 'does not restrict users when honeypot is filled after 5 seconds' , async ( ) => {
128+ const { delayedHoneypotSignup } = models ;
129+ const agent = await login ( ) ;
130+ await agent
131+ . post ( '/api/users' )
132+ . send ( {
133+ email : delayedHoneypotSignup . email ,
134+ hash : delayedHoneypotSignup . hash ,
135+ firstName : 'Slow' ,
136+ lastName : 'Fill' ,
137+ password : 'oh!' ,
138+ _passwordHoneypot : 'oh!' ,
139+ _formStartedAtMs : Date . now ( ) - 6000 ,
140+ } )
141+ . expect ( 201 ) ;
142+ const createdUser = await User . findOne ( { where : { email : delayedHoneypotSignup . email } } ) ;
143+ if ( ! createdUser ) {
144+ throw new Error ( 'Expected user to be created' ) ;
145+ }
146+ const { getSpamTagForUser } = await import ( 'server/spamTag/userQueries' ) ;
147+ const spamTag = await getSpamTagForUser ( createdUser . id ) ;
148+ expect ( spamTag ) . toBeNull ( ) ;
149+ await agent
150+ . put ( '/api/users' )
151+ . send ( { userId : createdUser . id , firstName : 'Still' , lastName : 'Allowed' } )
152+ . expect ( 201 ) ;
153+ } ) ;
154+
155+ it ( 'restricts and does not authenticate users when password honeypot is filled within 5 seconds' , async ( ) => {
156+ const { restrictedSignup } = models ;
157+ const agent = await login ( ) ;
158+ await agent
159+ . post ( '/api/users' )
160+ . send ( {
161+ email : restrictedSignup . email ,
162+ hash : restrictedSignup . hash ,
163+ firstName : 'Bot' ,
164+ lastName : 'Account' ,
165+ password : 'oh!' ,
166+ _passwordHoneypot : 'oh!' ,
167+ _formStartedAtMs : Date . now ( ) ,
168+ } )
169+ . expect ( 403 ) ;
170+ const createdUser = await User . findOne ( { where : { email : restrictedSignup . email } } ) ;
171+ expect ( createdUser ) . toBeDefined ( ) ;
172+ const { getSpamTagForUser } = await import ( 'server/spamTag/userQueries' ) ;
173+ const spamTag = await getSpamTagForUser ( createdUser ! . id ) ;
174+ expect ( spamTag ?. status ) . toEqual ( 'confirmed-spam' ) ;
175+
176+ expect ( deleteSessionsForUser ) . toHaveBeenCalledWith ( createdUser ! . email ) ;
177+ } ) ;
178+
47179 it ( 'allows an exisiting user to read another users profile info' , async ( ) => {
48180 const { user, suggestionUser } = models ;
49181 const agent = await login ( user ) ;
0 commit comments