Skip to content

Commit 40630ce

Browse files
committed
* parseToken( token ) now accepts a token of your choice to work with in the request or it will continue to discover it if not passed.
* Added new JWT Service method: `invalidateAll()` which invalidates all tokens in the token storage * Added the new event: `cbSecurity_onJWTInvalidateAllTokens` that fires once all tokens in the storage are cleared * Added storage of the authenticated user into the `prc` scope when using `attempt()` to be consistent with API calls ### Fixed * Spelling corrections on the readme * Added full var scoping for `cbsecurity` in JWTService calls
1 parent b65dd5f commit 40630ce

File tree

6 files changed

+103
-32
lines changed

6 files changed

+103
-32
lines changed

.markdownlint.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"line-length": false,
3+
"single-h1": false,
4+
"no-hard-tabs" : false,
5+
"fenced-code-language" : false,
6+
"no-bare-urls" : false,
7+
"first-line-h1": false,
8+
"no-multiple-blanks": {
9+
"maximum": 2
10+
},
11+
"no-duplicate-header" : {
12+
"siblings_only" : true
13+
}
14+
}

ModuleConfig.cfc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ component {
119119
"cbSecurity_onJWTInvalidClaims",
120120
"cbSecurity_onJWTExpiration",
121121
"cbSecurity_onJWTStorageRejection",
122-
"cbSecurity_onJWTValidParsing"
122+
"cbSecurity_onJWTValidParsing",
123+
"cbSecurity_onJWTInvalidateAllTokens"
123124
]
124125
};
125126

changelog.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
----
99

10-
## [2.8.0] =>
10+
## [2.8.0] => 2020-NOV-09
1111

1212
### Added
1313

14+
* `parseToken( token )` now accepts a token of your choice to work with in the request or it will continue to discover it if not passed.
15+
* Added new JWT Service method: `invalidateAll()` which invalidates all tokens in the token storage
16+
* Added the new event: `cbSecurity_onJWTInvalidateAllTokens` that fires once all tokens in the storage are cleared
1417
* Added storage of the authenticated user into the `prc` scope when using `attempt()` to be consistent with API calls
1518

19+
### Fixed
20+
21+
* Spelling corrections on the readme
22+
* Added full var scoping for `cbsecurity` in JWTService calls
23+
1624
----
1725

1826
## [2.7.0] => 2020-SEP-14

