1 | var JsonWebTokenError = require('./lib/JsonWebTokenError');
|
2 | var NotBeforeError = require('./lib/NotBeforeError');
|
3 | var TokenExpiredError = require('./lib/TokenExpiredError');
|
4 | var decode = require('./decode');
|
5 | var timespan = require('./lib/timespan');
|
6 | var PS_SUPPORTED = require('./lib/psSupported');
|
7 | var jws = require('jws');
|
8 |
|
9 | var PUB_KEY_ALGS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'];
|
10 | var RSA_KEY_ALGS = ['RS256', 'RS384', 'RS512'];
|
11 | var HS_ALGS = ['HS256', 'HS384', 'HS512'];
|
12 |
|
13 | if (PS_SUPPORTED) {
|
14 | PUB_KEY_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
|
15 | RSA_KEY_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
|
16 | }
|
17 |
|
18 | module.exports = function (jwtString, secretOrPublicKey, options, callback) {
|
19 | if ((typeof options === 'function') && !callback) {
|
20 | callback = options;
|
21 | options = {};
|
22 | }
|
23 |
|
24 | if (!options) {
|
25 | options = {};
|
26 | }
|
27 |
|
28 |
|
29 | options = Object.assign({}, options);
|
30 |
|
31 | var done;
|
32 |
|
33 | if (callback) {
|
34 | done = callback;
|
35 | } else {
|
36 | done = function(err, data) {
|
37 | if (err) throw err;
|
38 | return data;
|
39 | };
|
40 | }
|
41 |
|
42 | if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') {
|
43 | return done(new JsonWebTokenError('clockTimestamp must be a number'));
|
44 | }
|
45 |
|
46 | if (options.nonce !== undefined && (typeof options.nonce !== 'string' || options.nonce.trim() === '')) {
|
47 | return done(new JsonWebTokenError('nonce must be a non-empty string'));
|
48 | }
|
49 |
|
50 | var clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000);
|
51 |
|
52 | if (!jwtString){
|
53 | return done(new JsonWebTokenError('jwt must be provided'));
|
54 | }
|
55 |
|
56 | if (typeof jwtString !== 'string') {
|
57 | return done(new JsonWebTokenError('jwt must be a string'));
|
58 | }
|
59 |
|
60 | var parts = jwtString.split('.');
|
61 |
|
62 | if (parts.length !== 3){
|
63 | return done(new JsonWebTokenError('jwt malformed'));
|
64 | }
|
65 |
|
66 | var decodedToken;
|
67 |
|
68 | try {
|
69 | decodedToken = decode(jwtString, { complete: true });
|
70 | } catch(err) {
|
71 | return done(err);
|
72 | }
|
73 |
|
74 | if (!decodedToken) {
|
75 | return done(new JsonWebTokenError('invalid token'));
|
76 | }
|
77 |
|
78 | var header = decodedToken.header;
|
79 | var getSecret;
|
80 |
|
81 | if(typeof secretOrPublicKey === 'function') {
|
82 | if(!callback) {
|
83 | return done(new JsonWebTokenError('verify must be called asynchronous if secret or public key is provided as a callback'));
|
84 | }
|
85 |
|
86 | getSecret = secretOrPublicKey;
|
87 | }
|
88 | else {
|
89 | getSecret = function(header, secretCallback) {
|
90 | return secretCallback(null, secretOrPublicKey);
|
91 | };
|
92 | }
|
93 |
|
94 | return getSecret(header, function(err, secretOrPublicKey) {
|
95 | if(err) {
|
96 | return done(new JsonWebTokenError('error in secret or public key callback: ' + err.message));
|
97 | }
|
98 |
|
99 | var hasSignature = parts[2].trim() !== '';
|
100 |
|
101 | if (!hasSignature && secretOrPublicKey){
|
102 | return done(new JsonWebTokenError('jwt signature is required'));
|
103 | }
|
104 |
|
105 | if (hasSignature && !secretOrPublicKey) {
|
106 | return done(new JsonWebTokenError('secret or public key must be provided'));
|
107 | }
|
108 |
|
109 | if (!hasSignature && !options.algorithms) {
|
110 | options.algorithms = ['none'];
|
111 | }
|
112 |
|
113 | if (!options.algorithms) {
|
114 | options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ||
|
115 | ~secretOrPublicKey.toString().indexOf('BEGIN PUBLIC KEY') ? PUB_KEY_ALGS :
|
116 | ~secretOrPublicKey.toString().indexOf('BEGIN RSA PUBLIC KEY') ? RSA_KEY_ALGS : HS_ALGS;
|
117 |
|
118 | }
|
119 |
|
120 | if (!~options.algorithms.indexOf(decodedToken.header.alg)) {
|
121 | return done(new JsonWebTokenError('invalid algorithm'));
|
122 | }
|
123 |
|
124 | var valid;
|
125 |
|
126 | try {
|
127 | valid = jws.verify(jwtString, decodedToken.header.alg, secretOrPublicKey);
|
128 | } catch (e) {
|
129 | return done(e);
|
130 | }
|
131 |
|
132 | if (!valid) {
|
133 | return done(new JsonWebTokenError('invalid signature'));
|
134 | }
|
135 |
|
136 | var payload = decodedToken.payload;
|
137 |
|
138 | if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) {
|
139 | if (typeof payload.nbf !== 'number') {
|
140 | return done(new JsonWebTokenError('invalid nbf value'));
|
141 | }
|
142 | if (payload.nbf > clockTimestamp + (options.clockTolerance || 0)) {
|
143 | return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
|
144 | }
|
145 | }
|
146 |
|
147 | if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
|
148 | if (typeof payload.exp !== 'number') {
|
149 | return done(new JsonWebTokenError('invalid exp value'));
|
150 | }
|
151 | if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) {
|
152 | return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
|
153 | }
|
154 | }
|
155 |
|
156 | if (options.audience) {
|
157 | var audiences = Array.isArray(options.audience) ? options.audience : [options.audience];
|
158 | var target = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
159 |
|
160 | var match = target.some(function (targetAudience) {
|
161 | return audiences.some(function (audience) {
|
162 | return audience instanceof RegExp ? audience.test(targetAudience) : audience === targetAudience;
|
163 | });
|
164 | });
|
165 |
|
166 | if (!match) {
|
167 | return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or ')));
|
168 | }
|
169 | }
|
170 |
|
171 | if (options.issuer) {
|
172 | var invalid_issuer =
|
173 | (typeof options.issuer === 'string' && payload.iss !== options.issuer) ||
|
174 | (Array.isArray(options.issuer) && options.issuer.indexOf(payload.iss) === -1);
|
175 |
|
176 | if (invalid_issuer) {
|
177 | return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer));
|
178 | }
|
179 | }
|
180 |
|
181 | if (options.subject) {
|
182 | if (payload.sub !== options.subject) {
|
183 | return done(new JsonWebTokenError('jwt subject invalid. expected: ' + options.subject));
|
184 | }
|
185 | }
|
186 |
|
187 | if (options.jwtid) {
|
188 | if (payload.jti !== options.jwtid) {
|
189 | return done(new JsonWebTokenError('jwt jwtid invalid. expected: ' + options.jwtid));
|
190 | }
|
191 | }
|
192 |
|
193 | if (options.nonce) {
|
194 | if (payload.nonce !== options.nonce) {
|
195 | return done(new JsonWebTokenError('jwt nonce invalid. expected: ' + options.nonce));
|
196 | }
|
197 | }
|
198 |
|
199 | if (options.maxAge) {
|
200 | if (typeof payload.iat !== 'number') {
|
201 | return done(new JsonWebTokenError('iat required when maxAge is specified'));
|
202 | }
|
203 |
|
204 | var maxAgeTimestamp = timespan(options.maxAge, payload.iat);
|
205 | if (typeof maxAgeTimestamp === 'undefined') {
|
206 | return done(new JsonWebTokenError('"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
|
207 | }
|
208 | if (clockTimestamp >= maxAgeTimestamp + (options.clockTolerance || 0)) {
|
209 | return done(new TokenExpiredError('maxAge exceeded', new Date(maxAgeTimestamp * 1000)));
|
210 | }
|
211 | }
|
212 |
|
213 | if (options.complete === true) {
|
214 | var signature = decodedToken.signature;
|
215 |
|
216 | return done(null, {
|
217 | header: header,
|
218 | payload: payload,
|
219 | signature: signature
|
220 | });
|
221 | }
|
222 |
|
223 | return done(null, payload);
|
224 | });
|
225 | };
|