Skip to content

Commit

Permalink
fix: forgotPassword set expiration time (#9871)
Browse files Browse the repository at this point in the history
The logic for creating a timestamp for use in resetPassword was not
correctly returning a valid date.

---------

Co-authored-by: Patrik Kozak <[email protected]>
  • Loading branch information
DanRibbens and PatrikKozak authored Dec 11, 2024
1 parent ca52a50 commit 306b5d2
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 2 deletions.
4 changes: 2 additions & 2 deletions packages/payload/src/auth/operations/forgotPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(

user.resetPasswordToken = token
user.resetPasswordExpiration = new Date(
collectionConfig.auth?.forgotPassword?.expiration || expiration || Date.now() + 3600000,
).toISOString() // 1 hour
Date.now() + (collectionConfig.auth?.forgotPassword?.expiration ?? expiration ?? 3600000),
).toISOString()

user = await payload.update({
id: user.id,
Expand Down
37 changes: 37 additions & 0 deletions test/access-control/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,43 @@ describe('Access Control', () => {
expect(res).toBeTruthy()
})
})

describe('Auth - Local API', () => {
it('should not allow reset password if forgotPassword expiration token is expired', async () => {
// Mock Date.now() to simulate the forgotPassword call happening 1 hour ago (default is 1 hour)
const originalDateNow = Date.now
const mockDateNow = jest.spyOn(Date, 'now').mockImplementation(() => {
// Move the current time back by 1 hour
return originalDateNow() - 60 * 60 * 1000
})

let forgot
try {
// Call forgotPassword while the mocked Date.now() is active
forgot = await payload.forgotPassword({
collection: 'users',
data: {
email: '[email protected]',
},
})
} finally {
// Restore the original Date.now() after the forgotPassword call
mockDateNow.mockRestore()
}

// Attempt to reset password, which should fail because the token is expired
await expect(
payload.resetPassword({
collection: 'users',
data: {
password: 'test',
token: forgot,
},
overrideAccess: true,
}),
).rejects.toThrow('Token is either invalid or has expired.')
})
})
})

async function createDoc<TSlug extends CollectionSlug = 'posts'>(
Expand Down
3 changes: 3 additions & 0 deletions test/auth/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export default buildConfigWithDefaults({
tokenExpiration: 7200, // 2 hours
useAPIKey: true,
verify: false,
forgotPassword: {
expiration: 300000, // 5 minutes
},
},
fields: [
{
Expand Down
35 changes: 35 additions & 0 deletions test/auth/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -932,5 +932,40 @@ describe('Auth', () => {

expect(reset.user.email).toStrictEqual('[email protected]')
})

it('should not allow reset password if forgotPassword expiration token is expired', async () => {
// Mock Date.now() to simulate the forgotPassword call happening 6 minutes ago (current expiration is set to 5 minutes)
const originalDateNow = Date.now
const mockDateNow = jest.spyOn(Date, 'now').mockImplementation(() => {
// Move the current time back by 6 minutes (360,000 ms)
return originalDateNow() - 6 * 60 * 1000
})

let forgot
try {
// Call forgotPassword while the mocked Date.now() is active
forgot = await payload.forgotPassword({
collection: 'users',
data: {
email: '[email protected]',
},
})
} finally {
// Restore the original Date.now() after the forgotPassword call
mockDateNow.mockRestore()
}

// Attempt to reset password, which should fail because the token is expired
await expect(
payload.resetPassword({
collection: 'users',
data: {
password: 'test',
token: forgot,
},
overrideAccess: true,
}),
).rejects.toThrow('Token is either invalid or has expired.')
})
})
})

0 comments on commit 306b5d2

Please sign in to comment.