@@ -6,6 +6,14 @@ const asn1 = require('asn1.js');
6
6
const jws = require ( 'jws' ) ;
7
7
const url = require ( 'url' ) ;
8
8
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
+
9
17
const ECPrivateKeyASN = asn1 . define ( 'ECPrivateKey' , function ( ) {
10
18
this . seq ( ) . obj (
11
19
this . key ( 'version' ) . int ( ) ,
@@ -89,6 +97,44 @@ function validatePrivateKey(privateKey) {
89
97
}
90
98
}
91
99
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
+
92
138
/**
93
139
* This method takes the required VAPID parameters and returns the required
94
140
* header to be added to a Web Push Protocol Request.
@@ -123,11 +169,10 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
123
169
publicKey = urlBase64 . decode ( publicKey ) ;
124
170
privateKey = urlBase64 . decode ( privateKey ) ;
125
171
126
- const DEFAULT_EXPIRATION = Math . floor ( Date . now ( ) / 1000 ) + 43200 ;
127
-
128
172
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 ) ;
131
176
}
132
177
133
178
const header = {
@@ -137,7 +182,7 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
137
182
138
183
const jwtPayload = {
139
184
aud : audience ,
140
- exp : DEFAULT_EXPIRATION ,
185
+ exp : expiration ,
141
186
sub : subject
142
187
} ;
143
188
@@ -155,8 +200,10 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
155
200
156
201
module . exports = {
157
202
generateVAPIDKeys : generateVAPIDKeys ,
203
+ getFutureExpirationTimestamp : getFutureExpirationTimestamp ,
158
204
getVapidHeaders : getVapidHeaders ,
159
205
validateSubject : validateSubject ,
160
206
validatePublicKey : validatePublicKey ,
161
- validatePrivateKey : validatePrivateKey
207
+ validatePrivateKey : validatePrivateKey ,
208
+ validateExpiration : validateExpiration
162
209
} ;
0 commit comments