@@ -6,6 +6,14 @@ const asn1 = require('asn1.js');
66const jws = require ( 'jws' ) ;
77const url = require ( 'url' ) ;
88
9+ /**
10+ * DEFAULT_EXPIRATION is set to seconds in 12 hours
11+ */
12+ const DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60 ;
13+
14+ // Maximum expiration is 24 hours according. (See VAPID spec)
15+ const MAX_EXPIRATION_SECONDS = 24 * 60 * 60 ;
16+
917const ECPrivateKeyASN = asn1 . define ( 'ECPrivateKey' , function ( ) {
1018 this . seq ( ) . obj (
1119 this . key ( 'version' ) . int ( ) ,
@@ -89,6 +97,44 @@ function validatePrivateKey(privateKey) {
8997 }
9098}
9199
100+ /**
101+ * Given the number of seconds calculates
102+ * the expiration in the future by adding the passed `numSeconds`
103+ * with the current seconds from Unix Epoch
104+ *
105+ * @param {Number } numSeconds Number of seconds to be added
106+ * @return {Number } Future expiration in seconds
107+ */
108+ function getFutureExpirationTimestamp ( numSeconds ) {
109+ const futureExp = new Date ( ) ;
110+ futureExp . setSeconds ( futureExp . getSeconds ( ) + numSeconds ) ;
111+ return Math . floor ( futureExp . getTime ( ) / 1000 ) ;
112+ }
113+
114+ /**
115+ * Validates the Expiration Header based on the VAPID Spec
116+ * Throws error of type `Error` if the expiration is not validated
117+ *
118+ * @param {Number } expiration Expiration seconds from Epoch to be validated
119+ */
120+ function validateExpiration ( expiration ) {
121+ if ( ! Number . isInteger ( expiration ) ) {
122+ throw new Error ( '`expiration` value must be a number' ) ;
123+ }
124+
125+ if ( expiration < 0 ) {
126+ throw new Error ( '`expiration` must be a positive integer' ) ;
127+ }
128+
129+ // Roughly checks the time of expiration, since the max expiration can be ahead
130+ // of the time than at the moment the expiration was generated
131+ const maxExpirationTimestamp = getFutureExpirationTimestamp ( MAX_EXPIRATION_SECONDS ) ;
132+
133+ if ( expiration >= maxExpirationTimestamp ) {
134+ throw new Error ( '`expiration` value is greater than maximum of 24 hours' ) ;
135+ }
136+ }
137+
92138/**
93139 * This method takes the required VAPID parameters and returns the required
94140 * header to be added to a Web Push Protocol Request.
@@ -123,11 +169,10 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
123169 publicKey = urlBase64 . decode ( publicKey ) ;
124170 privateKey = urlBase64 . decode ( privateKey ) ;
125171
126- const DEFAULT_EXPIRATION = Math . floor ( Date . now ( ) / 1000 ) + 43200 ;
127-
128172 if ( expiration ) {
129- // TODO: Check if expiration is valid and use it in place of the hard coded
130- // expiration of 24hours.
173+ validateExpiration ( expiration ) ;
174+ } else {
175+ expiration = getFutureExpirationTimestamp ( DEFAULT_EXPIRATION_SECONDS ) ;
131176 }
132177
133178 const header = {
@@ -137,7 +182,7 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
137182
138183 const jwtPayload = {
139184 aud : audience ,
140- exp : DEFAULT_EXPIRATION ,
185+ exp : expiration ,
141186 sub : subject
142187 } ;
143188
@@ -155,8 +200,10 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
155200
156201module . exports = {
157202 generateVAPIDKeys : generateVAPIDKeys ,
203+ getFutureExpirationTimestamp : getFutureExpirationTimestamp ,
158204 getVapidHeaders : getVapidHeaders ,
159205 validateSubject : validateSubject ,
160206 validatePublicKey : validatePublicKey ,
161- validatePrivateKey : validatePrivateKey
207+ validatePrivateKey : validatePrivateKey ,
208+ validateExpiration : validateExpiration
162209} ;
0 commit comments