models/jwt/JwtService.cfc

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ component accessors="true" singleton {
138138
){
139139
// Authenticate via the auth service wired up
140140
// If it fails an exception is thrown
141-
var oUser = cbSecurity
141+
var oUser = variables.cbSecurity
142142
.getAuthService()
143143
.authenticate( arguments.username, arguments.password );
144144

@@ -159,14 +159,14 @@ component accessors="true" singleton {
159159
*/
160160
function logout(){
161161
invalidate( this.getToken() );
162-
cbSecurity.getAuthService().logout();
162+
variables.cbSecurity.getAuthService().logout();
163163
}
164164

165165
/**
166166
* Shortcut function to our authentication services to check if we are logged in
167167
*/
168168
boolean function isLoggedIn(){
169-
return cbSecurity.getAuthService().isLoggedIn();
169+
return variables.cbSecurity.getAuthService().isLoggedIn();
170170
}
171171

172172
/**
@@ -313,6 +313,29 @@ component accessors="true" singleton {
313313
return results;
314314
}
315315

316+
/**
317+
* Invalidates all tokens in the connected storage provider
318+
*
319+
* @async Run the clearing asynchronously or not, default is false
320+
*/
321+
JwtService function invalidateAll( boolean async=false ){
322+
if ( variables.log.canInfo() ) {
323+
variables.log.info( "Token invalidation request issued for all tokens" );
324+
}
325+
326+
// Clear all via storage
327+
getTokenStorage().clearAll( arguments.async );
328+
329+
// Announce the token invalidation
330+
variables.interceptorService.processState( "cbSecurity_onJWTInvalidateAllTokens" );
331+
332+
if ( variables.log.canInfo() ) {
333+
variables.log.info( "All tokens cleared via token storage clear all" );
334+
}
335+
336+
return this;
337+
}
338+
316339
/**
317340
* Verifies if the passed in token exists in the storage provider
318341
*
@@ -328,33 +351,34 @@ component accessors="true" singleton {
328351

329352
/**
330353
* Try's to get a jwt token from the authorization header or the custom header
331-
* defined in the configuration. If it is a valid token and it decodes we will then
332-
* continue to validat the subject it represents. Once those are satisfied, then it will
354+
* defined in the configuration or passed in by you. If it is a valid token and it decodes we will then
355+
* continue to validate the subject it represents. Once those are satisfied, then it will
333356
* store it in the `prc` as `prc.jwt_token` and the payload as `prc.jwt_payload`.
334357
*
358+
* @token The token to parse and validate, if not passed we call the discoverToken() method for you.
359+
*
335360
* @throws TokenExpiredException If the token has expired or no longer in the storage (invalidated)
336361
* @throws TokenInvalidException If the token doesn't verify decoding
337362
* @throws TokenNotFoundException If the token cannot be found in the headers
338363
*
339364
* @returns The payload for convenience
340365
*/
341-
struct function parseToken(){
342-
var jwtToken = discoverToken();
366+
struct function parseToken( string token = discoverToken() ){
343367

344368
// Did we find an incoming token
345-
if ( !len( jwtToken ) ) {
369+
if ( !len( arguments.token ) ) {
346370
if ( variables.log.canDebug() ) {
347-
variables.log.debug( "Token not found anywhere" );
371+
variables.log.debug( "Token empty or not found anywhere (headers, url, form)" );
348372
}
349373

350374
throw(
351-
message = "Token not found in authorization header or the custom header or the request collection",
375+
message = "Token not found in authorization header or the custom header or the request collection or not passed in",
352376
type = "TokenNotFoundException"
353377
);
354378
}
355379

356380
// Decode it
357-
var decodedToken = decode( jwtToken );
381+
var decodedToken = decode( arguments.token );
358382
var decodedClaims = decodedToken.keyArray();
359383

360384
// Verify the required claims
@@ -375,7 +399,7 @@ component accessors="true" singleton {
375399
variables.interceptorService.processState(
376400
"cbSecurity_onJWTInvalidClaims",
377401
{
378-
token : jwtToken,
402+
token : arguments.token,
379403
payload : decodedToken
380404
}
381405
);
@@ -398,7 +422,7 @@ component accessors="true" singleton {
398422
variables.interceptorService.processState(
399423
"cbSecurity_onJWTExpiration",
400424
{
401-
token : jwtToken,
425+
token : arguments.token,
402426
payload : decodedToken
403427
}
404428
);
@@ -416,7 +440,7 @@ component accessors="true" singleton {
416440
variables.interceptorService.processState(
417441
"cbSecurity_onJWTStorageRejection",
418442
{
419-
token : jwtToken,
443+
token : arguments.token,
420444
payload : decodedToken
421445
}
422446
);
@@ -436,22 +460,22 @@ component accessors="true" singleton {
436460
);
437461
}
438462

439-
// Store it
463+
// Store it on the PRC scope values
440464
variables.requestService
441465
.getContext()
442-
.setPrivateValue( "jwt_token", jwtToken )
466+
.setPrivateValue( "jwt_token", arguments.token )
443467
.setPrivateValue( "jwt_payload", decodedToken );
444468

445469
// Announce the valid parsing
446470
variables.interceptorService.processState(
447471
"cbSecurity_onJWTValidParsing",
448472
{
449-
token : jwtToken,
473+
token : arguments.token,
450474
payload : decodedToken
451475
}
452476
);
453477

454-
// Authenticate the payload
478+
// Authenticate the payload, because a token MUST be valid before usage
455479
authenticate();
456480

457481
// Return it
@@ -669,7 +693,12 @@ component accessors="true" singleton {
669693
/****************************** PRIVATE ******************************/
670694

671695
/**
672-
* Try to discover the jwt token from many incoming resources
696+
* Try to discover the jwt token from many incoming resources:
697+
* - The custom auth header: x-auth-token
698+
* - URL/FORM: x-auth-token
699+
* - Authorization Header
700+
*
701+
* @return The discovered token or an empty string
673702
*/
674703
private string function discoverToken(){
675704
var event = variables.requestService.getContext();

test-harness/tests/Application.cfc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ component {
3232

3333
// request start
3434
public boolean function onRequestStart( String targetPage ){
35-
return true;
36-
}
3735

38-
function onRequestEnd(){
39-
structDelete( application, "wirebox" );
36+
// Cleanup
37+
if( !isNull( application.cbController ) ){
38+
application.cbController.getLoaderService().processShutdown();
39+
}
4040
structDelete( application, "cbController" );
41+
structDelete( application, "wirebox" );
42+
43+
return true;
4144
}
4245

43-
}
46+
}

test-harness/tests/specs/integration/JWTSpec.cfc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
2020
beforeEach( function(currentSpec){
2121
// Setup as a new ColdBox request for this suite, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST.
2222
setup();
23+
// Get the Service
24+
variables.jwtService = getInstance( "jwtService@cbSecurity" );
2325
} );
2426

2527
given( "no jwt token and accessing a secure api call", function(){
@@ -87,11 +89,11 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
8789
} );
8890
} );
8991

90-
given( "an valid jwt token but it is not in the storage", function(){
92+
given( "a valid jwt token but it is not in the storage", function(){
9193
then( "it should block with no authorization", function(){
92-
var thisToken = getInstance( "jwtService@cbSecurity" ).attempt( "test", "test" );
94+
var thisToken = variables.jwtService.attempt( "test", "test" );
9395

94-
getInstance( "jwtService@cbSecurity" )
96+
variables.jwtService
9597
.getTokenStorage()
9698
.clearAll();
9799
getRequestContext().setValue( "x-auth-token", thisToken );
@@ -110,7 +112,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
110112
var thisToken = getInvalidUserToken();
111113
getRequestContext().setValue( "x-auth-token", thisToken.token );
112114

113-
getInstance( "jwtService@cbSecurity" )
115+
variables.jwtService
114116
.getTokenStorage()
115117
.set(
116118
key = thisToken.payload.jti,
@@ -130,20 +132,34 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
130132

131133
given( "a valid jwt token in all senses", function(){
132134
then( "it should allow the call", function(){
133-
var thisToken = getInstance( "jwtService@cbSecurity" ).attempt( "test", "test" );
135+
var thisToken = variables.jwtService.attempt( "test", "test" );
134136
getRequestContext().setValue( "x-auth-token", thisToken );
135137

136138
var event = execute( route = "/api/secure", renderResults = true );
137139
expect( event.getCurrentEvent() ).toBe( "api:secure.index" );
138140
} );
139141
} );
142+
143+
144+
story( "I want to invalidate all tokens in the storage", function(){
145+
given( "a valid jwt token and a invalidate all is issued", function(){
146+
then( "the storage should be empty", function(){
147+
var thisToken = variables.jwtService.attempt( "test", "test" );
148+
expect( variables.jwtService.getTokenStorage().size() ).toBeGT( 0 );
149+
150+
variables.jwtService.invalidateAll();
151+
expect( variables.jwtService.getTokenStorage().size() ).toBe( 0 );
152+
} );
153+
} );
154+
} );
155+
140156
} );
141157
}
142158

143159
private function getInvalidUserToken(){
144160
var timestamp = now();
145161
var userId = 123;
146-
var service = getInstance( "jwtService@cbsecurity" );
162+
var service = variables.jwtService;
147163
var payload = {
148164
// Issuing authority
149165
"iss" : service.getSettings().jwt.issuer,

0 commit comments

Comments
 (0)