import crypto from "crypto" ; import fetch from "node-fetch" ; /** Abstract base class for store implementations. */ export default abstract class AbstractStore { /** Lifespan of a nonce. */ nonceTtl : number = 15 * 60 * 1000 ; /** `fetch` timeout. */ requestTimeout : number = 10 * 1000 ; /** `fetchCached` minimum cache time. */ cacheMinTtl : number = 60 * 60 * 1000 ; /** Fetch JSON from cache or using HTTP GET. */ abstract fetchCached ( cacheId , url ) ; /** Generate and store a nonce. */ abstract createNonce ( email ) ; /** Consume a nonce, and check if it's valid for the given email address. */ abstract consumeNonce ( nonce , email ) ; /** Destroy the store. */ destroy ( ) { } /** Generate a new nonce. */ generateNonce ( _email ) { return crypto . randomBytes ( 16 ) . toString ( "hex" ) ; } /** Fetch a URL using HTTP GET. */ async fetch ( url ) { const res = await fetch ( url , { timeout : this . requestTimeout } ) ; if ( res . status !== 200 ) { throw Error ( `Unexpected status code ${ res . status }` ) ; } const data = await res . json ( ) ; if ( typeof data !== "object" ) { throw Error ( `Invalid response body` ) ; } let ttl = 0 ; const cc = res . headers . get ( "Cache-Control" ) ; if ( cc ) { const match = /max-age\s*=\s*(\d+)/ . exec ( cc ) ; if ( match ) { ttl = parseInt ( match [ 1 ] , 10 ) * 1000 ; } } ttl = Math . max ( this . cacheMinTtl , ttl ) ; return { ttl , data } ; } }