Skip to content

Commit e8765f3

Browse files
committed
test: update tests for new nonce implementation
1 parent edee9e1 commit e8765f3

File tree

2 files changed

+258
-76
lines changed

2 files changed

+258
-76
lines changed

apps/web/src/server/modules/auth/__tests__/auth.service.spec.ts

Lines changed: 114 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,19 @@ describe('auth.service', () => {
2020
describe('emailLogin', () => {
2121
it('should create a verification token and send OTP email', async () => {
2222
const email = '[email protected]'
23+
const nonce = 'test-nonce-123'
2324

24-
const result = await emailLogin(email)
25+
const result = await emailLogin({ email, nonce })
2526

2627
expect(result).toEqual({
2728
email,
2829
token: expect.any(String),
2930
otpPrefix: expect.any(String),
3031
})
3132

32-
// Verify token was created in database
33+
// Verify token was created in database with nonce as identifier
3334
const token = await db.verificationToken.findUnique({
34-
where: { identifier: email },
35+
where: { identifier: nonce },
3536
})
3637
expect(token).toBeDefined()
3738
expect(mockedMailService.sendMail).toHaveBeenCalledWith({
@@ -41,165 +42,233 @@ describe('auth.service', () => {
4142
})
4243
})
4344

44-
it('should reset attempts when sending new OTP for existing user', async () => {
45+
it('should reset attempts when sending new OTP for existing nonce', async () => {
4546
const email = '[email protected]'
47+
const nonce = 'test-nonce-123'
4648

4749
// First login
48-
await emailLogin(email)
50+
await emailLogin({ email, nonce })
4951

5052
// Simulate failed attempts
5153
await db.verificationToken.update({
52-
where: { identifier: email },
54+
where: { identifier: nonce },
5355
data: { attempts: 3 },
5456
})
5557

5658
// Second login should reset attempts
57-
await emailLogin(email)
59+
await emailLogin({ email, nonce })
5860

5961
const token = await db.verificationToken.findUnique({
60-
where: { identifier: email },
62+
where: { identifier: nonce },
6163
})
6264
expect(token?.attempts).toBe(0)
6365
})
6466

65-
it('should update existing token instead of creating duplicate', async () => {
67+
it('should update existing token instead of creating duplicate for same nonce', async () => {
6668
const email = '[email protected]'
69+
const nonce = 'test-nonce-123'
6770

68-
await emailLogin(email)
69-
await emailLogin(email)
71+
await emailLogin({ email, nonce })
72+
await emailLogin({ email, nonce })
7073

7174
// Should only have one record
7275
const tokens = await db.verificationToken.findMany({
73-
where: { identifier: email },
76+
where: { identifier: nonce },
7477
})
7578
expect(tokens).toHaveLength(1)
7679
})
80+
81+
it('should allow different nonces for same email', async () => {
82+
const email = '[email protected]'
83+
const nonce1 = 'test-nonce-1'
84+
const nonce2 = 'test-nonce-2'
85+
86+
await emailLogin({ email, nonce: nonce1 })
87+
await emailLogin({ email, nonce: nonce2 })
88+
89+
// Should have two records with different nonces
90+
const token1 = await db.verificationToken.findUnique({
91+
where: { identifier: nonce1 },
92+
})
93+
const token2 = await db.verificationToken.findUnique({
94+
where: { identifier: nonce2 },
95+
})
96+
97+
expect(token1).toBeDefined()
98+
expect(token2).toBeDefined()
99+
expect(token1?.token).not.toBe(token2?.token)
100+
})
77101
})
78102

79103
describe('emailVerifyOtp', () => {
80104
it('should successfully verify a valid OTP', async () => {
81105
const email = '[email protected]'
106+
const nonce = 'test-nonce-123'
82107

83108
// Create a verification token
84-
const { token } = await emailLogin(email)
109+
const { token } = await emailLogin({ email, nonce })
85110

86111
// Should not throw
87-
await expect(emailVerifyOtp({ email, token })).resolves.not.toThrow()
112+
await expect(
113+
emailVerifyOtp({ email, token, nonce }),
114+
).resolves.not.toThrow()
88115

89116
// Token should be deleted after successful verification
90117
const verificationToken = await db.verificationToken.findUnique({
91-
where: { identifier: email },
118+
where: { identifier: nonce },
92119
})
93120
expect(verificationToken).toBeNull()
94121
})
95122

96123
it('should reject an invalid OTP', async () => {
97124
const email = '[email protected]'
125+
const nonce = 'test-nonce-123'
98126
const token = 'WRONG6'
99127

100-
await emailLogin(email)
101-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow(
128+
await emailLogin({ email, nonce })
129+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow(
102130
'Token is invalid or has expired',
103131
)
104132
})
105133

106134
it('should reject an expired OTP', async () => {
107135
const email = '[email protected]'
136+
const nonce = 'test-nonce-123'
108137

109-
const { token, hashedToken } = createAuthToken(email)
138+
const { token, hashedToken } = createAuthToken({ email, nonce })
110139

111140
// Create a verification token with an old issuedAt date
112141
const oldDate = add(new Date(), { seconds: -700 }) // 700 seconds ago (beyond 600s expiry)
113142
await db.verificationToken.create({
114143
data: {
115-
identifier: email,
144+
identifier: nonce,
116145
token: hashedToken,
117146
issuedAt: oldDate,
118147
},
119148
})
120149

121-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow(
150+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow(
122151
'Token is invalid or has expired',
123152
)
124153
})
125154

126155
it('should increment attempts on each verification try', async () => {
127156
const email = '[email protected]'
157+
const nonce = 'test-nonce-123'
128158
const token = 'WRONG6'
129159

130-
await emailLogin(email)
160+
await emailLogin({ email, nonce })
131161

132162
// First attempt
133-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow()
163+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow()
134164
let verificationToken = await db.verificationToken.findUnique({
135-
where: { identifier: email },
165+
where: { identifier: nonce },
136166
})
137167
expect(verificationToken?.attempts).toBe(1)
138168

139169
// Second attempt
140-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow()
170+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow()
141171
verificationToken = await db.verificationToken.findUnique({
142-
where: { identifier: email },
172+
where: { identifier: nonce },
143173
})
144174
expect(verificationToken?.attempts).toBe(2)
145175
})
146176

147177
it('should reject after too many failed attempts (>5)', async () => {
148178
const email = '[email protected]'
179+
const nonce = 'test-nonce-123'
149180
const token = 'WRONG6'
150181

151-
await emailLogin(email)
182+
await emailLogin({ email, nonce })
152183

153184
// Make 5 failed attempts
154185
for (let i = 0; i < 5; i++) {
155-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow()
186+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow()
156187
}
157188

158189
// 6th attempt should give TOO_MANY_REQUESTS
159-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow(
190+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow(
160191
'Wrong OTP was entered too many times',
161192
)
162193
})
163194

164-
it('should throw error for non-existent email', async () => {
165-
const email = '[email protected]'
195+
it('should throw error for non-existent nonce', async () => {
196+
const email = '[email protected]'
197+
const nonce = 'nonexistent-nonce'
166198
const token = '123456'
167199

168-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow(
169-
'Invalid login email',
200+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow(
201+
'Invalid login email or missing nonce',
170202
)
171203
})
172204

173205
it('should delete verification token after successful verification', async () => {
174-
// Arrange
175206
const email = '[email protected]'
176-
const { token } = await emailLogin(email)
207+
const nonce = 'test-nonce-123'
208+
const { token } = await emailLogin({ email, nonce })
177209

178-
// Act
179-
await emailVerifyOtp({ email, token })
210+
await emailVerifyOtp({ email, token, nonce })
180211

181-
// Assert
182212
// Token should be deleted
183213
const verificationToken = await db.verificationToken.findUnique({
184-
where: { identifier: email },
214+
where: { identifier: nonce },
185215
})
186216
expect(verificationToken).toBeNull()
187217
})
188218

189219
it('should prevent token reuse after successful verification', async () => {
190-
// Arrange
191220
const email = '[email protected]'
192-
const { token } = await emailLogin(email)
221+
const nonce = 'test-nonce-123'
222+
const { token } = await emailLogin({ email, nonce })
193223

194-
// Act
195224
// First verification succeeds
196-
await expect(emailVerifyOtp({ email, token })).resolves.toBeDefined()
225+
await expect(
226+
emailVerifyOtp({ email, token, nonce }),
227+
).resolves.toBeDefined()
197228

198-
// Assert
199229
// Second verification with same token should fail
200-
await expect(emailVerifyOtp({ email, token })).rejects.toThrow(
201-
'Invalid login email',
230+
await expect(emailVerifyOtp({ email, token, nonce })).rejects.toThrow(
231+
'Invalid login email or missing nonce',
202232
)
203233
})
234+
235+
it('should not allow using token with wrong nonce', async () => {
236+
const email = '[email protected]'
237+
const nonce1 = 'test-nonce-1'
238+
const nonce2 = 'test-nonce-2'
239+
240+
const { token } = await emailLogin({ email, nonce: nonce1 })
241+
242+
// Try to verify with wrong nonce
243+
await expect(
244+
emailVerifyOtp({ email, token, nonce: nonce2 }),
245+
).rejects.toThrow('Invalid login email or missing nonce')
246+
247+
// Original token should still exist
248+
const verificationToken = await db.verificationToken.findUnique({
249+
where: { identifier: nonce1 },
250+
})
251+
expect(verificationToken).toBeDefined()
252+
})
253+
254+
it('should ensure OTP is tied to specific session via nonce', async () => {
255+
const email = '[email protected]'
256+
const nonce1 = 'session-1-nonce'
257+
const nonce2 = 'session-2-nonce'
258+
259+
// Two different sessions for same email
260+
const { token: token1 } = await emailLogin({ email, nonce: nonce1 })
261+
const { token: token2 } = await emailLogin({ email, nonce: nonce2 })
262+
263+
// Each token should only work with its own nonce
264+
await expect(
265+
emailVerifyOtp({ email, token: token1, nonce: nonce1 }),
266+
).resolves.toBeDefined()
267+
268+
// token2 with nonce2 should still work (not affected by token1 verification)
269+
await expect(
270+
emailVerifyOtp({ email, token: token2, nonce: nonce2 }),
271+
).resolves.toBeDefined()
272+
})
204273
})
205274
})

0 commit comments

Comments
 (0)