1+ 'use strict' ;
2+
3+ /*
4+ * Module dependencies.
5+ */
6+ const Request = require ( '../../../lib/request' ) ;
7+ const Model = require ( '../../../lib/model' ) ;
8+ const RevokeHandler = require ( '../../../lib/handlers/revoke-handler' ) ;
9+ const sinon = require ( 'sinon' ) ;
10+ const InvalidArgumentError = require ( '../../../lib/errors/invalid-argument-error' ) ;
11+ const InvalidClientError = require ( '../../../lib/errors/invalid-client-error' )
12+ const InvalidRequestError = require ( '../../../lib/errors/invalid-request-error' )
13+ const should = require ( 'chai' ) . should ( ) ;
14+
15+ /**
16+ * Test `TokenHandler`.
17+ */
18+
19+ describe ( 'RevokeHandler' , ( ) => {
20+ describe ( 'constructor()' , ( ) => {
21+ it ( 'should throw an error if `model` is missing' , ( ) => {
22+ ( ( ) => {
23+ new RevokeHandler ( { } ) ;
24+ } ) . should . throw ( InvalidArgumentError , 'Missing parameter: `model`' ) ;
25+ } ) ;
26+ it ( 'should throw an error if `model` does not implement `getClient()`' , ( ) => {
27+ ( ( ) => {
28+ new RevokeHandler ( { model : { } } ) ;
29+ } ) . should . throw ( InvalidArgumentError , 'Invalid argument: model does not implement `getClient()`' ) ;
30+ } ) ;
31+ it ( 'should throw an error if `model` does not implement `revokeToken()`' , ( ) => {
32+ ( ( ) => {
33+ new RevokeHandler ( {
34+ model : {
35+ getClient : ( ) => { }
36+ }
37+ } ) ;
38+ } ) . should . throw ( InvalidArgumentError , 'Invalid argument: model does not implement `revokeToken()`' ) ;
39+ } ) ;
40+ } ) ;
41+ describe ( 'getClient()' , ( ) => {
42+ it ( 'should call `model.getClient()`' , async ( ) => {
43+ const model = Model . from ( {
44+ getClient : sinon . stub ( ) . returns ( { grants : [ 'password' ] } ) ,
45+ saveToken : ( ) => {
46+ } ,
47+ revokeToken : ( ) => {
48+ }
49+ } ) ;
50+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
51+ const request = new Request ( {
52+ body : { client_id : 12345 , client_secret : 'secret' } ,
53+ headers : { } ,
54+ method : { } ,
55+ query : { }
56+ } ) ;
57+
58+ await handler . getClient ( request ) ;
59+ model . getClient . callCount . should . equal ( 1 ) ;
60+ model . getClient . firstCall . args . should . have . length ( 2 ) ;
61+ model . getClient . firstCall . args [ 0 ] . should . equal ( 12345 ) ;
62+ model . getClient . firstCall . args [ 1 ] . should . equal ( 'secret' ) ;
63+ model . getClient . firstCall . thisValue . should . equal ( model ) ;
64+ } ) ;
65+ it ( 'throws an error if the client is invalid' , async ( ) => {
66+ const model = Model . from ( {
67+ getClient : sinon . stub ( ) . returns ( null ) ,
68+ saveToken : ( ) => {
69+ } ,
70+ revokeToken : ( ) => {
71+ }
72+ } ) ;
73+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
74+ const request = new Request ( {
75+ body : { client_id : 12345 , client_secret : 'secret' } ,
76+ headers : { } ,
77+ method : { } ,
78+ query : { }
79+ } ) ;
80+
81+ try {
82+ await handler . getClient ( request ) ;
83+ should . fail ( ) ;
84+ } catch ( e ) {
85+ e . should . be . instanceOf ( InvalidClientError ) ;
86+ e . message . should . equal ( 'Invalid client: client is invalid' ) ;
87+ }
88+ } ) ;
89+ it ( 'throws an error if client id is using invalid chars' , async ( ) => {
90+ const model = Model . from ( {
91+ getClient : sinon . stub ( ) . returns ( { grants : [ 'password' ] } ) ,
92+ saveToken : ( ) => {
93+ } ,
94+ revokeToken : ( ) => {
95+ }
96+ } ) ;
97+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
98+ const request = new Request ( {
99+ body : { client_id : '12😵345' , client_secret : 'secret' } ,
100+ headers : { } ,
101+ method : { } ,
102+ query : { }
103+ } ) ;
104+
105+ try {
106+ await handler . getClient ( request ) ;
107+ should . fail ( ) ;
108+ } catch ( e ) {
109+ e . should . be . instanceOf ( InvalidRequestError ) ;
110+ e . message . should . equal ( 'Invalid parameter: `client_id`' ) ;
111+ }
112+ } ) ;
113+ it ( 'throws an error if client_secret is usind invalid chars' , async ( ) => {
114+ const model = Model . from ( {
115+ getClient : sinon . stub ( ) . returns ( { grants : [ 'password' ] } ) ,
116+ saveToken : ( ) => {
117+ } ,
118+ revokeToken : ( ) => {
119+ }
120+ } ) ;
121+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
122+ const request = new Request ( {
123+ body : { client_id : '12345' , client_secret : 'sec😵ret' } ,
124+ headers : { } ,
125+ method : { } ,
126+ query : { }
127+ } ) ;
128+
129+ try {
130+ await handler . getClient ( request ) ;
131+ should . fail ( ) ;
132+ } catch ( e ) {
133+ e . should . be . instanceOf ( InvalidRequestError ) ;
134+ e . message . should . equal ( 'Invalid parameter: `client_secret`' ) ;
135+ }
136+ } ) ;
137+ } ) ;
138+ describe ( 'getClientCredentials()' , ( ) => {
139+ it ( 'should throw an error if client credentials are missing on confidential clients' , async ( ) => {
140+ const model = Model . from ( {
141+ getClient : sinon . stub ( ) . returns ( { grants : [ 'client_credentials' ] } ) ,
142+ saveToken : ( ) => {
143+ } ,
144+ revokeToken : ( ) => {
145+ }
146+ } ) ;
147+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
148+ const request = new Request ( { body : { } , headers : { } , method : { } , query : { } } ) ;
149+
150+ try {
151+ await handler . getClientCredentials ( request ) ;
152+ should . fail ( ) ;
153+ } catch ( e ) {
154+ e . should . be . instanceOf ( InvalidClientError ) ;
155+ e . message . should . equal ( 'Invalid client: cannot retrieve client credentials' ) ;
156+ }
157+ } ) ;
158+ } ) ;
159+ describe ( 'updateSuccessResponse' , ( ) => {
160+ it ( 'updates the response with success information' , ( ) => {
161+ const model = Model . from ( {
162+ getClient : sinon . stub ( ) . returns ( { grants : [ 'password' ] } ) ,
163+ saveToken : ( ) => {
164+ } ,
165+ revokeToken : ( ) => {
166+ }
167+ } ) ;
168+ const handler = new RevokeHandler ( { accessTokenLifetime : 120 , model : model , refreshTokenLifetime : 120 } ) ;
169+ const response = {
170+ set : sinon . spy ( )
171+ } ;
172+
173+ handler . updateSuccessResponse ( response ) ;
174+ response . body . should . deep . equal ( { } ) ;
175+ response . status . should . equal ( 200 ) ;
176+ response . set . callCount . should . equal ( 2 ) ;
177+ response . set . firstCall . args . should . have . length ( 2 ) ;
178+ response . set . firstCall . args [ 0 ] . should . equal ( 'Cache-Control' ) ;
179+ response . set . firstCall . args [ 1 ] . should . equal ( 'no-store' ) ;
180+ response . set . secondCall . args . should . have . length ( 2 ) ;
181+ response . set . secondCall . args [ 0 ] . should . equal ( 'Pragma' ) ;
182+ response . set . secondCall . args [ 1 ] . should . equal ( 'no-cache' ) ;
183+ } ) ;
184+ } ) ;
185+ describe ( 'updateErrorResponse' , ( ) => {
186+ it ( 'updates the response with error information' , ( ) => {
187+ const model = Model . from ( {
188+ getClient : sinon . stub ( ) . returns ( { grants : [ 'password' ] } ) ,
189+ saveToken : ( ) => {
190+ } ,
191+ revokeToken : ( ) => {
192+ }
193+ } ) ;
194+ const handler = new RevokeHandler ( { model : model } ) ;
195+ const response = {
196+ set : sinon . spy ( )
197+ } ;
198+
199+ handler . updateErrorResponse ( response , new InvalidRequestError ( 'Invalid request 123' ) ) ;
200+ response . body . should . deep . equal ( {
201+ error : 'invalid_request' ,
202+ error_description : 'Invalid request 123'
203+ } ) ;
204+ response . status . should . equal ( 400 ) ;
205+ } ) ;
206+ } ) ;
207+ describe ( 'getToken' , ( ) => {
208+ const model = Model . from ( {
209+ getClient : ( ) => { } ,
210+ saveToken : ( ) => { } ,
211+ revokeToken : ( ) => { }
212+ } ) ;
213+ const handler = new RevokeHandler ( { model} ) ;
214+
215+ it ( 'throws an error if the token is missing' ) ;
216+ it ( 'throws an error if the token is in an invalid format' , ( ) => {
217+ const missing = [ false , null , undefined , '' , 0 ] ;
218+ missing . forEach ( ( token ) => {
219+ try {
220+ handler . getToken ( { body : { token } } ) ;
221+ should . fail ( ) ;
222+ } catch ( e ) {
223+ e . should . be . instanceOf ( InvalidRequestError ) ;
224+ e . message . should . equal ( 'Missing parameter: `token`' ) ;
225+ }
226+ } ) ;
227+ const invalid = [ '123❤️45' , { } , [ ] , true ] ;
228+ invalid . forEach ( ( token ) => {
229+ try {
230+ handler . getToken ( { body : { token } } ) ;
231+ should . fail ( ) ;
232+ } catch ( e ) {
233+ e . should . be . instanceOf ( InvalidRequestError ) ;
234+ e . message . should . equal ( 'Invalid parameter: `token`' ) ;
235+ }
236+ } ) ;
237+ } ) ;
238+ it ( 'returns the token and token_type_hint from the request body, if defined and valid' , ( ) => {
239+
240+ const token = '123445' ;
241+ const invalid = [ undefined , null , 'invalid' , 'refresh-token' , 'access-token' ] ;
242+ invalid . forEach ( ( tokenTypeHint ) => {
243+ const result = handler . getToken ( { body : { token, token_type_hing : tokenTypeHint } } ) ;
244+ result . token . should . equal ( token ) ;
245+ should . equal ( result . tokenTypeHint , undefined ) ;
246+ } ) ;
247+
248+ const valid = [ 'access_token' , 'refresh_token' ] ;
249+ valid . forEach ( ( tokenTypeHint ) => {
250+ const result = handler . getToken ( { body : { token, token_type_hint : tokenTypeHint } } ) ;
251+ result . token . should . equal ( token ) ;
252+ result . tokenTypeHint . should . equal ( tokenTypeHint ) ;
253+ } ) ;
254+ } ) ;
255+ } ) ;
256+ describe ( 'revokeToken' , ( ) => {
257+ it ( 'it ignores invalid token_type_hint values' ) ;
258+ it ( 'it revokes access tokens when token_type_hint is access_token' ) ;
259+ it ( 'it revokes refresh tokens when token_type_hint is refresh_token' ) ;
260+ it ( 'it revokes tokens without a token_type_hint' ) ;
261+ it ( 'it does not revoke tokens belonging to other clients' ) ;
262+ it ( 'it does not throw an error if the token to be revoked is not found' ) ;
263+ } ) ;
264+ } ) ;
0 commit comments