mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2026-10-05 14:38:23 +00:00
fix(nip98): reject tokens whose created_at is in the future
validateEventTimestamp() computed `now - created_at < 60`, which is satisfied by any timestamp ahead of now, since the difference goes negative. A token signed with a `created_at` far in the future therefore validates indefinitely, and the 60 second window -- the only replay defense NIP-98 has, as there is no nonce or seen-token cache -- never closes for it. Compare the absolute difference instead, so the window is 60 seconds on both sides.
This commit is contained in:
@@ -136,6 +136,17 @@ describe('validateToken', () => {
|
|||||||
expect(isTokenValid).rejects.toThrow(Error)
|
expect(isTokenValid).rejects.toThrow(Error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('throws an error for an event timestamp in the future', async () => {
|
||||||
|
const sk = generateSecretKey()
|
||||||
|
const invalidToken = await getToken('http://test.com', 'get', e => {
|
||||||
|
e.created_at = Math.round(Date.now() / 1000) + 3600
|
||||||
|
return finalizeEvent(e, sk)
|
||||||
|
})
|
||||||
|
const isTokenValid = validateToken(invalidToken, 'http://test.com', 'get')
|
||||||
|
|
||||||
|
expect(isTokenValid).rejects.toThrow(Error)
|
||||||
|
})
|
||||||
|
|
||||||
test('throws an error for invalid url', async () => {
|
test('throws an error for invalid url', async () => {
|
||||||
const sk = generateSecretKey()
|
const sk = generateSecretKey()
|
||||||
const token = await getToken('http://test.com', 'get', e => finalizeEvent(e, sk))
|
const token = await getToken('http://test.com', 'get', e => finalizeEvent(e, sk))
|
||||||
@@ -239,6 +250,16 @@ describe('validateEventTimestamp', () => {
|
|||||||
|
|
||||||
expect(isEventTimestampValid).toBe(false)
|
expect(isEventTimestampValid).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('returns false for a timestamp in the future', async () => {
|
||||||
|
const sk = generateSecretKey()
|
||||||
|
const token = await getToken('http://test.com', 'get', e => finalizeEvent(e, sk), true)
|
||||||
|
const unpackedEvent: Event = await unpackEventFromToken(token)
|
||||||
|
unpackedEvent.created_at = Math.round(Date.now() / 1000) + 3600
|
||||||
|
const isEventTimestampValid = validateEventTimestamp(unpackedEvent)
|
||||||
|
|
||||||
|
expect(isEventTimestampValid).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('validateEventKind', () => {
|
describe('validateEventKind', () => {
|
||||||
|
|||||||
@@ -87,14 +87,16 @@ export async function unpackEventFromToken(token: string): Promise<Event> {
|
|||||||
/**
|
/**
|
||||||
* Validates the timestamp of an event.
|
* Validates the timestamp of an event.
|
||||||
* @param event - The event object to validate.
|
* @param event - The event object to validate.
|
||||||
* @returns A boolean indicating whether the event timestamp is within the last 60 seconds.
|
* @returns A boolean indicating whether the event timestamp is within 60 seconds of now.
|
||||||
*/
|
*/
|
||||||
export function validateEventTimestamp(event: Event): boolean {
|
export function validateEventTimestamp(event: Event): boolean {
|
||||||
if (!event.created_at) {
|
if (!event.created_at) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.round(new Date().getTime() / 1000) - event.created_at < 60
|
// the comparison has to be absolute: a `created_at` in the future produces a
|
||||||
|
// negative difference, which would otherwise satisfy the check forever.
|
||||||
|
return Math.abs(Math.round(new Date().getTime() / 1000) - event.created_at) < 60
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user