Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
breaking: use grantManager to parse token in onSubscriptionConnect
Browse files Browse the repository at this point in the history
  • Loading branch information
darahayes committed Jan 6, 2020
1 parent d2cf4ea commit f41812c
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 90 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"examples:seed": "node scripts/initKeycloak.js"
},
"dependencies": {
"apollo-server-express": "2.9.14",
"apollo-server-express": "2.9.15",
"graphql-tools": "4.0.6",
"keycloak-connect": "8.0.1"
},
Expand Down
6 changes: 3 additions & 3 deletions src/KeycloakContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ export class KeycloakContext extends KeycloakContextBase implements AuthContextP
export class KeycloakSubscriptionContext extends KeycloakContextBase {
/**
*
* @param token a keycloak token object
* @param grant a keycloak Grant object
*/
constructor(token: Keycloak.Token) {
super(token)
constructor(grant: Keycloak.Grant) {
super(grant.access_token)
}
}
20 changes: 11 additions & 9 deletions src/KeycloakSubscriptionHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Keycloak from './KeycloakTypings'
import { Token } from './KeycloakToken'
import { KeycloakSubscriptionHandlerOptions } from './api'

/**
Expand Down Expand Up @@ -55,7 +54,7 @@ export class KeycloakSubscriptionHandler {
* @param webSocket
* @param context
*/
public async onSubscriptionConnect(connectionParams: any, webSocket: any, context: any): Promise<Keycloak.Token | undefined | Error> {
public async onSubscriptionConnect(connectionParams: any, webSocket: any, context: any): Promise<Keycloak.Grant | undefined> {
if (!connectionParams || typeof connectionParams !== 'object') {
if (this.protect === true) {
throw new Error('Access Denied - missing connection parameters for Authentication')
Expand All @@ -72,20 +71,23 @@ export class KeycloakSubscriptionHandler {
}
return
}
const token = this.getBearerTokenFromHeader(header, this.keycloak.config.clientId)
try {
await this.keycloak.grantManager.validateToken(token, 'Bearer')
//@ts-ignore
return token
const accessToken = this.getAccessTokenFromHeader(header)
const grant = await this.keycloak.grantManager.createGrant(accessToken)
return grant
} catch (e) {
throw new Error(`Access Denied - ${e}`)
}
}

private getBearerTokenFromHeader(header: any, clientId?: string) {
private getAccessTokenFromHeader(header: any) {
if (header && typeof header === 'string' && (header.indexOf('bearer ') === 0 || header.indexOf('Bearer ') === 0)) {
const token = header.substring(7)
return new Token(token, clientId)
const tokenString = header.substring(7)
return {
access_token: tokenString
};
} else {
throw new Error('Invalid Authorization field in connection params. Must be in the format "Authorization": "Bearer <token string>"')
}
}
}
2 changes: 1 addition & 1 deletion test/KeycloakContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('KeycloakSubscriptionContext accessToken is the access_token in req.kauth',
}
} as Keycloak.Token

const provider = new KeycloakSubscriptionContext(token)
const provider = new KeycloakSubscriptionContext({ access_token: token } as Keycloak.Grant)
t.deepEqual(provider.accessToken, token)
})

Expand Down
135 changes: 59 additions & 76 deletions test/KeycloakSubscriptionHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import test from 'ava'
import Keycloak from '../src/KeycloakTypings'

import { KeycloakSubscriptionHandler } from '../src/KeycloakSubscriptionHandler'
import { Token } from '../src/KeycloakToken';
import { Token } from './utils/KeycloakToken';

const TEST_CLIENT_ID = 'voyager-testing'

test('onSubscriptionConnect throws if no keycloak provided', async t => {
t.throws(() => {
Expand All @@ -13,14 +15,9 @@ test('onSubscriptionConnect throws if no keycloak provided', async t => {

test('onSubscriptionConnect throws if no connectionParams Provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -34,14 +31,9 @@ test('onSubscriptionConnect throws if no connectionParams Provided', async t =>

test('onSubscriptionConnect throws if no connectionParams is not an object', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -56,14 +48,9 @@ test('onSubscriptionConnect throws if no connectionParams is not an object', asy

test('onSubscriptionConnect throws if no Auth provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -76,16 +63,28 @@ test('onSubscriptionConnect throws if no Auth provided', async t => {
}, 'Access Denied - missing Authorization field in connection parameters')
})

test('onSubscriptionConnect throws if no "Authorization" field is not formed correctly', async t => {
const stubKeycloak = {
grantManager: {
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak

const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: '1234' }

await t.throwsAsync(async () => {
await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
}, 'Access Denied - Error: Invalid Authorization field in connection params. Must be in the format "Authorization": "Bearer <token string>"')
})

test('onSubscriptionConnect returns a token Object if the keycloak library considers it valid', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -95,20 +94,19 @@ test('onSubscriptionConnect returns a token Object if the keycloak library consi
const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: tokenString }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(token instanceof Token)
const grant = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(grant)
//@ts-ignore
t.truthy(grant.access_token)
//@ts-ignore
t.truthy(grant.access_token.content)
})

test('onSubscriptionConnect can also parse the token with lowercase \'bearer\'', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -118,20 +116,21 @@ test('onSubscriptionConnect can also parse the token with lowercase \'bearer\'',
const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: tokenString }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(token instanceof Token)
const grant = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})

t.truthy(grant)
//@ts-ignore
t.truthy(grant.access_token)
//@ts-ignore
t.truthy(grant.access_token.content)
})

test('the token object will have hasRole, hasRealmRole and hasPermissions if the', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
console.log(`createGant called with token String ${tokenString}`)
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -143,19 +142,18 @@ test('the token object will have hasRole, hasRealmRole and hasPermissions if the
const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: tokenString, clientId: 'voyager-testing' }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {}) as Token
t.truthy(token instanceof Token)
t.truthy(token.hasRole('tester'))
const grant = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {}) as Keycloak.Grant
t.truthy(grant.access_token)
console.log(grant.access_token)
//@ts-ignore
t.truthy(grant.access_token.hasRole('tester'))
})

test('If the keycloak token validation fails, then onSubscriptionConnect will throw', async t => {
test('If grant creation fails then onSubscriptionConnect will throw', async t => {
const errorMsg = 'token is invalid'
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
createGrant: (token: any) => {
return new Promise((resolve, reject) => {
reject(new Error(errorMsg))
})
Expand All @@ -177,14 +175,9 @@ test('If the keycloak token validation fails, then onSubscriptionConnect will th

test('onSubscriptionConnect with {protect: false} does not throw if no connectionParams Provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -198,14 +191,9 @@ test('onSubscriptionConnect with {protect: false} does not throw if no connectio

test('onSubscriptionConnect with {protect: false} does not throw if connectionParams is not an object', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -220,14 +208,9 @@ test('onSubscriptionConnect with {protect: false} does not throw if connectionPa

test('onSubscriptionConnect with {protect: false} does not throw if no Auth provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand Down
File renamed without changes.

0 comments on commit f41812c

Please sign in to comment.