@@ -62,6 +62,7 @@ function AuthorizeHandler(options) {
6262 this . allowEmptyState = options . allowEmptyState ;
6363 this . authenticateHandler = options . authenticateHandler || new AuthenticateHandler ( options ) ;
6464 this . authorizationCodeLifetime = options . authorizationCodeLifetime ;
65+ this . PKCEEnabled = options . PKCEEnabled ;
6566 this . model = options . model ;
6667}
6768
@@ -95,18 +96,25 @@ AuthorizeHandler.prototype.handle = function(request, response) {
9596 var scope ;
9697 var state ;
9798 var ResponseType ;
99+ var codeChallenge ;
100+ var codeChallengeMethod ;
98101
99102 return Promise . bind ( this )
100- . then ( function ( ) {
103+ . then ( function ( ) {
101104 scope = this . getScope ( request ) ;
105+ codeChallenge = this . getCodeChallenge ( request , client ) ;
102106
103- return this . generateAuthorizationCode ( client , user , scope ) ;
104- } )
107+ if ( codeChallenge ) {
108+ codeChallengeMethod = this . getCodeChallengeMethod ( request ) ;
109+ }
110+ 111+ return this . generateAuthorizationCode ( client , user , scope ) ;
112+ } )
105113 . then ( function ( authorizationCode ) {
106114 state = this . getState ( request ) ;
107115 ResponseType = this . getResponseType ( request ) ;
108116
109- return this . saveAuthorizationCode ( authorizationCode , expiresAt , scope , client , uri , user ) ;
117+ return this . saveAuthorizationCode ( authorizationCode , expiresAt , scope , client , uri , user , codeChallenge , codeChallengeMethod ) ;
110118 } )
111119 . then ( function ( code ) {
112120 var responseType = new ResponseType ( code . authorizationCode ) ;
@@ -135,11 +143,31 @@ AuthorizeHandler.prototype.handle = function(request, response) {
135143
136144AuthorizeHandler . prototype . generateAuthorizationCode = function ( client , user , scope ) {
137145 if ( this . model . generateAuthorizationCode ) {
138- return promisify ( this . model . generateAuthorizationCode ) . call ( this . model , client , user , scope ) ;
146+ return promisify ( this . model . generateAuthorizationCode , 3 ) . call ( this . model , client , user , scope ) ;
139147 }
140148 return tokenUtil . generateRandomToken ( ) ;
141149} ;
142150
151+ AuthorizeHandler . prototype . getCodeChallenge = function ( request , client ) {
152+ var codeChallenge = request . body . code_challenge || request . query . code_challenge ;
153+ 154+ if ( this . PKCEEnabled && client . isPublic && _ . isEmpty ( codeChallenge ) ) {
155+ throw new InvalidRequestError ( 'Missing parameter: `code_challenge`. Public clients must include a code_challenge' ) ;
156+ }
157+ 158+ return codeChallenge ;
159+ } ;
160+ 161+ AuthorizeHandler . prototype . getCodeChallengeMethod = function ( request ) {
162+ var codeChallengeMethod = request . body . code_challenge_method || request . query . code_challenge_method || 'plain' ;
163+ 164+ if ( ! _ . includes ( [ 'S256' , 'plain' ] , codeChallengeMethod ) ) {
165+ throw new InvalidRequestError ( 'Invalid parameter: `code_challenge_method`' ) ;
166+ }
167+ 168+ return codeChallengeMethod ;
169+ } ;
170+ 143171/**
144172 * Get authorization code lifetime.
145173 */
@@ -172,6 +200,7 @@ AuthorizeHandler.prototype.getClient = function(request) {
172200 throw new InvalidRequestError ( 'Invalid request: `redirect_uri` is not a valid URI' ) ;
173201 }
174202 return promisify ( this . model . getClient , 2 ) . call ( this . model , clientId , null )
203+ . bind ( this )
175204 . then ( function ( client ) {
176205 if ( ! client ) {
177206 throw new InvalidClientError ( 'Invalid client: client credentials are invalid' ) ;
@@ -192,6 +221,16 @@ AuthorizeHandler.prototype.getClient = function(request) {
192221 if ( redirectUri && ! _ . includes ( client . redirectUris , redirectUri ) ) {
193222 throw new InvalidClientError ( 'Invalid client: `redirect_uri` does not match client value' ) ;
194223 }
224+ 225+ if ( this . PKCEEnabled ) {
226+ if ( client . isPublic === undefined ) {
227+ throw new InvalidClientError ( 'Invalid client: missing client `isPublic`' ) ;
228+ }
229+ 230+ if ( typeof client . isPublic !== 'boolean' ) {
231+ throw new InvalidClientError ( 'Invalid client: `isPublic` must be a boolean' ) ;
232+ }
233+ }
195234 return client ;
196235 } ) ;
197236} ;
@@ -257,12 +296,14 @@ AuthorizeHandler.prototype.getRedirectUri = function(request, client) {
257296 * Save authorization code.
258297 */
259298
260- AuthorizeHandler . prototype . saveAuthorizationCode = function ( authorizationCode , expiresAt , scope , client , redirectUri , user ) {
299+ AuthorizeHandler . prototype . saveAuthorizationCode = function ( authorizationCode , expiresAt , scope , client , redirectUri , user , codeChallenge , codeChallengeMethod ) {
261300 var code = {
262301 authorizationCode : authorizationCode ,
263302 expiresAt : expiresAt ,
264303 redirectUri : redirectUri ,
265- scope : scope
304+ scope : scope ,
305+ codeChallenge : codeChallenge ,
306+ codeChallengeMethod : codeChallengeMethod
266307 } ;
267308 return promisify ( this . model . saveAuthorizationCode , 3 ) . call ( this . model , code , client , user ) ;
268309} ;
0 commit comments